在谈hook之前,我们先谈下代理。代理对学编程的人来说,应该非常熟悉,毕竟代理模式还是很常用的。考虑到真有人不知道代理模式,我们先从静态代理开始讲解。
先写个接口:
public interface Animal {
void eat();
int feet();
}
实现该接口:
public class Cat implements Animal{
@Override
public void eat() {
System.out.print("cat eat");
}
@Override
public int feet() {
return 4;
}
}
现在写过静态代理ProxyAnimal:
public class ProxyAnimal implements Animal {
private Animal realAnim;
public ProxyAnimal(Animal realAnim){
this.realAnim = realAnim;
}
@Override
public void eat() {
realAnim.eat();
}
@Override
public int feet() {
return realAnim.feet();
}
}
这样很简单,就是实现了一个静态代理模式,如果第三方想访问cat,你给它一个ProxyAnimal就可以了,这样也避免暴露Cat对象,具体作用可以百度代理模式。
静态代理模式尽管简单,但是它有点繁琐,假如我还有一个Shopping也需要个代理,那你还需要依葫芦画瓢,写一个ShopingProxy,这样公式化代码太多,为避免这种局面,出现了动态代理。无论为多少个接口实现代理,都只需要写一个代理类即可。如:
public class ProxyHandler implements InvocationHandler {
private Object realObj;
public ProxyHandler(Object realObj){
this.realObj = realObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("excute before >>>" + method.getName());
Object obj;
try {
obj = method.invoke(realObj, args);
} catch (Exception e) {
obj = null;
}
System.out.println("excute after >>>" + method.getName());
return obj;
}
}
接着就可以在client测试如下:
public static void main(String [] args){
Animal cat = new Cat();
Animal proxy = (Animal)Proxy.newProxyInstance(Animal.class.getClassLoader(),cat.getClass().getInterfaces(),
new ProxyHandler(cat));
proxy.eat();
Shopping shopping = new ShoppingImpl();
Shopping shopProxy = (Shopping)Proxy.newProxyInstance(Shopping.class.getClassLoader(),
shopping.getClass().getInterfaces(),new ProxyHandler(shopping));
shopProxy.doShopping();
}
这样就避免去写AnimalProxy和ShoppingProxy,用一个ProxyHandler就搞定。
到这基础已经讲解完毕,接着开始进入正题。
谈到hook,其实说白了就是找一个hook点,hook点的选取十分重要,好坏决定难易程度。通常我们优先考虑静态属性或方法, & public属性或方法,实在没办法才去考虑保护型或私有型属性&方法(毕竟这种类型需要考虑版本兼容)。
原理知道后,还是实战来得舒畅,下面以startActivity启动一个activity为例,在Activity中启动另一个Activity,首先我们需要了解activity的启动流程,一步步跟踪,发现最终在Activity.startActivityForResult中以如下方式启动:
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
如果你对Activity启动熟悉的话会发现,此处的mInstrumentation就是ActivityThread通过ativity.attach传过来的,而ActivityThread一个app只有一个,而mInstrumentation就是在ActivityThread创建后马上创建的,此时,是不是感觉这个mInstrumentation符合hook点,ok,先hook一把
public static void hookCurrentThread(){
try {
Class> activityThreadCls = Class.forName("android.app.ActivityThread");
//1.获取ActivityThread对象
//hook点,有public的方法或属性,优先
Method currentActThreadMethod = activityThreadCls.getDeclaredMethod("currentActivityThread");
Object curThreadObj = currentActThreadMethod.invoke(null);
//获取mInstrumentation
Field instrumentationField = curThreadObj.getClass().getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
instrumentationField.set(curThreadObj,new InstrumentProxy((Instrumentation) instrumentationField.get(curThreadObj)));
} catch (Exception e) {
e.printStackTrace();
}
其中InstrumentProxy如下:
public class InstrumentProxy extends Instrumentation{
private Instrumentation realObj;
public InstrumentProxy(Instrumentation obj){
this.realObj = obj;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(realObj, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
// 某该死的rom修改了 需要手动适配
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}
此处,先获取ActivityThread中的mInstrumentation属性,然后再采用静态代理将其替换掉,这样就hook住系统的方法,我们也就可以在InstrumentProxy中任意插桩。
倘若你没有发现mInstrumentation符合hook点,你可以继续跟踪Instrumentation.execStartActivity方法,里面有个非常明显的hook点:
try {
1494 intent.migrateExtraStreamToClipData();
1495 intent.prepareToLeaveProcess();
1496 int result = ActivityManagerNative.getDefault()
1497 .startActivity(whoThread, who.getBasePackageName(), intent,
1498 intent.resolveTypeIfNeeded(who.getContentResolver()),
1499 token, target != null ? target.mEmbeddedID : null,
1500 requestCode, 0, null, options);
1501 checkStartActivityResult(result, intent);
1502 } catch (RemoteException e) {
1503 }
对就是ActivityManagerNative.getDefault(),我们可以进入ActivityManagerNative类,发现getDefault方法实现如下:
static public IActivityManager getDefault() {
81 return gDefault.get();
82 }
其中gDefault是个static属性,完全符合hook要求,具体hook如下:
public static void hookAMNative(){
try {
Class> actManagerNativeCls = Class.forName("android.app.ActivityManagerNative");
//获取gDefault
Field gDefaultField = actManagerNativeCls.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefaultObj = gDefaultField.get(null);
// Method getField = gDefaultObj.getClass().getDeclaredMethod("get");
// Object activityImpl = getField.invoke(null);
Class> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object activityImpl = mInstanceField.get(gDefaultObj);
// Method activityManagerMethod= actManagerNativeCls.getMethod("getDefault");
// Object actManagerImpl = activityManagerMethod.invoke(null);
Object actProxy = Proxy.newProxyInstance(activityImpl.getClass().getClassLoader(),
activityImpl.getClass().getInterfaces(),new ProxyHandler(activityImpl,null));
mInstanceField.set(gDefaultObj,actProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
其中ProxyHandler如下:
public class ProxyHandler implements InvocationHandler{
private Object realObj;
public ProxyHandler(Object obj,Object d){
this.realObj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if("startActivity".equals(methodName)){
android.util.Log.e("hooktest",">>>>proxyhandler>>>startActivityBefore");
Object retur = method.invoke(realObj,args);
android.util.Log.e("hooktest",">>>>proxyhandler>>>startActivityAfter");
return retur;
}
return method.invoke(realObj,args);
}
}
采用动态代理的方法对IActivityManager进行了接管,同样完成了startActivity的hook。
其实不选单例、静态属性或共有属性,整个private的也是可以的,还以启动startActivity为例,考虑到Activity是继承ContextWrapper,而ContextWrapper中有个属性mBase,如果我们能对mBase hook也是可以的,这样就需要对ContextImpl来个代理就可以了,代码可以如下:
public static void hookContextWrapper(ContextWrapper wrapper) {
try {
Field mBaseFiled;
Class> wrapperClass = ContextWrapper.class;
mBaseFiled = wrapperClass.getDeclaredField("mBase");
mBaseFiled.setAccessible(true);
mBaseFiled.set(wrapper,new HookWrapper((Context) mBaseFiled.get(wrapper)));
} catch (Exception e) {
e.printStackTrace();
}
}
这样尽管可以,但是启动activity就需要注意了,只要弄走到mBase.startActivity(intent)接口才生效,如果没走它,就hook失效啰,所以hook点选择很关键,尽管都hook到了东西,但是是不是hook住了全部,还需要验证下。
hook重在选hook点!!!