Patch-to-PoC Pipeline Skill

SkillSecurity

The end-to-end patch-diff vulnerability reproduction workflow — patch analysis (read diff, identify protective pattern, hypothesize bug class), source or binary-only code path walking (Ghidra + BinDiff), PoC generation (manual craft OR AFL++/libFuzzer harness with ASan/UBSan), CyberGym-style differential verification (vuln crashes, patched clean) as the deterministic stop condition, and YARA + Sigma detection rule authoring tested against both versions. Covers 2024-2026 CVEs (libwebp, xz-utils, runc, glibc Looney Tuner, regreSSHion, MOVEit, Jenkins, Confluence, TeamCity, OFBiz). Solidifies validation/scenarios/SCEN-008.md into a reusable knowledge base and wires in Schema 3 reproduction memory for memory-driven convergence.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Patch-to-PoC Pipeline Skill skill

What this skill tells your AI

The instructions your AI receives, as published by brucesongs/kali-claw in skills/patch-to-poc-pipeline/SKILL.md and read by ahel’s review.

"The patch is a confession. Read it carefully and it will tell you exactly where the bug lives, what shape it has, and how to walk to it from any front door." — traditional vuln-research maxim, paraphrased from Project Zero's "Patch Gapping" methodology.

Summary

The patch-to-poc-pipeline is kali-claw's workflow skill for turning a published patch diff into a working PoC plus detection coverage. It is the pipeline itself: the discipline of reading a diff, forming a bug-class hypothesis, walking the code path from attacker-controlled input to the patched sink, generating a trigger input (manually or via fuzzer harness), differentially verifying the input crashes the pre-patch binary and leaves the post-patch binary clean, and finally shipping YARA + Sigma rules that fire on the vulnerable pattern and the exploitation telemetry respectively. This skill solidifies the methodology of validation/scenarios/SCEN-008.md into a reusable knowledge base — the scenario is the per-CVE runbook; this skill is the standing capability that executes it.

The skill is distinct from its constituents: it does not teach Ghidra decompilation (binary-reverse / reverse-engineering-advanced own that), full exploit construction (exploit-development owns ROP / shellcode / heap feng shui), fuzzer operation (ai-fuzzing owns AFL++/libFuzzer tuning), or generic Sigma/YARA authoring discipline (detection-engineering owns that). What this skill owns is the orchestration contract between them — the bug-class hypothesis taxonomy that drives Phase 1, the call-chain walking pattern that drives Phase 2, the harness-vs-manual decision matrix that drives Phase 3, the CyberGym-style differential stop condition that drives Phase 4, and the Schema 3 reproduction memory that knits every phase into a memory-driven convergence loop.

The strategic value of this skill is calibration. CyberGym (ICLR 2026, UC Berkeley; 1,507 CVEs across 188 OSS projects) evaluates AI agents on exactly this task: given pre-patch source, produce a PoC that crashes the vulnerable build but not the patched build, offline. kali-claw's Q3 2026 external calibration goal is to run this pipeline against a curated CyberGym subset and produce a public success-rate number. Each phase emits a Schema 3 memory delta; the runner halts only when verification_results.vulnerable.crashed == true AND verification_results.patched.crashed == false — any other terminal state fires an anti-pattern alert. This is the MopMonk "三招" (structured memory + memory-driven convergence + shared-memory multi-agent) applied to vulnerability reproduction.

This skill is the meta-pipeline for SCEN-008-class work. When you have a patch in hand and need a PoC plus detection coverage by morning, this is the door you walk through.

Distinct from adjacent skills

