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/แอปที่ได้รับอนุญาต)
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)
2. Spawn vs Attach + ติดตั้ง
| โหมด | flag | เมื่อไร |
|---|---|---|
| Attach | -p | process รันอยู่แล้ว — hook ระหว่างทาง |
| Spawn | -f | เริ่ม process ใหม่ hook 'ตั้งแต่แรก' (จับ init/decrypt ต้นๆ) |
| USB (mobile) | -U | target อยู่บนอุปกรณ์ผ่าน frida-server |
| Remote | -H host:port | frida-server บนเครื่อง/emulator remote |
# ฝั่งเครื่องเรา
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-pause3. 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 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 }));
}
});// สร้าง 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', []));4. Hook Java (Android — Java.perform)
สำหรับ Android/Java ใช้ Java.perform() เพื่อเข้าถึง runtime แล้ว Java.use('com.pkg.Class') เพื่อ hook method — เขียนทับ implementation ด้วย .implementation = function(...) อ่าน argument, เปลี่ยน return, หรือดักค่าที่ method คำนวณ
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 () {}
});
});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 เอง
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 disableJava.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); }
});6. frida-trace — สำรวจเร็ว
# 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)7. Workflow
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 ลองทำตามนี้ทีละขั้น
- 1pipx install frida-tools แล้วเช็ค frida --version ให้ใช้ได้ก่อน
- 2ตัดสินใจ: target เป็น native binary (Linux) ธรรมดา หรือ Android app
- 3ใช้ ghidra หา offset ของฟังก์ชัน check/decrypt ที่น่าสนใจก่อนเขียน hook
- 4เขียน hook.js เบื้องต้น: Interceptor.attach ที่ export name หรือ base+offset
- 5รัน frida -f ./binary -l hook.js --no-pause (spawn) หรือ frida -p
-l hook.js (attach ของที่รันอยู่แล้ว) - 6ดู log ค่า argument/return ที่ print ออกมาใน console
- 7ถ้าอยากบังคับผ่าน check: แก้ onLeave ให้ retval.replace(0) หรือ (1) ตามที่ check ต้องการ
- 8Android: frida-ps -Uai เช็ค process บนอุปกรณ์ก่อน แล้ว frida -U -f com.pkg -l hook.js --no-pause
- 9ถ้าอยากสำรวจเร็วๆ ไม่อยากเขียน script เอง: frida-trace -f ./binary -i 'check*'
- 10ยืนยันผล: เห็น 'Correct'/flag ปรากฏ หรือ traffic ที่ decrypt ได้แล้ว
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ติดตั้ง frida (ไม่มีใน Kali default) | - | pipx install frida-tools | - |
| หา offset ก่อน hook | ghidra, radare2 | apt install ghidra | dogbolt.org |
| รัน frida-server บน Android (root) | adb | โหลด frida-server จาก github release | - |
| สำรวจ function ที่ถูกเรียกจริง | frida-trace | pipx install frida-tools | - |
| คำสั่งสำเร็จรูป (ssl pinning/root bypass) | - | pipx install objection | - |
| disassemble เทียบ offset | objdump, radare2 | apt install radare2 | onlinedisassembler.com |
| debug คู่กับ frida | gdb | apt install gdb | - |
| ถอดรหัส/วิเคราะห์ค่าที่ hook ได้ | python3 | - | CyberChef |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้