feat(maribank): PH 注册 OTP 突破、SG bypass 与中文文档

菲律宾 SeaBank 在 Root+LSPosed+Shamiko 下 register 已通过并触发 OTP;扩展 SG 包名、加密前 Hook、Magisk 设备伪装脚本,并将 docs 整理为中文操作与风控说明。
This commit is contained in:
mars
2026-07-06 14:01:49 +08:00
parent d488a0759f
commit 81119e0ff9
29 changed files with 2248 additions and 102 deletions

View File

@@ -2,7 +2,9 @@
Android 应用,监听通知栏消息并通过 **双通道**(通知监听 + Xposed Hook抓取内容支持 PC 本地调试台转发。
> **Hook 架构与扩展指南**:详见 [docs/HOOK_GUIDE.md](docs/HOOK_GUIDE.md)Telegram 实现、接入新 App 步骤
> **Hook 架构与扩展指南**:详见 [docs/Hook指南.md](docs/Hook指南.md)(含 **Xposed / LSPosed**、Telegram、接入新 App
> **手机部署与银行 bypass 操作**:详见 [docs/手机操作手册.md](docs/手机操作手册.md)
> **MariBank 风控与 register 载荷**[docs/MariBank风控与载荷说明.md](docs/MariBank风控与载荷说明.md)
## 项目结构
@@ -29,7 +31,9 @@ notiMessage/
├── scripts/ # build-debug.ps1, install-full.ps1, start-debug-server.ps1
├── library/ # 基础库模块 (com.miraclegarden.library)
│ └── MiracleGardenActivity<T> # ViewBinding 基类
├── docs/HOOK_GUIDE.md # Hook 架构与扩展文档
├── docs/Hook指南.md # Hook 架构、Xposed/LSPosed、扩展
├── docs/手机操作手册.md # Root 机部署与日常操作
├── docs/MariBank风控与载荷说明.md # register 字段与风控说明
├── build.gradle # 根构建文件
├── settings.gradle # 模块: app, library, xposed-module
└── gradle.properties # Gradle 配置

View File

@@ -1,6 +1,43 @@
# 消息抓取架构与 Hook 扩展指南
本文说明 notiMessage 的双通道抓取机制、Telegram 的实现方式,以及日后接入其他 App 的步骤。
本文说明 notiMessage 的双通道抓取机制、Telegram 的实现方式、**Xposed 与 LSPosed**,以及日后接入其他 App 的步骤。
> 手机部署步骤见 [`手机操作手册.md`](手机操作手册.md) · MariBank 载荷与风控见 [`MariBank风控与载荷说明.md`](MariBank风控与载荷说明.md)
---
## 0. Xposed 与 LSPosed
### 0.1 是什么关系
| | **Xposed经典** | **LSPosed** |
|--|-------------------|-------------|
| 性质 | Hook 框架概念 + 老实现(改 `/system` | 现代实现,**不动 system** |
| 依赖 | 老 Root / Recovery | **Magisk + Zygisk** |
| 作用域 | 全局或 Installer 里选 | **按 App 勾选** |
| 模块 API | `IXposedHookLoadPackage` 等 | **同一套 API** |
| 本项目 | 源码在 `xposed-module/` | **手机上实际加载框架** |
日常说法:**「Xposed 模块」= APK****「启用 Xposed」= 在 LSPosed 里打开模块并勾选作用域**。
### 0.2 与本项目的关系
```
Magisk → Zygisk → LSPosed → com.miraclegarden.smsmessage.xposed
├── MariBankRootBypassHook银行
├── TelegramMessageHookTelegram
└── …
```
测银行 App 时通常还需 **Shamiko**Hide Magisk与 LSPosed 分工见 [`MariBank风控与载荷说明.md` §6](MariBank风控与载荷说明.md)。
### 0.3 与其他工具对比
| 工具 | 特点 |
|------|------|
| **LSPosed** | 常驻、开机自动,适合银行 bypass |
| **Frida** | 临时 attach与 LSPosed 同时开易冲突 |
| **Magisk 模块** | 改系统属性serial不是 Hook Java |
---

View File

@@ -0,0 +1,255 @@
# MariBank 菲律宾注册突破 & 新加坡现状2026-07-06
> **设备**Pixel 6`1C081FDF600K5Q`)· Magisk 30.7 + Zygisk + LSPosed
> **结论****菲律宾 SeaBank 已能发 OTP****新加坡 MariBank 仍 3100012**
> **关联**[`MariBank实现说明.md`](MariBank实现说明.md) · [`MariBank新加坡逆向.md`](MariBank新加坡逆向.md)
---
## 1. 结果一览
| 地区 | 包名 | API | 注册结果 | 错误码 |
|------|------|-----|----------|--------|
| **菲律宾** | `ph.seabank.seabank` | `https://api.seabank.ph` | ✅ **成功 → OTP 短信** | `code=0` |
| **新加坡** | `sg.com.maribankmobile.digitalbank` | `https://api.maribank.com.sg` | ❌ 仍失败 | **3100012** |
**关键意义**:同一台 Root 机、同一套 Xposed + Magisk 伪装下,**PH 服务端已放行注册**,说明 Hook 链与 attestation 载荷 **对 PH 有效**SG 失败更可能是 **区域风控策略差异****SG 侧设备黑名单**而非「Root bypass 完全无效」。
---
## 2. 测试环境
| 项 | 值 |
|----|-----|
| 手机 | Google Pixel 6oriole |
| Root | Magisk **30.7**`MAGISK:R` |
| Magisk 模块 | `maribank_device_spoof``zygisk_shamiko``playintegrityfix``zygisk_vector` |
| DenyList | MariBank PH/SG 均已加入;**Enforce DenyList = OFF**Shamiko 要求) |
| LSPosed 模块 | `com.miraclegarden.smsmessage.xposed` |
| LSPosed 作用域 | `ph.seabank.seabank``sg.com.maribankmobile.digitalbank` 等 |
| 伪装 serial | `Y1Rr2fxhOI0ZCQSb`resetprop + Magisk 模块) |
| 伪装 android_id | `87ec910ab5d6f423` |
| 对照干净机 | `25078RA3EY`:关 USB 调试可进 SG OTP无 LSPosed |
---
## 3. 菲律宾突破 — log 证据2026-07-06 13:40
### 3.1 第一次 registerstep=BE
```text
POST https://api.seabank.ph/uapi/v2/register
```
加密前明文(`MariBankEncrypt in0 byte[1180]`)核心字段:
```json
{
"cyCode": "63",
"phone": "<RSA 加密>",
"rdVerifyInfo": {
"bioStatus": 0,
"data": "tPNHr/0eZzaPXs9oimvMNVWI/...",
"dataKey": "H/A6AqA8NhXZoaL0alG5VGRovcfuJykY92U...",
"deviceFingerprint": "jrKyNF/Fx4gcnzVpf1u9bw==|WJMksio9SaKKuyk1KMSilUGivdF+SHbqjB+aL65fucQcOCdojhBN3GKoY58sxVomiCfi5w==|TXeh8FyEf/8qHdAS|00|0",
"afExtInfo": { "modeInCall": "N", "modeInCommunication": "N", "modeCallScreening": "N" }
},
"scene": "REGISTRATION",
"step": "BE",
"source": "app"
}
```
服务端响应:
```json
{"code":0,"msg":"success","data":{"scene":"REGISTRATION","step":"BSO","tranId":"7f21098a-2113-40fb-bb6a-e2e651460f53",...}}
```
### 3.2 第二次 registerOTP 触发step=BSO
```json
{
"cyCode": "63",
"rdVerifyInfo": {
"action": "OTP_SMS_TRIGGER",
"deviceFingerprint": "jrKyNF/...|00|0",
"operationId": "61f9bcc6-dc0c-4f3d-8566-e1d3dc02653f1783316458430"
},
"scene": "REGISTRATION",
"step": "BSO",
"tranId": "7f21098a-2113-40fb-bb6a-e2e651460f53"
}
```
响应仍为 **`code=0`** → **App 可发验证码**
### 3.3 与 7 月 3 日对比
| 日期 | PH register | 说明 |
|------|-------------|------|
| 2026-07-03 | ❌ 4067012 | 仅 riskToken 尾部净化,无加密前 Hook |
| 2026-07-06 | ✅ code=0 → OTP | 加密前 Hook + Attestation Hook + 方案 B 换 ID + Shamiko |
---
## 4. 新加坡仍失败 — log 证据
### 4.1 典型失败11:50换 ID 前)
```text
POST https://api.maribank.com.sg/uapi/v2/register
→ {"code":3100012,"msg":"Unexpected error occurred. Please try again later. "}
```
加密前 `deviceFingerprint` 示例:
```text
X4Ln9cRV1v6aabPt4Ymnqw==|RmdlV34wu8nk8n9nj9A+32Z0BjF/29py3UVnqGWHBJRj5HuTmmOPX+pfJWkZBOn/n41pYw==|3ailMzmgCFzRDFGv|00|0
```
尾部 `\|00\|0` 已净化,**仍 3100012**。
### 4.2 SG 错误码含义(推断)
| 码 | 地区 | 文案 |
|----|------|------|
| 4067012 | PH | For your account's security, temporarily blocked... |
| 3100012 | SG | Unexpected error occurred. Please try again later. |
同属 **服务端风控拒绝**,非本地 Root 弹窗。
---
## 5. 已实现的 bypass 架构
### 5.1 分层模型
```
客户端本地层 服务端层
───────────────── ─────────────────
SafeMode Root 弹窗/自杀 → (不决定 OTP只决定能否进 App
SG ADB 全屏拦截页 →
SHPSSDK assessRisk → 解密 rdVerifyInfo.data/dataKey
riskToken / deviceFingerprint → 查 deviceHash 黑名单
Magisk resetprop 换 serial → 区域策略PH 松 / SG 严)
```
### 5.2 Xposed 模块文件
| 文件 | 作用 |
|------|------|
| `MariBankRootBypassHook.java` | SafeMode、SHPSSDK、ADB Settings、OkHttp 日志、4067/3100012 记录 |
| `MariBankShpsNativeHook.java` | `/proc` 过滤、SystemProperties、requestDefense 净化(不再 block |
| `MariBankSdkUtilsHook.java` | 加密前 Hook `utils.d` / `uvwuvwuv` / Gson |
| `MariBankRegisterPayloadUtil.java` | `scene=REGISTRATION` JSON 净化 |
| `MariBankAttestationHook.java` | native 桥 attestation 链 + 环境探测拦截 |
| `MariBankRiskTokenUtil.java` | riskToken 尾部 `\|09\|1``\|00\|0` |
### 5.3 方案 BMagisk 设备 ID 伪装
路径:`scripts/magisk/maribank-device-spoof/`
| 脚本 | 时机 | 内容 |
|------|------|------|
| `post-fs-data.sh` | 早期启动 | `resetprop` serial、boot 属性、关 adb 属性 |
| `service.sh` | 开机完成后 | 写入新 `Settings.Secure.android_id` |
PC 一键:
```powershell
.\scripts\maribank-spoof-device.ps1 # 一次性 resetprop
.\scripts\maribank-spoof-device.ps1 -InstallModule # 安装持久模块
.\scripts\maribank-scheme-b-finish.ps1 # LSPosed 作用域 + pm clear
```
### 5.4 Shamiko 配置要点
1. Magisk → 开启 **Zygisk**
2. **Configure DenyList** 开启 → 勾选 MariBank PH/SG 全部进程
3. **Enforce DenyList 必须关闭**(否则 Shamiko 不生效)
4. 安装 **Shamiko** 模块并重启
---
## 6. 为何 PH 能过、SG 不过(推断)
> **详细展开**(菲律宾真实原因、`afExtInfo`、USB 调试与 Hook见 [`MariBank风控与载荷说明.md`](MariBank风控与载荷说明.md) §4§5。
| 维度 | PH ✅ | SG ❌ |
|------|-------|-------|
| 同一 deviceFingerprint 结构 | 有,尾部 `\|00\|0` | 有,尾部 `\|00\|0` |
| `data`/`dataKey` attestation | 服务端接受 | 服务端拒绝3100012 |
| 测试次数 / 黑名单 | 较少失败记录 | Pixel 6 多次测 SG 注册 |
| API 风控强度 | 当日实测 **通过** | 当日实测 **拒绝** |
| 干净机对照 | 未详测 PH | 25078RA3EY 关 ADB 可 OTP |
**结论**
1. **改 riskToken 尾部 alone 不够**,但加上 **加密前 Hook + Shamiko + 换 ID****PH 足够**
2. SG 可能在 attestation 解密后仍有 **更严规则**Play Integrity、设备信誉、区域黑名单
3. PH 成功 **不能** 直接类推 SG**diff 两次 register 明文****换未测过 SG 的环境** 再试。
---
## 7. 已知问题与踩坑
| 问题 | 现象 | 处理 |
|------|------|------|
| Gson Hook 误报 | i18n 文案含 "register" 刷屏 | 已改为只匹配 `scene=REGISTRATION` |
| `requestDefense` 被 block | 缺 `x-sap-fixme` | 已改为执行后净化,不 block |
| ProcessBuilder 抛异常 | `which su` → SecurityException | **待修**:应返回空进程而非抛异常 |
| Magisk zip 在 Windows 打包 | 只解压 module.prop | 用 `tar -a -cf` 或 adb 手动 push sh |
| 关 USB 调试后 adb 断开 | 收尾脚本卡住 | `maribank-scheme-b-finish.ps1` 已加 60s 超时 |
| Attest native Hook 命中少 | 仅 8 个 hook | `data`/`dataKey` 仍未在 native 层改写 |
### 禁止操作(会导致白屏/崩溃)
- Hook `System.loadLibrary`SG RN 崩溃)
- Hook `RealInterceptorChain.proceed`
- 过早 Hook `ShpssInstall` / `vuvuwwwuw`
---
## 8. 常用命令
```powershell
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
# 构建安装
.\scripts\build-debug.ps1
.\scripts\install-debug.ps1
# 抓注册 log先 logcat -c再点 Next
& $adb logcat -d | Select-String "MariBankEncrypt in0 byte\[1|MariBankRoot HTTP.*register|3100012|4067012|OTP_SMS"
# 验证伪装 ID
& $adb shell su -c "getprop ro.serialno; settings get secure android_id"
```
---
## 9. 新加坡后续建议
1. **用 PH 成功时的同一环境** 测 SG关 USB 调试、`pm clear` SG 包、冷启动
2. **diff PH vs SG** 加密前 register JSON`deviceFingerprint` 段、`data` 长度与字段)
3. 修复 **ProcessBuilder** 不抛异常,避免 SHPSSDK 检测 tamper
4. 若仍 3100012考虑 **未测过 SG 的干净机****native hook `libshpssdk_bank.so`**
5. 菲律宾侧:继续走完 **OTP → 开户** 验证全流程稳定性
---
## 10. 相关文档
| 文档 | 说明 |
|------|------|
| [`MariBank风控与载荷说明.md`](MariBank风控与载荷说明.md) | **register 字段、afExtInfo、USB 调试、PH 真实原因、Xposed/LSPosed** |
| [`手机操作手册.md`](手机操作手册.md) | **Root 机部署、LSPosed、日常操作、抓 log** |
| [`Hook指南.md`](Hook指南.md) | Xposed/LSPosed 概念、Telegram Hook、扩展 App |
| [`MariBank实现说明.md`](MariBank实现说明.md) | PH Hook 实现细节(含 7/3 失败记录,已追加 7/6 突破) |
| [`MariBank新加坡逆向.md`](MariBank新加坡逆向.md) | SG 逆向与 ADB 检测 |
| [`工作日志_2026-07-03.md`](工作日志_2026-07-03.md) | 7/3 工作记录 |
---
*记录日期2026-07-06 · 最后更新:文档体系整理(操作手册 + 风控载荷说明)*

