
Patch Diff Exploit
- 160 installs
- 18.1k repo stars
- Updated August 4, 2026
- zhaoxuya520/reverse-skill
Compare patched versus unpatched binaries with BinDiff, Diaphora, ghidriff, and radiff2 when hunting regressions or exploit surfaces in reverse-engineering workflows.
About
patch-diff-exploit is an agent skill that documents how solo builders and security researchers run binary patch differential analysis across common engines. It explains which tool fits IDA-heavy labs versus Ghidra headless CI, how to export BinExport artifacts, and how to read diff databases when a vendor ships a fix. The skill is for indie reversers validating CVE patches, comparing firmware builds, or narrowing exploit research to changed functions—not for generic file diffing of source trees. It assumes comfort with disassemblers, long analysis runs, and interpreting function match quality rather than one-click answers. Use it when you have two related binaries and need a repeatable exploit-or-hardening workflow instead of ad-hoc hex compares.
- Side-by-side comparison of six mainstream binary diff tools (BinDiff, Diaphora, ghidriff, DeepDiff, radiff2, patchdiff2)
- End-to-end BinDiff flow: IDA BinExport for old and new binaries, plugin diff, and CLI bindiff with SQLite .BinDiff outpu
- ghidriff path for headless Ghidra and CI-friendly markdown diff reports without commercial IDA
- Install snippets for Linux .deb and Windows MSI plus explicit radare2 radiff2 embedding notes
- Guidance on when function-level matching (BinDiff/Diaphora) beats lightweight radiff2-only workflows
Patch Diff Exploit by the numbers
- 160 all-time installs (skills.sh)
- +28 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #864 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zhaoxuya520/reverse-skill --skill patch-diff-exploitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 160 |
|---|---|
| repo stars | ★ 18.1k |
| Last updated | August 4, 2026 |
| Repository | zhaoxuya520/reverse-skill ↗ |
What it does
Compare patched versus unpatched binaries with BinDiff, Diaphora, ghidriff, and radiff2 when hunting regressions or exploit surfaces in reverse-engineering workflows.
Files
N-day 补丁差分到利用 (Patch Diff Exploit)
适用范围
当任务属于以下场景时使用本 skill:
1. 已知 CVE 但无公开 PoC — 厂商公告写了"修复了 XX 组件的越界写"但没放 PoC,需要从补丁反推 2. SRC / 红队打 N-day — 目标资产未及时更新,需要把刚发布的补丁差成可用的 1-day 利用 3. Patch Tuesday 跟进 — 每月第二个周二微软放补丁,需要快速锁定高价值漏洞(Kernel / Win32k / AFD / CLFS) 4. Linux LTS 补丁分析 — 主线 fix 已合并,但旁支或某发行版 backport 不全,找未修补面 5. 驱动 / 服务的安全补丁还原 — 显卡驱动、AV 引擎、虚拟化组件等闭源软件的补丁分析
与其他 skill 的分工
| 场景 | 用什么 |
|---|---|
| 有旧版符号,迁移到新版本帮助分析 | binary-diff/ |
| 从补丁找漏洞、写 PoC 打补丁前版本 | 本 skill |
| 写出完整利用链(堆喷、ROP、提权) | pwn-chain/ |
| 把 1-day 武器化部署到目标网络 | pentest-tools/network-attack-defense/ |
| 从零逆向一个二进制 | ida-reverse/ / radare2/ |
差别的关键:binary-diff 的目标是让新版可分析(把旧符号搬过来),本 skill 的目标是找出补丁修了什么 bug 然后打补丁前的版本。前者服务防御侧 / 研究侧分析,后者服务攻击侧武器化。
核心原理
patched 二进制 (after) unpatched 二进制 (before)
↓ ↓
导入 IDA/Ghidra 导入 IDA/Ghidra
↓ ↓
└──────── BinDiff / ghidriff ──────┘
↓
函数级 diff(matched / unmatched / changed)
↓
聚焦 match score 中等的函数(0.5 - 0.9)
↓
看新增了什么:边界检查 / 锁 / 字段清零 / 整数溢出检查
↓
反推 bug class:OOB / Race / Info Leak / UAF / Integer Overflow
↓
在 unpatched 版本上写 PoC 触发
↓
验证:unpatched 崩 / patched 不崩 → 漏洞确认补丁修复模式 → 漏洞类型反查:
| 新增内容 | 大概率的 bug class |
|---|---|
if (a + b < a) / __builtin_add_overflow | 整数溢出 |
KeAcquireSpinLock / mutex_lock | 竞争条件 (TOCTOU / double-free) |
if (idx >= MAX) / if (len > buf_size) | 越界读 / 越界写 |
RtlZeroMemory / memset(struct, 0, ...) | 未初始化内存信息泄漏 |
InterlockedDecrement + refcount 检查 | UAF / 引用计数错误 |
ProbeForRead / ProbeForWrite | 用户态指针未校验 |
SeAccessCheck / capability 校验 | 权限校验缺失 |
删除 / 收紧 IOCTL code | 暴露面收敛(看老接口怎么打) |
工作流
5 步完整流程
Step 1: 拿 before / after 二进制
- Windows: Microsoft Update Catalog 下 MSU/MSP,用 expand.exe / dism 解包
- Linux: 从发行版 USN/RHSA 拉 .deb/.rpm,用 dpkg-deb / rpm2cpio 解包
- 第三方软件: 官网取 N-1 和 N 版本安装包
Step 2: 对齐符号
- 有 PDB 直接吃,没 PDB 时用 binary-diff skill 把 N-1 版本的符号搬到 N 版本
- Linux 内核取对应版本的 vmlinux + System.map / debuginfo
Step 3: 二进制 diff
- BinDiff: 直接给两个 IDB,看函数级匹配结果
- ghidriff: pip 一键安装,CLI 输出 markdown 报告
- Diaphora: IDA 内插件,老牌但需要 IDA Pro
Step 4: 定位变更
- 过滤 match score 0.5-0.95 的函数(完全相同的不看,完全不同的多半是新加 / 重命名)
- 重点看:新增的 if / 新增的循环边界 / 删除的代码块(删了什么也是线索)
- 用 LLM 看 before/after 伪代码反推 bug class(见 references/root-cause-and-poc.md)
Step 5: 写 PoC
- 整数溢出:构造边界值(INT_MAX-1、0xFFFFFFFF)
- 竞争:多线程 hammer,open/close + ioctl 高频并发
- UAF:spray → free → reuse pattern
- OOB:精确控制 len / index 越过边界
- 验证 patched 版本不再崩,unpatched 版本稳定崩 → bug 复现成功工具调用顺序
下补丁 → 解包 → 加载到 IDA/Ghidra → BinDiff/ghidriff → 看 unmatched/low-match 函数
→ LLM 反推 bug class → 写 PoC → 在 unpatched 跑 → 崩 → 收工典型场景示例
场景 1:Windows Patch Tuesday — Kernel CVE 复现
背景:2025 年 11 月 Patch Tuesday,MSRC 公告 CVE-2025-62215
Windows Kernel race condition 导致 double free,CVSS 7.0,本地提权
微软只放了补丁,没放细节,没有公开 PoC
目标:复现 PoC,验证未打补丁的 Windows 11 22H2 / 23H2 可提权
步骤:
1. Microsoft Update Catalog 搜 "2025-11" + KB 号,下两个版本:
- 22H2 build 22621.xxxx (unpatched)
- 22H2 build 22621.yyyy (patched 后)
命令:
expand.exe Windows-KB5052000-x64.msu -F:* C:\out\patched\
expand.exe C:\out\patched\Windows-KB5052000-x64.cab -F:* C:\out\patched\
提取 ntoskrnl.exe / win32k.sys / win32kfull.sys / afd.sys
2. 两个版本都吃 PDB (微软符号服务器):
symchk /v /r ntoskrnl.exe /s SRV*C:\sym*https://msdl.microsoft.com/download/symbols
3. 跑 BinDiff:
bindiff old.BinExport new.BinExport
或 ghidriff:
ghidriff ntoskrnl_old.exe ntoskrnl_new.exe -o diff_out/
4. 看报告,过滤 similarity 0.6-0.95 的函数。
假设定位到 NtXxxIoctl 类函数新增了一段:
KeAcquireSpinLockRaiseToDpc(&obj->Lock);
if (obj->RefCount == 0) { ... goto cleanup; }
→ 新增了锁 + 引用计数检查 → race + double free,符合公告描述
5. 写 PoC:用户态多线程同时调 NtClose + 触发同一对象的 IOCTL,
制造 close 释放与 IOCTL 还在用之间的竞争窗口
崩在 ntoskrnl 的 ObfDereferenceObject 后续 free 路径上
6. 验证:
- unpatched 22621.xxxx 上跑 PoC,~30 秒内 BSOD (BAD_POOL_HEADER 或 DOUBLE_FREE)
- patched 22621.yyyy 上跑同 PoC,无任何异常
→ 复现成功场景 2:Linux 内核 LTS 分支补丁找未修的旁支
背景:主线 6.x 已修某 net subsystem 的 OOB 写
Ubuntu 22.04 (5.15 LTS) 的 USN 已发布更新
但某些 OEM kernel / Azure kernel 的 backport 节奏更慢
想确认未更新的旁支是否仍可打
目标:取 patched/unpatched 内核,差出 fix commit 对应的二进制变更,
在 unpatched 旁支上重写 PoC
步骤:
1. 拉 patched 与 unpatched 包:
apt download linux-image-5.15.0-101-generic # patched
apt download linux-image-5.15.0-100-generic # unpatched
dpkg-deb -x linux-image-5.15.0-101-generic_*.deb ./patched/
dpkg-deb -x linux-image-5.15.0-100-generic_*.deb ./unpatched/
提取 boot/vmlinuz → 用 extract-vmlinux 还原 ELF
2. 同步取 dbgsym:
apt download linux-image-unsigned-5.15.0-101-generic-dbgsym
3. 用 ghidriff (Linux 友好):
ghidriff vmlinux_5.15.0-100 vmlinux_5.15.0-101 \
-o /tmp/kdiff/ --max-section-funcs-analyze 8000
4. 报告里搜 net/ipv4/ net/ipv6/ net/sched/ 等子系统的 changed 函数
找到补丁前 skb_copy_bits 调用前缺少 skb->len 上限校验
→ OOB read,可能配合可触发的 sysctl 升级到 OOB write
5. 在 unpatched 旁支(例如 Azure 5.15.0-1080 backport 落后的版本)
交叉验证:同一函数 fix 是否已 backport
如果没 backport → 旁支仍可打 → 写 PoC 重放
6. 写 PoC:syzkaller harness 改造 / 直接 C PoC 触发对应 syscall
验证旁支 panic / KASAN 报 OOB注意事项
- 法律边界 — 武器化 N-day 必须在授权范围内(SRC / Bug Bounty / 自有靶机 / CTF)。对生产环境直接打 1-day 等同入侵
- 补丁可能只是"减小爆炸半径" — 看到 patch 不一定就是完整修复,有可能只是补一个利用路径,原始 bug 仍可从别的路径触发(一鱼多吃)
- 变量名/类型不要被欺骗 — Windows 补丁经常顺手做 cleanup / rename,看似变更很大但实际无关。要看控制流和数据流,不要看 token 级 diff
- 微软的补丁可能加了 mitigation 而不是 fix — 看到
_guard_xfg_dispatch_icall_fptr这种 CFG 强化不要当成 fix,那是 mitigation - 匿名化 — writeup / PoC 公开时脱敏目标机器名、内网 IP、用户名(写
{target_ip}{username}占位) - patched 版本上要能跑通无害化测试 — 别只在 unpatched 上跑,否则可能是环境因素导致的崩溃,不是漏洞
- 二进制 diff 不万能 — 编译器升级 / 优化等级变化也会让函数 layout 大变,先用 N 版本和 N-1 版本(同一编译器)对比,不要跨大版本
---
按需自举(On-Demand Bootstrap)
工具依赖
| 工具 | 用途 | 可自动安装 |
|---|---|---|
| BinDiff (Google, 5.x+) | 函数级二进制 diff,IDA/Ghidra 插件 | ✓ (有官方 .deb / .msi) |
| Diaphora | IDA 老牌 diff 插件,需要 IDA Pro | ✓ (git clone) |
| ghidriff | Ghidra headless CLI diff,输出 markdown | ✓ (pip install ghidriff) |
| DeepDiff (商业) | 新一代 diff 工具,准确度更高 | ✗ (商业授权) |
| Ghidra | ghidriff 的运行底座 | ✓ |
| IDA Pro | BinDiff / Diaphora 的运行底座 | ✗ (商业) |
| Microsoft Update Catalog | 下 MSU/MSP 补丁包 | 在线服务 |
| wsuspect-proxy | 透明拦截 Windows Update 流量取补丁 | ✓ (git clone) |
| expand.exe / dism | 解 MSU / cab | ✓ (Windows 自带) |
| rpm2cpio / dpkg-deb | 解 Linux 发行版包 | ✓ |
| symchk | 从微软符号服务器拉 PDB | ✓ (Windows SDK) |
自举命令
powershell -NoProfile -ExecutionPolicy Bypass -File "<SKILL_ROOT>\skills\scripts\bootstrap-reverse.ps1" -Capability @('bindiff','ghidriff','ghidra','wsuspect-proxy') -StartServices详细工具对比与命令见 references/diff-tools-comparison.md。 详细 Patch Tuesday 工作流见 references/patch-tuesday-workflow.md。 根因反推与 PoC 模板见 references/root-cause-and-poc.md。
---
路由上下文
上游入口: skills/SKILL.md(总控)、routing.md
上游 skill:
reverse-engineering/— 在做 diff 之前可能要先理解目标二进制的整体结构binary-diff/— 如果补丁后版本无符号、补丁前有符号,先用 binary-diff 搬符号过来
下游 skill:
pwn-chain/— 反推出 bug class 后,需要写完整利用(堆喷、ROP、SMEP/SMAP 绕过、提权 payload)pentest-tools/network-attack-defense/— 把 N-day 武器化部署到目标网络(包装成可投递载荷、对接 C2)attack-chain/— 把这一个 N-day 串到完整攻击链里(初始访问 → 提权 → 横向)
触发条件: 任务包含"N-day"、"补丁"、"CVE 复现"、"找补丁修了什么"、"打未更新主机" 等意图
二进制差分工具对比
补丁差分的核心工具是二进制 diff 引擎。本文档对比当前主流工具的强项、安装、典型命令、输出解读,以及组合工作流。
---
工具总览
| 工具 | 作者/厂商 | 底座 | License | 强项 | 弱项 |
|---|---|---|---|---|---|
| BinDiff | Google (zynamics) | IDA Pro / Ghidra / Binary Ninja | 免费 | 业界标杆,函数级匹配最稳,GUI 视图清晰 | 需要 IDA 才能玩到极致 |
| Diaphora | Joxean Koret | IDA Pro | GPL | 老牌、活跃维护、SQLite 持久化、支持伪代码 diff | 重度依赖 IDA Pro,对超大二进制慢 |
| ghidriff | Clearbluejar | Ghidra headless | Apache 2.0 | 纯 CLI,CI 友好,输出 markdown,免费开源 | 准确度依赖 Ghidra 反编译 |
| DeepDiff (Karambit) | Karambit.AI | 自带 | 商业 | LLM 辅助、变量名重命名感知 | 商业授权 |
| radiff2 | radare2 项目 | radare2 | LGPL | 全开源、嵌入 r2 工作流 | 函数匹配能力弱于 BinDiff |
| patchdiff2 | tetrane | IDA | GPL | IDA 老插件,年代久 | 已基本被 BinDiff/Diaphora 取代 |
---
BinDiff
安装
Linux:
wget https://github.com/google/bindiff/releases/latest/download/bindiff_8-amd64.deb
sudo dpkg -i bindiff_8-amd64.debWindows:
# 从 https://github.com/google/bindiff/releases 下 .msi
msiexec /i bindiff_8.msi /quiet
# 自动注册到 IDA Pro 插件目录IDA 内使用
1. 在 IDA 里加载 unpatched 二进制,分析完,File → Produce file → BinExport 导出 old.BinExport 2. 加载 patched 二进制,同样导出 new.BinExport 3. Edit → Plugins → BinDiff → 选 new.BinExport 对比 → 输出 .BinDiff 数据库
CLI 用法
# 直接两个二进制 diff(先用 binexport 提取)
bindiff old.exe new.exe
# 或显式给 BinExport
bindiff --primary=old.BinExport --secondary=new.BinExport \
--output_dir=./bindiff_out/
# 输出: ./bindiff_out/old_vs_new.BinDiff (SQLite)输出读法
打开 .BinDiff 文件后,关键列:
similarity— 0.0 ~ 1.0,函数相似度confidence— 算法信心algorithm— 匹配算法 (name hash, function hash, MD index 等)
聚焦工作区:
- similarity 1.0 + confidence 1.0 — 完全不变,跳过
- similarity 0.0 (unmatched) — 新增或重大重构,留意但通常是新功能
- similarity 0.5 ~ 0.95 — 重点目标,多半是修了 bug
- similarity 0.95 ~ 0.99 — 微调,可能是 mitigation 或 cleanup
GUI 内:双击函数 → 看 graph view 的 basic block 着色:
- 灰:完全相同
- 黄:相似但有指令差异
- 红:新增或删除
- 蓝:仅 patched 有的 block — 重点看这种
---
Diaphora
安装
cd ~/idapro/plugins/
git clone https://github.com/joxeankoret/diaphora.git
# 在 IDA 里 File → Script file → ~/idapro/plugins/diaphora/diaphora.py工作流
1. IDA 打开 unpatched → 跑 diaphora.py → 选 "Export current database" → 保存 old.sqlite 2. IDA 打开 patched → 跑 diaphora.py → 选 "Diff against another database" → 选 old.sqlite → 输出对比报告
输出读法
Diaphora 输出多个 tab:
- Best matches — 高置信度匹配,跳过
- Partial matches — 部分匹配,重点看这里
- Unreliable matches — 算法不确定,可能误报
- Unmatched in primary / secondary — 单边出现的函数
每个函数支持 side-by-side 伪代码 diff(IDA 反编译输出)。
关键 SQL 查询
Diaphora 用 SQLite 存数据,可写 SQL 找目标:
-- 找出 diff 比例在中等区间的函数
SELECT f1.name, f2.name, ratio
FROM results
WHERE ratio BETWEEN 0.5 AND 0.9
ORDER BY ratio DESC;---
ghidriff
安装
pip install ghidriff
# ghidriff 会自动下载 Ghidra 到 ~/.ghidra/ (首次运行需要联网)
# 或指定 Ghidra 路径:
export GHIDRA_INSTALL_DIR=/opt/ghidra_11.0基础用法
# 最小命令
ghidriff old.so new.so
# 指定输出目录 + 引擎
ghidriff old.exe new.exe \
-o ./diff_out/ \
--engine VersionTrackingDiff \
--threaded \
--max-section-funcs-analyze 5000
# 看所有可用引擎
ghidriff --list-engines
# 常见: VersionTrackingDiff (默认), SimpleDiff, StructualGraphDiff输出格式
ghidriff 默认输出三种产物: 1. Markdown 报告 — 人类可读,包含 added / deleted / modified 函数清单 + 伪代码 diff 块 2. JSON 报告 — 程序化消费 3. GhidraProject — 可以打开继续手工分析
报告关键章节:
## Summary— 总体统计 (匹配率)## Strings Diff— 字符串变化(信息泄漏类 CVE 经常字符串变了)## Functions→### Modified— 重点- 每个函数下:
before/after伪代码 + unified diff
推荐参数
# Windows 内核大文件:限制单 section 分析数量,避免 OOM
ghidriff ntoskrnl_old.exe ntoskrnl_new.exe \
-o /tmp/nt_diff/ \
--max-section-funcs-analyze 8000 \
--max-section-funcs-full 800 \
--threaded
# 大量小函数(驱动):开启所有 diff
ghidriff afd_old.sys afd_new.sys -o /tmp/afd_diff/ --side-by-side配 LLM 自动总结
ghidriff 支持插件式输出,可以喂给 LLM 自动生成根因分析(见 root-cause-and-poc.md 的 LLM 章节)。
# 输出 JSON 给下游 LLM 脚本消费
ghidriff old new -o out/ --json-format
python my-llm-summarizer.py out/diff.json---
radiff2
安装
# 通常随 radare2 安装
sudo apt install radare2
# 或源码
git clone https://github.com/radareorg/radare2 && radare2/sys/install.sh用法
# 字节级 diff
radiff2 old.bin new.bin
# 函数级 diff (需要先各自 r2 分析)
radiff2 -A -C old.bin new.bin # -A 分析, -C 函数级
# 输出 graph (Graphviz)
radiff2 -g main old new | dot -Tpng -o diff.png
# JSON 输出
radiff2 -j -A -C old new > diff.json适合:与 radare2 工作流深度集成的场景,或者 BinDiff/Ghidra 跑不动的小文件。
---
DeepDiff (Karambit.AI, 商业)
特点:
- 不依赖 IDA / Ghidra
- 自带反编译器
- 用 ML 模型做函数匹配,对 inline / rename / 控制流变化更鲁棒
- 自动生成 root cause 假设
适合资金充足的团队批量处理 Patch Tuesday。本文档不展开,因需要商业授权。
---
工具组合工作流
推荐组合(按场景)
场景 A: 有 IDA Pro 授权,质量优先
BinDiff (主) + Diaphora (副验证) + LLM 辅助根因
场景 B: 无 IDA,纯开源
ghidriff (主) + LLM 辅助根因
场景 C: 批量 Patch Tuesday 流水线
ghidriff CLI + JSON → 脚本筛选 → 人工 review top N
场景 D: 小型驱动 / 嵌入式 firmware
radiff2 / ghidriff 都行粗筛 → 精看 流程
Step 1 (粗筛): 跑 ghidriff,过滤
- 整体 matched 比例 < 90% → 警惕,多半对齐失败 (可能是不同编译器、不同混淆)
- matched 比例 > 99% → 补丁很微小,可能只动 1-2 个函数
- 列出 Modified 函数清单,按变更行数排序
Step 2 (优先级): 按以下加权
- 函数名含敏感词 (Ioctl / DispatchIoCtrl / Probe / Copy / Length) +5
- 出现在已知攻击面驱动 (afd/clfs/win32k) +5
- 变更涉及 if 边界 / lock / refcount +3
- 仅做字符串 / 日志变更 -3
Step 3 (精看): 把 top 10 函数喂给 BinDiff GUI 或 IDA 手工,
或贴 before/after 伪代码给 LLM 反推 bug class---
常见坑
- 对齐失败:matched 比例 < 90% 时不要继续。先确认两个二进制是同一编译器版本、同一架构、同一优化等级。常见原因:跨大版本编译器、PGO/LTO 不同、ASLR rebase 没对齐
- inline 函数把 diff 撑大:编译器决定 inline 的函数变了会让父函数大范围变红,但根本没改逻辑。要看真实控制流分支
- switch table 重排:编译器可能重排 case,看着像新增 block,其实只是顺序变化
- Profile-Guided Optimization (PGO) 抖动:连续两个版本的二进制即使源码一字不改也可能 layout 不同
- stripped Linux 内核:没 debuginfo 时所有函数名都是
FUN_xxxx,必须先吃 BTF / kallsyms 或用 binary-diff 迁移符号
Patch Tuesday 工作流
每月第二个周二 (Pacific Time) 微软发布安全更新,俗称 Patch Tuesday。这是 N-day 研究最稳定的输入源:补丁数量大、价值高、披露口径统一。本文档给出从订阅公告到拿到可对比二进制的完整流程。
---
1. 信息源订阅
| 来源 | 用途 | 备注 |
|---|---|---|
| MSRC Security Update Guide | 当月所有 CVE 列表 + CVSS + 受影响产品 | https://msrc.microsoft.com/update-guide/ 有 API |
| MSRC CVRF API | 程序化拉当月 CVE JSON | https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/{YEAR}-{Mon} |
| Microsoft Update Catalog | 下 MSU/MSP 实物 | https://www.catalog.update.microsoft.com/ |
| Patch Tuesday Dashboard (Tenable / Trend / Rapid7) | 第三方汇总优先级 | 看哪些标了 "exploited in the wild" |
| ZDI advisories | ZDI 提交的 CVE 详细描述 | 经常有比 MSRC 更详细的 root cause hint |
| Project Zero 周期性 issue tracker | 半年后会公开细节 | 可作为历史对照 |
拉当月 CVE 列表 (PowerShell)
$year = '2026'; $mon = 'May'
$url = "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/$year-$mon"
Invoke-RestMethod $url | ConvertTo-Json -Depth 10 | Out-File "C:\patches\msrc-$year-$mon.json" -Encoding utf8---
2. 锁定高价值补丁
按以下顺序过滤当月公告:
1. CVSS >= 7.0 2. Exploitation: Exploitation Detected / More Likely 3. 组件白名单(高价值攻击面):
Windows Kernel(ntoskrnl.exe)Win32k(win32k.sys, win32kfull.sys, win32kbase.sys)Ancillary Function Driver for WinSock(afd.sys) — 历史 LPE 大户Common Log File System(clfs.sys) — 过去三年多次被野利用Print Spooler(spoolsv.exe, win32spl.dll)Cloud Files Mini Filter Driver(cldflt.sys)NTFS(ntfs.sys)Hyper-V(vmswitch.sys, hvix64.exe, hvax64.exe)Cryptography Services/NTLM/KerberosRDP(rdpcorets.dll, rdpbase.dll)
4. 本地提权 (EoP) > RCE > Info Disclosure — EoP 武器化门槛最低 5. 是否有 KB 直接对应 — 没有 KB 号的(例如纯 Defender 定义更新)跳过
---
3. 下载 patched / unpatched 二进制
3.1 Microsoft Update Catalog 手动下
1. 访问 https://www.catalog.update.microsoft.com/Search.aspx?q=KB5052000
2. 按 Products 过滤 (Windows 11, Server 2022 等)
3. 选目标架构 (x64 / arm64),下载 .msu
4. 同时下 N-1 版本 (上个月的 KB 号) 作为 unpatched 基线3.2 程序化批量下
# msdownload 第三方脚本
git clone https://github.com/JaschaUrbach/msu-downloader.git
python msu-downloader.py --kb KB5052000 --arch x64 --out C:\patches\3.3 wsuspect-proxy 拦截真实流量取最新补丁
适用于:MSRC 还没在 Catalog 上架,但已通过 WU 推送给客户端的早期窗口。
git clone https://github.com/ctxis/wsuspect-proxy.git
cd wsuspect-proxy
# 在被测机器上手工设 WSUS = http://attacker:8530,触发 wuauclt /detectnow
python wsuspect-proxy.py --listen 0.0.0.0:8530 --dump-cabs C:\patches\dump\3.4 Delta 包 vs Cumulative 包
- 微软现在主推
.msucumulative update — 体积大但完整 - 也有
.msuExpress / Delta 包 — 体积小但只含差异,对差分研究不友好 - 优先下 cumulative
---
4. 解包 MSU / CAB / MSP
4.1 MSU 解包(Windows)
# Step 1: MSU 是 cab 套娃,先解外层
expand.exe Windows-KB5052000-x64.msu -F:* C:\patches\out\
# Step 2: 解里面的 .cab
expand.exe C:\patches\out\Windows-KB5052000-x64.cab -F:* C:\patches\out\
# Step 3: 关键文件在 amd64_microsoft-windows-{component}_*\ 下
# 例如 ntoskrnl.exe 在:
# C:\patches\out\amd64_microsoft-windows-os-kernel_*\ntoskrnl.exe4.2 用 dism 装到离线镜像(拿干净的 patched 二进制)
# 挂载离线 WIM
dism /mount-image /imagefile:install.wim /index:1 /mountdir:C:\mnt
# 应用补丁
dism /image:C:\mnt /add-package /packagepath:Windows-KB5052000-x64.msu
# 提取目标二进制
copy C:\mnt\Windows\System32\ntoskrnl.exe C:\patches\patched\
# 卸载
dism /unmount-image /mountdir:C:\mnt /commit4.3 MSP (Office 等) 解包
# MSP 是 Windows Installer Patch
msiexec /a base.msi /p update.msp TARGETDIR=C:\patches\office_patched
# 或用 lessmsi (推荐, 不动注册表)
lessmsi x update.msp C:\patches\office_msp\4.4 用第三方工具一站式拆包
# PatchExtract by Greg Linares - 一条命令解多层
.\PatchExtract.ps1 -PatchFile Windows-KB5052000-x64.msu -ExtractedFolder C:\patches\extracted\---
5. 取符号 (PDB)
# Windows SDK 自带 symchk
symchk /v /r C:\patches\patched\ntoskrnl.exe /s SRV*C:\sym*https://msdl.microsoft.com/download/symbols
symchk /v /r C:\patches\unpatched\ntoskrnl.exe /s SRV*C:\sym*https://msdl.microsoft.com/download/symbols注意:微软偶尔会延迟或下架某个版本 PDB。如果当前下不到 N-1 的 PDB,用 binary-diff skill 把 N-2 的 PDB 符号迁移到 N-1。
---
6. Windows 高价值二进制路径速查
| 文件 | 路径 | 关注组件 |
|---|---|---|
| ntoskrnl.exe | System32\ | 内核核心、对象管理、Ob、Ps、Mm、Io |
| win32k.sys | System32\ | GUI 子系统入口 |
| win32kfull.sys | System32\ | GUI 完整版(桌面会话) |
| win32kbase.sys | System32\ | GUI 基础库 |
| afd.sys | System32\drivers\ | WinSock AFD,LPE 高发 |
| clfs.sys | System32\drivers\ | CLFS,0-day 高发 |
| cldflt.sys | System32\drivers\ | Cloud Files mini filter |
| spoolsv.exe | System32\ | Print Spooler |
| dwm.exe | System32\ | Desktop Window Manager |
| lsass.exe | System32\ | LSA 认证 |
| ntdll.dll | System32\ | syscall stub、PE loader |
| ksecdd.sys | System32\drivers\ | Kernel security driver |
---
7. Linux 等价流程
7.1 信息源
| 来源 | 适用 |
|---|---|
| Ubuntu USN | https://ubuntu.com/security/notices |
| Red Hat RHSA | https://access.redhat.com/security/security-updates/ |
| Debian DSA | https://www.debian.org/security/ |
| CentOS / Rocky / Alma announce list | 通常 mirror Red Hat |
| upstream kernel CVE list | https://cve.kernel.org/ |
7.2 拉补丁包
# Debian/Ubuntu
apt download linux-image-5.15.0-101-generic
apt download linux-image-5.15.0-100-generic
apt download linux-image-unsigned-5.15.0-101-generic-dbgsym
# CentOS/Rocky
dnf download --downloadonly --downloaddir=./patched kernel-5.14.0-362.18.1.el9_3
dnf download --downloadonly --downloaddir=./unpatched kernel-5.14.0-362.13.1.el9_37.3 解包
# .deb
dpkg-deb -x linux-image-5.15.0-101-generic_*.deb ./patched/
# .rpm
rpm2cpio kernel-5.14.0-362.18.1.el9_3.x86_64.rpm | cpio -idmv -D ./patched/7.4 从 vmlinuz 还原 vmlinux
# 用内核自带 scripts/extract-vmlinux
wget https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux
chmod +x extract-vmlinux
./extract-vmlinux ./patched/boot/vmlinuz-5.15.0-101-generic > vmlinux_5.15.0-101
./extract-vmlinux ./unpatched/boot/vmlinuz-5.15.0-100-generic > vmlinux_5.15.0-1007.5 关注子系统路径
| 子系统 | 历史 N-day 高发函数前缀 |
|---|---|
| net/ | __skb_*、sk_*、tcp_*、udp_*、nf_* |
| fs/ | do_*、vfs_*、__lookup_* |
| io_uring/ | 整个子系统(近三年漏洞王) |
| netfilter/ | nft_*、nf_tables_* |
| net/sched/ | tc 各 qdisc |
| drivers/net/ | 各 NIC 驱动 |
| bpf/ | verifier 类 |
---
8. 真实公开 CVE 案例参考
| CVE | 组件 | 类型 | 用于练手原因 |
|---|---|---|---|
| CVE-2025-62215 | Windows Kernel | race condition + double free | 2025-11 公开,公告说"race + double free",差分入门题 |
| CVE-2023-28252 | CLFS | OOB write → LPE | 野外利用,PoC 已公开,可对照学习方法 |
| CVE-2022-37969 | CLFS | type confusion → LPE | 同样有公开分析 |
| CVE-2021-40449 | Win32k | UAF → LPE | 公开报告完整 |
| CVE-2022-21882 | Win32k | type confusion,由 CVE-2021-1732 不完整修复变出 | 经典"一鱼多吃"案例 |
练习建议:拿一个已经有公开分析的 CVE,按本流程独立差出来一次,再对照公开 writeup 校准方法论。
---
9. 速查清单
- [ ] MSRC 月度公告已读,筛出 CVSS≥7 且组件在白名单内的 CVE
- [ ] 从 Catalog 下到 unpatched (N-1) 和 patched (N) 的 MSU
- [ ] 用 expand / dism 取出目标 .exe / .sys / .dll
- [ ] symchk 把 PDB 都吃到(吃不到走 binary-diff 迁移)
- [ ] 用 BinaryNinja / IDA / Ghidra 各开两个工程
- [ ] 跑 BinDiff / ghidriff,导出 diff 报告
- [ ] 按相似度过滤函数,进
references/root-cause-and-poc.md反推根因 - [ ] 写 PoC,验证 unpatched 崩、patched 不崩
- [ ] 回写 field-journal 记录复用经验
根因反推与 PoC 编写
拿到 diff 报告只是第一步。真正的工作是看着 before/after 反推出 bug class,然后写出能稳定触发的 PoC。本文档给出反推模式表、LLM 辅助 prompt、PoC 编写模板、验证方法。
---
1. 补丁修复模式 → 漏洞类型反查
1.1 整数溢出 / 下溢
新增模式:
// 加法溢出检查
if (a > UINT_MAX - b) goto error;
if (a + b < a) goto error;
__builtin_add_overflow(a, b, &sum);
// 乘法溢出检查
if (a != 0 && b > UINT_MAX / a) goto error;
__builtin_mul_overflow(a, b, &prod);
// 减法下溢检查
if (a < b) goto error;反推:
- 看
a和b是否来自用户态可控输入 - 通常前后会有
kmalloc(a+b)/RtlAllocateHeap(size)等分配,溢出后分配过小 → 后续 memcpy → 堆 OOB 写
PoC 思路:
- 让
a + b实际溢出回小值,但拷贝长度还按未溢出的算 - 边界值:
a = 0xFFFFFFF0, b = 0x100(32 位) /a = 0xFFFFFFFFFFFFFFF0, b = 0x100(64 位)
---
1.2 越界读 / 越界写
新增模式:
if (idx >= ARRAY_SIZE(arr)) return -EINVAL;
if (offset + len > buf_size) return STATUS_INVALID_PARAMETER;
if (len > sizeof(local_buf)) return -E2BIG;
// Windows 内核典型
if (InputBufferLength < sizeof(MY_STRUCT)) return STATUS_BUFFER_TOO_SMALL;
if (req->Length > KERNEL_MAX) return STATUS_INVALID_PARAMETER;反推:
- 看新增 if 守护了哪个后续操作(memcpy / memmove / 数组下标)
- 没守护前的版本可以传超大 length / 超大 index
PoC 思路:
- 把 length 字段设成超过 buf 的值
- 把 index 字段设成 array_size + N
- 用 IOCTL InputBufferLength 调到刚好绕过旧检查但触发新检查
---
1.3 竞争条件 (Race / TOCTOU)
新增模式:
// 加锁
KeAcquireSpinLockAtDpcLevel(&obj->Lock);
mutex_lock(&inode->i_mutex);
spin_lock(&list->lock);
// 引用计数原子化
InterlockedIncrement(&obj->RefCount);
ObReferenceObject(obj);
get_file(file);
// 检查 - 操作之间不再释放锁反推:
- 加锁说明此前同一对象在并发场景下被改了
- 引用计数加 ref 说明此前对象可能在用的时候被释放
- 找两个 syscall 路径,其中一个会修改 / 释放对象,另一个会用对象
PoC 思路:
// 经典 hammer
DWORD WINAPI thread_close(LPVOID arg) {
while (running) CloseHandle((HANDLE)arg);
return 0;
}
DWORD WINAPI thread_use(LPVOID arg) {
while (running) DeviceIoControl((HANDLE)arg, IOCTL_X, ...);
return 0;
}
// CreateThread 各开几个,sleep 30s 等崩- 多线程绑核 (SetThreadAffinityMask) 提高竞争概率
- 用
_mm_pause()/sched_yield()控制窗口 - 配合 syscall pinning(同一对象的两个不同入口)
---
1.4 未初始化内存信息泄漏
新增模式:
// 字段清零
RtlZeroMemory(&output, sizeof(output));
memset(buf, 0, sizeof(buf));
output.reserved = 0;
output.padding = 0;
output._pad1 = 0;
// 整个结构归零再赋值
KeStackAttachProcess; output = (MY_STRUCT){0};反推:
- 旧版从内核栈 / 内核堆拷数据到用户态时没清 padding / reserved 字段
- padding 内残留前一次函数调用的栈数据 → 内核地址泄漏 (绕 KASLR)
PoC 思路:
- 反复调 IOCTL 取 output buffer
- 把每个字节按 8 字节解析,找像内核地址的值(Windows: 0xFFFFxxxx / Linux: 0xFFFFFFFFxxxxxxxx)
- 找规律 → 推 kbase
---
1.5 UAF / 引用计数错误
新增模式:
// Windows
if (InterlockedDecrement(&obj->RefCount) == 0) {
Free(obj);
return;
}
// Linux
if (refcount_dec_and_test(&obj->ref)) {
kfree(obj);
}
// 或新增 ObReferenceObject 在某条路径上(之前漏加 ref)反推:
- 旧版某条出错路径 free 了对象但调用者还会用
- 或者多路径同时持有指针,其中一条 free 后其他没感知
PoC 思路:
// spray → free → reuse
HANDLE objs[1000];
for (int i = 0; i < 1000; i++) objs[i] = CreateObject(...);
trigger_free(objs[500]); // 触发漏洞 free
spray_kernel_pool(0xDEADBEEFDEADBEEF, target_sz); // 占住被 free 的洞
use_after_free(objs[500]); // 触发用 -> 控数据- Windows 内核常用
NtAllocateReserveObject/ Pipe 属性 / Window Class 名称喷 - Linux 常用
msgsnd/setxattr/userfaultfd-stalled vma 喷
---
1.6 用户态指针未校验
新增模式:
// Windows
ProbeForRead(UserBuffer, Length, sizeof(ULONG));
ProbeForWrite(UserBuffer, Length, sizeof(ULONG));
// __try/__except 包裹访问
__try {
Probe...
RtlCopyMemory(KernelBuf, UserBuffer, Length);
} __except(EXCEPTION_EXECUTE_HANDLER) { ... }
// Linux
if (!access_ok(VERIFY_WRITE, ptr, size)) return -EFAULT;
copy_from_user(kbuf, uptr, len);反推:
- 旧版直接 deref 用户态指针没 ProbeForRead/access_ok
- 用户态可以传内核地址 → 任意地址读 / 任意地址写
PoC 思路:
- 传一个内核空间指针(例如 ntoskrnl 某个全局变量地址)作为输入指针
- 内核执行
*UserBuffer = X时实际写到内核地址 → 任意写
---
1.7 权限校验缺失
新增模式:
if (!SeAccessCheck(...)) return STATUS_