
โจทย์นี้จะให้เราหา Flag ที่ซ่อนอยู่ใน App ออกมา แต่โจทย์นี้คือต้องแกะ Native library ออกมา
พอหลังดาวโหลดไฟล์ de4flag.apk และติดตั้งเข้า Androi Emulator จะเห็นหน้าเข้าสู่ระบบ

แต่พอลองกรอกเข้าไปแล้วเจอขึ้ว่า ❌ Wrong credential

ทีนี่เราก็ไม่รู้ Username & Password ว่าคืออะไร ดังนั้นจะใช้โปรแกรม JadX ในการ Decompile APK ออกมาเพื่อดู Source code ด้านในของ App
ทีนี้เราลองไปเช็คที่ AndroidManifest.xml เพื่อทราบชื่อ Package ว่าชื่ออะไร

พอทราบว่าชื่อ Package คือ com.main.de4flagnative ก็จะไป Folder ที่ Source code > com > main > de4flagnative

พอลองเช็คจะเห็นว่าตัว function SendToServer วิ่งไปยัง Host localhost:8000 ผมก็เลยลองจำลองเป็น HTTP Server ธรรมดาขึ้นมา และ reverse adb เข้าไปยัง Android Emulator
python3 -m http.server 8000adb reverse tcp:8000 tcp:8000แต่ลองทดสอบปรากฏว่า ก็ยังเหมือนเดิม ผมก็ “เอ๊ะ OwO! ได้ไงกันนะ” ผมก็ลองเปิด log โดยใช้ adb logcat ดูปรากฏว่า
3331 3331 I System.out: [Request] Sending login request to http://localhost:8000/login3331 3331 I System.out: [Request] Failed to connect: Cleartext HTTP traffic to localhost not permittedก็เลยถึงบางอ้อ เลยเพราะเนื่องด้วยใน Android 9 (Pie) เป็นต้นมา มีการ บังคับใช้งาน Network Security Configuration เพื่อเพิ่มความปลอดภัย ปิดการอนุญาต HTTP แบบไม่เข้ารหัส (cleartext) ตามค่า default แล้วบังคับใช้เฉพาะ HTTPS (TLS/SSL) แทน
แต่ปัญหาคือ Code นี้ถูก Obfuscator มา + กลัวว่าหากไปแก้ Code อะไรจะทำให้ตัว Verify signature ไม่ผ่านทำให้แอพเด้งออก ก็เลยจะใช้ Frida ในการ Injection ใน function ที่ Android ถูกเรียกใช้งาน
โดยการเขียนผมจะเป็นดังนี้
Java.perform(() => { const clsName = 'com.main.de4flagnative.DevCheck'; try { const DevCheck = Java.use(clsName);
DevCheck.SecretVerify.overload('java.lang.String').implementation = function (value) { console.log('[*] SecretVerify(String) ENTER'); console.log("[*] User: ", value);
return true; };
console.log('[*] Hooked:', clsName + '.decodePromo(Context)'); } catch (e) { console.log('[-] Hook setup error:', e); }});โดย Script นี้จะทำการ Bypass การยิง API ไปยังเซิฟเวอร์ โดยให้กาาคืนค่าให้เป็น true แทน
และก็รันด้วย Script frida
frida -U -f com.main.de4flagnative -l ./de4flag.jsแต่พอลองแล้วรอบนี้ไม่เกิดอะไรขึ้น งั้นแสดว่า HTTP นี้คือหลอกติดกับดักสินะ OwO! แล้วต้องทำยังไงละทีนี้
ก็เลยลองไปอ่านดี ๆ
private boolean SecretVerify(String str) throws IOException { if (!this.isDevDebug || this.authFailCount != 0) { SendToServer(str); this.authFailCount++; return false; } if (str.equals("pass9876::myuser5555")) { System.out.println("Secret value printed via Console Log"); new SecretFlag().print(str, this); } return true; }ผมก็เอ๊ะ Function มันก็ไม่ได้มีอะไร Action กับการเรียก Function ด้านล่าง แต่จะเห็นเงื่อนไขว่า !this.isDevDebug || this.authFailCount != 0 ก็เลยสันนิฐานว่าอันนี้คือหากไม่ใช้ Debug dev mode จะให้ยิงไปหา API แทน
ก็เลยลองปรับให้ไปยิงเข้า SecretFlag ตรง ๆ เลยว่าจะได้อะไรกลับมา
Final frida code
Java.perform(() => { const clsName = 'com.main.de4flagnative.DevCheck'; try { const DevCheck = Java.use(clsName); let SecretFlag = Java.use("com.main.de4flagnative.SecretFlag");
DevCheck.SecretVerify.overload('java.lang.String').implementation = function (value) { console.log('[*] SecretVerify(String) ENTER'); console.log("[*] User: ", value);
const pass = "pass9876::myuser5555"; let flag;
// Correct overload with two params const p = SecretFlag.print.overload('java.lang.String', 'com.main.de4flagnative.DevCheck');
try { // If print is static: receiver is the class wrapper; second arg is the *instance* (this) flag = p.call(SecretFlag, pass, this); } catch (e1) { // If print is an instance method: create an instance and use that as receiver try { const sf = SecretFlag.$new(); flag = p.call(sf, pass, this); } catch (e2) { console.log('[-] Failed to invoke SecretFlag.print:', e1, e2); return this.SecretVerify(value); // fallback to original if you want } }
console.log('[*] Flag =>', flag); return true; };
console.log('[*] Hooked:', clsName + '.decodePromo(Context)'); } catch (e) { console.log('[-] Hook setup error:', e); }});ทีนี้ลองใหม่

STH{6ea68d9d69ead13e0a08c6b08a53a38f}เย้! ได้ Flag ออกมาแย้ว >w< ”