以下分析基于 Magisk 76ddfeb93a8b3612cd68988323f422e996751e16
由于 Magisk 更新太快了,决定弃坑,自己去看源码罢!
Zygisk 加载是通过替换 app_process ,修改 LD_PRELOAD ,再执行原 app_process 实现的。
想用 Xposed 实现一个纯注入系统的「去你大爷的内置浏览器」,需要实现一个「询问」功能,也就是启动内置浏览器 Activity 的时候并不总是替换,而是询问用户应该是用内置浏览器打开还是用户浏览器(显然内置浏览器往往不是 exported 的,因此不会出现在系统选择器中)。
既然是询问,肯定要有一个 Window 和用户交互,但是应该如何呈现这个 Window 很伤脑筋。一开始尝试把要启动的 Intent 用模块的 Activity 替换,模仿系统的 ResolverActivity 「转发」Intent,但是注意到这样处理多用户(工作空间)可能就有些棘手,此外「转发」也很麻烦。
于是换一种思路:hook 系统服务侧的 startActivity ,发现启动「内置浏览器」的 Intent 后,直接返回 0 (START_SUCCESS) ,同时显示一个 dialog 询问用户,根据用户操作再恢复 intent 的发送。
这个 dialog 自然是在系统服务最方便,但是系统服务不像普通的 app ,它没有 Activity ,又怎么弹 Dialog 呢?
最近把 Magisk 从 alpha 升级到了 24.2 ,不过 Alpha 频道有一个提醒:
MIUI设备在升级 官方 v24.2 后可能会无法隐藏/还原Magisk app,这是MIUI系统本身的问题,请向MIUI反馈PackageInstaller API ( https://developer.android.com/reference/android/content/pm/PackageInstaller ) 无法正常工作。
临时解决办法:关闭MIUI优化后重试。
自己用的刚好是 MIUI 12.5 ,试了一下,尝试随机包名安装后一直提示失败。
查了一下源码,引入这个改进是这个提交:
探测包名是 Android 应用安全措施的重要方法,而手段也多种多样。常规的通过 PMS 查询包名的方法已经有 HMA 帮忙隐藏,且随着 Android 版本更新也逐渐限制应用任意地获取包名(虽然可以声明需要查询的类型),但还有一种更阴险的方式,就是利用 /data/data 目录或 /sdcard/Android/data 的漏洞——它们下面的目录都是以包名命名的应用数据目录,尽管这两个目录不能直接列出,但是任何应用都具有这个目录的 x 权限(否则无法访问自己的数据目录),因此如果已知需要探测的包名,就可以通过 stat 等系统调用判断目录是否存在,进而确定包名的存在(成功或 Permission Denied 都表明文件存在)。这种方法并不能通过简单的 hook 防止(尤其是绕过库函数直接 syscall 的情况)。
不过 Android 11 实际上还引入了一些措施来防止包名的泄露,那就是通过文件系统层面的措施,将 data 和 Android/data 下的包名直接隔离,每个应用的进程基本上只能看到属于自己 uid 的数据目录。
检查下面两个 prop :
getprop persist.zygote.app_data_isolation
getprop persist.sys.vold_app_data_isolation_enabled
Window Manager Service 是 Android 的重要服务,各种窗口(Activity, toast, Dialog, 系统 UI 等)都通过这个服务注册和管理。
经常玩系统隐藏 API 的都知道这个实际上是一个名为 window
的 Binder 服务,通过 ServiceManager.getService("window")
就能拿到它的 BinderProxy ,进而转为 android.view.IWindowManager 直接调用 API
但是当你在 app 的 shell 下尝试获取这个服务,却会发现根本无法找到(以下在 Termux 中测试):
对比 activity 服务:
使用 frida 的时候经常需要通过 pid 附加到进程,因为 frida 自带的 -n
参数实在太不好用了——本来 Android 的「进程名」概念是很清楚的,在 Manifest 就能看到,要是不清楚也可以通过 packageName 或者 cmdline 来替代,但 frida 却非要让「应用的名字」作为注入应用主进程的唯一标识,什么包名、命令行,统统不认,遇到中文应用名就很麻烦;此外,这个操作似乎还要注入系统服务,从而导致 stop server 后发生各种诡异崩溃,因此我不太喜欢用这个参数。然而每次都要打开 shell ,输入 ps -ef | grep
找应用的 pid 很是麻烦,并且对于多进程的应用并非总是能「猜对」它的进程名的。于是写了这个脚本,至少可以确定当前看到的这个窗口属于哪个进程(没焦点的就不好说了)。
脚本运行在 Android 上的 shell (建议 /system/bin/sh
),在主机端只要简单用 adb shell 之类的封装一下用起来就很方便了。
脚本主要利用 dumpsys window
的 mFocusedWindow 取得当前焦点窗口的 hash ,并查找 hash 对应窗口的 Session 信息获取 uid 和 pid 。
仅在 Android 11 上通过测试,adb 权限即可。
import os | |
# find icons in %AppData%\Microsoft\Windows\Start Menu\Programs\JetBrains Toolbox | |
template_add = r'''Windows Registry Editor Version 5.00 | |
[HKEY_CLASSES_ROOT\Directory\Background\shell\{prog_name}] | |
@="Open with {name}" | |
"Icon"="\"{current}\\icons\\{prog_name}.ico\"" |
function injector(url) { | |
console.log("injected"); | |
let im = document.querySelector("img"); | |
function loaded() { | |
let canvas = document.createElement("canvas"); | |
canvas.width = im.naturalWidth; | |
canvas.height = im.naturalHeight; | |
var ctx = canvas.getContext("2d"); | |
ctx.drawImage(im, 0, 0); |