HOOK AMS 技术白皮书
Android AMS Hook 跳转治理能力说明
版本:V1.0
适用平台:Android 8.0 及以上
适用对象:SDK 接入方 / 技术评审 / 安全合规 / 架构评估
源码:见底部
一、摘要(Executive Summary)
随着 Android 生态中广告 SDK 与第三方 SDK 的复杂化,应用在跳转治理、行为可解释性与合规控制方面面临显著挑战。 提供一套基于 Android AMS(Activity Manager Service)Hook 的跳转治理能力,在不修改系统、不依赖系统权限的前提下,实现对应用内跳转行为的统一监听、分析与控制。
二、背景与问题定义
2.1 行业痛点
- 跳转行为由多方 SDK 触发,来源不可追溯
- 广告点击是否引发跳离难以精确判断
- 隐式 Intent / PendingIntent 绕过业务层监控
- 合规审查缺乏技术证据链
2.2 Android 系统特性
在 Android 系统中,所有跨组件、跨进程启动行为最终均需通过 AMS 完成调度。
包括:
- startActivity
- startService
- sendBroadcast
- startIntentSender
三、技术方案概述
3.1 技术定位
App 在 应用进程内 对 AMS Binder 进行代理,实现对跳转行为的统一治理。
特性包括:
- SDK 级能力(无需系统权限)
- 不修改 system_server
- 覆盖所有跳转来源
- 可审计、可降级、可关闭
3.2 架构总览
业务代码 / 第三方 SDK
↓
AMS Hook SDK(代理层)
↓
原始 AMS Binder
↓
system_server
四、核心技术原理
4.1 AMS 在应用进程中的形态
Android 在应用进程中通过 Singleton 缓存 AMS Binder 实例:
| Android 版本 | 接口 |
|---|---|
| 8–10 | IActivityManager |
| 10+ | IActivityTaskManager |
4.2 Hook 实现机制
SDK 使用以下 Java 标准能力:
- Reflection(反射)
- Dynamic Proxy(动态代理)
实现方式:
- 获取 AMS Singleton
- 取得原始 Binder 实例
- 创建代理对象
- 替换 Singleton.mInstance
SDK 在代理中完成监控与策略判断后,原样调用系统能力。
五、功能能力说明
5.1 覆盖的系统调用
| 类型 | 支持 |
|---|---|
| Activity 启动 | ✅ |
| Service 启动 | ✅ |
| Broadcast 发送 | ✅ |
| PendingIntent | ✅ |
5.2 跳转分析能力
SDK 可解析以下信息:
- 目标包名 / 组件名
- 显式 / 隐式 Intent
- 调用线程
- 调用堆栈
并区分:
- 本应用内部跳转
- 第三方应用跳转
- 高风险跳转行为
六、典型应用场景
6.1 广告与商业化
- 判断广告点击是否导致 App 跳离
- 构建点击 → 跳转证据链
6.2 SDK 行为治理
- 审计第三方 SDK 跳转行为
- 防止私自拉起第三方应用
6.3 合规与安全
- 提供可验证的技术说明
- 跳转行为可回溯、可解释
七、合规与安全声明
7.1 SDK 不涉及的能力
- 不采集用户隐私数据
- 不绕过系统权限模型
- 不修改系统服务
7.2 合规原则
- 仅作用于当前应用进程
- 所有系统调用原样转发
- 支持随时关闭与降级
八、版本适配与稳定性
| Android 版本 | 支持情况 |
|---|---|
| 8–11 | 稳定支持 |
| 12 | 自动降级 |
| 13+ | 建议与 Instrumentation 联合 |
九、接入建议
- 在 Application.attachBaseContext 初始化
- 支持灰度启用
- 提供统一开关配置
十、结语
AMS Hook 跳转治理能力是 Android SDK 体系中,实现跳转可控、行为可解释与责任可追溯的重要技术基础。
源码
@SuppressLint({"PrivateApi", "BlockedPrivateApi"})
public final class AMSHookHelper {
private static boolean sHooked = false;
public static void hook() {
if (sHooked) return;
try {
int sdk = Build.VERSION.SDK_INT;
Object singleton;
if (sdk >= 29) {
singleton = getStaticField(
"android.app.ActivityTaskManager",
"IActivityTaskManagerSingleton"
);
} else if (sdk >= 26) {
singleton = getStaticField(
"android.app.ActivityManager",
"IActivityManagerSingleton"
);
} else {
singleton = getStaticField(
"android.app.ActivityManagerNative",
"gDefault"
);
}
Object raw = getField(
"android.util.Singleton",
singleton,
"mInstance"
);
if (raw == null) {
if (sdk >= 29) {
callStatic("android.app.ActivityTaskManager", "getService");
} else if (sdk >= 26) {
callStatic("android.app.ActivityManager", "getService");
} else {
callStatic("android.app.ActivityManagerNative", "getDefault");
}
raw = getField(
"android.util.Singleton",
singleton,
"mInstance"
);
}
if (raw == null) return;
Class<?> iFace = sdk >= 29
? Class.forName("android.app.IActivityTaskManager")
: Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(
iFace.getClassLoader(),
new Class[]{iFace},
new AMSInvocationHandler(raw)
);
setField(
"android.util.Singleton",
singleton,
"mInstance",
proxy
);
sHooked = true;
} catch (Throwable ignored) {
}
}
// ====== 反射工具 ======
private static Object getStaticField(String cls, String field)
throws Exception {
Class<?> c = Class.forName(cls);
Field f = c.getDeclaredField(field);
f.setAccessible(true);
return f.get(null);
}
private static Object getField(String cls, Object obj, String field)
throws Exception {
Field f = Class.forName(cls).getDeclaredField(field);
f.setAccessible(true);
return f.get(obj);
}
private static void setField(String cls, Object obj, String field, Object val)
throws Exception {
Field f = Class.forName(cls).getDeclaredField(field);
f.setAccessible(true);
f.set(obj, val);
}
private static void callStatic(String cls, String method)
throws Exception {
Method m = Class.forName(cls).getDeclaredMethod(method);
m.setAccessible(true);
m.invoke(null);
}
}
//拦截点
public class AMSInvocationHandler implements InvocationHandler {
private final Object mBase;
public AMSInvocationHandler(Object base) {
this.mBase = base;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String name = method.getName();
if ("startActivity".equals(name)
|| "startActivityAsUser".equals(name)
|| "startActivityIntentSender".equals(name)) {
Intent intent = findIntent(args);
if (intent != null) {
// === 这里放你原来 Jump / Block / Record 逻辑 ===
}
}
return method.invoke(mBase, args);
}
private Intent findIntent(Object[] args) {
if (args == null) return null;
for (Object arg : args) {
if (arg instanceof Intent) {
return (Intent) arg;
}
}
return null;
}
}