Android Hook技术实践

Android Hook技术实践一、hook简介hook俗称钩子,主要作用是替换系统内存中的对象,在上层调用此对象的方法的时候可以修改传入参数或者返回值,达到欺骗上层的目的,就像小红帽故事里的大灰狼,通过扮演小红帽的外婆的角色来达到欺骗小红帽的目的。其实hook就是一种中间人劫持的思想,如图所示:在安卓中实现hook主要通过两种方式:1.反射技术和代理实现,当然代理不管是动态还是静态的都是可以实现的,但是只能ho

大家好,又见面了,我是你们的朋友全栈君。

一、hook简介

hook俗称钩子,主要作用是替换系统内存中的对象,在上层调用此对象的方法的时候可以修改传入参数或者返回值,达到欺骗上层的目的,就像小红帽故事里的大灰狼,通过扮演小红帽的外婆的角色来达到欺骗小红帽的目的。其实hook就是一种中间人劫持的思想,如图所示:

Android Hook技术实践

在安卓中实现hook主要通过两种方式:

1.反射技术和代理实现,当然代理不管是动态还是静态的都是可以实现的,但是只能hook自己应用内存中的对象;

2.在root的情况下,Xposed通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持,可以达到hook整个系统中所有进程内存里面的对象的目的;

方式2虽然很强大、很逆天,但是也有限制就是必须的root,所以本文主要讲方式1的实现。

二、hookAms实践:

插件技术中很重要的一项就是宿主启动插件APK中的Activity,因为插件都是后面业务迭代加进来的,所以Activity不可能提前注册在宿主Activity的清单文件中的,所以正常的情况下是不可能启动插件里的Activity的,因为启动Activity的过程是需要在清单文件中寻找是否注册,若没有,则直接crash。
所以想实现跳过系统检查,做法就是先在宿主里注册一个ProxyActivity,在启动插件的Activity的时候,把我们这个真实意图Intent替换为可以通过检查的启动ProxyActivity的代理意图Intent,然后让真实意图作为Extra添加进代理意图里,在通过检查后再取出来替换回来,而这样的功能,是通过hook实现的。
我们知道Activity的启动过程是通过AIDL Binder的方式跟AMS进行一系列的交互,最终通过反射newInstance创建出来的,由于AMS处于系统进程中,所以我们是没法从它里面寻找hook点的。所以这里所说的hookAms,其实是hook位于我们自己的应用这边的与AMS交互的AIDL的接口IActivityManeger,那怎么才能找到这个对象并且进行替换呢,需要看FrameWork源码:

2376    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
2377        protected IActivityManager More ...create() {
2378            IBinder b = ServiceManager.getService("activity");
2379            if (false) {
2380                Log.v("ActivityManager", "default service binder = " + b);
2381            }
2382            IActivityManager am = asInterface(b);
2383            if (false) {
2384                Log.v("ActivityManager", "default service = " + am);
2385            }
2386            return am;
2387        }
2388    };

26public abstract class More ...Singleton<T> {
27    private T mInstance;
28
29    protected abstract T More ...create();
30
31    public final T More ...get() {
32        synchronized (this) {
33            if (mInstance == null) {
34                mInstance = create();
35            }
36            return mInstance;
37        }
38    }
39}

gDefault是在
android.app.ActivityManagerNative里面的一个静态内部类实现了Singleton的抽象方法create()并且返回mInstance即IActivityManeger
基于此,我们便可以通过反射和动态代理来下钩子了:

