คลัง
reverse

Frida (Dynamic Instrumentation)

Frida คือ dynamic instrumentation toolkit ที่ inject JavaScript engine เข้า process ที่กำลังรัน เพื่อ hook function, อ่าน/แก้ argument และ return value, bypass check แบบ realtime ใช้ได้ทั้ง native (C/C++) และ Java/Android/iOS บทนี้ลงลึก spawn vs attach, Interceptor.attach สำหรับ native, Java.perform hook สำหรับ Android, การ bypass SSL pinning และ root/anti-debug check, frida-trace และ script จริงที่ใช้ได้ (เนื้อหาเพื่อฝึกใน lab/CTF/แอปที่ได้รับอนุญาต)

IntermediateAdvanced#frida#instrumentation#hooking#dynamic-analysis#ssl-pinning#interceptor#java-perform#mobile

1. Frida ทำอะไรได้

Frida inject JavaScript engine (QuickJS/V8) เข้า address space ของ process ที่กำลังรัน ทำให้เขียน JS สั่ง hook function ใดก็ได้ขณะทำงาน: ดู/แก้ argument, แก้ return value, เรียก function เอง, อ่าน/เขียน memory ต่างจาก static analysis (Ghidra) ที่อ่านโค้ดนิ่งๆ — Frida เห็น ค่าจริงตอน runtime เหมาะกับ logic ที่ถูก obfuscate, ค่าที่คำนวณ runtime, แอปที่มี anti-debug, หรือ mobile app ที่ decrypt string ตอนรัน

สถาปัตยกรรม: ฝั่งเรารัน Python/CLI (frida, frida-trace) สื่อสารกับ frida-agent ที่ถูก inject เข้า target ผ่าน RPC ส่วน mobile ต้องมี frida-server รันบนอุปกรณ์ (Android root/iOS jailbreak) หรือ repackage APK ด้วย frida-gadget (ไม่ต้อง root)

เนื้อหานี้เพื่อฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab, แอปของตัวเอง, pentest ที่มี scope) เท่านั้น

2. Spawn vs Attach + ติดตั้ง

โหมดflagเมื่อไร
Attach-p / ชื่อ processprocess รันอยู่แล้ว — hook ระหว่างทาง
Spawn-f เริ่ม process ใหม่ hook 'ตั้งแต่แรก' (จับ init/decrypt ต้นๆ)
USB (mobile)-Utarget อยู่บนอุปกรณ์ผ่าน frida-server
Remote-H host:portfrida-server บนเครื่อง/emulator remote
ติดตั้ง + setup frida-server (Android)
# ฝั่งเครื่องเรา
pip install frida-tools          # ได้ frida, frida-trace, frida-ps

# Android (ต้อง root): ดาวน์โหลด frida-server ตรง arch + version
#   https://github.com/frida/frida/releases  (เช่น frida-server-16.x-android-arm64)
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c /data/local/tmp/frida-server &"

# ตรวจว่าเชื่อมได้ + list process บนอุปกรณ์
frida-ps -U
frida-ps -Uai            # -a เฉพาะแอป, -i รวมที่ยังไม่รัน

# spawn แอปแล้ว attach script
frida -U -f com.target.app -l hook.js --no-pause
frida-server version ต้อง 'ตรง' กับ frida-tools ฝั่ง host; ไม่ root → ใช้ frida-gadget ฝังใน APK แทน (objection patchapk)

3. Hook native (Interceptor.attach)

สำหรับ native function (C/C++, .so, ELF, PE) ใช้ Interceptor.attach(address, callbacks)onEnter(args) เข้าถึง/แก้ argument ก่อนฟังก์ชันทำงาน, onLeave(retval) อ่าน/แก้ return value หา address จาก export name หรือ base + offset (offset จาก Ghidra)

hook.js — แก้ argument / return value (native)
// hook by export name — เห็นค่าที่ strcmp เทียบ (มัก = password/flag)
Interceptor.attach(Module.getExportByName(null, 'strcmp'), {
  onEnter(args) {
    this.a = args[0].readUtf8String();
    this.b = args[1].readUtf8String();
    console.log('[strcmp]', this.a, 'vs', this.b);
  },
  onLeave(retval) {
    // บังคับให้ strcmp คืน 0 (=เท่ากัน) เสมอ → ผ่าน check
    retval.replace(0);
  }
});

