android java层实现hook替换method

  Android上的热修复框架 AndFix 大家都很熟悉了,它的原理实际上很简单:

  方法替换——Java层的每一个方法在虚拟机实现里面都对应着一个ArtMethod的结构体,只要把原方法的结构体内容替换成新的结构体的内容,在调用原方法的时候,真正执行的指令会是新方法的指令;这样就能实现热修复,详细代码见 AndFix。需要了解Android 虚拟机的方法调用过程才能彻底理解。

众所周知,AndFix是一种 native 的hotfix方案,它的替换过程是用 c 在 native层完成的,但其实,我们也可以用纯Java实现它!

方法替换原理

既然我们知道 AndFix 的原理是方法替换,那么为什么直接替换Java里面的 java.lang.reflect.Method 有什么问题吗?直接这样貌似很难下结论,那我们换个思路。我们实现方法替换的结果,就是调用原方法的时候最终是调用被替换的方法。因此,我们可以看看 java.lang.reflect.Method类的 invoke 方法。(Foo.bar()这种直接调用与反射调用Foo.class.getDeclaredMethod(“bar”).invoke(null) 有什么区别吗?)

1 private native Object invoke(Object receiver, Object[] args, boolean accessible)
2         throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

  这个invoke是一个native方法,它的native实现在 art/runtime/native/java_lang_reflect_Method.cc 里面,这个jni方法最终调用了 art/runtime/reflection.cc 的 InvokeMethod方法:

 1 object InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod,
 2                      jobject javaReceiver, jobject javaArgs, bool accessible) {
 3   // 略...
 4
 5   mirror::ArtMethod* m = mirror::ArtMethod::FromReflectedMethod(soa, javaMethod);
 6
 7   mirror::Class* declaring_class = m->GetDeclaringClass();
 8
 9   // 按需初始化类,略。。
10
11   mirror::Object* receiver = nullptr;
12   if (!m->IsStatic()) {
13     // Check that the receiver is non-null and an instance of the field‘s declaring class.
14     receiver = soa.Decode<mirror::Object*>(javaReceiver);
15     if (!VerifyObjectIsClass(receiver, declaring_class)) {
16       return NULL;
17     }
18
19     // Find the actual implementation of the virtual method.
20     m = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m);
21   }
22
23   // 略..
24   InvokeWithArgArray(soa, m, &arg_array, &result, shorty);
25   // 略 。。
26   // Box if necessary and return.
27   return soa.AddLocalReference<jobject>(BoxPrimitive(mh.GetReturnType()->GetPrimitiveType(),
28                                                      result));
29 }

上面函数 InvokeMethod 的第二个参数 javaMethod 就是Java层我们进行反射调用的那个Method对象,在jni层反映为一个jobject;InvokeMethod这个native方法首先通过 mirror::ArtMethod::FromReflectedMethod 获取了Java对象的在native层的 ArtMethod指针,我们跟进去看看是怎么实现的:

1 ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa,  jobject jlr_method) {2
3   mirror::ArtField* f =
4       soa.DecodeField(WellKnownClasses::java_lang_reflect_AbstractMethod_artMethod);
5   mirror::ArtMethod* method = f->GetObject(soa.Decode<mirror::Object*>(jlr_method))->AsArtMethod();
6   DCHECK(method != nullptr);
7   return method;
8 }

AndFix的实现里面,也正是使用这个 FromReflectedMethod 方法拿到Java层Method对应native层的ArtMethod指针,然后执行替换的。我们在这里看到了一点端倪,获取到了Java层那个Method对象的一个叫做 artMethod的字段,然后强转成了ArtMethod指针(这里的说法不是很准确,但是要搞明白这里面的细节一两篇文章讲不清楚 ~_~,我们暂且这么认为吧。)

上面我们也看到了,我们在native层替换的那个 ArtMethod 不是在 Java 层也有对应的东西么?我们直接替换掉 Java 层的这个artMethod 字段不就OK了?但是我们要注意的是,在Java里面除了基本类型,其他东西都是引用。要实现类似C++里面那种替换引用所指向内容的机智,需要一些黑科技。

Unsafe 和 Memory

