如何应对Andriod面试官 -> 如何 Hook Activity 的启动流程?

前言


image.png

本章继续讲解 AMS 相关的知识点,如何 Hook Activity 的启动流程;

attach


我们这样直接从 attach 入口讲起

private void attach(boolean system, long startSeq) {// 省略部分代码// AMSfinal IActivityManager mgr = ActivityManager.getService();try {// 这里直接调用到了 AMS 的 attachApplication 方法;mgr.attachApplication(mAppThread, startSeq);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();} 
}

attach 中直接调用到了 AMS 的 attachApplication 方法,我们进入这个方法看下:

public final void attachApplication(IApplicationThread thread, long startSeq) {if (thread == null) {throw new SecurityException("Invalid application interface");}synchronized (this) {// 获取调用端的 pidint callingPid = Binder.getCallingPid();// 获取调用端的 uidfinal int callingUid = Binder.getCallingUid();// 把 pid/uid 设置成 AMS 的final long origId = Binder.clearCallingIdentity();// attachApplicationLocked(thread, callingPid, callingUid, startSeq);// 恢复为 app 的Binder.restoreCallingIdentity(origId);}
}

这里有一个比较关键的点就是:把 binder 的 id 设置成 AMS 的,这样做是为了权限验证;Binder.clearCallingIdentity() 最终调用到 IPCThreadState.cpp 中的同名方法,我们进入这个 cpp 文件的同名方法看下:


int64_t IPCThreadState::clearCallingIdentity() { int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid; clearCaller(); return token; 
}

这里调用了 clearCaller 函数,我们进入这个函数看下:

// 清空远程调用端的uid和pid,用当前本地进程的uid和pid替代
void IPCThreadState::clearCaller() { mCallingPid = getpid(); mCallingUid = getuid(); 
}

restoreCallingIdentity 也是调用的同名方法;

// 从token中解析出pid和uid,并赋值给相应变量,该方法正好是clearCallingIdentity的相反过程
void IPCThreadState::restoreCallingIdentity(int64_t token) { mCallingUid = (int)(token>>32); mCallingPid = (int)token; 
}

image.png

AndroidManifest.xml 中的 Activity 是在哪里注册的?


我们在启动一个 Activity 的时候,如果没有在 AndroidManifest 中进行注册的话,就会抛出一段异常,那么 AndroidManifest.xml 中的 Activity 是在哪里注册的呢?

我们直接进入启动 Activity 的代码中看下:

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target,Intent intent, int requestCode, Bundle options) {// 省略部分代码// 调用 AMS 的 startActivity 方法并获取一个放回结果,然后检测这个结果int result = ActivityTaskManager.getService().startActivity(whoThread,who.getOpPackageName(), who.getAttributionTag(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,requestCode, 0, null, options);checkStartActivityResult(result, intent);
}

这里调用 AMS 的 startActivity 方法并获取一个返回结果,然后检测这个结果;

public static void checkStartActivityResult(int res, Object intent) {if (!ActivityManager.isStartResultFatalError(res)) {return;}switch (res) {case ActivityManager.START_INTENT_NOT_RESOLVED:case ActivityManager.START_CLASS_NOT_FOUND:if (intent instanceof Intent && ((Intent)intent).getComponent() != null)throw new ActivityNotFoundException("Unable to find explicit activity class "+ ((Intent)intent).getComponent().toShortString()+ "; have you declared this activity in your AndroidManifest.xml?");throw new ActivityNotFoundException("No Activity found to handle " + intent);}
}

可以看到,在这个检测结果方法中,抛出了 Activity 未注册的异常,到这里,我们的报错点找到了;

我们接着来看下启动流程中是在哪里返回的 result 的,根据上一章节的启动流程介绍,我们很容易找到在 ActivityStarter 的 executeRequest 方法中返回了一个 result;

private int executeRequest(Request request) {// 省略部分代码// 可以看到 error 的初始值为 SUCCESS,默认初始化成功,我们接着往下看 err 的赋值的地方int err = ActivityManager.START_SUCCESS;// 权限拒绝的时候返回的 err 信息;if (caller != null) {callerApp = mService.getProcessController(caller);if (callerApp != null) {callingPid = callerApp.getPid();callingUid = callerApp.mInfo.uid;} else {// 权限拒绝返回 PERMISSION_DENIEDerr = ActivityManager.START_PERMISSION_DENIED;}}// 从 Intent 中无法找到相应的 Componentif (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {err = ActivityManager.START_INTENT_NOT_RESOLVED;}// 从 Intent 中无法找到相应的 ActivityInfoif (err == ActivityManager.START_SUCCESS && aInfo == null) {// We couldn't find the specific class specified in the Intent.// Also the end of the line.err = ActivityManager.START_CLASS_NOT_FOUND;}
}

