SuperAPI AI Directory

re-patching

General & other

View repository

Updated 10-15 21:34

About this skill

Patch authoring (byte-level patch / instruction rewriting)

When to use / when not to use

  • Boundary (inherits [[re-cracking]]): limited to own software / authorized testing / CTF·research environments; unauthorized software unlocking and commercial software bypass are prohibited;operate on a sample copy first(in [[re-ghidra]]/[[re-ida]] save a copy or export a temporary Program); directly patching the original sample is prohibited as it destroys the evidence chain
  • Use: when the validation point has already been located from [[re-license]] and bytes need to be changed to bypass validation / modify behavior (unlock features, skip validation)
  • Use: as a substitute for a keygen when the validation algorithm is irreversible (hash / asymmetric signature verification) (see [[re-keygen]] pitfall 1)
  • Use: for distribution scenarios (patch file is small, does not modify the original file for distribution)
  • Do not use: when the validation point has not yet been located (do [[re-license]] first)
  • Do not use: when a legitimate serial number needs to be generated for genuine users (algorithm is reversible → going through [[re-keygen]] is more elegant, does not modify the target)
  • Do not use: when only memory is changed without writing to disk (session-level hook goes through [[re-frida]]; modifications inside a debugger that are not saved do not count as a patch)
  • Note: validation of the patched version must be done in a sandbox ([[re-sandbox]], [[re-analyze/platform-tips]] highest principle); back up the original file before modification (see pitfall 1)

Tool preparation

Hex editor

  • Linux: apt install hexedit / dnf install hexedit / pacman -S hexedit, verify hexedit --version(interactively press F2 to save)
  • Windows: HxD (official free download, portable version requires no installation); or 010 Editor (commercial, strong template parsing)
  • macOS: brew install hexedit
  • WSL: the Linux version inside WSL; for cross-boundary file editing use HxD on the Windows side ([[re-analyze/platform-tips]] WSL branch)

Debugger (by OS, to locate the patch point / confirm branch direction)

  • Debugging PE under Linux / Wine: [[re-gdb]] —— apt install gdb, verify gdb --version;Wine direct read:wine sample.exe then gdb -p <pid> attach ([[re-analyze/platform-tips]] Linux branch)
  • Windows: [[re-x64dbg]] —— official release zip, verify: loading the sample allows single-stepping (attach requires administrator privileges, [[re-analyze/platform-tips]] Windows branch)
  • macOS: [[re-lldb]](brew install lldb); Windows targets go through Wine + gdb or a VM
  • Purpose: breakpoints confirm the real direction of the success / failure branch, avoiding changing the wrong branch (see pitfall 2)

rizin(wx writes bytes, the main workhorse for command-line patching)

  • For the installation matrix see [[re-radare2]] (distribution varies by distro: Debian/Ubuntu distro repositories have no rizin package,apt install rizin will fail, switch to the official release binary or rz-pm)
  • Verify: rizin -v
  • Write-byte syntax: wx 90 @ 0x401234(write one byte at 0x401234 as 0x90),wx 909090 @ 0x401234(write three in a row)

Patch file tools (export diff / patch)

  • rz-diff(bundled with rizin, binary diff comparison) / radiff2(bundled with radare2, command-compatible)
  • bsdiff / bspatch:Linux apt install bsdiff / macOS brew install bsdiff, verify bsdiff -h
  • xdelta3:Linux apt install xdelta3, verify xdelta3 -V
  • Windows side:llvm-objdump(LLVM suite) to compare raw byte segments

Operating steps

