文章目录
启动插件Activity
1 动态加载apk
Activity初始化时,使用DexPathLoader动态加载apk
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chajian);
initPermission();
init();
}
//在attachBaseContext()中调用会有问题
private void init(){
new Thread(){
@Override
public void run() {
super.run();
String cachePath = TestChaJianActivity.this.getCacheDir().getAbsolutePath();
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-chajian.apk";
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cachePath, cachePath, getClassLoader());
HookHelper.inject(dexClassLoader);
try {
//注意这里hook的源码是针对8.0系统的,测试时候要用8.0手机
AMSHookHelper.hookActivityManagerNative();
AMSHookHelper.hookActivityThreadHandler();
} catch (Exception e) {
Log.e("===TAG===", "hook failed message = " + e.getMessage());
e.printStackTrace();
}
}
}.start();
}
public static void inject(DexClassLoader loader) {
try {
//1.1 获取PathClassLoader中的pathList
Object pathClassLoader = TestApplication.getContext().getClassLoader();
Field pathLoaderPathList = Class.forName("dalvik.system.BaseDexClassLoader").getDeclaredField("pathList");
pathLoaderPathList.setAccessible(true);
Object mainPathList = pathLoaderPathList.get(pathClassLoader);
//1.2 获取DexClassLoader中的pathList
Field dexLoaderPathList = Class.forName("dalvik.system.BaseDexClassLoader").getDeclaredField("pathList");
dexLoaderPathList.setAccessible(true);
Object chajianPathList = dexLoaderPathList.get(loader);
//2.1 获取PathClassLoader#PathList中的dexElements
Field mainElementsField = mainPathList.getClass().getDeclaredField("dexElements");
mainElementsField.setAccessible(true);
Object mainDexElements = mainElementsField.get(mainPathList);
//2.2 获取DexClassLoader#PathList中的dexElements
Field chajianElementsField = chajianPathList.getClass().getDeclaredField("dexElements");
chajianElementsField.setAccessible(true);
Object chajianDexElements = chajianElementsField.get(chajianPathList);
//3 将宿主中的Element[]和插件中的Element[]整合到一起
Object dexElements = combineArray(mainDexElements, chajianDexElements);
//4 重新赋值给宿主中PathClassLoader#DexPathList#dexElements
mainElementsField.set(mainPathList, dexElements);
} catch (Exception e) {
Log.e("Main", "inject message = " + e.getMessage());
e.printStackTrace();
}
}
获取DexClassLoader#DexPathList中的Elements,并赋值给PathDexClassLoader#DexPathList中的Elements中
private static Object combineArray(Object suZhuDexElements, Object chaJianDexElements) {
//1 获取DexPathList.Element.class
Class<?> localClass = suZhuDexElements.getClass().getComponentType();
//2 获取宿主Element[] dexElements数组长度
int i = Array.getLength(suZhuDexElements);
//3 获取插件Element[] dexElements 数组长度
int j = Array.getLength(chaJianDexElements) + i;
//4 创建Element[] 长度为i + j的和
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; k++) {
if (k < i) {
Array.set(result, k, Array.get(suZhuDexElements, k));
} else {
Array.set(result, k, Array.get(chaJianDexElements, k - i));
}
}
return result;
}
2 Hook AMS
将指定包名的activity替换为假的activity,并将真实启动的activity参数填充到extra参数中
public static void hookActivityManagerNative() throws Exception {
//1 获取ActivityManager中的IActivityManagerSingleton变量
Class<?> activityManager = Class.forName("android.app.ActivityManager");
Field gDefaultFild = activityManager.getDeclaredField("IActivityManagerSingleton");
gDefaultFild.setAccessible(true);
Object gDefault = gDefaultFild.get(null);
//2 获取IActivityManagerSingleton中的mInstance
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object rawIActivityManager = mInstanceField.get(gDefault);
//3 mInstance实际上是ams对象,现将proxy作为mInstance的代理
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[] {iActivityManagerInterface},
new IActivityManagerHandler(rawIActivityManager));
mInstanceField.set(gDefault, proxy);
}
hook了AMS的方法后,动态代理startActivity()方法,并将真正要启动的Activity放到intent对象的extra参数中,用本地假的Activity类来创建intent来骗过AMS的检查。
3 动态代理startActivity()
此过程只要是将要启动的插件中的Activity替换为已经在AndroidMenifest中注册的空壳Activity,并将实际的插件中的Activity信息作为参数赋值给intent
public class IActivityManagerHandler implements InvocationHandler {
Object mBase;
public IActivityManagerHandler(Object base){
mBase = base;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("===TAG===", "IActivityManagerHandler invoke method = " + method.getName());
if ("startActivity".equals(method.getName())) {
Intent raw;
int index = 0;
//1 找到intent参数
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
//2 保存原有的intent
raw = (Intent) args[index];
//3 创建新的intent
Intent newIntent = new Intent();
//4 替换成假的activity以及包名
String stubPackage = TestApplication.getContext().getPackageName();
ComponentName componentName = new ComponentName(stubPackage, FeignActivity.class.getName());
newIntent.setComponent(componentName);
//5 将原有的intent作为额外参数
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT,raw);
//6 将新的intent赋值给args
args[index] = newIntent;
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}
4 Hook ActivityThread#Handler
public static void hookActivityThreadHandler() throws Exception{
//1 获取ActivityThread中的静态方法currentActivityThread()
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method method = activityThreadClass.getDeclaredMethod("currentActivityThread");
method.setAccessible(true);
//2 反射调用currentActivityThread()方法来获取ActivityThread对象
Object activityThread = method.invoke(null);
// 3 获取ActivityThread中的Handler对象
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(activityThread);
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, new ActivityThreadHandlerCallBack(mH));
}
5 动态代理 handleMessage()
此过程主要是将intent中自定义参数传递的真实Activity信息取出来,然后启动真实的Activity
public class ActivityThreadHandlerCallBack implements Handler.Callback {
Handler mBase;
public ActivityThreadHandlerCallBack(Handler base){
mBase = base;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
Log.i("===TAG===", "ActivityThreadHandlerCallBack handleMessage msg.what = " + msg.what);
switch (msg.what) {
//对应ActivityThread中的 LAUNCH_ACTIVITY
case 100:
handleLaunchActivity(msg);
break;
}
mBase.handleMessage(msg);
return true;
}
private void handleLaunchActivity(Message msg){
Object obj = msg.obj;
try {
//1 obj实际上为ActivityRecord对象 首先获取ActivityRecord中的intent
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent raw = (Intent) intent.get(obj);
Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
raw.setComponent(target.getComponent());
} catch (Exception e) {
Log.i("===TAG===", "hook handleLaunchActivity failed e = " + e.getMessage());
e.printStackTrace();
}
}
}
以下是另一个apk中待启动的Activity
public class TestChaJianActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout frameLayout = new FrameLayout(this);
Button button = new Button(this);
button.setText("我是插件Activity");
button.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
frameLayout.addView(button);
setContentView(frameLayout);
}
}
插件中加载资源
1 创建用于加载资源的Resources对象
public class TestApplication extends Application {
private static Context mContext;
private AssetManager mAssetManager;
private Resources mResource;
private Resources.Theme mTheme;
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
mContext = base;
initResources();
}
private void initResources() {
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-chajian.apk";
try {
//1 创建AssetManager
mAssetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = mAssetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
//2 调用AssetManager#addAssetPath(Stirng path)方法 并传入插件资源路径
addAssetPathMethod.invoke(mAssetManager, apkPath);
//3 调用调用AssetManager#ensureStringBlocks()方法
Method ensureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
ensureStringBlocks.setAccessible(true);
ensureStringBlocks.invoke(mAssetManager);
//4 根据AssetManager创建Resources
Resources supResource = getResources();
mResource = new Resources(mAssetManager, supResource.getDisplayMetrics(), supResource.getConfiguration());
//5 创建Resource.Theme
mTheme = mResource.newTheme();
mTheme.setTo(super.getTheme());
} catch (Exception e) {
Log.e("===TAG===", "initResources failed message = " + e.getMessage());
e.printStackTrace();
}
}
public static Context getContext(){
return mContext;
}
@Override
public Resources getResources() {
return mResource == null ? super.getResources() : mResource;
}
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources.Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
}
2 在插件Activity中使用宿主中的Resources对象
public class TestChaJianActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cha_jian);
}
@Override
public Resources getResources() {
if (getApplication() != null && getApplication().getResources() != null) {
return getApplication().getResources();
}
return super.getResources();
}
@Override
public AssetManager getAssets() {
if (getApplication() != null && getApplication().getAssets() != null) {
return getApplication().getAssets();
}
return super.getAssets();
}
@Override
public Resources.Theme getTheme() {
if (getApplication() != null && getApplication().getTheme() != null) {
return getApplication().getTheme();
}
return super.getTheme();
}
}