在談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點!!!