ART定制方案比较
源码修改编译,定制rom 优点:开发简单,可以直接访问ART中的海量的丰富api 缺点:较笨重,首先需要一台性能足够强大的pc,其次,每次修改完代码都需要重新编译,刷机,测试。
基于frida,xposed等hook框架定制 优点:轻量,快捷,不需要性能强大的pc,也无需经历耗时的编译和刷机测试阶段,往往只需要重启app即可 缺点:往往无法直接复用ART源码中已有的api,只能后使用有限的api
将修改源码定制rom和frida等hook框架相结合,比如FART和frida相结合,脱壳功能更强大。 优点:结合了frida的轻量,快捷以及修改源码的简单。
工具使用: 1. 可以使用AndroidStudio导入aosp源码,对framework层相关的java类进行源码阅读和修改定制 2. 可以使用clion等ide利用aosp编译过程中生成的Cmakelists文件,导入ART部分的源码进行阅读和修改定制。
针对ART进行定制时,对于整个AOSP源码,Java层代码只关心Framework、libcore,因此没有必要全部作为源码导入 以下教程用的机型是pixel xl,源码是android 8.1
第一种:源码修改编译,定制rom 编译生成AndroidStudio项目配置文件并导入源码
编译生成idegen.jar在AOSP源码目录下输入以下命令,生成idegen.jar1 2 3 source build/envsetup.sh lunch aosp_marlin-userdebug mmm development/tools/idegen/
生成android.ipr和android.iml 执行idegen.sh脚本,生成即可1 development/tools/idegen/idegen.sh
运行完毕上面的命令之后,就在根目录生成了2个文件:android.ipr和android.iml
排除不必要的模块,提高加载速度 打开android.iml,找到excludeFolder属性,我们可以看到默认有一些模块是被排除了,不会被导入的。但实际上,整个AOSP代码中,我们只关心framework模块和libcore模块,因此可以根据自己的需求去掉其它所有模块,只保留framework和libcore,比如将exclude修改为如下内容:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <excludeFolder url="file://$MODULE_DIR$/art" /> <excludeFolder url="file://$MODULE_DIR$/bionic" /> <excludeFolder url="file://$MODULE_DIR$/bootable" /> <excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/cts" /> <excludeFolder url="file://$MODULE_DIR$/dalvik" /> <excludeFolder url="file://$MODULE_DIR$/developers" /> <excludeFolder url="file://$MODULE_DIR$/development" /> <excludeFolder url="file://$MODULE_DIR$/device" /> <excludeFolder url="file://$MODULE_DIR$/docs" /> <excludeFolder url="file://$MODULE_DIR$/external" /> <excludeFolder url="file://$MODULE_DIR$/hardware" /> <excludeFolder url="file://$MODULE_DIR$/kernel" /> <excludeFolder url="file://$MODULE_DIR$/libcore" /> <excludeFolder url="file://$MODULE_DIR$/libnativehelper" /> <excludeFolder url="file://$MODULE_DIR$/out" /> <excludeFolder url="file://$MODULE_DIR$/pdk" /> <excludeFolder url="file://$MODULE_DIR$/platform_testing" /> <excludeFolder url="file://$MODULE_DIR$/prebuilts" /> <excludeFolder url="file://$MODULE_DIR$/sdk" /> <excludeFolder url="file://$MODULE_DIR$/system" /> <excludeFolder url="file://$MODULE_DIR$/test" /> <excludeFolder url="file://$MODULE_DIR$/toolchain" /> <excludeFolder url="file://$MODULE_DIR$/tools" /> <excludeFolder url="file://$MODULE_DIR$/.repo" />
这样修改之后,打开速度会快很多。接下来使用AndroidStudio直接打开android.ipr文件。 > File-Open…,找到android.ipr即可。导入完成之后,就能看到整个AOSP源码了:橘红色的是在上一步exclude排除掉的模块。剩余配置参考以下文章:https://juejin.cn/post/6969508324618272782 https://blog.csdn.net/yanbober/article/details/48846331
Clion导入ART
编译生成cmakelists文件 打开以下两个开关,CMakeLists.txt就会根据编译环境自动生成1 2 3 4 source build/envsetup.sh lunch aosp_marlin-userdebug export SOONG_GEN_CMAKEFILES=1 export SOONG_GEN_CMAKEFILES_DEBUG=1
下面开始编译
生成的相关项目的cmakelists文件存放在out目录,不如我们需要导入ART的源码部分对应的路径为:out/development/ide/clion/art,可以看到下面还有很多模块,我们选择使用arm32或者arm64的runtime模块即可,对应的cmakelists文件在out/development/ide/clion/art/runtime/libart-arm-android/CMakeLists.txt以及out/development/ide/clion/art/runtime/libart-arm64-android/CMakeLists.txt然后修改根目录为源码的根目录 Tools > CMake > Change Project Root:
APP运行过程中so加载流程源码分析 接下来对so的加载流程进行分析,加载so有两种方式System.loadLibrary(“so文件名”);System.load(“so文件相对路径”);loadLibrary通过loader.findLibrary(libraryName)函数找到库文件的绝对路径,后续跟load一样调用doLoad(String name, ClassLoader loader),最后去调用art下的nativeLoad native函数/libcore/ojluni/src/main/java/java/lang/Runtime.java
1 2 3 4 5 6 7 8 9 10 11 private String doLoad (String name, ClassLoader loader) { String librarySearchPath = null ; if (loader != null && loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; librarySearchPath = dexClassLoader.getLdLibraryPath(); } java.lang.System.logE("Runtime doLoad -> " + name + ", classLoader -> " + loader.toString()); synchronized (this ) { return nativeLoad(name, loader, librarySearchPath); } }
也就是说doLoad和nativeLoad是所有加载so文件都会调用的函数,那可以在这个函数下添加log信息,这样就可以在Java下监控所有的so
1 java.lang.System.logE("Runtime doLoad -> " + name + ", classLoader -> " + loader.toString());
通过查找源码知道nativeLoad是在xref: /libcore/ojluni/src/main/native/Runtime.c下
1 2 3 4 5 6 JNIEXPORT jstring JNICALL Runtime_nativeLoad (JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) { return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath); }
然后通过调用链找到 nativeLoad ->Runtime_nativeLoad(Runtime.c) -> JVM_NativeLoad(Openjdk_Jvm.cc) -> LoadNativeLibrary(java_vm_ext.cc)最后是LoadNativeLibrary函数,查看函数流程,在函数刚进来的时候添加日志
1 LOG(ERROR) << "[java_vm_ext.cc]JavaVMExt::LoadNativeLibrary -> " << path.c_str();
在加载本地库的前后添加日志
1 2 3 4 5 6 7 8 9 10 11 LOG(ERROR) << "[java_vm_ext.cc]before call LoadNativeLibrary -> " << path_str; void * handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path, &needs_native_bridge, error_msg); LOG(ERROR) << "[java_vm_ext.cc]after call LoadNativeLibrary -> " << path_str;
接下来判断是否有JNI_OnLoad,没有则结束函数,有就加载JNI_OnLoad,在加载JNI_OnLoad前后添加日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 bool was_successful = false ;void * sym = library->FindSymbol("JNI_OnLoad" , nullptr);if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]" ; was_successful = true ; } else { ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]" ; typedef int (*JNI_OnLoadFn) (JavaVM*, void *) ; JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); LOG(ERROR) << "[java_vm_ext.cc]before call " << path_str << ".JNI_Onload" ; int version = (*jni_on_load)(this, nullptr); LOG(ERROR) << "[java_vm_ext.cc]after call " << path_str << ".JNI_Onload" << ", returm: " << version; if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21 ) { EnsureFrontOfChain(SIGSEGV); } self->SetClassLoaderOverride(old_class_loader.get()); if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"" , path.c_str()); } else if (JavaVMExt::IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d" , path.c_str(), version); } else { was_successful = true ; } VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure" ) << " from JNI_OnLoad in \"" << path << "\"]" ; }
到这里也能看出来so中的JNI_Onload的函数名必须要c的形式进行编译,还要导出的原因就是这里它需要去查找符号名重新编译,刷入手机运行有加壳的app,查看log
第二种:基于frida,xposed等hook框架定制 要实现对加载so监控的话,可以结合流程当中的api进行hook来监控so的加载流程,在hook中选择的api层级越底层越好,如果只通过System.load, loadLibrary进行监控的话,可能会遗漏掉通过runtime进行加载的so。上面分析到加载so底层的api是libnativeloader.so下的OpenNativeLibrary和libart.so下的LoadNativeLibrary可以使用frida进行hook,在hook前可以先把libnativeloader.so和libart.so脱出来用ida查看导出表OpenNativeLibrary和LoadNativeLibrary的导出符号/system/lib/libart.so或者/system/lib64/libart.so/system/lib/libnativeloader.so或者/system/lib64/libnativeloader.soOpenNativeLibrary的导出符号:_ZN7android17OpenNativeLibraryEP7_JNIEnviPKcP8_jobjectP8_jstringPbPNSt3__112basic_stringIcNS9_11char_traitsIcEENS9_9allocatorIcEEEE
使用frida进行hook 下面就监控OpenNativeLibrary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function tracesoload ( ){ console .log (Process .arch ); var libnativeloaderModule = Process .getModuleByName ("libnativeloader.so" ); var OpenNativeLibraryAddr = libnativeloaderModule.getExportByName ("_ZN7android17OpenNativeLibraryEP7_JNIEnviPKcP8_jobjectP8_jstringPbPNSt3__112basic_stringIcNS9_11char_traitsIcEENS9_9allocatorIcEEEE" ); Interceptor .attach (OpenNativeLibraryAddr , { onEnter : function (args ){ this .sopath = ptr (args[2 ]).readUtf8String (); console .log ("go into OpenNativeLibrary -> " + this .sopath ); }, onLeave : function (retval ) { console .log ("leave OpenNativeLibrary -> " + this .sopath , ", return: " + retval); } }) } function main ( ){ tracesoload (); } setImmediate (main);
输出打印
1 2 3 4 5 6 7 8 9 [AOSP on msm8996::com.iCitySuzhou.suzhou001]-> arm64 go into OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libshell-super.com.iCitySuzhou.suzhou001.so leave OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libshell-super.com.iCitySuzhou.suzhou001.so go into OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libjcore230.so leave OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libjcore230.so go into OpenNativeLibrary -> /system/app/webview/webview.apk!/lib/arm64-v8a/libwebviewchromium.so leave OpenNativeLibrary -> /system/app/webview/webview.apk!/lib/arm64-v8a/libwebviewchromium.so go into OpenNativeLibrary -> /system/lib64/libwebviewchromium_plat_support.so leave OpenNativeLibrary -> /system/lib64/libwebviewchromium_plat_support.so
加载so后还会对这个so进行查找是否含有 JNI_OnLoadvoid* sym = library->FindSymbol(“JNI_OnLoad”, nullptr);
1 2 3 4 5 6 7 void * FindSymbol (const std ::string & symbol_name, const char * shorty = nullptr) REQUIRES (!Locks::mutator_lock_) { return NeedsNativeBridge() ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty) : FindSymbolWithoutNativeBridge(symbol_name.c_str()); }
1 2 3 4 5 void * FindSymbolWithoutNativeBridge (const std ::string & symbol_name) REQUIRES (!Locks::mutator_lock_) { CHECK(!NeedsNativeBridge()); return dlsym(handle_, symbol_name.c_str()); }
1 void * dlsym (void * handle, const char * _Nonnull symbol) ;
这里对dlsym进行监控的话,就可以监视接下来对 JNI_OnLoad 的查询的过程dlsym位于libc.so,也使用freida进行hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function tracesoload ( ){ console .log (Process .arch ); var libnativeloaderModule = Process .getModuleByName ("libnativeloader.so" ); var OpenNativeLibraryAddr = libnativeloaderModule.getExportByName ("_ZN7android17OpenNativeLibraryEP7_JNIEnviPKcP8_jobjectP8_jstringPbPNSt3__112basic_stringIcNS9_11char_traitsIcEENS9_9allocatorIcEEEE" ); Interceptor .attach (OpenNativeLibraryAddr , { onEnter : function (args ){ this .sopath = ptr (args[2 ]).readUtf8String (); console .log ("go into OpenNativeLibrary -> " + this .sopath ); }, onLeave : function (retval ) { console .log ("leave OpenNativeLibrary -> " + this .sopath , ", return: " + retval); } }) var dlsymAddr = Module .findExportByName ("libc.so" , "dlsym" ); Interceptor .attach (dlsymAddr, { onEnter : function (args ){ this .handle = args[0 ]; this .symbol = ptr (args[1 ]).readUtf8String (); }, onLeave : function (retval ){ console .log ("leave dlsym: handle: " + this .handle + ", symbol: " + this .symbol , ", return: " + retval); } }) } function main ( ){ tracesoload (); } setImmediate (main);
已经看到查找JNI_OnLoad的流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 leave OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libshell-super.com.iCitySuzhou.suzhou001.so , return: 0x49668d9309b699ed leave dlsym: handle: 0x49668d9309b699ed, symbol: JNI_OnLoad , return: 0x730a123a5c leave dlsym: handle: 0xde2d62226fd8b40d, symbol: InitEsxProfile , return: 0x7314e10620 go into OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libjcore230.so leave OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001--s3SCRYsyaFmNLfEq3Pj3g==/lib/arm64/libjcore230.so , return: 0x513c62ce705a71c5 leave dlsym: handle: 0x513c62ce705a71c5, symbol: JNI_OnLoad , return: 0x7308e5c5bc leave OpenNativeLibrary -> /system/lib64/libwebviewchromium_plat_support.so , return: 0xef0d55dae65a7f63 leave dlsym: handle: 0xef0d55dae65a7f63, symbol: JNI_OnLoad , return: 0x731ca1e740 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_library_1loader_LibraryLoader_nativeLibraryLoaded , return: 0x730ce0dc34 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_library_1loader_LibraryLoader_nativeGetVersionNumber , return: 0x730ce0dee0 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_TraceEvent_nativeRegisterEnabledObserver , return: 0x730ce10300 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_PathService_nativeOverride , return: 0x730ce0ed00 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwContentsStatics_nativeGetProductVersion , return: 0x730cdca980 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwGLFunctor_nativeGetAwDrawGLFunction , return: 0x730cdcc4e4 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwContents_nativeSetAwDrawSWFunctionTable , return: 0x730cdbfcc4 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwContents_nativeSetAwDrawGLFunctionTable , return: 0x730cdb714c leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwContentsStatics_nativeSetCheckClearTextPermitted , return: 0x730cdcaad4 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_CommandLine_nativeHasSwitch , return: 0x730ce08b70 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_android_1webview_AwContentsStatics_nativeSetSafeBrowsingEnabledByManifest , return: 0x730cdcaa24 leave dlsym: handle: 0xe5b5bf72a8b510e7, symbol: Java_org_chromium_base_CommandLine_nativeAppendSwitch , return: 0x730ce08cd8 ......
第三种:定制rom和frida等hook框架相结合 在art中一些关键地方可以预留api给frida hook, 以下面跟踪jni函数绑定为例。
ART定制跟中jni函数绑定 在上面的日志看到很多symbol的值类似静态注册的JNI函数jni函数的绑定:每一个java层函数在native层都对应一个ArtMethod对象,jni函数在执行前必须完成对jni函数所在类的加载和初始化、以及该jni函数的ArtMethod对象当中属性和so中具体函数地址的绑定
静态注册的jni函数
动态注册的jni函数
静态注册的jni函数 通过源码追踪jni注册流程,首先当一个类中的 JNI 函数被调用的时候,ART 会首先确保该类已经被加载和初始化。这一过程通常通过 class_linker.cc 文件中的 ClassLinker::LoadClass 方法完成。在加载过程中,类的属性和成员函数也会被加载和初始化,这一步由 LoadClassMembers 方法处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 void ClassLinker::LoadClass (Thread* self, const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, Handle<mirror::Class> klass) { const uint8_t * class_data = dex_file.GetClassData(dex_class_def); if (class_data == nullptr) { return ; } LoadClassMembers(self, dex_file, class_data, klass); } void ClassLinker::LoadClassMembers (Thread* self, const DexFile& dex_file, const uint8_t * class_data, Handle<mirror::Class> klass) { { for (size_t i = 0 ; it.HasNextDirectMethod(); i++, it.Next()) { ArtMethod* method = klass->GetDirectMethodUnchecked(i, image_pointer_size_); LoadMethod(dex_file, it, klass, method); LinkCode(this, method, oat_class_ptr, class_def_method_index); uint32_t it_method_index = it.GetMemberIndex(); if (last_dex_method_index == it_method_index) { method->SetMethodIndex(last_class_def_method_index); } else { method->SetMethodIndex(class_def_method_index); last_dex_method_index = it_method_index; last_class_def_method_index = class_def_method_index; } class_def_method_index++; } } } static void LinkCode (ClassLinker* class_linker, ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) REQUIRES_SHARED (Locks::mutator_lock_) { ...... if (method->IsNative()) { method->UnregisterNative(); if (enter_interpreter || quick_code == nullptr) { const void * entry_point = method->GetEntryPointFromQuickCompiledCode(); DCHECK(class_linker->IsQuickGenericJniStub(entry_point) || class_linker->IsQuickResolutionStub(entry_point)); } } } void ArtMethod::UnregisterNative () { CHECK(IsNative() && !IsFastNative()) << PrettyMethod(); SetEntryPointFromJni(GetJniDlsymLookupStub()); } void SetEntryPointFromJni (const void * entrypoint) { DCHECK(IsNative()); SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize); } static inline const void * GetJniDlsymLookupStub () { return reinterpret_cast<const void *>(art_jni_dlsym_lookup_stub); }
不管是静态注册还是动态注册的jni函数, 当函数所在的类被加载进到 LinkCode 的时候,会进行函数第一次的地址绑定,当这个函数是静态注册的话,在第一次的时候会去调用dlsym查找符号进行绑定,当第二次调用的时候,由于入口的已经被绑定到真正的地址,就不在执行查找的过程,直接发起调用。实际上 art_jni_dlsym_lookup_stub 是汇编代码,通过源码查找 art_jni_dlsym_lookup_stubxref: /art/runtime/arch/arm/jni_entrypoints_arm.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 .extern artFindNativeMethod ENTRY art_jni_dlsym_lookup_stub push {r0 , r1 , r2 , r3 , lr } .cfi_adjust_cfa_offset 20 .cfi_rel_offset r0 , 0 .cfi_rel_offset r1 , 4 .cfi_rel_offset r2 , 8 .cfi_rel_offset r3 , 12 .cfi_rel_offset lr , 16 sub sp , #12 .cfi_adjust_cfa_offset 12 blx artFindNativeMethod mov r12 , r0 add sp , #12 .cfi_adjust_cfa_offset -12 cbz r0 , 1 f pop {r0 , r1 , r2 , r3 , lr } .cfi_adjust_cfa_offset -20 .cfi_restore r0 .cfi_restore r1 .cfi_restore r2 .cfi_restore r3 .cfi_restore lr bx r12
无论静态注册还是动态注册,当类加载完就被绑定到art_jni_dlsym_lookup_stub这个桥函数中,会执行这段汇编调用artFindNativeMethod去so查符号地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #if defined(__arm__) || defined(__aarch64__) extern "C" const void * artFindNativeMethod () { Thread* self = Thread::Current(); #else extern "C" const void * artFindNativeMethod (Thread* self) { DCHECK_EQ(self, Thread::Current()); #endif Locks::mutator_lock_->AssertNotHeld(self); ScopedObjectAccess soa (self) ; ArtMethod* method = self->GetCurrentMethod(nullptr); DCHECK(method != nullptr); void * native_code = soa.Vm()->FindCodeForNativeMethod(method); if (native_code == nullptr) { self->AssertPendingException(); return nullptr; } return method->RegisterNative(native_code, false ); } void * JavaVMExt::FindCodeForNativeMethod (ArtMethod* m) { ...... Thread* const self = Thread::Current(); void * native_method = libraries_->FindNativeMethod(self, m, detail); ...... return native_method; } void * FindNativeMethodInternal (Thread* self, void * declaring_class_loader_allocator, const char * shorty, const std ::string & jni_short_name, const std ::string & jni_long_name) REQUIRES (!Locks::jni_libraries_lock_) REQUIRES (!Locks::mutator_lock_) { MutexLock mu (self, *Locks::jni_libraries_lock_) ; for (const auto & lib : libraries_) { SharedLibrary* const library = lib.second; if (library->GetClassLoaderAllocator() != declaring_class_loader_allocator) { continue ; } const char * arg_shorty = library->NeedsNativeBridge() ? shorty : nullptr; void * fn = library->FindSymbol(jni_short_name, arg_shorty); if (fn == nullptr) { fn = library->FindSymbol(jni_long_name, arg_shorty); } if (fn != nullptr) { VLOG(jni) << "[Found native code for " << jni_long_name << " in \"" << library->GetPath() << "\"]" ; return fn; } } return nullptr; } void * FindSymbol (const std ::string & symbol_name, const char * shorty = nullptr) REQUIRES (!Locks::mutator_lock_) { return NeedsNativeBridge() ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty) : FindSymbolWithoutNativeBridge(symbol_name.c_str()); } void * FindSymbolWithoutNativeBridge (const std ::string & symbol_name) REQUIRES (!Locks::mutator_lock_) { CHECK(!NeedsNativeBridge()); return dlsym(handle_, symbol_name.c_str()); }
jni_short_name:是本地方法的短名,它的格式通常是 Java_<类的完全限定名><方法名>,其中类名和方法名中的每个字符(除了字母和数字)都被替换为 。 例如: Java_com_example_MyClass_myMethod jni_long_name:是本地方法的长名,它的格式通常是 Java <类的完全限定名> <方法名>__<参数签名>,其中参数签名部分用 _ 分隔每个参数类型的描述符。 例如: Java_com_example_MyClass_myMethod__ILjava_lang_String_2 通过FindCodeForNativeMethod找到的函数地址后,会调用 RegisterNative 才把它的地址绑定到artmethod里面,method->RegisterNative(native_code, false);回到上面的汇编代码
1 2 3 blx artFindNativeMethodmov r12 , r0 bx r12
经过artFindNativeMethod查到绑定后的artmethod地址返回接下来就可以对上面的一些流程添加log打印,在artFindNativeMethod下可以监控静态注册jni函数绑定的流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #if defined(__arm__) || defined(__aarch64__) extern "C" const void * artFindNativeMethod () { Thread* self = Thread::Current(); #else extern "C" const void * artFindNativeMethod (Thread* self) { DCHECK_EQ(self, Thread::Current()); #endif Locks::mutator_lock_->AssertNotHeld(self); ScopedObjectAccess soa (self) ; ArtMethod* method = self->GetCurrentMethod(nullptr); DCHECK(method != nullptr); void * native_code = soa.Vm()->FindCodeForNativeMethod(method); if (native_code == nullptr) { self->AssertPendingException(); return nullptr; } else { std ::stringstream oss; oss << "[artFindNativeMethod]StaticRegisterJniMethod->" << method->PrettyMethod() << ", add: " << native_code; const char *content = oss.str().c_str(); if (strstr (content, "zsklog" ) != nullptr) { LOG(ERROR) << content; } } return method->RegisterNative(native_code, false ); }
上面添加了标识 strstr(content, “zsklog”) 用来让frida可以控制日志打印开关
动态注册的jni函数 动态注册的jni函数都是通过 RegisterNatives 去注册绑定地址,定位到源码的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static jint RegisterNatives (JNIEnv* env, jclass java_class, const JNINativeMethod* methods, jint method_count) { ...... CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives" , methods, JNI_ERR); for (jint i = 0 ; i < method_count; ++i) { const char * name = methods[i].name; const char * sig = methods[i].signature; const void * fnPtr = methods[i].fnPtr; bool is_fast = false ; ...... const void * final_function_ptr = m->RegisterNative(fnPtr, is_fast); UNUSED(final_function_ptr); } return JNI_OK; }
遍历methods,最后跟静态注册jni函数的流程一样去调用 RegisterNative ,静态注册的log已经添加,那在 RegisterNative 里添加log不就能跟踪到动态注册的函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const void * ArtMethod::RegisterNative (const void * native_method, bool is_fast) { CHECK(IsNative()) << PrettyMethod(); CHECK(!IsFastNative()) << PrettyMethod(); CHECK(native_method != nullptr) << PrettyMethod(); if (is_fast) { AddAccessFlags(kAccFastNative); } void * new_native_method = nullptr; Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this, native_method, &new_native_method); std ::stringstream oss; oss << "[ArtMethod::RegisterNative]JniMethod->" << this->PrettyMethod() << ", add: " << native_method; const char *content = oss.str().c_str(); if (strstr (content, "zsklog" ) != nullptr) { LOG(ERROR) << content; } SetEntryPointFromJni(new_native_method); return new_native_method; }
jni函数的绑定分类总结
静态函数的jni函数:有两次绑定,第一次是在类加载时,会绑定到ART中的“桥函数”(art_jni_dlsym_lookup_stub) ,第二次绑定是在该jni函数第一次被真正调用时,ART 会通过桥函数查找该函数在本地库中的实际地址,并进行绑定。后续调用将直接使用该地址,避免重复查找。 LoadClass -> LoadClassMembers -> SetEntryPointFromJni(GetJniDlsymLookupStub() ->art_jni_dlsym_lookup_stub -> artFindNativeMethod -> FindCodeForNativeMethod(method)
动态注册的jni函数:也是有两次绑定,只不过和静态注册的jni函数的第二次绑定不同之处在于是由开发人员编写的代码主动调用RegeisterNatives进行绑定,而不是由ART“帮助”完成绑定。重新编写后刷入,frida代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 function traceJniRegiseter ( ){ var libc_module = Process .getModuleByName ("libc.so" ); var strstr = libc_module.getExportByName ("strstr" ); Interceptor .attach (strstr, { onEnter : function (args ) { this .thread_id = Process .getCurrentThreadId (); this .arg0 = ptr (args[0 ]).readUtf8String (); this .arg1 = ptr (args[1 ]).readUtf8String (); }, onLeave : function (retval ) { if (this .arg1 == "zsklog" ) { console .log ("[" + this .thread_id + "]" + "go into OpenNativeLibrary -> " + this .arg0 ); retval.replace (0x100 ); } } }) } function tracesoload ( ){ console .log (Process .arch ); var libnativeloaderModule = Process .getModuleByName ("libnativeloader.so" ); var OpenNativeLibraryAddr = libnativeloaderModule.getExportByName ("_ZN7android17OpenNativeLibraryEP7_JNIEnviPKcP8_jobjectP8_jstringPbPNSt3__112basic_stringIcNS9_11char_traitsIcEENS9_9allocatorIcEEEE" ); Interceptor .attach (OpenNativeLibraryAddr , { onEnter : function (args ){ this .sopath = ptr (args[2 ]).readUtf8String (); this .thread_id = Process .getCurrentThreadId (); console .log ("[" + this .thread_id + "]" + "go into OpenNativeLibrary -> " + this .sopath ); }, onLeave : function (retval ) { console .log ("[" + this .thread_id + "]" + "leave OpenNativeLibrary -> " + this .sopath , ", return: " + retval); } }) var dlsymAddr = Module .findExportByName ("libc.so" , "dlsym" ); Interceptor .attach (dlsymAddr, { onEnter : function (args ){ this .handle = args[0 ]; this .symbol = ptr (args[1 ]).readUtf8String (); this .thread_id = Process .getCurrentThreadId (); }, onLeave : function (retval ){ console .log ("[" + this .thread_id + "]" + "leave dlsym: handle: " + this .handle + ", symbol: " + this .symbol , ", return: " + retval); } }) } function main ( ){ tracesoload (); traceJniRegiseter (); } setImmediate (main);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 arm64 [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatdata , return: 0x7d1659d000 [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatlastword , return: 0x7d1659e224 [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatbss , return: 0x7d1659f000 [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatbsslastword , return: 0x7d165a2acc [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatbssmethods , return: 0x0 [8007]leave dlsym: handle: 0x2adcc85529763b0b, symbol: oatbssroots , return: 0x0 [8007]go into OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001-IqFG3pwpI6PxX176DYv1tw==/lib/arm64/libshell-super.com.iCitySuzhou.suzhou001.so [8007]leave dlsym: handle: 0xa5c5ee03e19145b9, symbol: mmap , return: 0x7db163acd4 [8007]leave dlsym: handle: 0xa5c5ee03e19145b9, symbol: write , return: 0x7db163b4cc [8007]leave dlsym: handle: 0xa5c5ee03e19145b9, symbol: close , return: 0x7db15ed138 [8007]leave dlsym: handle: 0xa5c5ee03e19145b9, symbol: fdatasync , return: 0x7db163a8b4 [8007]leave OpenNativeLibrary -> /data/app/com.iCitySuzhou.suzhou001-IqFG3pwpI6PxX176DYv1tw==/lib/arm64/libshell-super.com.iCitySuzhou.suzhou001.so , return: 0x5cd2a12d5c2fc103 [8007]leave dlsym: handle: 0x5cd2a12d5c2fc103, symbol: JNI_OnLoad , return: 0x7d160c6a5c [8007]go into OpenNativeLibrary -> [ArtMethod::RegisterNative]JniMethod->void com.wrapper.proxyapplication.WrapperProxyApplication.Ooo0ooO0oO(), add: 0x7d160c8418 [8007]go into OpenNativeLibrary -> [ArtMethod::RegisterNative]JniMethod->int com.wrapper.proxyapplication.CustomerClassLoader.ShowLogs(java.lang.String, int), add: 0x7d160c648c ......