HOOK技术一-HOOK技术初探
HOOK技术二-未注册Activity的启动
HOOK技术三-插件Activity启动前提分析
HOOK技术四-插件中Activity启动实战
HOOK技术五-使用LoadedApk式插件化的理论分析
HOOK技术六-LoadedApk式插件化代码实现
HOOK技术七-版本适配及总结
因为不同版本之间的源码差距,所以再Hook的时候, 往往会遇到版本兼容问题。
/**
* TODO 注意:此方法 适用于 21以下的版本 以及 21_22_23_24_25 26_27_28 等系统版本
* @param mContext
* @throws ClassNotFoundException
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws InvocationTargetException
*/
public static void mHookAMS(final Context mContext) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 公共区域
Object mIActivityManagerSingleton = null; // TODO 注意:公共区域 适用于 21以下的版本 以及 21_22_23_24_25 26_27_28 等系统版本
Object mIActivityManager = null; // TODO 注意:公共区域 适用于 21以下的版本 以及 21_22_23_24_25 26_27_28 等系统版本
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
// @3 的获取 系统的 IActivityManager.aidl
Class mActivityManagerClass = Class.forName("android.app.ActivityManager");
mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null);
// @1 的获取 IActivityManagerSingleton
Field mIActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
mIActivityManagerSingletonField.setAccessible(true);
mIActivityManagerSingleton = mIActivityManagerSingletonField.get(null);
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
// @3 的获取
Class mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
mIActivityManager = getDefaultMethod.invoke(null);
// @1 的获取 gDefault
Field gDefaultField = mActivityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
mIActivityManagerSingleton = gDefaultField.get(null);
}
// @2 的获取 动态代理
Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");
final Object finalMIActivityManager = mIActivityManager;
Object mIActivityManagerProxy = Proxy.newProxyInstance(mContext.getClassLoader(),
new Class[]{mIActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
// 把LoginActivity 换成 ProxyActivity
// TODO 狸猫换太子,把不能经过检测的LoginActivity 替换 成能够经过检测的ProxyActivity
Intent proxyIntent = new Intent(mContext, ProxyActivity.class);
// 把目标的LoginActivity 取出来 携带过去
Intent target = (Intent) args[2];
proxyIntent.putExtra(Parameter.TARGET_INTENT, target);
args[2] = proxyIntent;
}
// @3
return method.invoke(finalMIActivityManager, args);
}
});
if (mIActivityManagerSingleton == null || mIActivityManagerProxy == null) {
throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理..."); // 10.0
}
Class mSingletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 把系统里面的 IActivityManager 换成 我们自己写的动态代理 【第一步】
// @1 @2
mInstanceField.set(mIActivityManagerSingleton, mIActivityManagerProxy);
}
{
/**
* TODO 注意:此方法 适用于 21以下的版本 以及 21_22_23_24_25 26_27_28 等系统版本
* @param mContext
* @throws Exception
*/
public static void mActivityThreadmHAction(Context mContext) throws Exception {
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
do_26_27_28_mHRestore();
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()){
do_21_22_23_24_25_mHRestore();
} else {
throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理...");
}
}
/**
* 高版本 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
*/
/**
* TODO 看到此方法,应该明白,就是专门给 26_27_28 系统版本 做【还原操作】的
*/
private final static void do_26_27_28_mHRestore() throws Exception {
// @1 怎么得到? 看源码..
Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(mActivityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// @1 @2
// 把系统中的Handler.Callback实现 替换成 我们自己写的Custom_26_27_28_Callback,主动权才在我们手上
mCallbackField.set(mH, new Custom_26_27_28_Callback());
}
// @2
private static class Custom_26_27_28_Callback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
if (Parameter.EXECUTE_TRANSACTION == msg.what) {
/*final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);*/
Object mClientTransaction = msg.obj;
try {
// @1
// Field mActivityCallbacksField = mClientTransaction.getClass().getDeclaredField("mActivityCallbacks");
Class> mClientTransactionClass = Class.forName("android.app.servertransaction.ClientTransaction");
Field mActivityCallbacksField = mClientTransactionClass.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
// List mActivityCallbacks;
List mActivityCallbacks = (List) mActivityCallbacksField.get(mClientTransaction);
// TODO 需要判断
if (mActivityCallbacks.size() == 0) {
return false;
}
Object mLaunchActivityItem = mActivityCallbacks.get(0);
Class mLaunchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem");
// TODO 需要判断
if (!mLaunchActivityItemClass.isInstance(mLaunchActivityItem)) {
return false;
}
Field mIntentField = mLaunchActivityItemClass.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
// @2 需要拿到真实的Intent
Intent proxyIntent = (Intent) mIntentField.get(mLaunchActivityItem);
Log.d("hook", "proxyIntent:" + proxyIntent);
Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
if (targetIntent != null) {
mIntentField.set(mLaunchActivityItem, targetIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
// >>>>>>>>>>>>>>>>>>>>>>>> 下面是 就是专门给 21_22_23_24_25 系统版本 做【还原操作】的 低版本
/**
* TODO 看到此方法,应该明白,就是专门给 21_22_23_24_25 系统版本 做【还原操作】的
*/
private final static void do_21_22_23_24_25_mHRestore() throws Exception {
Class> mActivityThreadClass = Class.forName("android.app.ActivityThread");
Field msCurrentActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
msCurrentActivityThreadField.setAccessible(true);
Object mActivityThread = msCurrentActivityThreadField.get(null);
// 如何获取@1
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(mActivityThread);
Field mCallbackFile = Handler.class.getDeclaredField("mCallback");
mCallbackFile.setAccessible(true);
// @1 @2
mCallbackFile.set(mH, new Custom_21_22_23_24_25_Callback());
}
// @2
private static final class Custom_21_22_23_24_25_Callback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
if (Parameter.LAUNCH_ACTIVITY == msg.what) {
Object mActivityClientRecord = msg.obj;
try {
Field intentField = mActivityClientRecord.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(mActivityClientRecord);
// TODO 还原操作,要把之前的LoginActivity给换回来
Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
if (targetIntent != null) {
// :这种方式比方式要好一些哦,但是需要注意:必须是 setComponent的方式才使用哦
// proxyIntent.setComponent(targetIntent.getComponent());
// 反射的方式
intentField.set(mActivityClientRecord, targetIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
}
{
// private DexClassLoader dexClassLoader;
private Resources resources = null;
private AssetManager assetManager = null;
/**
* 此方法的主要目的是,宿主和插件的 DexElement融合
*/
public void mainPluginFuse(Context mContext) throws Exception {
// TODO 宿主的dexElements
// Object mainDexElements = getDexElements(mContext.getClassLoader());
Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(mContext.getClassLoader());
Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object mainDexElements = dexElementsField.get(mDexPathList);
// TODO 插件的dexElements
/*File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (file.exists() == false) {
return;
}*/
File fileDir = mContext.getDir("pDir", Context.MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(MyApplication.pluginPath, fileDir.getAbsolutePath(),null, mContext.getClassLoader());
// Object pluginDexElements = getDexElements(dexClassLoader);
Class mBaseDexClassLoaderClass2 = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField2 = mBaseDexClassLoaderClass2.getDeclaredField("pathList");
pathListField2.setAccessible(true);
Object mDexPathList2 = pathListField2.get(dexClassLoader);
Field dexElementsField2 = mDexPathList2.getClass().getDeclaredField("dexElements");
dexElementsField2.setAccessible(true);
Object pluginDexElements = dexElementsField2.get(mDexPathList2);
// TODO 创造出新的 newDexElements
int mainLen = Array.getLength(mainDexElements);
int pluginLen = Array.getLength(pluginDexElements);
int newDexElementsLength = (mainLen + pluginLen);
Object newDexElements = Array.newInstance(mainDexElements.getClass().getComponentType(), newDexElementsLength);
// 进行融合
for (int i = 0; i < newDexElementsLength; i++) {
// 先融合宿主
if (i < mainLen) {
Array.set(newDexElements, i, Array.get(mainDexElements, i));
} else { // 在融合插件,为什么要i - mainLen,是为了保证取出pluginDexElements,是从0 开始取的
Array.set(newDexElements, i, Array.get(pluginDexElements, i - mainLen));
}
}
// 把新的替换到宿主中去
dexElementsField.set(mDexPathList, newDexElements);
loadResource(mContext);
}
// todo 其实 宿主的dexElements 和 插件的dexElements 代码类似,所以可以抽取成方法的
// todo 我在这里就 不抽取方法了,为了让更好的理解
// @Deprecated
private Object getDexElements(ClassLoader classLoader) throws Exception {
Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(classLoader);
Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
return dexElementsField.get(mDexPathList);
}
/*public ClassLoader getClassLoader() {
return dexClassLoader;
}*/
/**
* 拥有加载资源的能力
* @param mContext
* @throws Exception
*/
private void loadResource(Context mContext) throws Exception {
Resources r = mContext.getResources();
assetManager = AssetManager.class.newInstance();
Method addAssetpathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetpathMethod.setAccessible(true);
// File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
addAssetpathMethod.invoke(assetManager, MyApplication.pluginPath);
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
}
public Resources getResources() {
return resources;
}
/**
* 只需要 让插件去那 宿主的getResources 就好了,不需要让插件去那AssetManager了,为什么呢?
* 答:因为宿主和插件进行了融合,插件只要拿到宿主中的Resources,就等于拿到了 AssetManager了,因为AssetManager属于单利的哦
* @return
*/
public AssetManager getAssetManager() {
return assetManager;
}
}
写Hook的时候,从结果往过程推导比较容易,不然从过程开始看会觉得莫名其妙,实际上过程中的变量都是为了最后实现目的。
1.占位插件化 Activity - ProxyActivity -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
2.占位插件化 Service - ProxyService -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
3.占位插件化 动态广播 - ProxyReceiver -->插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
4.占位插件化 静态广播 – 分析系统是如何解析APK文件的,源码的阅读,阅读源码的思路,PMS入手-(模仿系统是如何解析,我们就怎么解析)(难点)
稳定,但是插件化开发很痛苦 比如:开源中的框架 插件化框架 DL
5.Hook从入门 到 熟练 --(1.替换,2.被替换的 动态代理/接口设置) --> Hook系统源码 – (Hook1)AMS检测是否注册 — (Hook2)换回来
6.安卓的类加载 -->PathClassLoader(加载运行App中的class),DexClassLoader(apk zip), DexPathList Element,插件Element和宿主Element
7.真正的融合–>就可以加载插件里面的class, 插件里面的Layout怎么去加载呢,AseetManager Resources (难点)
Hook式插件化框架 --> 融为一体,Hook方式: 不用考虑宿主的环境,不稳定==兼容性, 360开源框架 Hook方式实现的
8.LoadedApk插件化-- ActivityThread LAUNCH_ACTIVITY源码切入点,ClassLoader–>宿主 --> LoadedApk.ClassLoader --> 宿主class
9.自定义LoadedApk.自定义ClassLoader —> 插件的class, mPackages – size=2 , 没法绕过PMS检测,检测插件包名是否安装了
10.开关 切换 宿主 插件, 再次运行会报错,是因为 PMS检测, Hook PMS,绕过了pi == null的情况
不稳定且有兼容性问题。
所以版本适配中也是采用的融合的方式做插件化的。