到这里的时候,我们就找到前面说的异常抛出的原因,是因为 aInfo == null,那么这个 aInfo 是怎么赋值的呢?它又是因为什么原因是 null 呢?我们接着往回看;

private int executeRequest(Request request) {// 是从 request 的 activityInfo 获取的,这个 request 是传递进来的,我们接着往上一层看ActivityInfo aInfo = request.activityInfo;
}

是从 request 的 activityInfo 获取的,这个 request 是传递进来的,我们接着往上一层看;

int execute() {// 解析 activityInfo if (mRequest.activityInfo == null) {mRequest.resolveActivity(mSupervisor);}
}

可以看到在 execute 中会执行 activityInfo 的解析逻辑,我们进入这个方法看下它在什么情况下会返回 null;

void resolveActivity(ActivityTaskSupervisor supervisor) {// 如果 rInfo 不为 null 则获取 rInfo 的 activityInfo 赋值给 aInfo 并返回;final ActivityInfo aInfo = rInfo != null ? rInfo.activityInfo : null;
}

如果 rInfo 不为 null 则获取 rInfo 的 activityInfo 赋值给 aInfo 并返回;那么这个 rInfo 的 activityInfo 是什么情况在为空的呢?这个 rInfo 又是在哪里赋值并传递进来的呢?我们接着返回看;

void resolveActivity(ActivityTaskSupervisor supervisor) {// 省略部分代码// 在这里获取 rInforesolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,0 /* matchFlags */,computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid));
}

通过 resolveIntent 获取 rInfo,我们进入这个 resolveIntent 方法看下;

ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,int filterCallingUid) {// 省略部分代码// 返回 ResolveInfotry {return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,filterCallingUid);} finally {Binder.restoreCallingIdentity(token);}
}

这里又回到了 AMS 中的 resolveIntent 方法,我们进入这个方法看下;

PackageManagerInternal getPackageManagerInternalLocked() {if (mPmInternal == null) {mPmInternal = LocalServices.getService(PackageManagerInternal.class);}return mPmInternal;
}

这里是先获取 PackageManagerInternal 然后调用其 resolveIntent 方法,这个 PackageManagerInternal 是一个抽象类,resolveIntent 是一个抽象方法,我们需要进入 PackageManagerInternal 的实现类看下, 它的具体实现就是 PackageManagerService,我们进入它的 resolveIntent 方法看下,它最终调用的是 resolveIntentInternal;

private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,@PrivateResolveFlags int privateResolveFlags, int userId, boolean resolveForStart,int filterCallingUid) {// 省略部分代码// 获取 ResolveInfo 并返回final ResolveInfo bestChoice =chooseBestActivity(intent, resolvedType, flags, privateResolveFlags, query, userId,queryMayBeFiltered);
}

通过调用 chooseBestActivity 获取 ResolveInfo 并返回,我们进入这个方法看下;

private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,boolean queryMayBeFiltered) {// 省略部分的代码// 获取 ResolveInfo,如果不为空,直接返回ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);if (ri != null) {return ri;}
}

我们接着进入这个 findPreferredActivityNotLocked 方法看下;

ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,List<ResolveInfo> query, int priority, boolean always,boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {// 省略部分代码// 这里主要是获取 ActivityInfofinal ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS| MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,userId);
}

findPreferredActivityNotLocked 主要是为了获取 ActivityInfo 并将 ActivityInfo 赋值给 ResolveInfo 的 resolveInfo;

public final ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
}
public final ActivityInfo getActivityInfoInternal(ComponentName component, int flags,int filterCallingUid, int userId) {// 省略部分代码return getActivityInfoInternalBody(component, flags, filterCallingUid, userId);
}
protected ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,int filterCallingUid, int userId) {ParsedActivity a = mComponentResolver.getActivity(component);if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());if (ps == null) return null;if (shouldFilterApplicationLocked(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {return null;}return PackageInfoUtils.generateActivityInfo(pkg,a, flags, ps.readUserState(userId), userId, ps);}if (resolveComponentName().equals(component)) {return PackageParser.generateActivityInfo(mResolveActivity, flags, new PackageUserState(), userId);}return null;
}

可以看到往下的逻辑,就是解析 AndroidManifest.xml 文件来获取对应的目标 Activity 信息并返回,如果解析不到,则抛出相应的异常,关于解析 AndroidManifest.xml 会在 PackageManagerService 的讲解中重点讲解;

Hook Activity


