我们在上篇文章中主要讲了加载jar包和加载apk换肤,没看的可以直接戳着个链接:
这篇文章主要讲解如何利用动态代理技术Hook掉系统的AMS服务,来实现拦截Activity的启动流程,这种hook原理方式来自DroidPlugin
。代码量不是很多,为了更容易的理解,需要掌握JAVA的反射,动态代理技术,以及Activity的启动流程。
1、寻找Hook点的原则
Android中主要是依靠分析系统源码类来做到的,首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?一般来说,静态变量和单例变量是相对不容易改变,是一个比较好的hook点,而普通的对象有易变的可能,每个版本都不一样,处理难度比较大。我们根据这个原则找到所谓的Hook点。
2.寻找Hook点
通常我们启动一个Activity.这中间发生了什么,我们如何Hook,来实现Activity启动的拦截呢?
/**
* hookActivity(启动一个无注册的activity)
*/
public void hookActivity(View view) {
Intent intent = new Intent(this, OtherActivity.class);
intent.putExtra(DLConstants.EXTRA_CLASS, "plugin.charles.com.plugindemo.plugin.OtherActivity");
intent.putExtra(DLConstants.EXTRA_PACKAGE, "plugin.charles.com.plugindemo");
startActivity(intent);
}
我们目的是要拦截startActivity()方法 跟踪源码,跟着这个方法一步一步跟踪,会发现它最后在startActivityForResult里面调用了Instrument对象的execStartActivity方法;其实这个类相当于启动Activity的中间者,启动Activity中间都是由它来操作的
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);
//通过ActivityManagerNative.getDefault()获取一个对象,开始启动新的Activity
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;
}
对于ActivityManagerNative这个东东,熟悉Activity/Service启动过程的都不陌生
public abstract class ActivityManagerNative extends Binder implements IActivityManager
继承了Binder,实现了一个IActivityManager接口,这就是为了远程服务通信做准备的"Stub"类,一个完整的AID L有两部分,一个是个跟服务端通信的Stub,一个是跟客户端通信的Proxy。ActivityManagerNative就是Stub,阅读源码发现在ActivityManagerNative 文件中还有个ActivityManagerProxy,这里就多不扯了。
static public IActivityManager getDefault() {
return gDefault.get();
}
ActivityManagerNative.getDefault()获取的是一个IActivityManager对象,由IActivityManager去启动Activity,IActivityManager的实现类是ActivityManagerService,ActivityManagerService是在另外一个进程之中,所有Activity 启动是一个跨进程的通信的过程,所以真正启动Activity的是通过远端服务ActivityManagerService来启动的。
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;
}
其实gDefalut借助Singleton实现的单例模式,而在内部可以看到先从ServiceManager中获取到AMS远端服务的Binder对象,然后使用asInterface方法转化成本地化对象,我们目的是拦截startActivity,所以改变IActivityManager对象可以做到这个一点,这里gDefault又是静态的,根据Hook原则,这是一个比较好的Hook点。
3.我们来拦截startActivity()方法,并且打印日志
public class HookUtils {
private Class<?> proxyActivity;
private Context context;
public HookUtils(Class<?> proxyActivity, Context context) {
this.proxyActivity = proxyActivity;
this.context = context;
}
public void hookAms() {
//一路反射,直到拿到IActivityManager的对象
try {
Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
Field defaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
defaultFiled.setAccessible(true);
Object defaultValue = defaultFiled.get(null);
//反射SingleTon
Class<?> SingletonClass = Class.forName("android.util.Singleton");
Field mInstance = SingletonClass.getDeclaredField("mInstance");
mInstance.setAccessible(true);
//到这里已经拿到ActivityManager对象
Object iActivityManagerObject = mInstance.get(defaultValue);
//开始动态代理,用代理对象替换掉真实的ActivityManager,瞒天过海
Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{IActivityManagerIntercept}, handler);
//现在替换掉这个对象
mInstance.set(defaultValue, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
private class AmsInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
private AmsInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("HookUtil", method.getName());
if ("startActivity".contains(method.getName())) {
//Todo
Log.e("HookUtil", "Activity已经开始启动");
}
return method.invoke(iActivityManagerObject, args);
}
}
然后在Application配置下
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookUtils hookUtil = new HookUtils(ProxyActivity.class, this);
hookUtil.hookAms();
}
}
这里的proxyActivity 可以提前在清单文件中注册,这个属于占坑位,下面会讲无需注册启动Activity,就需要再宿主中先注册个占坑位的Activity.
最后我们只需要 调用上面第二点里面的hookActivity()方法就可以。
看看执行效果
image.png
可以看到我们已经进行拦截了,并且打印我们想要的日志.
4.无需注册Activity,并且启动
如下:我们将下面这个OtherActivity 在清单文件中不注册了。当我们去点击跳转时肯定就会报错,
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
/**
* hookActivity(启动一个无注册的activity)
*/
public void hookActivity(View view) {
Intent intent = new Intent(this, OtherActivity.class);
intent.putExtra(DLConstants.EXTRA_CLASS, "plugin.charles.com.plugindemo.plugin.OtherActivity");
intent.putExtra(DLConstants.EXTRA_PACKAGE, "plugin.charles.com.plugindemo");
startActivity(intent);
}
上面已经拦截了启动Activity流程,在invoke中我们可以得到启动参数intent信息,那么就在这里,我们可以自己构造一个假的Activity信息的intent,这个Intent启动的Activity是在清单文件中注册的,当真正启动的时候(ActivityManagerService校验清单文件之后),用真实的Intent把代理的Intent在调换过来,然后启动即可。
首先获取真实启动参数intent信息,在拦截地方添加如下代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("HookUtil", method.getName());
//我要在这里搞点事情
if ("startActivity".contains(method.getName())) {
Log.e("HookUtil", "Activity已经开始启动");
//替换Intent 先找到原始的intent,然后直接伪造一个Intent 给AMS
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Intent) {
//说明找到了startActivity的Intent参数
intent = (Intent) args[i];
//这个意图是不能被启动的,因为Acitivity没有在清单文件中注册
index = i;
}
}
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(context, proxyActivity);
proxyIntent.setComponent(componentName);
proxyIntent.putExtra(DLConstants.EXTRA_CLASS, intent.getStringExtra(DLConstants.EXTRA_CLASS));
proxyIntent.putExtra(DLConstants.EXTRA_PACKAGE, intent.getStringExtra(DLConstants.EXTRA_PACKAGE));
proxyIntent.putExtra("oldIntent", intent);
args[index] = proxyIntent;
}
return method.invoke(iActivityManagerObject, args);
}
有了上面的两个步骤,这个代理的Intent是可以通过ActivityManagerService检验的,因为我在清单文件中注册过
<!-- 替身Activity, 用来欺骗AMS -->
<activity android:name=".hook.ProxyActivity"></activity>
为了不启动ProxyActivity,现在我们需要找一个合适的时机,把真实的Intent换过了来,启动我们真正想启动的Activity。看过Activity的启动流程的朋友,我们都知道这个过程是由Handler发送消息来实现的,可是通过Handler处理消息的代码来看,消息的分发处理是有顺序的,下面是Handler处理消息的代码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handler处理消息的时候,首先去检查是否实现了callback接口,如果有实现的话,那么会直接执行接口方法,然后才是handleMessage方法,最后才是执行重写的handleMessage方法,我们一般大部分时候都是重写了handleMessage方法,而ActivityThread主线程用的正是重写的方法,这种方法的优先级是最低的,我们完全可以实现接口来替换掉系统Handler的处理过程.
这个Handler.Callback是一个接口,我们可以使用动态代理或者普通代理完成Hook,这里我们使用普通的静态代理方式;创建一个自定义的Callback类:
public void hookSystemHandler() {
try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
//获取主线程对象
Object activityThread = currentActivityThread.invoke(null);
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
Handler handler = (Handler) mH.get(activityThread);
Field mCallback = Handler.class.getField("mCallback");
mCallback.setAccessible(true);
//设置自己实现的CallBack
mCallback.set(handler, new ActivityThreadHandlerCallback(handler));
} catch (Exception e) {
e.printStackTrace();
}
}
自定义Callback类
public class ActivityThreadHandlerCallback implements Handler.Callback {
private Handler handler;
public ActivityThreadHandlerCallback(Handler handler) {
this.handler = handler;
}
@Override
public boolean handleMessage(Message msg) {
//代表ActivityThread mH中的launch_activity
if(msg.what == 100){
handleLaunchActivity(msg);
}
handler.handleMessage(msg);
return true;
}
private void handleLaunchActivity(Message msg) {
//ActivityClientRecord
Object obj = msg.obj;
try {
Field intentField = obj.getClass().getDeclaredField("intent");
intentField .setAccessible(true);
Intent proxyInent = (Intent) intentField.get(obj);
Intent realIntent = proxyInent.getParcelableExtra("oldIntent");
if (realIntent != null) {
proxyInent.setComponent(realIntent.getComponent());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
最后在Application中配置
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookUtils hookUtil = new HookUtils(ProxyActivity.class, this);
hookUtil.hookSystemHandler();
hookUtil.hookAms();
}
}
到这里,我们已经成功地绕过AMS,完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的过程。执行hookActivity()方法就能成功跳入.
能正确收到生命周期回调吗?虽然我们完成了『启动没有在AndroidManifest.xml中显式声明的Activity 』 但是启动的OtherActivity是否有自己的生命周期呢,我们还需要额外的处理过程吗?
需要额外处理
4.生命周期处理
把一个Activity类加载之后,怎么使插件里的Activity具有生命周期。
这里使用Activity代理模式。老套路,在宿主APK注册一个ProxyActivity(代理Activity),就是作为占坑使用。每次打开插件APK里的某一个Activity的时候,都是在宿主里使用启动ProxyActivity,然后在ProxyActivity的生命周期里方法中,调用插件中的Activity实例的生命周期方法,从而执行插件APK的业务逻辑。所以思路就来了。
第一、ProxyActivity中需要保存一个Activity实例,该实例记录着当前需要调用插件中哪个Activity的生命周期方法。
第二、ProxyActivity如何调用插件apk中Activity的所有生命周期的方法,使用反射呢?还是其他方式。
这里借用dynamic-load-apk
经过优化,改成接口的方式,将activity的生命周期方法封装一个接口,代理activity中实现一下这个接口,然后还是通过代理activity去调用插件activity实现的生命周期方法.
4.1: 接口类
public interface DLPlugin {
public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void attach(Activity proxyActivity);
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(WindowManager.LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
public boolean onCreateOptionsMenu(Menu menu);
public boolean onOptionsItemSelected(MenuItem item);
}
public interface DLAttachable {
public void attach(DLPlugin proxyActivity);
}
4.2在代理类ProxyActivity中的实现
public class ProxyActivity extends AppCompatActivity implements DLAttachable {
private DLProxyImpl impl = new DLProxyImpl(this);
protected DLPlugin mRemoteActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
impl.onCreate(getIntent());
Log.e("Charles2","代理的onCreate");
}
@Override
protected void onStart() {
if(mRemoteActivity != null){
mRemoteActivity.onStart();
}
super.onStart();
Log.e("Charles2","代理的onStart");
}
@Override
protected void onRestart() {
if(mRemoteActivity != null){
mRemoteActivity.onRestart();
}
super.onRestart();
Log.e("Charles2","代理的onRestart");
}
@Override
protected void onResume() {
if(mRemoteActivity != null){
mRemoteActivity.onResume();
}
super.onResume();
Log.e("Charles2","代理的onResume");
}
@Override
protected void onPause() {
if(mRemoteActivity != null){
mRemoteActivity.onPause();
}
super.onPause();
Log.e("Charles2","代理的onPause");
}
@Override
protected void onDestroy() {
if(mRemoteActivity != null){
mRemoteActivity.onDestroy();
}
super.onDestroy();
Log.e("Charles2","代理的onDestroy");
}
@Override
public void attach(DLPlugin proxyActivity) {
mRemoteActivity = proxyActivity;
}
}
4.3 代理实现类
public class DLProxyImpl {
private Activity mProxyActivity;
private String mPackageName;
private String mClass;
protected DLPlugin mPluginActivity;
public DLProxyImpl(Activity activity) {
mProxyActivity = activity;
}
public void onCreate(Intent intent) {
if (intent != null) {
mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
}
launchTargetActivity();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
try {
Class<?> localClass = mProxyActivity.getClassLoader().loadClass(mClass);
//Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[]{});
Object instance = localConstructor.newInstance(new Object[]{});
mPluginActivity = (DLPlugin) instance;
((DLAttachable) mProxyActivity).attach(mPluginActivity);
// attach the proxy activity and plugin package to the mPluginActivity
mPluginActivity.attach(mProxyActivity);
Bundle bundle = new Bundle();
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
public ClassLoader getClassLoader() {
return PluginManager.getInstance(mProxyActivity).createDexClassLoader("");
}
}
4.4. 最后看看我们的目标Activity-->otherActivity
public class OtherActivity extends DLBasePluginActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("Charles2", "plugin--onCreate");
that.setContentView(R.layout.activity_other);
}
@Override
public void onResume() {
super.onResume();
Log.e("Charles2", "plugin--onResume");
}
@Override
public void onStart() {
super.onStart();
Log.e("Charles2", "plugin--onStart");
}
@Override
public void onPause() {
super.onPause();
Log.e("Charles2", "plugin--onPause");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("Charles2", "plugin--onDestroy");
}
}
public class DLBasePluginActivity extends Activity implements DLPlugin {
/**
* 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
*/
protected Activity mProxyActivity;
/**
* 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
* 可以当作this来使用
*/
protected Activity that;
private int type = 0;
@Override
public void attach(Activity proxyActivity) {
mProxyActivity = (Activity) proxyActivity;
that = mProxyActivity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (type == 1) {
super.onCreate(savedInstanceState);
}
}
@Override
public void onStart() {
if (type == 1) {
super.onStart();
}
}
@Override
public void onRestart() {
if (type == 1) {
super.onRestart();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
@Override
public void onResume() {
if (type == 1) {
super.onResume();
}
}
@Override
public void onPause() {
if (type == 1) {
super.onPause();
}
}
@Override
public void onStop() {
if (type == 1) {
super.onStop();
}
}
@Override
public void onDestroy() {
if (type == 1) {
super.onDestroy();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
@Override
public void onNewIntent(Intent intent) {
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
}
@Override
public void onBackPressed() {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return false;
}
}
到这里全部就全部结束了。这里我们来看看效果,是否会走我们的生命周期.
image.png
demo地址:https://github.com/15189611/pluginDemo
参考:https://github.com/singwhatiwanna/dynamic-load-apk
网友评论