SkillScopeBoundary with this skill
binary-reverseRE techniques (disassembly, decompilation, Ghidra basics)Consumed by Phase 2 when source is unavailable
reverse-engineering-advancedDeep RE (type recovery, angr symbolic execution, decompiler-aided analysis)Consumed by Phase 2 for binary-only call-chain reconstruction
exploit-developmentFull exploit construction (ROP, shellcode, heap grooming, GOT overwrite)This skill stops at a crashing PoC — weaponization is out of scope
ai-fuzzingFuzzer operation (corpus curation, mutation strategies, AFL++ tuning, coverage maps)Consumed by Phase 3 when Strategy B (fuzzer harness) is chosen
detection-engineeringGeneric Sigma/YARA authoring discipline, FP tuning, CI/CD for rulesConsumed by Phase 5 for the craft of writing rules; this skill owns the specific YARA/Sigma patterns for the patched bug class
verification-loopGeneral verification state-machine for any engagementThis skill uses a specialized differential stop condition (vuln-vs-patched)
supply-chain-securitySBOM-driven software factory defense, dependency confusionConsumed post-PoC for fleet-scale rollout of the new YARA rule
patch-to-poc-pipeline (this)The pipeline that orchestrates the above — bug-class taxonomy, 5-phase contract, CyberGym differential stop condition, Schema 3 reproduction memoryOwns the workflow itself

Use Cases

Reconnaissance & Triage

  1. Acquire patch from a CVE advisory — pull *.patch from a distro gitweb, GitHub advisory, or the upstream commit ref
  2. Triage patch severitygit diff --stat to scope blast radius; classify as "adds a check" (informative), "refactor" (low signal), or "backdoor" (xz-utils special case)
  3. Cross-reference patch to CWE — match protective pattern to CWE (bounds check → CWE-787/125, integer guard → CWE-190, free + NULL → CWE-416, etc.)
  4. Rank candidate CVEs for reproduction by ROI — exploitability × deployment breadth × patch recency
  5. Detect malicious patches (xz-utils case) — triage for obfuscated control flow, IFUNC hooks, build-system tampering
  6. Map patch to MITRE ATT&CK detection coverage — pre-stage the Sigma rule's ATT&CK tags before Phase 5

Phase 1 — Patch Analysis

  1. Read unified diff and identify the protective pattern — bounds check, type check, sanitize, length validation, NULL check, integer-overflow guard, capability drop
  2. Compute file/line hunk statslines_added vs lines_removed; patches that only add are most informative
  3. Hypothesize bug class from the 12-class taxonomymemory_corruption | integer_overflow | type_confusion | auth_bypass | path_traversal | sqli | xss | ssrf | use_after_free | oob_read | oob_write | race_condition
  4. Identify suspected vulnerable function — usually the function receiving the new check or the file most heavily modified
  5. Initialize Schema 3 memory with Phase 1 delta — patch_analysis.{key_change, suspected_vuln_function, suspected_vuln_type, confidence}
  6. Apply memory-driven convergence check — abort Phase 2 if patch_analysis.key_change is empty (招二: no-evidence path switch)

Phase 2 — Code Path Walking

  1. Source-available walkgrep -rn <vuln_func> + call graph from public entry point to sink
  2. Binary-only walk — Ghidra headless decompile + BinDiff "changed functions" diff
  3. Measure attacker-input distance — call-depth from public API (e.g., WebPDecode → VP8LDecodeHeader → BuildHuffmanTable, distance 4)
  4. Recover types from stripped binaries — apply Ghidra Auto Type + angr Typehoon if symbols stripped
  5. Pin down the tainted variable — the exact field of attacker input that reaches the sink
  6. Write Phase 2 memory deltacode_path.{entry_function, call_chain_to_vuln, input_to_vuln_distance}

Phase 3 — PoC Generation

  1. Choose strategy via decision matrix — manual craft (well-understood bug, fast) vs fuzzer harness (subtle bug, thorough)
  2. Manual craft via hex editor / Python struct — take a valid sample, mutate the field that controls the vulnerable parameter
  3. Author AFL++/libFuzzer harnessLLVMFuzzerTestOneInput calling the public API with attacker bytes
  4. Compile with sanitizer matrix-fsanitize=fuzzer,address,undefined (ASan + UBSan) for memory bugs; MSan for uninitialized reads; TSan for race conditions
  5. Construct seed corpus — valid samples from project's test suite + boundary inputs (max sizes, zero lengths, off-by-one)
  6. Run fuzzer with budgetmax_total_time=1800 for first pass; record crashes to artifact_prefix=/work/crashes/
  7. Triage crash with ASanasan_symbolize to map stack frames to source lines; verify the crashing function matches Phase 1 hypothesis
  8. Apply convergence rule — if 3 candidate inputs fail to crash, switch strategy (manual ↔ fuzzer); log failed_attempts delta

