157 lines
4.2 KiB
Markdown
157 lines
4.2 KiB
Markdown
# libweapon VIMG 纯算实现记录
|
||
|
||
> 状态:已完成
|
||
>
|
||
> 约束:运行时不依赖 Frida、抓包、APK、ELF、Unicorn 或 app-fields。
|
||
|
||
## 1. 最终结论
|
||
|
||
`passport_account_image` 是客户端本地确定性生成值:
|
||
|
||
```text
|
||
mf(context, event).a().toString()
|
||
-> Engine.pr(99999, 0, javaLength * 2, json)
|
||
-> VIMG_<base64>$AI_<32hex>
|
||
-> WeaponHI.img / wcfg["a_y_q_z"]
|
||
-> mobile/checker、requestMobileCode、mobileVerifyCode
|
||
```
|
||
|
||
`wcfg` 只负责本地缓存。`$AI_` 不是服务端签发值,`/f/a/p` 也不是
|
||
登录票据的生成前置条件。
|
||
|
||
目标样本:
|
||
|
||
```text
|
||
out/p0_64_extract/lib/arm64-v8a/libweapon.2.2174a68a..so
|
||
SHA256 5d7681c583c66b35e0ee3ce558e5a1ff7fa5be44f19a1e217acc2224455837d7
|
||
```
|
||
|
||
## 2. Java 与 JNI 链
|
||
|
||
- `wc.k()` 调用 `Engine.pr(ze.v0, 0, s2.a(str) * 2, str)`。
|
||
- `ze.v0 = 99999`。
|
||
- `str = new mf(context, event).a().toString()`。
|
||
- mode 0 对应 native `0x19ea90`。
|
||
- `0x19ea90` 依次调用 base 生成器 `0x1a1850` 和 AI 生成器 `0x1a2d94`。
|
||
|
||
## 3. VIMG Base
|
||
|
||
```text
|
||
payload_bytes = JNI modified UTF-8(payload)
|
||
plain = 2d 3d 00 00 7d 01 || LE16(len(payload_bytes)) || payload_bytes
|
||
native_buffer = plain XOR 0x55
|
||
cipher = ChaCha20-IETF(native_buffer, fixed_state, counter=1)
|
||
base = "VIMG_" + Base64(cipher)
|
||
```
|
||
|
||
ChaCha 初始状态:
|
||
|
||
```text
|
||
1783595e 8dc26a78 2599184e 729781d3
|
||
2adef3f4 9876ef16 9abed34f 9103de12
|
||
a92157f6 a9a24ff4 9138d3fd 2a2193f3
|
||
00000001 74f4cbcd 98c26a78 ab99184e
|
||
```
|
||
|
||
轮函数是标准 20 轮 ChaCha,旋转量为 `16/12/8/7`。
|
||
|
||
## 4. AI H1
|
||
|
||
将完整 `VIMG_...` ASCII 文本按小端 uint32 分组。调用方每批处理最多
|
||
64 个字,native 压缩函数先折叠为 16 个字:
|
||
|
||
```python
|
||
folded = [0] * 16
|
||
for index, word in enumerate(source_words):
|
||
folded[index % 16] ^= word
|
||
```
|
||
|
||
随后执行自定义 IV 的 10 轮 BLAKE2s 压缩:
|
||
|
||
```text
|
||
H0:
|
||
aa98186e f3ccd768 99531aae 669781d3
|
||
125fd5b4 9883595e 74f4cbcd 98c26a78
|
||
|
||
IV:
|
||
ab99184e f3ccd768 99531aae 669781d3
|
||
125fd5b4 9883595e 74f4cbcd 98c26a78
|
||
```
|
||
|
||
- counter 是累计处理的 uint32 字数。
|
||
- 最后一批令 `v[14] ^= 0xffffffff`。
|
||
- sigma 是标准 BLAKE2s 10x16 消息置换表。
|
||
- 输出为 8 个低 32 位链值。
|
||
- H1 文本为 8 个 `%08x` 直接拼接,末尾附一个空格。
|
||
|
||
## 5. AI H2
|
||
|
||
H2 只使用 H1 文本前 16 个 ASCII 字节:
|
||
|
||
```text
|
||
ADD_BASE = 0c35ff3dcbfefb3f3efd6539fd39efcd
|
||
H2[i] = ((ADD_BASE[i] + 3) & 0xff) XOR H1_TEXT[i]
|
||
AI[i] = H2[i] XOR (2dd345c0)[i % 4]
|
||
```
|
||
|
||
最终:
|
||
|
||
```text
|
||
passport_account_image = base + "$AI_" + hex(AI)
|
||
```
|
||
|
||
## 6. mf Payload
|
||
|
||
`core/weapon_mf.py` 按 `mf.a()` 的 JSONObject 插入顺序构造字段,包括:
|
||
|
||
- 设备型号、厂商、Android 版本和本地 DID。
|
||
- elapsedRealtime、uptime、启动时间和 boot count。
|
||
- Weapon 上报时间与本地报告计数器。
|
||
- 样本中的反调试/环境状态字段。
|
||
|
||
同一轮登录只生成一次票据,并复用于预检、发码和验码。
|
||
|
||
## 7. 实现与验证
|
||
|
||
生产实现:
|
||
|
||
- `core/weapon_vimg.py`:VIMG base、H1/H2、完整票据与反解。
|
||
- `core/weapon_mf.py`:设备 JSON 和 profile 票据生成。
|
||
- `tools/sms_login_cli.py`:纯算票据接入登录链。
|
||
|
||
分析 oracle:
|
||
|
||
- `tools/libweapon_vimg_oracle.py`:仅用于离线差分,不进入生产路径。
|
||
|
||
验证结果:
|
||
|
||
- 固定 native 向量覆盖空输入、UTF-8、ChaCha 跨块、H1 跨 16/64 字。
|
||
- 21 组不同长度随机输入与 Unicorn native oracle 逐字节一致。
|
||
- 捕获票据反解出的 757 字节真实 `mf` JSON 可由 oracle 重新生成完全相同
|
||
的 base 和 `$AI_`。
|
||
|
||
## 8. CLI
|
||
|
||
短信登录每次生成全新画像、在线注册 DFP,并在注册完成后用当前云 DID 现场生成
|
||
`passport_account_image`:
|
||
|
||
```powershell
|
||
uv run python -m tools.sms_login_cli --mobile MOBILE
|
||
```
|
||
|
||
已有验证码时跳过发码:
|
||
|
||
```powershell
|
||
uv run python -m tools.sms_login_cli `
|
||
--mobile MOBILE `
|
||
--code CODE
|
||
```
|
||
|
||
CLI 不提供设备画像、app-fields、WCFG 或复现种子的注入入口,避免把历史设备
|
||
状态带入下一次登录。
|
||
|
||
DFP 在线注册可能把本地初始 DID 替换为云 DID。主 APP 随后调用
|
||
`WeaponHI.setG(currentDid)`,所以 `mf` 字段 `03000` 必须使用注册后的
|
||
`DeviceProfile.did`,并与登录 query 的 `did` 保持一致,不能继续使用
|
||
`local_did`。
|