Android 通过 Hook 启动未注册 Activity
简介
hook 是钩子的意思,hook 的过程是通过反射、代理等改变系统原有的行为以达到自己的目的。
本文主要是通过 hook android 中的 ActivityManagerService 和 Handler.CallBack,欺骗系统调起 activity 的过程,在调用 startActivity 时将 targetIntent 通过 proxy 伪装为 proxyIntent,等到通过系统验证,正式启动 activity 时,再讲 proxyIntent 恢复为 targetIntent,从而实现调用未在 AndroidManifest.xml 中注册的 activity。
需要注意,本方法只在 Api<26 下有效。具体原因见后面。
具体实现
1.新建 Activity 等
IndexActivity.java
用于启动targetIntent
((Button)findViewById(R.id.btn1)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动未在 AndroidManifest.xml 注册的 activity
mContext.startActivity(new Intent(mContext,TargetActivity.class));
}
});
TargetActivity.java
和ProxyActivity.java
分别设置对应页面布局setContentView(R.layout.activity_xxx);
HookApplication.java
用于调用 hook 方法
public class HookApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Utils.hookAms(this);
Utils.hookHandle();
}
}
在AndroidManifest.xml
中注册IndexActivity
和ProxyActivity
,Application 使用HookApplication
。
2.Utils.java 实现 hook 具体逻辑
Utils.hookAms()
实现拦截 targetIntent 并发起 proxyIntent,欺骗系统对 activity 是否已注册的验证,其中 proxyIntent 通过proxyIntent.putExtra(TARGET_KEY, targetIntent);
方法携带 targetIntent。
//hookAms() 核心代码
Class hookActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
//在 api>26 时无此变量:gDefault,该方法失效
Field gDefault = hookActivityManagerNative.getDeclaredField("gDefault");
gDefault.setAccessible(true);
Object object = gDefault.get(null);
Class hookSingleton = Class.forName("android.util.Singleton");
Field mInstance = hookSingleton.getDeclaredField("mInstance");
mInstance.setAccessible(true);
Object oldAms = mInstance.get(object);
Class hookIActivityManagerService = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{hookIActivityManagerService},
new MAmsInvocationHandler(context,oldAms));
//将原有的 ActivityManagerService 替换为我们自定义的
mInstance.set(object,proxy);
在MAmsInvocationHandler
里面实现 targetIntent 和 proxy 的转换
//MAmsInvocationHandler 核心代码
public class MAmsInvocationHandler implements InvocationHandler{
public static final String TARGET_KEY = "targetIntent";
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
int index = 0;
Intent targetIntent = null;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
targetIntent = (Intent) args[i];
break;
}
}
if (targetIntent != null) {
Intent proxyIntent = new Intent(mContext, ProxyActivity.class);
proxyIntent.putExtra(TARGET_KEY, targetIntent);
args[index] = proxyIntent;
}
}
return method.invoke(mOldAms,args);
}
}
至此,已经对 activity.startActivity 做了拦截,所有的 targetIntent 都会被拦截,存储在 proxyIntent 中,以通过系统的检查。
接下来,通过系统检查后,hookHandle()
通过重写 Handler.CallBack,对启动 proxyIntent 事件做拦截,使之启动 targetIntent 对应的 Activity。
//hookHandle() 核心代码
Class activityThreadCls = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadCls.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
Field mH = activityThreadCls.getDeclaredField("mH");
mH.setAccessible(true);
Handler handler = (Handler) mH.get(activityThread);
Field callBack = Handler.class.getDeclaredField("mCallback");
callBack.setAccessible(true);
callBack.set(handler, new ActivityThreadHandlerCallBack(handler));
其中ActivityThreadHandlerCallBack
将返回我们自定义的 CallBack 以替换系统的,实现启动 targetIntent 而非 proxyIntent。
//ActivityThreadHandlerCallBack 核心代码
public class ActivityThreadHandlerCallBack implements Handler.Callback{
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 100) {
handleLaunchActivity(msg);
}
mHandler.handleMessage(msg);
return true;
}
//主要代码,在这里将 proxyIntent 转化为 targetIntent
private void handleLaunchActivity(Message msg) {
Object object = msg.obj;
try {
Field intent = object.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent proxyIntent = (Intent) intent.get(object);
Intent targetIntent = proxyIntent.getParcelableExtra(MAmsInvocationHandler.TARGET_KEY);
if (targetIntent != null) {
proxyIntent.setComponent(targetIntent.getComponent());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
到这里,就实现了启动通过已经注册了的 ProxyActivity 启动未注册 TargetActivity 的全过程。
主要思想是找到系统实现该过程的逻辑,在对应地方通过反射获取到对应变量,插入自己的逻辑,从而达到目的。
附录
上面涉及到的代码路径:
参考了几篇文章,其中较为完整的一篇如下: