Android 앱에서 JNI API 사용을 추적하는 Frida 기반 도구.
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 -lib.so- 추적 할 라이브러리를 지정하는 데 사용됩니다. 이 인수는 여러 번 사용하거나 * 사용하여 모든 라이브러리를 추적 할 수 있습니다. 예를 들어 -l libnative-lib.so -l libanother-lib.so 또는 -l * .com.example.myapplication 추적 할 안드로이드 패키지입니다. 이 패키지는 이미 장치에 설치해야합니다.선택적 인수는 다음과 같습니다.
-R <host>:<port> - 원격 Frida 서버의 네트워크 위치를 지정하는 데 사용됩니다. A : 지정되지 않은 경우 LocalHost : 27042는 Deafult에서 사용됩니다.-m <spawn|attach> -는 사용할 Frida 부착 메커니즘을 지정하는 데 사용됩니다. 스폰되거나 첨부 될 수 있습니다. Spawn은 기본 및 권장 옵션입니다.-b <fuzzy|accurate|none> -는 백트레이스 출력을 제어하는 데 사용됩니다. 기본적으로 jnitrace accurate 모드에서 백 트레이서를 실행합니다. 이 옵션은 fuzzy 모드로 변경하거나 none 옵션을 사용하여 백 트레이스를 중지하는 데 사용할 수 있습니다. 차이점에 대한 설명은 Frida 문서를 참조하십시오.-i <regex> - 추적 해야하는 메소드 이름을 지정하는 데 사용됩니다. 이것은 특히 큰 JNI 앱에서 노이즈를 줄이는 데 도움이 될 수 있습니다. 옵션은 여러 번 제공 될 수 있습니다. 예를 들어, -i Get -i RegisterNatives 이름에 Get 또는 RegisterNative가 포함 된 JNI 메소드 만 포함됩니다.-e <regex> - 추적에서 무시 해야하는 메소드 이름을 지정하는 데 사용됩니다. 이것은 특히 큰 JNI 앱에서 노이즈를 줄이는 데 도움이 될 수 있습니다. 옵션은 여러 번 제공 될 수 있습니다. 예를 들어, -e ^Find -e GetEnv getenv를 찾거나 포함하는 모든 jni 메소드 이름 결과에서 제외됩니다.-I <string> - 추적 해야하는 라이브러리에서 내보내기를 지정하는 데 사용됩니다. 이것은 소수의 방법 만 추적하려는 라이브러리에 유용합니다. JNITRACE가 내보내는 기능은 Java 측에서 직접 호출 할 수있는 기능으로, 이에 따라 RegisterNatives를 사용하여 바인딩 된 방법을 포함합니다. 옵션은 여러 번 제공 될 수 있습니다. 예를 들어, -I stringFromJNI -I nativeMethod([B)V Java_com_nativetest_MainActivity_stringFromJNI 라는 라이브러리의 내보내기를 포함 할 수 있으며, nativeMethod([B)V 의 서명과 레지스터 이름을 사용하여 바인딩하는 메소드를 포함 할 수 있습니다.-E <string> 추적해서는 안되는 라이브러리에서 내보내기를 지정하는 데 사용됩니다. 이것은 당신이 당신이 무시하고자하는 바쁜 네이티브 콜 그룹이있는 도서관에 유용합니다. JNITRACE가 내보내는 기능은 Java 측에서 직접 호출 할 수있는 기능으로, 이에 따라 RegisterNatives를 사용하여 바인딩 된 방법을 포함합니다. 옵션은 여러 번 제공 될 수 있습니다. 예를 들어, -E JNI_OnLoad -E nativeMethod JNI_OnLoad 함수 호출 추적과 nativeMethod 라는 이름의 모든 메소드에서 제외됩니다.-o path/output.json jnitrace 추적 된 모든 데이터를 저장하는 출력 경로를 지정하는 데 사용됩니다. 이 정보는 JSON 형식으로 저장되어 추적 데이터의 후반 처리를 허용합니다.-p path/to/script.js 제공된 경로는 jnitrace 스크립트가로드되기 전에 Frida 스크립트를 대상 프로세스에로드하는 데 사용됩니다. 이것은 jnitrace 시작되기 전에 안티 프리 다 또는 방지 코드를 물리 치는 데 사용될 수 있습니다.-a path/to/script.js 제공된 경로는 jnitrace 가로드 된 후 Frida 스크립트를 대상 프로세스에로드하는 데 사용됩니다.--hide-data 콘솔에 표시되는 출력량을 줄이는 데 사용됩니다. 이 옵션은 HexDump 또는 String De-References로 표시되는 추가 데이터를 숨 깁니다.--ignore-env 이 옵션을 사용하면 앱이 Jnienv struct를 사용하여 모든 통화를 숨 깁니다.--ignore-vm 이 옵션을 사용하면 앱이 Javavm Struct를 사용하여 모든 통화를 숨 깁니다.--aux <name=(string|bool|int)value> -응용 프로그램을 스폰 할 때 사용자 정의 매개 변수를 전달하는 데 사용됩니다. 예를 들어 --aux='uid=(int)10' 기본 사용자 0 대신 사용자 10에 대한 응용 프로그램을 스폰합니다.메모
jnitrace 실행하기 전에 Frida-Server가 실행 중임을 기억하십시오. Frida 설치를위한 기본 지침이 준수 된 경우 다음 명령이 jnitrace 에 대한 서버를 시작합니다.
adb shell /data/local/tmp/frida-server
Jnitrace에 전원을 공급하는 엔진은 별도의 프로젝트로 제공됩니다. 이 프로젝트를 사용하면 Frida Interceptor 사용하여 함수 및 주소에 연결하는 데 익숙한 방법으로 JNITRACE를 가져와 개별 JNI API 호출을 추적 할 수 있습니다.
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 인수에서 버퍼를 추출하여 인수 값 아래의 16 진수로 표시합니다.
리턴 값은 목록의 맨 아래에 |= 로 표시되며 무효 방법에는 존재하지 않습니다.
백 트레이스가 활성화되면 메소드 호출 아래에 Frida 백 트레이스가 표시됩니다. Frida Docs에 따라 퍼지 백 트레이스가 항상 정확하지는 않으며 정확한 백 트레이스가 제한된 결과를 제공 할 수 있습니다.
이 프로젝트의 목표는 대부분의 Android 응용 프로그램에 대해 JNI API를 효율적으로 추적 할 수있는 도구를 만드는 것이 었습니다.
불행히도, Jnienv 구조의 모든 함수 포인터에 부착하는 가장 간단한 접근법은 응용 프로그램에 과부하가됩니다. libart.so 에서 동일한 함수를 사용하는 다른 관련없는 라이브러리에서 수행 한 수의 기능 호출에 기초하여 충돌이 발생합니다.
이러한 성능 장벽을 다루기 위해 jnitrace 추적하려는 라이브러리에 공급할 수있는 그림자 jnienv를 만듭니다. Jnienv에는 JNI API가 일부 사용자 정의 Frida NativeCallbacks를 통해 JNI API 호출을 반사하여 해당 함수의 입력 및 출력을 추적하는 일련의 기능 트램폴린이 포함되어 있습니다.
일반 Frida API는 최소한의 노력으로 기능 트램폴린을 구축 할 수있는 플랫폼을 제공하는 데 큰 도움이됩니다. 그러나이 간단한 접근 방식이 모든 Jnienv API에 효과가있는 것은 아닙니다. 모든 방법을 추적하는 데있어 주요 문제는 API에서 변수 인수를 사용하는 것입니다. 호출 될 Java 방법의 모든 다른 조합이 미리 알려지지 않기 때문에 이러한 기능에 대한 nativecallback을 미리 미리 만들 수는 없습니다.
해결책은 런타임에서 메소드 식별자를 찾는 데 사용되는 GetMethodID 또는 GetStaticMethodID 호출하는 프로세스를 모니터링하는 것입니다. jnitrace jmethodID 조회를 보면 ID의 메소드 서명에 알려진 알려진 매핑이 있습니다. 나중에 JNI Java 메소드 호출이 이루어지면 초기 nativecallback을 사용하여 통화에서 메소드 ID를 추출합니다. 그런 다음이 메소드 서명을 구문 분석하여 메소드 인수를 추출합니다. jnitrace 가이 방법에서 인수를 추출하면 해당 방법에 대한 nativecallback을 동적으로 생성 할 수 있습니다. 새로운 nativecallback이 반환되고 약간의 아키텍처 특정 쉘 코드는 스택 및 레지스터 설정을 처리하여 해당 호출을 성공적으로 실행할 수 있도록합니다. 특정 방법에 대한 NativeCallback은 캐시되어 콜백이 여러 번 호출되는 경우 방법을보다 효율적으로 실행할 수 있도록합니다.
간단한 nativecallback이 메소드 호출에서 인수를 추출하기에 충분하지 않은 다른 장소는 VA_ARGS 포인터를 최종 인수로 사용하는 통화입니다. 이 경우 jnitrace 일부 코드를 사용하여 제공된 포인터에서 인수를 추출합니다. 다시 이것은 아키텍처에 따라 다릅니다.
이러한 기능 호출에서 추적 된 모든 데이터는 Python Console 응용 프로그램으로 전송되어이를 사용자에게 형식화하고 표시합니다.
이 도구의 대부분의 테스트는 Marshmallow를 실행하는 Android X86_64 에뮬레이터에서 수행되었습니다. 다른 장치에서 실행되는 경험이있는 모든 문제는 문제를 제출하지만 가능하면 유사한 에뮬레이터에서 실행을 시도하는 것이 좋습니다.
jnitrace 실행하는 경험이있는 모든 문제는 GitHub에서 문제를 만드십시오. 제출 된 문제에 다음 정보를 포함하십시오.