我們在上篇文章中主要講了加載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()方法就可以。
看看執行效果
可以看到我們已經進行攔截了,并且打印我們想要的日志.
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;
}
}
到這里全部就全部結束了。這里我們來看看效果,是否會走我們的生命周期.
demo地址:https://github.com/15189611/pluginDemo
參考:https://github.com/singwhatiwanna/dynamic-load-apk