Hook Activity 本质就是绕过 AMS 的检查,可以启动我们并没有在 AndroidManifest 中声明的 Activity;

本章只是讲解下 Hook 的原理,具体实战可以关注我的后续文章,后面在插件化实战的时候,会具体实现 Hook AMS;

在 startActivity 的时候,会和 AMS 通信,经过 AMS 的验证之后,返回 app 所在进程,通过 Instrumention 创建目标 Activity,AMS 控制 Instrumention 执行 Activity 的生命周期;

也就说,我们不能传递真正的 Activity 给 AMS,我们需要传递一个假的(也就是在 AndroidManifest 中注册的 Activity),当 AMS 验证通过并返回到 app 的时候,我们需要再替换回目标 Activity 来完成,目标 Activity 的启动;

我们需要寻找至少两个 Hook 点;

  1. startActivity 的时候,将目标 Activity 替换成假的 Activity;
  2. AMS 验证通过之后,再替换回去;

image.png

整体的启动流程如上图,我们需要在这几个流程中找到比较容易 hook 的点;

找 hook 点,我们一般找 public static 的,这样比较容易反射修改;

从图中以及结合我们前面讲的启动流程,

  1. Instrumentation 是一个 hook 点(滴滴 VirtualAPK 开源框架方案);
  2. IActivityManager 是一个 hook 点(360 DroidPlugin 开源框架方案);

这里我贴了一下 360 的具体实现方案(仅 Hook IActivityManager 的地方)

