1. 程式人生 > >Android外掛化架構之Hook繞過manifest檢測

Android外掛化架構之Hook繞過manifest檢測

學習自

https://www.jianshu.com/p/e359fafe5c29

問題

我們外掛apk是不會進行一個安裝的,那麼他的manifest就不會生效,所以我們直接啟動肯定是行不通的。所以我們只能隔絕掉我們主apk的manifest的檢測。

具體思路如下

只需要動態代理hook,先在AMS的startActivity的方法中的Intent中啟動一個已註冊的活動假扮,然後在ActivityThread.H.launchActivity的處理中替換我們真實的Intent即可!

具體程式碼不解釋了,很簡單!

package com.example.myapplication;
import android.app.Activity;
import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class App extends Application { private static final String TAG = "xbh"; @Override public void onCreate() { super.onCreate(); mContext = this; try { hookStartActivity(); hookLaunchActivity(); } catch (Exception e) { e.printStackTrace();
} } private Context mContext; /** * hook */ public void hookStartActivity() throws Exception{ //ActivityManagerNative.gDefault Class<?> amnClazz = Class.forName("android.app.ActivityManagerNative"); Field defaultField = amnClazz.getDeclaredField("gDefault"); defaultField.setAccessible(true); Object gDefaultObj = defaultField.get(null); //gDefault中的singletoninstance,他就是AMS Class<?> singletonClazz = Class.forName("android.util.Singleton"); Field amsField = singletonClazz.getDeclaredField("mInstance"); amsField.setAccessible(true); Object amsObj = amsField.get(gDefaultObj); //動態代理Hook下鉤子 amsObj = Proxy.newProxyInstance(mContext.getClass().getClassLoader(), amsObj.getClass().getInterfaces(), new StartActivityInvocationHandler(amsObj)); // 注入 amsField.set(gDefaultObj,amsObj); } private class StartActivityInvocationHandler implements InvocationHandler { private Object mAmsObj; StartActivityInvocationHandler(Object amsObj){ this.mAmsObj = amsObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 攔截到所有AMS中被呼叫的方法 if(method.getName().equals("startActivity")){ Intent realIntent = (Intent) args[2]; Intent proxyIntent = new Intent(); proxyIntent.setComponent(new ComponentName(mContext,MainActivity.class)); // 把原來的Intent綁在代理Intent上面 proxyIntent.putExtra("realIntent",realIntent); // proxyIntent去晒太陽,借屍 args[2] = proxyIntent; } return method.invoke(mAmsObj,args); } } /** * 還魂 */ public void hookLaunchActivity() throws Exception{ // 獲取ActivityThread Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread"); Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThreadField.setAccessible(true); Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null); // 獲取Handler mH Field mHField = activityThreadClazz.getDeclaredField("mH"); mHField.setAccessible(true); Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj); // 設定Callback Field callBackField = Handler.class.getDeclaredField("mCallback"); callBackField.setAccessible(true); callBackField.set(mH, new ActivityThreadHandlerCallBack()); } class ActivityThreadHandlerCallBack implements Handler.Callback { @Override public boolean handleMessage(Message msg) { if (msg.what == 100) handleLaunchActivity(msg); return false; } } // 還魂 private void handleLaunchActivity(Message msg) { // final ActivityClientRecord r = (ActivityClientRecord) msg.obj; try { Object obj = msg.obj; Field intentField = obj.getClass().getDeclaredField("intent"); intentField.setAccessible(true); Intent proxyIntent = (Intent) intentField.get(obj); // 代理意圖 Intent originIntent = proxyIntent.getParcelableExtra("realIntent"); if (originIntent != null) { // 替換意圖 intentField.set(obj, originIntent); } } catch (Exception e) { e.printStackTrace(); } } }

然後你去啟動一個未註冊的活動就可以成功了。但是上述的前提是你的target活動是繼承自Activity,而不是AppcompatActivity。否則會報

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.example.myapplication/com.example.myapplication.Main3Activity}

這樣的錯。

原因我猜測一下:Appcompat在找的時候,依然會去從manifest中去找!他找了,結果自然是找不到。我的意思就是Activity是單重檢查,Appcompat是一前一後的雙重檢查,如何規避掉第二重檢查使我們解決問題的關鍵!目前暫時沒能解決,需要看PMS有關的原始碼!

最後我定位到PMS的這個方法

private ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
        int filterCallingUid, int userId) {
    if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId, component);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
            false /* requireFullPermission */, false /* checkShell */, "get activity info");
    synchronized (mPackages) {
        PackageParser.Activity a = mActivities.mActivities.get(component);
        if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
        if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
            PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
            if (ps == null) return null;
            if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
                return null;
}
            return PackageParser.generateActivityInfo(
                    a, flags, ps.readUserState(userId), userId);
}
        if (mResolveComponentName.equals(component)) {
            return PackageParser.generateActivityInfo(
                    mResolveActivity, flags, new PackageUserState(), userId);
}
    }
    return null;
}

如果這個方法返回空,就會丟擲crash中的那個異常!

ComponentName就是我們目標的Activity的ComponentName,在這裡也就是:

ComponentInfo{com.example.myapplication/com.example.myapplication.Main3Activity

顯然這個東西在這個方法裡是通不過的,會返回null,而註冊後就不會返回null了,這個就是突破口

最後瞭解到在apk安卓的時候,PackageParser就會解析一次manifest,所以哪怕繞過第一次manifest的校驗,也無法繞過第二次針對於AppcompatActivity的校驗。所以提出兩個方式解決:

1.運用大量反射,代理,HOOK,在程式碼中動態修改manifest或者在校驗源頭動態增加我們的target activity

2.直接在註冊檔案裡註冊得了!最省事!只不過能否註冊未安裝apk中的元件,還有待考究,個人感覺是可以的|