Android Hook技术实践

一、hook简介

hook俗称钩子,主要作用是替换系统内存中的对象,在上层调用此对象的方法的时候可以修改传入参数或者返回值,达到欺骗上层的目的,就像小红帽故事里的大灰狼,通过扮演小红帽的外婆的角色来达到欺骗小红帽的目的。其实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) {
    }
}


猜你喜欢

转载自blog.csdn.net/u012874222/article/details/72860380