feat(maribank): PH 注册 OTP 突破、SG bypass 与中文文档
菲律宾 SeaBank 在 Root+LSPosed+Shamiko 下 register 已通过并触发 OTP;扩展 SG 包名、加密前 Hook、Magisk 设备伪装脚本,并将 docs 整理为中文操作与风控说明。
This commit is contained in:
@@ -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 配置
|
||||
|
||||
@@ -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(银行)
|
||||
├── TelegramMessageHook(Telegram)
|
||||
└── …
|
||||
```
|
||||
|
||||
测银行 App 时通常还需 **Shamiko**(Hide Magisk),与 LSPosed 分工见 [`MariBank风控与载荷说明.md` §6](MariBank风控与载荷说明.md)。
|
||||
|
||||
### 0.3 与其他工具对比
|
||||
|
||||
| 工具 | 特点 |
|
||||
|------|------|
|
||||
| **LSPosed** | 常驻、开机自动,适合银行 bypass |
|
||||
| **Frida** | 临时 attach,与 LSPosed 同时开易冲突 |
|
||||
| **Magisk 模块** | 改系统属性(serial),不是 Hook Java |
|
||||
|
||||
---
|
||||
|
||||
255
docs/MariBank_2026-07-06_菲律宾突破.md
Normal file
255
docs/MariBank_2026-07-06_菲律宾突破.md
Normal 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 6(oriole) |
|
||||
| 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 第一次 register(step=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 第二次 register(OTP 触发,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 方案 B:Magisk 设备 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 · 最后更新:文档体系整理(操作手册 + 风控载荷说明)*
|
||||
@@ -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` → OTP(BSO)** |
|
||||
| 注册接口 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 服务端注册拒绝 — 4067012(2026-07-06 已突破 PH)
|
||||
|
||||
**现象**:
|
||||
> **更新**:2026-07-06 在 Shamiko + 方案 B + `MariBankSdkUtilsHook` / `MariBankAttestationHook` 下,PH register 返回 **`code=0`,step=BSO,OTP_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 机部署与日常操作 |
|
||||
|
||||
|
||||
---
|
||||
217
docs/MariBank新加坡逆向.md
Normal file
217
docs/MariBank新加坡逆向.md
Normal 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.2(versionCode **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 突破*
|
||||
215
docs/MariBank风控与载荷说明.md
Normal file
215
docs/MariBank风控与载荷说明.md
Normal 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 连着 adb,App 仍可能认为未开调试**,本地不拦、register 载荷也不带「adb 开」的自报字段。
|
||||
|
||||
### 4.3 与干净机对照
|
||||
|
||||
| 环境 | USB 调试 | 结果 |
|
||||
|------|----------|------|
|
||||
| 干净机(无 Hook) | 开 | 本地 ADB 页,进不了 OTP |
|
||||
| 干净机 | 关 | ✅ OTP |
|
||||
| Pixel 6 + LSPosed + Shamiko | **开** | ✅ PH OTP(Hook 伪装) |
|
||||
|
||||
### 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 分工
|
||||
|
||||
```
|
||||
Magisk(Root 权限)
|
||||
└── 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*
|
||||
@@ -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` | 澳大利亚三家银行 Hook(Up / 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` | 澳大利亚三家银行 Hook(Up / 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
156
docs/手机操作手册.md
Normal 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. 抓 log(PC)
|
||||
|
||||
```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*
|
||||
@@ -1,5 +1,19 @@
|
||||
# 更新说明
|
||||
|
||||
## MariBank 风控 bypass(2026-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.1(2026-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`
|
||||
|
||||
---
|
||||
0
reverse/maribank_sg_screen.png
Normal file
0
reverse/maribank_sg_screen.png
Normal file
38
reverse/scripts/dump_sg_safemode.py
Normal file
38
reverse/scripts/dump_sg_safemode.py
Normal 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])
|
||||
28
reverse/scripts/find_sg_adb_classes.py
Normal file
28
reverse/scripts/find_sg_adb_classes.py
Normal 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())
|
||||
39
reverse/scripts/find_sg_adb_strings.py
Normal file
39
reverse/scripts/find_sg_adb_strings.py
Normal 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)
|
||||
82
reverse/scripts/scan_maribank_sg.py
Normal file
82
reverse/scripts/scan_maribank_sg.py
Normal 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
|
||||
@@ -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",
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
# MariBank / Hook 相关 logcat(adb 不在 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"
|
||||
|
||||
6
scripts/magisk/maribank-device-spoof/module.prop
Normal file
6
scripts/magisk/maribank-device-spoof/module.prop
Normal 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.
|
||||
50
scripts/magisk/maribank-device-spoof/post-fs-data.sh
Normal file
50
scripts/magisk/maribank-device-spoof/post-fs-data.sh
Normal 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"
|
||||
37
scripts/magisk/maribank-device-spoof/service.sh
Normal file
37
scripts/magisk/maribank-device-spoof/service.sh
Normal 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"
|
||||
75
scripts/maribank-scheme-b-finish.ps1
Normal file
75
scripts/maribank-scheme-b-finish.ps1
Normal 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
|
||||
}
|
||||
121
scripts/maribank-spoof-device.ps1
Normal file
121
scripts/maribank-spoof-device.ps1
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 PH(ph.seabank.seabank)Root 检测绕过。
|
||||
* 逆向:SafeMode SDK + SHPSSDK;检测到 Root 后会 Toast 并 Process.killProcess 自杀。
|
||||
* MariBank / SeaBank Root 检测绕过(PH + SG)。
|
||||
* 逆向:SafeMode SDK + SHPSSDK;SG 额外有 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 {
|
||||
}
|
||||
}
|
||||
|
||||
/** SG:ADB / 无线调试检测 + 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) {
|
||||
|
||||
@@ -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) + "...";
|
||||
}
|
||||
}
|
||||
@@ -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 相关 Hook(ClassLoader 已就绪)。 */
|
||||
/** 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";
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user