// hook by base + offset (offset จาก Ghidra ของฟังก์ชัน check)
const base = Module.getBaseAddress('target');    // หรือ 'libnative.so'
Interceptor.attach(base.add(0x1234), {
  onEnter(args) {
    console.log('arg0 =', args[0].readUtf8String());
    // แก้ argument: ชี้ไป buffer ใหม่
    args[0] = Memory.allocUtf8String('forced_input');
  },
  onLeave(retval) {
    console.log('ret =', retval);
    retval.replace(1);            // บังคับ return 1 (bypass bool check)
  }
});

// อ่าน/dump memory ที่ pointer
Interceptor.attach(Module.getExportByName(null, 'memcpy'), {
  onEnter(args) {
    const len = args[2].toInt32();
    if (len < 64) console.log(hexdump(args[1], { length: len }));
  }
});
onEnter อ่าน/แก้ args; onLeave แก้ retval; retval.replace(0) กับ strcmp = บังคับ 'เท่ากัน'; ค่า this.* ส่งจาก onEnter ไป onLeave ได้
เรียก function เอง + Interceptor.replace
// สร้าง NativeFunction เรียก function ในโปรแกรมเอง (เช่น decrypt(idx))
const base = Module.getBaseAddress('target');
const decrypt = new NativeFunction(base.add(0x1500), 'pointer', ['int']);
for (let i = 0; i < 10; i++)
  console.log(i, decrypt(i).readUtf8String());   // ดึงทุก string ที่ decrypt

// แทนที่ทั้งฟังก์ชัน (เช่น anti-debug ให้ return 0 เสมอ)
Interceptor.replace(base.add(0x1600), new NativeCallback(function () {
  return 0;                       // is_debugger_present() → false
}, 'int', []));
NativeFunction เรียก function เดิมด้วย argument ที่เราคุม (brute idx ดึง string); Interceptor.replace เขียนทับทั้งฟังก์ชัน เหมาะ neutralize anti-debug

4. Hook Java (Android — Java.perform)

สำหรับ Android/Java ใช้ Java.perform() เพื่อเข้าถึง runtime แล้ว Java.use('com.pkg.Class') เพื่อ hook method — เขียนทับ implementation ด้วย .implementation = function(...) อ่าน argument, เปลี่ยน return, หรือดักค่าที่ method คำนวณ

android-hook.js — hook method + เปลี่ยน return
Java.perform(function () {
  // 1) hook method ตรวจ license/flag → บังคับ return true
  const Check = Java.use('com.target.app.LicenseCheck');
  Check.isValid.implementation = function (input) {
    console.log('[isValid] input =', input);
    const orig = this.isValid(input);      // เรียกของเดิมดูผลจริง
    console.log('[isValid] orig =', orig);
    return true;                            // บังคับผ่านเสมอ
  };

  // 2) ดักค่าที่ method คำนวณ (เช่น flag ที่ประกอบ runtime)
  const Crypto = Java.use('com.target.app.Crypto');
  Crypto.decrypt.overload('java.lang.String').implementation = function (s) {
    const out = this.decrypt(s);
    console.log('[decrypt]', s, '=>', out);
    return out;
  };

  // 3) hook overload หลายตัว: ระบุ .overload(types...)
  const Str = Java.use('java.lang.String');
  Str.equals.overload('java.lang.Object').implementation = function (o) {
    console.log('[String.equals] this =', this.toString(), ' arg =', o);
    return this.equals(o);
  };

  // 4) enumerate instance ที่มีอยู่ใน heap (ดึง object ที่ถือ key อยู่)
  Java.choose('com.target.app.Session', {
    onMatch: function (inst) { console.log('token =', inst.token.value); },
    onComplete: function () {}
  });
});
Java.perform ครอบทุกอย่าง; .implementation เขียนทับ method; ถ้ามีหลาย overload ต้องระบุ .overload(argTypes); Java.choose ดึง instance ที่มีชีวิตอยู่ใน heap
objection (สร้างบน Frida) ให้คำสั่งสำเร็จรูปโดยไม่ต้องเขียน JS: objection -g com.target.app explore แล้ว android hooking search classes flag, android sslpinning disable, android root disable — เร็วมากสำหรับงานมาตรฐาน

5. Bypass SSL Pinning / Root / Anti-debug

แอป mobile มัก SSL pinning (ปฏิเสธ cert ที่ไม่ตรง pin ทำให้ intercept traffic ด้วย Burp ไม่ได้) และ root detection Frida hook จุดตรวจให้ผ่านได้ — วิธีเร็วสุดใช้ objection; วิธีเข้าใจกลไกใช้ script เอง

