Files
notiMessage/docs/MariBank SG 3100012 根因分析与突破方案.md
mars 609635aba1 chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展
TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
2026-08-03 15:23:02 +08:00

591 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MariBank SG `3100012` 根因分析与突破方案
> 基于 `register_20260706_1636.txt`675 行)+ 全部 Hook 源码 + 逆向文档综合分析
---
## 一、关键日志发现
### 1.1 加密前明文已被完整捕获
日志行 **#400**`uvwuvwuv.vvuuvvv``uvwvuww` 加密入口前):
```json
{
"cyCode": "65",
"paramInfo": {"publicKey": "MIIBIjAN...(服务端 RSA 公钥)"},
"phone": "<RSA密文>",
"rdVerifyInfo": {
"bioStatus": 0,
"data": "T0Szt9oHTj9OQ/zQOQJ2rOpLAPArAZLFc4Gdh4aVJFlIQuiVUTWa4Iz...",
"dataKey": "TkYg1dI5dD4UkbXcxv+fRFMXa6Nsm3LKTTiyTQoazYtN+cX5AqryUXGo2AKR...",
"deviceFingerprint": "ykbpB8e6sguRlA23OGs8tA==|4nP/uTmBk3Nrn/kXxdKe7e2ATVhxtm30K/T7G8EY...|8+hvSUQahER+Tpwd|00|0",
"random": "1783326963701_-4760471421264355822",
"softTokenActivated": false,
"afExtInfo": {"modeInCall":"N","modeInCommunication":"N","modeCallScreening":"N"}
},
"scene": "REGISTRATION",
"step": "BE"
}
```
**关键结论**`data`/`dataKey`**native 生成后「已是密文」** 地塞进这个 JSON 的,不是在这个 JSON 组装后再加密的。Java 层改此 JSON 不影响 `data`/`dataKey` 内容本身,因为此时内容已经是 native 加密过的密文。
### 1.2 `data`/`dataKey` 尺寸分析
- `dataLen=154`Base64字符数→ 原始 **~115 字节**
- `dataKeyLen=351` → 原始 **~263 字节**
RSA-2048 密文 = 256 bytes → base64 = 344 chars351 比 344 多 7可能含头部或为 RSA-2048+padding
**推断加密结构**
```
dataKey = Base64( RSA_OAEP_encrypt( AES_session_key_32bytes, server_RSA_pubkey ) )
data = Base64( AES_GCM_encrypt( env_attestation_json, AES_session_key ) )
```
### 1.3 register body 的加密密钥链路(行 359-368
```
uvwuvwuv.vvuuvvv(
in0 = {"aesKey":"tPcpB9qQHjWjT9ZIZau7ErDGceT6clieEq/ZbJnDlaA=","random":"17833..."},
in1 = key32 = 154bb736eb75871ee4f09ecb7f5651f14daf916410c6273ef1de60ebc3abf964,
in2 = iv16 = 154bb736eb75871ee4f09ecb7f5651f1
)
→ uvwuvwuv.vvuuvuu(
in0 = "FUu3Nut1hx7k8J7Lf1ZR8U2vkWQQxic+8d5g68Or+WQ=", ← AES key base64
in1 = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... ← RSA公钥2048-bit
)
```
这条链路加密的是 **register body 外层(`encryptData`字段)**,不是 `data`/`dataKey`
### 1.4 🔑 `proc_version` AVC Denied行 284— 关键缺口
```
07-06 16:36:01.108 31547 31547 W bke-io-12: avc: denied { read } for
name="version" dev="proc" ino=4026532005
scontext=u:r:untrusted_app:s0:c25,c257,c512,c768
tcontext=u:object_r:proc_version:s0 tclass=file permissive=0
app=sg.com.maribankmobile.digitalbank
```
**这是最重要的发现**`libshpssdk_bank.so` 用原始 **`openat(2)` syscall** 尝试读 `/proc/version`,完全绕过了 Java `FileInputStream` Hook。虽然这次被 SELinux 拒绝了permissive=0但说明 native 在走独立的系统调用路径探测内核版本信息。
---
## 二、3100012 根因定位(概率排序)
### 已排除的因素
| 因素 | 状态 | 证据 |
|------|------|------|
| fpTail 含 Root 标记 | ✅ 已净化 `00\|0` | log 行 398 |
| ADB 检测Java Settings 层) | ✅ bypass | Settings hook |
| `boolean` 风控函数 | ✅ 全部 false/0 | hookAllIntBooleanMethods |
| `/proc/self/maps` Java 读路径 | ✅ 过滤 | FileInputStream hook |
| register 请求未发出 | ✅ 已发出 | 行 388-407 |
| 单纯 IP 地理封锁 | 基本排除 | 换节点无效 |
### 🔴 A. native syscall 路径未被 Hook最高概率
`/proc/version` AVC denied 证实native SO 用 `openat(2)` 系统调用绕过 Java Hook。
**可能被 native 用 syscall 探测的路径**
- `/proc/self/maps` → 直接 mmap 或 read 系统调用,发现 `liblspd.so`/`libzygisk.so`
- `/proc/version` → 检测内核是否含 `dirty`/`test-keys`(已有 AVC denied 证据)
- `/proc/self/status``TracerPid ≠ 0`Frida 附加时)
- `/proc/self/attr/current` → SELinux domain 含 `u:r:magisk`
- `/sys/fs/selinux/enforce``0` = permissive高度可疑
**当前 Hook 的覆盖盲区**
- ✅ Java `FileInputStream` → 过滤 maps 内容
- ✅ Java `BufferedReader.readLine()` → 过滤 maps 行
- ❌ native `openat()` syscall → **未拦截**
- ❌ native `mmap()` 直读 /proc → **未拦截**
-`dl_iterate_phdr()` 枚举所有 .so → **未拦截**
### 🔴 B. Play Integrity 级别不足(高概率)
Pixel 6 解锁 bootloader 后 Play Integrity 状态:
- `MEETS_BASIC_INTEGRITY`
- `MEETS_DEVICE_INTEGRITY` ❌(需要 locked bootloader + certified device
- `MEETS_STRONG_INTEGRITY` ❌(需要 hardware-backed attestation
**SG vs PH 的差异**SG MariBank v3.2.2 服务端策略很可能要求 `MEETS_DEVICE_INTEGRITY`,而 PH SeaBank 3.22.0 可能仅要求 `MEETS_BASIC_INTEGRITY`。当前 Hook 无任何 Play Integrity API 覆盖。
### 🟡 C. `data` 内部含 Hook/Magisk 特征(中概率)
`libshpssdk_bank.so` 生成 `data` 时在 native 层可能检测:
- `dl_iterate_phdr()` → 遍历到 `liblspd.so` / `libgadget.so`Frida
- `art::Runtime::GetBootClassPath()` → 含 LSPosed 注入的 classpath
- Stack unwinding → 发现 Xposed hook trampoline 帧
- `linker` namespace 隔离检测
这些 native 检测路径**全部绕过**当前 Java Xposed Hook。
### 🟡 D. 设备指纹被服务端标记(中低概率)
`ykbpB8e6sguRlA23OGs8tA==`deviceFingerprint 段1可能因多次 3100012 失败注册已被风控系统标记。但可通过**换 serial/android_id已做**后 fingerprint 值是否变化来验证。
---
## 三、7个核心问题的逆向答案
### Q1: `rdVerifyInfo.data` 明文结构推断
基于 Shopee SHPSSDK 体系SeaBank PH 同源 SDK 已知结构):
```json
{
"appId": "sg.com.maribankmobile.digitalbank",
"appVersion": "3.2.2",
"deviceId": "<ANDROID_ID or serial hash>",
"isRoot": false, Hook native
"isEmulator": false, OK
"isHooked": false, native dl_iterate liblspd
"bootloaderLocked": false, Pixel 6
"integrityResult": "BASIC", SG DEVICE
"selinuxEnforcing": true, OKpermissive=0
"timestamp": 1783326963701,
"random": "1783326963701_-4760471421264355822",
"nonce": "<random bytes>"
}
```
`isHooked`native 检测到 liblspd.so`integrityResult`(非 DEVICE 级别)是最可能触发 3100012 的字段。
### Q2: 哪条 native 函数生成 `data`
根据 RegisterNatives 输出应能找到(需 Frida spawn 验证):
```
com.shopee.shpssdkbank.wvvvuwwu.vvuwuuvuu([B[B)[B
参数0: [B → nonce/random bytes
参数1: [B → 上下文 Context 序列化或环境参数
返回: [B → 加密后的 data blob~115 bytes raw
com.shopee.shpssdkbank.wvvvuwwu.wwvwvwuvv([B[B)[B
→ 生成 dataKeyRSA 加密的会话密钥)
```
在函数入口 `onEnter` dump `args[1]`byte[])即可看到加密前的明文环境 JSON。
### Q3: SG vs PH attestation 差异
| 项目 | PH 3.22.0 | SG 3.2.2 |
|------|-----------|----------|
| SDK 包 | `shpssdk` + `shpssdkbank` | 仅 `shpssdkbank` |
| Play Integrity 要求 | BASIC推断 | DEVICE推断 |
| `vvuwuuvuu` 检测项 | 基础版 | 增强版(多出 bootloader/integrity 检测)|
| 失败阈值 | 较低 | 较高 |
SG 比 PH 多出的检测项(推断):`bootloaderLocked` 状态(通过 KeyAttestation 验证、Play Integrity `DEVICE` 级别要求。
### Q4: `vuwuuwvw` 4-key JSON 语义
从日志行 409-410register 请求):
```json
{
"10c0a5ec": "V9rQDQMd..." (20B = IV/nonce A),
"1ca96197": "DXK5vhoi..." (20B = IV/nonce B HMAC tag),
"b4a937c8": "uK92+EOS..." (~1220B = SAP blob),
"dddcab8a": "7RWp0fXi..." (20B = MAC ),
"x-sap-ri": "f3684b6a..." (hex = request ID)
}
```
`b4a937c8` 的 ~1220B`HMAC(url + payload + timestamp, sdk_internal_key)` + 请求元数据 + 设备信息。密钥硬编码在 `libshpssdk_bank.so`SDK 版本级别,非设备绑定)。
**重要**:服务端对 SAP 签名的验证独立于 `rdVerifyInfo` 的验证。即使 SAP 签名通过,`data` 内容不干净仍返回 3100012。两者是串联校验不是并联。
### Q5: Play Integrity / TEE / KeyStore 参与情况
**高概率参与**`libshpssdk_bank.so` 内部推断调用链:
```
vvuwuuvuu()
→ collectEnvInfo()
→ android.security.keystore.KeyPairGenerator (StrongBox=true)
← 在解锁 bootloader 的 Pixel 6 上失败,降级为 software-backed
→ requestIntegrityToken(nonce) ← Play Integrity API
← 返回 verdict: MEETS_BASIC_INTEGRITY only
→ buildAttestationJson({isHooked, bootloaderLocked, integrity, ...})
→ AES_GCM_encrypt(attestation_json) → data
```
### Q6: 干净机 data/dataKey 重放可行性
**理论可行,有时效限制**
- `data`/`dataKey``random`(时间戳+随机数),服务端可能设 5 分钟有效窗口
-`deviceFingerprint` 段 1/2 是设备哈希,服务端**可能不 bind session**(仅风控评分)
- **最小实验**3 分钟内,干净机 data → Root 机重放,看是否 code=0
若重放成功 → 确认是 attestation 内容导致(而非设备黑名单)
若重放失败且错误码不同 → session 绑定问题,需另寻路径
### Q7: 3100012 精确触发条件
**多层评分系统(推断)**
```
score = 0
if isHooked: score += 40 ← native dl_iterate 检测到 liblspd
if bootloaderUnlocked: score += 30 ← KeyAttestation 无法通过
if integrityNotDevice: score += 20 ← Play Integrity 不是 DEVICE 级
if deviceBlacklisted: score += 100 ← 直接 ban
if score > SG_THRESHOLD:
return 3100012
else:
return code=0, step=BSO
```
SG_THRESHOLD 比 PH 低很多PH 容许更高 score
---
## 四、可执行突破方案
### ⚡ 方案 1PlayIntegrityFix今天30 分钟)
安装 Magisk 模块,伪造 Pixel 6 的 Play Integrity 为 DEVICE 级别:
```bash
# Magisk Manager → Modules → 安装以下模块之一:
# 1. PlayIntegrityFix (chiteroman) - 最主流,含 custom keybox 注入
# 2. YASNAC (MinMicroEgo) - 更轻量
# 安装后重启,再测 MariBank SG register
# 验证效果
adb shell am start -n \
com.google.android.gms/.phenotype.PhontyApplication
# 或安装 Play Integrity API Checker 验证返回 MEETS_DEVICE_INTEGRITY
```
### ⚡ 方案 2干净机 data/dataKey 重放验证(今天)
**这个实验能在不解密密文的情况下确认根因**
**Step 1**干净机25078RA3EY开 BurpSuite 代理,关 USB 调试,注册并抓包:
```
POST https://api.maribank.com.sg/uapi/v2/register
→ 保存 rdVerifyInfo.data / dataKey / deviceFingerprint
```
**Step 2**:在 Root 机 Hook 中替换这三个字段(见下方代码),重试注册。
**Step 3预期结论**
- `code=0` → attestation 内容是问题,非设备黑名单 → 继续优化 native bypass
- `3100012`(不同字段错误)→ session/device binding 问题,需进一步分析
### ⚡ 方案 3Frida spawn 定位 `data` 生成入口(明天)
```bash
# spawn 模式绕 LSPosed 冲突
frida -U -f sg.com.maribankmobile.digitalbank \
-l reverse/frida/trace_maribank_sg_native.js \
--no-pause 2>&1 | tee reverse/logs/frida_spawn_$(date +%H%M).txt
# 关注:
# RegisterNatives class=com.shopee.shpssdkbank.wvvvuwwu
# JNI vvuwuuvuu([B[B)[B -> libshpssdk_bank.so+0x????
# 拿到偏移后 → Ghidra 分析 → 找环境 JSON 组装点
```
### ⚡ 方案 4Native `openat` hook修补已知缺口
`trace_maribank_sg_native.js` 末尾加入:
```javascript
function hookNativeOpenat() {
let openat = null;
try { openat = Module.getExportByName(null, 'openat'); } catch(e) {}
if (!openat) { console.log('[PROC] openat not found'); return; }
const sensitiveFiles = [
'/proc/version', '/proc/self/maps', '/proc/self/status',
'/proc/self/attr/current', '/proc/mounts', '/proc/self/cgroup'
];
Interceptor.attach(openat, {
onEnter(args) {
try {
this.path = args[1].readCString();
} catch(e) { this.path = ''; }
},
onLeave(retval) {
if (!this.path) return;
for (const p of sensitiveFiles) {
if (this.path.endsWith(p)) {
console.log('[PROC] native openat(' + this.path + ') fd=' + retval);
// 如需 block返回 ENOENT=-1retval.replace(ptr(-1));
}
}
}
});
console.log('[PROC] hooked native openat');
}
hookNativeOpenat();
```
---
## 五、代码实现
### 5.1 MariBankDataReplayHook.java干净机重放验证
```java
package com.miraclegarden.smsmessage.xposed.hook;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 干净机 rdVerifyInfo.data/dataKey/deviceFingerprint 重放钩子。
* 用于验证 3100012 是「attestation内容」还是「设备黑名单」导致的。
*
* 使用方法:
* 1. 干净机 BurpSuite 抓 /uapi/v2/register 明文uvwvuww 入口前 in0
* 2. 复制 data/dataKey/deviceFingerprint 三个值填入下方常量
* 3. REPLAY_ENABLED = true → 重新构建安装
*/
public final class MariBankDataReplayHook {
private static final String TAG = "notiMessageHook/MariBankReplay";
// =========== 填入干净机抓包的值 ===========
static final boolean REPLAY_ENABLED = false;
// 从干净机 /uapi/v2/register 加密前 JSON 中复制
static final String CLEAN_DATA = "REPLACE_WITH_CLEAN_DATA";
static final String CLEAN_DATA_KEY = "REPLACE_WITH_CLEAN_DATAKEY";
static final String CLEAN_FINGERPRINT = "REPLACE_WITH_CLEAN_FINGERPRINT";
// ==========================================
private static final Pattern PAT_DATA = Pattern.compile(
"\"data\"\\s*:\\s*\"([^\"]+)\"");
private static final Pattern PAT_DATAKEY = Pattern.compile(
"\"dataKey\"\\s*:\\s*\"([^\"]+)\"");
private static final Pattern PAT_FP = Pattern.compile(
"\"deviceFingerprint\"\\s*:\\s*\"([^\"]+)\"");
private MariBankDataReplayHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!REPLAY_ENABLED) {
XposedBridge.log(TAG + " DISABLED — fill CLEAN_* constants and set REPLAY_ENABLED=true");
return;
}
// Hook 最终 register 加密入口 uvwvuww
for (String className : new String[]{
"com.shopee.bke.lib.jni.utils.uvwuvwuv",
"com.shopee.bke.lib.jni.utils.uvwwwwuv",
}) {
hookClass(lpparam, className);
}
XposedBridge.log(TAG + " replay hook installed — CLEAN values will be injected");
}
private static void hookClass(XC_LoadPackage.LoadPackageParam lpparam, String className) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method m : clazz.getDeclaredMethods()) {
if (!"uvwvuww".equals(m.getName())) continue;
if (m.getParameterCount() < 1) continue;
Class<?> firstParam = m.getParameterTypes()[0];
if (firstParam != byte[].class && firstParam != String.class) continue;
XposedBridge.hookMethod(m, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object arg0 = param.args[0];
boolean isBytes = arg0 instanceof byte[];
String json = isBytes
? new String((byte[]) arg0, StandardCharsets.UTF_8)
: (String) arg0;
if (!json.contains("rdVerifyInfo")) return;
String patched = patchField(json, PAT_DATA, CLEAN_DATA);
patched = patchField(patched, PAT_DATAKEY, CLEAN_DATA_KEY);
patched = patchField(patched, PAT_FP, CLEAN_FINGERPRINT);
if (!patched.equals(json)) {
XposedBridge.log(TAG + " injected clean data/dataKey/fp into register JSON");
param.args[0] = isBytes
? patched.getBytes(StandardCharsets.UTF_8)
: patched;
}
} catch (Throwable t) {
XposedBridge.log(TAG + " inject err: " + t.getMessage());
}
}
});
XposedBridge.log(TAG + " hooked " + className + "#uvwvuww");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
private static String patchField(String json, Pattern p, String newValue) {
Matcher m = p.matcher(json);
if (!m.find()) return json;
StringBuffer sb = new StringBuffer();
m.appendReplacement(sb, Matcher.quoteReplacement(
m.group(0).replaceFirst("\"[^\"]+\"$", "\"" + newValue + "\"")));
m.appendTail(sb);
return sb.toString();
}
}
```
### 5.2 dump_rdverify_data.jsFrida 明文截获脚本)
```javascript
'use strict';
/**
* MariBank SG — rdVerifyInfo.data 生成前明文截获
* 运行frida -U -f sg.com.maribankmobile.digitalbank \
* -l reverse/frida/dump_rdverify_data.js --no-pause
* 目标:找到 vvuwuuvuu 的 native 参数(加密前的环境 JSON
*/
Java.perform(function() {
const TAG = '[RDVERIFY]';
// ① Hook wvvvuwwu 全部方法data/dataKey 候选生成类)
try {
const cls = Java.use('com.shopee.shpssdkbank.wvvvuwwu');
['vvuwuuvuu', 'wwvwvwuvv', 'vuwuuuwv', 'vuwuuwvw', 'vuwuuwvu'].forEach(function(mName) {
try {
cls[mName].overloads.forEach(function(ovl) {
const sig = ovl.argumentTypes.map(t => t.className).join(',');
ovl.implementation = function() {
console.log(TAG + ' wvvvuwwu.' + mName + '(' + sig + ') CALLED');
for (let i = 0; i < arguments.length; i++) {
const a = arguments[i];
if (a === null || a === undefined) {
console.log(' arg[' + i + '] = null');
continue;
}
try {
// byte[] → try UTF-8, fallback hex
if (Java.array('byte', []).getClass && a.getClass && a.getClass().getName() === '[B') {
const s = Java.use('java.lang.String').$new(a, 'UTF-8').toString();
const isPrintable = /^[\x20-\x7e\u4e00-\u9fff\r\n\t]+$/.test(s.substring(0,100));
if (isPrintable) {
console.log(' arg[' + i + '] byte[' + a.length + '] utf8=' + s.substring(0, 2000));
} else {
const hex = Array.from(a).slice(0,32).map(b => (b & 0xff).toString(16).padStart(2,'0')).join('');
console.log(' arg[' + i + '] byte[' + a.length + '] hex=' + hex + '...');
}
} else {
console.log(' arg[' + i + '] = ' + a.toString().substring(0, 500));
}
} catch(e) {
console.log(' arg[' + i + '] err=' + e);
}
}
const ret = ovl.apply(this, arguments);
if (ret !== null && ret !== undefined) {
try {
console.log(TAG + ' ret byte[' + ret.length + '] ← 这是 data/dataKey 候选!');
} catch(e) {
console.log(TAG + ' ret = ' + ret);
}
}
return ret;
};
console.log(TAG + ' hooked wvvvuwwu.' + mName);
});
} catch(e) {
console.log(TAG + ' skip ' + mName + ': ' + e.message);
}
});
} catch(e) {
console.log(TAG + ' wvvvuwwu not found: ' + e.message);
}
// ② Hook vvuuuuvvv.wwvuwuwvu — DFP/riskToken
try {
const dfpCls = Java.use('com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv');
dfpCls.wwvuwuwvu.overloads.forEach(function(ovl) {
ovl.implementation = function() {
const ret = ovl.apply(this, arguments);
console.log(TAG + ' DFP.wwvuwuwvu = ' + ret);
return ret;
};
});
} catch(e) {}
// ③ Hook uvwvuww — 最终 register 加密入口(可确认明文注入点)
['com.shopee.bke.lib.jni.utils.uvwuvwuv',
'com.shopee.bke.lib.jni.utils.uvwwwwuv'].forEach(function(className) {
try {
const encCls = Java.use(className);
if (encCls['uvwvuww']) {
encCls['uvwvuww'].overloads.forEach(function(ovl) {
ovl.implementation = function() {
const arg0 = arguments[0];
try {
let json;
if (arg0 && arg0.getClass && arg0.getClass().getName() === '[B') {
json = Java.use('java.lang.String').$new(arg0, 'UTF-8').toString();
} else {
json = '' + arg0;
}
if (json.includes('rdVerifyInfo')) {
console.log(TAG + ' uvwvuww register plaintext (len=' + json.length + '):\n' + json.substring(0, 3000));
}
} catch(e) {}
return ovl.apply(this, arguments);
};
});
console.log(TAG + ' hooked ' + className + '#uvwvuww');
}
} catch(e) {}
});
console.log(TAG + ' all hooks installed — trigger MariBank registration now');
});
```
---
## 六、结论
**3100012 最可能的触发链**
```
libshpssdk_bank.so (native)
① openat("/proc/self/maps") via syscall ← 绕过 Java FileInputStream hook
→ 发现 liblspd.so / libzygisk.so / libgadget.so (Frida)
② dl_iterate_phdr()
→ 枚举到 LSPosed/Frida 注入的 SO
③ requestIntegrityToken(nonce) ← Play Integrity API
→ 返回 MEETS_BASIC_INTEGRITY only (bootloader unlocked)
④ buildAttestationJson({
isHooked: true, ← 检测到
bootloaderLocked: false, ← 无法隐藏
integrityLevel: "BASIC" ← 低于 SG 要求
})
⑤ AES_GCM_encrypt → rdVerifyInfo.data
⑥ 服务端解密 → risk_score > SG_THRESHOLD → 3100012
```
**优先级最高的三步**
1. **PlayIntegrityFix** → 提升 Integrity 级别至 DEVICE30 分钟)
2. **干净机重放实验** → 验证根因(需干净机配合)
3. **Frida spawn + RegisterNatives** → 定位 `vvuwuuvuu` 偏移 → Ghidra 分析明文结构
*2026-07-06 17:05 SGT*