Binary Patching & x64dbg
Binary Patching คือการแก้ instruction ใน binary เพื่อเปลี่ยน logic โดยไม่ต้องรู้ input ที่ถูก — บังคับ check ให้ผ่านเสมอด้วยการ flip conditional jump (je↔jne), NOP ส่วน check ทิ้ง, หรือ force return value บทนี้ลงลึก workflow การ patch ใน x64dbg, Ghidra, radare2 และ Binary Ninja, ความต่างระหว่าง patch กับ keygen, ปัญหา checksum/integrity check และ walkthrough โจทย์ crackme (เนื้อหาเพื่อฝึกใน lab/CTF/ไฟล์ที่ได้รับอนุญาต)
1. แนวคิด patching
โจทย์ reversing จำนวนมากมี check แบบ if (correct) win() else fail() แทนที่จะเสียเวลาหา input ที่ถูก เราอาจ แก้ instruction ให้ check ผ่านเสมอได้เลย ระดับที่แก้คือ machine code (opcode) — เปลี่ยน byte ที่ตรงกับ instruction ที่ตัดสินใจ เช่นเปลี่ยน conditional jump ให้กระโดดสวนทาง, ลบ instruction check ทิ้ง, หรือบังคับให้ฟังก์ชันตรวจสอบ return ค่า 'ผ่าน' เสมอ
x64dbg เป็น debugger open-source ยอดนิยมบน Windows สำหรับ step, ดู register/memory, และ patch realtime แล้ว save กลับเป็นไฟล์ ส่วน static patch (แก้บนดิสก์โดยไม่รัน) ทำได้ใน Ghidra, radare2, Binary Ninja — เลือกตามสถานการณ์ (โปรแกรมมี anti-debug ก็ patch static ง่ายกว่า)
2. หัวใจ — conditional jump ทำงานอย่างไร
หลัง cmp/test CPU ตั้ง flag (ZF, CF, SF, OF) แล้ว conditional jump ตัดสินใจกระโดดจาก flag เหล่านั้น การ patch คือเปลี่ยน jump ให้ตีความ flag กลับด้าน หรือกระโดดเสมอ/ไม่กระโดดเลย
| instruction | กระโดดเมื่อ | opcode (rel8) | flip เป็น |
|---|---|---|---|
| je / jz | ZF=1 (เท่ากัน) | 74 | jne (75) |
| jne / jnz | ZF=0 (ไม่เท่า) | 75 | je (74) |
| jg / jnle | มากกว่า (signed) | 7F | jle (7E) |
| jl / jnge | น้อยกว่า (signed) | 7C | jge (7D) |
| ja / jnbe | มากกว่า (unsigned) | 77 | jbe (76) |
| jmp (short) | เสมอ (unconditional) | EB | — (บังคับไป win) |
| nop | ไม่ทำอะไร | 90 | ใช้ลบ instruction ทิ้ง |
3. หาจุด patch — String references
- Search for String references: ค้น string ผลลัพธ์ ('Correct!', 'Access Granted', 'Wrong', 'flag') → double-click ไปยังโค้ดที่ใช้ string นั้น → มองหา conditional jump 'เหนือ' จุดที่พิมพ์ success
- Intermodular calls: หา call สำคัญ เช่น
strcmp,memcmp,MessageBoxA— check มักอยู่ก่อน call แล้วตามด้วย jump - ดู flag/register ตอน breakpoint: ตั้ง breakpoint ที่
cmpแล้วดูค่าที่เทียบจริง (บางทีเห็น serial/flag ตรงๆ ไม่ต้อง patch ด้วยซ้ำ) - xref จาก win function: ถ้ารู้ฟังก์ชัน win หาว่าใครเรียก → ย้อนไปจุด check
cmp/test + conditional jump เหนือมัน = จุด patch วิธีนี้ใช้ได้เหมือนกันทั้งใน x64dbg, Ghidra, radare2 (ดู ghidra)4. x64dbg workflow (dynamic patch)
| คีย์/เมนู | ทำอะไร |
|---|---|
| F9 | run / continue |
| F8 | step over |
| F7 | step into |
| F2 | toggle breakpoint |
| spacebar | assemble instruction ใหม่ (แก้ ณ จุดนั้น) |
| Right-click > Binary > Fill with NOPs | NOP ช่วงที่เลือกทิ้ง |
| Ctrl+P | Patches manager → Patch File (save) |
| Right-click > Search for > String references | หา string หาจุด check |
# 1) เปิดไฟล์ใน x64dbg → F9 จนถึงจุดสนใจ (หรือตั้ง bp ที่ check)
# 2) หาจุด check:
# Right-click > Search for > All Modules > String references
# double-click 'Wrong password' → ไปที่โค้ด → มองขึ้นบนหา je/jne
# 3) แก้ instruction: คลิกบรรทัด → spacebar → พิมพ์ instruction ใหม่
# เดิม: je crackme.401080 (74 xx)
# แก้: jne crackme.401080 (75 xx) ← flip
# หรือ: jmp crackme.401080 (EB xx) ← บังคับกระโดดเสมอ
# หรือ: nop (90) ← ลบ check ทิ้ง
# 4) save: Right-click > Patches (Ctrl+P) > เลือกทุก patch > Patch File
# ได้ crackme_patched.exe ที่ผ่าน check ตลอด5. Static patch — Ghidra / radare2 / Binary Ninja
ถ้าโปรแกรมมี anti-debug (ดู anti-debug) หรือแค่อยากแก้บนดิสก์เร็วๆ ใช้ static patch ได้ — แก้ byte แล้ว export ไฟล์ใหม่โดยไม่ต้องรันโปรแกรมเลย
# เปิดในโหมดเขียน (-w)
r2 -w crackme
# ไปจุด check แล้วดู
[0x00400500]> s sym.main
[0x00400640]> pdf # ดู disassembly
# เจอ: 0x0040068a 74 0e je 0x40069a
# วิธี 1: assemble instruction ใหม่ ณ address
[0x0040068a]> s 0x0040068a
[0x0040068a]> "wa jne 0x40069a" # flip je -> jne
# วิธี 2: เขียน opcode byte ตรงๆ
[0x0040068a]> wx 75 # 74 -> 75 (je -> jne)
# วิธี 3: NOP ทั้ง instruction
[0x0040068a]> wao nop # แทนที่ด้วย nop ทั้งชุด
[0x0040068a]> q # เปลี่ยนเขียนลงไฟล์เลย (เพราะ -w)- Ghidra: Listing → คลิกขวา instruction → Patch Instruction (แก้ mnemonic เช่น JNZ→JZ) → เสร็จแล้ว File → Export Program เลือก format Original File เพื่อได้ binary ที่ patch แล้ว
- Binary Ninja: เลือก instruction → คลิกขวา → Patch → Invert Branch / Never Branch / Always Branch / Nop (มีเมนูสำเร็จรูป) แล้ว Save As
- radare2/rizin: เปิดด้วย
-wแล้วใช้wa/wx/wao nop(ตามตัวอย่างด้านบน) - hexedit ดิบ: รู้ file offset ของ byte (ไม่ใช่ virtual address) แล้วแก้ hex ตรงๆ — ระวังต่างระหว่าง VA กับ file offset
6. Patch vs Keygen
Patch = แก้ binary ให้ผ่าน check (เร็ว แต่ใช้ได้เฉพาะไฟล์ที่ patch และไม่ได้ 'flag' จริงถ้า flag ถูก derive จาก serial); Keygen = เข้าใจ algorithm ที่ตรวจ serial แล้วเขียนโปรแกรมสร้าง serial ที่ถูกต้อง ในโจทย์ CTF: ถ้าถาม 'ทำให้โปรแกรมบอก Correct' → patch พอ; ถ้าถาม 'flag คือ serial ที่ถูก' หรือ flag ถูกถอดรหัสด้วย key ที่คำนวณจาก input → ต้อง keygen/solver (เข้าใจ logic จริง)
7. Checksum / integrity check
บาง binary ตรวจ integrity ตัวเอง (self-checksum) — คำนวณ hash ของ .text section เทียบกับค่าที่ฝังไว้ ถ้า patch แล้ว hash เปลี่ยน โปรแกรมจะ crash/แจ้งว่าถูกแก้ วิธีรับมือ:
- หา + NOP ตัว checksum เอง: หาจุดที่คำนวณ/เทียบ checksum แล้ว patch ให้ผ่าน (patch จุดตรวจ แทนที่จะสู้กับมัน)
- Patch ที่ layer สูงกว่า: แก้ผลลัพธ์ของ check แทน byte ใน section ที่ถูก verify
- Runtime patch (x64dbg/Frida): แก้ใน memory ตอนรัน หลังจาก integrity check ผ่านไปแล้ว — ไม่แตะไฟล์บนดิสก์ (ดู
fridaสำหรับ dynamic patch) - คำนวณ checksum ใหม่: ถ้ารู้ algorithm ให้คำนวณค่าใหม่หลัง patch แล้วเขียนทับค่าที่ฝัง
8. CTF walkthrough — crackme (patch)
- 1รันโปรแกรม: ขอ password → พิมพ์มั่ว → ได้ 'Wrong password'
- 2เปิดใน x64dbg (หรือ Ghidra) → Search for String references → หา 'Wrong password'
- 3double-click → ไปยังโค้ด → มองขึ้นบนเจอ: cmp eax, 0 / test eax, eax แล้วตามด้วย je fail
- 4ตั้ง breakpoint ที่ cmp → F9 → ดูว่า eax คือผลของ strcmp/check function
- 5ตัดสินใจ: แค่ผ่าน → patch; flag = ผลถอดรหัส → ไป keygen
- 6patch (ถ้าแค่ผ่าน): เปลี่ยน je fail → jne fail (หรือ NOP je) → บังคับเข้า branch success
- 7save: Ctrl+P → Patch File → รัน binary ใหม่ → 'Correct!' / เห็น flag
# หลัง reverse ใน Ghidra เข้าใจว่า check ทำ:
# for i,c in enumerate(input): assert (c ^ 0x37) + i == table[i]
# → ย้อนกลับหา input ที่ผ่าน (keygen)
table = [0x6f, 0x63, 0x76, 0x64, 0x5c, 0x2d] # ค่าที่อ่านจาก binary
key = 0x37
flag = ""
for i, t in enumerate(table):
c = (t - i) ^ key # ย้อนสูตร: (t - i) แล้ว xor key
flag += chr(c)
print(flag) # ได้ serial/flag ที่ทำให้ check ผ่าน9. Quick Reference
- หาจุด check: Search String references → 'Wrong'/'Correct' → ดู cmp+jump เหนือมัน
- flip: je(74)↔jne(75), jg↔jle, ja↔jbe; บังคับ: jmp(EB); ลบ: nop(90)
- x64dbg: F9 run · F8/F7 step · F2 bp · spacebar assemble · Ctrl+P Patch File
- static: Ghidra Patch Instruction→Export; r2 -w แล้ว
wa/wx/wao nop; Binary Ninja Invert/Nop Branch - force success:
mov eax,1หลัง call check - flag = serial/ผลถอดรหัส → keygen (solver); แค่ผ่าน → patch
- เจอ self-checksum → NOP ตัว check เอง หรือ runtime patch (Frida)
- ระวัง VA ≠ file offset ตอน hexedit ดิบ
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเข้าใจ logic การเช็คแล้ว (จาก ghidra) แค่ต้องการให้โปรแกรมบอกว่า 'ถูก' โดยไม่ต้องรู้ input จริง มีแค่ Kali (x64dbg เป็นเครื่องมือ Windows ไม่มีใน Kali) ทำตามนี้ทีละขั้นด้วย radare2/ghidra แทน
- 1หาจุด check ก่อนด้วย ghidra: search string 'Wrong'/'Correct' แล้วกด X ดู xref
- 2จดตำแหน่ง address ของ conditional jump ที่อยู่เหนือจุดพิมพ์ผลลัพธ์ (เช่น je 0x4006xx)
- 3ตัดสินใจ: ต้องการแค่ผ่าน (patch) หรือ flag = serial/ผลถอดรหัส (ต้องเขียน solver/keygen แทน)
- 4ถ้า patch: เปิด r2 -w ./binary (โหมดเขียน)
- 5s ไปที่ instruction แล้ว pdf ดู disassembly ยืนยัน byte ก่อนแก้
- 6แก้ instruction: 'wa jne 0x....' หรือ 'wx 75' (flip je→jne) หรือ 'wao nop' (ลบ check ทิ้ง)
- 7q ออก (r2 -w เขียนกลับไฟล์ทันทีตอนแก้) แล้วรัน ./binary ใหม่ดูผล
- 8ถ้ามี self-checksum ตรวจจับการแก้ → หา + NOP จุดคำนวณ/เทียบ checksum เองด้วย
- 9ถ้า flag ที่ print ออกมาเป็นขยะ (เพราะ flag ผูกกับ input จริง) → กลับไปเขียน solver แทน patch
- 10ยืนยันผลลัพธ์สุดท้าย: 'Correct' ปรากฏ หรือได้ flag จริง
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| หาจุด check/xref | ghidra | apt install ghidra | dogbolt.org |
| patch แบบ static (เขียนไฟล์ตรง) | radare2 (r2 -w) | apt install radare2 | - |
| patch/debug runtime ก่อน save | gdb | apt install gdb | - |
| disassemble ยืนยัน opcode | objdump, xxd | - | onlinedisassembler.com |
| hook แทนการแก้ไฟล์ | frida | pipx install frida-tools | - |
| ตรวจ hex/byte offset ดิบ | xxd, hexdump | - | - |
| คำนวณ/เขียน solver | python3 | - | CyberChef, dcode.fr |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้