Phase 4 — Differential Verification

  1. Build patched binary with identical harness + sanitizer flags
  2. Run identical PoC against both versions — capture exit codes and ASan traces separately
  3. Apply CyberGym stop condition — vuln crashes AND patched clean = CONFIRMED; any other combination = loop back
  4. Detect wrong-root-cause failure — both crash → re-enter Phase 1 with new hypothesis
  5. Detect PoC-doesn't-reach-bug failure — neither crashes → re-enter Phase 3 with new candidate input
  6. Emit convergence event[convergence] event=POC_CONFIRMED_DIFFERENTIALLY stop_condition_met=true iterations=N

Phase 5 — Detection Rule Authoring

  1. Author YARA rule for vulnerable pattern — match function name + missing-guard regex; covers source + binary symbol
  2. Test YARA against both versions — MUST match libfoo-1.8.2.so, MUST NOT match libfoo-1.8.3.so
  3. Author Sigma rule for exploitation telemetry — process loading vulnerable .so + accessing crafted file extension, or auth-bypass URL pattern
  4. Validate Sigma syntaxsigma check then convert to Splunk / KQL / EQL backends
  5. SBOM-driven fleet rolloutsyft + grype --only-fixed to find every vulnerable deployment
  6. Emit final Schema 3 memory deltaverification_results, convergence_state.status=POC_CONFIRMED_DIFFERENTIALLY
  7. Generate reproduction report — markdown writeup + JSON memory snapshot for the CyberGym submission format
  8. Retire detection rule post-patch-cycle — track lifecycle; auto-suppress Sigma after fleet confirmed patched

Core Tools

Patch Acquisition & Forensics

ToolVendor / ProjectRole
gitgit projectgit diff, git log -p, git format-patch for diff acquisition
patch / git applyGNU / gitApply/reverse patches during reproduction
diffstatdiffstat projectHunk-level summary statistics
codespell / regex_scanOSSSurface suspicious strings in patches (xz-utils backdoor detection)

Reverse Engineering (binary-only path)

ToolRole
Ghidra (NSA, 11.x)Headless decompile: analyzeHeadless + DecompileFunction.java
BinDiff (Google/Zynamics, 6)Diff two .so files; mark "changed functions" as candidate vuln sites
radare2Quick aaa + pdf @ sym.vuln_func for inline disassembly
angrSymbolic execution to walk tainted branches when source unavailable
pwntoolsCrash inspection, ELF parsing, PoC packaging

Fuzzing & PoC Generation

ToolRole
AFL++Coverage-guided fuzzer; afl-fuzz -M main -S worker1 parallel mode
libFuzzer (LLVM)In-process fuzzer; LLVMFuzzerTestOneInput harness pattern
clang-fsanitize=fuzzer,address,undefined one-shot compile
asan_symbolizeResolve ASan stack traces back to source lines
afl-cmin / afl-tminCorpus minimization + crash-input minimization

Detection & Fleet Scanning

ToolRole
YARA (VirusTotal, 4.x)Binary + source pattern rules; yara -s rule.yar target.so
sigma-cli (SigmaHQ)Sigma rule authoring + sigma check + sigma convert -t splunk/kql/eql
syft (Anchore)SBOM generation: syft image:tag -o json
grype (Anchore)SBOM-driven vulnerability matching: grype sbom:sbom.json --only-fixed
jqMemory delta application; CyberGym JSON snapshot manipulation

Methodology — The 5-Phase Pipeline

The pipeline is a memory-driven state machine. Each phase reads Schema 3 memory, executes its task, writes a delta, and emits a convergence check. The runner halts only on the CyberGym stop condition (Phase 4 pass) or an anti-pattern abort.

Phase 1 — Patch Analysis

Goal: read the patch, identify the protective pattern, hypothesize the bug class, and pick the suspected vulnerable function.