View File

@@ -1,11 +1,11 @@
# MariBank 实现说明与问题记录
> **日期**2026-07-03
> **日期**2026-07-03**2026-07-06 更新PH 注册已成功发 OTP**,见 [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md)
> **App**MariBank / SeaBank PH · `ph.seabank.seabank` · v3.22.0 (32200)
> **设备**Pixel 6 · Magisk + Zygisk + LSPosed
> **设备**Pixel 6 · Magisk + Zygisk + LSPosed + Shamiko
> **源码**`xposed-module/.../hook/MariBank*.java`
本文档专门说明当日对 MariBank **注册流程风控绕过** 的 Xposed 实现细节,以及开发/测试中遇到的全部主要问题。通用工作汇总见 [`WORKLOG_2026-07-03.md`](WORKLOG_2026-07-03.md)。
本文档专门说明当日对 MariBank **注册流程风控绕过** 的 Xposed 实现细节,以及开发/测试中遇到的全部主要问题。通用工作汇总见 [`工作日志_2026-07-03.md`](工作日志_2026-07-03.md)。
---
@@ -21,7 +21,21 @@
→ 进入 OTP / 下一步
```
### 1.2 当日最终结果
### 1.2 最终结果2026-07-06 更新)
| 阶段 | 2026-07-03 | **2026-07-06** |
| -------------------- | ---------- | -------------- |
| 本地 Root 检测 / 自杀 / 弹窗 | **大部分绕过** | ✅ 稳定 |
| DFP 设备指纹上报 | **成功** `code=0` | ✅ |
| riskToken 尾部净化 | ✅ Hook 生效 | ✅ |
| 加密前 register 明文 | ❌ 未抓到 | ✅ `MariBankSdkUtilsHook` |
| 注册接口 PH | ❌ **4067012** | ✅ **`code=0` → OTPBSO** |
| 注册接口 SG | — | ❌ **3100012**(见 SG 文档) |
**结论2026-07-06**:在 Pixel 6 + Shamiko + 方案 B 换 ID + 全套 Hook 下,**菲律宾 SeaBank 注册已通过服务端校验并可发验证码**;新加坡 MariBank 仍被拒。详见 [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md)。
### 1.2.1 当日最终结果2026-07-03 历史记录,已被 7/6 突破)
| 阶段 | 结果 |
@@ -33,7 +47,7 @@
| 注册请求明文 | **未抓到**body native 加密) |
**结论**:本地层 bypass 已推进到能正常发起注册请求,但 **服务端风控仍拒绝**;当前实现不足以完成注册
**结论2026-07-03**:本地层 bypass 已推进到能正常发起注册请求,但 **服务端风控仍拒绝**
---
@@ -332,9 +346,12 @@ Hook 对含 `temporarily blocked` / `8424 8050` 的 Toast 会 **记录栈**`l
## 5. 遇到的问题(完整清单)
### 5.1 服务端注册拒绝 — 4067012核心未解问题
### 5.1 服务端注册拒绝 — 40670122026-07-06 已突破 PH
**现象**
> **更新**2026-07-06 在 Shamiko + 方案 B + `MariBankSdkUtilsHook` / `MariBankAttestationHook`PH register 返回 **`code=0`step=BSOOTP_SMS_TRIGGER 成功**。详见 [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md)。
> 以下内容为 **7 月 3 日** 失败时的记录,保留作对照。
**现象2026-07-03**
- 能进入 Sign up 页
- 请求发出后弹服务端封锁弹窗
@@ -560,7 +577,10 @@ Log in → 输入已开户手机号 → 继续
| `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` | 当日全项目工作汇总 |
| `docs/工作日志_2026-07-03.md` | 当日全项目工作汇总 |
| `docs/MariBank_2026-07-06_菲律宾突破.md` | PH OTP 突破 + SG 现状 |
| `docs/MariBank风控与载荷说明.md` | register 字段、afExtInfo、USB 调试 |
| `docs/手机操作手册.md` | Root 机部署与日常操作 |
---

View File

