Files
notiMessage/docs/MARIBANK_IMPLEMENTATION.md
Mars d488a0759f docs: MariBank 实现说明与登录/注册对比
补充 MARIBANK_IMPLEMENTATION 专文及工作说明中的登录路径分析、API 对照与测试建议。
2026-07-03 17:33:39 +08:00

568 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 实现说明与问题记录
> **日期**2026-07-03
> **App**MariBank / SeaBank PH · `ph.seabank.seabank` · v3.22.0 (32200)
> **设备**Pixel 6 · Magisk + Zygisk + LSPosed
> **源码**`xposed-module/.../hook/MariBank*.java`
本文档专门说明当日对 MariBank **注册流程风控绕过** 的 Xposed 实现细节,以及开发/测试中遇到的全部主要问题。通用工作汇总见 [`WORKLOG_2026-07-03.md`](WORKLOG_2026-07-03.md)。
---
## 1. 目标与结果概览
### 1.1 目标
在已 Root 设备上完成:
```
打开 App → Sign up → 输入菲律宾手机号 → Next
→ POST https://api.seabank.ph/uapi/v2/register
→ 进入 OTP / 下一步
```
### 1.2 当日最终结果
| 阶段 | 结果 |
| -------------------- | -------------------------------------- |
| 本地 Root 检测 / 自杀 / 弹窗 | **大部分绕过**,可稳定进入注册页 |
| DFP 设备指纹上报 | **成功**`code=0` |
| riskToken 尾部字段净化 | **Hook 生效**`\|09\|1``\|00\|0` |
| 注册接口 | **失败**,服务端返回 **4067012** / **4067004** |
| 注册请求明文 | **未抓到**body native 加密) |
**结论**:本地层 bypass 已推进到能正常发起注册请求,但 **服务端风控仍拒绝**;当前实现不足以完成注册。
---
## 2. App 风控架构(逆向结论)
MariBank 基于 Shopee BKE 技术栈,风控分 **本地 SDK 层****服务端校验层**
```
┌─────────────────────────────────────────────────────────┐
│ MariBank App │
├─────────────────────────────────────────────────────────┤
│ SafeMode SDK │ Root/篡改检测 → Toast + 自杀 │
│ SHPSSDK (bank) │ riskToken / DFP / requestDefense │
│ libshpssdk_bank.so │ native/proc/maps、hook 库扫描 │
│ libsdkutils.so │ register body native 加密 │
├─────────────────────────────────────────────────────────┤
│ OkHttp / Retrofit │ 出站 HTTP │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
POST /dfp/v1/data/report POST /uapi/v2/register
(设备指纹 + riskToken) (加密 body + 手机号)
│ │
└────────── api.seabank.ph ────┘
4067012 服务端拒绝
```
### 2.1 关键类与 SOv3.22.0
| 组件 | 真实符号 | 说明 |
| ----------- | ------------------------------------------- | ------------------------------------------------- |
| Application | `com.shopee.bke.digitalbank.BkeApplication` | `attachBaseContext` 后 ClassLoader 才完整 |
| SafeMode | `com.shopee.bke.lib.safemode.b` 等 | 方法名混淆,多返回 `boolean`/`int` |
| SHPSSDK | `com.shopee.shpssdkbank.SHPSSDK` | `getRiskToken` / `getRiskSync` / `requestDefense` |
| riskToken 链 | `vvuuuuvvv.wwvuwuwvu(Context)` | classes11.dex真实 token 生成入口 |
| SoUtils | `com.shopee.bke.lib.jni.utils.f` | 加载 `libsdkutils.so`log tag `SoUtils` |
| 加密包装 | `com.shopee.bke.lib.jni.utils.d` | Java 层加密入口 |
| Native 加密 | `com.shopee.bke.lib.jni.utils.uvwuvwuv` | JNI`libsdkutils.so` / `libbkutils.so` |
| SHPS native | `wvvvuwwu.vvuwuuvuu``wwvwvwuvv` | `libshpssdk_bank.so` |
| 字符串解密 | `uvuwwuvwv.uvwwuuvvw.uvuwwwuwu` | 解密 `x-sap-fixme` 等 |
### 2.2 注册相关 API
| 接口 | 作用 | 当日观测 |
| -------------------------- | ------------- | ---------------- |
| `POST /dfp/v1/data/report` | 设备指纹 / DFP 上报 | **成功** `code=0` |
| `POST /uapi/v2/register` | 手机号注册 | **失败** `4067012` |
### 2.3 riskToken 格式(尾部字段)
token 为 pipe 分隔字符串,**最后两段**表示风险标记,例如:
```text
...Base64段|Base64段|xx|09|1
↑ ↑
风险码 Hook/Root 等标志
```
当日 Hook 将尾部 `09|1` 改为 `00|0`(认为表示 Root + Hook 命中。logcat 可见净化日志,但 **register 仍被拒**,说明服务端还校验 token 其他段或独立 DFP 载荷。
### 2.4 登录 vs 注册(当日未测登录,以下为逆向 + 推断)
当日实测路径为 **Sign up → 注册****Log in 登录尚未在设备上验证**。以下基于 APK 字符串扫描与 Hook 架构整理。
#### 2.4.1 现有 Hook 是否覆盖登录
**是。** 当前 Xposed 按 **整包** `ph.seabank.seabank` 加载,不区分注册/登录:
| 能力 | 登录是否同样生效 |
|------|------------------|
| SafeMode / Root 自杀、弹窗拦截 | ✅ |
| SHPSSDK / riskToken、deviceToken 出站净化 | ✅ 所有 OkHttp 请求 |
| DFP `POST /dfp/v1/data/report` | ✅ App 启动即上报 |
| `/proc` 过滤、boot 属性伪装 | ✅ |
**无需为登录单独写 Hook**;能稳定进入 App登录页同样受益。
#### 2.4.2 API 路径不同
登录 **不走** `/uapi/v2/register`,而是独立 auth 链路v3.22.0 base APK DEX 扫描):
| 场景 | 典型路径 |
|------|----------|
| **注册(已实测)** | `POST /uapi/v2/register` |
| **登录预检** | `/v2/auth/precheck``/v1/pin/auth/precheck` |
| **OTP 登录** | `/v2/app/login/otp/get``/v2/app/login/otp/verify` |
| **PIN 登录** | `/v1/pin/auth` |
| **受限环境** | `/v2/restricted/auth``/v2/restricted/auth/precheck` |
| **通用 OTP** | `/v1/otp/send``/v1/otp/verify` |
| **设备指纹(共用)** | `POST /dfp/v1/data/report` |
ViewModel 层面:`RegisterViewModel``LoginViewModel` / `PinCodeViewModel` / `OneTimePasswordViewModel` 分离;**`PhoneNumViewModel` 可能与注册共用**(输入手机号 UI 类似)。
错误展示由 `GlobalAuthErrorImpl`**通用鉴权错误处理** 负责(`matchScene` 多场景4067 封锁弹窗 **不限于注册接口**
#### 2.4.3 登录可能的结果(推断)
| 情况 | 登录表现(推断) |
|------|------------------|
| **设备指纹已被服务端拉黑** | 可能在 `/v2/auth/precheck``/v2/app/login/otp/get` 即返回 **4067012**,与注册类似 |
| **仅「新开户」风控更严** | 若设备未全局封禁,**已有账户 + 正确 PIN/OTP** 的登录 **有机会** 通过 precheck |
| **本地 bypass 足够、服务端仍拒** | DFP `code=0` 但 auth 接口 4067 — 与注册相同,改 riskToken 尾部 **不够** |
#### 2.4.4 注册 vs 登录对比
| 维度 | 注册 (Sign up) | 登录 (Log in) |
|------|----------------|---------------|
| 前提 | 新菲律宾手机号 | **已有 MariBank 账户** + PIN 或 OTP |
| 关键 API | `/uapi/v2/register` | `/v2/auth/precheck``/login/otp/*``/pin/auth` |
| 风控强度 | 新户反欺诈通常 **更严** | 老户相对松,**设备黑名单仍生效** |
| 当日实测 | ❌ 4067012 | ❓ **未测** |
| Hook 专用日志 | 代码中对 `/register` 有较详细 body 日志 | 其它 URL 仅有通用 HTTP / 4067 日志 |
#### 2.4.5 若改测登录
1.**Log in**(非 Sign up输入 **已开户** 手机号
2. logcat`powershell -File scripts\logcat-maribank.ps1 -Follow`
3. 重点观察:`/v2/auth/precheck``/v2/app/login/otp/get``/v1/pin/auth``/dfp/v1/data/report` 的响应码
4. 若仍为 **4067012** → 倾向 **设备/环境级封锁**,换登录路径无法绕过
5. 若 precheck 通过、仅 OTP/PIN 失败 → 多为 **账户凭证** 问题,非 Root bypass 范畴
多次注册失败可能已加重设备风控,登录测试前建议 **冷却 2448h** 或使用未试过的环境。
---
## 3. Xposed 实现架构
### 3.1 入口与加载顺序
`MainHook.java``ph.seabank.seabank` 加载两个 Hook 类:
```java
MariBankShpsNativeHook.install(lpparam); // 第一阶段early
MariBankRootBypassHook.install(lpparam); // 第一阶段 + 调度 deferred
```
**两阶段加载**是当日最关键的工程决策之一:
| 阶段 | 时机 | 安装内容 |
| ------------ | ----------------------------------------- | ------------------------------------------- |
| **Early** | `loadPackage` 立即 | `/proc` 读过滤、系统属性伪装、`Process.killProcess` 拦截 |
| **Deferred** | `BkeApplication.attachBaseContext` **之后** | SafeMode、SHPSSDK、OkHttp 出站净化、riskToken Hook |
**原因**:过早 Hook SHPSSDK / `SoUtils.loadSoLibrary` 会导致 `**libsdkutils.so` 加载死循环 → 白屏**(见 §5.2)。
### 3.2 文件职责
| 文件 | 职责 |
| ----------------------------- | --------------------------------------------------------------------- |
| `MariBankShpsNativeHook.java` | Native 层检测绕过:`/proc/self/maps` 过滤、boot 属性伪装、Build 字段、`requestDefense` |
| `MariBankRootBypassHook.java` | Java 层SafeMode、SHPSSDK risk、OkHttp 出站净化、弹窗/自杀拦截、4067 日志 |
| `MariBankRiskTokenUtil.java` | riskToken / deviceToken 尾部净化工具类 |
---
## 4. 实现细节(按模块)
### 4.1 反自杀与反「假闪退」(`MariBankRootBypassHook`
**问题背景**SafeMode 检测到 Root 后会 `Toast` + `Process.killProcess` + `Activity.finishAffinity`,表现为闪退。
**实现**
1. Hook `Process.killProcess(self)``System.exit` → 直接拦截
2. Hook `Activity.finish` / `finishAffinity` / `finishAndRemoveTask` / `moveTaskToBack`
- 在 **8 秒宽限期**内(`SOFT_CRASH_GUARD_MS`)阻止 BKE 相关 Activity 被风控栈关闭
- 通过栈追踪识别 `safemode` / `shpssdk` / `errorcodehandler` 等调用来源
- **保留用户按返回键**`isUserBackNavigation()`
3. 拦截 Root 相关 Toast 文案(`rooted or jailbroken``magisk/xposed/frida` 等)
**logcat 典型输出**
```text
notiMessageHook/MariBankRoot: blocked killProcess(self)
notiMessageHook/MariBankRoot: blocked finishAffinity on com.shopee.bke...
```
### 4.2 SafeMode 检测绕过
**目标类**(方法名混淆,无法按名 Hook
- `com.shopee.bke.lib.safemode.b`
- `com.shopee.bke.lib.safemode.catchs.a`
- `com.shopee.bke.lib.safemode.util.c`
- `com.shopee.bke.biz.base.risk.a`
**策略**Hook 所有 **非 static、参数 ≤2、返回 boolean/int** 的实例方法,统一返回 `false` / `0`
**弹窗**Hook `SafeModeRecoverActivity` 相关 Dialog / AlertDialog.show阻止 Root 提示框。
### 4.3 SHPSSDK 风控Java 层)
**boolean 检测类**
- `com.shopee.shpssdkbank.SPSAssessRisk`
- `com.shopee.shpssdk.SPSAssessRisk`
**Token 相关 Hook**`hookShpsToken`
| 方法 | 处理 |
| ------------------------------------ | -------------------------------------------------- |
| `getRiskSync` / `getRiskAsync` | 返回 **空 risk 列表**,避免 `RISK_ROOT=1``RISK_HOOK=4` 写入 |
| `getRiskToken` / `getRiskTokenAsync` | 返回值经 `MariBankRiskTokenUtil.sanitizeRiskToken` |
| `vvuuuuvvv.wwvuwuwvu(Context)` | 真实 token 生成链,最早出口净化 |
| `getSecData` 等 | 字符串返回值净化 |
**RISK 常量(逆向)**`RISK_ROOT=1``RISK_HOOK=4` 等会编码进 token 尾部。
### 4.4 Native 层检测绕过(`MariBankShpsNativeHook`
`**/proc` 过滤**
- Hook `FileInputStream` / `BufferedReader` / `RandomAccessFile`
-`/proc/self/maps``/proc/version``/proc/self/status` 等读结果 **过滤敏感行**
- 隐藏 marker`xposed``lsposed``magisk``frida``zygisk``miraclegarden`
**Boot / 环境伪装**
- `SystemProperties.get` 伪装 `ro.boot.verifiedbootstate``ro.boot.vbmeta.device_state`
- `Build` 字段、`/proc/self/attr/current` → 伪造 SELinux 上下文
**SHPS native 桥**
- `requestDefense` → 解密/处理 `x-sap-fixme` 请求头
- `hookShpsNativeCore` → native 桥接类 boolean 返回 false
**刻意不 Hook**
```java
// MariBankShpsNativeHook.hookShpssInstall()
// 勿 Hook ShpssInstall / vuvuwwwuw会干扰 SoUtils.loadSoLibrary导致 libsdkutils.so 死循环白屏。
```
### 4.5 网络出站净化OkHttp 链)
**不能 Hook `RealInterceptorChain.proceed`**SO 硬编码检测),改为在更外层拦截:
| Hook 点 | 作用 |
| -------------------------------------- | ------------------- |
| `OkHttpClient.newCall` | 替换 `Request` 为净化版 |
| `RealCall.execute` / `enqueue` | 同上 |
| `Request.Builder.build` / `post(body)` | body 写入时净化 |
| `RequestBody.writeTo` | 写出字节时净化 |
| `Gson.toJson` / `fromJson` | JSON 中 riskToken 字段 |
| `JSONObject.put("riskToken", ...)` | 直接改字段 |
| `Response.request().url()` | 记录 URL关联 4067 响应 |
**净化逻辑**`MariBankRiskTokenUtil`
- 正则匹配 pipe-token 尾部 `\d+\|\d+` → 改为 `00|0`
- JSON 字段 `"riskToken"` / `"deviceToken"` 同步处理
**register body 日志**
```text
register request body unreadable (encrypted or one-shot)
```
说明 register 的 body 在 Java 层已是 **加密或一次性 RequestBody**,无法直接读明文。
### 4.6 错误码与弹窗监控
**服务端错误码**logcat 实测):
| code | 含义 |
| --------- | ------------------ |
| `4067004` | 安全封锁(与 4067012 同类) |
| `4067012` | 账户安全临时封锁 |
**客户端弹窗文案**(服务端下发,非本地 Root 弹窗):
```text
For your account's security, this service has been temporarily blocked...
(+632) 8424 8050
```
Hook 对含 `temporarily blocked` / `8424 8050` 的 Toast 会 **记录栈**`logBriefStack`),便于区分本地 vs 服务端弹窗。
---
## 5. 遇到的问题(完整清单)
### 5.1 服务端注册拒绝 — 4067012核心未解问题
**现象**
- 能进入 Sign up 页
- 请求发出后弹服务端封锁弹窗
- 多个号码(`9178854266``9133477799``9171243667` 等)均失败
**logcat**
```text
POST /uapi/v2/register
→ {"code":4067012,"msg":"For your account's security, this service has been temporarily blocked..."}
```
**分析**
1. **不是本地 Root 弹窗**——本地 bypass 后已能到达注册页并成功打 DFP
2. DFP 成功仅说明 **上报链路通**,不代表服务端认可设备
3. riskToken 尾部改掉 **不够**——token 主体可能含加密指纹,或服务端维护设备/号码黑名单
4. 多次失败可能 **加重封锁**(建议冷却 2448h
**待验证**
- 未 Root 干净机 + 菲律宾 IP + 新号码 → 区分设备封禁 vs 号码封禁
- Hook native 加密前明文,看清 register body 到底带了什么
---
### 5.2 过早 Hook 导致白屏libsdkutils 死循环)
**现象**App 启动白屏logcat 反复出现 `SoUtils` / `loadSoLibrary("sdkutils")`
**原因**
- `loadPackage` 时 split APK 的 ClassLoader **尚未绑定**
- 过早 Hook `ShpssInstall``vuvuwwwuw` 或完整 SHPSSDK 链 → 干扰 SO 加载顺序
**解决**
- Early 阶段 **只装** `/proc` 过滤 + 反自杀
-`BkeApplication.attachBaseContext` 回调后再 `installDeferredHooks`
- **永不 Hook** `ShpssInstall` / `vuvuwwwuw`
---
### 5.3 不能 Hook RealInterceptorChain.proceed
**现象**Hook 后触发 native 硬编码检测,行为异常或加重风控。
**逆向依据**`libshpssdk_bank.so` 字符串含 `RealInterceptorChain.proceed`
**解决**:改 Hook `OkHttpClient.newCall``RealCall.execute``RequestBody.writeTo` 等外层点。
---
### 5.4 register 请求体无法读取
**现象**logcat 始终为:
```text
register request body unreadable (encrypted or one-shot)
```
**原因**
- Body 经 `NativeEncryptUtilsWrapper` → native `uvwuvwuv` 加密
- 可能是 one-shot `RequestBody`,写入后不可重读
**影响**:无法在 Java 层确认手机号 JSON 明文内容,也无法在 Xposed 层直接改 register 字段(除 riskToken 出站净化外)。
**后续方向**Frida/native Hook `libsdkutils.so``utils.d` 包装方法。
---
### 5.5 wvvvuwwu.vuwuuwvw native 未找到
**现象**logcat 出现 native 方法实现找不到的警告。
**可能原因**
- LSPosed 与 Frida/Clash 等环境叠加干扰
- 混淆类在不同 split / 版本下位移
- Hook 时机与 ClassLoader 边界问题
**影响**:部分 SHPS native 桥接 Hook 可能未完全生效;但 DFP 已成功,说明主链路部分工作。
---
### 5.6 Frida 动态分析失败
**环境**Frida 17.15.3 + `/data/local/tmp/frida-server`
| 问题 | 详情 |
| --------------- | ------------------------------------------------- |
| attach 找不到 Java | 60s 内 `Java.available == false` |
| 进程名 | 需用 **MariBank** 而非 `ph.seabank.seabank` |
| spawn | native dlopen 可 hookJava 层 `Java is not defined` |
| CLI 退出 | spawn 无保活脚本会立刻 detach |
| 与 LSPosed 冲突 | 建议测 Frida 时 **关闭 LSPosed 对 MariBank 作用域** |
**结果**:未抓到 `/register` 明文 body当日分析 **主要依赖 LSPosed logcat**
脚本位置:`reverse/frida/trace_maribank_register.js``run_frida_trace.py`
---
---
### 5.7 本地 Root 检测 vs 服务端封锁(易混淆)
| 类型 | 文案特征 | 来源 | 当日状态 |
| ------- | -------------------------------------------- | ------------------ | ------------------- |
| 本地 Root | `rooted or jailbroken``magisk/xposed/frida` | SafeMode / SHPSSDK | **已拦截 Toast可继续使用** |
| 服务端封锁 | `temporarily blocked``8424 8050` | API 4067012 响应 | **未绕过** |
---
## 6. 实测 logcat 参考
### 6.1 成功片段DFP + token 净化)
```text
notiMessageHook/MariBankRoot: sanitized riskToken tail 09|1 -> 00|0
notiMessageHook/MariBankRoot: HTTP .../dfp/v1/data/report ... code=0
```
### 6.2 失败片段(注册)
```text
notiMessageHook/MariBankRoot: register request body unreadable (encrypted or one-shot)
notiMessageHook/MariBankRoot: HTTP .../uapi/v2/register ... code=4067012
notiMessageHook/MariBankRoot: security block Toast: For your account's security...
```
### 6.3 推荐过滤命令
```powershell
powershell -File scripts\logcat-maribank.ps1 -Follow
# 或
adb logcat -s notiMessageHook/MariBankRoot:V notiMessageHook/MariBankNative:V
```
---
## 7. 部署与验证
### 7.1 构建安装
```powershell
powershell -ExecutionPolicy Bypass -File scripts\build-debug.ps1
powershell -ExecutionPolicy Bypass -File scripts\install-debug.ps1
```
### 7.2 LSPosed 作用域
必须勾选:
- `ph.seabank.seabank`MariBank
- `com.miraclegarden.smsmessage`(主 App
- Xposed 模块自身
修改 Hook 后:**强制停止 MariBank** 再冷启动。
### 7.3 测试路径
**注册(当日已测)**
```
Sign up → 输入 10 位菲律宾号码(如 9XXXXXXXXX→ Next
观察 logcat 中 /dfp 与 /register 响应
```
**登录(当日未测,供对照)**
```
Log in → 输入已开户手机号 → 继续
观察 /v2/auth/precheck、/login/otp/* 或 /pin/auth 响应
详见 §2.4
```
---
## 8. 已知限制
1. **仅改 riskToken 尾部**不足以通过服务端 register 校验
2. **register body 加密**Java Hook 无法读明文、难改字段
3. **RealInterceptorChain** 等 SO 检测点不可 Hook
4. **ShpssInstall** 不可 Hook限制 SO 加载链上的干预手段
5. **Frida** 在当前 App 上 Java 桥不稳定
6. **多次失败** 可能导致号码/设备进入服务端黑名单
7. **登录流程未实测**;若设备已被 4067012 封锁,登录很可能同样失败(见 §2.4
---
## 9. 后续工作建议
| 优先级 | 方向 | 说明 |
| --- | -------------- | ---------------------------------------- |
| P0 | 对照实验 | 干净机 + PH IP + 新号,确认封禁维度;**有老户时可对比登录 precheck** |
| P1 | Native 明文 Hook | `utils.d` / `libsdkutils.so` 加密前抓包 |
| P2 | riskToken 全链 | 不仅尾部,需理解 pipe 各段含义及 DFP 载荷 |
| P3 | Frida 环境 | 无 LSPosed 冲突、或 Zygisk 隐藏 Frida 后再 attach |
| P4 | 停试冷却 | 已失败号码/设备 2448h 内勿反复请求 |
| — | 官方渠道 | (+632) 8424 8050 |
---
## 10. 相关文件索引
| 路径 | 说明 |
| ----------------------------------------------- | --------------------- |
| `xposed-module/.../MariBankRootBypassHook.java` | Java 层主 Hook~1700 行) |
| `xposed-module/.../MariBankShpsNativeHook.java` | Native/proc 层 Hook |
| `xposed-module/.../MariBankRiskTokenUtil.java` | Token 净化 |
| `xposed-module/.../MainHook.java` | 包名路由 |
| `reverse/frida/jni_targets.md` | JNI / Frida 目标 |
| `reverse/frida/trace_maribank_register.js` | Frida trace 脚本 |
| `scripts/logcat-maribank.ps1` | logcat 过滤 |
| `scripts/start-mari-trace.ps1` | Frida + logcat 联启 |
| `docs/WORKLOG_2026-07-03.md` | 当日全项目工作汇总 |
---
*文档版本2026-07-03 · 对应 commit 分支 `ysc`*