要在Java层操作内容,也不是没有办法做到;JDK给我们留了一个后门:sun.misc.Unsafe 类;在OpenJDK里面这个类灰常强大,从内存操作到CAS到锁机制,无所不能(可惜的是据说JDK8要去掉?)但是在Android 平台还有一点点不一样,在 Android N之前,Android的JDK实现是 Apache Harmony,这个实现里面的Unsafe就有点鸡肋了,没法写内存;好在Android 又开了一个后门:Memory 类。

有了这两个类,我们就能在Java层进行简单的内存操作了!!由于这两个类是隐藏类,我写了一个wrapper,如下:

 1 private static class Memory {
 2
 3     // libcode.io.Memory#peekByte
 4     static byte peekByte(long address) {
 5         return (Byte) Reflection.call(null, "libcore.io.Memory", "peekByte", null, new Class[]{long.class}, new Object[]{address});
 6     }
 7
 8     static void pokeByte(long address, byte value) {
 9         Reflection.call(null, "libcore.io.Memory", "pokeByte", null, new Class[]{long.class, byte.class}, new Object[]{address, value});
10     }
11
12     public static void memcpy(long dst, long src, long length) {
13         for (long i = 0; i < length; i++) {
14             pokeByte(dst, peekByte(src));
15             dst++;
16             src++;
17         }
18     }
19 }
20
21 static class Unsafe {
22
23     static final String UNSAFE_CLASS = "sun.misc.Unsafe";
24     static Object THE_UNSAFE;
25
26     private static boolean is64Bit;
27
28     static {
29         THE_UNSAFE = Reflection.get(null, UNSAFE_CLASS, "THE_ONE", null);
30         Object runtime = Reflection.call(null, "dalvik.system.VMRuntime", "getRuntime", null, null, null);
31         is64Bit = (Boolean) Reflection.call(null, "dalvik.system.VMRuntime", "is64Bit", runtime, null, null);
32     }
33
34     public static long getObjectAddress(Object o) {
35         Object[] objects = {o};
36         Integer baseOffset = (Integer) Reflection.call(null, UNSAFE_CLASS,
37                 "arrayBaseOffset", THE_UNSAFE, new Class[]{Class.class}, new Object[]{Object[].class});
38         return ((Number) Reflection.call(null, UNSAFE_CLASS, is64Bit ? "getLong" : "getInt", THE_UNSAFE,
39                 new Class[]{Object.class, long.class}, new Object[]{objects, baseOffset.longValue()})).longValue();
40     }
41 }

具体实现

接下来思路就很简单了呀,用伪代码表示就是:  

1 memcopy(originArtMethod, replaceArtMethod);

但是事情没有一个 sizeof 那么简单。你看AndFix的实现是在每个Android版本把ArtMethod这个结构体复制一份的;要想用sizeof还得把这个类所有的引用复制过来,及其麻烦。更何况在Java里面 sizeof都没有。不过也不是没有办法,既然我们已经能在Java层拿到对象的地址,只需要创建一个数组,丢两个ArtMethod,把两个数组元素的起始地址相减不就得到一个 artMethod的大小了吗?(此方法来自Android热修复升级探索——追寻极致的代码热替换)但是还有一个问题,我们要整个把 originMethod 的 artMethod 所在的内存直接替换为 replaceMethod 的artMethod 所在的内存(上面我们已经知道,Java层Method类的artMethod实际上就是native层的指针表示,在Android N上更明显,这玩意儿直接就是一个long),现在我们已经知道这两个地址是什么,那么我们把 replaceArtMethod 代表的内存复制到 originArtMethod 的区域,应该还需要知道一个 artMethod 有多大。

不过,既然我们实现了方法替换;还有最后一个问题,如果我们需要在替换后的方法里面调用原函数呢?这个也很简单,我们只需要把原函数copy一份保存起来,需要调用原函数的时候调用那个copy的函数不就行了?不过在具体实现的时候,会遇到一个问题,就是 Java的非static 非private的方法默认是虚方法,在调用这个方法的时候会有一个类似查找虚函数表的过程,这个在上面的代码 InvokeMethod 里面可以看到:  

 1 mirror::Object* receiver = nullptr;
 2 if (!m->IsStatic()) {
 3   // Check that the receiver is non-null and an instance of the field‘s declaring class.
 4   receiver = soa.Decode<mirror::Object*>(javaReceiver);
 5   if (!VerifyObjectIsClass(receiver, declaring_class)) {
 6     return NULL;
 7   }
 8
 9   // Find the actual implementation of the virtual method.
10   m = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m);
11 }