@@ -0,0 +1,217 @@
# MariBank 新加坡版逆向报告
> **日期**2026-07-06
> **设备**Pixel 6已 Root + LSPosed + ADB 开启)
> **APK 来源**:从本机 pull`reverse/apks/maribank_sg_*.apk`
---
## 1. 基本信息
| 项 | 值 |
|----|-----|
| **包名** | `sg.com.maribankmobile.digitalbank` |
| **显示名** | MariBank新加坡 |
| **版本** | 3.2.2versionCode **30220** |
| **对比 PH 版** | `ph.seabank.seabank` · v3.22.0 (32200) |
| **API 域名** | `https://api.maribank.com.sg`staging: `api.staging.maribank.com.sg` |
| **Application** | `com.shopee.bke.digitalbank.BkeApplication`(与 PH **相同** |
本地 APK
```
reverse/apks/maribank_sg_base.apk
reverse/apks/maribank_sg_arm64.apk → SO 在 reverse/extracted/native/
```
---
## 2. 截图现象(你当前遇到的)
启动后出现 **两层拦截**
| 现象 | 类型 | 说明 |
|------|------|------|
| 全屏 **「ADB/Wireless ADB Detected」** | 环境检测页 | 文案 key`bke_title_adb_wirelessadb_control``bke_btn_turn_off_adb_wireless_adb` |
| Toast **「Does not support root device」** | Root 检测 | SHPSSDK / SafeMode 典型提示 |
**含义**
1. **ADB 检测****Root 检测****独立项**——新加坡版在 PH 版基础上 **明显加强了 USB/无线调试检测**
2. 状态栏 **VPN 小钥匙** 可能额外触发 `RISK_*` 或网络风控(需 logcat 确认)。
3. 当前 **PH 版 Xposed Hook 未作用到 SG 包名**(只 Hook `ph.seabank.seabank`),所以 SG 版 **完全未 bypass**
**临时验证(无 Hook**:关闭 **USB 调试 + 无线调试** 后冷启动,可确认 ADB 页是否消失Root Toast 仍会存在。
---
## 3. 技术栈对比SG vs PH
| 组件 | SG 3.2.2 | PH 3.22.0 | 结论 |
|------|----------|-----------|------|
| Shopee BKE | ✅ | ✅ | 同框架 |
| `BkeApplication` | ✅ | ✅ | Hook 时机相同(`attachBaseContext` 后) |
| SafeMode | `com.shopee.bke.lib.safemode.b` 等 | 相同 | 可复用 boolean Hook |
| `SafeModeRecoverActivity` | ✅ | ✅ | Root 弹窗/恢复页 |
| SHPSSDK Bank | `com.shopee.shpssdkbank.SHPSSDK` | 相同 | 可复用 riskToken / requestDefense 链 |
| Native SO | `libshpssdk_bank.so``libsdkutils.so``libbkutils.so` | 相同 | SG 额外有 **`libshpssdk.so`** |
| 字符串解密 | `uvuwwuvwv.uvwwuuvvw` | 相同 | 混淆包名可能不同 |
**结论**SG 与 PH 是 **同一套 Shopee 银行 SDK**PH 上已写的 Hook **大部分可迁移**,需改 **包名****补充 ADB 风控**
---
## 4. 新加坡版新增ADB / 无线调试检测
### 4.1 SHPSSDK 风险常量DEX 内)
```
RISK_USB_ADB ← USB 调试
RISK_WIFI_ADB ← 无线调试
RISK_ADB
RISK_ROOT
RISK_HOOK
RISK_DEBUG
RISK_DEVELOPER_MODE
RISK_EMULATOR
...
```
与截图中 **ADB 拦截页 + Root Toast** 对应。
### 4.2 UI 字符串(资源 key
| Key | 用途 |
|-----|------|
| `bke_title_adb_wirelessadb_control` | 标题「ADB/Wireless ADB Detected」 |
| `bke_btn_turn_off_adb_wireless_adb` | 按钮「Turn Off ADB/Wireless ADB」 |
| `bke_desc_explain_detect_adb_1/2` | 说明文案 |
| `bke_desc_safeguard_bank_app_from_adb_wirelessadb_1/2` | 安全说明 |
| `AdbDetected` / `WifiAdbDetected` | RN / 业务路由标识 |
### 4.3 检测思路(推断)
1. **Settings.Global** / `adb_enabled`、无线调试相关属性
2. SHPSSDK `getRiskSync` → 命中 `RISK_USB_ADB` / `RISK_WIFI_ADB`
3. 进入 **ADB 专用拦截 UI**(非仅 SafeModeRecoverActivity
4. Root 仍走 `RISK_ROOT` → Toast「Does not support root device」
---
## 5. API 与登录SG
PH 实测重点为 `POST /uapi/v2/register`SG DEX 内可见:
| 类型 | 路径示例 |
|------|----------|
| 登录 | `/v3/login``/v4/login``/v2/login/bke/linkage` |
| 注册 | `/v2/register` |
| 登录前 | `/v1/list/latest-notice/pre-login` |
| 人脸 | `/v3/login/facial/verification` |
| DFP | `/dfp/`(与 PH 同类) |
| 通用 uapi | `/uapi/`47 处引用) |
**Host**`https://api.maribank.com.sg`(非 PH 的 `api.seabank.ph`)。
---
## 6. Native 库arm64 split
已从 `maribank_sg_arm64.apk` 解出至 `reverse/extracted/native/`,与风控相关:
| SO | 大小(约) | 作用 |
|----|-----------|------|
| `libshpssdk_bank.so` | 8.5 MB | SHPSSDK 银行风控、riskToken |
| `libshpssdk.so` | 6.3 MB | **SG 额外**PH 侧以 bank 为主) |
| `libsdkutils.so` | 789 KB | 注册/body 加密 |
| `libbkutils.so` | 102 KB | JNI 工具 |
| `libmemory_security.so` | 3.8 MB | 内存/安全相关 |
---
## 7. 与现有 Xposed 模块的关系
当前 `MariBankRootBypassHook` / `MariBankShpsNativeHook` **仅绑定**
```java
public static final String PACKAGE = "ph.seabank.seabank";
```
**SG 包名未纳入**,因此截图中的拦截 **预期行为**
### 7.1 可复用(改包名即可试)
- SafeMode boolean Hook`safemode.b` 等)
- `Process.killProcess` / `finishAffinity` 反自杀
- `/proc/self/maps` 过滤、`SystemProperties` 伪装
- SHPSSDK `getRiskSync` 清空、`getRiskToken` 净化
- OkHttp 出站 riskToken 净化
**已实现2026-07-06**`MariBankRootBypassHook` 已支持 `sg.com.maribankmobile.digitalbank`,含 ADB Settings 伪装、ADB 全屏页 finish、Root/ADB Toast 拦截。
### 7.2 SG 专用(已部分实现)
| 项 | 状态 |
|----|------|
| **包名** | ✅ `MainHook` + `arrays.xml` |
| **ADB 风险** | ✅ `Settings.Global/Secure` adb 键 → 0`init.svc.adbd` → stopped |
| **ADB 拦截页** | ✅ `Activity.onResume` 检测 ADB 文案后 `finish()` |
| **Root Toast** | ✅ 「Does not support root device」 |
### 7.3 不建议照搬 PH 服务端结论
PH 的 **4067012****菲律宾服务端** 结论SG 需单独测 `api.maribank.com.sg`
---
## 8. 逆向脚本
```powershell
# 已从手机 pull APK 后可本地扫描
python reverse/scripts/scan_maribank_sg.py
python reverse/scripts/find_sg_adb_strings.py
python reverse/scripts/find_sg_adb_classes.py
python reverse/scripts/list_safemode.py reverse/apks/maribank_sg_base.apk
```
从手机 pull已执行过
```powershell
adb shell pm path sg.com.maribankmobile.digitalbank
adb pull <base.apk> reverse/apks/maribank_sg_base.apk
```
---
## 9. 建议下一步2026-07-06 更新)
1. ~~LSPosed 增加 SG 包名~~ ✅ 已完成
2. ~~扩展 ADB bypass~~ ✅ 已完成
3. **SG**diff PH/SG register 明文;修复 ProcessBuilder 异常;仍 3100012 则试干净机
4. **PH**:走完 OTP → 开户全流程,确认稳定性
5. logcat`MariBankEncrypt in0 byte``3100012``OTP_SMS`
---
## 10. 实测状态2026-07-06
| 包 | 注册 API | 结果 |
|----|----------|------|
| `ph.seabank.seabank` | `api.seabank.ph/uapi/v2/register` | ✅ **code=0 → OTP** |
| `sg.com.maribankmobile.digitalbank` | `api.maribank.com.sg/uapi/v2/register` | ❌ **3100012** |
完整说明:[`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md)
---
## 11. 相关文档
| 文档 | 说明 |
|------|------|
| [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md) | **PH OTP 突破 + SG 现状 + 方案 B** |
| [`MariBank实现说明.md`](MariBank实现说明.md) | PH Hook 实现与问题清单 |
| [`工作日志_2026-07-03.md`](工作日志_2026-07-03.md) | PH 版 7/3 工作记录 |
---
*记录日期2026-07-06 · 最后更新:菲律宾 OTP 突破*

View File

@@ -0,0 +1,215 @@
# MariBank 风控与注册载荷说明
> 客户端检测分层、register 请求字段含义、菲律宾突破原因、USB 调试与 Hook 关系。
> 关联:[`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md) · [`MariBank实现说明.md`](MariBank实现说明.md) · [`手机操作手册.md`](手机操作手册.md)
---
## 1. 风控两层模型
| 层级 | 谁在做 | 失败表现 | bypass 方式 |
|------|--------|----------|-------------|
| **客户端本地** | SafeMode、SHPSSDK、SG ADB 页 | 弹窗、全屏拦截、自杀 | Xposed boolean / Settings / finish Activity |
| **服务端** | `api.seabank.ph` / `api.maribank.com.sg` | 4067012 / 3100012 | 让出站 attestation「够干净」+ 区域策略 |
**DFP `code=0` 只表示上报收到,不等于注册会通过。**
干净机对照25078RA3EY**开 USB 调试 → SG 本地 ADB 页;关调试 → SG OTP 可过**。说明本地检测与服务端决策 **相互独立**
---
## 2. 注册请求载荷结构App 发出)
加密前 JSON`MariBankEncrypt` / `uvwuvwuv.uvwvuww` 入口)典型结构:
```json
{
"cyCode": "63",
"phone": "<RSA 加密>",
"scene": "REGISTRATION",
"step": "BE",
"rdVerifyInfo": {
"bioStatus": 0,
"data": "<SHPSSDK native attestation 密文>",
"dataKey": "<attestation 密钥>",
"deviceFingerprint": "段1|段2|段3|00|0",
"fvInfo": {},
"afExtInfo": {
"modeInCall": "N",
"modeInCommunication": "N",
"modeCallScreening": "N"
},
"publicKeyAuthen": "pk2",
"random": "...",
"softTokenActivated": false
}
}
```
流程:**App 本地采集 → 拼 JSON → native 加密 → POST `/uapi/v2/register`**
---
## 3. 字段说明
### 3.1 `deviceFingerprint`
pipe 分隔字符串,示例:
```text
jrKyNF/Fx4gcnzVpf1u9bw==|WJMksio9SaKKuyk1KMSilUGivdF+...|TXeh8FyEf/8qHdAS|00|0
↑ Base64 段1 ↑ Base64 段2设备哈希 ↑ 短码 ↑ 风险码|标志
```
| 部分 | 含义 | Hook 能否改 |
|------|------|-------------|
| 前两段 Base64 | 设备 / 环境哈希,服务端可索引黑名单 | 换 serial/android_id 后 **重新生成** |
| 第三段 | 短码 | 随 SDK 变 |
| 最后 `\|xx\|y` | 风险摘要(如 Root+Hook `\|09\|1` | ✅ 已净化为 `\|00\|0` |
**只改尾部不够**:服务端还验 `data`/`dataKey` 解密内容。
### 3.2 `data` / `dataKey`
-**`libshpssdk_bank.so` native** 生成并签名
- 内含 Root / Hook / 调试等 attestation**Java 层改 JSON 字段无法重写密文**
- SG 3100012 时多半此层或设备黑名单未过PH 7/6 成功时服务端 **接受了** 当前密文
### 3.3 `afExtInfo`(银行 App 上报,非 PC 添加)
```json
"afExtInfo": {
"modeInCall": "N",
"modeInCommunication": "N",
"modeCallScreening": "N"
}
```
| 字段 | 推测含义 | `"N"` |
|------|----------|-------|
| `modeInCall` | 是否正在通话 | No |
| `modeInCommunication` | 是否通信/音频占用 | No |
| `modeCallScreening` | 来电筛选等相关状态 | No |
- **来源**App / SDK 读系统状态写入 `rdVerifyInfo`
- **去向**:随 register 加密后发给服务端
- **当前模块****未专门修改** 这三项(原样上报)
- **用途**:反欺诈辅助信号,**不是** USB 调试开关
### 3.4 其他常见字段
| 字段 | 说明 |
|------|------|
| `cyCode` | 国家码PH=`63`SG=`65` |
| `step` | `BE` 首次注册 → 成功后可到 `BSO`OTP |
| `action` | `OTP_SMS_TRIGGER` 触发短信验证码 |
| `bioStatus` | 生物识别状态,注册时多为 `0` |
---
## 4. 为何 USB 调试开着仍能发 OTP菲律宾实测
### 4.1 两个「USB 调试」不是一回事
| | PC 侧 adb | App 内部读到的值 |
|--|-----------|------------------|
| 你开 USB 调试 | ✅ 能 `adb shell` | 被 Hook **伪装为关闭** |
### 4.2 模块已做的 ADB bypass`MariBankRootBypassHook`
**Java Settings**
- `adb_enabled` / `adb_wifi_enabled` / `development_settings_enabled``0`
**系统属性(`MariBankShpsNativeHook`**
- `init.svc.adbd``stopped`
- `persist.sys.adb_enable``0`
**UI**
- 全屏「ADB / Wireless ADB Detected」页 → `Activity.finish()`
- 相关 Toast 拦截
因此:**PC 连着 adbApp 仍可能认为未开调试**本地不拦、register 载荷也不带「adb 开」的自报字段。
### 4.3 与干净机对照
| 环境 | USB 调试 | 结果 |
|------|----------|------|
| 干净机(无 Hook | 开 | 本地 ADB 页,进不了 OTP |
| 干净机 | 关 | ✅ OTP |
| Pixel 6 + LSPosed + Shamiko | **开** | ✅ PH OTPHook 伪装) |
### 4.4 注意
- native 仍可能用 Hook 未覆盖的路径读调试状态
- **SG 更严**,开 adb 风险高于 PH
- 文档仍建议测 SG 时 **关 USB 调试**
---
## 5. 菲律宾能发验证码的真实原因
### 5.1 能确定
- `api.seabank.ph` 返回 **`code=0`**step 进入 **BSO****OTP_SMS_TRIGGER** 成功
- 同一台机、同一套 Hook 下 **SG 仍 3100012** → 差异在 **服务端**,非「没风控」
### 5.2 7/3 失败 → 7/6 成功的变化
| 能力 | 7/3 | 7/6 |
|------|-----|-----|
| riskToken 尾部净化 | ✅ | ✅ |
| 加密前 register 明文 Hook | ❌ | ✅ |
| Attestation / 环境 Hook | 弱 | ✅ |
| Shamiko 藏 Magisk | 无/未配 | ✅ |
| 换 serial / android_id | 无 | ✅ |
**不是单一开关**,而是 **Shamiko + 新设备 ID + 加密前/采集链净化** 组合后PH 服务端认为 attestation **可接受**
### 5.3 高概率推断
1. **PH / SG 两套 API、两套规则** — 同一载荷 PH 过、SG 不过
2. **PH 侧设备黑名单** — Pixel 6 在 SG 测多次PH 可能未标记
3. **7/3 的 4067012** — bypass 不完整非「PH 永远不能 Root 注册」
### 5.4 无法无源码 100% 证实
- PH 后台具体哪条规则放行
- `data` 解密后哪一位从拒变收
---
## 6. Magisk / LSPosed / Shamiko 分工
```
MagiskRoot 权限)
└── Zygisk
├── LSPosed → 加载 xposed-module改 Java 检测、抓加密前 JSON
└── Shamiko → 对银行进程隐藏 Magisk/LSPosed
Magisk 模块 maribank_device_spoof → resetprop 换 serial 等
```
| 组件 | 作用 |
|------|------|
| **Magisk** | Root |
| **LSPosed** | 运行 Xposed 模块(与经典 Xposed 同 API见 [`Hook指南.md` §0](Hook指南.md) |
| **Shamiko** | 进程内 Hide Root改善 native attestation |
| **Xposed 模块** | SafeMode / ADB / 加密前净化 |
---
## 7. 服务端可能校验项(反推)
| 校验项 | 说明 |
|--------|------|
| `rdVerifyInfo.data` / `dataKey` | native attestation 解密 |
| `deviceFingerprint` 段 1/2 | 设备黑名单 |
| DFP 历史画像 | 与 register 是否一致 |
| 区域策略 | PH 松 / SG 严(实测) |
| IP / 号码 / 频率 | 次要,非本次主因 |
---
*最后更新2026-07-06*

View File

@@ -198,7 +198,7 @@ reverse/
- **44 个** 脚本内路径改为相对 `reverse/` 根(`apks/``extracted/``tmp/``output/`)。
- Frida 日志输出改到 `reverse/logs/frida/`
- 更新 `.gitignore``docs/BANK_REVERSE.md``pull_split_apk.ps1``start-mari-trace.ps1`
- 更新 `.gitignore``docs/银行逆向.md``pull_split_apk.ps1``start-mari-trace.ps1`
- `apk_extract/` 迁入 `extracted/apk_extract/``seabank.zip` 迁入 `apks/`
---
@@ -223,10 +223,13 @@ reverse/
| 文档 / 路径 | 说明 |
| ---------------------------------- | ----------------------------------- |
| `docs/MARIBANK_IMPLEMENTATION.md` | **MariBank 实现细节与问题记录(专文)** |
| `docs/BANK_REVERSE.md` | 澳大利亚三家银行 HookUp / Suncorp / ubank |
| `docs/HOOK_GUIDE.md` | 通用 Hook 架构与扩展 |
| `docs/CHANGELOG.md` | 主 App / TG Hook 版本历史 |
| `docs/MariBank实现说明.md` | **MariBank 实现细节与问题记录(专文)** |
| `docs/MariBank_2026-07-06_菲律宾突破.md` | PH OTP 突破 + SG 现状 |
| `docs/MariBank风控与载荷说明.md` | register 字段、afExtInfo、USB 调试 |
| `docs/手机操作手册.md` | Root 机部署与日常操作 |
| `docs/银行逆向.md` | 澳大利亚三家银行 HookUp / Suncorp / ubank |
| `docs/Hook指南.md` | 通用 Hook 架构、Xposed/LSPosed、扩展 |
| `docs/更新说明.md` | 主 App / TG Hook 版本历史 |
| `reverse/README.md` | 逆向工作区目录说明 |
| `reverse/frida/jni_targets.md` | JNI Hook 目标备忘 |
| `xposed-module/.../MariBank*.java` | MariBank bypass 实现 |

156
docs/手机操作手册.md Normal file
View File

@@ -0,0 +1,156 @@
# 手机操作手册
> 在 Root 手机上部署 **notiMessage** 主 App + **Xposed 模块**,以及 **MariBank / SeaBank** bypass 的日常步骤。
> 关联:[`Hook指南.md`](Hook指南.md) · [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md) · [`MariBank风控与载荷说明.md`](MariBank风控与载荷说明.md)
---
## 1. 手机前置条件(一次性)
| 步骤 | 操作 |
|------|------|
| 1 | 刷 **Magisk**Magisk 设置里开启 **Zygisk** |
| 2 | 安装 **LSPosed**Magisk 模块或 Manager APK |
| 3 | 安装 **Shamiko**(银行 App **强烈建议** |
| 4 | Magisk → **配置排除列表** → 开启 |
| 5 | 排除列表勾选目标银行 **全部子进程**PH / SG 包名见下) |
| 6 | **不要** 开启「强制启用排除列表」Shamiko 要求 Enforce = OFF |
| 7 | (可选)安装 Magisk 模块 `maribank-device-spoof` 持久换 serial / android_id |
### 目标包名
| App | 包名 |
|-----|------|
| 菲律宾 SeaBank | `ph.seabank.seabank` |
| 新加坡 MariBank | `sg.com.maribankmobile.digitalbank` |
| notiMessage 主 App | `com.miraclegarden.smsmessage` |
| Xposed 模块 | `com.miraclegarden.smsmessage.xposed` |
| Telegram抓消息 | `org.telegram.messenger``org.telegram.messenger.web` |
---
## 2. PC 安装 / 更新USB 连手机)
### 2.1 一键全套(推荐)
```powershell
cd C:\Users\Administrator\Desktop\notiMessage
.\scripts\install-full.ps1
```
自动完成:编译 → 安装双 APK → 配置 LSPosed 作用域 → 电池白名单 → 通知监听授权等。
### 2.2 仅编译安装 APK
```powershell
.\scripts\build-debug.ps1
.\scripts\install-debug.ps1
```
### 2.3 MariBank 测前收尾
```powershell
.\scripts\maribank-scheme-b-finish.ps1
```
作用:确认 Magisk 模块、写入 LSPosed 作用域、关 USB 调试(可选)、`pm clear` 银行 App。
换设备 ID
```powershell
.\scripts\maribank-spoof-device.ps1 -InstallModule # 持久模块
.\scripts\maribank-spoof-device.ps1 -ClearMariBank # 一次性 resetprop + 清数据
```
---
## 3. 手机上 LSPosed 必做步骤
**每次新装或更新 Xposed 模块 APK 后:**
1. 打开 **LSPosed**
2. 找到模块 **notiMessage Xposed****启用**
3. 进入 **作用域**,勾选需要的 App见 §1 包名表)
4.**每个已勾选 App** 执行 **软重启**(或整机重启)
未软重启时 Hook **不会** 注入目标 App。
---
## 4. 日常使用
### 4.1 菲律宾 SeaBank 注册(当前可 OTP
1. 确认 LSPosed 已启用且作用域含 `ph.seabank.seabank`
2. (建议)`pm clear ph.seabank.seabank` 或跑 `maribank-scheme-b-finish.ps1`
3. 打开 App → **Sign up** → 输入菲律宾手机号 → **Next**
4. 等待 OTP 短信
> **说明**:实测 **USB 调试可保持开启** 仍能 OTP因 Xposed 对 App 伪装 adb 状态(详见 [`MariBank风控与载荷说明.md` §4](MariBank风控与载荷说明.md))。测 SG 或求稳时仍建议关调试。
### 4.2 新加坡 MariBank
流程同上,包名为 `sg.com.maribankmobile.digitalbank`。当前仍可能 **3100012**,见 [`MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md)。
### 4.3 notiMessage 抓 Telegram
1. 打开 **notiMessage****开始监听** → 授予通知使用权
2. LSPosed 作用域勾选 Telegram + 主 App
3. PC 调试台(可选):`.\scripts\start-debug-server.ps1`(需 `adb reverse tcp:8765 tcp:8765`
---
## 5. 抓 logPC
```powershell
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
& $adb logcat -c
# 手机操作Sign up → Next
& $adb logcat -d | Select-String "MariBankEncrypt in0 byte\[1|MariBankRoot HTTP.*register|3100012|4067012|OTP_SMS"
```
验证伪装 ID
```powershell
& $adb shell su -c "getprop ro.serialno; settings get secure android_id"
```
---
## 6. 换机 / 新机迁移
1. 新机 Root + Magisk + Zygisk + LSPosed + Shamiko
2. PC 执行 `install-full.ps1`
3. LSPosed 启用模块 + 作用域 + 软重启
4. 安装 `maribank-device-spoof` → 重启 → `maribank-scheme-b-finish.ps1`
5. DenyList 重新勾选银行 App
---
## 7. 常见问题
| 现象 | 处理 |
|------|------|
| 银行 App 白屏 | LSPosed 软重启;勿改 Hook 加载时机(见实现说明) |
| Root / ADB 弹窗 | 确认模块启用 + 作用域已勾银行 App |
| adb 连不上 | 开发者选项重新开 **USB 调试**,解锁点允许 |
| 菲律宾又 4067012 | `pm clear ph.seabank.seabank`,必要时重跑 spoof |
| Hook 不生效 | LSPosed **软重启**目标 App不是只杀进程 |
| 收尾脚本卡住 | adb 断开;先开 USB 调试连 PC跑完脚本再关 |
---
## 8. 最短路径速查
```
PC: install-full.ps1
手机: LSPosed 启用 → 勾选 App → 软重启
银行: maribank-scheme-b-finish.ps1 → Sign up → Next
抓包: logcat -c → Next → logcat -d | Select-String register
```
---
*最后更新2026-07-06*

View File

@@ -1,5 +1,19 @@
# 更新说明
## MariBank 风控 bypass2026-07-06
- **菲律宾 SeaBank**`ph.seabank.seabank`Root Pixel 6 上 **注册成功、可发 OTP**`api.seabank.ph``code=0`
- **新加坡 MariBank**`sg.com.maribankmobile.digitalbank`):仍 **3100012**
- 新增 / 更新文档:
- [`docs/MariBank_2026-07-06_菲律宾突破.md`](MariBank_2026-07-06_菲律宾突破.md) — 突破记录与 PH/SG 对比
- [`docs/MariBank风控与载荷说明.md`](MariBank风控与载荷说明.md) — register 字段、afExtInfo、USB 调试、真实原因
- [`docs/手机操作手册.md`](手机操作手册.md) — Root 机部署与日常操作
- [`docs/Hook指南.md`](Hook指南.md) — 新增 §0 Xposed 与 LSPosed
- 脚本:`scripts/maribank-spoof-device.ps1``scripts/maribank-scheme-b-finish.ps1``scripts/magisk/maribank-device-spoof/`
- 文档文件名已全部改为中文(见 `docs/` 目录)
---
## v2.2.12026-07-02
### 修复Hook 通道在 notiMessage 后台时丢失消息
@@ -75,7 +89,7 @@ adb logcat | findstr /i "HookMessageReceiver DebugForwarder NotificationService
### 文档
- 架构与扩展:`docs/HOOK_GUIDE.md`
- 架构与扩展:`docs/Hook指南.md`
- Agent 说明:`AGENTS.md`
---

View File

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import subprocess
import zipfile
from pathlib import Path
APK = Path(__file__).resolve().parent.parent / "apks" / "maribank_sg_base.apk"
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
TARGETS = [
"Lcom/shopee/bke/lib/safemode/model/ErrorType;",
"Lcom/shopee/bke/lib/safemode/activity/SafeModeRecoverActivity;",
"Lcom/shopee/bke/lib/safemode/b;",
]
with zipfile.ZipFile(str(APK)) as zf:
for name in zf.namelist():
if not name.endswith(".dex"):
continue
data = zf.read(name)
if not any(t.replace("L", "").replace(";", "").encode() in data for t in TARGETS):
continue
tmp = Path(__file__).resolve().parent.parent / "tmp" / "sg_safemode.dex"
tmp.parent.mkdir(parents=True, exist_ok=True)
tmp.write_bytes(data)
out = subprocess.check_output(
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
)
for target in TARGETS:
if target.replace("L", "").replace(";", "") not in data.decode("latin1", errors="ignore"):
continue
print("=" * 60, name, target)
cap = False
for line in out.splitlines():
if f"Class descriptor : '{target}'" in line:
cap = True
elif cap and line.startswith(" Class descriptor") and target not in line:
break
if cap and ("name :" in line or "const-string" in line or "ErrorType" in line):
print(line.strip()[:140])

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
import re
import zipfile
from pathlib import Path
APK = Path(__file__).resolve().parent.parent / "apks" / "maribank_sg_base.apk"
with zipfile.ZipFile(str(APK)) as zf:
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
print("=== Adb / SafeMode related classes ===")
for m in re.finditer(rb"L[\w$/]*(Adb|ADB|SafeMode|safemode|Recover)[\w$/]*;", data):
c = m.group().decode()
if "shopee" in c or "bke" in c or "maribank" in c.lower():
print(c)
print("\n=== shpssdk bank ===")
for m in re.finditer(rb"Lcom/shopee/shpssdk[\w$/]*;", data):
print(m.group().decode())
print("\n=== api.seabank / maribank hosts ===")
for m in re.finditer(rb"https?://[a-zA-Z0-9._/-]{8,80}", data):
u = m.group().decode()
if "maribank" in u or "seabank" in u:
print(u)
print("\n=== login/register uapi ===")
for m in re.finditer(rb"/uapi/[a-zA-Z0-9_/-]+", data):
print(m.group().decode())

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import re
import zipfile
from pathlib import Path
APK = Path(__file__).resolve().parent.parent / "apks" / "maribank_sg_base.apk"
with zipfile.ZipFile(str(APK)) as zf:
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
for pat in [
b"Does not support",
b"Turn Off ADB",
b"Wireless ADB",
b"ADB/Wireless",
b"support root",
b"RISK_USB_ADB",
b"RISK_WIFI_ADB",
b"KEY_ALLOW_ADB",
b"SafeModeRecover",
b"sg.com.maribank",
]:
print(pat.decode(), "->", data.count(pat))
print("\n--- UI strings ---")
for m in re.finditer(rb"[\x20-\x7e]{10,200}", data):
s = m.group().decode("ascii", "ignore")
sl = s.lower()
if ("adb" in sl and ("detect" in sl or "turn" in sl or "wireless" in sl or "debug" in sl)) or "does not support root" in sl:
print(s)
print("\n--- Application ---")
for m in re.finditer(rb"Lcom/shopee/bke/[\w$/]*Application[\w$/]*;", data):
print(m.group().decode())
print("\n--- ADB risk classes ---")
for m in re.finditer(rb"L[\w$/]*(adb|Adb|ADB)[\w$/]*;", data):
c = m.group().decode()
if "shopee" in c.lower() or "bke" in c.lower() or "shps" in c.lower():
print(c)

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""Quick DEX scan for MariBank SG detection / auth strings."""
import re
import sys
import zipfile
from pathlib import Path
APK = Path(__file__).resolve().parent.parent / "apks" / "maribank_sg_base.apk"
if len(sys.argv) > 1:
APK = Path(sys.argv[1])
needles = [
b"ADB",
b"Wireless ADB",
b"USB debugging",
b"Wireless debugging",
b"Does not support root",
b"root device",
b"rooted",
b"jailbroken",
b"safemode",
b"SafeMode",
b"shpssdk",
b"SHPSSDK",
b"getRiskToken",
b"requestDefense",
b"BkeApplication",
b"errorcodehandler",
b"4067012",
b"4067004",
b"/uapi/",
b"/dfp/",
b"register",
b"login",
b"auth/precheck",
]
class_needles = [
rb"Lcom/shopee/bke/[\w$/]+;",
rb"Lcom/shopee/shpssdk[\w$/]*;",
]
with zipfile.ZipFile(str(APK)) as zf:
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
print("APK:", APK.name, "dex bytes:", len(data))
print("\n=== string hits ===")
for n in needles:
c = data.count(n)
if c:
print(f" {n.decode(errors='replace')!r}: {c}")
print("\n=== api paths (sample) ===")
paths = sorted(set(m.group().decode() for m in re.finditer(rb"/v[0-9]/[a-zA-Z0-9_/-]{4,80}", data)))
for p in paths:
pl = p.lower()
if any(k in pl for k in ("auth", "login", "register", "otp", "dfp", "user", "mobile", "pin")):
print(" ", p)
print("\n=== shopee/bke classes (sample) ===")
classes = sorted(set(m.group().decode() for m in re.finditer(rb"Lcom/shopee/bke/[\w$/]{8,120};", data)))
keywords = ("safemode", "risk", "adb", "debug", "root", "error", "user", "digitalbank", "Application")
shown = 0
for c in classes:
cl = c.lower()
if any(k in cl for k in keywords):
print(" ", c)
shown += 1
if shown >= 40:
break
print(f" ... total bke classes: {len(classes)}")
print("\n=== context: ADB Detected ===")
idx = data.find(b"ADB")
while idx >= 0 and idx < len(data):
chunk = data[max(0, idx - 30) : idx + 80]
if b"Detect" in chunk or b"debug" in chunk.lower() or b"Wireless" in chunk:
s = re.sub(rb"[^\x20-\x7e]+", b" ", chunk).decode("ascii", "ignore").strip()
if len(s) > 20:
print(" ", s[:120])
idx = data.find(b"ADB", idx + 1)
if idx > 0 and data.find(b"ADB", idx + 1) == -1:
break

View File

@@ -9,6 +9,8 @@ scopes = [
"org.telegram.messenger.web",
"org.telegram.messenger",
"com.miraclegarden.smsmessage",
"sg.com.maribankmobile.digitalbank",
"ph.seabank.seabank",
"au.com.up.money",
"au.com.suncorp.marketplace",
"au.com.bank86400",

View File

@@ -1,29 +1,15 @@
# MariBank / Hook 相关 logcatadb 不在 PATH 时也可用)
param(
[switch]$Clear,
[switch]$Follow
)
$ErrorActionPreference = "Stop"
$sdk = "C:\Users\Administrator\AppData\Local\Android\Sdk"
$adb = Join-Path $sdk "platform-tools\adb.exe"
# Capture MariBank SG hook logs (clears buffer first if -Clear switch passed)
param([switch]$Clear)
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
if (-not (Test-Path $adb)) {
Write-Host "adb not found at $adb" -ForegroundColor Red
Write-Host "adb not found: $adb" -ForegroundColor Red
exit 1
}
$pattern = "MariBankRoot|1201|seabank|ClashMeta|LSPosed-Bridge.*notiMessage"
if ($Clear) {
& $adb logcat -c
Write-Host "logcat cleared." -ForegroundColor Green
Write-Host "Logcat cleared. Now click Next in MariBank, then run without -Clear:" -ForegroundColor Yellow
Write-Host " .\scripts\logcat-maribank.ps1"
exit 0
}
if ($Follow) {
Write-Host "Following logcat (Ctrl+C to stop)..." -ForegroundColor Cyan
& $adb logcat | Select-String -Pattern $pattern
} else {
& $adb logcat -d | Select-String -Pattern $pattern
}
& $adb logcat -d 2>&1 | Select-String -Pattern "MariBankRoot|MariBankNative" |
Select-String -Pattern "HTTP|outbound|register|faked|finish adb|blocked|ErrorFlow|RegisterViewModel|skip error|late app|late native|assessRisk|risk callback"

View File

@@ -0,0 +1,6 @@
id=maribank_device_spoof
name=MariBank Device Spoof
version=v1.0
versionCode=1
author=miraclegarden
description=Spoof serial/boot/build props for MariBank SHPSSDK fingerprint. Pair with Shamiko + DenyList for sg.com.maribankmobile.digitalbank. Generates a stable fake serial and android_id on first boot.

View File

@@ -0,0 +1,50 @@
#!/system/bin/sh
# Early boot: spoof read-only props before most apps start.
# resetprop is provided by Magisk.
MODDIR=${0%/*}
LOGTAG="maribank_device_spoof"
log() {
echo "[$LOGTAG] $*" >> /cache/maribank_device_spoof.log 2>/dev/null
echo "[$LOGTAG] $*"
}
if [ ! -f "$MODDIR/serial.txt" ]; then
# 16-char alphanumeric serial, stable across reboots
SERIAL=$(cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '-' | cut -c1-16)
[ -z "$SERIAL" ] && SERIAL="MB$(date +%s | tail -c 9)"
echo "$SERIAL" > "$MODDIR/serial.txt"
fi
SERIAL=$(cat "$MODDIR/serial.txt")
log "serial=$SERIAL"
# --- device identity (SHPSSDK / attestation often reads these) ---
resetprop -n ro.serialno "$SERIAL"
resetprop -n ro.boot.serialno "$SERIAL"
resetprop -n ro.boot.serialno "$SERIAL"
resetprop -n persist.sys.serialno "$SERIAL"
# --- hide root / debug fingerprint ---
resetprop -n ro.debuggable 0
resetprop -n ro.secure 1
resetprop -n ro.adb.secure 1
resetprop -n ro.build.type user
resetprop -n ro.build.tags release-keys
resetprop -n ro.boot.verifiedbootstate green
resetprop -n ro.boot.flash.locked 1
resetprop -n ro.boot.vbmeta.device_state locked
resetprop -n vendor.boot.vbmeta.device_state locked
resetprop -n ro.boot.veritymode enforcing
resetprop -n ro.boot.warranty_bit 0
resetprop -n ro.crypto.state encrypted
# --- adb off (match Java-layer bypass) ---
resetprop -n init.svc.adbd stopped
resetprop -n init.svc.adb stopped
resetprop -n service.adb.root 0
resetprop -n persist.sys.adb_enable 0
resetprop -n persist.adb.wifi.enabled 0
log "post-fs-data done"

View File

@@ -0,0 +1,37 @@
#!/system/bin/sh
# After boot: rotate Settings.Secure.android_id once (global, affects all apps).
MODDIR=${0%/*}
LOGTAG="maribank_device_spoof"
log() {
echo "[$LOGTAG] $*" >> /cache/maribank_device_spoof.log 2>/dev/null
}
# Wait for SettingsProvider
i=0
while [ "$(getprop sys.boot_completed)" != "1" ] && [ "$i" -lt 120 ]; do
sleep 1
i=$((i + 1))
done
sleep 3
if [ ! -f "$MODDIR/android_id.txt" ]; then
# 16 hex chars (standard ANDROID_ID format)
AID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '-' | cut -c1-16)
[ -z "$AID" ] && AID="$(date +%s | md5sum 2>/dev/null | cut -c1-16)"
echo "$AID" > "$MODDIR/android_id.txt"
fi
AID=$(cat "$MODDIR/android_id.txt")
settings put secure android_id "$AID" 2>/dev/null
log "android_id=$AID"
# Clear MariBank cache so SHPSSDK re-collects with new props (optional, user can disable)
PKG="sg.com.maribankmobile.digitalbank"
if [ -f "$MODDIR/clear_maribank_on_boot" ]; then
pm clear "$PKG" 2>/dev/null
log "pm clear $PKG"
fi
log "service.sh done"

View File

@@ -0,0 +1,75 @@
# Finish scheme B after reboot: LSPosed scope + verify spoof + clear MariBank
param([switch]$Reboot)
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $PSScriptRoot
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
$Pkg = "sg.com.maribankmobile.digitalbank"
$WaitSeconds = 60
Write-Host "Checking adb ($WaitSeconds s timeout)..." -ForegroundColor Cyan
$deadline = (Get-Date).AddSeconds($WaitSeconds)
$ready = $false
while ((Get-Date) -lt $deadline) {
$lines = & $adb devices 2>&1
if ($lines -match "1C081FDF600K5Q\s+device") {
$ready = $true
break
}
if ($lines -match "\tunauthorized") {
Write-Host "Device connected but UNAUTHORIZED — unlock phone and tap Allow USB debugging." -ForegroundColor Red
}
Start-Sleep -Seconds 2
}
if (-not $ready) {
Write-Host @"
No adb device found after ${WaitSeconds}s.
On Pixel 6:
1. USB cable connected (data port, not charge-only)
2. Settings -> Developer options -> USB debugging ON
3. USB mode: File transfer / PTP
4. Unlock screen -> tap Allow on RSA prompt
5. Re-run: .\scripts\maribank-scheme-b-finish.ps1
If USB debugging was turned off earlier, you must enable it on the phone first.
"@ -ForegroundColor Yellow
exit 1
}
Start-Sleep -Seconds 2
Write-Host "`n=== Modules ===" -ForegroundColor Cyan
& $adb shell su -c "ls /data/adb/modules/"
& $adb shell su -c "magisk --denylist status"
Write-Host "`n=== Identity ===" -ForegroundColor Cyan
& $adb shell su -c "getprop ro.serialno; getprop ro.boot.serialno; settings get secure android_id"
& $adb shell su -c "cat /data/adb/modules/maribank_device_spoof/serial.txt 2>/dev/null; cat /data/adb/modules/maribank_device_spoof/android_id.txt 2>/dev/null"
Write-Host "`n=== LSPosed scope (MariBank SG) ===" -ForegroundColor Cyan
$apkPath = (& $adb shell pm path com.miraclegarden.smsmessage.xposed 2>$null) -replace '^package:', ''
$apkPath = $apkPath.Trim()
if (-not $apkPath) {
Write-Host "Xposed module not installed" -ForegroundColor Red
exit 1
}
& $adb shell su -c "cp /data/adb/lspd/config/modules_config.db /sdcard/Download/modules_config.db; chmod 644 /sdcard/Download/modules_config.db"
$db = Join-Path $env:TEMP "modules_config_finish.db"
& $adb pull /sdcard/Download/modules_config.db $db | Out-Null
python "$ProjectRoot\scripts\configure-lsposed.py" $db $apkPath
& $adb push $db /sdcard/Download/modules_config.db | Out-Null
& $adb shell su -c "cp /sdcard/Download/modules_config.db /data/adb/lspd/config/modules_config.db; rm -f /data/adb/lspd/config/modules_config.db-wal /data/adb/lspd/config/modules_config.db-shm; chmod 660 /data/adb/lspd/config/modules_config.db"
Write-Host "`n=== Disable USB debug + clear MariBank ===" -ForegroundColor Cyan
& $adb shell su -c "settings put global adb_enabled 0; settings put global development_settings_enabled 0"
& $adb shell su -c "pm clear $Pkg"
Write-Host "`n=== Done ===" -ForegroundColor Green
Write-Host "Open MariBank -> Sign up -> enter phone -> Next"
Write-Host "Log: adb logcat -d | Select-String 'MariBankEncrypt|MariBankAttest|3100012|deviceFingerprint'"
if ($Reboot) {
Write-Host "Rebooting..." -ForegroundColor Yellow
& $adb reboot
}

View File

@@ -0,0 +1,121 @@
# MariBank device spoof — Magisk resetprop + optional module install
# Usage:
# .\scripts\maribank-spoof-device.ps1 # apply resetprop once via adb su
# .\scripts\maribank-spoof-device.ps1 -InstallModule # zip & push Magisk module
# .\scripts\maribank-spoof-device.ps1 -NewIdentity # regenerate serial/android_id files on device
# .\scripts\maribank-spoof-device.ps1 -ClearMariBank # pm clear MariBank after spoof
param(
[switch]$InstallModule,
[switch]$NewIdentity,
[switch]$ClearMariBank,
[string]$DeviceSerial = ""
)
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $PSScriptRoot
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
$ModuleDir = Join-Path $PSScriptRoot "magisk\maribank-device-spoof"
$Pkg = "sg.com.maribankmobile.digitalbank"
if (-not (Test-Path $adb)) {
Write-Host "adb not found: $adb" -ForegroundColor Red
exit 1
}
function Invoke-AdbShell($cmd) {
& $adb shell "su -c '$cmd'" 2>&1
}
function Test-Magisk {
$m = Invoke-AdbShell "command -v resetprop 2>/dev/null || ls /data/adb/magisk/magisk 2>/dev/null"
return ($LASTEXITCODE -eq 0 -and "$m" -match "resetprop|magisk")
}
Write-Host "=== MariBank Device Spoof (方案 B: Magisk resetprop) ===" -ForegroundColor Cyan
& $adb devices -l
if (-not (Test-Magisk)) {
Write-Host "Magisk/resetprop not found on device. Install Magisk first." -ForegroundColor Red
exit 1
}
if ($InstallModule) {
$zipPath = Join-Path $env:TEMP "maribank-device-spoof.zip"
if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
# Use tar (Windows 10+) for Unix paths; Magisk needs post-fs-data.sh at zip root
Push-Location $ModuleDir
tar -a -cf $zipPath module.prop post-fs-data.sh service.sh
Pop-Location
Write-Host "Pushing module to /sdcard/Download/ ..."
& $adb push $zipPath /sdcard/Download/maribank-device-spoof.zip
Write-Host @"
Module zip pushed. On phone:
1. Magisk -> Modules -> Install from storage -> maribank-device-spoof.zip
2. Reboot
3. Enable Shamiko (see below)
"@ -ForegroundColor Yellow
}
if ($NewIdentity) {
Write-Host "Removing saved identity (module will regenerate on next boot) ..."
Invoke-AdbShell "rm -f /data/adb/modules/maribank_device_spoof/serial.txt /data/adb/modules/maribank_device_spoof/android_id.txt"
}
if ($DeviceSerial -eq "") {
$DeviceSerial = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | ForEach-Object { [char]$_ })
}
$AndroidId = -join ((48..57) + (97..102) | Get-Random -Count 16 | ForEach-Object { [char]$_ })
Write-Host "Applying one-shot resetprop (serial=$DeviceSerial android_id=$AndroidId) ..."
$props = @(
"resetprop ro.serialno $DeviceSerial",
"resetprop ro.boot.serialno $DeviceSerial",
"resetprop persist.sys.serialno $DeviceSerial",
"resetprop ro.debuggable 0",
"resetprop ro.secure 1",
"resetprop ro.build.tags release-keys",
"resetprop ro.boot.verifiedbootstate green",
"resetprop ro.boot.flash.locked 1",
"resetprop ro.boot.vbmeta.device_state locked",
"resetprop ro.boot.veritymode enforcing",
"resetprop init.svc.adbd stopped",
"resetprop persist.sys.adb_enable 0"
)
foreach ($p in $props) {
Invoke-AdbShell $p | Out-Null
}
Invoke-AdbShell "settings put secure android_id $AndroidId" | Out-Null
Write-Host "Verify:" -ForegroundColor Green
Invoke-AdbShell "getprop ro.serialno; getprop ro.boot.serialno; settings get secure android_id"
if ($ClearMariBank) {
Write-Host "Clearing MariBank app data ..."
Invoke-AdbShell "pm clear $Pkg"
Write-Host "MariBank data cleared. Cold start Sign up again." -ForegroundColor Green
}
Write-Host @"
--- Shamiko checklist (required for scheme B) ---
1. Magisk -> Settings -> Configure DenyList -> enable DenyList
2. DenyList -> add $Pkg (all sub-processes)
3. Install Shamiko module (Magisk repo / GitHub releases)
4. Magisk -> Settings -> hide Magisk app (optional)
5. LSPosed: keep module scoped to MariBank; soft reboot MariBank after spoof
6. Turn OFF USB debugging before testing register (or rely on Xposed adb bypass)
To install persistent module:
.\scripts\maribank-spoof-device.ps1 -InstallModule
To force new identity on next boot:
.\scripts\maribank-spoof-device.ps1 -NewIdentity -InstallModule
(then reboot)
Log on device: /cache/maribank_device_spoof.log
"@ -ForegroundColor Cyan

View File

@@ -20,7 +20,6 @@ public class MainHook implements IXposedHookLoadPackage {
private static final String UP_BANK_PACKAGE = "au.com.up.money";
private static final String SUNCORP_PACKAGE = "au.com.suncorp.marketplace";
private static final String UBANK_PACKAGE = "au.com.bank86400";
private static final String MARIBANK_PACKAGE = MariBankRootBypassHook.PACKAGE;
private static final String MAIN_APP_PACKAGE = "com.miraclegarden.smsmessage";
@Override
@@ -58,7 +57,7 @@ public class MainHook implements IXposedHookLoadPackage {
return;
}
if (MARIBANK_PACKAGE.equals(lpparam.packageName)) {
if (MariBankRootBypassHook.isTargetPackage(lpparam.packageName)) {
MariBankShpsNativeHook.install(lpparam);
MariBankRootBypassHook.install(lpparam);
return;

View File

@@ -0,0 +1,232 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
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;
/**
* SHPSSDK attestation 生成链 Hook含 native JNI
* 目标:在 {@code rdVerifyInfo.data/dataKey} 组装前,让 native 采集层读到「干净环境」。
*/
public final class MariBankAttestationHook {
private static final String TAG = "notiMessageHook/MariBankAttest";
private static volatile boolean installed = false;
private MariBankAttestationHook() {
}
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
}
installed = true;
int n = 0;
n += hookAttestationClass(lpparam, "com.shopee.shpssdkbank.wvvvuwwu");
n += hookAttestationClass(lpparam, "com.shopee.shpssdk.wvvvuwwu");
n += hookAttestationClass(lpparam,
"com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu");
n += hookKnownAttestationMethods(lpparam);
hookEnvironmentProbes(lpparam);
XposedBridge.log(TAG + " attestation hooks=" + n);
}
/** 逆向确认的 attestation / requestDefense 桥接方法。 */
private static int hookKnownAttestationMethods(XC_LoadPackage.LoadPackageParam lpparam) {
int count = 0;
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vvuwuuvuu");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "wwvwvwuvv");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuuwv");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wuvwuvwwu");
return count;
}
private static int hookMethodByName(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (!methodName.equals(method.getName())) {
continue;
}
if (hookAttestationMethod(className, method)) {
count++;
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + "." + methodName + ": " + t.getMessage());
}
return count;
}
private static int hookAttestationClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (!Modifier.isStatic(method.getModifiers())) {
continue;
}
if (hookAttestationMethod(className, method)) {
count++;
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
}
return count;
}
private static boolean hookAttestationMethod(String className, Method method) {
Class<?> rt = method.getReturnType();
if (rt == String.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (!(result instanceof String)) {
return;
}
String s = (String) result;
String sanitized = sanitizeAttestationString(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " " + className + "#" + method.getName()
+ (Modifier.isNative(method.getModifiers()) ? " (native)" : "")
+ " sanitized len=" + s.length());
}
}
});
return true;
}
if (rt == byte[].class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (!(result instanceof byte[])) {
return;
}
byte[] bytes = (byte[]) result;
byte[] out = MariBankRegisterPayloadUtil.sanitizeRegistrationBytes(bytes);
if (out != bytes) {
param.setResult(out);
XposedBridge.log(TAG + " " + className + "#" + method.getName()
+ " byte[] sanitized");
} else {
byte[] tokenOut = MariBankRiskTokenUtil.sanitizeBytes(bytes, 0, bytes.length);
if (tokenOut != bytes) {
param.setResult(tokenOut);
XposedBridge.log(TAG + " " + className + "#" + method.getName()
+ " byte[] riskToken sanitized");
}
}
}
});
return true;
}
if (rt == boolean.class || rt == Boolean.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
return true;
}
if (rt == int.class || rt == Integer.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(0);
}
});
return true;
}
return false;
}
private static String sanitizeAttestationString(String s) {
if (s == null || s.isEmpty()) {
return s;
}
if (MariBankRegisterPayloadUtil.isRegistrationPayload(s)) {
return MariBankRegisterPayloadUtil.sanitizeRegistrationJson(s);
}
if (s.contains("|")) {
return MariBankRiskTokenUtil.sanitizeRiskToken(s);
}
return MariBankRiskTokenUtil.sanitizeAllInText(s);
}
private static void hookEnvironmentProbes(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"android.os.Debug",
lpparam.classLoader,
"isDebuggerConnected",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
}
);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"android.os.Debug",
lpparam.classLoader,
"waitingForDebugger",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
}
);
} catch (Throwable ignored) {
}
RootBypassHelper.hookFileExists(lpparam);
RootBypassHelper.hookRuntimeExec(lpparam);
RootBypassHelper.hookSystemGetProperty(lpparam);
hookProcessBuilder(lpparam);
}
private static void hookProcessBuilder(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
ProcessBuilder.class,
"start",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
ProcessBuilder pb = (ProcessBuilder) param.thisObject;
if (pb == null || pb.command() == null) {
return;
}
String joined = String.join(" ", pb.command()).toLowerCase();
if (joined.contains(" su") || joined.startsWith("su")
|| joined.contains("magisk") || joined.contains("which su")
|| joined.contains("getprop ro.debuggable")) {
XposedBridge.log(TAG + " blocked ProcessBuilder: " + joined);
throw new SecurityException("blocked root probe");
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " ProcessBuilder hook failed: " + t.getMessage());
}
}
}

