PAGalaxyLab / YAHFA

Yet Another Hook Framework for ART
GNU General Public License v3.0
1.57k stars 351 forks source link

android7.0和android7.1的模拟器出现java.lang.IncompatibleClassChangeError #9

Closed yunshouhu closed 7 years ago

yunshouhu commented 7 years ago

测试环境: android7.0 armeabi模拟器/android7.1 x86模拟器。 多次执行Toast.show方法后出现如下java.lang.IncompatibleClassChangeError异常,然后程序退出

05-24 04:13:43.362: W/YAHFA(1439): hook end 05-24 04:13:43.366: W/YAHFA(1439): hook 05-24 04:13:43.367: W/YAHFA(1439): hook end 05-24 04:13:43.367: I/Choreographer(1439): Skipped 42 frames! The application may be doing too much work on its main thread. 05-24 04:13:44.462: W/YAHFA(1439): hook 05-24 04:13:44.467: D/AndroidRuntime(1439): Shutting down VM 05-24 04:13:44.472: E/AndroidRuntime(1439): FATAL EXCEPTION: main 05-24 04:13:44.472: E/AndroidRuntime(1439): Process: lab.galaxy.yahfa.demoApp, PID: 1439 05-24 04:13:44.472: E/AndroidRuntime(1439): java.lang.IncompatibleClassChangeError: The method 'void android.widget.Toast.show()' was expected to be of type static but instead was found to be of type virtual (declaration of 'com.yunshouhu.hookitem.Hook_Toast_show' appears in /data/app/lab.galaxy.yahfa.demoApp-1/base.apk) 05-24 04:13:44.472: E/AndroidRuntime(1439): at com.yunshouhu.hookitem.Hook_Toast_show.hook(Hook_Toast_show.java:19) 05-24 04:13:44.472: E/AndroidRuntime(1439): at com.yunshouhu.MainActivity$4.onClick(MainActivity.java:88) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.view.View.performClick(View.java:5610) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.view.View$PerformClick.run(View.java:22260) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.os.Handler.handleCallback(Handler.java:751) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.os.Handler.dispatchMessage(Handler.java:95) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.os.Looper.loop(Looper.java:154) 05-24 04:13:44.472: E/AndroidRuntime(1439): at android.app.ActivityThread.main(ActivityThread.java:6077) 05-24 04:13:44.472: E/AndroidRuntime(1439): at java.lang.reflect.Method.invoke(Native Method) 05-24 04:13:44.472: E/AndroidRuntime(1439): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) 05-24 04:13:44.472: E/AndroidRuntime(1439): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) 05-24 04:13:44.515: W/ActivityManager(378): Force finishing activity lab.galaxy.yahfa.demoApp/com.yunshouhu.MainActivity

rk700 commented 7 years ago

Android N下面有比较多的坑,所以还只是试验性质

yunshouhu commented 7 years ago

目前通过对比android6.0和android7.0的源码: http://androidxref.com/7.0.0_r1/xref/art/runtime/class_linker.cc#7380 定位到是runtime->class_linker->ResolveMethod->CheckIncompatibleClassChange引起的。 android7.0之后ClassLinker开启了kForceICCECheck 对调用者进行了强检查。