Execute in order, recording the result of each step (original bytes → modified bytes → offset, for evidence path see [[re-triage]]).Before modification cp sample.exe sample.bak and sha256sum archive(See pitfall 1).

  1. Locate the patch point (failure jump → change to jz / jnz):

    • Get the address from the checkpoint list in [[re-license]]; decompile to confirm the instruction form at that point:failure jump(after comparison jz fail / jnz fail) is a branch that treats validation failure as an immediate jump away
    • Dynamically confirm direction (inside sandbox): break the debugger at that branch, run once with a wrong serial number, observe the jump and flags ([[re-gdb]] / [[re-x64dbg]]) — only after confirming "which condition to change, where it jumps to" is it a bypass (see pitfall 2)
    • Record: patch address, original bytes, target form (e.g. jz 0x401300 → nop)
  2. Byte modification (nop / jump rewrite):

    • Invert conditional jump:jz ↔ jnz(x86 short jump 74 ↔ 75, near jump 0F 84 ↔ 0F 85); unconditional jump EB(rel8)/ E9(rel32) lands directly on the success path
    • Skip validation: at the decision point nop it out (90); or make the validation function return success directly: change the push ebp at the function start to mov eax,1; ret(B8 01 00 00 00 C3, adjusting for stack balance)
    • rizin write mode:
      rizin -w sample.exe
      [0x00401000]> wx 90 90 @ 0x00401234            # nop 掉两个字节的失败跳转
      [0x00401000]> wx 0f 85 00 00 00 00 @ 0x00401230 # jz(0F84) 改 jnz(0F85),注意 rel32 需重算
      [0x00401000]> q
      
    • hexedit directly:hexedit sample.exe, Ctrl+S to jump to address → modify bytes → F2 to save
    • Jump target offset must be recalculated: after rewriting a conditional jump (especially rel8 → rel32), re-encode the target address (see pitfall 5)
    • Immediately after modifying sha256sum sample.exe archive the intermediate artifact
  3. Checksum / self-check handling:

    • Symptom: running the patched version exits immediately / pops "file is corrupted" (see pitfall 3)
    • Locate: xref functions that reference the self image base address / data section start address, or search for CRC32 / MD5 computation calls (import table CheckSumMappedFile etc.); decompile to confirm the comparison target (file bytes vs expected value)
    • Handling: patch the self-check function to skip (similarly change the decision branch / mov eax,1; ret return early); or recompute the checksum value from the new bytes and write it back
    • Note: patch the self-check first, then patch the target logic, or do it all at once and fix the checksum uniformly — the order does not affect the result, but after each step re-run in the sandbox to confirm
  4. Patch export (binary diff / patch file):

    • Generate patch:bsdiff sample.bak sample.exe sample.patch(when distributing bspatch sample.exe sample_patched.exe sample.patch); or xdelta3 -e -s sample.bak sample.exe sample.patch
    • Record the patch content list (address → original bytes → new bytes),rz-diff -D sample.bak sample.exe output byte-by-byte differences for reference in the report (in radare2 use the equivalent radiff2 -D)
    • Cross-version adaptation: generate patches separately for different versions, and note the version number in the list (see pitfall 4)
  5. Multi-architecture (ARM change B instruction, etc.):

    • ARM32: unconditional jump B = EA(followed by a 24-bit offset); conditional branch BEQ = 0A、BNE = 1A(swapping them means inversion);NOP = 00 00 A0 E1(condition prefix adjustable)
    • Thumb: short jump B encoding E000 starts at; conditional branch inversion change cond high nibble (D0 EQ ↔ D1 NE)
    • AArch64: B = 0x14000000 | (imm26 << 0)(±128MB);B.EQ = 0x54000000 | (cond<<12)(EQ means cond=0x0);NOP = D503201F
    • Use [[re-ghidra]] / [[re-radare2]] to disassemble and confirm the original encoding of the target instruction before writing; after modification, disassemble again to verify the instruction parses normally
    • Android hardening / so-layer verification note: unpack first ([[re-anti-analysis]] / [[re-memdump]]) then patch; what you patch is the unpacked artifact (see [[re-mobile]])

Verification:sha256sum Record artifacts → re-run in sandbox ([[re-sandbox]]) — the patched version must passAllcheckpoints ([[re-license]] checklist), and functional behavior must match the original sample; then statically decompile to confirm the patched instruction form is correct.