View File

@@ -0,0 +1,71 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.robv.android.xposed.XposedBridge;
/**
* register 加密前 JSON 净化:{@code scene=REGISTRATION} 路径上的 deviceFingerprint / riskToken。
*/
final class MariBankRegisterPayloadUtil {
private static final String TAG = "notiMessageHook/MariBankRegister";
private static final Pattern DEVICE_FINGERPRINT = Pattern.compile(
"\"deviceFingerprint\"\\s*:\\s*\"([^\"]+)\"");
private MariBankRegisterPayloadUtil() {
}
static boolean isRegistrationPayload(String text) {
if (text == null || text.length() < 24) {
return false;
}
return text.contains("\"scene\":\"REGISTRATION\"")
|| text.contains("\"scene\": \"REGISTRATION\"")
|| (text.contains("rdVerifyInfo") && text.contains("\"step\":\"BE\""));
}
static byte[] sanitizeRegistrationBytes(byte[] data) {
if (data == null || data.length == 0) {
return data;
}
String text = new String(data, StandardCharsets.UTF_8);
if (!isRegistrationPayload(text)) {
return MariBankRiskTokenUtil.sanitizeBytes(data, 0, data.length);
}
String out = sanitizeRegistrationJson(text);
if (out.equals(text)) {
return data;
}
XposedBridge.log(TAG + " sanitized register payload len=" + data.length + " -> " + out.length());
return out.getBytes(StandardCharsets.UTF_8);
}
static String sanitizeRegistrationJson(String json) {
if (json == null) {
return null;
}
String out = MariBankRiskTokenUtil.sanitizeAllInText(json);
Matcher m = DEVICE_FINGERPRINT.matcher(out);
StringBuffer sb = new StringBuffer();
boolean changed = false;
while (m.find()) {
String old = m.group(1);
String neu = MariBankRiskTokenUtil.sanitizeRiskToken(old);
if (!neu.equals(old)) {
changed = true;
}
m.appendReplacement(sb, Matcher.quoteReplacement(
"\"deviceFingerprint\":\"" + neu + "\""));
}
if (changed) {
m.appendTail(sb);
out = sb.toString();
XposedBridge.log(TAG + " deviceFingerprint sanitized in register JSON");
}
return out;
}
}

