คลัง
reverse

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/ไฟล์ที่ได้รับอนุญาต)

IntermediateAdvanced#patching#x64dbg#debugger#jump-patch#nop#keygen#radare2#ghidra

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 ง่ายกว่า)

เนื้อหานี้เพื่อฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, crackme, ไฟล์ของตัวเอง) เท่านั้น — การ patch ซอฟต์แวร์ลิขสิทธิ์เพื่อ bypass license ผิดกฎหมาย

2. หัวใจ — conditional jump ทำงานอย่างไร

หลัง cmp/test CPU ตั้ง flag (ZF, CF, SF, OF) แล้ว conditional jump ตัดสินใจกระโดดจาก flag เหล่านั้น การ patch คือเปลี่ยน jump ให้ตีความ flag กลับด้าน หรือกระโดดเสมอ/ไม่กระโดดเลย

instructionกระโดดเมื่อopcode (rel8)flip เป็น
je / jzZF=1 (เท่ากัน)74jne (75)
jne / jnzZF=0 (ไม่เท่า)75je (74)
jg / jnleมากกว่า (signed)7Fjle (7E)
jl / jngeน้อยกว่า (signed)7Cjge (7D)
ja / jnbeมากกว่า (unsigned)77jbe (76)
jmp (short)เสมอ (unconditional)EB— (บังคับไป win)
nopไม่ทำอะไร90ใช้ลบ instruction ทิ้ง
flip jump: je → jne (byte 74 → 75)
cmp eax, ebx ; input == key? 74 xx : je win win() [ถ้าถูก] fail() [ถ้าผิด] patch: 74→75 je → jne = สลับทาง // jne: กระโดด win เมื่อ 'ไม่เท่า' → ใส่ input มั่วก็ผ่าน (สลับ logic)

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
เร็วสุด: Search for String references หา string ผลลัพธ์ → double-click ไปที่โค้ด → ดู cmp/test + conditional jump เหนือมัน = จุด patch วิธีนี้ใช้ได้เหมือนกันทั้งใน x64dbg, Ghidra, radare2 (ดู ghidra)

4. x64dbg workflow (dynamic patch)

คีย์/เมนูทำอะไร
F9run / continue
F8step over
F7step into
F2toggle breakpoint
spacebarassemble instruction ใหม่ (แก้ ณ จุดนั้น)
Right-click > Binary > Fill with NOPsNOP ช่วงที่เลือกทิ้ง
Ctrl+PPatches manager → Patch File (save)
Right-click > Search for > String referencesหา string หาจุด check
x64dbg — patch แล้ว save เป็นไฟล์Windows
# 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 ตลอด
spacebar = assemble ณ จุดนั้น; Ctrl+P = Patches manager แล้ว 'Patch File' เพื่อ save ลงดิสก์ (x64dbg แก้ทั้งใน memory และเขียนกลับไฟล์ให้)

5. Static patch — Ghidra / radare2 / Binary Ninja

ถ้าโปรแกรมมี anti-debug (ดู anti-debug) หรือแค่อยากแก้บนดิสก์เร็วๆ ใช้ static patch ได้ — แก้ byte แล้ว export ไฟล์ใหม่โดยไม่ต้องรันโปรแกรมเลย

radare2 — patch จาก CLI (write mode)
# เปิดในโหมดเขียน (-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)
r2 -w = write mode; wa = assemble, wx = write hex, wao nop = NOP instruction; แก้ปุ๊บบันทึกลงไฟล์ทันที
  • 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
ระวัง VA (virtual address) ≠ file offset — ตอนแก้ hex ดิบต้องแปลง address ที่เห็นใน disassembler เป็น offset ในไฟล์ก่อน (ผ่าน section header) เครื่องมืออย่าง r2/Ghidra จัดการให้อัตโนมัติ จึงปลอดภัยกว่าการ hexedit เอง

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 จริง)

หลายโจทย์จงใจให้ flag = ผลของการ decrypt ด้วย key ที่มาจาก input ที่ถูกต้อง — patch จะไม่ได้ flag เพราะ decrypt ด้วย key ผิดจะได้ขยะ ต้อง reverse algorithm แล้ว keygen เท่านั้น
Patch หรือ Keygen?
โจทย์ต้องการอะไร?
แค่ให้โปรแกรมบอกผ่านpatch (flip jump/NOP/force ret)
flag = serial ที่ถูกkeygen (เขียน solver)
flag = decrypt ด้วย key จาก inputreverse algo → keygen
patch: flip JE/JNE / jmp / NOP → save
keygen: เข้าใจ algorithm → reverse → สร้าง input/serial ที่ผ่าน
ได้ flag / โปรแกรมผ่าน

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. 1รันโปรแกรม: ขอ password → พิมพ์มั่ว → ได้ 'Wrong password'
  2. 2เปิดใน x64dbg (หรือ Ghidra) → Search for String references → หา 'Wrong password'
  3. 3double-click → ไปยังโค้ด → มองขึ้นบนเจอ: cmp eax, 0 / test eax, eax แล้วตามด้วย je fail
  4. 4ตั้ง breakpoint ที่ cmp → F9 → ดูว่า eax คือผลของ strcmp/check function
  5. 5ตัดสินใจ: แค่ผ่าน → patch; flag = ผลถอดรหัส → ไป keygen
  6. 6patch (ถ้าแค่ผ่าน): เปลี่ยน je fail → jne fail (หรือ NOP je) → บังคับเข้า branch success
  7. 7save: Ctrl+P → Patch File → รัน binary ใหม่ → 'Correct!' / เห็น flag