Method:

  1. Acquire patch: git diff <vuln_tag> <patched_tag> -- <pathspec> > CVE-XXXX-YYYYY.patch
  2. Compute stats: git diff --stat, diffstat -p CVE-XXXX-YYYYY.patch
  3. Classify protective pattern (bounds check | type check | sanitize | length validation | NULL check | integer-overflow guard | capability drop | auth check | backdoor — special case)
  4. Map pattern → CWE → bug class via the 12-class taxonomy
  5. Identify suspected vulnerable function (recipient of new check)
  6. Write Schema 3 delta: patch_analysis.{files_changed, lines_added, lines_removed, key_change, suspected_vuln_function, suspected_vuln_type, confidence}

Memory contract (Schema 3 before/after):

FieldBeforeAfter Phase 1
patch_analysis{}populated, 7 fields
patch_analysis.confidencenull0.6 - 0.9 (heuristic)
convergence_state.iterations01

Convergence trigger: if patch_analysis.key_change is empty after this phase, abort — no point walking code paths without a hypothesis (招二: memory-driven convergence).

Phase 2 — Code Path Walking

Goal: trace attacker-controlled input from the public API surface down to the patched sink.

Source-available path:

grep -rn "<vuln_func>" /targets/<pkg>-<vuln_ver>/src/
# Build call graph from public entry to vuln function
ctags -R /targets/<pkg>-<vuln_ver>/ && your_callgraph_tool

Binary-only path:

/opt/ghidra/support/analyzeHeadless /work proj \
  -import /targets/<pkg>-<vuln_ver>.so \
  -postScript DecompileFunction.java -scriptPath /work/scripts \
  -functionName <vuln_func>

bindiff /targets/<pkg>-<vuln_ver>.so /targets/<pkg>-<patched_ver>.so \
  -o /work/<pkg>.BinDiff
# BinDiff marks <vuln_func> as "changed" — start there

Memory contract:

FieldBeforeAfter Phase 2
code_path.entry_functionnull"main() → parse_input()"
code_path.call_chain_to_vuln[]["main", "parse_input", "decode_chunk"]
code_path.input_to_vuln_distancenull3

Convergence trigger: if no path exists from public API to vuln function (distance = -1), abort — the bug may be unreachable from attacker input (defender's win, but no PoC).

Phase 3 — PoC Generation

Goal: produce a candidate input that triggers the patched bug on the vulnerable version.

Strategy decision matrix:

ConditionStrategy
Bug class well-understood (memory_corruption from obvious overflow)A — Manual craft
Bug class subtle (type_confusion, race_condition)B — Fuzzer harness
Public test corpus existsA first, B as backup
Phase 1 confidence < 0.7B mandatory (manual likely to miss)
Sanitizer crash already observed in OSS-Fuzz trackerA — clone and minimize

Strategy A — Manual craft: take a valid sample, mutate the field that controls the vulnerable parameter with a hex editor or python3 -c 'import struct; ...'.

Strategy B — Fuzzer harness:

#include "<pkg>/<api>.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    <api>decode(data, size, NULL);
    return 0;
}

Memory contract:

FieldBeforeAfter Phase 3
candidate_inputs[].test_statusPENDINGVULNERABLE_CRASHED | CLEAN_NO_CRASH
candidate_inputs[].asan_evidencenull"ERROR: AddressSanitizer: heap-buffer-overflow on address 0x..."
convergence_state.iterations1N (one per candidate)

Convergence trigger (招二): if test_status stays PENDING after 3 candidates, switch strategy (A ↔ B). Increment failed_attempts; force path switch at >= path_switch_threshold.

Phase 4 — Differential Verification (the CyberGym Stop Condition)

Goal: confirm PoC crashes vulnerable AND leaves patched clean. This is the deterministic stop condition CyberGym scores on.

Decision matrix:

VulnerablePatchedVerdictNext
crashescleanCONFIRMEDPhase 5
crashescrashesWrong root causePhase 1 with new hypothesis
no crashno crashPoC doesn't reach bugPhase 3 with new candidate
no crashcrashesImpossibleRecheck build / harness

Memory contract (the convergence event):

FieldBeforeAfter Phase 4
verification_results.vulnerable.crashednulltrue
verification_results.patched.crashednullfalse
convergence_state.status"IN_PROGRESS""POC_CONFIRMED_DIFFERENTIALLY"
convergence_state.stop_condition_metfalsetrue

Stop condition: runner halts only when vulnerable.crashed == true AND patched.crashed == false. Any other terminal state with stop_condition_met=true fires the "Premature stop" anti-pattern alert (see SCEN-MEMORY-SCHEMA.md).

Phase 5 — Detection Rule Authoring

Goal: ship a YARA rule that fires on the vulnerable pattern across the fleet + a Sigma rule that fires on exploitation telemetry.

YARA: source pattern (function name + missing-guard regex) AND binary symbol pattern. Test: MUST match <pkg>-<vuln_ver>.so, MUST NOT match <pkg>-<patched_ver>.so.

Sigma: host/network telemetry rule — e.g., process loading <vuln_lib>.so AND accessing crafted file extension; or auth-bypass URL pattern in reverse-proxy logs. Convert to Splunk / KQL / EQL backends via sigma-cli.

Fleet rollout: syft + grype --only-fixed to find every deployment of the vulnerable version. Submit detection rule to detection-engineering CI for staged rollout.

Memory Schema Integration

This skill operates on Schema 3 — Patch-Diff Reproduction Memory (see validation/scenarios/SCEN-MEMORY-SCHEMA.md):

{
  "schema_version": "1.0",
  "task": {
    "vulnerable_version": "/targets/libfoo-1.8.2",
    "patched_version": "/targets/libfoo-1.8.3",
    "patch_file": "CVE-2024-12345.patch",
    "cve": "CVE-2024-12345"
  },
  "patch_analysis": { /* Phase 1 delta */ },
  "code_path": { /* Phase 2 delta */ },
  "candidate_inputs": [ /* Phase 3 delta */ ],
  "verification_results": { /* Phase 4 delta */ },
  "convergence_state": {
    "iterations": 4,
    "status": "POC_CONFIRMED_DIFFERENTIALLY",
    "stop_condition_met": true
  }
}

MopMonk "三招" applied:

  1. 招一 (Structured memory): every phase reads + writes JSON, never prose
  2. 招二 (Memory-driven convergence): 3 evidence-free attempts → path switch; empty patch_analysis.key_change → abort Phase 2
  3. 招三 (Shared-memory multi-agent): parallel agents claim distinct paths (patch-diff, harness-entry, sanitizer) against the same memory file via atomic writes + version vector

Anti-patterns the runner enforces:

Anti-PatternDetection
Free-form explorationmemory_lock.last_read_at is null when write attempted
Memory driftDecision-log entry references finding not in findings[]
Repeat-without-deltafailed_attempts >= 3 on same hypothesis
Path-claim deadlockactive_paths has duplicate values
Premature stopstop_condition_met=true but verification_results has null fields

Practical Steps

Step A — Acquire and triage the patch

# Example: CVE-2023-4863 libwebp heap buffer overflow
cd /targets/libwebp
git clone https://chromium.googlesource.com/webm/libwebp .
git checkout v1.3.2  # patched tag
git diff v1.3.1 v1.3.2 -- src/dec/huffman_dec.c > /work/CVE-2023-4863.patch
git diff v1.3.1 v1.3.2 --stat
# src/dec/huffman_dec.c | 17 ++++++++++-------
# 1 file changed, 14 insertions(+), 3 deletions(-)

diffstat -p /work/CVE-2023-4863.patch
# huffman_dec.c | 14 ++++++++++++--
# 1 file changed, 12 insertions(+), 2 deletions(-)

Step B — Identify protective pattern and hypothesize bug class

# View the diff
git diff v1.3.1 v1.3.2 -- src/dec/huffman_dec.c | less
# Key change: +if (table_size >= (1U << 31)) return NULL;
# Protective pattern: integer-overflow guard before calloc
# Bug class hypothesis: memory_corruption (heap-buffer-overflow from undersized alloc)
# CWE: CWE-787 Out-of-Bounds Write (root cause: CWE-190 Integer Overflow)

Apply Schema 3 delta:

