安卓漏洞学习(七):Android Hook 技术

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按键消息拦截

  1. 监听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();
        }
    }
  1. 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;  
    }    
  1. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值