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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • JVM内存结构面试问题及解答[通俗易懂]

    JVM内存结构面试问题及解答[通俗易懂]以下是jvm内存的常见面试问题:1、JVM管理的内存结构是怎样的?2、不同的虚拟机在实现运行时内存的时候有什么区别?3、运行时数据区中哪些区域是线程共享的?哪些是独享的?4、除了JVM运行时内存以外,还有什么区域可以用吗?5、堆和栈的区别是什么?6、Java中的数组是存储在堆上还是栈上的?7、Java中的对象创建有多少种方式?8、Java中对象创建的过程是怎么样的?9、Java…

    2022年5月12日
    30
  • 【C 语言】文件操作 ( fopen 文件打开方式详解 )「建议收藏」

    【C 语言】文件操作 ( fopen 文件打开方式详解 )「建议收藏」r:只读方式打开文件,文件必须存在;文件不存在打开失败;+:读写方式打开文件;w:打开只写文件,文件不存在创建文件,文件存在覆盖文件;a:打开只写文件,文件不存在创建文件,文件存在追加文件;

    2022年7月13日
    16
  • MS WORD 表格自己主动调整列宽,自己主动变美丽,依据内容自己主动调整

    MS WORD 表格自己主动调整列宽,自己主动变美丽,依据内容自己主动调整

    2022年2月4日
    42
  • 超简单的windows发包工具—小兵以太网测试仪

    超简单的windows发包工具—小兵以太网测试仪小兵以太网测试仪是一款windows平台下的发包工具。该软件小巧、易用、开源、免费。该软件的功能有:各种常见报文(包括arpipicmpudptcp等)的编辑与发送发包速率控制抓包对抓到的包进行修改编辑及发送将报文导出为tcpdump/ethereal/wireshark存档(pcap格式)从tcpdump/ethereal/wireshark存档导入报文发送巨帧(j

    2022年9月3日
    3
  • CentOS 7 常用软件安装汇总

    CentOS 7 常用软件安装汇总基本指令:cdabc->进入子目录cd/->进入根目录ll->显示当前路径文件(详细)ls->同上(简略)cp->拷贝mkdir->创建目录rm->删除文件或文件夹mv->移动文件或改名clear->清屏pwd->显示当前路径more->显示文本文档una……

    2022年7月15日
    16
  • 设置pycharm背景颜色_python设置背景颜色

    设置pycharm背景颜色_python设置背景颜色PyCharm颜色设置选择主题和背景图片选择字体、修改字体大小新建颜色主题修改背景颜色修改注释颜色File  –>  Setting  (Ctrl+Shift+S)1、选择不同的主题、选择背景图片            Appearnce&Behavior  –>  Appearance2、字体                        Editor  –>  Font3、建…

    2022年8月27日
    4

发表回复

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

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