View File

@@ -2,6 +2,7 @@ package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Activity;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.os.Process;
import android.view.View;
@@ -22,13 +23,30 @@ import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* MariBank / SeaBank PHph.seabank.seabankRoot 检测绕过。
* 逆向SafeMode SDK + SHPSSDK检测到 Root 后会 Toast 并 Process.killProcess 自杀
* MariBank / SeaBank Root 检测绕过PH + SG
* 逆向SafeMode SDK + SHPSSDKSG 额外有 USB/无线 ADB 检测RISK_USB_ADB / RISK_WIFI_ADB
*/
public final class MariBankRootBypassHook {
private static final String TAG = "notiMessageHook/MariBankRoot";
/** 菲律宾 MariBank / SeaBank PH */
public static final String PACKAGE = "ph.seabank.seabank";
/** 新加坡 MariBank */
public static final String PACKAGE_SG = "sg.com.maribankmobile.digitalbank";
private static final String[] TARGET_PACKAGES = {PACKAGE, PACKAGE_SG};
public static boolean isTargetPackage(String packageName) {
if (packageName == null) {
return false;
}
for (String pkg : TARGET_PACKAGES) {
if (pkg.equals(packageName)) {
return true;
}
}
return false;
}
/** 服务端注册被拒错误码logcat 实测)。 */
private static final int ERROR_CODE_SECURITY_BLOCKED = 4067004;
@@ -56,18 +74,21 @@ public final class MariBankRootBypassHook {
"com.shopee.bke.biz.user.errorcodehandler.a",
"com.shopee.bke.biz.user.errorcodehandler.b",
"com.shopee.bke.biz.user.rn.helper.ErrorFlowHelper",
"com.shopee.bke.biz.user.viewmodel.RegisterViewModel",
};
private MariBankRootBypassHook() {
}
private static volatile boolean deferredHooksInstalled = false;
private static volatile boolean lateAppHooksInstalled = false;
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!PACKAGE.equals(lpparam.packageName)) {
if (!isTargetPackage(lpparam.packageName)) {
return;
}
XposedBridge.log(TAG + " install for " + lpparam.packageName);
hookAntiSuicide(lpparam);
hookAntiSoftCrash(lpparam);
scheduleAppHooks(lpparam);
@@ -85,6 +106,13 @@ public final class MariBankRootBypassHook {
MariBankShpsNativeHook.installDeferred(lpparam);
}
};
XC_MethodHook afterOnCreate = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
MariBankShpsNativeHook.installLateNativeHooks(lpparam);
installLateAppHooks(lpparam);
}
};
try {
XposedHelpers.findAndHookMethod(
"com.shopee.bke.digitalbank.BkeApplication",
@@ -92,36 +120,54 @@ public final class MariBankRootBypassHook {
"attachBaseContext",
"android.content.Context",
afterAttach);
XposedBridge.log(TAG + " waiting attachBaseContext for app hooks");
XposedHelpers.findAndHookMethod(
"com.shopee.bke.digitalbank.BkeApplication",
lpparam.classLoader,
"onCreate",
afterOnCreate);
XposedBridge.log(TAG + " waiting attachBaseContext + onCreate for app hooks");
} catch (Throwable t) {
XposedBridge.log(TAG + " attachBaseContext hook failed, install now: " + t.getMessage());
XposedBridge.log(TAG + " BkeApplication hook failed, install now: " + t.getMessage());
installDeferredHooks(lpparam);
MariBankShpsNativeHook.installDeferred(lpparam);
MariBankShpsNativeHook.installLateNativeHooks(lpparam);
installLateAppHooks(lpparam);
}
}
/** onCreate 之后补装:此时 classes11 / SHPSSDK 与 RN SO 均已就绪。 */
private static void installLateAppHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (lateAppHooksInstalled) {
return;
}
lateAppHooksInstalled = true;
int hooked = 0;
for (String className : SAFE_MODE_CLASSES) {
hooked += hookAllBooleanChecks(lpparam, className);
}
hooked += hookShpsRisk(lpparam);
hookShpsToken(lpparam);
hookErrorFlowLogging(lpparam);
MariBankSdkUtilsHook.installLate(lpparam);
MariBankAttestationHook.installLate(lpparam);
XposedBridge.log(TAG + " late app hooks installed, booleanHooks=" + hooked);
}
private static void installDeferredHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (deferredHooksInstalled) {
return;
}
deferredHooksInstalled = true;
int hooked = 0;
for (String className : SAFE_MODE_CLASSES) {
hooked += hookAllBooleanChecks(lpparam, className);
}
hookSafeModeDialog(lpparam);
hookRootDialogBlock(lpparam);
hookErrorFlowLogging(lpparam);
hooked += hookShpsRisk(lpparam);
hookShpsToken(lpparam);
hookAdbBypass(lpparam);
hookNetworkLogging(lpparam);
XposedBridge.log(TAG + " app hooks installed, booleanHooks=" + hooked);
XposedBridge.log(TAG + " early app hooks installed");
}
/** SafeMode 类方法名被混淆Hook 所有返回 boolean/int 的实例方法。 */
private static int hookAllBooleanChecks(XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
@@ -416,7 +462,9 @@ public final class MariBankRootBypassHook {
}
String args = Arrays.toString(param.args);
if (args.contains("1201") || args.contains("-1201")
|| args.toLowerCase().contains("error")) {
|| args.contains("406")
|| args.toLowerCase().contains("error")
|| args.toLowerCase().contains("unavailable")) {
XposedBridge.log(TAG + " " + className + "."
+ method.getName() + " args=" + args);
}
@@ -903,12 +951,17 @@ public final class MariBankRootBypassHook {
body = sanitized;
}
String lower = body.toLowerCase();
if (lower.contains("blocked")
String url = CURRENT_REQUEST_URL.get();
boolean maribankApi = url != null
&& (url.contains("maribank.com") || url.contains("seabank.ph"));
boolean interesting = lower.contains("blocked")
|| body.contains(String.valueOf(ERROR_CODE_SECURITY_BLOCKED))
|| body.contains(String.valueOf(ERROR_CODE_SECURITY_BLOCKED_ALT))
|| lower.contains("risktoken")
|| lower.contains("\"code\"")) {
String url = CURRENT_REQUEST_URL.get();
|| lower.contains("\"code\"")
|| (maribankApi && (lower.contains("register")
|| lower.contains("error") || lower.contains("unavailable")));
if (interesting) {
String snippet = body.length() > 600
? body.substring(0, 600) + "..." : body;
XposedBridge.log(TAG + " HTTP"
@@ -1035,6 +1088,20 @@ public final class MariBankRootBypassHook {
if (sanitized != request) {
XposedHelpers.setObjectField(param.thisObject, "originalRequest", sanitized);
}
try {
Object req = XposedHelpers.getObjectField(param.thisObject, "originalRequest");
if (req != null) {
Object url = XposedHelpers.callMethod(req, "url");
if (url != null) {
String urlStr = String.valueOf(url);
if (urlStr.contains("maribank.com") || urlStr.contains("seabank.ph")
|| urlStr.contains("/register")) {
XposedBridge.log(TAG + " outbound " + urlStr);
}
}
}
} catch (Throwable ignored) {
}
} catch (Throwable ignored) {
}
}
@@ -1516,6 +1583,139 @@ public final class MariBankRootBypassHook {
}
}
/** SGADB / 无线调试检测 + Root 弹窗/Toast/全屏页拦截。 */
private static void hookAdbBypass(XC_LoadPackage.LoadPackageParam lpparam) {
hookAdbSettings(lpparam);
hookAdbActivityEscape(lpparam);
}
private static void hookAdbSettings(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook fakeDisabled = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args.length < 2 || !(param.args[1] instanceof String)) {
return;
}
String key = (String) param.args[1];
if (!isAdbSettingKey(key)) {
return;
}
Class<?> ret = ((Method) param.method).getReturnType();
if (ret == int.class || ret == Integer.class) {
param.setResult(0);
} else if (ret == long.class || ret == Long.class) {
param.setResult(0L);
} else if (ret == String.class) {
param.setResult("0");
}
XposedBridge.log(TAG + " faked Settings key=" + key);
}
};
String[][] targets = {
{"android.provider.Settings$Global", "getInt"},
{"android.provider.Settings$Global", "getLong"},
{"android.provider.Settings$Global", "getString"},
{"android.provider.Settings$Secure", "getInt"},
{"android.provider.Settings$Secure", "getString"},
{"android.provider.Settings$System", "getInt"},
};
for (String[] target : targets) {
try {
Class<?> clazz = XposedHelpers.findClass(target[0], lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (!target[1].equals(method.getName())) {
continue;
}
Class<?>[] params = method.getParameterTypes();
if (params.length >= 2 && ContentResolver.class.isAssignableFrom(params[0])
&& params[1] == String.class) {
XposedBridge.hookMethod(method, fakeDisabled);
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Settings hook " + target[0] + ": " + t.getMessage());
}
}
}
/** 若全屏/RN 页已展示 ADB 拦截文案,直接 finish 退出该 Activity。 */
private static void hookAdbActivityEscape(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
Activity.class,
"onResume",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (activity == null || activity.isFinishing()) {
return;
}
String name = activity.getClass().getName();
if (name.contains("SafeModeRecoverActivity")) {
return;
}
String text = extractActivityText(activity);
if (isAdbBlockText(text)) {
XposedBridge.log(TAG + " finish adb block activity: " + name);
activity.finish();
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " adb activity hook failed: " + t.getMessage());
}
}
private static String extractActivityText(Activity activity) {
StringBuilder sb = new StringBuilder();
try {
CharSequence title = activity.getTitle();
if (title != null) {
sb.append(title);
}
} catch (Throwable ignored) {
}
try {
if (activity.getWindow() != null) {
collectTextViews(activity.getWindow().getDecorView(), sb);
}
} catch (Throwable ignored) {
}
return sb.toString();
}
private static boolean isAdbSettingKey(String key) {
if (key == null) {
return false;
}
String lower = key.toLowerCase();
return lower.contains("adb")
|| "development_settings_enabled".equals(lower)
|| lower.contains("wireless_debug");
}
private static boolean isAdbBlockText(String text) {
if (text == null || text.isEmpty()) {
return false;
}
String lower = text.toLowerCase();
return lower.contains("adb/wireless adb")
|| lower.contains("wireless adb detected")
|| lower.contains("usb debugging")
|| lower.contains("wireless debugging")
|| lower.contains("turn off adb")
|| lower.contains("third parties to access")
|| lower.contains("safeguard your banking")
|| (lower.contains("adb") && lower.contains("detect"));
}
private static boolean isEnvironmentBlockText(String text) {
return isRootBlockText(text) || isAdbBlockText(text);
}
private static void hookSafeModeDialog(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
@@ -1540,9 +1740,9 @@ public final class MariBankRootBypassHook {
XC_MethodHook blankRootTextHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args.length > 0 && isRootBlockText(String.valueOf(param.args[0]))) {
if (param.args.length > 0 && isEnvironmentBlockText(String.valueOf(param.args[0]))) {
param.args[0] = " ";
XposedBridge.log(TAG + " blanked root dialog message");
XposedBridge.log(TAG + " blanked env block dialog message");
}
}
};
@@ -1574,9 +1774,9 @@ public final class MariBankRootBypassHook {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String s = (String) param.getResult();
if (isRootBlockText(s)) {
if (isEnvironmentBlockText(s)) {
param.setResult(" ");
XposedBridge.log(TAG + " blanked root string resource");
XposedBridge.log(TAG + " blanked env block string resource");
}
}
}
@@ -1593,8 +1793,8 @@ public final class MariBankRootBypassHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Dialog dialog = (Dialog) param.thisObject;
if (isRootBlockText(extractDialogText(dialog))) {
XposedBridge.log(TAG + " blocked root Dialog.show: "
if (isEnvironmentBlockText(extractDialogText(dialog))) {
XposedBridge.log(TAG + " blocked env Dialog.show: "
+ dialog.getClass().getSimpleName());
param.setResult(null);
}
@@ -1620,6 +1820,9 @@ public final class MariBankRootBypassHook {
if (isRootBlockText(text)) {
XposedBridge.log(TAG + " blocked root Toast.show");
param.setResult(null);
} else if (isAdbBlockText(text)) {
XposedBridge.log(TAG + " blocked adb Toast.show");
param.setResult(null);
}
}
}
@@ -1638,7 +1841,9 @@ public final class MariBankRootBypassHook {
|| lower.contains("modified device")
|| lower.contains("magisk/xposed/frida")
|| lower.contains("cannot be accessed on such devices")
|| lower.contains("restore to factory settings");
|| lower.contains("restore to factory settings")
|| lower.contains("does not support root")
|| lower.contains("support root device");
}
private static boolean isSecurityBlockText(String text) {

View File

@@ -0,0 +1,225 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
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;
/**
* register body 加密前 Hook{@code com.shopee.bke.lib.jni.utils.d} / {@code uvwuvwuv}。
* 勿 Hook {@code utils.f}SoUtils否则会 libsdkutils 白屏。
*/
public final class MariBankSdkUtilsHook {
private static final String TAG = "notiMessageHook/MariBankEncrypt";
private static final int MAX_LOG = 2000;
private static final String WRAPPER = "com.shopee.bke.lib.jni.utils.d";
private static final String NATIVE_ENCRYPT = "com.shopee.bke.lib.jni.utils.uvwuvwuv";
private static volatile boolean installed = false;
private MariBankSdkUtilsHook() {
}
/** BkeApplication.onCreate 之后安装libsdkutils 已加载)。 */
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
}
installed = true;
int n = hookEncryptWrapper(lpparam);
n += hookNativeEncryptUtils(lpparam);
n += hookGsonRegister(lpparam);
XposedBridge.log(TAG + " late encrypt hooks=" + n);
}
private static int hookEncryptWrapper(XC_LoadPackage.LoadPackageParam lpparam) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(WRAPPER, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String name = method.getName();
XposedBridge.log(TAG + " >> utils.d." + name);
for (int i = 0; i < param.args.length; i++) {
Object sanitized = sanitizeArg(param.args[i]);
if (sanitized != param.args[i]) {
param.args[i] = sanitized;
}
logArg(" in" + i, param.args[i]);
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (result == null) {
return;
}
if (result instanceof String[]) {
for (int i = 0; i < ((String[]) result).length; i++) {
logArg(" out" + i, ((String[]) result)[i]);
}
} else if (result instanceof byte[]) {
logArg(" out", result);
}
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked utils.d methods=" + count);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip utils.d: " + t.getMessage());
}
return count;
}
private static int hookNativeEncryptUtils(XC_LoadPackage.LoadPackageParam lpparam) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(NATIVE_ENCRYPT, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (!Modifier.isStatic(method.getModifiers())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " >> uvwuvwuv." + method.getName());
for (int i = 0; i < param.args.length; i++) {
Object sanitized = sanitizeArg(param.args[i]);
if (sanitized != param.args[i]) {
param.args[i] = sanitized;
}
logArg(" in" + i, param.args[i]);
}
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked uvwuvwuv methods=" + count);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip uvwuvwuv: " + t.getMessage());
}
return count;
}
private static int hookGsonRegister(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"com.google.gson.Gson",
lpparam.classLoader,
"toJson",
Object.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof String)) {
return;
}
String json = (String) param.getResult();
if (!looksLikeRegisterJson(json)) {
return;
}
String sanitized = MariBankRegisterPayloadUtil.isRegistrationPayload(json)
? MariBankRegisterPayloadUtil.sanitizeRegistrationJson(json)
: MariBankRiskTokenUtil.sanitizeAllInText(json);
if (!sanitized.equals(json)) {
param.setResult(sanitized);
json = sanitized;
}
if (MariBankRegisterPayloadUtil.isRegistrationPayload(json)) {
XposedBridge.log(TAG + " Gson REGISTRATION: " + truncate(json));
}
}
}
);
return 1;
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Gson.toJson: " + t.getMessage());
return 0;
}
}
private static boolean looksLikeRegisterJson(String text) {
if (text == null || text.length() < 8) {
return false;
}
if (MariBankRegisterPayloadUtil.isRegistrationPayload(text)) {
return true;
}
String lower = text.toLowerCase();
return lower.contains("rdverifyinfo")
|| lower.contains("devicefingerprint")
|| (lower.contains("encphone") && lower.contains("scene"));
}
private static Object sanitizeArg(Object arg) {
if (arg instanceof String) {
String s = (String) arg;
if (!s.contains("|") && !looksLikeRegisterJson(s)) {
return arg;
}
String out = MariBankRegisterPayloadUtil.isRegistrationPayload(s)
? MariBankRegisterPayloadUtil.sanitizeRegistrationJson(s)
: MariBankRiskTokenUtil.sanitizeAllInText(s);
return out.equals(s) ? arg : out;
}
if (arg instanceof byte[]) {
byte[] bytes = (byte[]) arg;
byte[] out = MariBankRegisterPayloadUtil.sanitizeRegistrationBytes(bytes);
return out == bytes ? arg : out;
}
return arg;
}
private static void logArg(String label, Object arg) {
if (arg == null) {
XposedBridge.log(TAG + label + " null");
return;
}
if (arg instanceof String) {
String s = (String) arg;
if (s.length() > 4 || s.contains("|") || looksLikeRegisterJson(s)) {
XposedBridge.log(TAG + label + " String(" + s.length() + ") " + truncate(s));
}
return;
}
if (arg instanceof byte[]) {
byte[] bytes = (byte[]) arg;
String text;
try {
text = new String(bytes, StandardCharsets.UTF_8);
} catch (Throwable t) {
text = "<bin>";
}
if (text.contains("|") || looksLikeRegisterJson(text) || bytes.length < 512) {
XposedBridge.log(TAG + label + " byte[" + bytes.length + "] " + truncate(text));
}
}
}
private static String truncate(String s) {
if (s == null) {
return "";
}
if (s.length() <= MAX_LOG) {
return s;
}
return s.substring(0, MAX_LOG) + "...";
}
}