public static void hookAms(Context context){    try {        //获取ActivityManagerNative里面的gDefault静态对象即是内存的唯一对象        Class<?> forName = Class.forName("android.app.ActivityManagerNative");        Field defauleField = forName.getDeclaredField("gDefault");        defauleField.setAccessible(true);        Object defauleValue = defauleField.get(null);//静态对象传null即可        //获取gDefault里面的iActivityManagerObject对象        Class<?> forName2 = Class.forName("android.util.Singleton");        Field instanceField = forName2.getDeclaredField("mInstance");        instanceField.setAccessible(true);        Object iActivityManagerObject = instanceField.get(defauleValue);        //传入真实对象生成代理对象proxyy        Object proxyy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),                new Class<?>[]{Class.forName("android.app.IActivityManager")},                new AmsInvocationHandler(context,iActivityManagerObject));        //真实对象替换为代理对象proxyy        instanceField.set(defauleValue,proxyy);    } catch (Exception e) {        e.printStackTrace();    }}
public class AmsInvocationHandler implements InvocationHandler {    private Context context;    private Object iActivityManagerObject;    public AmsInvocationHandler(Context context,Object iActivityManagerObject) {        this.context = context;        this.iActivityManagerObject = iActivityManagerObject;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if("startActivity".contains(method.getName())){            Intent intent = null;            int index = 0;            for(int i = 0;i<args.length;i++){                if(args[i] instanceof Intent){                    intent = (Intent)args[i];                    index = i;                    break;                }            }            Intent proxyIntent = new Intent(context,ProxyActivity.class);            proxyIntent.putExtra("oldIntent",intent);            args[index] = proxyIntent;        }        return method.invoke(iActivityManagerObject,args);    }}

如此便完成了意图的替换,接下来就是在通过检查后、启动Activity前把意图替换回来:

通过源码我们知道ActivityThread里维护着一个系统Handler:mH,mH主要负责发送和处理各种系统服务的msg,包括四大组件的创建启动
由于mH的代码量比较大,我这里就只贴出handlerMessage的launchActivity部分
case LAUNCH_ACTIVITY: {
1273                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
1274                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
1275
1276                    r.packageInfo = getPackageInfoNoCheck(
1277                            r.activityInfo.applicationInfo, r.compatInfo);
1278                    handleLaunchActivity(r, null);
1279                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1280                } break;

可以看到msg携带的obj是一个叫做ActivityClientRecord的对象,而这个对象里面其实携带了Intent,基于此,我们便可以在mH的handlerMessage之前将msg截取修改里面的Intent参数就可以了,由于Looper从消息队列中取出msg,然后在获取他的target即是handler的时候是调用handler的dispatchMessage的,所以我们可以先看看此方法

public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}

可以看到方法里会先检查有没有mCallback,如果有则直接调用它的handlerMesage,所以这里我们可以直接hook它的mCallback

public static void hookSystemHandler(){
    try {
        // 获取全局的ActivityThread对象
        Class<?> forName = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        Object currentActivityThread = currentActivityThreadField.get(null);

        // 获取ActivityThread里面的mH对象
        Field handlerField = forName.getDeclaredField("mH");
        handlerField.setAccessible(true);
        Handler handlerObject = (Handler)handlerField.get(currentActivityThread);

        // 获取mH里面的mCallback对象,并且替换为我们自己的ActivityThreadHandlerCallBack
        Field callBackFieid = Handler.class.getDeclaredField("mCallback");
        callBackFieid.setAccessible(true);
        callBackFieid.set(handlerObject,new ActivityThreadHandlerCallBack(handlerObject));
    }catch (Exception e){
    }
}
public class ActivityThreadHandlerCallBack implements Handler.Callback{
    Handler handler;
    public ActivityThreadHandlerCallBack(Handler handler) {
        this.handler = handler;
    }
    @Override
    public boolean handleMessage(Message msg) {
        if(msg.what == 100){
            launchActiity(msg);
        }
        handler.handleMessage(msg);
        return true;
    }
    private void launchActiity(Message msg){
        Object obj = msg.obj;
        try {
            Field intentField = obj.getClass().getDeclaredField("intent");
            intentField .setAccessible(true);
            Intent proxyIntent = (Intent)intentField.get(obj);
            Intent realIntent = proxyIntent.getParcelableExtra("oldIntent");
            if(realIntent != null){
                proxyIntent.setComponent(realIntent.getComponent());
            }
        }catch (Exception e){
        }
    }
}

至此,我们通过hook技术完成了启动不在清单注册也能启动Activity这个功能。

三、hookPms实践

类似于AMS,ActivityThread也是通过AIDL接口IPackageManager来与系统的PackageManagerService进行交互的,所以我们只要hook我们应用内的IPackageManager就可以达到修改PMS的一些服务目的,这里我们通过hookPms来修改应用内签名获取的方法,这样做的意义在于当我们进行反编译的时候,并且遇到反编译的APP有签名校验的时候,可以用事先获取到的真实签名数据来替换APP内获取的签名,从而达到破解签名校验的目的。当然,实际开发的时候也是可以用到的,我们在做安卓终端开发的时候,用到了百度地图SDK,所以首先得去他们网站上注册一个应用,填写包名和签名sha1值,但是我们的应用是需要在多个平台上运行的,而不同的平台使用的系统签名不一样,所以不能做到创建一个应用通用全部平台,所以这里我就是通过hookPms的方式欺骗了百度地图,让底层返回那个百度后台创建过的应用的签名,这样百度SDK会判定你的包名、APPKEY和签名信息都是同一个应用且正确的,从而可以正常的使用。通过hook技术,我们避免了在百度后台创建多个应用,而且打包的时候根据每个平台来修改它meta-data的key值也是很麻烦的。

public static void hookPms(Context context, String signed, int hashCode){
    try{
        // 获取全局的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod =
                activityThreadClass.getDeclaredMethod("currentActivityThread");
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
        // 获取ActivityThread里面原始的sPackageManager
        Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
        sPackageManagerField.setAccessible(true);
        Object sPackageManager = sPackageManagerField.get(currentActivityThread);
        // 准备好代理对象, 用来替换原始的对象
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {
  
  Class.forName("android.content.pm.IPackageManager") },
                new PmsHookBinderInvocationHandler(sPackageManager, signed, hashCode));
        //  替换掉ActivityThread里面的 sPackageManager 字段
        sPackageManagerField.set(currentActivityThread, proxy);
        //  替换 ApplicationPackageManager里面的 mPM对象
        PackageManager pm = context.getPackageManager();
        Field mPmField = pm.getClass().getDeclaredField("mPM");
        mPmField.setAccessible(true);
        mPmField.set(pm, proxy);
    }catch (Exception e){
    }
}
public class PmsHookBinderInvocationHandler implements InvocationHandler{

