本文 Android 系統原始碼基於 9.0
我們知道新建一個 Activity 之後我們需要在 manifest 中註冊,否則啟動的時候就會崩潰,現在使用 Hook 的方法繞過檢查來啟動一個沒有註冊的 Activity。
如果我們不註冊的話就會報下面的錯誤:
Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.jeanboy.app.hooktrainning/com.jeanboy.app.hooktrainning.UnregisterActivity}; have you declared this activity in your AndroidManifest.xml?
startActivity(new Intent(this, UnregisterActivity.class));
我們從 startActivity() 可以看到:
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
startActivityForResult(intent, -1);
}
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
// 關鍵程式碼
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());
}
if (requestCode >= 0) {
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
可以看到進入到了 Instrumentation 這個類中的 execStartActivity() 方法。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
// ...
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() 是拿到 ActivityManagerService 服務在原生的代理物件,然後通過它操作 ActivityManagerService 執行 startActivity() 方法返回一個結果,最後執行 checkStartActivityResult() 方法。
public static void checkStartActivityResult(int res, Object intent) {
if (!ActivityManager.isStartResultFatalError(res)) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
// ...
}
}
在 checkStartActivityResult() 方法中可以看到,當 res 返回是 START_CLASS_NOT_FOUND 的時候就會報出一開始的錯誤了。因為我們傳過去的 Activity ActivityManagerService 找不到。
所以我們就可以把檢查方法之前的 ActivityManager.getService().startActivity() 作為一個 Hook 點,我們給它隨便傳一個註冊過的 Acivity,這樣就可以欺騙 ActivityManagerService 了。
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_TASK_SERVICE);
return IActivityManager.Stub.asInterface(b);
}
};
public abstract class Singleton<T> {
public Singleton() { }
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
可以看到 Singleton 是一個系統的單例類,getService()
方法呼叫的時候,就會 create() 方法,最終會呼叫 IActivityManagerSingleton 中的 create() 方法建立一個 IActivityManager 返回。
IActivityManager 就是 ActivityManagerService 在原生的代理物件。用來進行程序間的 Binder 通訊。
我們來 Hook IActivityManager,替換成我們自己的。
先定義一個空的 ProxyActivity,並在 AnroidManifest 中註冊:
public class ProxyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proxy);
}
}
然後在 Application 中 Hook 住 ActivityManagerService。
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookAMS.hookStartActivity(this);
}
}
public class HookAMS {
public static void hookStartActivity(final Context context) {
try {
// 獲取到 ActivityTaskManager 的 Class 物件
@SuppressLint("PrivateApi")
Class<?> amClass = Class.forName("android.app.ActivityManager");
// 獲取到 IActivityTaskManagerSingleton 成員變數
Field iActivityTaskManagerSingletonField = amClass.getDeclaredField("IActivityManagerSingleton");
iActivityTaskManagerSingletonField.setAccessible(true);
// 獲取 IActivityTaskManagerSingleton 成員變數的值
Object IActivityTaskManagerSingleton = iActivityTaskManagerSingletonField.get(null);
// 獲取 getService() 方法
@SuppressLint("BlockedPrivateApi")
Method getService = amClass.getDeclaredMethod("getService");
getService.setAccessible(true);
// 執行 getService() 方法
final Object IActivityTaskManager = getService.invoke(null);
// 獲取到 IActivityTaskManager 的 Class 物件
@SuppressLint("PrivateApi")
Class<?> iamClass = Class.forName("android.app.IActivityManager");
// 建立代理類 IActivityTaskManager
Object proxyIActivityManager = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{iamClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
Intent proxyIntent = new Intent(context, ProxyActivity.class);
// startActivity 第三個引數為 Intent
proxyIntent.putExtra("targetIntent", (Intent) args[2]);
args[2] = proxyIntent;
}
return method.invoke(IActivityTaskManager, args);
}
});
// 獲取到 Singleton 的 Class 物件
@SuppressLint("PrivateApi")
Class<?> sClass = Class.forName("android.util.Singleton");
// 獲取到 mInstance 成員變數
Field mInstanceField = sClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 賦值 proxyIActivityManager 給 mInstance 成員變數
mInstanceField.set(IActivityTaskManagerSingleton, proxyIActivityManager);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
我們的目的很清楚,通過反射拿到 IActivityManager 的範例,然後把它替換成我們自己的 proxyIActivityManager。動態代理物件中,我們把 intent 替換成一個註冊過的 Activity 也就是 ProxyActivity。現在我們就攔截住了,當我們跳轉到 UnregisterActivity 這個沒有註冊的 Activity 的時候,就會先跳轉到該 ProxyActivity。
當然這不是我們想要的效果,我們需要在檢查完之後再給它替換回來,所以在檢查完後還要 Hook 一個地方給它換回來。
熟悉 Activity 的啟動流程的都知道,ActivityManagerService 處理完成之後,會執行到 realStartActivityLocked()。最終會回到 ActivityThread 類中的 mH 這個 Handler 中進行最後的處理。
class H extends Handler {
// ...
public void handleMessage(Message msg) {
switch (msg.what) {
// ...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
}
}
ClientTransaction 內部有一個 ClientTransactionItem 的集合。
public class ClientTransaction implements Parcelable, ObjectPoolItem {
private List<ClientTransactionItem> mActivityCallbacks;
// ...
}
在 realStartActivityLocked() 方法中可以看到將一個 LaunchActivityItem 新增到 ClientTransaction 中的集合中,也就是 mActivityCallbacks 中。
public class LaunchActivityItem extends ClientTransactionItem {
private Intent mIntent;
private int mIdent;
private ActivityInfo mInfo;
private Configuration mCurConfig;
private Configuration mOverrideConfig;
private CompatibilityInfo mCompatInfo;
private String mReferrer;
private IVoiceInteractor mVoiceInteractor;
private int mProcState;
private Bundle mState;
private PersistableBundle mPersistentState;
private List<ResultInfo> mPendingResults;
private List<ReferrerIntent> mPendingNewIntents;
private boolean mIsForward;
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
// ...
}
LaunchActivityItem 中儲存了 Activity 的各種資訊,這裡有一個 mIntent 引數,它現在的跳轉是我們在上一個 Hook 點改變成的 ProxyActivity,所以這裡我們需要重新給他還原會我們的 UnregisterActivity,這樣才能順利跳轉到 UnregisterActivity 中。
因此,我們需要在執行 Handler 中的 handleMessage() 方法之前將它給改了。
我們知道 Handler 的訊息分發機制中有一個 dispatchMessage() 方法:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Activity 的啟動最終會執行 handleMessage() 方法,而在這個之前有一個判斷,如果 mCallback 不為 null 就執行 mCallback.handleMessage(msg) 方法。所以我們可以給它傳一個我們自己的 CallBack,在內部將 mIntent 給改了,然後返回 false 它還是會繼續執行下面的 handleMessage 方法,這樣就完成了替換。
然後在 Application 中 Hook 住 ActivityThread。
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookAMS.hookStartActivity(this);
HookAMS.hookActivityThread();
}
}
public class HookAMS {
// ...
public static void hookActivityThread() {
try {
// 獲取到 mH 物件
@SuppressLint("PrivateApi")
Class<?> atClass = Class.forName("android.app.ActivityThread");
Field mHField = atClass.getDeclaredField("mH");
mHField.setAccessible(true);
// 獲取到 ActivityThread 物件
@SuppressLint("DiscouragedPrivateApi")
Method currentActivityThreadMethod = atClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
Object mH = mHField.get(currentActivityThread);
// 拿到 mCallback 替換成我們自己的
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new MyCallback());
} catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
private static class MyCallback implements Handler.Callback {
@Override
public boolean handleMessage(@NonNull Message msg) {
Object clientTransactionObj = msg.obj;
try {
@SuppressLint("PrivateApi")
Class<?> laiClass = Class.forName("android.app.servertransaction.LaunchActivityItem");
Field mActivityCallbacksField = clientTransactionObj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List activityCallbackList = (List) mActivityCallbacksField.get(clientTransactionObj);
if (activityCallbackList == null || activityCallbackList.size() == 0) {
return false;
}
Object mLaunchActivityItem = activityCallbackList.get(0);
if (!laiClass.isInstance(mLaunchActivityItem)) {
return false;
}
Field mIntentField = laiClass.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
// 獲取代理的 Intent
Intent proxyIntent = (Intent) mIntentField.get(mLaunchActivityItem);
if (proxyIntent == null) {
return false;
}
// 獲取到前面傳入的 targetIntent
Intent targetIntent = proxyIntent.getParcelableExtra("targetIntent");
if (targetIntent != null) {
// 替換 Intent
mIntentField.set(mLaunchActivityItem, targetIntent);
}
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
}
}
這樣就完成了對 ActivityManagerService 的欺騙,可以啟動沒有在 manifest 中註冊過的 Activity 了。
歡迎加入技術交流群,來一起交流學習。
歡迎關注我的公眾號,分享各種技術乾貨,各種學習資料,職業發展和行業動態。