Android Hook 技术
在Android开发中,Hook技术是一种强大的手段,它允许开发者拦截和修改系统或应用的行为。通过Hook,我们可以在事件传递的过程中插入自定义的逻辑,从而实现对应用行为的监控和修改。
Android 系统有自己的事件分发机制,所有的代码调用和回调都遵循一定的顺序执行。Hook 技术的作用就在于,可以在事件传送到终点前截获并监控该事件的传输,并进行自定义的处理。
在 Java 中,常见的 Hook 技术包括:
- 使用反射修改现有类的方法实现
- 利用动态代理创建代理对象
- 通过 Java Instrumentation 接口修改类的字节码
- 利用 Java 的 SecurityManager 进行权限控制
通过这些技术,我们可以在不修改程序源码的情况下,动态地拦截和修改程序的行为,从而实现各种功能扩展和系统监控的需求。
利用动态代理创建代理对象
package org.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class DynamicProxyExample {
interface Greeting {
void sayMessage(String message);
void writeMessage(String message);
}
static class GreetingImpl implements Greeting {
@Override
public void sayMessage(String message) {
System.out.println("Hello, " + message);
}
@Override
public void writeMessage(String message) {
System.out.println("Write this message to file, " + message);
}
}
static class LogHandler implements InvocationHandler {
private final Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Method: " + method.getName() + " with arguments " + Arrays.toString(args));
// 在调用原始方法前执行一些逻辑
System.out.println("Before reading file");
Object result = method.invoke(target, args);
// 在调用原始方法后执行一些逻辑
System.out.println("After reading file");
return result;
}
}
public static void main(String[] args) {
GreetingImpl greetingImpl = new GreetingImpl();
InvocationHandler handler = new LogHandler(greetingImpl);
Greeting proxy = (Greeting) Proxy.newProxyInstance(
Greeting.class.getClassLoader(), // 类加载器
new Class<?>[]{Greeting.class}, // 代理需要实现的接口
handler // 调用处理器
);
proxy.sayMessage("World");
proxy.writeMessage("World");
}
}
虽然动态代理提供了极大的灵活性,但它也有一些局限性。例如,动态代理只能为接口创建代理,不能为类创建代理。此外,动态代理的性能开销通常比直接调用方法要高,因此在性能敏感的应用中需要谨慎使用。
利用HOOK技术实现Button按键消息拦截
- 监听OnClickListener的回调方法,然后拦截onClick,在其中加入我们的逻辑
Button btn_click = (Button)findViewById(R.id.button);
btn_click.setOnClickListener(new MyOnClickListener());
//在不修改以上代码的情况下,通过Hook把((Button)view).getText() 内容修改
try {
hook(btn_click);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this,"Hook失败"+e.toString(),Toast.LENGTH_SHORT).show();
}
class MyOnClickListener implements View.OnClickListener {
public void onClick(View v){
Toast.makeText(MainActivity.this, "" + ((Button) v).getText(), Toast.LENGTH_SHORT).show();
}
}
- setOnClickListener源码解析,找到合适的Method和Field
View.java
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l; //传输的OnClickListener 复制给新创建的对象
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo(); //新创建一个ListenerInfo对象
return mListenerInfo;
}
- HOOK实现代码
private void hook(View view) throws Exception {
//1.获取View对象
Class mViewClass=Class.forName("android.view.View");
//2.获取getListenerInfo方法
Method getListenerInfoMethod = mViewClass.getDeclaredMethod("getListenerInfo");
//2.1因为getListenerInfo不是公开的,所以必须授权虚拟机去访问
getListenerInfoMethod.setAccessible(true);
//2.2传入hook的按钮对象获取
Object mListenerInfo = getListenerInfoMethod.invoke(view);//调用getListenerInfo方法,相当于btn_click对象里调用mListenerInfo = new ListenerInfo();
//获取ListenerInfo class
Class<?> mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");//通过反射来获取一个内部类的 Class 对象时,可以使用 "外部类名$内部类名" 的格式来表示。
//获取mOnClickListener属性
Field mOnClickListenerField = mListenerInfoClass.getField("mOnClickListener"); //内部类ListenerInfo有一个成员变量mOnClickListener
//由于mOnClickListener是public 不用授权访问
final Object mOnClickListenerObj = mOnClickListenerField.get(mListenerInfo);//获取mOnClickListener的实际引用,实际是获取mListenerInfo对象的mOnClickListener值,实际获取的是 btn_click.setOnClickListener(new MyOnClickListener())中设置的值,为new MyOnClickListener()。
//创建代理,在View.OnClickListener类中的方法执行前,会先执行我们设定的代码
//1.监听onClick,当用户点击按钮的时候,我们先拦截下来
Object mOnClickListenerProxy=Proxy.newProxyInstance(MainActivity.this.getClassLoader(),
new Class[]{View.OnClickListener.class},//2.要监听的接口(要监听什么接口,就返回什么接口
new InvocationHandler() {//3.监听接口方法里面的回调
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//加入自己的逻辑
Log.d("hook", "拦截到了 OnClickListener的方法了: ");
Button button=new Button(MainActivity.this);
button.setText("这是我第一次拦截啊");
//让系统程序片段=====正常继续执行下去
return method.invoke(mOnClickListenerObj,button);
}
});
//把系统的mOnClickListener 换成我们自己写的动态代理
mOnClickListenerField.set(mListenerInfo,mOnClickListenerProxy);
}
使用Java的Instrumentation API实现类的动态加载
Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操作,例如性能监控、代码覆盖率分析等。
Java提供了一个名为java.lang.instrument的包,其中包含API用于在运行时更改和监控Java类。
Java Instrumentation的主要功能包括:
- 在类文件加载到JVM之前,改变类文件的字节码。
- 在运行时计算应用程序中对象的大小。
- 在运行时更改类的定义。
- 为JVM提供一种获取加载到Java应用程序的类文件的方式。
Java Agent是Java Instrumentation的一种应用,是一种特殊的Java程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。
Java Agent主要有两种类型:静态agent和动态agent。静态agent在JVM启动时通过命令行参数指定,并在主程序启动之前运行。动态agent则可以在JVM运行时随时加载。
Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。一些常见的Java诊断和监控工具(如JProfiler、VisualVM等)就是通过Java Agent实现的。
java.lang.instrument包中的关键类和接口
Instrumentation:此接口提供了用于实施字节码转换和获取对象的相关信息的方法。
ClassFileTransformer:这是一个接口,其中定义了一个transform方法,允许我们在类加载到JVM之前对其进行转换。
UnmodifiableClassException:这是一个异常,会在尝试修改或重定义它的状态时,由Instrumentation.retransformClasses方法和Instrumentation.redefineClasses方法抛出。
- Instrumentation接口及其方法
Instrumentation接口提供了许多方法,这些方法可以用来改变和检查应用程序的行为,包括:
addTransformer(ClassFileTransformer transformer, boolean canRetransform): 添加一个类文件转换器。
removeTransformer(ClassFileTransformer transformer): 移除一个类文件转换器。
redefineClasses(ClassDefinition… definitions): 重新定义类。
retransformClasses(Class<?>… classes): 改变已经加载到JVM的类。
getObjectSize(Object objectToSize): 返回对象的大小(以字节为单位)。
ClassFileTransformer接口
Java的Instrumentation API允许你在运行时修改已加载的类的字节码。要实现类的动态加载,你需要遵循以下步骤:
- 首先,确保你已经安装了Java Development Kit (JDK)。
- 创建一个Java代理类(Agent Class),这个类将实现premain方法。premain方法是Java Agent的入口点,当JVM启动时,它会自动调用此方法。在这个方法中,你可以注册一个ClassFileTransformer,它将在类加载之前修改类的字节码。
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new MyClassFileTransformer());
}
}
- 创建一个实现ClassFileTransformer接口的类。在这个类中,你需要实现transform方法。这个方法将在类加载之前被调用,你可以在这里修改类的字节码。
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 只修改我们关心的类
if ("com/example/MyClass".equals(className)) {
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.example.MyClass");
// 在这里修改类的字节码,例如添加方法、字段等
// cc.addMethod(...);
return cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
- 创建一个名为MANIFEST.MF的清单文件,其中包含Java Agent的入口点信息。
Manifest-Version: 1.0
Premain-Class: MyAgent
-
将你的Java Agent打包成一个JAR文件。确保MANIFEST.MF文件位于JAR文件的根目录下。
jar cfm myagent.jar MANIFEST.MF MyAgent.class MyClassFileTransformer.class -
在运行Java应用程序时,使用-javaagent选项指定你的Java Agent。
java -javaagent:myagent.jar -jar yourapp.jar
现在,当你的Java应用程序运行时,Java Agent将在类加载之前修改指定的类。你可以根据需要修改MyClassFileTransformer类中的transform方法来实现你的动态加载需求。
参考:https://blog.youkuaiyun.com/shippingxing/article/details/139422746
https://blog.youkuaiyun.com/lizhong2008/article/details/139320458
https://blog.youkuaiyun.com/m0_62787113/article/details/139345058
https://blog.youkuaiyun.com/wangshuai6707/article/details/133848681