背景
项目中遇到需要监控 System.loadLibrary 方法调用,实现如果链接失败则弹出对话框的「问题」。「解决方案」就是 hook java.lang.Runtime#nativeLoad 方法对应的 JNI 函数入口 art#ArtMethod#ptr_sized_fields#data_ 字段,从而先跳转到我们 hook 的函数再执行系统的 JNI 函数。这个需要在运行时拿到一个 Java 方法的相关信息,比如 Method 类、ArtMethod 类、JNI 函数信息等。反射是能读写一个类、对象的内部数据的一种手段,在此做记录。
问题
- 我们能不能拿到系统某些 private 字段的信息 ?
- 我们能不能执行系统某些 private 方法?
- 我们能不能拦截某些系统的行为?
接下来记录一下使用反射拦截主线程消息队列例子
反射的使用方法
- 拿到 Class 对象
- 通过 java.lang.Class#forName(java.lang.String) 拿类名对应的类
- 通过 java.lang.Object#getClass 拿对象所属的类
- 通过 java.lang.Class#getSuperclass 拿父类
- 拿到 Field / Method 对象
- 拿当前类(不包含父类)字段 java.lang.Class#getDeclaredField
- 拿当前类方法 java.lang.Class#getDeclaredMethod
- 读写字段 / 执行方法
- 读字段 java.lang.reflect.Field#get
- 写字段 java.lang.reflect.Field#set
- 执行方法 java.lang.reflect.Method#invoke
拦截主线程消息处理
Android Message 处理流程- Android 主线程是设计成事件驱动,Java 层的核心是生产者/消费者模型
- 主线程属于消费者,负责从 android.os.MessageQueue 中取消息执行
- 当前进程内的所有线程都可以作为生产者向 android.os.MessageQueue 中投递消息,这样就让某些代码在主线程执行
- 主线程执行到 android.os.Looper#loop 中时就进入了轮询过程,取消息,执行消息
- 由于 android.os.Looper#sThreadLocal 变量是 java.lang.ThreadLocal 类型,所以每个 java.lang.Thread 对象至多有一个 android.os.Looper 对象
- 因此我们需要拿到主线程的 android.os.MessageQueue 和 android.os.Looper 对象
- 利用 android.os.Looper#getMainLooper 接口可以很简单拿到 Looper 对象
- 拿到 Looper 对象后可以利用反射遍历对象的所有字段,拿到 android.os.Looper#mQueue 字段
- 接下来我们模拟 android.os.Looper#loop 内部的实现,反射执行 android.os.MessageQueue#next 方法取得一条消息
- 找到消息所属的 Handler 对象 android.os.Message#getTarget
- 给 Handler 派发消息 android.os.Handler#dispatchMessage
这样我们在应用层就能够看到主线程的每条消息,从而改变系统的某些行为,比如某些消息不执行
// 拿到 Looper 对象
Looper mainLooper = Looper.getMainLooper();
// 拿到 MessageQueue 对象
MessageQueue queue = (MessageQueue) Reflection.field(null, null, mainLooper, "mQueue");
while (true){
// 取消息
Message msg = (Message) Reflection.call(null, null, queue, "next", new Class[]{});
if (msg != null){
Log.d(TAG, "run: " + msg);
// 这里拦截消息
...
Handler handler = msg.getTarget();
// 派发消息
handler.dispatchMessage(msg);
}
}
练习题
- 利用反射遍历类层次结构
// 拿到 Class 对象
Class c = xx;
while (c != null) {
// 读取 c 信息
...
// 指向父类
c = c.getSuperclass();
}
- 利用反射遍历对象的所有字段和值
Class c = xx;
while (c != null) {
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
try {
f.setAccessible(true);
Log.d(TAG, f + " = " + f.get(obj));
} catch (Exception e) {
e.printStackTrace();
}
}
c = c.getSuperclass();
}
- 利用反射修改 private 字段和执行 private 方法
Class c = xx;
// 遍历类结构
while (c != null) {
// 遍历当前类所有字段
for (Field f : c.getDeclaredFields()) {
// 查找指定字段名
if (f.getName().equals(fieldName)) {
f.setAccessible(true);
// 修改字段的值
f.set(o, value);
return;
}
}
c = c.getSuperclass();
}
Class c = xx;
while (c != null) {
Method m = null;
try{
// 在当前类查找
m = c.getDeclaredMethod(name, parameterTypes);
} catch (Throwable e){
Log.e(TAG, "call: ", e);
}
if (m != null){
m.setAccessible(true);
// 执行方法
value = m.invoke(o, args);
break;
}
c = c.getSuperclass();
}
总结
- 在读写系统私有信息方面,反射是一个不错的技术手段。
网友评论