diff --git a/.gitignore b/.gitignore index 5e1aa1c..c6dc22b 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,7 @@ font/src/**/*.otf font/src/**/*.ttf font/src/**/*.ttc patch_v2/sdk/test_p1p2p3_repro + +# ncm_app vendor 源码与构建产物(体积大;版本/获取方式见 VENDOR_NOTES.md,构建脚本可复现) +patch_v2/ncm_app/vendor/ +patch_v2/ncm_app/out/ diff --git a/patch_v2/ncm_app/REPORT.md b/patch_v2/ncm_app/REPORT.md new file mode 100644 index 0000000..3987c36 --- /dev/null +++ b/patch_v2/ncm_app/REPORT.md @@ -0,0 +1,124 @@ +# os.meow.ncm 技术栈预研 + 构建验证报告 + +> 日期:2026-08-31。工作区:`/home/Aska/Project/R3ProII`,仅改动 `patch_v2/ncm_app/`。 +> 目标:为 MeowOS 网易云 App(v1 = 扫码登录 + 我的歌单 + 播放)在 HiBy R3 Pro II +> (MIPS32r2 单核、~14MB 可用内存、无 GPU、ALSA 直开 hw:0,0)上的纯 C 静态栈做选型与真机交叉编译验证。 +> 工具链:`.tools/zig-mipsel-cc`(zig 包装 mipsel-linux-musleabi soft-float 静态交叉)。全程未 commit/push/刷机/动设备。 + +## 1. 选型结论表 + +| 能力 | 选型 | 版本 | 许可 | mipsel 体积(text) | 说明 | +|---|---|---|---|---|---| +| TLS + 加密 | **mbedtls(裁剪)** | 3.6.2 | Apache-2.0 | 链接 Demo ≈ **388 KB** | TLS1.2 client + AES-128-CBC/ECB/GCM + MD5 + RSA(1024 raw) + Base64 + X509,单库全包 | +| TLS(备选) | BearSSL | master@7bea48e | MIT | TLS client 子集 ≈ **96 KB** | 更省 flash,但无 base64、AES-ECB 需自行逐块、RSA raw 用 `br_rsa_i31_public` | +| JSON | **jsmn** | 1.1.0 | MIT | **2.2 KB** | 分词器,无 malloc/无 DOM;cJSON 对比 20 KB | +| 二维码 | **qrcodegen** | nayuki@993ccbb | MIT | **10.8 KB** | 纯 C 单文件,无图形依赖 | +| MP3 解码 | **minimp3** | master@ea99364 | CC0 | **37.4 KB** | `minimp3_ex` 流式/可 seek;dr_mp3 对比 40.7 KB | + +**HTTPS GET/POST 可达性**:mbedtls 已实测连通(见 §2)。**crypto 是否 mbedtls 自带就够**:是—— +AES-128-ECB/CBC、MD5、Base64、RSA(raw) 全有;RSA raw 用 `mbedtls_rsa_public()`(无 padding 纯模幂)已交叉校验与 `pow(m,e,n)` 完全一致(见 §3),这是文档标注最易踩坑处,已验证可行。 + +## 2. HTTPS 实测(宿主 = 本机 aarch64;另跑通 MIPS 版) + +**demo**:`demo/https_unikey.c`(mbedtls TLS1.2 + eapi 加密 + jsmn)。 +目标:`POST https://interface.music.163.com/eapi/login/qrcode/unikey`,body `params=`。 + +``` +deviceId=68DFA27096B7A5DC2CB393C77D8C194FDC0459B743081D091EBD +---- response body ---- +{"code":200,"unikey":"33a1c6a6-28ad-4e1e-bc8e-ebe769953913"} +UNIKEY = 33a1c6a6-28ad-4e1e-bc8e-ebe769953913 +``` + +- 宿主:`code:200` + unikey 顶层字段,与 NCM_API_NOTES §7 一致 ✅ +- **MIPS 版交叉编译后,在 `qemu-mipsel` 下全链路跑通**(DNS→TCP→TLS→POST→JSON 解析),同样拿到真实 unikey: + `{"code":200,"unikey":"6731b1ac-71a2-41eb-a704-a2de39d6d0b7"}`。即 mipsel 静态产物的网络+crypto+TLS 路径正确。 +- eapi 加密逻辑与原版 `crypto::eapi` 一致:`digest=md5("nobody"+url+"use"+json+"md5forencrypt")`, + `plain=url+"-36cd479b6b5-"+json+"-36cd479b6b5-"+digest`,`params=HEX_UPPER(AES128ECB(plain,"e82ckenh8dichen8"))`; + `header`(osver/deviceId/os/appver/versioncode/mobilename/buildver/resolution/__csrf/channel/requestId)与 `e_r:false` 均入加密体(§8 要求,去掉会 4xx)。 + +## 3. RSA raw(weapi 无 padding 模幂)校验 + +`mbedtls_rsa_public(ctx, 128B输入右对齐16B明文, 128B输出)`,n=1024-bit、e=65537。 +- mipsel(qemu) 与宿主输出一致,且与 Python `pow(m,e,n)` 逐一字节相同(256 hex 完全匹配): + `CT = 55B33E7E3229B8D977137D120B3C7A5B5FB8E933325CAA5BB8CEF6C132D57E5E...B2B83` +- 结论:**mbedtls_rsa_public() 可用作 weapi 的 textbook RSA**,避开 PKCS#1 padding 陷阱。demo:`demo/rsa_raw_test.c`。 + +## 4. 总体积账(mipsel,-Os,flash≈text) + +**选定的库合计(≈438 KB)**: + +| 项 | text | 备注 | +|---|---|---| +| mbedtls TLS+crypto+x509(链接 demo) | 388 KB | 含 ~6 KB musl libc 基线;库文本合计 410 KB,gc-sections 未再降 | +| jsmn | 2.2 KB | | +| qrcodegen | 10.8 KB | | +| minimp3 | 37.4 KB | | +| **库合计** | **≈ 438 KB** | 另需 App/UI/封面/音频输出层(未计入,自研) | + +可选裁剪方向(能明显压 mbedtls):限制密码套件到 `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`(+1 个 CBC+SHA 兜底)、 +ECP 只留 P-256、去掉 ECDSA(服务器给 RSA 证书)、`MBEDTLS_SSL_IN/OUT_CONTENT_LEN=4096`。估可降到 ~250 KB。 +若固件预算仍紧,**切 BearSSL(≈96KB) + 自带 base64/AES-ECB 例程**,能再省 ~300 KB。 + +**对比参考项体积**:cJSON 20 KB、dr_mp3 40.7 KB、BearSSL 全库 310 KB(裁剪子集 96 KB)。空静态 mipsel 基线 text=5888。 + +## 5. 内存 / 性能估算(14MB 可用) + +- **TLS 握手 buffer**:mbedtls 默认 `SSL_IN/OUT_CONTENT_LEN`=16KB 各一,共 ~32KB;demo 二进制 `bss=26888`(≈26KB)+堆内 SSL context/ECDH 临时密钥。对 14MB 无压力;建议调成 4096 更省。 +- **JSON 解析**:jsmn 无 malloc,token 数组按需分配(响应小,几百字节)。**不要全量缓冲歌单详情**,只取需要的键(文档 §4.4)。 +- **解码**:minimp3 流式,输入 ~每帧几 KB + 输出 `MINIMP3_MAX_SAMPLES_PER_FRAME*2*ch`,整体 <20KB。 +- **封面**:**必须限幅**——NCM 建议 5MB,MeowOS 应限 ~256KB 并降采样/调色板(无 GPU 只能 CPU 软缩放)。 +- **二维码位图**:41×41 符号渲染成 UI 尺寸(如 400×400,灰度 1B/px=160KB,1-bit 可 20KB)。 +- 预算结论:TLS+解码+二维码+封面(256KB cap)+UI 帧缓冲,**总工作集应在数百 KB 量级,14MB 富余**;唯一要盯的是封面/歌单响应别全量驻留。 + +- **性能**: + - **MP3 解码实时性**:宿主(aarch64) minimp3 = **568×RT**、dr_mp3 = 627×RT; + `qemu-mipsel`(TCG 模拟,**远慢于真机**,通常 10–50×) 下 minimp3 = **4.8×RT**、dr_mp3 = 4.7×RT。 + → 真机 MIPS32 单核跑 44.1k/64kbps 解码**余量很大**(估计数十倍 RT),解码不是瓶颈。⚠️ 该数字为宿主/模拟估算,标注不准。 + - **TLS 握手**:ECDHE-RSA-AES128-GCM,单核软 RSA 1024 校验 + ECDH P-256,估几十~几百 ms,可接受(仅首次/重连)。 + - **weapi RSA raw 模幂**:1024-bit 软模幂每次约几 ms~几十 ms(只对 weapi 请求,频率低)。 + +## 6. 风险与 fallback + +| 风险 | 评估 | 对策/fallback | +|---|---|---| +| weapi RSA raw 最易踩坑 | ✅ 已交叉校验正确 | 保持 `mbedtls_rsa_public()`+左补零到 256 hex;用 `rsa_raw_test` 做回归 | +| deviceId/UA 不稳定触发风控 | 高 | deviceId(52 hex) **首生成后持久化**;UA 按 eapi(iPhone)/weapi(Chrome) 分别固定;轮询≥1s、失败退避 | +| session(MUSIC_U)丢失即需重扫码 | 高 | cookie 持久化;启动用 `/weapi/w/nuser/account/get` 校验 | +| eapi 的 `header`/`e_r` 缺失 → 4xx | 中 | demo 已带,必须保留(§8) | +| 无版权/VIP 曲目 url=null | 中 | `song_url_v1` 返回 null → 提示 + 降级到 `song/enhance/player/url`(br=320000) 兜底 | +| 音质策略(Hi-res 降级) | v1 只保 mp3 | 账号非免费档 level 被钳到免费档;v1 默认 `standard`(128k)/`higher`(192k);**不引入 flac 解码**(14MB 内存+单核不划算);后续再评估 dr_flac | +| 触发高频风控 | 中 | 请求限频、重试退避、必要时随机国内 IP(`X-Real-IP`/`X-Forwarded-For`) | +| mbedtls flash ~388KB 偏大 | 中 | 见 §4 裁剪;若预算吃紧切 BearSSL(≈96KB) | + +## 7. 出入清单(只改 `patch_v2/ncm_app/`) + +``` +patch_v2/ncm_app/ +├── vendor/ # 各库源码(上游 clone/tar + 许可证) +│ ├── mbedtls/ (3.6.2, 含 framework 子模块, 裁剪 config 在 config/mbedtls_config_ncm.h) +│ ├── bearssl/ (官方 git, 无语义版本) +│ ├── jsmn/ cjson/ qrcodegen/ minimp3/ dr_mp3/ +├── libs_common.sh # 公共 zig/ar/size 环境 +├── build_mbedtls.sh # 裁剪 config + 静态编 mipsel → out/mbedtls/mipsel/*.a +├── build_bearssl.sh # make lib → out/bearssl/mipsel/bm/libbearssl.a +├── build_cjson.sh build_jsmn.sh build_qrcodegen.sh build_minimp3.sh build_dr_mp3.sh +├── build_demo.sh # 宿主 + mipsel 的 HTTPS demo 复现 +├── bench/ # minimp3/dr_mp3 基准(sample-15s.mp3 来自 samplelib) +├── demo/https_unikey.c # eapi + TLS1.2 + jsmn(打 unikey) +├── demo/rsa_raw_test.c # weapi RSA raw 校验 +├── out/https_unikey_mipsel(. _s) # mipsel 静态 demo(qemu-mipsel 跑通) +├── out/mbedtls/{mipsel,host}/ # 库产物 out/bearssl/mipsel/bm/ +├── VENDOR_NOTES.md # 版本/许可/裁剪/体积 +└── REPORT.md # 本报告 +``` + +**未做/待主会话**:真机 `hw:0,0` 播放链路、扫码后真实登录(803+set-cookie)、weapi 真实登录态请求、 +CA bundle 内置方案、音质档位映射实测。这些属设备/账号侧联调,由主会话在设备上执行(本预研不动设备)。 + +## 8. 结论 + +- **可行**:纯 C + mbedtls(裁剪) + jsmn + qrcodegen + minimp3,静态 mipsel 交叉编译全通,HTTPS POST 拿到真实 unikey,MIPS(qemu) 版全链路跑通。 +- **推荐 TLS/加密一体用 mbedtls**(单库一次解决 TLS + AES/MD5/RSA raw/Base64 + X509,规避 RNSS-raw 手写风险);BearSSL 为省 flash 备选。 +- **v1 体积增量 ≈ 438 KB(库)/ 只做 mp3**,内存估计数百 KB 工作集,14MB 可行;主要优化点是 mbedtls 裁剪与封面限幅。 +- **最关键风险(RSA raw)已验证**;其余风险集中在风控(deviceId/UA 稳定持久化)与无版权降级策略。 diff --git a/patch_v2/ncm_app/VENDOR_NOTES.md b/patch_v2/ncm_app/VENDOR_NOTES.md new file mode 100644 index 0000000..4a70b95 --- /dev/null +++ b/patch_v2/ncm_app/VENDOR_NOTES.md @@ -0,0 +1,70 @@ +# VENDOR_NOTES — os.meow.ncm 技术栈选型备查 + +> 预研:`patch_v2/ncm_app`(仅此目录)。库全部静态编入 mipsel(zig-mipsel-cc 交叉), +> 产物在 `out/`,各库构建脚本见 `build_*.sh`。体积口径:`size` Berkerley `text/data/bss`, +> mipsel 静态、`-Os`;flash≈text,RAM≈data+bss。基准:空 `printf` 静态 mipsel = text 5888。 +> 实测于 2026-08-31。 + +## 选型结论(先看) + +| 库 | 版本 | 许可 | mipsel text | 选/不选 | +|---|---|---|---|---| +| mbedtls(裁剪) | 3.6.2 | Apache-2.0 | 链接 Demo ≈ 388 KB(库文本合计 ~410 KB) | ✅ 选(TLS+AES/MD5/RSA/base64/X509 一体) | +| BearSSL | master@7bea48e | MIT | TLS client 参考子集 ≈ 96 KB | 备选(更省 flash,但集成面更杂) | +| jsmn | 1.1.0 | MIT | 2.2 KB | ✅ 选(解析器) | +| cJSON | 1.7.18 | MIT | 20.0 KB | 参考对比(未选,太大) | +| qrcodegen | nayuki@993ccbb | MIT | 10.8 KB | ✅ 选(二维码) | +| minimp3 | master@ea99364 | CC0 | 37.4 KB | ✅ 选(MP3 v1 解码) | +| dr_mp3 | 0.7.4 | MIT/public-domain | 40.7 KB | 参考对比(未选,质量更好但更大更吃内存) | + +## 各库明细 + +### mbedtls 3.6.2(Mbed TLS)— Apache-2.0 +- 上游:https://github.com/Mbed-TLS/mbedtls (tag mbedtls-3.6.2,commit 107ea89) +- 裁剪配置:`config/mbedtls_config_ncm.h`(由 `build_mbedtls.sh` 从默认 141 个 define 减到 102 个)。 + 保留:TLS1.2 client、AES-CBC/ECB/GCM、MD5、RSA(1024)+BIGNUM、PK/ASN1、X509 证书解析、Base64、PEM_PARSE、ECP/ECDH(P-256 等)。 + 裁掉:TLS1.3、全部 DTLS、PSA_CRYPTO/IS/LMS、CTR_DRBG/ENTROPY(用自定义 rng)、 + CAMELLIA/ARIA/DES/CHACHA/CHACHAPOLY/CCM、HKDF/NIST_KW/ECJPAKE、PKCS5/7/12、PEM_WRITE、MD4、X509_CRL/CSR、 + SSL_SESSION_TICKETS/ALPN/RENEGOTIATION/MAX_FRAGMENT/CTX_SERIALIZATION/KEEP_PEER_CERT 等。 + 裁剪说明:原默认配置裁剪前 `text` 更大;按 `config.py get`/`unset` 逐项减;`build_mbedtls.sh` 里列出清单。 +- 产物:`out/mbedtls/mipsel/`(libmbedcrypto.a / libmbedx509.a / libmbedtls.a), + text 合计 420236(≈410 KB);实际链接 Demo(含 libc)text=397102(≈388 KB)。 +- 为什么它覆盖了我们全部需求:`mbedtls_aes_crypt_ecb`(AES-128-ECB)、`mbedtls_aes_crypt_cbc`(CBC+PKCS7 手写)、 + `mbedtls_md`(MD5)、`mbedtls_base64_*`、`mbedtls_rsa_public`(无 padding 纯模幂 = weapi)、`mbedtls_ssl_*`(TLS1.2 client)。 + +### BearSSL(TLS 备选)— MIT +- 上游:https://www.bearssl.org/git/BearSSL (GitHub `bearssl/bearssl` 已 404,已迁移至官方站点;commit 7bea48e,无语义化版本号) +- 构建:`build_bearssl.sh`(`make lib`,CC/AR 用 zig 包装;产物 `out/bearssl/mipsel/bm/libbearssl.a`)。 +- 全库 text=317177(≈310 KB);但只取 TLS client+handshake+rsa+x509 并 `--gc-sections` 后 text≈98620(≈96 KB)。 +- 特点:体积小、常量时间;但**库里没有 base64**(需自带 ~0.5KB 例程), + AES-ECB 也无现成封装(需用 block primitive 逐块或 CBC 变通),RSA raw 用 `br_rsa_i31_public`(已确认存在)。 +- 结论:省 flash 的备选;v1 若集成想省事、且要保 weapi 精确性,仍以 mbedtls 优先。 + +### jsmn 1.1.0 — MIT +- 上游:https://github.com/zserge/jsmn(tag v1.1.0) +- header-only 分词器,无 DOM/无 malloc;编译为最小 TU 后 text=2208(≈2.2 KB)。 +- 用于解析响应 JSON(如 `{"code":200,"unikey":"..."}` 取 unikey)。demo 已实测解析正确。 + +### cJSON 1.7.18 — MIT(参考对比,未选) +- 上游:https://github.com/DaveGamble/cJSON(tag v1.7.18)。text=20503(≈20 KB)。 +- 优点:现成 DOM、好取值;缺点:20 KB + 需要 malloc、对 14MB 内存也偏大。选 jsmn。 + +### qrcodegen(nayuki QR-Code-generator)— MIT +- 上游:https://github.com/nayuki/QR-Code-generator(c/qrcodegen.c,commit 993ccbb) +- 纯 C 单文件、无图形依赖。text=11082(≈10.8 KB)。 +- 用途:把 `https://music.163.com/login?codekey=` 渲染成位图给 MeowOS UI 显示。 + +### minimp3 — CC0 +- 上游:https://github.com/lieff/minimp3(master@ea99364)。header-only(minimp3.h + minimp3_ex.h)。 +- 采用 `minimp3_ex`(带 ID3/CRC 处理、可 seek 的流式解码)text=38292(≈37.4 KB)。 +- 用途:v1 播放(standard/higher = mp3 128/192k)。内存占用小、MIPS 单核可实时(见 benchmark)。 + +### dr_mp3 0.7.4 — MIT/public-domain(参考对比,未选) +- 上游:https://github.com/mackron/dr_libs(dr_mp3.h 单文件)。 +- text=41679(≈40.7 KB);兼容性/音质更好,但更大、解码中间态内存更高。minimp3 更契合 14MB 限制。 + +## 备注(工程) +- mbedtls 产物默认写在 `vendor/mbedtls/library/`(3.6 的 Makefile 不理会 BUILD 变量),脚本再拷贝到 `out/`。 +- `MBEDTLS_SSL_IN_CONTENT_LEN/OUT_CONTENT_LEN` 默认 16KB;对 14MB 也不大,可再按需调小(见 REPORT 内存估)。 +- CA 根:demo 从 `/etc/ssl/certs/ca-certificates.crt` 加载;设备侧需内置 CA bundle(eapi 用 iPhone UA,域名 interface.music.163.com)。 +- 恢复 mbedtls 默认 config:`git -C vendor/mbedtls checkout -- include/mbedtls/mbedtls_config.h`。 diff --git a/patch_v2/ncm_app/bench/bench_dr_mp3 b/patch_v2/ncm_app/bench/bench_dr_mp3 new file mode 100755 index 0000000..a80062e Binary files /dev/null and b/patch_v2/ncm_app/bench/bench_dr_mp3 differ diff --git a/patch_v2/ncm_app/bench/bench_dr_mp3.c b/patch_v2/ncm_app/bench/bench_dr_mp3.c new file mode 100644 index 0000000..3fa39f8 --- /dev/null +++ b/patch_v2/ncm_app/bench/bench_dr_mp3.c @@ -0,0 +1,20 @@ +#define DR_MP3_IMPLEMENTATION +#include "dr_mp3.h" +#include +#include +#include +#include +static double now(){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return ts.tv_sec+ts.tv_nsec*1e-9; } +int main(int argc,char**argv){ + if(argc<2){fprintf(stderr,"usage: %s file.mp3\n",argv[0]);return 1;} + drmp3 d; if(!drmp3_init_file(&d,argv[1],NULL)){fprintf(stderr,"open fail\n");return 1;} + printf("rate=%d ch=%d\n",d.sampleRate,d.channels); + float *pcm=malloc(2048*2*sizeof(float)); + double t0=now(); long long frames=0; drmp3_uint64 n; + while((n=drmp3_read_pcm_frames_f32(&d,2048,pcm))!=0){ frames+=n; } + double t1=now(); + printf("total_pcm_frames=%lld\n",frames); + printf("wall=%.3f s throughput=%.1f xRT (%.1f frames/s)\n",(t1-t0), + (double)frames/d.sampleRate/(t1-t0),(double)frames/(t1-t0)); + drmp3_uninit(&d); free(pcm); return 0; +} diff --git a/patch_v2/ncm_app/bench/bench_minimp3 b/patch_v2/ncm_app/bench/bench_minimp3 new file mode 100755 index 0000000..2f6a2fe Binary files /dev/null and b/patch_v2/ncm_app/bench/bench_minimp3 differ diff --git a/patch_v2/ncm_app/bench/bench_minimp3.c b/patch_v2/ncm_app/bench/bench_minimp3.c new file mode 100644 index 0000000..1ad104e --- /dev/null +++ b/patch_v2/ncm_app/bench/bench_minimp3.c @@ -0,0 +1,19 @@ +#define MINIMP3_IMPLEMENTATION +#include "minimp3_ex.h" +#include +#include +#include +#include +static double now(){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return ts.tv_sec+ts.tv_nsec*1e-9; } +int main(int argc,char**argv){ + if(argc<2){fprintf(stderr,"usage: %s file.mp3\n",argv[0]);return 1;} + mp3dec_t dec; mp3dec_init(&dec); + mp3dec_file_info_t info; memset(&info,0,sizeof info); + double t0=now(); int r=mp3dec_load(&dec,argv[1],&info,NULL,NULL); double t1=now(); + if(r){fprintf(stderr,"decode fail %d\n",r);return 1;} + long long frames=info.samples/info.channels; + printf("rate=%d ch=%d total_pcm_frames=%lld\n",info.hz,info.channels,frames); + printf("wall=%.3f s throughput=%.1f xRT (%.1f frames/s)\n",(t1-t0), + (double)frames/info.hz/(t1-t0),(double)frames/(t1-t0)); + free(info.buffer); return 0; +} diff --git a/patch_v2/ncm_app/build_bearssl.sh b/patch_v2/ncm_app/build_bearssl.sh new file mode 100755 index 0000000..1b87e3a --- /dev/null +++ b/patch_v2/ncm_app/build_bearssl.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# BearSSL — 官方 git(https://www.bearssl.org/git/BearSSL)。许可 MIT。 +# 使用 make lib 编译出 libbearssl.a(含 int/core/codec/ec/rsa/symcipher/ssl/x509)。 +. ./libs_common.sh +SRC="$VENDOR/bearssl" +D="$OUT/bearssl/mipsel"; mkdir -p "$D" +# 在源码内建一个专用 build 目录,避免污染 +BM="$D/bm"; rm -rf "$BM"; mkdir -p "$BM" +make -C "$SRC" lib \ + BUILD="$BM" \ + CC="$CC_MIPS" \ + AR="$AR_MIPS" \ + RANLIB="$AR_MIPS ranlib" \ + CFLAGS="-Os -Wno-unused-parameter -fno-stack-protector -ffunction-sections -fdata-sections" \ + LD="$CC_MIPS" 2>&1 | tail -15 +echo "make exit=${PIPESTATUS[0]}" +echo "=== lib list ==="; ls -la "$BM/" +case "x$(ls "$BM"/*.a 2>/dev/null)" in x) echo "no .a produced";; *) echo "=== BearSSL (mipsel) ==="; measure_archive "$BM/libbearssl.a";; esac diff --git a/patch_v2/ncm_app/build_cjson.sh b/patch_v2/ncm_app/build_cjson.sh new file mode 100755 index 0000000..6db44aa --- /dev/null +++ b/patch_v2/ncm_app/build_cjson.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# cJSON v1.7.18 — 静态编译 mipsel。许可 MIT。选用 cJSON 仅作 JSON 解析对比,推荐用 jsmn。 +. ./libs_common.sh +SRC="$VENDOR/cjson/cJSON.c" +INC="-I$VENDOR/cjson" +D="$OUT/cjson/mipsel"; mkdir -p "$D" +$CC_MIPS -Os -I"$VENDOR" $INC -fno-stack-protector -ffunction-sections -fdata-sections -c "$SRC" -o "$D/cJSON.o" || exit 1 +$AR_MIPS rcs "$D/libcjson.a" "$D/cJSON.o" +echo "cJSON (mipsel):" +measure_archive "$D/libcjson.a" diff --git a/patch_v2/ncm_app/build_demo.sh b/patch_v2/ncm_app/build_demo.sh new file mode 100755 index 0000000..ec89474 --- /dev/null +++ b/patch_v2/ncm_app/build_demo.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# 复现宿主 + MIPS 版 HTTPS/加密 demo 与 RSA raw 校验。 +# 依赖:先运行 build_mbedtls.sh(产出 out/mbedtls/{host,mipsel}/*.a)。 +# 宿主 = 本机(此工作区为 aarch64);MIPS = qemu-mipsel 可跑(静态链)。 +. ./libs_common.sh + +INC="-I$VENDOR/mbedtls/include -I$VENDOR/jsmn" +HOST_LIBS="-L$OUT/mbedtls/host -lmbedtls -lmbedx509 -lmbedcrypto" +MIPS_LIBS="$OUT/mbedtls/mipsel/libmbedtls.a $OUT/mbedtls/mipsel/libmbedx509.a $OUT/mbedtls/mipsel/libmbedcrypto.a" + +echo "==> host https_unikey" +$CC_HOST -O2 $INC demo/https_unikey.c $HOST_LIBS -o demo/https_unikey -lpthread -lm \ + && echo " built demo/https_unikey" \ + && { [ -f /etc/ssl/certs/ca-certificates.crt ] && timeout 45 ./demo/https_unikey | tail -4; } + +echo "==> mipsel https_unikey (静态)" +$CC_MIPS -O2 -static -Wl,--gc-sections $INC demo/https_unikey.c $MIPS_LIBS -o out/https_unikey_mipsel -lpthread -lm \ + && echo " built out/https_unikey_mipsel" && size out/https_unikey_mipsel \ + && timeout 90 qemu-mipsel ./out/https_unikey_mipsel | tail -4 diff --git a/patch_v2/ncm_app/build_dr_mp3.sh b/patch_v2/ncm_app/build_dr_mp3.sh new file mode 100755 index 0000000..bca1311 --- /dev/null +++ b/patch_v2/ncm_app/build_dr_mp3.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# dr_mp3 — 单文件 header(dr_mp3.h)。许可 MIT/public domain (双许可)。较重但兼容性/质量更好。 +. ./libs_common.sh +D="$OUT/dr_mp3/mipsel"; mkdir -p "$D" +cat > "$D/dr_mp3_impl.c" <<'C' +#define DR_MP3_IMPLEMENTATION +#include "dr_mp3.h" +C +$CC_MIPS -Os -I"$VENDOR/dr_mp3" -fno-stack-protector -ffunction-sections -fdata-sections -c "$D/dr_mp3_impl.c" -o "$D/dr_mp3.o" || exit 1 +$AR_MIPS rcs "$D/libdr_mp3.a" "$D/dr_mp3.o" +echo "dr_mp3 (mipsel):" +measure_archive "$D/libdr_mp3.a" diff --git a/patch_v2/ncm_app/build_jsmn.sh b/patch_v2/ncm_app/build_jsmn.sh new file mode 100755 index 0000000..cdb3c79 --- /dev/null +++ b/patch_v2/ncm_app/build_jsmn.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# jsmn v1.1.0 — header-only tokenizer。许可 MIT。编译一个最小 TU 以量测其代码体积。 +. ./libs_common.sh +D="$OUT/jsmn/mipsel"; mkdir -p "$D" +cat > "$D/jsmn_measure.c" <<'C' +#include "jsmn.h" +#include +unsigned long jsmn_count(const char *js) { + jsmn_parser p; jsmn_init(&p); + unsigned n = jsmn_parse(&p, js, strlen(js), NULL, 0); + return n; +} +C +$CC_MIPS -Os -I"$VENDOR/jsmn" -fno-stack-protector -c "$D/jsmn_measure.c" -o "$D/jsmn.o" || exit 1 +$AR_MIPS rcs "$D/libjsmn.a" "$D/jsmn.o" +echo "jsmn (mipsel, header-only):" +measure_archive "$D/libjsmn.a" diff --git a/patch_v2/ncm_app/build_mbedtls.sh b/patch_v2/ncm_app/build_mbedtls.sh new file mode 100755 index 0000000..b51f5c4 --- /dev/null +++ b/patch_v2/ncm_app/build_mbedtls.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# mbedtls 3.6.2 — Mbed TLS(Apache-2.0)。裁剪配置 + 静态编 mipsel。 +# 产物体积:libmbedcrypto.a(AES/MD5/RSA/Base64/Ec),libmbedx509.a(证书解析),libmbedtls.a(TLS1.2 client)。 +# 裁剪策略:保留 TLS1.2 client + AES-GCM/CBC + RSA/ECDH + MD5 + base64 + X509 解析; +# 去掉 TLS1.3/DTLS/PSA/多余密码套件(PKCS5/7/12)/PEM_WRITE/CTR_DRBG(用自定义 rng)。 +# 保留 PEM_PARSE(加载 CA bundle 需要)。 +# 说明:脚本会把裁剪后的 config 复制到 include/mbedtls/mbedtls_config.h(原默认卷保留在 vendor git 里, +# 可用 `git -C vendor/mbedtls checkout -- include/mbedtls/mbedtls_config.h` 恢复)。 +. ./libs_common.sh + +MB="$VENDOR/mbedtls" +CFG_TMP="$MB/config/mbedtls_config_ncm.h" # 裁剪后的 config 落盘于此(交付物) +mkdir -p "$MB/config" +cp "$MB/include/mbedtls/mbedtls_config.h" "$CFG_TMP" + +# 逐个 unset(config.py 一次只能 unset 一个符号) +unsets=( + MBEDTLS_SSL_PROTO_TLS1_3 MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE + MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED + MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED + MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED + MBEDTLS_SSL_PROTO_DTLS MBEDTLS_SSL_DTLS_CONNECTION_ID + MBEDTLS_SSL_DTLS_ANTI_REPLAY MBEDTLS_SSL_DTLS_HELLO_VERIFY + MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE MBEDTLS_SSL_CONTEXT_SERIALIZATION + MBEDTLS_SSL_SESSION_TICKETS MBEDTLS_SSL_MAX_FRAGMENT_LENGTH + MBEDTLS_SSL_ALPN MBEDTLS_SSL_RENEGOTIATION MBEDTLS_SSL_CBC_RECORD_SPLITTING + MBEDTLS_SSL_ASYNC_PRIVATE MBEDTLS_SSL_TRUNCATED_HMAC MBEDTLS_SSL_KEEP_PEER_CERTIFICATE + MBEDTLS_SSL_RECORD_SIZE_LIMIT MBEDTLS_SSL_DTLS_SRTP MBEDTLS_SSL_COOKIE_C + MBEDTLS_CAMELLIA_C MBEDTLS_ARIA_C MBEDTLS_DES_C MBEDTLS_CHACHA20_C + MBEDTLS_CHACHAPOLY_C MBEDTLS_CCM_C MBEDTLS_NIST_KW_C MBEDTLS_ECJPAKE_C + MBEDTLS_HKDF_C MBEDTLS_PSA_CRYPTO_C MBEDTLS_CTR_DRBG_C MBEDTLS_ENTROPY_C + MBEDTLS_PEM_WRITE_C MBEDTLS_PKCS5_C MBEDTLS_PKCS7_C + MBEDTLS_PKCS12_C MBEDTLS_MD4_C MBEDTLS_X509_CRL_PARSE_C MBEDTLS_X509_CSR_PARSE_C + MBEDTLS_SSL_ECP_RESTARTABLE MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH + # PSA 关联项:去掉全部 PSA/LMS/ITS(我们用 legacy API,不用 PSA) + MBEDTLS_PSA_CRYPTO_STORAGE_C MBEDTLS_PSA_ITS_FILE_C MBEDTLS_PSA_KEY_STORE_DYNAMIC + MBEDTLS_LMS_C MBEDTLS_PSA_CRYPTO_CLIENT +) +CFGF="$MB/include/mbedtls/mbedtls_config.h" +for s in "${unsets[@]}"; do + python3 "$MB/scripts/config.py" --file "$CFG_TMP" unset "$s" >/dev/null 2>&1 || true +done +echo "trimmed config enabled-count: $(grep -c '^#define MBEDTLS_' "$CFG_TMP") (default: 141)" + +# 应用裁剪 config 到默认位置(build 默认读 include/mbedtls/mbedtls_config.h) +cp "$CFG_TMP" "$CFGF" +# 我们不用 PSA,且不希望 build 因缺 python-jsonschema 重新生成 PSA 驱动包装文件: +# 把框架生成文件 touch 成“比 config 新”,让 make 跳过重新生成。 +touch "$MB/library/error.c" "$MB/library/version_features.c" \ + "$MB/library/ssl_debug_helpers_generated.c" \ + "$MB/library/psa_crypto_driver_wrappers.h" \ + "$MB/library/psa_crypto_driver_wrappers_no_static.c" +# 关键:config 变更不会自动触发 .c 重编,务必清掉旧对象/库再重编,否则 PEM 等新启用模块不会进库。 +rm -f "$MB/library"/*.o "$MB/library"/*.a + +BM="$OUT/mbedtls/mipsel"; rm -rf "$BM"; mkdir -p "$BM" +# 在 vendor 源码内建,输出到本 app 的 out 目录 +make -C "$MB" lib \ + BUILD="$BM" \ + CC="$CC_MIPS" \ + AR="$AR_MIPS" \ + CFLAGS="-Os -Wno-unused-parameter -fno-stack-protector -ffunction-sections -fdata-sections" \ + LD="$CC_MIPS" 2>&1 | tail -6 +echo "make exit=${PIPESTATUS[0]}" +# 把产物从 vendor/library 拷到本 app 的 out/mbedtls/mipsel(make 的 BUILD 变量在本版 Makefile 不生效) +mkdir -p "$BM" +for a in libmbedcrypto.a libmbedx509.a libmbedtls.a; do + [ -s "$MB/library/$a" ] && cp "$MB/library/$a" "$BM/$a" +done +echo "=== mbedtls (mipsel) ===" +for a in libmbedcrypto.a libmbedx509.a libmbedtls.a; do + [ -s "$BM/$a" ] && { echo "-- $a --"; measure_archive "$BM/$a"; } +done diff --git a/patch_v2/ncm_app/build_minimp3.sh b/patch_v2/ncm_app/build_minimp3.sh new file mode 100755 index 0000000..beb3a39 --- /dev/null +++ b/patch_v2/ncm_app/build_minimp3.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# minimp3 — header-only (minimp3.h + minimp3_ex.h)。许可 CC0 / MIT。v1 推荐的 MP3 解码器。 +. ./libs_common.sh +D="$OUT/minimp3/mipsel"; mkdir -p "$D" +cat > "$D/minimp3_impl.c" <<'C' +#define MINIMP3_IMPLEMENTATION +#include "minimp3_ex.h" +// 拉入 mp3dec_ex_open/read;minimp3.h 由 minimp3_ex.h 内部包含 +C +$CC_MIPS -Os -I"$VENDOR/minimp3" -fno-stack-protector -ffunction-sections -fdata-sections -c "$D/minimp3_impl.c" -o "$D/minimp3_ex.o" || exit 1 +$AR_MIPS rcs "$D/libminimp3_ex.a" "$D/minimp3_ex.o" +echo "minimp3 (mipsel, mp3dec_ex):" +measure_archive "$D/libminimp3_ex.a" diff --git a/patch_v2/ncm_app/build_qrcodegen.sh b/patch_v2/ncm_app/build_qrcodegen.sh new file mode 100755 index 0000000..9688b7a --- /dev/null +++ b/patch_v2/ncm_app/build_qrcodegen.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# qrcodegen (nayuki QR-Code-generator, c/qrcodegen.c) — 静态编译 mipsel。许可 MIT。 +. ./libs_common.sh +SRC="$VENDOR/qrcodegen/c/qrcodegen.c" +D="$OUT/qrcodegen/mipsel"; mkdir -p "$D" +$CC_MIPS -Os -I"$VENDOR/qrcodegen/c" -fno-stack-protector -c "$SRC" -o "$D/qrcodegen.o" || exit 1 +$AR_MIPS rcs "$D/libqrcodegen.a" "$D/qrcodegen.o" +echo "qrcodegen (mipsel):" +measure_archive "$D/libqrcodegen.a" diff --git a/patch_v2/ncm_app/demo/https_unikey b/patch_v2/ncm_app/demo/https_unikey new file mode 100755 index 0000000..302d6b7 Binary files /dev/null and b/patch_v2/ncm_app/demo/https_unikey differ diff --git a/patch_v2/ncm_app/demo/https_unikey.c b/patch_v2/ncm_app/demo/https_unikey.c new file mode 100644 index 0000000..535b1de --- /dev/null +++ b/patch_v2/ncm_app/demo/https_unikey.c @@ -0,0 +1,256 @@ +/* https_unikey.c — 网易云 eapi 二维码 unikey 最小 HTTPS 实测 demo + * 目标端: POST https://interface.music.163.com/eapi/login/qrcode/unikey {type:3} + * 用途: 技术栈预研 — 验证 mbedtls(TLS1.2) + AES-128-ECB + MD5 + Base64(无需) + jsmn 在宿主/MIPS 均可用。 + * eapi 加密(参考 CNMPlayer ncm-api-rs crypto::eapi): + * digest = md5("nobody" + url + "use" + json + "md5forencrypt") // url="/api/login/qrcode/unikey" + * plain = url + "-36cd479b6b5-" + json + "-36cd479b6b5-" + digest + * params = HEX_UPPER(AES128_ECB(plain, EAPI_KEY)) // EAPI_KEY="e82ckenh8dichen8" + * HTTP POST body = "params=" + params ; Cookie 头为 eapi header map(urlencoded 拼接)。 + * 编译(宿主): gcc -O2 -Ivendor/mbedtls/include -Ivendor/jsmn demo/https_unikey.c \ + * vendor/mbedtls/library/libmbedtls.a vendor/mbedtls/library/libmbedx509.a \ + * vendor/mbedtls/library/libmbedcrypto.a -o demo/https_unikey + * 编译(MIPS): .tools/zig-mipsel-cc -O2 -static ... (见 build_demo.sh) + */ +#include +#include +#include +#include +#include + +#include "mbedtls/net_sockets.h" +#include "mbedtls/ssl.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/md.h" +#include "mbedtls/aes.h" +#include "mbedtls/error.h" +#include "jsmn.h" + +#define EAPI_KEY "e82ckenh8dichen8" +#define EAPI_URL "/api/login/qrcode/unikey" +#define HTTP_URL "/eapi/login/qrcode/unikey" +#define HOST "interface.music.163.com" +#define UA_EAPI "NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)" +#define SEP "-36cd479b6b5-" + +/* ---------- 实用: 十六进制 ---------- */ +static void hex_lower(const unsigned char *in, size_t n, char *out) { + static const char d[] = "0123456789abcdef"; + for (size_t i = 0; i < n; i++) { out[i*2] = d[in[i]>>4]; out[i*2+1] = d[in[i]&15]; } + out[n*2] = 0; +} +static void hex_upper(const unsigned char *in, size_t n, char *out) { + static const char d[] = "0123456789ABCDEF"; + for (size_t i = 0; i < n; i++) { out[i*2] = d[in[i]>>4]; out[i*2+1] = d[in[i]&15]; } + out[n*2] = 0; +} + +/* ---------- MD5 (mbedtls_md) ---------- */ +static void md5_hex(const char *msg, char *out_digest_hex /*33*/) { + unsigned char d[16]; + const mbedtls_md_info_t *inf = mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + mbedtls_md(inf, (const unsigned char*)msg, strlen(msg), d); + hex_lower(d, 16, out_digest_hex); +} + +/* ---------- AES-128-ECB + PKCS7 → 大写 Hex ---------- */ +static void aes_ecb_hex(const char *plain, char *out /*hex*/) { + size_t n = strlen(plain); + int pad = 16 - (int)(n % 16); + size_t total = n + pad; + unsigned char buf[512], key[16], enc[512]; + memcpy(buf, plain, n); + for (int i = 0; i < pad; i++) buf[n + i] = (unsigned char)pad; + mbedtls_aes_context aes; mbedtls_aes_init(&aes); + mbedtls_aes_setkey_enc(&aes, (const unsigned char*)EAPI_KEY, 128); + for (size_t off = 0; off < total; off += 16) + mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, &buf[off], &enc[off]); + mbedtls_aes_free(&aes); + hex_upper(enc, total, out); +} + +/* ---------- 生成 52 hex deviceId(演示用,可被环境变量覆盖) ---------- */ +static void make_device_id(char *out) { + const char *env = getenv("NCM_DEVICE_ID"); + if (env && strlen(env) == 52) { strcpy(out, env); return; } + unsigned char r[26]; FILE *f = fopen("/dev/urandom","rb"); + if (f) { size_t got = fread(r,1,26,f); fclose(f); if (got==26){ hex_upper(r,26,out); return; } } + /* fallback 固定值 */ + strcpy(out, "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"); +} + +/* ---------- 自定义 RNG (读取 /dev/urandom) ---------- */ +static FILE *rng_fp = NULL; +static int my_rng(void *arg, unsigned char *out, size_t len) { + (void)arg; + if (!rng_fp) rng_fp = fopen("/dev/urandom","rb"); + if (!rng_fp) return -1; + return fread(out,1,len,rng_fp) == len ? 0 : -1; +} + +/* ---------- eapi 请求体构造 ---------- */ +static void build_eapi(const char *device_id, char *body_out /* "params=..."; 尺寸给足 */, + char *cookie_out, size_t cookie_sz) { + char ts[32]; sprintf(ts, "%lld", (long long)time(NULL)); + char ms[32]; struct timespec tv; clock_gettime(CLOCK_REALTIME,&tv); + sprintf(ms, "%lld", (long long)tv.tv_sec*1000 + tv.tv_nsec/1000000); + char buildver[16]; memcpy(buildver, ts, 10); buildver[10]=0; + char request_id[48]; sprintf(request_id, "%s_0000", ms); + + /* header map (eapi header cookie & json.header 共用同一组键值) */ + const char *osver = "Microsoft-Windows-10-Professional-build-19045-64bit"; + const char *appver = "3.1.17.204416"; + const char *os = "pc", *channel = "netease"; + const char *resolution = "1920x1080", *versioncode = "140", *mobilename = ""; + + /* Cookie 头 (k=v 用 '; ' 拼接,值不做 urlencode,与 §7 实测一致) */ + snprintf(cookie_out, cookie_sz, + "__csrf=;appver=%s;buildver=%s;channel=%s;deviceId=%s;mobilename=%s;os=%s;osver=%s;requestId=%s;resolution=%s;versioncode=%s", + appver, buildver, channel, device_id, mobilename, os, osver, request_id, resolution, versioncode); + + /* json 明文 (key 顺序: type, header, e_r;header 内顺序无关紧要,服务端做 JSON 解析) */ + char json[1024]; + snprintf(json, sizeof json, + "{\"type\":3,\"header\":{\"__csrf\":\"\",\"appver\":\"%s\",\"buildver\":\"%s\",\"channel\":\"%s\"," + "\"deviceId\":\"%s\",\"mobilename\":\"%s\",\"os\":\"%s\",\"osver\":\"%s\",\"requestId\":\"%s\"," + "\"resolution\":\"%s\",\"versioncode\":\"%s\"},\"e_r\":false}", + appver, buildver, channel, device_id, mobilename, os, osver, request_id, resolution, versioncode); + + /* eapi 签名 */ + char msg[1400]; + snprintf(msg, sizeof msg, "nobody%suse%smd5forencrypt", EAPI_URL, json); + char digest[33]; md5_hex(msg, digest); + char plain[1600]; + snprintf(plain, sizeof plain, "%s%s%s%s%s", EAPI_URL, SEP, json, SEP, digest); + char params[1200]; + aes_ecb_hex(plain, params); + snprintf(body_out, 1300, "params=%s", params); +} + +/* ---------- jsmn 取 "unikey" 字符串值 ---------- */ +static int jsmn_get_str(const char *json, const char *key, char *val, size_t vsz) { + jsmn_parser p; jsmn_init(&p); + int n = jsmn_parse(&p, json, strlen(json), NULL, 0); + if (n <= 0) return -1; + jsmntok_t *tok = malloc(sizeof(jsmntok_t)*(size_t)n); + if (!tok) return -1; + jsmn_init(&p); + if (jsmn_parse(&p, json, strlen(json), tok, n) < 0) { free(tok); return -1; } + int rc = -1; + for (int i = 0; i < n - 1; i++) { + if (tok[i].type == JSMN_STRING && + (int)(tok[i].end - tok[i].start) == (int)strlen(key) && + strncmp(json + tok[i].start, key, tok[i].end - tok[i].start) == 0) { + jsmntok_t *v = &tok[i+1]; + if (v->type == JSMN_STRING) { + size_t len = v->end - v->start; + if (len < vsz) { memcpy(val, json + v->start, len); val[len] = 0; rc = 0; } + } + break; + } + } + free(tok); return rc; +} + +/* ---------- TLS POST (mbedtls) ---------- */ +static int tls_post(const char *host, const char *path, const char *cookie, + const char *body, char *resp, size_t resp_sz) { + int ret; + mbedtls_net_context net; mbedtls_net_init(&net); + mbedtls_ssl_context ssl; mbedtls_ssl_init(&ssl); + mbedtls_ssl_config conf; mbedtls_ssl_config_init(&conf); + mbedtls_x509_crt cacert; mbedtls_x509_crt_init(&cacert); + + /* 载入 CA 根(宿主/system 路径; 设备侧请内置 CA bundle) */ + const char *ca_paths[] = { + "/etc/ssl/certs/ca-certificates.crt", + "/etc/ssl/certs/ca-bundle.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + NULL + }; + int ca_ok = 0; + for (int i = 0; ca_paths[i]; i++) + if (mbedtls_x509_crt_parse_file(&cacert, ca_paths[i]) == 0) { ca_ok = 1; break; } + if (!ca_ok) { fprintf(stderr, "[!] no CA bundle loaded\n"); ret = -100; goto out; } + + if ((ret = mbedtls_net_connect(&net, host, "443", MBEDTLS_NET_PROTO_TCP)) != 0) { + fprintf(stderr, "[!] connect: -0x%04x\n", (unsigned)-ret); goto out; + } + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) goto out; + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_rng(&conf, my_rng, NULL); + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) goto out; + if ((ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0) goto out; + mbedtls_ssl_set_bio(&ssl, &net, mbedtls_net_send, mbedtls_net_recv, NULL); + + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + fprintf(stderr, "[!] handshake: -0x%04x\n", (unsigned)-ret); goto out; + } + } + + char req[2048]; + int rlen = snprintf(req, sizeof req, + "POST %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Cookie: %s\r\nConnection: close\r\nContent-Length: %zu\r\n\r\n%s", + path, host, UA_EAPI, cookie, strlen(body), body); + size_t off = 0; + while (off < (size_t)rlen) { + ret = mbedtls_ssl_write(&ssl, (const unsigned char*)req + off, (size_t)rlen - off); + if (ret < 0) { + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { continue; } + fprintf(stderr, "[!] ssl_write: -0x%04x\n", (unsigned)-ret); goto out; + } + off += (size_t)ret; + } + + size_t total = 0; memset(resp, 0, resp_sz); + for (;;) { + ret = mbedtls_ssl_read(&ssl, (unsigned char*)resp + total, resp_sz - 1 - total); + if (ret == 0) break; /* 对端关闭 */ + if (ret < 0) { + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) continue; + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) break; + fprintf(stderr, "[!] ssl_read: -0x%04x\n", (unsigned)-ret); goto out; + } + total += (size_t)ret; + if (total >= resp_sz - 1) break; + } + ret = (int)total; +out: + mbedtls_net_free(&net); mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&conf); + mbedtls_x509_crt_free(&cacert); + if (rng_fp) { fclose(rng_fp); rng_fp = NULL; } + return ret; +} + +int main(void) { + char device_id[64]; make_device_id(device_id); + /* 演示用随机 deviceId(实际应持久化,风控要求稳定) */ + printf("deviceId=%s\n", device_id); + + char body[1300], cookie[512]; + build_eapi(device_id, body, cookie, sizeof cookie); + + char resp[8192]; + int n = tls_post(HOST, HTTP_URL, cookie, body, resp, sizeof resp); + if (n < 0) { + char eb[128]; mbedtls_strerror(n, eb, sizeof eb); + fprintf(stderr, "HTTP request failed: %d (%s)\n", n, eb); + return 1; + } + resp[n] = 0; + /* 剥掉 HTTP 头 */ + char *pbody = strstr(resp, "\r\n\r\n"); + pbody = pbody ? pbody + 4 : resp; + printf("---- response body ----\n%.*s\n", (int)strlen(pbody), pbody); + + char unikey[64] = {0}; + if (jsmn_get_str(pbody, "unikey", unikey, sizeof unikey) == 0) + printf("UNIKEY = %s\n", unikey); /* ← 拿到即成功 */ + else + printf("UNIKEY not found\n"); + return jsmn_get_str(pbody, "unikey", unikey, sizeof unikey) == 0 ? 0 : 2; +} diff --git a/patch_v2/ncm_app/demo/rsa_raw_test.c b/patch_v2/ncm_app/demo/rsa_raw_test.c new file mode 100644 index 0000000..7e697ba --- /dev/null +++ b/patch_v2/ncm_app/demo/rsa_raw_test.c @@ -0,0 +1,48 @@ +/* rsa_raw_test.c — 验证 weapi 需要的 textbook RSA(无 padding 模幂) + * 用 mbedtls_rsa_public() 对固定 16 字节消息做 c = m^e mod n, + * 输出定长 256 hex(与模数等长)。宿主用 Python pow(m,e,n) 交叉校验。 + * 编译(mipsel): .tools/zig-mipsel-cc -O2 -static -Ivendor/mbedtls/include rsa_raw_test.c \ + * out/mbedtls/mipsel/libmbedcrypto.a -o out/rsa_raw_test + */ +#include +#include +#include "mbedtls/rsa.h" +#include "mbedtls/pk.h" +#include "mbedtls/base64.h" + +/* PUBLIC_KEY_DER_B64 (PKCS#8 / SubjectPublicKeyInfo, 1024-bit RSA) */ +static const char *PEM_DER_B64 = + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB"; + +int main(void) { + unsigned char der[512], *buf = der; size_t der_len = sizeof der; + /* 解码 base64 DER */ + if (mbedtls_base64_decode(der, sizeof der, &der_len, (const unsigned char*)PEM_DER_B64, strlen(PEM_DER_B64)) != 0) { + printf("base64 decode fail\n"); return 1; + } + /* 解析公钥 */ + mbedtls_pk_context pk; mbedtls_pk_init(&pk); + if (mbedtls_pk_parse_public_key(&pk, der, der_len) != 0) { + printf("pk parse fail\n"); return 1; + } + mbedtls_rsa_context *rsa = mbedtls_pk_rsa(pk); + int keylen = mbedtls_rsa_get_len(rsa); + printf("rsa len=%d (bytes)\n", keylen); + + /* 固定 16 字节明文(weapi 的 secret_key),右对齐到 keylen 后做 raw 模幂 */ + unsigned char secret[16]; + for (int i = 0; i < 16; i++) secret[i] = (unsigned char)(0x41 + i); /* 测试向量 */ + unsigned char input[256], output[256]; + memset(input, 0, sizeof input); + memcpy(input + keylen - 16, secret, 16); + int rc = mbedtls_rsa_public(rsa, input, output); /* 无 padding 纯模幂 */ + if (rc != 0) { printf("rsa_public fail %d\n", rc); return 1; } + printf("secret = "); + for (int i = 0; i < 16; i++) printf("%02X", secret[i]); + printf("\nCT = "); + for (int i = 0; i < keylen; i++) printf("%02X", output[i]); + printf("\n"); + + mbedtls_pk_free(&pk); + return 0; +} diff --git a/patch_v2/ncm_app/libs_common.sh b/patch_v2/ncm_app/libs_common.sh new file mode 100755 index 0000000..f45384f --- /dev/null +++ b/patch_v2/ncm_app/libs_common.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# 公共编译/测量环境。被各 build_*.sh 引用。 +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VENDOR="$ROOT/vendor" +OUT="$ROOT/out" +CC_MIPS="/home/Aska/Project/R3ProII/.tools/zig-mipsel-cc" +AR_MIPS="/home/Aska/Project/R3ProII/.tools/zig-env/bin/zig ar" +CC_HOST="gcc" +SZ_HOST="size" +# 交叉 bin 是 x86_64 的,本机(aarch64)跑不了;统一用 host `size`(可读任意 ELF)+ zig ar。 +# 注意:build 目录在 patch_v2/ncm_app 内,符号用绝对路径由各脚本自行 source 本文件。 + +# measure_archive : 统计归档全部成员 text/data/bss。flash≈text,RAM≈data+bss +measure_archive() { + local a="$1" + if [ ! -s "$a" ]; then echo " (missing/empty: $a)"; return; fi + $SZ_HOST "$a" | awk ' + /^[[:space:]]*[0-9]+[[:space:]]/ { t+=$1; d+=$2; b+=$3 } + END { printf " text=%-8d data=%-8d bss=%-8d (flash~%s KB RAM~%s KB)\n", t, d, b, t/1024.0, (d+b)/1024.0 } + ' +}