Game 20
reverseme: ELF 32-bit LSB executable, Intel i386, statically linked, not strippedSource
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(需要 字节)
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 的链式效应实现栈迁移:
auth的leave恢复了被篡改的 ebp 给mainmain结束时执行leave(mov esp, ebp; pop ebp),esp 被迁移到&inputmain执行ret,从&input + 4弹出值作为 EIP
Payload 结构
12 字节 = 3 个 DWORD(little-endian):
| Offset | 值 | 作用 |
|---|---|---|
| 0-3 | 0xDEADBEEF | main 的 pop ebp 读取,同时满足 correct() 中 input == 0xDEADBEEF |
| 4-7 | &correct (0x0804925f) | main 的 ret 弹入 EIP |
| 8-11 | &input (0x0811c9ec) | 覆盖 auth 的 saved ebp,触发栈迁移 |
Exploit
获取地址(statically linked,地址固定):
$ objdump -t ./suninatas | grep -E ' (correct|input)$'0811c9ec g O .bss 0000000d input0804925f g F .text 00000031 correct生成 payload:
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
$ ./suninatasAuthenticate : ****************hash : d932bf2657c20fd638787756d680c95cCongratulation! you are good!776t3l+SBAjsyREI