keygen แบบ solver (เมื่อ flag = ผล transform ของ input)
# หลัง 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 ผ่าน
keygen = ย้อนสมการของ check กลับ; ต่างจาก patch ตรงที่ได้ input จริงที่ผ่าน (สำหรับโจทย์ที่ flag ผูกกับ input)

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. 1หาจุด check ก่อนด้วย ghidra: search string 'Wrong'/'Correct' แล้วกด X ดู xref
  2. 2จดตำแหน่ง address ของ conditional jump ที่อยู่เหนือจุดพิมพ์ผลลัพธ์ (เช่น je 0x4006xx)
  3. 3ตัดสินใจ: ต้องการแค่ผ่าน (patch) หรือ flag = serial/ผลถอดรหัส (ต้องเขียน solver/keygen แทน)
  4. 4ถ้า patch: เปิด r2 -w ./binary (โหมดเขียน)
  5. 5s
    ไปที่ instruction แล้ว pdf ดู disassembly ยืนยัน byte ก่อนแก้
  6. 6แก้ instruction: 'wa jne 0x....' หรือ 'wx 75' (flip je→jne) หรือ 'wao nop' (ลบ check ทิ้ง)
  7. 7q ออก (r2 -w เขียนกลับไฟล์ทันทีตอนแก้) แล้วรัน ./binary ใหม่ดูผล
  8. 8ถ้ามี self-checksum ตรวจจับการแก้ → หา + NOP จุดคำนวณ/เทียบ checksum เองด้วย
  9. 9ถ้า flag ที่ print ออกมาเป็นขยะ (เพราะ flag ผูกกับ input จริง) → กลับไปเขียน solver แทน patch
  10. 10ยืนยันผลลัพธ์สุดท้าย: 'Correct' ปรากฏ หรือได้ flag จริง
Patch ให้ผ่าน check ทีละขั้น
โจทย์ต้องการอะไร
✅ แค่ให้โปรแกรมบอกว่าผ่าน→ patch (flip jump/NOP)
❌ flag = serial/ผลถอดรหัสจาก input→ ต้องเขียน solver/keygen แทน patch อย่างเดียว
r2 -w ./binary → s <addr> → pdf ดู instruction ที่จะแก้
แก้ instruction: wa jne .. / wx 75 / wao nop → q (บันทึกอัตโนมัติ)
รัน ./binary ใหม่ ผลเป็นอย่างไร
✅ ผ่าน check แล้ว ('Correct'/เข้าเงื่อนไข)→ เสร็จ ได้ผลลัพธ์ที่ต้องการ
❌ crash/behaviour แปลกหลัง patch→ อาจมี self-checksum ตรวจจับการแก้ไฟล์
❌ ผ่าน check แต่ flag ที่ print ออกมาเป็นขยะ→ flag ผูกกับ input จริง ต้อง keygen แทน
หา + NOP จุดคำนวณ/เทียบ checksum เองด้วย
เขียน solver ย้อนสมการจาก logic ที่ reverse ได้
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
หาจุด check/xrefghidraapt install ghidradogbolt.org
patch แบบ static (เขียนไฟล์ตรง)radare2 (r2 -w)apt install radare2-
patch/debug runtime ก่อน savegdbapt install gdb-
disassemble ยืนยัน opcodeobjdump, xxd-onlinedisassembler.com
hook แทนการแก้ไฟล์fridapipx install frida-tools-
ตรวจ hex/byte offset ดิบxxd, hexdump--
คำนวณ/เขียน solverpython3-CyberChef, dcode.fr
🚑 ถ้าตันสนิท ลองท่าถัดไป: ghidra — ยังไม่รู้จุด check แน่ชัด ต้องหาก่อนค่อย patch; anti-debug — โปรแกรมกันการ debug/patch runtime อยู่; dynamic-analysis — อยากยืนยันค่า/พฤติกรรมก่อน patch จริงลงไฟล์; frida — ไม่อยากแก้ไฟล์ถาวร ใช้ hook แทนได้; static-analysis — ยังไม่แน่ใจโครงสร้างไฟล์ กลับไป triage ก่อน

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้