public static void hookIActivityTaskManager(){try{Field singletonField = null;// 这里要适配不同的系统版本,后面插件化的时候会具体讲解Class<?> actvityManager = Class.forName("android.app.ActivityTaskManager");singletonField = actvityManager.getDeclaredField("IActivityTaskManagerSingleton");singletonField.setAccessible(true);Object singleton = singletonField.get(null);// 拿IActivityManager对象Class<?> singletonClass = Class.forName("android.util.Singleton");Field mInstanceField = singletonClass.getDeclaredField("mInstance");mInstanceField.setAccessible(true);// 原始的IActivityTaskManagerfinal Object IActivityTaskManager = mInstanceField.get(singleton);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{Class.forName("android.app.IActivityTaskManager")}, new InvocationHandler() {@Overridepublic Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {Intent raw = null;int index = -1;if ("startActivity".equals(method.getName())) {for (int i = 0; i < args.length; i++) {if(args[i] instanceof  Intent){raw = (Intent)args[i];index = i;}}// 代替的IntentIntent newIntent = new Intent();newIntent.setComponent(new ComponentName("com.example.hook", StubActivity.class.getName()));newIntent.putExtra(EXTRA_TARGET_INTENT,raw);args[index] = newIntent;}return method.invoke(IActivityTaskManager, args);}});mInstanceField.set(singleton, proxy);} catch (Exception e){e.printStackTrace();}
}

AMS 验证之前的 Hook,接下来是 AMS 验证之后的 Hook;

public static void hookHandler() {try {// 这里要适配不同的系统版本,后面插件化的时候会具体讲解Class<?> atClass = Class.forName("android.app.ActivityThread");Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");sCurrentActivityThreadField.setAccessible(true);Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);// ActivityThread 一个app进程 只有一个,获取它的mHField mHField = atClass.getDeclaredField("mH");mHField.setAccessible(true);final Handler mH = (Handler) mHField.get(sCurrentActivityThread);//获取mCallbackField mCallbackField = Handler.class.getDeclaredField("mCallback");mCallbackField.setAccessible(true);mCallbackField.set(mH, new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case 100: {}break;case 159: {Object obj = msg.obj;Log.i(TAG, "handleMessage: obj=" + obj);try {Field mActivityCallbacksField = obj.getClass().getDeclaredField("mActivityCallbacks");mActivityCallbacksField.setAccessible(true);List mActivityCallbacks = (List) mActivityCallbacksField.get(obj);if (mActivityCallbacks.size() > 0) {Log.i(TAG, "handleMessage: size= " + mActivityCallbacks.size());String className = "android.app.servertransaction.LaunchActivityItem";if (mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {Object object = mActivityCallbacks.get(0);Field intentField = object.getClass().getDeclaredField("mIntent");intentField.setAccessible(true);Intent intent = (Intent) intentField.get(object);Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);intent.setComponent(targetIntent.getComponent());}}} catch (Exception e) {e.printStackTrace();}}break;}mH.handleMessage(msg);return true;}});} catch (Exception e) {Log.e(TAG, "hookHandler: " + e.getMessage());e.printStackTrace();}
}

没有贴上不同系统版本的兼容处理,感兴趣的,可以自己查看不同版本的具体实现,从而针对性的进行修改适配;

下一章预告


PackageManagerService

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~

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

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

相关文章

gcc原理和使用

gcc gcc是什么 GCC&#xff0c;全称 GNU Compiler Collection&#xff08;GNU 编译器套件&#xff09;&#xff0c;是一套功能强大的编程语言编译器&#xff0c;由自由软件基金会&#xff08;Free Software Foundation, FSF&#xff09;作为GNU项目的一部分开发和维护。它最初…

我们一起看看《看漫画学C++》中如何介绍的字符串的用法

C中的字符串使用的是 std::string 类型&#xff0c;它是C标准库中提供的字符串类&#xff0c;提供了丰富的字符串操作方法。下面是关于C字符串的一些常用用法&#xff1a; 字符串拼接 字符串查找 字符串追加 购书地址&#xff1a;https://item.jd.com/14418856.html

网工交换基础——MUX VLAN

前言&#xff1a; MUX VLAN&#xff08;Multiplex VLAN&#xff0c;多复用VLAN&#xff09;提供了一种通过VLAN进行网络资源控制的机制。例如&#xff0c;在企业网络中&#xff0c;企业员工和企业客户可以访问企业的服务器。对于企业来说&#xff0c;希望企业内部员工之…

c++的学习之路:24、 二叉搜索树概念

摘要 本章主要是讲一下二叉搜索树的实现 目录 摘要 一、二叉搜索树概念 二、 二叉搜索树操作 1、二叉搜索树的查找 2、二叉搜索树的插入 3、二叉搜索树的删除 三、二叉搜索树的实现 1、插入 2、中序遍历 3、删除 4、查找 四、二叉搜索树的递归实现 1、插入 2、删…

2024年机电工程与航天航空技术国际研讨会(ISMEEAT2024)

2024年机电工程与航天航空技术国际研讨会&#xff08;ISMEEAT2024) 会议简介 会议委员会很高兴邀请您参加在上海举行的2024国际机电工程与航空航天技术研讨会&#xff08;ISMEEAT2024&#xff09;。ISMEEAT2024为研究人员提供了一个创新的环境&#xff0c;展示他们的新研究和…

Pygame经典游戏:坦克大战TankWar+五子棋人机对弈(安排!!)

Python108款&#xff0c;小游戏集合&#xff0c;总有一个是你想要的 中国象棋 像素鸟 五子棋 24点小游戏 贪吃蛇 扫雷 俄罗斯方块 魂斗罗 消消乐 坦克大战 外星人入侵 汤姆猫 斗地主 乒乓球 推箱子 植物大战僵尸 围棋 超级玛丽 飞机大战 迷宫 滑雪 吃豆人…等等&#xff08;…

postman汉化

一、postman历史版本下载&#xff1a;Postman 10.24.16 Download for Windows / Old Versions / FileHorse.comhttps://www.filehorse.com/download-postman/old-versions/ 二、汉化包下载&#xff1a; Releases hlmd/Postman-cn GitHubPostman汉化中文版. Contribute to h…

Python文件操作大全

1 文件操作 1.1 文件打开与关闭 1.1.1 打开文件 在Python中&#xff0c;你可以使用 open() 函数来打开文件。以下是一个简单的例子&#xff1a; # 打开文件&#xff08;默认为只读模式&#xff09; file_path example.txt with open(file_path, r) as file:# 执行文件操作…

CTFHUB-技能树-Web前置技能-文件上传(无验证,JS前端验证,前端验证)

CTFHUB-技能树-Web前置技能-文件上传&#xff08;无验证&#xff0c;JS前端验证&#xff0c;前端验证—.htaccess&#xff09; 文章目录 CTFHUB-技能树-Web前置技能-文件上传&#xff08;无验证&#xff0c;JS前端验证&#xff0c;前端验证—.htaccess&#xff09;文件上传无验…

机器学习引领金融革命:重塑金融服务领域新格局,开启智能化新篇章

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

Oracle11.2.0.1,(CVE-2012-1675)漏洞解决方案

1.进入容器停止监听 docker exec -it -u 0 oracle11g bash su - oracle lsnrctl stop listener2.找到监听配置文件位置&#xff0c;修改监听文件 echo $ORACLE_HOMEvi network/admin/listener.ora #在文件底部添加 SECURE_REGISTER_LISTENER (IPC) #启动监听 lsnrctl start …

angular node版本问题导致运行出错时应该怎么处理

如下图所示&#xff1a; 处理方式如下&#xff1a; package.json中start跟build中添加&#xff1a;SET NODE_OPTIONS--openssl-legacy-provider即可