以下是android7.0 runtime的class_linker.cc源码 template ArtMethod ClassLinker::ResolveMethod(const DexFile& dex_file, uint32_t method_idx, Handle dex_cache, Handle class_loader, ArtMethod referrer, InvokeType type) { DCHECK(dex_cache.Get() != nullptr); // Check for hit in the dex cache. ArtMethod resolved = dex_cache->GetResolvedMethod(method_idx, image_pointersize); if (resolved != nullptr && !resolved->IsRuntimeMethod()) { DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex(); if (kResolveMode == ClassLinker::kForceICCECheck) { if (resolved->CheckIncompatibleClassChange(type)) { ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer); return nullptr; } } return resolved; } // Fail, get the declaring class. const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); mirror::Class klass = ResolveType(dex_file, method_id.classidx, dex_cache, class_loader); if (klass == nullptr) { DCHECK(Thread::Current()->IsExceptionPending()); return nullptr; } .......... } 本来想通过hook绕过检查,但是他们都是inline函数,无法HooK。 您看下有什么办法绕过CheckIncompatibleClassChange检查?问题可能还是在dex_cache这块。

rk700 commented 7 years ago

看了下Android 6.0上的ResolveMethod方法,也是有检查方法的属性的: http://androidxref.com/6.0.1_r10/xref/art/runtime/class_linker.cc#5409

那么问题应该就是Android N的混合编译造成的。Android N的应用在安装时使用的是解释模式,运行过程中可能还会有有编译的过程。所以在应用运行一段时间后,也会有调用到ResolveMethod。但是Android N之前就不存在这个问题,因为应用在安装时就已经编译完成,运行后不会再调用ResolveMethod了。

hook绕过检查感觉确实不太好做,正如所说,是inline的

不过Android N上在应用运行时加载的dex,是编译而非解释的,所以同样的操作放到VirtualHook中应该就不会出现,可以试验下

yunshouhu commented 7 years ago

hook了ThrowIncompatibleClassChangeError方法强制绕过后,后面还是存在调用匹配检查, 从原方法句柄在jit-code-cache中,可以看出如果应用运行时加载的dex,经过dex2oat编译后,应该可以绕过ThrowIncompatibleClassChangeError异常。 http://androidxref.com/7.0.0_r1/xref/art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc#2063 06-02 09:38:39.414: W/YAHFA(2674): Hook_Toast_show 06-02 09:38:39.415: E/YAHFA(2674): Hook_Toast_show yahfahook origin 06-02 09:38:39.415: I/YAHFA-Native(2674): ThrowIncompatibleClassChangeError expected_type=0,found_type=2 //这里hook了ThrowIncompatibleClassChangeError方法强制绕过后 06-02 09:38:39.415: A/art(2674): art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc:2063] Check failed: self->IsExceptionPending() 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] Runtime aborting... 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] Aborting thread: 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] "main" prio=5 tid=1 Runnable 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] | group="" sCount=0 dsCount=0 obj=0x73c3f950 self=0xa3c8b400 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] | sysTid=2674 nice=0 cgrp=default sched=0/0 handle=0xa7fbf534 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] | state=R schedstat=( 0 0 0 ) utm=7 stm=28 core=1 HZ=100 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] | stack=0xbf69d000-0xbf69f000 stackSize=8MB 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] | held mutexes= "abort lock" "mutator lock"(shared held) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #00 pc 003cb8de /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #01 pc 00397f6e /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #02 pc 00394f6b /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #03 pc 00380d43 /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #04 pc 003809fa /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #05 pc 0037127b /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #06 pc 00119572 /system/lib/libart.so (_ZN3art10LogMessageD1Ev+1298) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #07 pc 004cc093 /system/lib/libart.so (???) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #08 pc 0010801d /system/lib/libart.so (art_quick_invoke_static_trampoline_with_access_check+77) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] native: #09 pc 000003ac /dev/ashmem/dalvik-jit-code-cache (deleted)
(Java_com_yunshouhu_hookitem_Hook_1Toast_1show_hook_1dvm__Lcom_yunshouhu_dalvik_XC_1MethodHook_00024MethodHookParam_2Landroid_widget_Toast_2+396) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at com.yunshouhu.hookitem.Hook_Toast_show.hook_dvm(Hook_Toast_show.java:65) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at com.yunshouhu.hookitem.Hook_Toast_show.hook(Hook_Toast_show.java:39) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at com.yunshouhu.MainActivity$4.onClick(MainActivity.java:83) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.view.View.performClick(View.java:5610) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.view.View$PerformClick.run(View.java:22260) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.os.Handler.handleCallback(Handler.java:751) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.os.Handler.dispatchMessage(Handler.java:95) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.os.Looper.loop(Looper.java:154) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at android.app.ActivityThread.main(ActivityThread.java:6077) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at java.lang.reflect.Method.invoke!(Native method) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] Dumping all threads without appropriate locks held: thread list lock 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] All threads: 06-02 09:38:39.434: A/art(2674): art/runtime/runtime.cc:403] All threads:

yunshouhu commented 7 years ago

1、初步测试了通过DexClassLoader动态加载apk/dex的方式去hook后: dex执行了dex2oat流程,不经过jit-code-cache,就没有出现java.lang.IncompatibleClassChangeError异常。 即像博主说的:Android N上在应用运行时加载的dex,是编译而非解释的, 但是多次点击循环执行后,出现了gc的Tried to mark 0xa4d85060 not contained by any spaces异常。 06-03 01:51:52.563: A/art(3245): art/runtime/gc/collector/mark_sweep.cc:413] Tried to mark 0xa4d85060 not contained by any spaces 06-03 01:51:52.563: A/art(3245): art/runtime/utils.cc:184] 12c00000-12e08000 rw-p 00000000 00:04 5615 /dev/ashmem/dalvik-main space (deleted)

2、目前初步估计使用public static void origin(Toast toast) { }方式来保存原方法的句柄在android7.0以上的系统可能会导致: a、声明的调用类型和运行时的调用类型不匹配异常,即强检查了java.lang.IncompatibleClassChangeError。 b、gc的mark_sweep算法无法标记回收,即Tried to mark 0xa4d85060 not contained by any spaces。 也许在android7.0之后使用legend的方法来保存原来的artMethod方法句柄可能兼容性会更好些。

rk700 commented 7 years ago

没有试过用legend的方式在android 7.0上做hook,可以试验一下

rk700 commented 7 years ago

https://github.com/rk700/YAHFA/issues/8 重复