    private Object base;
    //应用正确的签名信息
    private String SIGN;
    private int hashCode = 0;
    public PmsHookBinderInvocationHandler(Object base, String sign, int hashCode) {
        try {
            this.base = base;
            this.SIGN = sign;
            this.hashCode = hashCode;
        } catch (Exception e) {
        }
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("getPackageInfo".equals(method.getName())){
            Integer flag = (Integer)args[1];
            if(flag == PackageManager.GET_SIGNATURES){
               Signature sign = new Signature(SIGN);
               if(hashCode != 0){
                  try{
                      Class<?> clazz = sign.getClass();
                      Field mHaveHashCodeF = clazz.getDeclaredField("mHaveHashCode");
                      mHaveHashCodeF.setAccessible(true);
                      mHaveHashCodeF.set(sign, true);
                      Field mHashCodeF = clazz.getDeclaredField("mHashCode");
                      mHashCodeF.setAccessible(true);
                      mHashCodeF.set(sign, hashCode);
                   }catch(Exception e){
                   }
               }
               PackageInfo info = (PackageInfo) method.invoke(base, args);
               info.signatures[0] = sign;
               return info;
            }
        }
        return method.invoke(base, args);
    }
}

最后再附上获取签名信息的方法:

private void getSignature() {
    try {
        PackageInfo packageInfo = getPackageManager().getPackageInfo(
                getPackageName(), PackageManager.GET_SIGNATURES);
        if (packageInfo.signatures != null) {
           Log.d("", "sig:"+packageInfo.signatures[0].toCharsString()+
                    "hashcode:"+packageInfo.signatures[0].hashCode());
        }
    } catch (Exception e2) {
    }
}


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/141290.html原文链接:https://javaforall.net

(0)
上一篇 2022年5月26日 上午10:20
下一篇 2022年5月26日 上午10:20


相关推荐

  • nginx正向代理概念

    nginx正向代理概念1 正向代理概念正向代理 也就是我们常说的代理 其工作原理为 我访问不了某个网站 但是我能访问一个代理服务器 这个代理服务器他能访问那个我不能访问的网站 于是我连上代理服务器 告诉他我需要那个无法访问网站的内容 代理服务器去取回来之后返回给我 从网站的角度来说只在代理服务器来取内容的时候有一次记录 有时候并不知道是用户的请求 也隐藏了用户的资料 这取决于代理是否告诉网站 总之 正向代理是一个

    2026年3月26日
    3
  • IntelliJ IDEA创建maven web项目(IDEA新手适用)

    IntelliJ IDEA创建maven web项目(IDEA新手适用)PS:从eclipse刚转到IDEA,对于这个陌生的工具我表示无言,但听说很好用,也就试试,结果我几乎花了一晚上的时间才搭起来mavenweb项目,觉得在此给各位一个搭建mavenweb项目的教程,指出我踩过的各种坑!步骤一:首先先创建一个project,在这里就是创建一个maven的工作空间步骤二:按照下面的步骤操作就可以了,最后next首先,选择左边的maven然后在右…

    2022年6月26日
    58
  • 传统Legacy引导+MBR硬盘格式无损转换UEFI+GPT的操作方法

    传统Legacy引导+MBR硬盘格式无损转换UEFI+GPT的操作方法

    2026年3月16日
    2
  • docker命令详解「建议收藏」

    docker命令详解「建议收藏」镜像下载搜索镜像dockersearch+镜像名字#dockersearchcentos从DockerHub中搜索符合条件的镜像下载镜像#dockerpull+镜像名字#dockerpullcentos查看镜像#dockerimages开启网络转发功能(默认为开启状态)若无开启,可以使用以下命令开启#vim/etc/sysctl…

    2022年5月13日
    42
  • PHP CI框架调试开启报错信息方法「建议收藏」

    PHP CI框架调试开启报错信息方法

    2022年2月8日
    81
  • eNSP常用命令 华为模拟器eNSP常用命令

    eNSP常用命令 华为模拟器eNSP常用命令路由器常用命令 进入任务视图给路由器取名 进入指定接口 给当前路由器接口配置 IP 地址和子网掩码 退出接口或系统视图 启用 DHCP 指定该接口拥有 DHCP 功能 指定 DNS 服务器的 IP 地址 显示全部 ip 的路由表 显示指定 ip 路由表 添加静态路由 交换机常用命令 交换机改变语言模式 创建 vlan 查看所有 vlan 将接口拆分为多个子接口 指定接口与哪个 vlan 关联 启用 arp 广播 将接口修改为 access 接口 将接口修改为 trunk 接口 将接口划分到指定 vlan 里 查看开启 stp 后的交换机接口的接口情况 查看交换

    2026年3月16日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号