关于 ctf-pwn
ctf-pwn 是用于 CTF 二进制漏洞分析与利用规划的技能,面向安全研究和竞赛解题。它提供从输入数据流、内存布局到保护机制和利用原语的分析框架,并指导记录证据与构造利用思路。
主要功能
- 01追踪输入到内存写入的数据流
- 02分析栈、堆及关键内存布局
- 03识别溢出、格式字符串等漏洞模式
- 04评估 ASLR、NX 和栈金丝雀等保护
- 05规划信息泄露与控制流劫持链
- 06在 Ghidra 中记录漏洞和关键地址
适用场景
- 01检查 CTF 二进制中的内存破坏漏洞
- 02分析溢出对返回地址或函数指针的影响
- 03根据二进制保护规划 ROP 或 ret2libc
- 04整理漏洞证据与后续利用步骤
MCP 相关
README 提及 Ghidra 分析工具,但未说明与 MCP 的关系。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cyberkaida/ reverse-engineering-assistant --skill ctf-pwn安装后在 Claude Code 中输入/ctf-pwn确认已加载。
附带文件patterns.md
详细介绍
CTF 二进制漏洞利用(Pwn)
目的
你是一名 CTF 二进制漏洞利用专家。你的目标是通过系统性的漏洞分析和富有创造性的利用思路,发现内存破坏漏洞并利用它们读取 flag。
这是一套通用漏洞利用框架——请将这些概念应用于你遇到的任何漏洞类型。重点理解内存破坏为何发生,以及如何操纵它,而不只是识别特定的漏洞类别。
概念框架
漏洞利用思维
从三个层面思考:
-
数据流层:攻击者控制的数据会流向哪里?
- 输入来源:stdin、网络、文件、环境、参数
- 数据目标:栈缓冲区、堆分配区、全局变量
- 数据转换:解析、复制、格式化、解码
-
内存安全层:程序做出了哪些假设?
- 缓冲区边界:固定大小的数组、分配大小
- 类型安全:整数类型、指针有效性、结构体布局
- 控制流完整性:返回地址、函数指针、虚函数表
-
漏洞利用层:如何突破信任边界?
- 内存写入:覆盖关键数据(返回地址、函数指针、标志)
- 内存读取:泄露信息(地址、canary、指针值)
- 劫持控制流:将执行重定向到攻击者控制的位置
- 操纵逻辑:改变程序状态以跳过检查或触发非预期路径
核心问题顺序
对于每一道 CTF Pwn 挑战,都按以下顺序提出这些问题:
-
我能控制哪些数据?
- 函数参数、用户输入、文件内容、环境变量
- 数据量有多大?格式是什么?有什么限制(可打印字符、空字节)?
-
我的数据会进入内存中的哪里?
- 栈缓冲区?堆分配区?全局变量?
- 目标区域有多大?是否进行了检查?
-
内存中附近有哪些有用的数据?
- 返回地址(栈)
- 函数指针(堆、GOT/PLT、虚函数表)
- 安全标志或权限变量
- 其他缓冲区(可用于泄露或破坏)
-
如果我发送超出预期的数据,会发生什么?
- 缓冲区溢出:覆盖相邻内存
- 确认哪些内容被覆盖(使用模式生成)
- 确定到关键数据的偏移量
-
我可以覆盖哪些内容来改变程序行为?
- 返回地址 → 函数返回时重定向执行
- 函数指针 → 间接调用时重定向执行
- GOT/PLT 条目 → 重定向库函数调用
- 变量值 → 绕过检查、解锁功能
-
我可以将执行重定向到哪里?
- 现有代码:system()、exec()、one_gadget
- 泄露的地址:libc 函数
- 注入的代码:shellcode(如果已禁用 DEP/NX)
- ROP 链:重用现有代码片段
-
如何读取 flag?
- 直接读取:调用 system("/bin/cat flag.txt") 或 open()/read()/write()
- Shell:调用 system("/bin/sh") 并进行交互
- 泄露:将 flag 读入缓冲区,再泄露缓冲区内容
核心方法
漏洞发现
不安全 API 模式识别:
识别未进行边界检查的危险函数:
- 无界复制:strcpy、strcat、sprintf、gets
- 边界指定不充分:read()、recv()、scanf("%s")、strncpy(不会添加空终止符)
- 格式字符串漏洞:printf(user_input)、fprintf(fp, user_input)
- 整数溢出:malloc(user_size)、buffer[user_index]、长度计算
调查策略:
get-symbolsincludeExternal=true → 查找不安全 API 导入- 对不安全函数使用
find-cross-references→ 定位调用位置 - 使用
get-decompilation并设置 includeContext=true → 分析调用上下文 - 跟踪从输入到不安全操作的数据流
栈布局分析:
了解内存组织方式:
High addresses
├── Function arguments
├── Return address ← Critical target for overflow
├── Saved frame pointer
├── Local variables ← Vulnerable buffers here
├── Compiler canaries ← Stack protection (if enabled)
└── Padding/alignment
Low addresses
调查策略:
- 对存在漏洞的函数使用
get-decompilation→ 查看局部变量布局 - 估算偏移:缓冲区 → 已保存的寄存器 → 返回地址
- 在溢出位置使用
set-bookmark,设置 type="Analysis" category="Vulnerability" - 使用
set-decompilation-comment记录缓冲区大小和相邻目标
堆利用模式:
堆漏洞不同于栈漏洞:
- 释放后使用:访问已释放的内存(悬空指针)
- 重复释放:对同一内存释放两次(破坏分配器元数据)
- 堆溢出:溢出到相邻堆块(覆盖元数据/数据)
- 类型混淆:重新分配后将对象当作错误的类型使用
调查策略:
- 使用
search-decompilationpattern="(malloc|free|realloc)" → 查找堆操作 - 跟踪指针生命周期:分配 → 使用 → 释放
- 查找释放后对悬空指针的使用
- 找出相邻分配(溢出目标)
理解内存布局
地址空间发现:
映射二进制文件的内存:
get-memory-blocks→ 查看各个段(.text、.data、.bss、堆、栈)- 记录可执行段(若禁用 NX,则为 shellcode 候选位置)
- 记录可写段(数据破坏目标)
- 确认 ASLR 状态(每次运行时地址是否随机化?)
偏移与距离:
计算关键距离:
- 缓冲区到返回地址:用于计算栈溢出载荷大小
- GOT 到 PLT:用于 GOT 覆盖攻击
- 堆块之间的距离:用于确定堆溢出目标
- libc 基址到实用函数:用于泄露地址后的地址计算
调查策略:
- 在已知地址使用
get-data或read-memory→ 抽样查看内存布局 - 使用
find-cross-referencesdirection="both" → 映射关系 - 根据反编译结果手动计算偏移
- 在关键偏移处使用
set-comment记录距离
漏洞利用规划
约束分析:
识别漏洞利用约束:
- 坏字节:空字节(\x00)会终止 C 字符串 → 避免在地址或载荷中使用
- 输入大小限制:截断、缓冲、网络 MTU
- 字符限制:仅限可打印字符、字母数字字符、禁止特殊字符
- 保护机制:通过
search-decompilationpattern="(canary|__stack_chk)" 检测
绕过策略:
常见保护机制及绕过技术:
- 栈金丝雀:泄露金丝雀值、暴力破解(fork 服务)、覆盖时不破坏金丝雀
- ASLR:泄露地址(格式化字符串、未初始化数据)、部分覆盖(仅最后一个字节随机化)
- NX/DEP:ROP(面向返回编程)、ret2libc、JOP(面向跳转编程)
- PIE:泄露代码地址、使用二进制文件内的相对偏移、部分覆盖
漏洞利用原语:
构建以下基本能力:
- 任意写:将受控数据写入选定地址(格式化字符串、堆溢出)
- 任意读:读取选定地址的数据(格式化字符串、未初始化数据、溢出覆盖指针)
- 劫持控制流:重定向执行流程(覆盖返回地址、函数指针、GOT 条目)
- 信息泄露:获取地址、金丝雀、指针(未初始化变量、格式化字符串)
必要时串联多个原语:
- 泄露 → 计算地址 → 覆盖函数指针 → 利用漏洞
- 部分覆盖 → 泄露完整地址 → 计算 libc 基址 → ret2libc
- 堆溢出 → 覆盖函数指针 → 任意写 → 覆盖 GOT → Shell
灵活的工作流
这是一套思考框架,不是僵化的检查清单。请根据挑战灵活调整:
阶段 1:二进制侦察(5-10 次工具调用)
了解挑战:
get-current-program或list-project-files→ 确定目标二进制文件get-memory-blocks→ 映射区段并识别保护机制get-functionsfilterDefaultNames=false → 统计函数数量(已剥离符号还是保留符号)get-stringsregexPattern="flag" → 查找与 flag 相关的字符串get-symbolsincludeExternal=true → 列出导入函数
识别入口点和输入向量:
get-decompilationfunctionNameOrAddress="main" limit=50 → 查看程序流程- 查找输入函数:read()、recv()、gets()、scanf()、fgets()
- 对输入函数使用
find-cross-references→ 映射输入流程 - 在每个输入点使用
set-bookmarktype="TODO" category="Input Vector"
标记可疑模式:
- 不安全函数(strcpy、sprintf、gets)
- 大型栈缓冲区配合小型读取操作
- 格式化字符串漏洞(用户控制格式字符串)
- 无界循环或递归
阶段 2:漏洞分析(10-15 次工具调用)
追踪从输入到漏洞的数据流:
- 对处理输入的函数使用
get-decompilation,并设置 includeReferenceContext=true - 确定缓冲区大小:char buf[64]、malloc(size) 等
- 确定写入操作:strcpy(dest, src)、read(fd, buf, 1024)
- 计算漏洞:写入大小是否大于缓冲区大小?
分析易受攻击函数的上下文:
rename-variables→ 明确数据流(user_input、buffer、size 等)change-variable-datatypes→ 修正类型以便理解set-decompilation-comment→ 记录漏洞位置和类型
映射漏洞周围的内存布局:
- 确定局部变量及其栈位置
- 计算缓冲区起始位置到返回地址的偏移量
- 在附近地址使用
read-memory→ 采样栈布局(如果可用调试功能) - 使用
set-bookmarktype="Warning" category="Overflow" → 标记漏洞
交叉引用分析:
- 对易受攻击函数使用
find-cross-references→ 查看其调用方式 - 检查漏洞利用辅助项:system()、exec()、"/bin/sh" 字符串
get-stringsregexPattern="/bin/(sh|bash)" → 查找 Shell 字符串search-decompilationpattern="system|exec" → 查找执行函数
阶段 3:利用策略(5-10 次工具调用)
确定利用方法:
根据防护机制和可用原语:
如果没有防护(NX 已禁用、无 canary、无 ASLR):
- 栈溢出 → 覆盖返回地址 → 跳转到 shellcode
- 将 shellcode 注入缓冲区,跳转到缓冲区地址
如果已启用 NX,但未启用 ASLR:
- ret2libc:覆盖返回地址 → 链接到带有 "/bin/sh" 的 system()
- ROP 链:链接 gadgets 以构造 system("/bin/sh") 调用
- GOT 覆盖:覆盖 GOT 条目以重定向库调用
如果已启用 ASLR:
- 先泄露地址(格式化字符串、未初始化数据)
- 根据泄露的地址计算 libc 基址
- 利用泄露的地址构造 ROP 链或使用地址正确的 ret2libc
如果存在栈 canary:
- 泄露 canary 值(格式化字符串、连续溢出)
- 在溢出载荷中保留 canary
- 或改用堆利用
针对每种策略进行调查:
get-stringsregexPattern="(\x2f|/)bin/(sh|bash)" → 查找 shell 字符串find-cross-referencesto "/bin/sh" → 获取字符串地址get-symbolsincludeExternal=true → 查找 system/exec 导入项get-decompilationof system → 获取地址(如果不是 PIE)
针对 ROP:
5. search-decompilation pattern="(pop|ret)" → 查找可能的 gadget
6. 手动发现 ROP gadget(使用 ROPgadget 等外部工具)
7. 使用 set-bookmark type="Note" category="ROP Gadget" 记录 gadget 地址
针对格式化字符串利用:
8. get-decompilation of printf call → 分析格式化字符串控制
9. 测试格式化字符串原语:%x(泄露)、%n(写入)、%s(任意读取)
10. 使用 set-comment 记录利用原语
阶段 4:载荷构造(概念性)
构造利用载荷:
此步骤在 Ghidra 外部 使用 Python/pwntools 完成,但请在此规划:
-
使用
set-comment记录载荷结构:Payload structure: [padding: 64 bytes] + [saved rbp: 8 bytes] + [return addr: 8 bytes] + [args] -
使用
set-bookmark记录关键地址:- 缓冲区地址:0x7fffffffdd00
- 返回地址位置:0x7fffffffdd40(偏移 +64)
- system() 地址:0x7ffff7e14410
- "/bin/sh" 字符串:0x00404030
-
使用
set-bookmarktype="Analysis" category="Exploit Plan" 记录利用步骤:Step 1: Send 64 bytes padding Step 2: Overwrite return address with system() address Step 3: Inject "/bin/sh" pointer as argument Step 4: Trigger return to execute system("/bin/sh") -
使用
set-bookmarktype="Warning" category="Assumption" 跟踪假设:- "假设栈地址稳定(无 ASLR)"
- "根据反编译结果假设不存在 canary(需在运行时验证)"
阶段 5:利用验证(迭代)
此阶段在 Ghidra 外部进行,但请记录发现:
- 使用本地二进制文件测试利用
- 根据崩溃分析调整偏移量
- 处理坏字节或字符限制
- 逐步完善载荷,直至利用成功
使用发现更新 Ghidra 数据库:
- 使用
set-comment记录实际有效的偏移量 - 使用
set-bookmark记录成功的利用 checkin-programmessage="已记录 function_X 中缓冲区溢出的成功利用"
模式识别
详细漏洞模式请参阅 patterns.md:
- 不安全的 API 使用模式
- 缓冲区溢出指示
- 格式化字符串漏洞特征
- 堆利用模式
- 整数溢出场景
- 劫持控制流的机会
利用技术参考
栈缓冲区溢出
概念:写入超出缓冲区边界,以覆盖栈上的返回地址或函数指针。
发现方法:
- 查找不安全的复制操作:strcpy、gets、scanf("%s")、具有较大长度参数的 read
- 根据反编译结果确定缓冲区大小
- 比较缓冲区大小与最大输入大小
- 计算到返回地址的偏移量(缓冲区大小 + 已保存的寄存器)
利用方法:
- 载荷:[填充至返回地址] + [新的返回地址] + [可选参数/ROP 链]
- 目标:覆盖返回地址以重定向执行
格式字符串漏洞
概念:由用户控制的格式字符串允许任意读写内存。
发现:
search-decompilationpattern="printf|fprintf|sprintf"- 检查格式字符串是否来自用户输入:printf(user_buffer)
- 易受攻击的模式:printf(input),而不是 printf("%s", input)
利用:
- 读取:%x、%p(泄露栈值)、%s(通过栈上的指针进行任意读取)
- 写入:%n(将已打印的字节数写入栈上的指针)
- 位置:%N$x(直接访问第 N 个参数)
调查:
4. 使用 get-decompilation 并设置 includeReferenceContext → 查看 printf 调用上下文
5. 使用 set-decompilation-comment 记录格式字符串的控制方式
6. 设置 set-bookmark type="Warning" category="Format String"
面向返回编程(ROP)
概念:将以“ret”结尾的现有代码片段(gadget)串联起来,在不注入代码的情况下构建任意计算。
发现:
- 查找 gadget:
pop reg; ret、mov [addr], reg; ret、syscall; ret - 外部工具:ROPgadget、ropper(Ghidra 没有内置的 gadget 搜索功能)
- 使用
set-bookmarktype="Note" category="ROP Gadget" 在 Ghidra 中记录 gadget
利用:
- 将地址放到栈上,以串联 gadget
- 每个 gadget 执行后,“ret”会弹出下一个 gadget 地址
- 使用正确的寄存器构建系统调用:execve("/bin/sh", NULL, NULL)
工作流:
4. 确定实现目标所需的 gadget(例如,execve 系统调用)
5. 在 gadget 地址处使用 set-comment 记录其用途
6. 使用 set-bookmark type="Analysis" category="ROP Chain" 规划 ROP 链结构
ret2libc
概念:将执行重定向到 libc 函数(system、exec、one_gadget),而不是使用 shellcode。
发现:
get-symbolsincludeExternal=true → 查找 libc 导入项- 对 system、execve 执行
find-cross-references→ 获取地址 get-stringsregexPattern="/bin/sh" → 查找 shell 字符串
利用(无 ASLR):
- 覆盖返回地址 → 指向 system 函数地址
- 设置第一个参数 → 指向 "/bin/sh" 字符串的指针
- 调用约定:x86-64 使用 RDI 作为第一个参数,x86 使用栈
利用(启用 ASLR):
- 泄露 libc 地址(格式字符串、未初始化指针)
- 计算 system/exec 地址 = libc_base + 偏移量
- 使用计算出的地址构建 ROP 链
调查:
4. 在 GOT 条目处使用 get-data → 查看 libc 函数地址
5. 根据已知偏移量计算 libc 基址
6. 使用 set-bookmark 记录计算出的地址
堆利用
概念:破坏堆元数据,或溢出堆块之间的边界,以实现任意写入或劫持控制流。
发现:
search-decompilationpattern="malloc|free|realloc"- 跟踪分配和释放模式
- 查找释放后使用:在 free() 之后仍使用指针
- 查找堆溢出:写入超出已分配大小的内容
利用技术:
- 释放后使用:释放对象,在同一槽位分配新对象,然后通过旧指针访问新对象(类型混淆)
- 重复释放:两次释放同一指针,破坏分配器元数据
- 堆溢出:溢出到下一个堆块,覆盖元数据(大小、指针)或数据(函数指针)
- Fastbin/tcache 中毒:破坏空闲链表指针,以分配任意内存
调查:
5. 使用 rename-variables 为堆指针命名(heap_ptr、freed_ptr、chunk1、chunk2)
6. 在分配和释放位置使用 set-decompilation-comment
7. 设置 set-bookmark type="Warning" category="Use-After-Free"
整数溢出
概念:整数溢出或下溢会导致缓冲区大小计算错误,或绕过边界检查。
发现:
- 查找大小计算:size = user_input * sizeof(element)
- 检查是否会溢出:如果 user_input 非常大,会怎样?
- 查找边界检查:if (index < size) → 如果 index 是较大的无符号数,会怎样?
利用:
- 分配大小溢出 → 堆缓冲区过小 → 堆溢出
- 大小检查下溢 → 绕过负数检查 → 缓冲区溢出
- 算术回绕 → 绕过长度检查
调查:
4. 使用 change-variable-datatypes 设置为合适的整数类型(uint32_t、size_t)
5. 在注释中标出溢出场景
6. set-bookmark type="Warning" category="Integer Overflow"
工具集成
系统地使用 ReVa 工具:
发现工具
get-symbols→ 查找不安全的 API 导入get-strings→ 查找有意义的字符串(flag、shell、路径)search-decompilation→ 查找漏洞模式(不安全函数)get-functions-by-similarity→ 查找与已知易受攻击模式相似的函数
分析工具
get-decompilation,并设置includeIncomingReferences=true和includeReferenceContext=truefind-cross-references,并设置includeContext=true→ 跟踪数据流get-data→ 检查全局变量、GOT 条目和常量数据read-memory→ 抽样检查内存布局
数据库改进工具
rename-variables→ 明确与利用相关的变量(buffer、user_input、return_addr)change-variable-datatypes→ 修正类型,以便正确理解set-decompilation-comment→ 在反编译代码中就地记录漏洞set-comment→ 在关键地址记录利用策略set-bookmark→ 跟踪漏洞、gadget 和利用计划
组织工具
set-bookmarktype="Warning" category="Vulnerability" → 标记漏洞set-bookmarktype="Note" category="ROP Gadget" → 跟踪 gadgetset-bookmarktype="Analysis" category="Exploit Plan" → 记录策略set-bookmarktype="TODO" category="Verify" → 跟踪待验证的假设checkin-program→ 保存进度
成功标准
满足以下条件即表示成功完成挑战:
- 已识别漏洞:记录具体函数、行号和漏洞类型
- 已了解内存布局:映射缓冲区大小、偏移量和相邻数据
- 已规划利用策略:记录从漏洞到 flag 的明确路径
- 已记录关键地址:记录漏洞利用载荷所需的所有地址
- 已跟踪假设:记录所有假设及其置信度
- 已改进数据库:重命名变量、添加注释并设置书签,以便清晰理解
- 已准备好利用计划:信息足以在 Ghidra 外编写利用代码
返回给用户:
- 附有证据的漏洞描述
- 利用方法说明
- 关键地址和偏移量
- 载荷结构计划
- 假设和待验证事项
- 后续任务(如有需要,例如“针对二进制文件测试漏洞利用”)
反模式
不要:
- 在没有证据的情况下假设存在漏洞(检查缓冲区大小!)
- 忘记防护措施(canary、NX、ASLR、PIE)
- 忽视输入限制(坏字符、大小限制)
- 只盯着一种方法(尝试不同的利用技术)
- 忽视调用约定(x86 与 x64 的参数传递方式)
- 忘记空字节终止符(C 字符串函数)
应该:
- 根据反编译结果验证缓冲区大小
- 检查是否存在栈 canary:
__stack_chk_fail引用 - 精确计算偏移量(从缓冲区到返回地址)
- 使用
set-bookmarktype="Warning" 记录所有假设 - 根据现有防护措施调整利用技术
- 创造性地思考(串联原语,使用非常规目标)
牢记
二进制利用是一种创造性的问题求解:
- 理解漏洞存在的原因(不安全的假设)
- 思考如何操纵内存(数据流分析)
- 规划要覆盖什么(控制流、数据、指针)
- 确定要重定向到哪里(现有代码、注入的代码、ROP)
- 逐步执行(泄露、计算、覆盖、触发)
每个 CTF 挑战都不一样。使用此框架来思考漏洞利用,而不是将其当作一份盲目遵循的清单。
你的目标:在 Ghidra 中记录足够的信息,以便编写漏洞利用脚本。实际利用在外部进行,而分析在这里完成。