android 全局hook_【Hook】实现无清单启动Activity

09822aa6a9210bffa2c20ea20b872286.png

引子

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,最终采用的方案,是 HookAMS,实现了全局的 startActivity动作的 劫持. 现在就从这个AMS的 hook为起点,来实现 无清单启动Activity.

Activity启动流程的 hook的Demo里,最后实现的效果是,每次跳转 Activity,都能看到这个日志:

f62244ea41092fee87575ce2902bc60e.png

那么,我们既然侦测到了 startActivity这个方法的调用,那么自然就可以拿到里面的实参,比如, IntentIntent是跳转意图,从哪里来,跳到哪里去的信息,都包含在 Intent里面.而, manifestActivity的检测,也是要根据 Intent里面的信息来的.所以,要骗过系统,要假装我们跳的 Activity是已经注册过的,那么只需要将 Intent里面的信息换成 已经在 manifest中注册的某个 Activity就可以了( 这里可能就有人像抬杠了,你怎么知道manifest里面一定有注册Activity....如果一个Activity都没有,你的app是怎么启动的呢,至少得有一个LauncherActivity吧--!).

确定思路: 1.在AMS的hook函数中,将 真实的Intent中的信息,替换成manifest中已有的Activity信息. 骗过系统的检测机制。2.虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到 "假"Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将 真实的Activity信息,还原回去, 跳到原本该去的 Activity.

对应的核心代码,其实也就两句话:dd7248e4a316a5a55262af7a317688f1.png


二.源码索引

下图大致画出了:Activity.startActivity动作开始,到最终 跳转动作的最终执行者 全过程.

1f8530bc1b1ac245b29bdb5e9f5425e6.png

下面开始看源码,从 Activity.startActivity开始:

1e13bf2123f034fbfc5c99543189b5dc.png1fd320daf6b22ee0e80582419a6fbc5e.png8af9a1129cec8053f1c33a6b4c8c289c.png7ae4fd79d68625948d41e2c9f900eb5a.png

这里开始分支:if(mParent==null),但是两个分支最终执行如下:

966b89f3264cbf9fc9318673be4b704e.png9e8bea94cc7f2601122011a6f4e3ac7c.png

很显然,两者都是同样的调用过程:

Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(....);

mMainThread.sendActivityResult(...);

第一句, execStartActivity 是 对一些参数的合法性校验,如果不合法,那就会直接抛出异常,比如之前的 8517f2005828481889f1040dffb56783.png第二句, sendActivityResult才是真正的 跳转动作执行者

先进入第一句Instrumentation.ActivityResultar=mInstrumentation.execStartActivity看看,既然是合法性校验,且看他是如何校验的。

这是 InstrumentationexecStartActivity方法503d5ea647f558c85b4e278e4cb3c9df.png结论:它是通过AMS去校验的,AMS startActivity会返回一个int数值,随后, checkStartActivityResult方法会根据这个 int值,抛出响应的异常,或者什么都不做.a8812d6be90ddd959b8ad9edf6bf30fc.png

再进入第二句 mMainThread.sendActivityResult看真正的跳转动作是如何执行的:ps:这里其实有个诀窍,既然我们的终极目标是要骗过系统的Activity Intent检测,那么,跟着Intent这个变量,就不会偏离方向.

4788b59858620bc24a1ee62dea19dd6f.png

既然intent被封装到了 ClientTransaction,交给了 mAppThread,那么继续:

ec08a8d6deb00b3bd03dd2fefd5a08ea.png前方有坑,请注意:androidStudio里并不能直接跳转,所以要手动,找到下图中的方法,这个 ClientTransactionHandlerActivityThread的父类.37ab1ccd0cd3dfdaf5eb0a15c1d895b5.png上图中,调用了 sendMessage(int,Object),在 ActivityThread中找到这个方法的实现:7a34f0c71b981820ac9cdb5b8df65204.png找它的最终实现:a2fd47ab31333b207345f40f14ff0f9b.png找到另一个关键点:mH ,a59761ee5142ef59de70976e07b0efe9.pngH类的定义:(太长了,我就不完整截图了,留下关键的信息)

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行055d3afe346bf47eac968cf9a8ec9010.pnghook核心代码如下