ssl-bypass.js — neutralize pinning (OkHttp/TrustManager)
Java.perform(function () {
  // 1) OkHttp CertificatePinner.check() → ไม่ทำอะไร (ผ่าน pinning)
  try {
    const CP = Java.use('okhttp3.CertificatePinner');
    CP.check.overload('java.lang.String', 'java.util.List').implementation =
      function () { console.log('[pinning] bypassed'); return; };
  } catch (e) {}

  // 2) แทน TrustManager ให้ยอมรับทุก cert (SSLContext)
  const X509 = Java.use('javax.net.ssl.X509TrustManager');
  const SSLContext = Java.use('javax.net.ssl.SSLContext');
  const TM = Java.registerClass({
    name: 'org.pwn.TrustAll',
    implements: [X509],
    methods: {
      checkClientTrusted: function () {},
      checkServerTrusted: function () {},
      getAcceptedIssuers: function () { return []; }
    }
  });
  const init = SSLContext.init.overload(
    '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;',
    'java.security.SecureRandom');
  init.implementation = function (km, tm, sr) {
    init.call(this, km, [TM.$new()], sr);   // ยัด TrustManager ที่ยอมรับทุกอย่าง
  };
});
// รัน: frida -U -f com.target.app -l ssl-bypass.js --no-pause
// หรือใช้ objection: android sslpinning disable
hook ทั้ง OkHttp CertificatePinner และ SSLContext.init ครอบ implementation ส่วนใหญ่; ทำให้ Burp intercept HTTPS ของแอปได้
root/anti-debug bypass (native + Java)
Java.perform(function () {
  // root check ที่เช็คไฟล์ su
  const File = Java.use('java.io.File');
  File.exists.implementation = function () {
    const p = this.getAbsolutePath();
    if (p.indexOf('su') !== -1 || p.indexOf('magisk') !== -1) return false;
    return this.exists();
  };
});

// native ptrace anti-debug: ptrace(PTRACE_TRACEME) → บังคับ return 0
Interceptor.attach(Module.getExportByName(null, 'ptrace'), {
  onLeave(retval) { retval.replace(0); }
});
root detection มักเช็ค /system/bin/su, magisk; anti-debug native มักใช้ ptrace(TRACEME) — hook ให้คืนค่าปกติ

6. frida-trace — สำรวจเร็ว

frida-trace — auto-hook + generate handler
# native: hook ทุก function ที่ match pattern (สร้าง stub ให้แก้)
frida-trace -p <PID> -i 'strcmp'
frida-trace -f ./binary -i 'check*' -i '*decrypt*'   # spawn + หลาย pattern

# Android: hook Java method (-U USB, -j = Java)
frida-trace -U -f com.target.app -j 'com.target.app.*!*'
frida-trace -U com.target.app -j '*!*decrypt*'

# frida-trace สร้างไฟล์ __handlers__/<fn>.js ให้แก้ log ได้เอง
# เช่นเติม console.log(args[0].readUtf8String()) ใน onEnter แล้ว save (hot-reload)
frida-trace เหมาะ 'สำรวจ' ว่า function ไหนถูกเรียก + เมื่อไร; แก้ __handlers__ เพื่อ log ค่าเพิ่ม; -j = Java method (Android)

7. Workflow

ใช้ Frida แก้โจทย์
spawn หรือ attach?
ต้องจับตั้งแต่ init/decrypt ต้นๆspawn (-f) --no-pause
process รันอยู่แล้วattach (-p / ชื่อ)
frida-trace ดู function ที่ถูกเรียก + args
เป้าหมายแบบไหน?
เทียบ password/serialhook strcmp/equals เห็นค่า
check return boolonLeave retval.replace / return true
flag ถูก decrypt runtimehook decrypt / เรียก NativeFunction เอง
SSL pinning / root checkssl-bypass / objection disable
เขียน hook.js แก้ค่า realtime
ผ่าน check / เห็น flag / intercept traffic ได้
คู่กับ static เสมอ: Ghidra บอก ตรงไหน (offset, ชื่อ class/method) → Frida บอก ค่าอะไรจริง ตอนรัน สองอย่างเสริมกัน โดยเฉพาะโจทย์ที่ obfuscate หรือ decrypt string ตอน runtime (ดู dynamic-analysis, android-apk-analysis)

