插件化:Android插件化技術,可以實現功能模塊的按需加載和動態更新(從服務器上下載),其本質是動態加載未安裝的apk。從而減小apk的大小。其中最主要的就是Activity的插件化技術,主要是通過hook來實現的。因為Activity的啟動是要經過AMS的校驗的,所以就需要對AMS下功夫。
Step1: 在宿主工程的AndroidManifest.xml中預先注冊一個沒有任何功能的Activity進行占坑。
Step2.:使用占坑Activity繞過AMS驗證:Activity的啟動,實際會調用
Instrumentation
類的execStartActvity
方法,所以可以對其進行hook,將啟動插件Activity的Intent替換成宿主預注冊的插樁Activity,從而繞過ASM的驗證,并將插件Activity的Intent保存起來。Step3: 還原插件Activity:在 AMS 校驗完畢的時候,通過 binder 告知我們的應用啟動相應 activity 的時候,我們將 插件Activity的intent 的信息再取出來,還原。
本文以 API 27 的源碼為基礎分析
Hook 的選擇點:
靜態變量和單例,因為一旦創建對象,它們不容易變化,非常容易定位。
Hook 過程:
1、尋找 Hook 點,原則是靜態變量或者單例對象,盡量 Hook public 的對象和方法。
選擇合適的代理方式,如果是接口可以用動態代理。
2、用代理對象替換原始對象。
3、Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的兼容工作。
Hook AMS
我們先來 mInstrumentation.execStartActivity 方法
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
這里我們留意 ActivityManager.getService().startActivity 這個方法
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
可以看到 IActivityManagerSingleton 是一個單例對象,因此,我們可以 hook 它。
public static void hookAMSAfter26() throws Exception {
// 第一步:獲取 IActivityManagerSingleton
Class<?> aClass = Class.forName("android.app.ActivityManager");
Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");
declaredField.setAccessible(true);
Object value = declaredField.get(null);
Class<?> singletonClz = Class.forName("android.util.Singleton");
Field instanceField = singletonClz.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(value);
// 第二步:獲取我們的代理對象,這里因為 IActivityManager 是接口,我們使用動態代理的方式
Class<?> iActivity = Class.forName("android.app.IActivityManager");
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
Class<?>[]{iActivity}, handler);
// 第三步:偷梁換柱,將我們的 proxy 替換原來的對象
instanceField.set(value, proxy);
}
public class AMSInvocationHandler implements InvocationHandler {
private static final String TAG = "AMSInvocationHandler";
Object iamObject;
public AMSInvocationHandler(Object iamObject) {
this.iamObject = iamObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.e(TAG, method.getName());
if ("startActivity".equals(method.getName())) {
Log.i(TAG, "ready to startActivity");
for (Object object : args) {
Log.d(TAG, "invoke: object=" + object);
}
}
return method.invoke(iamObject, args);
}
}
執行以下測試代碼
try {
HookHelper.hookAMS();
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(this,TestActivity.class);
startActivity(intent);
接下來我們一起來看一下 API 25 Instrumentation 的代碼(自 API 26 開始 ,Instrumentation execStartActivity 方法有所改變)
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
可以看到這里啟動 activity 是調用 ActivityManagerNative.getDefault().startActivity 啟動的。
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
}
同理我們看到 ActivityManagerNative 的 gDefault 是一個靜態變量,因此,我們可以嘗試 hook gDefault.
public static void hookAmsBefore26() throws Exception {
// 第一步:獲取 IActivityManagerSingleton
Class<?> forName = Class.forName("android.app.ActivityManagerNative");
Field defaultField = forName.getDeclaredField("gDefault");
defaultField.setAccessible(true);
Object defaultValue = defaultField.get(null);
Class<?> forName2 = Class.forName("android.util.Singleton");
Field instanceField = forName2.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(defaultValue);
// 第二步:獲取我們的代理對象,這里因為 IActivityManager 是接口,我們使用動態代理的方式
Class<?> iActivity = Class.forName("android.app.IActivityManager");
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);
// 第三步:偷梁換柱,將我們的 proxy 替換原來的對象
instanceField.set(defaultValue, proxy);
}
啟動一個沒有在 AndroidManifest 聲明的 Activity
我們知道我們啟動的 activity 信息都儲存在 intent 中,那么我們若想要 啟動一個沒有在 AndroidManifest 聲明的 Activity,那我們只需要在 某個時機,即調用 startActivity 方法之前欺騙 AMS ,我們的 activity 已經注冊(即替換 intent)。
這里我們重新理一下 Activity 大概的啟動流程:
app 調用 startActivity 方法 -> Instrumentation 類通過 ActivityManagerNative 或者 ActivityManager( API 26以后)將啟動請求發送給 AMS -> AMS 進行一系列檢查并將此請求通過 Binder 派發給所屬 app -> app 通過 Binder 收到這個啟動請求 -> ActivityThread 中的實現將收到的請求進行封裝后送入 Handler -> 從 Handler 中取出這個消息,開始 app 本地的 Activity 初始化和啟動邏輯。
public class HookHelper {
private static final String TAG = "xiaosanye";
public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
public static void hookIActivityManager() {
//TODO:
// 1. 找到了Hook的點
// 2. hook點 動態代理 靜態?
// 3. 獲取到getDefault的IActivityManager原始對象
// 4. 動態代理 準備classloader 接口
// 5 classloader, 獲取當前線程
// 6. 接口 Class.forName("android.app.IActivityManager");
// 7. Proxy.newProxyInstance() 得到一個IActivityManagerProxy
// 8. IActivityManagerProxy融入到framework
try {
Field gDefaultField = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Class<?> activityManager = Class.forName("android.app.ActivityManager");
gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
} else {
Class<?> activityManager = Class.forName("android.app.ActivityManagerNative");
//拿到 Singleton<IActivityManager> gDefault
gDefaultField = activityManager.getDeclaredField("gDefault");
}
gDefaultField.setAccessible(true);
//Singlon<IActivityManager>
//所有靜態對象的反射可以通過傳null獲取。如果是實列必須傳實例
Object gDefault = gDefaultField.get(null);
//拿到Singleton的Class對象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//獲取到ActivityManagerNative里面的gDefault對象里面的原始的IActivityManager對象
final Object rawIActivityManager = mInstanceField.get(gDefault);
//進行動態代理
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//生產IActivityManager的代理對象
Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{iActivityManagerInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(TAG, "invoke: method " + method.getName());
if ("startActivity".equals(method.getName())) {
Log.i(TAG, "準備啟動activity");
for (Object obj : args) {
Log.i(TAG, "invoke: obj= " + obj);
}
//偷梁換柱 把Target 換成我們的Stub,欺騙AMS的權限驗證
//拿到原始的Intent,然后保存
Intent raw = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
raw = (Intent) args[i];
index = i;
break;
}
}
Log.i(TAG, "invoke: raw= " + raw);
//替換成Stub
Intent newIntent = new Intent();
String stubPackage = "com.xiaosanye.activityhookdemo";
newIntent.setComponent(new ComponentName(stubPackage, StubActivity.class.getName()));
//把這個newIntent放回到args,達到了一個欺騙AMS的目的
newIntent.putExtra(EXTRA_TARGET_INTENT, raw);
args[index] = newIntent;
}
return method.invoke(rawIActivityManager, args);
}
});
//把我們的代理對象融入到framework
mInstanceField.set(gDefault, proxy);
} catch (Exception e) {
Log.e(TAG, "hookIActivityManager: " + e.getMessage());
e.printStackTrace();
}
}
/**
* hook ActivityThread 的 mH,還原未注冊的Activity
*/
public static void hookHandler() {
//TODO:
try {
Class<?> atClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
//ActivityThread 一個app進程 只有一個,獲取它的mH
Field mHField = atClass.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(sCurrentActivityThread);
//獲取mCallback
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg.what);
switch (msg.what) {
case 100: {
// final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
// static final class ActivityClientRecord {
// IBinder token;
// int ident;
// Intent intent;//hook 恢復
//恢復真身
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
intent.setComponent(targetIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
break;
case 159: {
Object obj = msg.obj;
Log.i(TAG, "handleMessage: obj=" + obj);
try {
Field mActivityCallbacksField = obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List)mActivityCallbacksField.get(obj);
Log.i(TAG, "handleMessage: mActivityCallbacks= " + mActivityCallbacks);
if(mActivityCallbacks.size()>0){
Log.i(TAG, "handleMessage: size= " + mActivityCallbacks.size());
String className = "android.app.servertransaction.LaunchActivityItem";
if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)){
Object object = mActivityCallbacks.get(0);
Field intentField =object.getClass().getDeclaredField("mIntent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(object);
Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
intent.setComponent(targetIntent.getComponent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
mH.handleMessage(msg);
return true;
}
});
} catch (Exception e) {
Log.e(TAG, "hookHandler: " + e.getMessage());
e.printStackTrace();
}
}
}
public void onBtnHookClicked() {
HookHelper.hookIActivityManager();
HookHelper.hookHandler();
Intent intent = new Intent(this,TestActivity.class);
startActivity(intent);
}
運行以上代碼,可以看到我們可以正常啟動沒有在 AndroidManifest 的 activity
小結
啟動沒有在 AndroidManifest 注冊的 Activity 可以分為連個步驟
- 1、在 AMS 通過 intent 校驗 activity 是否注冊的時候,用已經在 AndroidManifet 注冊的 Activity 欺騙 AMS,繞過 原有 activity 的校驗,并將原有的 intent 信息儲存起來
- 2、在 AMS 校驗完畢的時候,通過 binder 告知我們的應用啟動相應 activity 的時候,我們將 intent 的信息取出來,還原。
參考文章:https://blog.csdn.net/gdutxiaoxu/article/details/81459910