เครื่องมือที่ใช้ Frida เพื่อติดตามการใช้งาน JNI API ในแอพ Android
ไลบรารีดั้งเดิมที่มีอยู่ภายในแอพ Android มักใช้ประโยชน์จาก JNI API เพื่อใช้งานรันไทม์ Android การติดตามการโทรเหล่านั้นผ่านวิศวกรรมย้อนกลับแบบแมนนวลอาจเป็นกระบวนการที่ช้าและเจ็บปวด jnitrace ทำงานเป็นเครื่องมือติดตามการวิเคราะห์แบบไดนามิกคล้ายกับ Frida-trace หรือ Strace แต่สำหรับ JNI
วิธีที่ง่ายที่สุดในการทำงานด้วย jnitrace คือการติดตั้งโดยใช้ PIP:
pip install jnitrace
หลังจากการติดตั้ง PIP มันใช้งานง่าย jnitrace :
jnitrace -l libnative-lib.so com.example.myapplication
jnitrace ต้องใช้พารามิเตอร์อย่างน้อยสองพารามิเตอร์ในการรันร่องรอย:
-l libnative-lib.so -ใช้เพื่อระบุไลบรารีเพื่อติดตาม อาร์กิวเมนต์นี้สามารถใช้หลายครั้งหรือ * สามารถใช้ในการติดตามไลบรารีทั้งหมด ตัวอย่างเช่น -l libnative-lib.so -l libanother-lib.so หรือ -l *com.example.myapplication - เป็นแพ็คเกจ Android ที่จะติดตาม แพ็คเกจนี้จะต้องติดตั้งบนอุปกรณ์แล้วอาร์กิวเมนต์เสริมแสดงอยู่ด้านล่าง:
-R <host>:<port> - ใช้เพื่อระบุตำแหน่งเครือข่ายของเซิร์ฟเวอร์ระยะไกล Frida หาก A: ไม่ระบุรายละเอียด LocalHost: 27042 จะถูกใช้โดย Deafult-m <spawn|attach> - ใช้เพื่อระบุกลไกการแนบ Frida ที่จะใช้ มันสามารถวางไข่หรือแนบ การวางไข่เป็นตัวเลือกเริ่มต้นและแนะนำ-b <fuzzy|accurate|none> - ใช้เพื่อควบคุมเอาต์พุต backtrace โดยค่าเริ่มต้น jnitrace จะเรียกใช้ backtracer ในโหมด accurate ตัวเลือกนี้สามารถเปลี่ยนเป็นโหมด fuzzy หรือใช้เพื่อหยุด backtrace โดยใช้ตัวเลือก none ดูเอกสาร Frida สำหรับคำอธิบายเกี่ยวกับความแตกต่าง-i <regex> - ใช้เพื่อระบุชื่อวิธีที่ควรติดตาม สิ่งนี้จะเป็นประโยชน์สำหรับการลดเสียงรบกวนในแอพ JNI ขนาดใหญ่โดยเฉพาะ ตัวเลือกสามารถจัดหาได้หลายครั้ง ตัวอย่างเช่น -i Get -i RegisterNatives จะรวมเฉพาะวิธี JNI ที่มี Get หรือ Registernatives ในชื่อของพวกเขา-e <regex> - ใช้เพื่อระบุชื่อวิธีที่ควรถูกละเว้นในการติดตาม สิ่งนี้จะเป็นประโยชน์สำหรับการลดเสียงรบกวนในแอพ JNI ขนาดใหญ่โดยเฉพาะ ตัวเลือกสามารถจัดหาได้หลายครั้ง ตัวอย่างเช่น -e ^Find -e GetEnv จะแยกออกจากผลลัพธ์ทั้งหมดชื่อเมธอด JNI ทั้งหมดที่เริ่มค้นหาหรือมี getenv-I <string> - ใช้เพื่อระบุการส่งออกจากไลบรารีที่ควรติดตาม สิ่งนี้มีประโยชน์สำหรับห้องสมุดที่คุณต้องการติดตามวิธีการจำนวนน้อยเท่านั้น ฟังก์ชั่น Jnitrace พิจารณาว่าส่งออกเป็นฟังก์ชั่นใด ๆ ที่เรียกได้โดยตรงจากด้าน Java เช่นนี้ซึ่งรวมถึงวิธีการที่ใช้โดยใช้การลงทะเบียน ตัวเลือกสามารถจัดหาได้หลายครั้ง ตัวอย่างเช่น -I stringFromJNI -I nativeMethod([B)V สามารถใช้เพื่อรวมการส่งออกจากห้องสมุดที่เรียกว่า Java_com_nativetest_MainActivity_stringFromJNI และวิธีการที่ใช้โดยใช้ชื่อการลงทะเบียนที่มีลายเซ็นของ nativeMethod([B)V-E <string> ใช้เพื่อระบุการส่งออกจากไลบรารีที่ไม่ควรติดตาม สิ่งนี้มีประโยชน์สำหรับไลบรารีที่คุณมีกลุ่มโทรแบบเนทีฟที่ยุ่งซึ่งคุณต้องการเพิกเฉย ฟังก์ชั่น Jnitrace พิจารณาว่าส่งออกเป็นฟังก์ชั่นใด ๆ ที่เรียกได้โดยตรงจากด้าน Java เช่นนี้ซึ่งรวมถึงวิธีการที่ใช้โดยใช้การลงทะเบียน ตัวเลือกสามารถจัดหาได้หลายครั้ง ตัวอย่างเช่น -E JNI_OnLoad -E nativeMethod จะแยกออกจากการติดตามการเรียกใช้ฟังก์ชั่ JNI_OnLoad และวิธีการใด ๆ ที่มีชื่อ nativeMethod-o path/output.json - ใช้เพื่อระบุเส้นทางเอาต์พุตที่ jnitrace จะจัดเก็บข้อมูลที่ติดตามทั้งหมด ข้อมูลจะถูกเก็บไว้ในรูปแบบ JSON เพื่ออนุญาตให้โพสต์ข้อมูลการติดตามในภายหลัง-p path/to/script.js - เส้นทางที่ให้ไว้ใช้ในการโหลดสคริปต์ Frida ลงในกระบวนการเป้าหมายก่อนที่สคริปต์ jnitrace จะโหลด สิ่งนี้สามารถใช้สำหรับการเอาชนะรหัสต่อต้าน Frida หรือ anti-debugging ก่อนที่ jnitrace จะเริ่ม-a path/to/script.js - เส้นทางที่ให้ไว้ใช้ในการโหลดสคริปต์ FRIDA ลงในกระบวนการเป้าหมายหลังจากโหลด jnitrace แล้ว--hide-data -ใช้เพื่อลดปริมาณของเอาต์พุตที่แสดงในคอนโซล ตัวเลือกนี้จะซ่อนข้อมูลเพิ่มเติมที่แสดงเป็น hexdumps หรือเป็นการอ้างอิงสตริง--ignore-env การใช้ตัวเลือกนี้จะซ่อนการโทรทั้งหมดที่แอปกำลังทำโดยใช้ JNIENV Struct--ignore-vm การใช้ตัวเลือกนี้จะซ่อนการโทรทั้งหมดแอปกำลังทำโดยใช้ javavm struct--aux <name=(string|bool|int)value> -ใช้เพื่อผ่านพารามิเตอร์ที่กำหนดเองเมื่อวางไข่แอปพลิเคชัน ตัวอย่างเช่น --aux='uid=(int)10' จะวางไข่แอปพลิเคชันสำหรับผู้ใช้ 10 แทนผู้ใช้เริ่มต้น 0บันทึก
โปรดจำไว้ว่า Frida-Server จะต้องทำงานก่อนที่จะเรียกใช้ jnitrace หากมีการติดตามคำสั่งเริ่มต้นสำหรับการติดตั้ง FRIDA แล้วคำสั่งต่อไปนี้จะเริ่มต้นเซิร์ฟเวอร์พร้อมสำหรับ jnitrace :
adb shell /data/local/tmp/frida-server
เครื่องยนต์ที่ให้พลัง Jnitrace มีให้บริการเป็นโครงการแยกต่างหาก โครงการนั้นช่วยให้คุณสามารถนำเข้า Jnitrace เพื่อติดตามการโทร JNI API แต่ละรายการในวิธีการที่คุ้นเคยกับการใช้ FRIDA Interceptor เพื่อแนบกับฟังก์ชั่นและที่อยู่
import { JNIInterceptor } from "jnitrace-engine" ;
JNIInterceptor . attach ( "FindClass" , {
onEnter ( args ) {
console . log ( "FindClass method called" ) ;
this . className = Memory . readCString ( args [ 1 ] ) ;
} ,
onLeave ( retval ) {
console . log ( "tLoading Class:" , this . className ) ;
console . log ( "tClass ID:" , retval . get ( ) ) ;
}
} ) ;ข้อมูลเพิ่มเติม: https://github.com/chame1eon/jnitrace-engine
การสร้าง jnitrace จากแหล่งที่มาต้องการให้ติดตั้ง node ก่อน หลังจากติดตั้ง node ต้องเรียกใช้คำสั่งต่อไปนี้:
npm installnpm run watch npm run watch จะเรียกใช้ frida-compile ในพื้นหลังรวบรวมแหล่งที่มากับไฟล์เอาต์พุต, build/jnitrace.js jnitrace.py โหลดจาก build/jnitrace.js โดยค่าเริ่มต้นดังนั้นจึงไม่จำเป็นต้องมีการเปลี่ยนแปลงอื่น ๆ เพื่อเรียกใช้การอัปเดต
เช่นเดียวกับ Frida-trace เอาต์พุตจะเป็นสีตามเธรดการโทร API
ใต้ ID เธรดทันทีในจอแสดงผลคือชื่อวิธี JNI API ชื่อเมธอดตรงกับที่เห็นในไฟล์ส่วนหัว jni.h
บรรทัดที่ตามมามีรายการอาร์กิวเมนต์ที่ระบุโดย A |- หลังจาก |- อักขระเป็นประเภทอาร์กิวเมนต์ตามด้วยค่าอาร์กิวเมนต์ สำหรับ jmethods, jfields และ jclasses ประเภท Java จะแสดงในวงเล็บปีกกา สิ่งนี้ขึ้นอยู่กับ jnitrace เมื่อเห็นวิธีดั้งเดิมฟิลด์หรือการค้นหาในชั้นเรียน สำหรับวิธีการใด ๆ ที่ผ่านบัฟเฟอร์ jnitrace จะแยกบัฟเฟอร์จากอาร์กิวเมนต์และแสดงเป็น hexdump ต่ำกว่าค่าอาร์กิวเมนต์
ค่าส่งคืนจะแสดงที่ด้านล่างของรายการเป็น |= และจะไม่ปรากฏสำหรับวิธีการเป็นโมฆะ
หากเปิดใช้งาน backtrace จะมีการแสดง FRIDA backtrace ด้านล่างการโทรวิธี โปรดทราบตามเอกสาร Frida backtrace ฟัซซี่ไม่ถูกต้องเสมอไปและ backtrace ที่ถูกต้องอาจให้ผลลัพธ์ที่ จำกัด
เป้าหมายของโครงการนี้คือการสร้างเครื่องมือที่สามารถติดตามการโทร JNI API ได้อย่างมีประสิทธิภาพสำหรับแอปพลิเคชัน Android ส่วนใหญ่
น่าเสียดายที่วิธีการที่ง่ายที่สุดในการเชื่อมต่อกับพอยน์เตอร์ฟังก์ชั่นทั้งหมดในโครงสร้าง JNIENV โอเวอร์โหลดแอปพลิเคชัน มันทำให้เกิดความผิดพลาดตามจำนวนการเรียกใช้ฟังก์ชันที่ทำโดยไลบรารีที่ไม่เกี่ยวข้องอื่น ๆ โดยใช้ฟังก์ชั่นเดียวกันใน libart.so
เพื่อจัดการกับอุปสรรคด้านประสิทธิภาพนั้น jnitrace สร้าง Shadow Jnienv ว่าสามารถจัดหาให้กับห้องสมุดที่ต้องการติดตาม jnienv นั้นมีชุดของแทรมโพลีนฟังก์ชั่นที่ตีกลับการเรียก JNI API ผ่าน Frida NativeCallbacks ที่กำหนดเองเพื่อติดตามอินพุตและเอาต์พุตของฟังก์ชั่นเหล่านั้น
Frida API ทั่วไปทำหน้าที่ได้อย่างยอดเยี่ยมในการจัดหาแพลตฟอร์มเพื่อสร้างแทรมโพลีนฟังก์ชั่นเหล่านั้นด้วยความพยายามน้อยที่สุด อย่างไรก็ตามวิธีการง่ายๆนั้นไม่ได้ผลสำหรับ JNIENV API ทั้งหมด ปัญหาสำคัญเกี่ยวกับการติดตามวิธีการทั้งหมดคือการใช้อาร์กิวเมนต์ variadic ใน API มันเป็นไปไม่ได้ที่จะสร้าง nativeCallback สำหรับฟังก์ชั่นเหล่านี้ล่วงหน้าเนื่องจากไม่ทราบล่วงหน้าการรวมกันของวิธี Java ที่แตกต่างกันทั้งหมดที่จะเรียก
วิธีแก้ปัญหาคือการตรวจสอบกระบวนการสำหรับการโทรไปยัง GetMethodID หรือ GetStaticMethodID ใช้เพื่อค้นหาตัวระบุวิธีการจากรันไทม์ เมื่อ jnitrace เห็นการค้นหา jmethodID มันมีการแมปที่รู้จักกันดีของ ID ไปยังลายเซ็นวิธี ต่อมาเมื่อมีการเรียกใช้วิธี JNI Java จะใช้ nativeCallback เริ่มต้นเพื่อแยก ID วิธีการในการโทร ลายเซ็นวิธีการนั้นจะถูกแยกวิเคราะห์เพื่อแยกอาร์กิวเมนต์วิธีการ เมื่อ jnitrace ได้แยกอาร์กิวเมนต์ในวิธีการแล้วมันสามารถสร้าง nativeCallback แบบไดนามิกสำหรับวิธีนั้น NativeCallback ใหม่นั้นถูกส่งคืนและมีการจัดสถาปัตยกรรมเล็กน้อยเกี่ยวกับการตั้งค่าสแต็กและการลงทะเบียนเพื่อให้การเรียกนั้นทำงานได้สำเร็จ nativeCallbacks เหล่านั้นสำหรับวิธีการเฉพาะจะถูกแคชเพื่อให้การเรียกกลับทำงานได้อย่างมีประสิทธิภาพมากขึ้นหากวิธีการหากเรียกว่าหลายครั้ง
สถานที่อื่น ๆ ที่ NativeCallback ง่าย ๆ ไม่เพียงพอสำหรับการแยกอาร์กิวเมนต์จากการเรียกใช้วิธีการโทรโดยใช้ตัวชี้ VA_ARGS เป็นอาร์กิวเมนต์สุดท้าย ในกรณีนี้ jnitrace ใช้รหัสบางอย่างเพื่อแยกอาร์กิวเมนต์ออกจากตัวชี้ที่ให้ไว้ นี่คือสถาปัตยกรรมที่เฉพาะเจาะจง
ข้อมูลทั้งหมดที่ติดตามในการเรียกใช้ฟังก์ชันเหล่านี้จะถูกส่งไปยังแอปพลิเคชันคอนโซล Python ที่จัดรูปแบบและแสดงต่อผู้ใช้
การทดสอบเครื่องมือนี้ส่วนใหญ่ทำบน Android X86_64 Emulator ที่ใช้งานมาร์ชเมลโล่ ปัญหาใด ๆ ที่มีประสบการณ์ในการทำงานบนอุปกรณ์อื่นโปรดยื่นปัญหา แต่ถ้าเป็นไปได้ขอแนะนำให้ลองใช้งานบนตัวจำลองที่คล้ายกัน
สำหรับปัญหาใด ๆ ที่มีประสบการณ์การใช้งาน jnitrace โปรดสร้างปัญหาเกี่ยวกับ GitHub โปรดระบุข้อมูลต่อไปนี้ในปัญหาที่ยื่น: