Android 7.1 APP 启动流程分析 里面讲解的有Activity的启动流程图,想要使用hook替换目标Activity需要先了解AMS如何创建的Activity。
总体流程
总体的思路就是通过反射,先使用占坑Activity创建activity,然后再替换成目标activity,大致流程如下:
由上图可知
1.通过反射 使用 如下代码设置 MyIActivityManagerHandler.java 到AMS中
我们先使用占坑的activity替换成我们启动的activity,然后当activity创建完成之后再替换成我们目标的activity。
2.再使用反射获取 ActivityThread.java 设置 MyActivityThreadHandlerCallback,在MyActivityThreadHandlerCallback中替换成自己的目标Activity。
hook的好处是可以不在AndroidManifest.xml注册我们Activity也能启动我们的Activity。
项目详解
整体的项目目录
├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ └── ccl
│ │ └── demo
│ │ ├── GoalActivity.java //目标类 ,未注册
│ │ ├── HookUtils.java //hook工具
│ │ ├── MainActivity.java //主函数
│ │ ├── MyActivityThreadHandlerCallback.java //Handler.Callback子类
│ │ ├── MyIActivityManagerHandler.java //InvocationHandler子类
│ │ └── PitActivity.java //占坑类
首先查看AndroidManifest.xml代码,PitActivity是用来天坑的Activity
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ccl.demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--占坑类-->
<activity android:name=".PitActivity"/>
</application>
</manifest>
GoalActivity.java
public class GoalActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("chencl_"," 目标activity GoalActivity onCreate ");
}
}
PitActivity.java
public class PitActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("chencl_"," 占坑 Activity PitActivity onCreate ");
}
}
HookUtils.java 主要负责hook的类,hookIActivityManager 方法用于获取 android.util.Singleton 并且将 new MyIActivityManagerHandler(rawIActivityManager)替换到 Proxy.newProxyInstance 中进行动态化代理。hookActivityThreadHandler方法 获取到 android.app.ActivityThread 将MyActivityThreadHandlerCallback 设置进去,在MyActivityThreadHandlerCallback 中替换为我们自己的activity。
public class HookUtils {
public static final String EXTRA_TARGET_INTENT = "activity";
/**
* Hook AMS
* 主要完成的操作是,代理Hook AMS 在应用进程的本地代理IActivityManager ,
* 把真正要启动的Activity临时替换为在AndroidManifest.xml 里坑位Activity
* 进而骗过AMS
*/
public static void hookIActivityManager() throws Exception {
Field amSingletonField =null;
if (Build.VERSION.SDK_INT >= 26) {
Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
amSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
}else{
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
amSingletonField = activityManagerNativeClass.getDeclaredField("gDefault");
}
amSingletonField.setAccessible(true);
Object iamSingletonObj = amSingletonField.get(null);
// 得到iamSingletonObj ,得到iamSingletonObj 对象里的mInstance 字段值,这个值就是那个需要的单例,
// 就是AMS 在应用进程的本地代理对象
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 原始的 IActivityManager对象
Object rawIActivityManager = mInstanceField.get(iamSingletonObj);
// 用动态代理,这里在执行相应方法执行我们的一些逻辑(这里指的是修改Intent 使用坑位Activity ,从而可以越过AMS)
// 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerInterface }, new MyIActivityManagerHandler(rawIActivityManager));
mInstanceField.set(iamSingletonObj, proxy);
}
/**
* 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity ,不然就真的启动替身了
* 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
* H 会完成这个消息转发,这里对H 的mCallback 进行处理
*/
public static void hookActivityThreadHandler() throws Exception {
// 先获取到当前的ActivityThread 对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 获取ActivityThread 的字段mH 对象
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
// 设置我们的代理CallBack
mCallBackField.set(mH, new MyActivityThreadHandlerCallback(mH));
}
}
MyIActivityManagerHandler.java 负责具体的替换过程
class MyIActivityManagerHandler implements InvocationHandler {
private static final String TAG = "MyIActivityManagerHandler";
Object mBase;
public MyIActivityManagerHandler(Object base) {
mBase = base;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
// 找到参数里面的第一个Intent 对象
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
Intent newIntent = new Intent();
// 替身Activity的包名, 也就是我们自己的包名
String stubPackage = "com.ccl.demo";
// 这里我们把启动的Activity临时替换为坑位 StubActivity
ComponentName componentName = new ComponentName(stubPackage, PitActivity.class.getName());
newIntent.setComponent(componentName);
// 把我们原始要启动的TargetActivity先存起来
newIntent.putExtra(HookUtils.EXTRA_TARGET_INTENT, raw);
// 替换掉Intent, 达到欺骗AMS的目的
args[index] = newIntent;
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}
MyActivityThreadHandlerCallback.java 负责替换为我们目标activity
class MyActivityThreadHandlerCallback implements Handler.Callback {
Handler mBase;
public MyActivityThreadHandlerCallback(Handler base) {
mBase = base;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
// ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
// 本来使用反射的方式获取最好, 这里为了简便而直接使用硬编码
case 100:
handleLaunchIntent(msg);
break;
}
mBase.handleMessage(msg);
return true;
}
private void handleLaunchIntent(Message msg) {
// 这里简单起见,直接取出TargetActivity;
Object obj = msg.obj;
// 根据源码,这个msg.obj 对象是 ActivityClientRecord 类型,修改它的intent字段,恢复目标Activity
try {
// 把替身恢复成真身
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent raw = (Intent) intent.get(obj);
Intent target = raw.getParcelableExtra(HookUtils.EXTRA_TARGET_INTENT);
raw.setComponent(target.getComponent());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
MainActivity.java 调用的时候先调用 hookIActivityManager hookActivityThreadHandler
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
//先执行 hookIActivityManager hookActivityThreadHandler
HookUtils.hookIActivityManager();
HookUtils.hookActivityThreadHandler();
} catch (Exception e) {
Log.d("chencl_",e.toString());
e.printStackTrace();
}
//开启目标Activity GoalActivity
startActivity(new Intent(MainActivity.this, GoalActivity.class));
}
}
执行项目打印的log是
06-07 17:27:56.230 32029-32029/com.ccl.demo D/chencl_: 目标activity GoalActivity onCreate
而 GoalActivity 并未在 AndroidManifest.xml 中注册。