คลัง
reverse

Anti-Debugging

Anti-Debugging คือเทคนิคที่โปรแกรม (มัก malware หรือโจทย์ reversing ขั้นสูง) ใช้ตรวจจับว่ากำลังถูก debug แล้วเปลี่ยนพฤติกรรม/ขัดขวางการวิเคราะห์ บทนี้ลงลึกเทคนิค anti-debug ทุกแบบบน Linux (ptrace/proc/timing/breakpoint detection) พร้อมวิธี bypass แต่ละแบบด้วย patch/LD_PRELOAD/gdb (เนื้อหาเพื่อการศึกษา/วิเคราะห์ที่ได้รับอนุญาต)

Advanced#anti-debug#reverse#ptrace#evasion#bypass#timing#ctf

1. หลักการ

โปรแกรมตรวจว่าถูก debug อยู่ไหม ถ้าใช่ก็เปลี่ยนพฤติกรรม (crash, ให้ flag ปลอม, ออกเงียบๆ, เปลี่ยน logic) เพื่อกันการวิเคราะห์ พบใน malware (กันนักวิเคราะห์) และโจทย์ reversing ที่ตั้งใจทำให้ยาก เป้าหมายของเราคือตรวจเจอเทคนิค anti-debug แล้ว bypass เพื่อวิเคราะห์/debug ต่อได้

เนื้อหานี้เพื่อการศึกษาและการวิเคราะห์ในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab, malware analysis ที่ถูกต้อง) เท่านั้น

2. เทคนิค anti-debug บน Linux

เทคนิคทำงานอย่างไรตรวจเจอใน
ptrace(PTRACE_TRACEME)เรียก ptrace ตัวเอง — ถ้าถูก debug จะ fail (debugger ใช้ ptrace อยู่แล้ว ผูกได้ตัวเดียว)call ptrace ใน Ghidra
/proc/self/status TracerPidอ่าน TracerPid — ถ้า != 0 = ถูก debugopen("/proc/self/status")
timing checkวัดเวลา execution (rdtsc/time) — debugger ทำให้ช้าผิดปกติrdtsc, clock_gettime
breakpoint detectionscan โค้ดตัวเองหา 0xCC (int3 ของ sw breakpoint)loop อ่าน .text เทียบ 0xCC
parent process checkดูว่า parent เป็น gdb/strace ไหมgetppid + อ่าน /proc/ppid
signal-basedใช้ SIGTRAP handler เอง — debugger แย่ง signalsigaction SIGTRAP

3. หาจุดตรวจ (static)

หา anti-debug ใน static + ltraceLinux
# imports — เห็น ptrace = มี anti-debug แน่
rabin2 -i binary | grep -iE 'ptrace|getppid'

# ltrace เห็นการเรียก ptrace ตอน run
ltrace ./binary 2>&1 | grep -i ptrace
# เช่น: ptrace(PTRACE_TRACEME, 0, 0, 0) = -1  ← ตรวจ debug

# ใน Ghidra/IDA: หา call ptrace, open("/proc/self/status"), rdtsc
# X xref ไปดูว่าผลถูกใช้ตัดสินใจอย่างไร (if แล้ว exit/crash)
เริ่มจาก imports — เห็น ptrace = มี anti-debug; ltrace ยืนยันการเรียกตอน runtime

4. Bypass ptrace (พบบ่อยสุด)

หลายวิธี bypass ptraceLinux
# วิธี 1: LD_PRELOAD override ptrace ให้คืน 0 เสมอ
cat > fake.c << 'C'
long ptrace(int r, int p, void* a, void* d) { return 0; }
C
gcc -shared -fPIC fake.c -o fake.so
LD_PRELOAD=./fake.so ./binary
# หรือใน gdb:
gdb ./binary
gef> set environment LD_PRELOAD ./fake.so

# วิธี 2: gdb catch syscall ptrace แล้วแก้ return
gef> catch syscall ptrace
gef> run
gef> set $rax = 0       # บังคับให้ผลเป็น "ไม่ถูก debug"
gef> continue

