一、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) { } }