228 lines
21 KiB
Markdown
228 lines
21 KiB
Markdown
# qianbian Android APK 逆向分析报告(client-qbsg_v1.1.0.apk)
|
||
|
||
> 分析方法:androguard 4.1.4 静态分析(DEX 字符串表 + xref + DAD 反编译目标类);native 库 strings/导出符号粗扫。
|
||
> 对照基线:analysis/web/protocol-raw.md(下称"Web 报告")。
|
||
> 分析产物:analysis/apk/{classes.txt, classes-qbsg.txt, strings-clean.txt, xrefs-key-strings.txt, decomp/*.java, native/libapp.so, native/libeditor.so}
|
||
|
||
## 0. APK 基本信息
|
||
|
||
| 项 | 值 | 证据 |
|
||
|---|---|---|
|
||
| 包名 | `io.github.bszapp.dzjq42` | AndroidManifest |
|
||
| 版本 | v1.1.0-20260612-a2c9488 (versionCode 4) | AndroidManifest |
|
||
| minSdk / targetSdk | 24 / 37 | AndroidManifest |
|
||
| 主 Activity | `qbsg.top.MainActivity` | AndroidManifest |
|
||
| 权限 | BLUETOOTH/ADMIN/SCAN/CONNECT、ACCESS_FINE_LOCATION、INTERNET、**REQUEST_INSTALL_PACKAGES** | AndroidManifest |
|
||
| native 库 | arm64-v8a: libapp.so (1.5MB)、libeditor.so (3.8MB)、libandroidx.graphics.path.so | APK lib/ |
|
||
| 技术栈 | Kotlin Compose Multiplatform;共享逻辑经 R8 混淆(`La0/*`、`Lz7/*` 等);`qbsg.top.*` 为保留名的 Android 平台层;libapp.so/libeditor.so 为 **Rust** 编译(含 sha2 0.10.9、aes 0.8.4、rust-secp256k1 0.11 crate 路径字符串) | classes.txt、libapp.so strings |
|
||
|
||
应用结构要点:
|
||
- `qbsg.top.ble.BleManager/BleSession`:Android GATT 平台层(未混淆)。
|
||
- `e8.o`(抽象 EpdManager)两个实现:`c8.g`(固件家族名"平平/扫地",`j()` 返回)与 `d8.f`("轻舞/时光")——**APK 显式支持至少两个固件家族**,协议细节有差异(见 §3/§4)。
|
||
- `z7.b0`:独立 SUOTA 管理器(扫描→连接→SPOTA 全流程,20.6 万字符反编译代码)。
|
||
- `a8.g`:经已有会话的 OTA 通道(GattOtaChannel)。
|
||
- `qbsg.top.ble.Simulated*`:整套模拟设备框架("NRF-模拟设备"演示模式,SimulatedBleKt.buildSimulatedProfiles 引用 "NRF-")。
|
||
- `qbsg.top.template.editor.LibEditor`:JNI 桥到 libeditor.so(Rust 模板编辑器,导出 create/renderPng/exportBitmap/createElement 等)。
|
||
|
||
## a. GATT UUID 对照
|
||
|
||
证据:`qbsg.top.ble.BleManagerKt.<clinit>`(decomp/qbsg_top_ble_BleManagerKt.java:11-21)。
|
||
|
||
| UUID | 用途 | 与 Web 一致? |
|
||
|---|---|---|
|
||
| `13187b10-eba9-a3ba-044e-83d3217d9a38` | EPD_SERVICE_UUID | ✅ 一致 |
|
||
| `4b646063-6264-f3a7-8941-e65356ea82fe` | EPD_CHAR_UUID | ✅ 一致 |
|
||
| `00001f10-0000-1000-8000-00805f9b34fb` | TX_SERVICE_UUID(RXTX) | ✅ 一致 |
|
||
| `00001f1f-0000-1000-8000-00805f9b34fb` | TX_CHAR_UUID | ✅ 一致 |
|
||
| `00002902-...` | CCCD | Web 未显式用(Web BLE 隐式) |
|
||
| `00002A24-...` / `00002A26-...` | MODEL_NUMBER / FIRMWARE_REVISION(DIS) | **Web 端没有**(APK 连接后读型号+固件版本,BleManager$startDeviceInfoRead$1.java:47-56) |
|
||
|
||
**Web 端没有、APK 新增的 UUID**(全部用于 OTA,详见 §e):
|
||
|
||
| UUID | 角色 |
|
||
|---|---|
|
||
| `0000fef5-0000-1000-8000-00805f9b34fb` | Dialog SPOTA 服务(z7_b0.java:666 等) |
|
||
| `64b4e8b5-0de5-401b-a21d-acc8db3b913a` | SPOTA_SERV_STATUS(z7_b0.java:768) |
|
||
| `8082caa8-41a6-4021-91c6-56f9b954cc34` | SPOTA_MEM_DEV(z7_b0.java:679) |
|
||
| `724249f0-5ec3-4b5f-8804-42345af08651` | SPOTA_GPIO_MAP(z7_b0.java:677) |
|
||
| `457871e8-d516-4ca1-9116-57d0b17b9cb2` | **本固件的 SPOTA_PATCH_DATA**(z7_b0.java:665/1029 错误文案"SPOTA_PATCH_DATA未找到"对应此 UUID;注意这是 Dialog 标准里的 PATCH_LEN UUID,厂商做了自定义映射) |
|
||
| `9d84b9a3-000c-49d8-9183-855b673fda31` | **本固件的 SPOTA_PATCH_LEN**(z7_b0.java:670/1023;非标准 UUID) |
|
||
| `42c3dfdd-77be-4d9c-8454-8f875267fb3b` | SPOTA_MEM_INFO(兼容探测,z7_b0.java:772、a8_g.java:255) |
|
||
| `61c8849c-f639-4765-946e-5c3419bebb2a` / `b7de1eea-823d-43bb-a3af-c4903dfce23c` | OTA 兼容探测用备选特征(z7_b0.java:769-770、a8_g.java 头部) |
|
||
| `0000221f-...`(Web 声明未用) | APK strings 中**未出现** |
|
||
| `258EAFA5-E914-47DA-95CA-C5AB0DC85B11` | 仅字符串表存在,无代码 xref |
|
||
|
||
设备名过滤:代码扫描不过滤(`startScan(0, ...)` 无 ScanFilter,BleManager.java:761),UI 层按 `name.startsWith("NRF-")` 过滤(a9_b0.java:102;SUOTA 扫描同,a9_v2.java:190)。与 Web `namePrefix:'NRF-'` 一致。
|
||
|
||
连接参数:连接后 `requestMtu(512)`(BleManager$startDeviceInfoRead$1.java:80),`onMtuChanged` 更新会话 negotiatedMtu(默认 23);`BleSession.getMaxPayloadBytes() = max(20, mtu-3)`(BleSession.java:162-170)。connectGatt 用 TRANSPORT_LE(2)、autoConnect=false(BleManager.java:619)。
|
||
|
||
## b. 命令集对照
|
||
|
||
APK 命令发送模型与 Web 不同:**命令列表执行器** `e8.j`——把 `["e10201","TIME","e2"]` 这类列表逐条经 RXTX `sendCommand` 发出,`"TIME"` 占位符运行时替换为 `e8.o.m()` 构造的 `dd…` 时间命令(e8_j.java:111-114)。`sendCommand` 按 MTU-3 分块、互斥锁串行、10s 超时(BleSession.java:187-258)。
|
||
|
||
| 命令 | APK 证据 | 与 Web 一致? |
|
||
|---|---|---|
|
||
| `dd{unix:4B}{year:2B}{mo}{d}{week}` 设置时间 | `e8.o.m()`(e8_o.java:57-73):unix 秒+时区偏移、4B/2B/1B/1B/1B 大端 hex | ✅ 完全一致(Web ble.js:251-257) |
|
||
| `e2` 提交 | 所有命令列表收尾(c8_g.java:63 等) | ✅ |
|
||
| 模式切换 e100/e101/e102 系列 | 动态构造:行 0/1/2 → 前缀 `e102`/`e101`/`e100`(c8_g.java:39-54、s() in d8_f.java:135-150);`e1020{n+1}` 时钟、`e1010{n+3}` 日历、`e10000` 图片(c8_g.java:137-146) | ✅ 覆盖 Web 的 e10000-03/e10103-05/e10201-03 |
|
||
| 停车牌 `ef`+号码 | `c8.g.d()`:**逐字符双写**("12"→`ef1122`,c8_g.java:72-91);`d8.f.d()`:**逐字符转 hex 拼接**(Integer.toString(char,16),d8_f.java:168-189)——两固件家族格式不同!均校验 11 位数字 | ⚠️ Web 只有双写式;d8/f 的 hex 拼接式 Web 没有 |
|
||
| 倒计时文字位图 `04`/`0{n}`/`990{j}` | `f8.a.c()`(f8_a.java:70-162):gridSize 16 → ≤4 字 `0{n}` 单包、5-8 字 `04`+前段 / `0{n}`+128B 后段;gridSize 24 → 每字 `990{j}`+72B;位图 MSB-first | ✅ 与 Web timedjs2 一致 |
|
||
| 倒计时日期 `ff00`/`ffff`+`%04d%02d%02d` | `f8.a.b()`(f8_a.java:58-68):gridSize≠24→`ff00`,24→`ffff`,年为 4 位十进制 ASCII | ✅ 一致 |
|
||
| `FA` 关倒计时 | 字符串表存在(`fa`),命令列表中未直接出现(UI 文案有"关闭") | ≈ |
|
||
| 全刷/其他设置(e3/e4/e5/e6/14/15/2300/2301/FB/e901/EA0x) | **DEX 字符串与 xref 中均无**(xref 验证:'2300'/'2301'/'fb01'/'e901'/'EA00'/'EFEF' → 0 引用) | ❌ **APK 不实现**(三色/黑白切换、12/24h、颜色、反色、字体、旋转、LUT 校准 E6、休眠时段 FB、轮播 e901/EA000、模板槽位 EA0x 均为 Web 独有) |
|
||
| `EFEF` 请求设备 ID | **无**(xref=0)。APK 不读设备 14B ID,改用 DIS 2A24/2A26 + MAC 识别设备 | ❌ Web 独有 |
|
||
| WiFi 配置 `WIFI_SSID:...` | **无**(strings 无 WIFI_SSID,UI 无 WiFi 入口) | ❌ Web 独有 |
|
||
| 激活 `EF{code}` 发设备 | **无**(APK 激活是 App 本地的,见 §d) | ❌ 机制完全不同 |
|
||
| `$IMG(0,0)`(hex `24494d4728302c3029`) | c8/f 图片上传末尾经 RXTX 发送(c8_f.java:186) | ❌ **APK 独有**,Web 无此命令 |
|
||
| 上传开始 `8000`/`8001`、`0000`、`020000`,结束 `0101` | 见 §c | ❌ 帧格式与 Web 不同(见差异节) |
|
||
|
||
## c. 图像传输与像素处理
|
||
|
||
### 发送流程(EPD 通道,`BleSession.sendEpdBytes`,带写完成回调、互斥锁、5s 超时)
|
||
|
||
`c8.f`("平平/扫地"固件,c8_f.java:137-200):
|
||
1. EPD `{80,00}`(`8000`)
|
||
2. EPD `{00,00}`(`0000`)
|
||
3. EPD `{02,00,00}`(`020000`)
|
||
4. 数据帧循环:`[0x03, plane, offHi, offLo, ≤240B]`,plane:BW 平面 `0xFF`、红平面 `0x00`,offset 大端(c8_f.java:246-270)
|
||
5. EPD `{01,01}`(`0101`)
|
||
6. RXTX `$IMG(0,0)` → RXTX `e2`
|
||
|
||
`d8.e`("轻舞/时光"固件,d8_e.java:92-170):
|
||
1. EPD `8001` → `0000` → `020000`
|
||
2. 同样的 0x03 数据帧(240B/块,ff=BW、00=红)
|
||
3. EPD `0101` → delay(300ms) → RXTX `e2`(普通)或 `aa`(**isCalendar 预设为真时**,d8_e.java:150-167,g8.c.f=isCalendar)
|
||
|
||
与 Web 对照:分块 240B、平面码 ff/00、offset 大端 ✅ 一致;**帧头恒为 0x03,APK 没有 Web 的 0x04 小图帧**;首尾帧不同(Web:`00{cmd}` 开头 / `01` 或 `AA` 结尾;APK:`80xx`+`0000`+`020000` 开头 / `0101` 结尾 + RXTX `e2`/`aa`)。三色 = 两个平面依次发送 ✅。
|
||
|
||
### 像素打包(与 Web 完全同源)
|
||
|
||
- `a.b.h()`(a_b.java:866-914):行主序 MSB-first 8px/B;BW 平面 `R>0&&G>0&&B>0→1`,红平面 `R>0&&G==0&&B==0→1` —— = Web canvas2bytes。
|
||
- `a.b.i()`(a_b.java:916-963):列主序 x 从 W-1→0 —— = Web canvas2bytes_bw(296/250/212 宽屏用)。
|
||
- `d8.f.o()`(d8_f.java:39-96):d8/f 家族自带打包:宽 212/250/296 走列主序否则行主序;BW 平面判定为 **`R>0→1`**(d8_f.java:119-133,不含 G/B 检查;对抖动后的纯色调色板等价,但红像素在 BW 平面也会置 1,与 a.b.h 不同)。
|
||
- 红色判定 `g8.f.e()`:`r > g*1.5 && r > b*1.5 && r > threshold`(g8_f.java:45-53,float 1.5)—— = Web timedjs.js:255。
|
||
- 灰度 `g8.f.d()`:0.299/0.587/0.114(g8_f.java:40-43)。
|
||
- Bayer 4x4 矩阵 [[15,135,45,165],[195,75,225,105],[60,180,30,150],[240,120,210,90]](g8_f.java:5-8)—— 与 Web 逐值一致。
|
||
|
||
### 抖动算法
|
||
|
||
`g8.a` 枚举:NONE / FLOYD_STEINBERG / **ATKINSON(默认,g8.a.d)** / BAYER / STUCKI / JARVIS(g8_a.java:10-16)——与 Web 同 6 种;默认 Atkinson 与 Web 默认 bwr_atkinson 对应。配置 `g8.d`(ImageConfig):亮度/对比度/饱和度/diffusion/threshold(默认128)/旋转角度/scaleMode/isThreeColor(g8_d.java:229-250)。
|
||
预设 `g8.c` = FirmwareImageUploadPreset(screenSize, uploadWidthPx, uploadHeightPx, useRotatedScan, singleChannel, isCalendar)——**上传参数由固件型号预设驱动**,默认 400x300(g8_d.java:102-130)。
|
||
|
||
倒计时文字位图渲染 `f8.a.a()`:Android Canvas 绘制(DEFAULT_BOLD、fakeBold、抗锯齿、居中),按阈值二值化(f8_a.java:10-56)。
|
||
|
||
## d. 激活流程 ★(Web 端没有的关键信息)
|
||
|
||
**APK 的激活完全是 App 本地校验,算法在 libapp.so(Rust)里。**
|
||
|
||
JNI 接口 `qbsg.top.activation.ActivationManager`(decomp/qbsg_top_activation_ActivationManager.java):
|
||
|
||
```java
|
||
static { System.loadLibrary("app"); }
|
||
public final native boolean nativeActivate(String code);
|
||
public final native boolean nativeChecksumOk(String code);
|
||
public final native String nativeDecryptSegment(String hex);
|
||
public final native String nativeGetDeviceId();
|
||
public final native void nativeInit(Object ctx);
|
||
```
|
||
|
||
libapp.so 导出符号(readelf .dynstr):
|
||
- `Java_qbsg_top_activation_ActivationManager_native{Init,GetDeviceId,ChecksumOk,Activate,DecryptSegment}`
|
||
- `Java_io_github_bszapp_dzjq42_utils_SignatureUtils_getNativeSignature`(签名防伪校验)
|
||
- 字符串证据:引用 `android/provider/Settings$Secure`、`android_id`、`getContentResolver()`(**设备 ID 源自 ANDROID_ID**);rust-secp256k1(`pubkey != NULL`、`SECP256K1_FLAGS_BIT_COMPRESSION`)、sha2-0.10.9、aes-0.8.4 crate;运行时错误串 `[not activated]`、`[decrypt error: too short]`。
|
||
|
||
调用链(MainActivity.onCreate,qbsg_top_MainActivity.java:19-35):
|
||
1. `nativeInit(context)` → `nativeGetDeviceId()` 写入 StateFlow(UI 展示设备 ID)。
|
||
2. 读 SharedPreferences `activation_code`;非空且 `nativeChecksumOk(code)` 为真 → `nativeActivate(code)` → 置"已激活"全局标志(`w7.a.h.b.c`)。
|
||
3. `y7.a.a(code, fromQr)`(y7_a.java:21-61):手动/扫码激活入口,成功则持久化 `activation_code`;失败提示"激活码无效或不属于本设备"。
|
||
4. 扫码激活 `y7.a.b(bytes)`(y7_a.java:63-110):QR 原始字节 ≥71、前 4 字节 ASCII `"qbsg"`,取**最后 67 字节**作为激活码字符串 → `nativeActivate`。→ **激活码为 67 字符**。
|
||
5. `s0.u0` case 13(s0_u0.java:82-99):`nativeDecryptSegment` 仅在"已激活且处于特定模式"时可用——用于解密 §e 的两段密文 UUID。
|
||
|
||
密码学形态推断(基于 crate 组合,未做指令级逆):secp256k1(很可能 ECDSA/Schnorr 公钥验证,私钥在签发方)+ SHA-256 + AES(DecryptSegment 用)。"不属于本设备"说明激活码与 `nativeGetDeviceId()`(ANDROID_ID 派生)绑定。**这取代了 Web 的服务端 key_generator.php 模式**:Web 发 `EF`+激活码给设备固件校验;APK 在 App 侧本地校验,不给设备发任何激活命令。
|
||
|
||
另有**服务端激活 API**(面向"换固件"付费场景,与本地码并行):
|
||
- `POST https://qbsg.top/api/activation/activate`,JSON `{token, provider_id, firmware_id, mac: "mac1,mac2,…"}`(a8_f.java:529-545,`k8.h.c` = okhttp POST application/json,k8_f.java)。
|
||
- `POST https://qbsg.top/api/activation/check`,body 为设备 ID 字符串(a9_c2.java:62/80/98)。
|
||
- `POST https://qbsg.top/api/activation/firmware-list`(b9_g0.java:127)。
|
||
- 文案佐证:"Switching firmware requires paid activation."、"以下 %s 台设备未找到激活码"、"BatchActivationFirmwareOption(id=…"、"Batch firmware upgrades and sequential commands"(批量固件升级+顺序命令)。
|
||
|
||
## e. 固件 OTA / SUOTA ★(Web 端完全没有)
|
||
|
||
APK 有完整 OTA 体系,两条路径同一协议核心:
|
||
|
||
### 路径 1:独立 SUOTA 管理器 `z7.b0`(扫描 NRF- 设备→专连升级,支持批量/并发 Semaphore)
|
||
|
||
流程(z7_b0.java:655-1720,超时:连接 30s、写 8s、状态等待 60s):
|
||
1. 连接 + 服务发现,必须存在服务 `0000fef5`(否则"设备不支持SUOTA服务")。
|
||
2. 兼容探测 SERV_STATUS `64b4e8b5`;另探测 `42c3dfdd`/`b7de1eea`/`61c8849c` 备选特征。
|
||
3. `requestMtu(23)`(默认;若已协商更高则用 min(20, mtu-3) 作写块),`requestHighPriority()`。
|
||
4. SERV_STATUS 开 notify(CCCD 写 `{01,00}`,z7.k0.a)。
|
||
5. 写 MEM_DEV `8082caa8` ← `g(318767104)` = 4B 小端 `{00,00,00,13}`(mem type 0x13=SPI Flash;`z7.b0.c()` 恒返回该值,z7_b0.java:426-430)。
|
||
6. 写 GPIO_MAP `724249f0` ← `g(84280064)` = `{00,00,06,05}`(对应 UploadConfig 默认 misoGpio=5, mosiGpio=6, csGpio=3, sckGpio=0, i2cAddress=0x50,z7_o0.java toString)。
|
||
7. 镜像封装 `z7.h0`(z7_h0.java:7-34):**固件字节末尾追加 1 字节全镜像 XOR 校验**;块长 b = max(240, chunk)(默认 240B),写块长 c = MTU-3(默认 20B)。
|
||
8. 每块:写 PATCH_LEN `9d84b9a3` ← `f(blockLen)`(2B 小端)→ 分 20B 无响应写 PATCH_DATA `457871e8` → 等 SERV_STATUS notify == 2,否则报"块N失败: …"。
|
||
9. 全部块后写 MEM_DEV ← `g(-33554432)` = `{00,00,00,FE}`(END_SIGNAL)→ 等最终 SERV_STATUS==2(设备侧 XOR/CRC 校验,失败报"CRC校验失败")。
|
||
10. 写 MEM_DEV ← `g(-50331648)` = `{00,00,00,FD}`(重启设备)。
|
||
|
||
SERV_STATUS 错误码表(z7_k0.java:8-28,中文文案):2=操作成功 3=强制退出SPOTA 4=Patch Data CRC失败 5=长度与PATCH_LEN不符 6=外部存储写失败 7=内部存储不足 8=无效存储器类型 9=应用层错误 17=无效镜像Bank 18=无效镜像头部 19=无效镜像大小 20=无效产品头部 21=相同镜像错误 22=外部存储读失败 65535=通信错误 65534=设备不支持SUOTA。
|
||
|
||
### 路径 2:会话内 OTA `a8.g.f(byte[] firmware, String, GattOtaChannel)`(a8_g.java:94-…)
|
||
|
||
同一协议核心(fef5 服务探测、MTU 默认 23→20B 块、z7.h0 240B 块封装、PATCH_LEN/PATCH_DATA、SERV_STATUS 轮询),经 `BleManager$AndroidGattOtaChannel` 在已连接会话上执行。
|
||
|
||
### 固件来源与目录
|
||
|
||
- `POST https://qbsg.top/api/firmware/catalog` → 固件目录(b9_g0.java:118);`POST /api/activation/firmware-list` → 已购固件列表(b9_g0.java:127)。
|
||
- 目录项字段:firmwareId / firmwareFileName / firmwareCrc / firmwareTotalBytes / downloadUrl(strings-clean.txt:2247-2253、21021-21023;b1_m 同构解析逻辑)。
|
||
- 下载 `k8.h.b(url,…)` 流式写本地缓存(a8_f.java:396-435),`.img` 后缀(strings:3035)。**OTA 用固件从 qbsg.top 服务器下载,与 Web 端 4638e813e4.qicp.vip:8889/GuJian/file_down.php 不同源**。
|
||
- OTA 前还有 FirmwareImageUploadPreset 驱动的"升级画面"图片上传(g8.c)。
|
||
|
||
### 激活门控的加密 UUID
|
||
|
||
`z7.b0` 构造时(z7_b0.java:50-51)调 `s0.u0(13)` → `ActivationManager.nativeDecryptSegment` 解密两段 128-hex 密文:
|
||
- `a232499efa74dd64…d01d` → `this.e`
|
||
- `3fd29fea11187d48…7791` → `this.f`
|
||
|
||
字段初始为全零 UUID(`00000000-0000-0000-8000-000000000000`),**未激活时解密返回空串**(s0_u0.java:89-97 的门控逻辑)。两段明文应为 UUID(类静态块编译了 UUID 正则)。这是反盗版设计:部分 OTA 能力(大概率是某自定义服务/特征 UUID 或厂商私有权鉴)只对已激活 App 可用。
|
||
|
||
### App 自更新(与设备固件无关)
|
||
|
||
`POST https://qbsg.top/api/check-update`,body `{machine: <设备ID>, version: 4, platform: "android"}` → `{isLatest, latestVersionCode, latestVersionName, downloadUrl, updates:[{versionName,changelog}]}`(b1_m.java:215-245);下载 APK 后借 FileProvider + REQUEST_INSTALL_PACKAGES 安装("下载完成,点击安装")。
|
||
|
||
## f. 网络依赖清单(本地化去云清单)
|
||
|
||
DEX 中全部 URL(strings-clean.txt 提取,**无 mspppclock.tech、无 4638e813e4.qicp.vip**——Web 端两个服务器在 APK 均不存在):
|
||
|
||
| 端点 | 用途 | 证据 |
|
||
|---|---|---|
|
||
| `https://qbsg.top/api/activation/activate` | 固件激活(付费换固件) | a8_f.java:541 |
|
||
| `https://qbsg.top/api/activation/check` | 激活状态查询 | a9_c2.java |
|
||
| `https://qbsg.top/api/activation/firmware-list` | 已购固件列表 | b9_g0.java:127 |
|
||
| `https://qbsg.top/api/firmware/catalog` | 固件目录 | b9_g0.java:118 |
|
||
| `https://qbsg.top/api/check-update` | App 自更新 | b1_m.java:217 |
|
||
| `https://qbsg.top/api/fonts` | 云端字体 | strings |
|
||
| `https://qbsg.top/api/pictures`(+`/thumbnail?name=`)、`https://qbsg.top/Pictures/` | 云图库 | strings |
|
||
| `https://qbsg.top/api/templates/community/{list,download,downloaded,like,unlike}`、`/api/templates/{submit,my-list,preview,delete,tags,tmp-upload?token=}` | 模板社区 | strings |
|
||
| `https://qbsg.top/api/user/{info,logout}`、`https://qbsg.top/#/loginonly`、`/#/me?logintoken=` | 账号体系(AccountManager、网页登录 token) | strings、AccountScreen$submitToken |
|
||
| 营销外链 | QQ 群 280372025、拼多多店铺 mall_id=214155601、闲鱼 userid=355198804 | strings |
|
||
|
||
HTTP 栈:okhttp3,`k8.h`(POST JSON `k8.h.c`、下载 `k8.h.b`)。Web 端协议报告的激活站 `key_generator.php` 与固件站 `file_down.php` 在 APK 中均无迹。
|
||
|
||
## g. 与 Web 端差异汇总
|
||
|
||
| # | 差异 | 说明 |
|
||
|---|---|---|
|
||
| 1 | **激活机制根本不同** | Web:设备 ID(EFEF 14B)→qicp.vip key_generator.php→`EF{码}` 发设备;APK:App 本地 libapp.so(Rust, secp256k1+SHA-256+AES) 校验 67 字符码(QR: "qbsg"+67B),绑定 ANDROID_ID,不发任何激活命令给设备 |
|
||
| 2 | **OTA 仅 APK 有** | 完整 Dialog SPOTA/SUOTA(fef5 服务、240B 块+20B 写块、XOR 尾字节、FE/FD 收尾),固件来自 qbsg.top;Web 无任何 OTA |
|
||
| 3 | **图像首尾帧不同** | Web:`00{cmd}` 起、`01`/`AA`(EPD) 止;APK:`8000/8001`+`0000`+`020000` 起、`0101`(EPD)+`e2`/`aa`(RXTX) 止,c8/g 家族还发 ASCII `$IMG(0,0)`;APK 恒用 0x03 帧头(无 Web 的 0x04 小图帧) |
|
||
| 4 | **Web 独有的设备功能** | WiFi 配置、EFEF 设备 ID、2300/2301、14/15、e3/e4/e5、E6 LUT、FB 休眠、e901/EA000 轮播、EA0x 槽位——APK 全部未实现(APK 定位是"图片/模板/倒计时上传 + 固件升级"工具) |
|
||
| 5 | **APK 独有的协议细节** | 连接后 requestMtu(512);读 DIS 2A24/2A26 型号与固件版本;双固件家族(平平/扫地 vs 轻舞/时光)打包与停车牌格式分叉;`aa` 按 isCalendar 预设走 RXTX;SUOTA PATCH_LEN/DATA 使用非标准 UUID 映射(9d84b9a3 / 457871e8) |
|
||
| 6 | **服务器完全不同** | Web:4638e813e4.qicp.vip:8889(激活+固件);APK:qbsg.top 全家桶 API(激活/固件/字体/图库/模板社区/账号/自更新) |
|
||
| 7 | 一致项 | EPD/RXTX UUID、NRF- 前缀、240B 分块、ff/00 平面码、dd 时间帧、e2 收尾、e100/e101/e102 模式族、倒计时 04/0n/990/ff00/ffff 帧、6 种抖动(Bayer 矩阵逐值一致)、红色判定 r>1.5g&&r>1.5b、灰度系数 |
|
||
|
||
## 附:关键文件位置
|
||
|
||
- 反编译源码:analysis/apk/decomp/(BleManagerKt=UUID 常量、BleSession=发送层、e8_o/e8_j=命令执行器、c8_g/d8_f=模式管理器、c8_f/d8_e=图像上传、g8_f/a_b=像素与抖动、f8_a=倒计时、z7_b0/z7_h0/z7_k0=SUOTA、a8_g/a8_f=OTA 与会话、b9_g0=固件目录、b1_m=自更新、y7_a=激活入口、s0_u0=解密门控)
|
||
- native 库:analysis/apk/native/libapp.so(激活算法,Rust)、libeditor.so(模板编辑器)
|
||
- 字符串表:analysis/apk/strings-clean.txt(28367 条);关键字符串 xref:analysis/apk/xrefs-key-strings.txt
|