# วิธี 3: patch binary (NOP call ptrace หรือแก้ jump)
# ใน Ghidra: Patch Instruction เปลี่ยน JNZ→JMP/NOP ข้าม check
LD_PRELOAD เร็วสุดสำหรับ ptrace; catch syscall + set $rax ยืดหยุ่น; patch ถาวรใน binary

5. Bypass เทคนิคอื่น

  • /proc/self/status TracerPid: patch จุดที่อ่าน/เทียบ TracerPid (แก้ให้เป็น 0 เสมอ) หรือ hook open ด้วย LD_PRELOAD ให้คืนไฟล์ปลอม
  • timing check: patch จุดเทียบเวลา (ให้ผ่านเสมอ) หรือใน gdb อย่า step ทีละ instruction ตรงนั้น (ใช้ bp ข้าม) — เพราะ step ทำให้ช้า
  • breakpoint detection (0xCC): ใช้ hardware breakpoint แทน software (hbreak ใน gdb — ไม่ใส่ 0xCC ในโค้ด)
  • parent process check: patch จุดเทียบ หรือรันผ่าน wrapper ที่ปลอม parent
  • หลักรวม: หาจุด 'ตัดสินใจ' (if debug then ...) ใน Ghidra แล้ว patch ให้ไปทาง 'ไม่ถูก debug' เสมอ — แก้ที่ผลการ check ครอบคลุมทุกเทคนิค
วิธีครอบจักรวาล: หา จุด branch ที่ตัดสินจากผล anti-debug ใน decompiler (มัก if (is_debugged) exit();) แล้ว patch instruction นั้น (NOP หรือกลับ jump) — แก้ทีเดียวผ่านทุก check ที่นำมาที่ branch นั้น