/**

* 这里对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);

}

}

}

handlerdispatchMessage逻辑,是 先执行 mCallbackhandlerMessage,然后根据它的返回值决定要不要执行 handler本身的 handlerMessage函数. 我们的目的是还原 Intent,并不需要对ActivityThread原本的mH做出逻辑修改,所以, hook mCallback,加入还原 Intent的逻辑,即可.

这次hook,对照的源码是(源码太长了,我就直接截取了ActivityThread里面一些关键的代码):28c7f24d56b58bacd6a38b0a53ad2ded.png

下面是 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的去向,只知道它最后被封装成了一个 ClientTransactionac53d0226586419ce45674ee3995a5c5.png09548478a333e9ae7d81af253d4a7058.png

70115aa3d313ee79697f134cf0353607.png但是,最后我从 mHswitchcaseEXECUTE_TRANSACTION分支,去 debug(因为无法继续往下查源码)的时候,

发现 原本塞进去的 ActivityResultItem list,居然变成了 LaunchActivityItemlist,而我居然查了半天,查不到是在源码何处发生的变化.

而 LaunchActivityItem 和 ActivityResultItem 他们两个都是ClientTransaction的子类

public class LaunchActivityItem extends ClientTransactionItem

public class ActivityResultItem extends ClientTransactionItem

emmmm...也是很尴尬。=_ =!

不过,最后能够确定,从 mHswitchcaseEXECUTE_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()去看看:

5da5a6c6f3c8d0f7ff941be6a3e584fe.png看来就是这里报的错,继续:f190693c1823f2cb321953fcb9285cbb.png找到可疑点:7f8b31318bb11ea17593ec1aa7f836a9.png可能就是这里抛出的异常,继续:a5bb35ce75dbbf921a37b7c7d985d19d.png然而,它是一个接口,那么就找它的实现类(注意,如果这个接口涉及到隐藏@hide的类,你用ctrl+T是不能找到的,不过也有办法,回到NavUtil.java):061d63a93f68a3d178ff725fce231268.png哦,原来 pm对象是来自 context,既然提到了 context这个抽象类,它的很多抽象方法的实现都在 ContextImpl,手动进入 ContextImpl:找这个方法:d742df2634114e0086da76d91aee298f.png这个 pm对象原来是来自 ActivityThread,然后进行了一次封装,最后返回出去的是一个 ApplicationPackageManager对象.

那就进入主线程咯.bb2d93874f2a183af1aa7a106121b387.png看看 IPackageManager的内容:它是 AIDL动态生成的接口,用 androidStudio是看不到接口内容的,只能到源码官网,查到的接口如下:

interface IPackageManager {

...

ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);

}

Ok,看到 IBinder,就知道应该无法继续往下追查了,已经跨进程了. 前面提到了,从主线程拿到的 pm,被封装成了 ApplicationPackageManager,那么,进入它里面去找: getActivityInfo方法:1faa32cd216e03f8978666dbf40e89a9.png原来异常是这里抛出的,当 mPm.getActivityInfo为空的时候,才会抛出. OK,就查到这里,得出结论: 源码,其实对 Activity的合法性进行了两次检测,一次是在 AMS,一次是在这里的 PMS,前面的 AMS,我们用一个已有的 Activity伪装了一下,通过了验证,那么这里的 PMS,我们也可以采用同样的方式. 注:上图的参数 ComponentNameclassName,其实,他就是!IntentComponentName成员:2799978af0cf9b9bd6681ba96bb3e944.png懂了吧··这里对intent又进行了一次检查,检查的就是这个 ComponentName.

接下来用同样的方式对 PMS的检测进行 hook,让它不再报异常. 此次 hook的参照的源码是:

5951528d8b36413da474622c90ca0c6e.png

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:978e239041e88dcbda771b6c3f5e37f2.gif上图中有3个Activity跳转,但是我们看demo的清单文件,只有一个 launchActivity

1b1b5baeefb7c450193fa6af2f65d0ca.png


结语

又是历时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.

往期hook技术文章回顾:Hook技术入门篇Hook——Activity启动流程(1)Hook——Activity启动流程(2)

2393a0ae9cc8d502728fe55db7e88718.png

知道你 “在看”e2b7b9a1c364f29f7e717ee4a227cc49.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/501183.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IOC操作Bean管理XML方式(有参构造注入属性)

IOC操作Bean管理XML方式 目录 有参构造注入属性 &#xff08;1&#xff09;步骤&#xff08;创建类&#xff0c;定义属性&#xff0c;创建属性对应的有参构造方法&#xff09;&#xff1a; &#xff08;2&#xff09;步骤&#xff1a;在Spring 的xml配置文件中进行配置 &a…

python3调用java_Python3-JPype调用Java代码

原博文 2019-12-31 10:54 − ## JPype调用Java代码 ### 安装所需要的库 python pip install JPype1-py3### 准备一个jar包 #### java工程目录 ![](https://img2018.cnblogs.com/blog/1895590/201912/18955... 相关推荐 2019-12-06 14:59 − 近期在Python中使用java语言的时候有…

IOC操作Bean管理XML方式(P名称空间注入)

P名称空间注入&#xff08;用的不多&#xff0c;但是也是一种方式&#xff09; 目录 P名称空间注入&#xff08;用的不多&#xff0c;但是也是一种方式&#xff09; &#xff08;1&#xff09;使用P名称空间注入&#xff0c;可以简化基于xml 配置方式 第一步&#xff1a;添…

textview 结束后释放_等待按键释放,你的代码如何写?

今天&#xff0c;还是一个问题&#xff0c;在QQ群(300384358)里有小伙伴一直在问一个问题&#xff0c;如上图。一个按键控制电机的转动&#xff0c;按键按下后&#xff0c;电机转动&#xff0c;按键释放&#xff0c;电机停止&#xff0c;再加一个按键按下时长的检测&#xff0c…

python中的demo_python中简单工厂实现demo

1 classBenBen:2 def __init__(self, money):3 """ 4 奔奔金融支付5 :param money:支付金额6 """ 7 self.money money8 9 defpay(self):10 print("收到奔奔金融支付金额{0}".format(self.money))11 12 13 classWeChat:14 def __init__…

IOC操作Bean管理XML方式(注入空值和特殊符号)

IOC操作Bean管理XML方式&#xff08;注入空值和特殊符号&#xff09; 目录 IOC操作Bean管理XML方式&#xff08;注入空值和特殊符号&#xff09; &#xff08;1&#xff09;通过null标签注入空值&#xff1a; 标签注入空值 &#xff08;2&#xff09;属性值中包含特殊符号…

麦克风增强软件_唱吧麦克风不会唱歌用它唱都好听,《向往的生活》同款麦克风...

当代年轻人的快乐&#xff0c;其实很简单。被工作狠狠虐了千百遍后&#xff0c;只要到KTV吼几嗓子放松一下&#xff0c;就又和这个世界和解了。唱歌&#xff0c;真的是一件解压又治愈的事情。就像《向往的生活》里&#xff0c;何炅、黄磊和其他嘉宾&#xff0c;饭后娱乐就是唱歌…

bert 多义词_广告行业中那些趣事系列15:超实用的基于BERT美团搜索实践

摘要&#xff1a;本篇主要学习和分析BERT在美团搜索排序中的项目实践。首先介绍了业务背景&#xff0c;主要对比了美团搜索业务场景和我们的应用场景&#xff1b;然后讲了下计算query和doc相关性的方法&#xff0c;主要包括计算字面相关性和计算语义相关性&#xff0c;重点介绍…

vba 跳到下一个循环_遍历工作薄和工作表(For Each循环的利用)

今日的内容是“VBA之EXCEL应用”的第三章“工作簿(Workbook)和工作表(Worksheet)对象(Object)”中第三节“遍历工作薄和工作表(For Each循环的利用)”。“VBA之EXCEL应用”这套教程从简单的录制宏的讲解&#xff0c;一直到窗体的搭建&#xff0c;内容丰富&#xff0c;案例众多。…

IOC操作Bean管理XML方式(注入外部bean)

目录 1..注入属性-外部 bean &#xff08;1&#xff09;创建两个类 service 类和 dao 类【分别创建两个包存放两个类】 &#xff08;2&#xff09;在 service 调用 dao 里面的方法 &#xff08;3&#xff09;&#xff08;后来做法&#xff09;在Spring 配置文件中进行配置 1…

数据增强 transform_深度学习-Pytorch框架学习之数据处理篇

前言数据是深度学习的核心&#xff0c;大部分论文里都会提到data-driven这个词&#xff0c;也就是数据驱动的意思。基本的模型搭建完成后&#xff0c;如何处理数据&#xff0c;如何将数据送给网络&#xff0c;如何做数据增强等等&#xff0c;对于提高网络的性能都十分重要&…

python元祖用法_Python序列(列表list和元组tuple)用法完全攻略

所谓序列&#xff0c;指的是一种包含多项数据的数据结构&#xff0c;序列包含的多个数据项(也叫成员)按顺序排列&#xff0c;可通过索引来访问成员。Python 的常见序列类型包括字符串、列表和元组。前一章介绍过的字符串&#xff0c;其实就是一种常见的序列&#xff0c;通过索引…

IOC操作Bean管理XML方式(注入内部 bean 和 级联赋值)

目录 1.注入属性-内部 bean &#xff08;1&#xff09;一对多关系&#xff1a;部门和员工 &#xff08;2&#xff09;在实体类之间表示一对多关系 &#xff08;3&#xff09;在Spring 配置文件中进行配置 2.注入属性-级联赋值 &#xff08;1&#xff09;第一种写法类似外部…

单处理机系统的进程调度动态优先_操作系统复习笔记(王道)C2.1

进程与线程一、进程特征和概念前提&#xff1a;允许多个程序并发执行。1.概念PCB(process control block)进程控制块&#xff0c;系统利用 PCB 来描述进程的基本情况和运行状 态&#xff0c;进而控制和管理进程。PCB是进程存在的唯一标志。进程是进程实体的运行过程&#xff0c…

非线性动力学_非线性科学中的现代数学方法:综述

Ch0【引言】本文是作者的一个总结&#xff0c;力图在极度繁杂的数理知识体系中摘选出那些最广泛应用的核心工具及思想。本文主要关注的问题都是非线性的、动态的。具体地讲&#xff0c;主要涉及的是&#xff1a;微分动力系统、泛函的最优化初步&#xff08;但不涉及最优控制及微…

IOC操作Bean管理XML方式(注入集合类型属性)

目录 IOC操作Bean管理XML方式&#xff08;注入集合类型属性&#xff09; &#xff08;1&#xff09;首先进行环境的搭建和准备 &#xff08;2&#xff09;创建一个类&#xff1a;用来完成集合类型属性注入 &#xff08;3&#xff09;在Spring 配置文件进行配置 &#xff0…

matlab 最小二乘法拟合_高中数学:最小二乘法与线性回归方程

1、怎样的拟合直线最好&#xff1f;——与所有点都近&#xff0c;即与所有点的距离之和最小。最小二乘法可以帮助我们在进行线性拟合时&#xff0c;如何选择“最好”的直线。要注意的是&#xff0c;利用实验数据进行拟合时&#xff0c;所用数据的多少直接影响拟合的结果&#x…

python多次循环输出_python循环

python_day_2一. 循环. while循环while 条件:代码块(循环体)执行流程:判断条件是否为真. 如果真. 执行代码块再次判断条件是否为真......当条件为假.执行else 跳出循环. 循环结束1.让计算机连续打印5次corn,每次延迟一秒:importtimecount 1while count <5:print("corn&…

预充电电路工作原理_LED触摸调光台灯控制电路板的工作原理

一款美观实用的台灯堪称书桌上的艺术品&#xff0c;看着书桌上精致美观的台灯&#xff0c;可能还会增加一些阅读的乐趣吧。那么触摸开关台灯的工作原理到底是怎么的呢?两按键触摸调光电路板工作原理:一、两按键触摸调光电路板&#xff0c;触摸芯片工作电压2.4-5.5V。输入也可用…

IOC操作Bean管理XML方式(FactoryBean)

目录 IOC操作Bean管理XML方式&#xff08;FactoryBean&#xff09; Spring 有两种类型的bean 第一种&#xff1a;是普通的 bean 第二种&#xff1a;工厂bean FactoryBean 第一步&#xff1a;创建类&#xff0c;让这个类作为工厂bean 第二步&#xff1a;在类中实现接口里面…