8.插件化开发--Hook基础
2017-04-06 本文已影响188人
android_赵乐玮
1.基础:Activity的启动流程
- 启动流程
Activity1(A1)【IActicityManager】--IPC
--->AMS------>PackageManagerServer(扫描清单文件)----->找到Activity2()--->启动A2并停止A1
Hook后:
Activity1(A1)【IActicityManager】----->Hook(使用代理Activity)--intent(proxy)
--->AMS------>PackageManagerServer(扫描清单文件)----->找到Activity2()-intent(real)
-->启动A2并停止A1
2.具体项目示例
- 基础Hook案例:
启动未在清单文件中注册的Activity。 - 实现思路:
根据ActivityThread源码可以看Activity的启动仅仅需要提供intent(主要提供目标Activity)就能启动一个Activity
//源码流程:ActivityThread.handleMessage() ==>handleLaunchActivity() ==>performLaunchActivity()==>mInstrumentation.newActivity()
//主要查看ActivityClientRecord数据包的流向
public Activity newActivity(Class<?> clazz, Context context,
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null, null, null);
return activity;
}
-
但是大家都知道,如果Activity没有在清单文件中注册就会混抛异常。那怎么办呢?(怎样能让系统不抛异常?)
思路很简单 , 抛出异常原因是intent经过PMS后会进行扫描清单文件,如果没有就会抛异常。
那么只要在PMS处理之前把intent的目的Activity地址改为已知的代理Activity,在PMS之后再恢复成原地址即可, -
那么如何去替换intent呢?在哪里换呢?
替换intent可以使用反射机制去动态替换intent内容
由上面的Activity启动流程图可以看出,最好的替换地点就在Activity intent发出和Acitvity的启动前进行替换和逆替换.其他地方都不是在UI线程上也不好替换。
咱们从源码一个一个分析:
发送Intent的具体流程:
//当然看源码从Acitvity.startActivity()中开始,看intent的流向
Acitvity.startActivity()==>startActivityForResult()
==>startActivityFromChild()
==>Instrumentation.execStartActivity()
==>ActivityManagerNative.getDefault().startActivity()
哈哈找到IActivityManager(ActivityManagerNative.getDefault()
就是它)了
Activity就是根据IActivityManager与AMS进行跨进程通信的(IPC)
那么只要代理替换掉IActivityManager中的intent就可以了。
- 下面是具体实现方法(其实为什么比怎么实现的更重要!)
public void hookAms() throws Exception {
Logger.d(TAG, "start hook ");
Class<?> forName = Class.forName("android.app.ActivityManagerNative");
Field defaultField = forName.getDeclaredField("gDefault");
defaultField.setAccessible(true);
Object defaultValue = defaultField.get(null);
Class<?> singleton = Class.forName("android.util.Singleton");
Field instanceField = singleton.getDeclaredField("mInstance");
instanceField.setAccessible(true);
//获取到IActivityManager
Object iActivityManagerObj = instanceField.get(defaultValue);
//代理对象-hook
Class<?> iActivityManagerIntercept = Class.forName("android.app.IActivityManager");
AmsInvocationHanadel hanadel = new AmsInvocationHanadel(iActivityManagerObj);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivityManagerIntercept}, hanadel);
instanceField.set(defaultValue, proxy);
}
class AmsInvocationHanadel implements InvocationHandler {
private Object iActivityManagerObj;
public AmsInvocationHanadel(Object iActivityManagerObj) {
this.iActivityManagerObj = iActivityManagerObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Logger.d(TAG, "----------------AmsInvocationHanadel--------------------");
if ("startActivity".equals(method.getName())) {
Logger.d(TAG, "methodName:" + method.getName());
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
//找到intent;
index = i;
intent = (Intent) args[i];
break;
}
}
Intent proxyIntent = new Intent();
ComponentName compnnentName = new ComponentName(context, proxyActivity);
proxyIntent.setComponent(compnnentName);
proxyIntent.putExtra("oldIntent", intent);
Logger.d(TAG, "index :" + index);
args[index] = proxyIntent;
Logger.d(TAG, "替换成proxyIntent :" + proxyIntent.getComponent().getPackageName() + "; " + proxyIntent.getComponent().getShortClassName());
return method.invoke(iActivityManagerObj, args);
}
return method.invoke(iActivityManagerObj, args);
}
}
- 逆替换
//----------逆替换------------
public void hookSystemHandler() {
try {
Class<?> forName = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThread = forName.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThread.setAccessible(true);
Object activityThreadValue = sCurrentActivityThread.get(null); //系统程序入口
Field handlerField = forName.getDeclaredField("mH");
handlerField.setAccessible(true);
Handler handlerObj = (Handler) handlerField.get(activityThreadValue);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(handlerObj, new ActivityThreadHandlerCallback(handlerObj));
} catch (Exception e) {
e.printStackTrace();
}
}
class ActivityThreadHandlerCallback implements Handler.Callback {
Handler handler;
public ActivityThreadHandlerCallback(Handler handler) {
super();
this.handler = handler;
}
@Override
public boolean handleMessage(Message msg) {
Logger.d(TAG, "message callback");
//替换回去
if (msg.what == 100) {
Logger.d(TAG, "what:" + msg.what);
Logger.d(TAG, "Activity lunchActivity");
handleLunchActivity(msg);
}
handler.handleMessage(msg);
return true;
}
private void handleLunchActivity(Message msg) {
Object obj = msg.obj;//ActivityClientRecord
try {
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(obj); //代理Intent
Logger.d(TAG, "获取到proxyIntent");
Intent realIntent = proxyIntent.getParcelableExtra("oldIntent");
if (realIntent != null) {
Logger.d(TAG, " have realIntent ");
proxyIntent.setComponent(realIntent.getComponent());//替换属性
} else {
Logger.d(TAG, "realIntent is null");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 重写Application方法
/**
* Created by zlw on 2017/4/5.
*/
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
HookAmsUtils amsUtils = new HookAmsUtils(this, ProxyActivity.class);
try {
amsUtils.hookAms();
amsUtils.hookSystemHandler();
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后配置下清单文件就可以了
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zlw.main.myhookdemo">
<application
android:name=".base.MyApp"
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=".base.ProxyActivity" />
</application>
</manifest>