tmp=$(mktemp)
jq '.patch_analysis = {
  "files_changed": ["src/dec/huffman_dec.c"],
  "lines_added": 14, "lines_removed": 3,
  "key_change": "Added overflow check on Huffman table size; rejects code-length sequences yielding >= 2^31 entries",
  "suspected_vuln_function": "BuildHuffmanTable()",
  "suspected_vuln_type": "heap-buffer-overflow",
  "confidence": 0.85
} | .convergence_state.iterations += 1' \
  /work/repro-attempt-memory.json > "$tmp" && mv "$tmp" /work/repro-attempt-memory.json

Step C — Walk the code path

# Source-available
grep -rn "BuildHuffmanTable" /targets/libwebp-1.3.1/src/
# src/dec/huffman_dec.c:187: static int BuildHuffmanTable(...) { ... }
# src/dec/vp8l_dec.c:412:   ok = BuildHuffmanTable(...);
grep -rn "VP8LBuildHuffmanTable\|VP8LDecodeHeader\|WebPDecode" /targets/libwebp-1.3.1/src/ | head
# Call chain (distance 4):
# WebPDecode → VP8LDecodeImageStream → VP8LDecodeHeader → VP8LBuildHuffmanTable → BuildHuffmanTable
# Binary-only alternative
/opt/ghidra/support/analyzeHeadless /work proj \
  -import /targets/libwebp-1.3.1.so \
  -postScript DecompileFunction.java -scriptPath /work/scripts \
  -functionName BuildHuffmanTable

bindiff /targets/libwebp-1.3.1.so /targets/libwebp-1.3.2.so \
  -o /work/libwebp-1.3.1_vs_1.3.2.BinDiff

Step D — Generate the PoC (Strategy B: AFL++/libFuzzer harness)

// /work/harness_huffman.c
#include "webp/decode.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    WebPDecode(data, size, NULL);
    return 0;
}
# Build with sanitizer matrix
clang -g -O1 -fsanitize=fuzzer,address,undefined \
  -I/targets/libwebp-1.3.1 \
  /work/harness_huffman.c /targets/libwebp-1.3.1/src/.libs/libwebp.a \
  -o /work/harness_vulnerable

# Seed corpus
mkdir -p /work/seeds && cp /targets/samples/*.webp /work/seeds/

# Run with 30-minute budget
ASAN_OPTIONS=detect_leaks=0 /work/harness_vulnerable /work/seeds/ \
  -max_len=65536 -max_total_time=1800 -artifact_prefix=/work/crashes/

# Inspect crash
# ==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200005cfe1
# WRITE of size 1 at 0x60200005cfe1 thread T0
#     #0 0x... in BuildHuffmanTable src/dec/huffman_dec.c:187
#     #1 0x... in VP8LBuildHuffmanTable ...

Step E — Differential verification (the CyberGym stop condition)

# Build patched harness (identical flags)
clang -g -O1 -fsanitize=fuzzer,address,undefined \
  -I/targets/libwebp-1.3.2 \
  /work/harness_huffman.c /targets/libwebp-1.3.2/src/.libs/libwebp.a \
  -o /work/harness_patched

# Run the SAME PoC against both
for variant in vulnerable patched; do
  ASAN_OPTIONS=symbolize=1:abort_on_error=1 \
    /work/harness_${variant} /work/crashes/crash-POC \
    > /work/${variant}.stdout 2> /work/${variant}.stderr
  echo "exit=$?" > /work/${variant}.exitcode
done

# Inspect
cat /work/vulnerable.exitcode  # exit=1 (ASan abort)
cat /work/patched.exitcode     # exit=0 (clean)
head -1 /work/vulnerable.stderr  # ERROR: AddressSanitizer: heap-buffer-overflow...
# Emit convergence event
jq -n --slurpfile v /work/vulnerable.exitcode --slurpfile p /work/patched.exitcode '{
  convergence_event: "POC_CONFIRMED_DIFFERENTIALLY",
  vulnerable_crashed: ($v[0]|tonumber > 0),
  patched_crashed: ($p[0]|tonumber > 0),
  stop_condition_met: (($v[0]|tonumber > 0) and ($p[0]|tonumber == 0))
}'

Step F — Author and test detection rules

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
71
Forks
18
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
patch-to-poc-pipeline
Source
github.com/brucesongs/kali-claw