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中的singleton的instance,他就是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中的元件,還有待考究,個人感覺是可以的|