8. Quick Reference

  • spawn: frida -U -f pkg -l hook.js --no-pause · attach: frida -p PID -l hook.js
  • list: frida-ps -Uai · trace: frida-trace -f ./bin -i 'check*'
  • native: Interceptor.attach(addr,{onEnter,onLeave}) · args[0].readUtf8String()
  • bypass native: retval.replace(1) · แทนทั้ง fn: Interceptor.replace
  • Java: Java.perform(()=>{ Cls.method.implementation = ... }); overload ระบุ types
  • ดึง instance: Java.choose; เรียก fn เอง: new NativeFunction
  • SSL pinning: hook CertificatePinner+SSLContext.init หรือ objection ... sslpinning disable
  • root/anti-debug: hook File.exists / ptrace → คืนค่าปกติ
  • คู่ Ghidra: static หา offset → Frida ดู/แก้ค่า runtime

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

สมมติเจอโจทย์ที่ patch ไฟล์ตรงๆ ไม่สะดวก (มี self-check, เป็น mobile app, หรืออยากดูค่า runtime แบบ inject) มีแค่ Kali ลองทำตามนี้ทีละขั้น

  1. 1pipx install frida-tools แล้วเช็ค frida --version ให้ใช้ได้ก่อน
  2. 2ตัดสินใจ: target เป็น native binary (Linux) ธรรมดา หรือ Android app
  3. 3ใช้ ghidra หา offset ของฟังก์ชัน check/decrypt ที่น่าสนใจก่อนเขียน hook
  4. 4เขียน hook.js เบื้องต้น: Interceptor.attach ที่ export name หรือ base+offset
  5. 5รัน frida -f ./binary -l hook.js --no-pause (spawn) หรือ frida -p -l hook.js (attach ของที่รันอยู่แล้ว)
  6. 6ดู log ค่า argument/return ที่ print ออกมาใน console
  7. 7ถ้าอยากบังคับผ่าน check: แก้ onLeave ให้ retval.replace(0) หรือ (1) ตามที่ check ต้องการ
  8. 8Android: frida-ps -Uai เช็ค process บนอุปกรณ์ก่อน แล้ว frida -U -f com.pkg -l hook.js --no-pause
  9. 9ถ้าอยากสำรวจเร็วๆ ไม่อยากเขียน script เอง: frida-trace -f ./binary -i 'check*'
  10. 10ยืนยันผล: เห็น 'Correct'/flag ปรากฏ หรือ traffic ที่ decrypt ได้แล้ว
hook ด้วย Frida ทีละขั้น
pipx install frida-tools แล้วเช็ค frida --version
target เป็น native binary หรือ Android app?
✅ native binary (Linux)→ ใช้ Interceptor.attach ตรง address/export
✅ Android APK→ ต้องมี frida-server บนอุปกรณ์/emulator ก่อน
เขียน hook.js แล้ว frida -f ./binary -l hook.js --no-pause
hook ทำงานไหม เห็น log ค่าจริง
✅ เห็น argument/return ตามที่คาด→ ปรับ onLeave retval.replace ให้ผ่าน check
❌ ไม่เจอ function/attach fail→ เช็คชื่อ export/offset ใหม่ หรือใช้ frida-trace สำรวจก่อน
❌ process มี anti-debug/anti-frida detection→ ต้อง bypass detection ก่อน hook ต่อได้
frida-trace -f ./binary -i 'check*' สำรวจ function ที่ถูกเรียกจริง
ได้ flag / traffic ที่ decrypt / check ผ่านแล้ว
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ติดตั้ง frida (ไม่มีใน Kali default)-pipx install frida-tools-
หา offset ก่อน hookghidra, radare2apt install ghidradogbolt.org
รัน frida-server บน Android (root)adbโหลด frida-server จาก github release-
สำรวจ function ที่ถูกเรียกจริงfrida-tracepipx install frida-tools-
คำสั่งสำเร็จรูป (ssl pinning/root bypass)-pipx install objection-
disassemble เทียบ offsetobjdump, radare2apt install radare2onlinedisassembler.com
debug คู่กับ fridagdbapt install gdb-
ถอดรหัส/วิเคราะห์ค่าที่ hook ได้python3-CyberChef
🚑 ถ้าตันสนิท ลองท่าถัดไป: ghidra — ต้องหา offset/ชื่อฟังก์ชันให้แม่นก่อน hook; dynamic-analysis — อยากดูด้วย gdb/ltrace ก่อนเขียน frida script เอง; anti-debug — โปรแกรมตรวจจับ debugger/frida ต้อง bypass ก่อน; binary-patching — อยากแก้ถาวรลงไฟล์แทนที่จะ hook ทุกครั้งที่รัน; static-analysis — ยังไม่รู้จะ hook อะไร กลับไป triage imports ก่อน

โน้ตของฉัน

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