Cross-domain combination

  • [[re-cracking]]: this gateway is step 4 (patching) of the re-cracking workflow; step 6 handles unified verification
  • [[re-license]]: upstream — the patch points come from its checkpoint list (address + branch direction)
  • [[re-keygen]]: interchangeable relationship — prefer keygen when the algorithm is reversible; use this skill when irreversible or for quick bypass
  • [[re-anti-analysis]]: unpack packed samples first (locate patch points after OEP); self-check/anti-tamper often stacks with packing
  • [[re-binary-core]]: decompilation workbench ([[re-ghidra]] / [[re-ida]] / [[re-radare2]]'s Patch Instruction can also be written directly)
  • Dynamic: [[re-gdb]] / [[re-x64dbg]] / [[re-lldb]] (confirm branch direction), [[re-frida]] (session-level hook alternative that does not write to disk)
  • [[re-sandbox]]: patched-version re-run verification sandbox ([[re-analyze/platform-tips]] highest principle)
  • [[re-ioc]]: patch byte signatures / typical bypass patterns can serve as detection signatures

Common pitfalls and traps

  • Wrong branch modified → abnormal functionality: symptom — after patching, startup verification passes, but the program crashes / shows infinite popups / behaves erratically; cause — what was modified is not the authorization check (you patched a business branch), or the condition direction was reversed (what should be nop'ed was changed to a jump); countermeasure — in step 1, first dynamically confirm the real branch direction (run once with an erroneous serial number and watch the jump), make only minimal changes (1-2 bytes), verify each change as you make it
  • Self-check detects modification: symptom — the patched version exits immediately in the sandbox / pops up "file is corrupted" / restarts endlessly; cause — the program compares its own bytes (CRC / file size / section check), and after patching they do not match; countermeasure — in step 3, locate the self-check function (xref the image base / search for CRC calls) and patch it to skip or recompute the check value; back up the original bytes first for easy rollback
  • Patch fails across versions: symptom — the same patch is ineffective on another version or breaks the program; cause — offset changes between versions / version-number detection; countermeasure — in step 4, export patches separately per version and annotate the version; before patching, first confirm the target address bytes match expectations (xxd / hexedit spot check); if they do not match, the version is wrong — do not force the write
  • Signature (Authenticode) invalidation prompt: symptom — running the patched version on Windows pops up "publisher cannot be verified / file is corrupted"; cause — patching breaks the code-signing directory; countermeasure — this is expected: the prompt can be ignored, or the signature can be removed.pesign -S -i signed.exe First check the signature count (-u N can specify the Nth one);pesign -r [-u N] -i in.exe -o out.exe remove the signature and output a new file (keeping the original file). These are two different operations:-r rewrites the PE to remove the WIN_CERTIFICATE certificate table entry; if you only want the loader to stop checking the signature, you can also just zero out the offset+size of the PE Security directory entry (DataDirectory[SECURITY]), while the certificate table remains in the file. Note that -d/--digest-type is for --hash to specify the digest algorithm, not to delete the signature. Signatures are irreversible; do not forge them
  • Jump offset miscalculated → jump goes wild: Symptom—crashes immediately on run after patch / undefined instructions appear in disassembly; Cause—when rewriting a conditional jump (rel8 ↔ rel32) or jmp , the target offset was not recalculated (relative offset = target − address of next instruction); Countermeasure—use rizin / Ghidra disassembly to verify that the patched instruction is decoded correctly and the jump target is correct, prefer preserving the rel8 form for short distances, and only use rel32 when out of range
  • Blind-patching patch points / entry form not verified: Symptom—after writing the gadget at a fixed offset, it crashes or has no effect at all even though the bytes are clearly in place; Cause—the entry bytes are not the standard prologue form for that platform/runtime (such as a push-based function prologue), or the write lands outside the entry; substituting an unverified constant slot/resource return value for an object return, causing a native crash due to type mismatch; changing BL to NOP while the stack accounting on the two sides is unbalanced (there is a push before but no pop after); Countermeasure—verify the entry byte form before placing the gadget, never blindly write at offset 0; place the gadget on the first conditional branch inside the function prologue where the frame has not yet been reallocated; when removing a component/object, prefer "return null" rather than using an unverified constant return; changing a call to NOP is safe only when the return value is subsequently overwritten; for boolean/gate logic, prefer branch flipping (1 word, low risk), and for removing an entire function use rewriting methods such as returning null (source: reverse-skills (inliver233), MIT)
  • Patch blast radius: shared callers / hot entry points / shared flags: Symptom—flipping one condition causes an entire page to go blank, startup to hang, or popups to pile up, while the patch bytes are in place and the logic direction is correct; Cause—the function containing the condition is shared by multiple callers (a rendering load-bearing wall), and forcing the condition kills all callers together; hot entry points (async state machines / startup controllers) are emptied and starve the subsequent callback graph; the flipped flag is dual-purpose (display gate + anti-tamper signal) and cannot be distinguished statically; the branch may consume a value later reloaded from the stack, so patching the first load has no effect; Countermeasure—first prove that the target caller is unique before flipping the condition, and do not touch branches of functions shared by multiple callers; patch internal calls/branches rather than hot entry points; do not touch shared flags, instead change the rendering layer (opacity / offscreen / empty child component); conditional branches should patch the source register's last writer (consumption point) rather than the first load; if three structurally distinct straight paths all fail (flipping the condition gives a white screen / changing the return crashes / runtime flipping cannot be statically expressed), it means it is locked by a runtime invariant, so stop retrying, switch abstraction layers, or switch targets (source: reverse-skills (inliver233), MIT)
  • NOP safety is per-function: Symptom—the same NOP technique works in function A but breaks the startup flow in function B; Cause—calls that can be safely NOPed have an exact byte template (envelope: push → call → pop → return value overwritten → return), and blind NOPs outside the template break stack balance or type constraints, and safety differs per function and cannot be generalized from A to B; Countermeasure—first mechanically classify and scan call sites that can be safely NOPed according to the byte template, then verify them one by one instead of guessing; independently regression-test each function (especially that the startup flow can still complete normally); if the symptom still persists after NOPing all known orchestration calls (for example, it appears much later), it means this is not the path, so triage from a different source; converge the minimal patch surface—a controlled block that references only one non-critical resource is an ideal branch-flip target (source: reverse-skills (inliver233), MIT)