StockMask 在 system_server 内按 Binder 调用方过滤部分 PackageManager 特性和权限响应。本文说明作用范围及随 Android 版本变化的入口。模块源码属于私有项目,随文片段来自原排查,不构成可公开复现的完整模块,也不能证明所有检测通道均已消失。
$ pm list features | grep lineage
feature:org.lineageos.android
feature:org.lineageos.livedisplay
$ pm list permissions | grep lineage
permission:lineageos.permission.TRUST_INTERFACE
App 只需调用一次 hasSystemFeature(),就能查到这些特征。如果修改 /product/etc/permissions/ 的 XML,会导致 system_server 里的原生服务(如 livedisplay)因断言失败而崩溃。必须在 system_server 响应阶段,根据调用方的真实身份实施动态过滤。
我将钩子挂在负责回答查询的 PackageManagerService (PMS)。Binder 事务中的 CallingUid 由内核提供,用户态无法伪造,因此可以据此区分调用方:
+-------------------+ Binder IPC +-------------------------+
| Caller App | -----------------------------> | system_server (PMS) |
| (uid: 10236) | hasSystemFeature() | getCallingUid() = 10236 |
+-------------------+ <----------------------------- | return false (Spoofed) |
+-------------------------+
|
+-------------------+ Binder IPC (same method)
| System Service | -----------------------------> |
| (uid: 1000) | hasSystemFeature() v
+-------------------+ <----------------------------- | return true (Original) |
我在 com.stockmask.Main(私有源码,公开链接不可用) 中按 appId 过滤,判定它是否大于 10000(即第三方应用):
private static boolean shouldFilter() {
int uid;
try { uid = Binder.getCallingUid(); } catch (Throwable t) { return false; }
return (uid % 100000) >= 10000;
}
在拦截 hasSystemFeature 时,我利用 after hook 等待原始调用结束,再修改返回结果。这种方式避免了破坏原有状态机:
private final XC_MethodHook hasFeatureHook = new XC_MethodHook() {
@Override protected void afterHookedMethod(MethodHookParam p) {
try {
if (!shouldFilter() || p.args == null || p.args.length == 0) return;
Object a0 = p.args[0];
if (a0 instanceof String && ((String) a0).contains("lineage")
&& Boolean.TRUE.equals(p.getResult())) {
p.setResult(false);
}
} catch (Throwable ignored) {}
}
};
对于获取特性列表的 getSystemAvailableFeatures(),在 Android 14+ 架构中,数据读取被重构为了无锁的写时复制快照(ComputerEngine)。不仅要 hook PMS,还要覆盖 IPackageManagerImpl。其返回类型被包装在 ParceledListSlice 中。我需要将其拆包、过滤,再重新封装。
权限过滤(getAllPermissionGroups 等)也按调用方处理。过滤发生在 system_server 中,App 进程无需加载模块。RASP 扫描自身的 /proc/self/maps 时,看不到注入的模块和被修改的方法签名;Binder 返回之前,响应已经改好。
验证时应结合 UID、SELinux 域和挂载命名空间,不能把 root shell 的响应当作 App 的结果。
评论