6. Quick Reference

  • โปรแกรมตรวจว่าถูก debug แล้วเปลี่ยนพฤติกรรม
  • Linux: ptrace(TRACEME), /proc/self/status TracerPid, timing(rdtsc), 0xCC scan, getppid
  • หาจุดตรวจ: rabin2 -i | grep ptrace; ltrace; Ghidra xref
  • bypass ptrace: LD_PRELOAD override / gdb catch syscall + set $rax=0 / patch
  • timing → hbreak (ไม่ step); 0xCC → hardware breakpoint
  • ครอบจักรวาล: patch จุด branch ที่ตัดสินจากผล anti-debug

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติกำลังสู้กับ binary ที่มี anti-debug — เปิด gdb ปุ๊บพฤติกรรมเปลี่ยนทันที (crash/ข้ามคำตอบ/ค้าง) มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อยืนยันว่ามันเช็คอะไรแล้ว bypass ให้ debug ต่อได้

  1. 1รันเทียบกันก่อน: ./binary ปกติ vs gdb ./binary แล้ว run — ถ้าพฤติกรรมต่างกันชัดเจน (crash/ข้าม/ค้าง) ให้สงสัย anti-debug ทันที
  2. 2เช็ค imports: rabin2 -i ./binary | grep -iE 'ptrace|getppid' — ถ้าเจอ ptrace แปลว่ามี anti-debug แน่นอน
  3. 3ยืนยันด้วย runtime: ltrace ./binary 2>&1 | grep -i ptrace — ดูว่ามีการเรียก ptrace(PTRACE_TRACEME,...) จริงไหม
  4. 4เปิด ghidra หา xref ของ call ptrace หรือ open(\"/proc/self/status\") แล้วดูว่าผลถูกใช้ตัดสินใจตรงไหน (มัก if แล้ว exit)
  5. 5ถ้าเป็น ptrace check: ลอง LD_PRELOAD ปลอมฟังก์ชัน ptrace ให้คืน 0 เสมอ แล้วรันใหม่
  6. 6ถ้าอยากคุมเองใน gdb: gef> catch syscall ptrace แล้ว set $rax = 0 ตอนหยุด
  7. 7ถ้าเป็น timing check (rdtsc): ใช้ hbreak (hardware breakpoint) แทน stepi/si ทีละคำสั่ง เพราะ step ทำให้ช้าจนโดนจับ
  8. 8ถ้าเป็น breakpoint scan (หา byte 0xCC): ใช้ hardware breakpoint เท่านั้น อย่าใช้ software breakpoint ธรรมดา
  9. 9ลองรันใหม่ผ่าน gdb/ltrace อีกครั้ง — ถ้า debug ได้ปกติแล้ว (bp ทำงาน ไม่ crash) ไปทำ dynamic analysis ต่อได้เลย
  10. 10ถ้ายังติด/มีหลายชั้นซ้อนกัน ให้ patch จุด branch ที่ตัดสินใจถาวรใน ghidra หรือ radare2 แทนที่จะ bypass ทุกครั้งที่รัน
หา + bypass anti-debug ทีละขั้น
รันเทียบ ./binary ปกติ vs gdb ./binary → run
พฤติกรรมต่างกัน (crash/ข้าม/ค้าง) = สงสัย anti-debug
rabin2 -i ./binary | grep -iE 'ptrace|getppid'; ltrace ./binary 2>&1 | grep -i ptrace
✅ เจอ ptrace(PTRACE_TRACEME) หรือ TracerPid→ ยืนยันมี anti-debug ไปหาจุด check ต่อใน ghidra
❌ ไม่เจอ ptrace แต่ gdb ยังพฤติกรรมแปลก→ อาจเป็น timing check (rdtsc) หรือ 0xCC breakpoint scan
เลือกวิธี bypass ตามเทคนิคที่เจอ
✅ เจอ ptrace(TRACEME)→ LD_PRELOAD fake.so คืน 0 เสมอ หรือ gdb catch syscall ptrace + set $rax=0
✅ เจอ timing check (rdtsc)→ ใช้ hbreak (hardware bp) แทน step ทีละคำสั่ง
✅ เจอ 0xCC scan (software bp detection)→ ใช้ hardware breakpoint แทน int3
ลองรันใหม่ผ่าน gdb/ltrace อีกครั้งหลัง bypass
✅ debug ได้ปกติแล้ว (bp ทำงาน ไม่ crash)→ ไป dynamic analysis ต่อ (bp ที่จุด compare, dump ค่า)
❌ ยังติด/มีชั้นซ้อนหลายเทคนิค→ patch จุดตัดสินใจให้ผ่านถาวร ไม่ต้อง bypass ซ้ำทุกครั้ง
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
เช็ค imports หา ptracerabin2 (radare2)apt install radare2-
ยืนยัน syscall ตอนรันจริงltrace, straceapt install ltrace strace-
debug พร้อม UI ช่วยดู register/stackgdb + gef/pwndbggit clone gef.git แล้วรัน gdbinit.py-
อ่าน logic/หา xref จุดตรวจghidraapt install ghidradogbolt.org
patch จุดตัดสินใจให้ผ่านถาวรradare2 (r2 -w)--
hook/override ฟังก์ชันโดยไม่แก้ไฟล์fridapipx install frida-tools-
เทียบ assembly ระหว่าง compiler--godbolt.org
ตรวจโครงสร้างไฟล์ ELF เพิ่มเติมreadelf, objdump--
🚑 ถ้าตันสนิท ลองท่าถัดไป: ghidra — ยังไม่รู้ตำแหน่ง call ที่ตัดสินใจ ต้องหาจุด xref ให้ชัดก่อน bypass; dynamic-analysis — bypass ได้แล้วอยากไปดูค่า runtime/compare ต่อ; binary-patching — อยากแก้ถาวรลงไฟล์ ไม่ต้อง LD_PRELOAD/set ค่าเองทุกครั้งที่รัน; frida — โจทย์ mobile หรืออยาก hook ข้าม check โดยไม่แตะไฟล์เลย; static-analysis — ยังไม่แน่ใจด้วยซ้ำว่ามี anti-debug จริงไหม กลับไป triage ให้ครบก่อน

โน้ตของฉัน

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