最近在梳理 Root 检测的处置规则,模拟器、云手机这几类设备怎么都排不顺。它们的 su 是镜像自带的,检测一跑就中,可里面既有在 PC 上正常玩的,也有批量刷号的工作室。按 Root 一刀切,全放或全封都不对。
这类环境查 Root 没意义,要看的是:它是不是一台一个人在用的真机,能从游戏里拿走多少收益。
先看环境长什么样
模拟器的痕迹最多:构建属性(ro.boot.qemu 只有 AOSP 自家模拟器认,第三方不一定遵守)、虚拟设备节点、缺失的电话能力,x86 机器上还有跑 ARM SO 用的转译库。Unity 里最顺手的是 GPU 名:
string gpu = SystemInfo.graphicsDeviceName; // 真机多为 Adreno / Mali / PowerVR / Immortalis
bool suspicious = Regex.IsMatch(gpu,
"GeForce|Radeon|NVIDIA|Intel|SwiftShader|llvmpipe", RegexOptions.IgnoreCase);
只能当信号。各家模拟器的返回值得实测,三星有些机型是 AMD 架构 GPU,正则写宽了会误伤。
多开不一定要 Root。虚拟框架用占位 Activity 拉起游戏,反射替换 ActivityThread 里的 Application,再 Hook libc 的 open()、mkdir() 把读写重定向到宿主目录,游戏一启动就跑在别人的进程里,Hook 能力是现成的。常规查法是看 getDataDir() 在不在 /data/data/<pkg>、调用栈里有没有宿主帧。
这些我不太指望,查的全是对方能改的东西:改机工具能伪造构建属性和 GPU 名,宿主能在 Java 层伪造数据目录。

换个它不好改的地方查
宿主的代码得加载进游戏进程才能拉起游戏,所以它的 APK 一定出现在 /proc/self/maps 里。藏掉这一行要改内核视图,比改 Java 返回值贵得多。
// 示意代码,生产要处理跨块截断的行
int fd = syscall(__NR_openat, AT_FDCWD, "/proc/self/maps", O_RDONLY);
ssize_t n = syscall(__NR_read, fd, buf, sizeof(buf) - 1);
syscall(__NR_close, fd);
for (char *line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) {
if (!strstr(line, "/base.apk") || strstr(line, self_pkg)) continue;
if (strstr(line, "webview") || strstr(line, "trichrome")) continue; // 系统 WebView 正常会映射进来
return 1; // 别人的代码出现在本进程
}
WebView 白名单不加,真机会大面积误报。走 syscall 是因为宿主最常 Hook 的就是 libc,绕开它,对手只能去动内核。大型 Unity / UE 进程的 maps 可能有几千行,放在登录、进副本时抽查,耗时在低端机上测过再定。
另一条是硬件证明。模拟器、手机内虚拟机、ARM 容器云手机没有可用的 TEE,Key Attestation 的 attestationSecurityLevel 到不了 TrustedEnvironment 或 StrongBox,Software 级在自带 Root 的镜像里等于没证明。challenge 服务端下发、至少 16 字节、一次一用,证书链在服务端验。生成密钥慢,登录时异步做一次缓存。
板卡云手机到这里就查不动了。机柜里插的是真机主板,硬件和 TEE 都是真的,证明也可能过,前面那些检测抓的是伪造的破绽,它没伪造任何东西。这类不常见,但工作室做大了迟早往这儿走。

标签打准,收益交给服务端
客户端的活是把标签打准,拦人交给服务端。服务端看规模痕迹:机房 IP 段、一个设备指纹挂一串账号、一批号的上线时间和操作间隔聚成一团。有客户端标签才能分层设阈值,不然只能一刀切。
模拟器我不主张封。外挂在模拟器上的收益主要来自键鼠和脚本压制手机玩家,进了单独匹配池,这部分收益就没了。
// 环境标签只调收益上限,不直接封号;比例为示意,按业务定
double cap = env switch {
EnvTag.Emulator => normalCap, // 已进单独匹配池
EnvTag.VirtualContainer => normalCap * 0.5, // 同设备账号数另有上限
EnvTag.CloudPhoneSuspect => normalCap * 0.3, // 机房 IP + 指纹聚类命中
_ => normalCap
};
GrantReward(Math.Min(claimed, cap));
| 环境 | 主要信号 | 处置 |
|---|---|---|
| PC 模拟器 | 构建属性、GPU 名、转译库 | 单独匹配池,不拦截 |
| 虚拟框架 / 多开 | maps 里有宿主代码、数据目录异常 | 限制同设备账号数 |
| 手机内虚拟机 / ARM 容器云手机 | 无硬件级证明、机房 IP、传感器缺失 | 限制交易和高价值产出 |
| 板卡真机云手机 | 客户端基本正常,看 IP 段、指纹、操作节奏 | 收益封顶,加人工抽查 |

工作室的账按台算。想过这关,要么花钱弄一台能过硬件证明的单人真机,要么认了每个号都被压在上限以下,哪条路单台利润都变薄。