详细代码见:github/epic

在调用的时候,如果不是static的方法,会去查找这个方法的真正实现;我们直接把原方法做了备份之后,去调用备份的那个方法,如果此方法是public的,则会查找到原来的那个函数,于是就无限循环了;我们只需要阻止这个过程,查看 FindVirtualMethodForVirtualOrInterface 这个方法的实现就知道,只要方法是 invoke-direct 进行调用的,就会直接返回原方法,这些方法包括:构造函数,private的方法( 见 https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html) 因此,我们手动把这个备份的方法属性修改为private即可解决这个问题。

原文地址:https://www.cnblogs.com/linghu-java/p/9685861.html

时间: 2024-10-10 16:36:04

android java层实现hook替换method的相关文章

Android逆向之旅---Hook神器家族的Frida工具使用详解

一.前言 在逆向过程中有一个Hook神器是必不可少的工具,之前已经介绍了Xposed和Substrate了,不了解的同学可以看这两篇文章:Android中Hook神器Xposed工具介绍 和 Android中Hook神器SubstrateCydia工具介绍 这两篇文章非常重要一个是Hook Java层的时候最常用的Xposed和Hook Native层的SubstrateCydia,可以看我之前的文章比如写微信插件等都采用了Xposed工具,因为个人觉得Xposed用起来比较爽,写代码比较方便.

Cocos2d-x3.3RC0通过JNI调用Android的Java层URI代码发送短信

1.Jni不在赘述.翻看前面博客 2.直接上代码 1)Java层,直接加在AppActivity.java中 public class AppActivity extends Cocos2dxActivity{ public static Activity acty; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); acty = this; } static { Sy

Android Multimedia框架总结(四)MediaPlayer中从Java层到C++层类关系及prepare及之后其他过程

转载请把头部出处链接和尾部二维码一起转载,本文出自:http://blog.csdn.net/hejjunlin/article/details/52420803 前言:在上篇中,分析了MediaPlayer的从创建到setDataSource过程,尽管看了代码,但是没有从MediaPlayer生态上认识各类库之间依赖调用关系,在本篇中将作一个补充整体上的认识.看下今天的Agenda: MediaPlayer各个so库之间关系结构图 MediaPlayer各个具体类之间依赖关系图 prepare

Android开发实践:Java层与Jni层的数组传递

Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni层也需要把从Socket接收到的数据流返回给Java层.我简单地总结了一下,从Java层到Jni层,从Jni层到JAVA层,各有3种传递方式,下面用代码示例简单地介绍一下. 示例代码的主要文件有两个,一个是Native.java,是Java层的类:另一个是Native.c,是JNI层的文件,关键的地

android java.lang.IllegalArgumentException: Comparison method violates its general contract! 问题

android  java.lang.IllegalArgumentException: Comparison method violates its general contract! 问题 java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:743) at java.util.TimSort.

转:Android开发实践:Java层与Jni层的数组传递

Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni层也需要把从Socket接收到的数据流返回给Java层.我简单地总结了一下,从Java层到Jni层,从Jni层到JAVA层,各有3种传递方式,下面用代码示例简单地介绍一下. 示例代码的主要文件有两个,一个是Native.java,是Java层的类:另一个是Native.c,是JNI层的文件,关键的地

Android消息机制1-Handler(Java层)(转)

转自:http://gityuan.com/2015/12/26/handler-message-framework/ 相关源码 framework/base/core/java/andorid/os/Handler.java framework/base/core/java/andorid/os/Looper.java framework/base/core/java/andorid/os/Message.java framework/base/core/java/andorid/os/Mes

【Android】java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread &#39;JavaBridge&#39;.

一.问题 Java调用JS事件出现 java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {3474c308} called on Looper (JavaBridg

Android native进程间通信实例-binder篇之——HAL层访问JAVA层的服务

有一天在群里聊天的时候,有人提出一个问题,怎样才能做到HAL层访问JAVA层的接口?刚好我不会,所以做了一点研究. 之前的文章末尾部分说过了service call 可以用来调试系统的binder服务. 传送门: Android native进程间通信实例-binder篇之——简单的单工通信 这次可以用到这个命令了! 1. 随机选取一个java层的服务. adb shell 中输入命令 service list,选取一个服务来做研究,这次看中的是 textservices, 注意第一个服务 by