引子
Hook技术在android开发领域算是一项黑科技,那么一个新的概念进入视线,我们最关心的3个问题就是,它是什么,有什么用,怎么用
本系列将由浅入深 手把手讲解这三大问题。
本文是第三篇,
高级篇。
前面两篇Hook博文,写了两个demo,一个是hook入门,一个是略微复杂的Activity启动流程的hook。
那么玩点更高端的吧, 正常开发中,所有
Activity
都要在AndroidManifest.xml
中进行注册,才可以正常跳转,通过hook
,可以绕过系统对activity
注册的检测,即使不注册,也可以正常跳转。
鸣谢
感谢大神的博文 https://www.jianshu.com/p/eb4121b037e2
正文大纲
一.整体思路
二.源码索引
三.hook核心代码
四.最终效果
Demo地址:
https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste
正文
提示:本文所有源码索引图,都基于 SDK28-android9.0
系统.
一.整体思路
在之前Activity启动流程的hook的Demo里,我进行了
Activity
流程的hook
,最终采用的方案,是Hook
了AMS
,实现了全局的startActivity
动作的劫持
. 现在就从这个AMS的hook
为起点,来实现无清单启动Activity
.
在 Activity
启动流程的 hook
的Demo里,最后实现的效果是,每次跳转 Activity
,都能看到这个日志:
那么,我们既然侦测到了 startActivity
这个方法的调用,那么自然就可以拿到里面的实参,比如, Intent
。Intent
是跳转意图,从哪里来,跳到哪里去的信息,都包含在 Intent
里面.而, manifestActivity
的检测,也是要根据 Intent
里面的信息来的.所以,要骗过系统,要假装我们跳的 Activity
是已经注册过的,那么只需要将 Intent
里面的信息换成 已经在 manifest
中注册的某个 Activity
就可以了( 这里可能就有人像抬杠了,你怎么知道manifest里面一定有注册Activity....如果一个Activity都没有,你的app是怎么启动的呢,至少得有一个LauncherActivity吧--!
).
确定思路: 1.在AMS的hook函数中,将 真实的Intent中的信息,替换成manifest中已有的Activity信息. 骗过系统的检测机制。2.虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到
"假"
的Activity
,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将 真实的Activity信息,还原回去, 跳到原本该去的Activity
.对应的核心代码,其实也就两句话:
二.源码索引
下图大致画出了:从 Activity.startActivity
动作开始,到最终 跳转动作的最终执行者
全过程.
下面开始看源码,从 Activity.startActivity
开始:
这里开始分支:if(mParent==null)
,但是两个分支最终执行如下:
很显然,两者都是同样的调用过程:
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(....);
mMainThread.sendActivityResult(...);
第一句,
execStartActivity
是 对一些参数的合法性校验,如果不合法,那就会直接抛出异常,比如之前的第二句,
sendActivityResult
才是真正的跳转动作执行者
先进入第一句Instrumentation.ActivityResultar=mInstrumentation.execStartActivity
看看,既然是合法性校验,且看他是如何校验的。
这是
Instrumentation
的execStartActivity
方法结论:它是通过AMS去校验的,AMS startActivity会返回一个int数值,随后,
checkStartActivityResult
方法会根据这个int
值,抛出响应的异常,或者什么都不做.
再进入第二句 mMainThread.sendActivityResult
看真正的跳转动作是如何执行的:ps:这里其实有个诀窍,既然我们的终极目标是要骗过系统的Activity Intent检测,那么,跟着Intent这个变量,就不会偏离方向.
既然intent被封装到了 ClientTransaction
,交给了 mAppThread
,那么继续:
前方有坑,请注意:
androidStudio
里并不能直接跳转,所以要手动,找到下图中的方法,这个ClientTransactionHandler
是ActivityThread
的父类.上图中,调用了
sendMessage(int,Object)
,在ActivityThread
中找到这个方法的实现:找它的最终实现:
找到另一个关键点:
mH
,H
类的定义:(太长了,我就不完整截图了,留下关键的信息)
final H mH = new H();
class H extends Handler {
...
public static final int EXECUTE_TRANSACTION = 159;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
...
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
}
很明显,他就是一个 Handler
的普通子类,定义了主线程 ActivityThread
中可能发生的各种事件。PS: 这里,我留下了 caseEXECUTE_TRANSACTION:
分支,是因为,之前 ClientTransactionHandler
抽象类里面, sendMessage(ActivityThread.H.EXECUTE_TRANSACTION,transaction);
,就是用的这个 EXECUTE_TRANSACTION
常量。终于找到了 startActivity
的最终执行代码!
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
Ok,就到这里了.(事实上,我本来还想往下追查, Intent
被封装到 ClientTransaction
之后,又被得到了什么样的处理,最后发现居然查到了一个源码中都不存在的类,我表示看不懂了,就到这里吧,不影响我们 hook
)
三.hook核心代码
还记得我们的整体思路么?
1.在AMS的hook函数中,将 真实的Intent中的信息,替换成manifest中已有的Activity信息. 骗过系统的检测机制。2.虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到
"假"
的Activity
,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将 真实的Activity信息,还原回去, 跳到原本该去的Activity
.
说通俗一点就是,第一,伪造一个 Intent
,骗过 ActivityManifest
检测。第二,真正要跳转之前,把原始的Intent还原回去.开始撸代码,大量 反射代码
即将到来,注释应该很详尽了,特别注意:看反射代码要对照源代码来看,不然很容易走神
:
伪造intent,骗过Activity Manifest检测
这里,请对照:
ActivityManager.java的4125-4137行
hook核心代码如下
/**
* 这里对AMS进行hook
*
* @param context
*/
private static void hookAMS(Context context) {
try {
Class> ActivityManagerClz;
final Object IActivityManagerObj;//这个就是AMS实例
Method getServiceMethod;
Field IActivityManagerSingletonField;
if (ifSdkOverIncluding26()) {//26,27,28的ams获取方式是通过ActivityManager.getService()
ActivityManagerClz = Class.forName("android.app.ActivityManager");
getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");
IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");//单例类成员的名字也不一样
} else {//25往下,是ActivityManagerNative.getDefault()
ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");
IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");//单例类成员的名字也不一样
}
IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
// 2.现在创建我们的AMS实例
// 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
Class> IActivityManagerClz = Class.forName("android.app.IActivityManager");
// 构建代理类需要两个东西用于创建伪装的Intent
String packageName = Util.getPMName(context);
String clz = Util.getHostClzName(context, packageName);
Object proxyIActivityManager =
Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManagerClz},
new ProxyInvocation(IActivityManagerObj, packageName, clz));
//3.拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
//偷梁换柱
IActivityManagerSingletonField.setAccessible(true);
Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
Class> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个Singleton的class
Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);
} catch (Exception e) {
e.printStackTrace();
}
}
private static final String ORI_INTENT_TAG = "origin_intent";
/**
* 把InvocationHandler的实现类提取出来,因为这里包含了核心技术逻辑,最好独立,方便维护
*/
private static class ProxyInvocation implements InvocationHandler {
Object amsObj;
String packageName;//这两个String是用来构建Intent的ComponentName的
String clz;
public ProxyInvocation(Object amsInstance, String packageName, String clz) {
this.amsObj = amsInstance;
this.packageName = packageName;
this.clz = clz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
if (method.getName().equals("startActivity")) {
Log.d("GlobalActivityHook", "全局hook 到了 startActivity");
Intent currentRealIntent = null;//侦测到startActivity动作之后,把intent存到这里
int intentIndex = -1;
//遍历参数,找到Intent
for (int i = 0; i < args.length; i++) {
Object temp = args[i];
if (temp instanceof Intent) {
currentRealIntent = (Intent) temp;//这是原始的Intent,存起来,后面用得着
intentIndex = i;
break;
}
}
//构造自己的Intent,这是为了绕过manifest检测(这个Intent是伪造的!只是为了让通过manifest检测)
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新创建一个intent
proxyIntent.setComponent(componentName);
proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//将真正的proxy作为参数,存放到extras中,后面会拿出来还原
args[intentIndex] = proxyIntent;//替换掉intent
//哟,已经成功绕过了manifest清单检测. 那么,我不能老让它跳到 伪装的Activity啊,我要给他还原回去,那么,去哪里还原呢?
//继续看源码。
}
return method.invoke(amsObj, args);
}
}
真正要跳转之前,把原始的Intent还原回去
PS: 这里
hook
mh
的手段,并不是针对mh
本身做代理,而是对mh的mCallback成员. 因为:
public class Handler {
...
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
}
handler
的dispatchMessage
逻辑,是 先执行mCallback
的handlerMessage
,然后根据它的返回值决定要不要执行handler
本身的handlerMessage
函数. 我们的目的是还原Intent
,并不需要对ActivityThread原本的mH做出逻辑修改,所以,hook mCallback
,加入还原Intent
的逻辑,即可.这次hook,对照的源码是(源码太长了,我就直接截取了ActivityThread里面一些关键的代码):
下面是 HookMh
的完整代码:
//下面进行ActivityThread的mH的hook,这是针对SDK28做的hook
private static void hookActivityThread_mH_After28() {
try {
//确定hook点,ActivityThread类的mh
// 先拿到ActivityThread
Class> ActivityThreadClz = Class.forName("android.app.ActivityThread");
Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object ActivityThreadObj = field.get(null);//OK,拿到主线程实例
//现在拿mH
Field mHField = ActivityThreadClz.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,当前的mH拿到了
//再拿它的mCallback成员
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
//2.现在,造一个代理mH,
// 他就是一个简单的Handler子类
ProxyHandlerCallback proxyMHCallback = new ProxyHandlerCallback();//错,不需要重写全部mH,只需要对mH的callback进行重新定义
//3.替换
//将Handler的mCallback成员,替换成创建出来的代理HandlerCallback
mCallbackField.set(mHObj, proxyMHCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
private static class ProxyHandlerCallback implements Handler.Callback {
private int EXECUTE_TRANSACTION = 159;//这个值,是android.app.ActivityThread的内部类H 中定义的常量EXECUTE_TRANSACTION
@Override
public boolean handleMessage(Message msg) {
boolean result = false;//返回值,请看Handler的源码,dispatchMessage就会懂了
//Handler的dispatchMessage有3个callback优先级,首先是msg自带的callback,其次是Handler的成员mCallback,最后才是Handler类自身的handlerMessage方法,
//它成员mCallback.handleMessage的返回值为true,则不会继续往下执行 Handler.handlerMessage
//我们这里只是要hook,插入逻辑,所以必须返回false,让Handler原本的handlerMessage能够执行.
if (msg.what == EXECUTE_TRANSACTION) {//这是跳转的时候,要对intent进行还原
try {
//先把相关@hide的类都建好
Class> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
Class> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");
Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
mActivityCallbacksField.setAccessible(true);
//类型判定,好习惯
if (!ClientTransactionClz.isInstance(msg.obj)) return true;
Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根据源码,在这个分支里面,msg.obj就是 ClientTransaction类型,所以,直接用
//拿到了ClientTransaction的List mActivityCallbacks;
List list = (List) mActivityCallbacksObj;
if (list.size() == 0) return true;
Object LaunchActivityItemObj = list.get(0);//所以这里直接就拿到第一个就好了
if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
//这里必须判定 LaunchActivityItemClz,
// 因为 最初的ActivityResultItem传进去之后都被转化成了这LaunchActivityItemClz的实例
Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
Intent oriIntent = (Intent) mIntent.getExtras().get(ORI_INTENT_TAG);
//那么现在有了最原始的intent,应该怎么处理呢?
Log.d("1", "2");
mIntentField.set(LaunchActivityItemObj, oriIntent);
return result;
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
}
PS:这里有个坑(请看上面
if(!LaunchActivityItemClz.isInstance(LaunchActivityItemObj))returntrue;
, 我为什么要加这个判断?因为,我通过debug
,发现,从mH
里面的msg.what
得到的ClientTransaction
,它有这么一个成员ListmActivityCallbacks;
注意看,从list
里面拿到的ClientTransactionItem
的实际类型是:LaunchActivityItem
. ) 之前我索引源码的时候,追查Intent的去向,只知道它最后被封装成了一个ClientTransaction
但是,最后我从
mH
的switchcaseEXECUTE_TRANSACTION
分支,去debug
(因为无法继续往下查源码)的时候,发现 原本塞进去的
ActivityResultItem
的list
,居然变成了LaunchActivityItem
的list
,而我居然查了半天,查不到是在源码何处发生的变化.而 LaunchActivityItem 和 ActivityResultItem 他们两个都是ClientTransaction的子类
public class LaunchActivityItem extends ClientTransactionItem
public class ActivityResultItem extends ClientTransactionItem
emmmm...也是很尴尬。=_ =!
不过,最后能够确定,从
mH
的switchcaseEXECUTE_TRANSACTION
分支得到的transaction
,就是包含了Intent的包装对象,所以只需要解析这个对象,就可以拿到intent,进行还原.OK,大功告成,安装好 android 9.0 SDK 28的模拟器,启动起来,运行程序,看看能不能无清单跳转:
结果,脸一黑:报错!*
一份大礼:
2019-02-27 18:20:12.287 28253-28253/study.hank.com.activityhookdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: study.hank.com.activityhookdemo, PID: 28253
java.lang.RuntimeException: Unable to start activity ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at study.hank.com.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
提取关键信息:
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{study.hank.com.activityhookdemo/study.hank.com.activityhookdemo.methodA.Main2Activity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
居然找不到包?问题出在:NavUtils.getParentActivityName
>
还是被谷歌摆了一道,查原因啊,进去
NavUtils.getParentActivityName()
去看看:
看来就是这里报的错,继续:
找到可疑点:
可能就是这里抛出的异常,继续:
然而,它是一个接口,那么就找它的实现类(注意,如果这个接口涉及到隐藏@hide的类,你用ctrl+T是不能找到的,不过也有办法,回到NavUtil.java):
哦,原来
pm
对象是来自context
,既然提到了context
这个抽象类,它的很多抽象方法的实现都在ContextImpl
,手动进入ContextImpl
:找这个方法:这个
pm
对象原来是来自ActivityThread
,然后进行了一次封装,最后返回出去的是一个ApplicationPackageManager
对象.那就进入主线程咯.
看看
IPackageManager
的内容:它是AIDL
动态生成的接口,用androidStudio
是看不到接口内容的,只能到源码官网,查到的接口如下:
interface IPackageManager {
...
ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}
Ok,看到
IBinder
,就知道应该无法继续往下追查了,已经跨进程了. 前面提到了,从主线程拿到的pm
,被封装成了ApplicationPackageManager
,那么,进入它里面去找:getActivityInfo
方法:原来异常是这里抛出的,当
mPm.getActivityInfo
为空的时候,才会抛出. OK,就查到这里,得出结论: 源码,其实对Activity
的合法性进行了两次检测,一次是在AMS
,一次是在这里的PMS
,前面的AMS
,我们用一个已有的Activity
伪装了一下,通过了验证,那么这里的PMS
,我们也可以采用同样的方式. 注:上图的参数ComponentNameclassName
,其实,他就是!Intent
的ComponentName
成员:懂了吧··这里对intent又进行了一次检查,检查的就是这个
ComponentName
.
接下来用同样的方式对 PMS
的检测进行 hook
,让它不再报异常. 此次 hook
的参照的源码是:
hook
核心代码如下(对 sPackageManager
进行代理替换,让代理类检查的永远是合法的 Activity
):
private static void hookPMAfter28(Context context) throws ClassNotFoundException,
NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
InvocationTargetException {
String pmName = Util.getPMName(context);
String hostClzName = Util.getHostClzName(context, pmName);
Class> forName = Class.forName("android.app.ActivityThread");//PM居然是来自ActivityThread
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThread = field.get(null);
Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
Object iPackageManager = getPackageManager.invoke(activityThread);
String packageName = Util.getPMName(context);
PMSInvocationHandler handler = new PMSInvocationHandler(iPackageManager, packageName, hostClzName);
Class> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
Class>[]{iPackageManagerIntercept}, handler);
// 获取 sPackageManager 属性
Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
iPackageManagerField.setAccessible(true);
iPackageManagerField.set(activityThread, proxy);
}
static class PMSInvocationHandler implements InvocationHandler {
private Object base;
private String packageName;
private String hostClzName;
public PMSInvocationHandler(Object base, String packageName, String hostClzName) {
this.packageName = packageName;
this.base = base;
this.hostClzName = hostClzName;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getActivityInfo")) {
ComponentName componentName = new ComponentName(packageName, hostClzName);
return method.invoke(base, componentName, PackageManager.GET_META_DATA, 0);//破费,一定是这样
}
return method.invoke(base, args);
}
}
四.最终效果
ok,见证奇迹的时候到了,准备好
SDK28-android9.0
虚拟机,运行demo:上图中有3个Activity跳转,但是我们看demo的清单文件,只有一个
launchActivity
:
结语
往期hook技术文章回顾:Hook技术入门篇Hook——Activity启动流程(1)Hook——Activity启动流程(2)又是历时3天,无清单注册,启动
Activity
也完成。结果是美好的,然而,过程,是纠结的,但是也是值得的. 完成了3篇博文,自己对Hook
技术也有了更深层次的认识:所谓hook
,套路是简单的,就是:1.找到hook点(比如上面,3次hook,一次是系统的AMS,一次是 ActivityThread的mH,还有一次是,ActivityThread的sPackageManager,注意,这里的sPackageManager的hook,我只针对了SDK28-9.0设备进行了hook,在其他版本的设备上运行可能会出现其他问题,比如,Intent中的参数传递不正常等.)
2.用合适的方式创建代理对象,通常 要hook一个系统类,就用继承的方式,重写某方法。hook一个系统接口的实现类,那就用JDK的Proxy动态代理
3.最后用代理对象,反射set,替换被hook的对象.
*套路并不难,掌握好
反射
,以及代理模式
,就行了. 真正难的是哪里? 是 源码的阅读能力,还有写出兼容性Hook核心代码的能力!androidSDK
有很多版本迭代,现在最新的是SDK28
,我们要保证我们的hook代码能够兼容所有的系统版本,就需要大量阅读源码,确保万无一失,比如上面,如果不是在SDK28-android9.0
的模拟器上运行发现报异常,我根本就不会去做最后一次的hook.
我知道你 “在看”