View File

@@ -72,6 +72,15 @@ public final class MariBankShpsNativeHook {
}
private static volatile boolean deferredInstalled = false;
private static volatile boolean lateNativeInstalled = false;
/**
* 勿 Hook负责 SoUtils.loadSoLibrary / libshpssdk_bank.so 加载Hook 会导致 SO 找不到。
*/
private static final Set<String> NATIVE_BRIDGE_EXCLUDED = new HashSet<>(Arrays.asList(
"com.shopee.shpssdkbank.vuvuwwwuw",
"com.shopee.shpssdkbank.vwuuwwvwv"
));
/** loadPackage 阶段只装 /proc 过滤,避免过早触发 SHPSSDK / libsdkutils 死循环白屏。 */
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
@@ -82,21 +91,28 @@ public final class MariBankShpsNativeHook {
XposedBridge.log(TAG + " early hooks OK (proc only)");
}
/** attachBaseContext 之后安装 SHPSSDK 相关 HookClassLoader 已就绪)。 */
/** attachBaseContext 之后:仅装不干扰 SO/RN 加载的 Hook。 */
public static void installDeferred(XC_LoadPackage.LoadPackageParam lpparam) {
if (deferredInstalled) {
return;
}
deferredInstalled = true;
hookLoadLibrary(lpparam);
hookShpssInstall(lpparam);
hookRequestDefense(lpparam);
hookShpsNativeBridge(lpparam);
hookShpsNativeCore(lpparam);
hookBuildFields(lpparam);
XposedBridge.log(TAG + " deferred hooks installed for " + lpparam.packageName);
}
/** BkeApplication.onCreate 之后RN / shpssdk SO 已加载,再装 native 桥接 Hook。 */
public static void installLateNativeHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (lateNativeInstalled) {
return;
}
lateNativeInstalled = true;
hookShpsNativeBridge(lpparam);
hookShpsNativeCore(lpparam);
XposedBridge.log(TAG + " late native hooks installed for " + lpparam.packageName);
}
/** native 直接读 /proc/self/maps 查 hook 库;过滤内容。 */
private static void hookProcAccess(XC_LoadPackage.LoadPackageParam lpparam) {
try {
@@ -233,32 +249,7 @@ public final class MariBankShpsNativeHook {
return "";
}
private static void hookLoadLibrary(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook logHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String lib = String.valueOf(param.args[param.args.length - 1]);
if (lib.contains("shpssdk")) {
XposedBridge.log(TAG + " loading native lib: " + lib);
}
}
};
try {
XposedHelpers.findAndHookMethod(
Runtime.class, "loadLibrary0", ClassLoader.class, String.class, logHook);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
System.class, "loadLibrary", String.class, logHook);
} catch (Throwable ignored) {
}
}
private static void hookShpssInstall(XC_LoadPackage.LoadPackageParam lpparam) {
// 勿 Hook ShpssInstall / vuvuwwwuw会干扰 SoUtils.loadSoLibrary导致 libsdkutils.so 死循环白屏。
}
/** 保留 requestDefense 执行(生成 x-sap-fixme仅净化返回值中的 risk 字段。 */
private static void hookRequestDefense(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : new String[]{
"com.shopee.shpssdkbank.SHPSSDK",
@@ -271,9 +262,26 @@ public final class MariBankShpsNativeHook {
"requestDefense",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked requestDefense");
param.setResult(null);
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (result instanceof String) {
String s = (String) result;
if (s.contains("|")) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " requestDefense String sanitized");
}
}
} else if (result instanceof byte[]) {
byte[] bytes = (byte[]) result;
byte[] out = MariBankRiskTokenUtil.sanitizeBytes(
bytes, 0, bytes.length);
if (out != bytes) {
param.setResult(out);
XposedBridge.log(TAG + " requestDefense byte[] sanitized");
}
}
}
}
);
@@ -304,6 +312,10 @@ public final class MariBankShpsNativeHook {
};
int total = 0;
for (String className : classes) {
if (NATIVE_BRIDGE_EXCLUDED.contains(className)) {
XposedBridge.log(TAG + " skip native-bridge (so loader): " + className);
continue;
}
total += hookAllIntBooleanMethods(lpparam, className);
total += hookAllStringSanitize(lpparam, className);
}
@@ -360,6 +372,10 @@ public final class MariBankShpsNativeHook {
&& rt != int.class && rt != Integer.class) {
continue;
}
if (Modifier.isNative(method.getModifiers()) && rt != boolean.class
&& rt != Boolean.class && rt != int.class && rt != Integer.class) {
continue;
}
if (method.getParameterTypes().length > 4) {
continue;
}
@@ -409,6 +425,9 @@ public final class MariBankShpsNativeHook {
if (!Modifier.isStatic(method.getModifiers())) {
continue;
}
if (Modifier.isNative(method.getModifiers())) {
continue;
}
Class<?> rt = method.getReturnType();
if (rt == String.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@@ -547,6 +566,15 @@ public final class MariBankShpsNativeHook {
if ("ro.debuggable".equals(key)) {
return "0";
}
if ("init.svc.adbd".equals(key) || "init.svc.adb".equals(key)) {
return "stopped";
}
if ("service.adb.root".equals(key)) {
return "0";
}
if ("persist.sys.adb_enable".equals(key)) {
return "0";
}
if ("ro.secure".equals(key)) {
return "1";
}

View File

@@ -9,5 +9,6 @@
<item>au.com.suncorp.marketplace</item>
<item>au.com.bank86400</item>
<item>ph.seabank.seabank</item>
<item>sg.com.maribankmobile.digitalbank</item>
</string-array>
</resources>