From e0bd503261a911fd7f13e4a8b2274e53b455bd5a Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 11:44:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(ncm):=20=E4=BC=9A=E8=AF=9D=E5=AF=86?= =?UTF-8?q?=E9=92=A5=E6=94=B9=E7=94=A8=E8=AE=BE=E5=A4=87=E7=A7=81=E6=9C=89?= =?UTF-8?q?=20.mkey=EF=BC=8C=E7=BD=91=E5=8D=A1/MAC=20=E5=8F=98=E5=8C=96?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E4=B8=A2=E7=99=BB=E5=BD=95=E6=80=81=EF=BC=88?= =?UTF-8?q?M9=20=E5=90=AF=E5=8A=A8=E6=97=A0=E7=BD=91=E6=A0=B9=E5=9B=A0?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因(代码级):会话 cookie 加密密钥 = sha256(首选网卡 MAC + 盐),MAC 取自 /sys/class/net 首个非 lo 网卡。设备断网时 Wi-Fi 网卡可能从 /sys/class/net 消失, 或 readdir 顺序变化选到另一块网卡 → 派生密钥改变 → session.enc 解不开 → ncm_ctx_load_cookie 失败 → cookie 为空 → g_st.logged_in=0 → 启动直接进扫码页。 这与「把网络不可达当登录过期」是同一症状的另一条路径,且发生在登录门之前。 改动: - 新密钥优先 /.mkey(32B 随机,首次生成后稳定,与网络状态无关); .mkey 不可写(只读 FS/无权限)才回退旧 MAC 派生,保持极端环境可启动。 - 旧版 MAC 派生密文仍可解(cookie_decrypt_sel 二次尝试);load_session 命中旧密钥时 自动重写为 .mkey 密钥(透明迁移,不改调用方)。 - ncm_store.h 同步说明:machine_key 仅作旧密文兼容,新写入不再使用。 --- patch_v2/ncm_app/core/ncm_store.c | 68 +++++++++++++++++++++++++++---- patch_v2/ncm_app/core/ncm_store.h | 10 +++-- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/patch_v2/ncm_app/core/ncm_store.c b/patch_v2/ncm_app/core/ncm_store.c index 4006da5..d29ec76 100644 --- a/patch_v2/ncm_app/core/ncm_store.c +++ b/patch_v2/ncm_app/core/ncm_store.c @@ -170,6 +170,28 @@ int ncm_store_machine_key(const char *salt, unsigned char key[16]) { return 0; } +/* 设备私有密钥派生:sha256(.mkey_hex + 盐)。.mkey 是首次生成的 32 字节随机数, + * 与网络状态/网卡集合无关。 */ +static int key_from_mkey(const char *salt, unsigned char key[16]) { + unsigned char mkey[32], h[32]; + if (get_or_create_mkey(mkey) != 0) return -1; + char hex[65], buf[96]; + ncm_hex_encode(mkey, 32, hex, 0); + snprintf(buf, sizeof buf, "%s%s", hex, salt ? salt : ""); + if (ncm_sha256(buf, strlen(buf), h) != 0) return -1; + memcpy(key, h, 16); + return 0; +} + +/* 会话密钥(M9 根因修复):优先设备私有 .mkey,只有它不可用(只读文件系统/无权限)才回退 + * 旧版 MAC 派生。旧版密钥依赖「首选网卡 MAC」——设备断网时网卡可能从 /sys/class/net 消失, + * 或 readdir 顺序变化选到另一块网卡,派生结果随之改变 → session.enc 解不开 → App 以为 + * 「未登录」直接进扫码页(用户报告的「一断网就退到扫码登录」正是该路径)。 */ +static int session_key(const char *salt, unsigned char key[16]) { + if (key_from_mkey(salt, key) == 0) return 0; + return ncm_store_machine_key(salt, key); +} + static int pkcs7_pad_len(size_t n) { return (int)(16 - (n % 16)); /* 1..16 */ } @@ -178,7 +200,7 @@ int ncm_store_cookie_encrypt(const char *plain, unsigned char *out, size_t *out_len, size_t cap) { if (!plain || !out || !out_len) return NCM_ERR_ARG; unsigned char key[16]; - if (ncm_store_machine_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO; + if (session_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO; size_t n = strlen(plain); int pad = pkcs7_pad_len(n); @@ -212,13 +234,9 @@ int ncm_store_cookie_encrypt(const char *plain, return NCM_OK; } -int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len, - char *out, size_t out_sz) { - if (!in || !out || !out_sz) return NCM_ERR_ARG; - if (in_len < SESSION_MAGIC_LEN + 16 || memcmp(in, SESSION_MAGIC, SESSION_MAGIC_LEN) != 0) - return NCM_ERR_CRYPTO; - unsigned char key[16]; - if (ncm_store_machine_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO; +/* 用指定密钥解密已解包(魔数/iv 已剥离)的密文;成功返回 NCM_OK。 */ +static int cookie_decrypt_key(const unsigned char *in, size_t in_len, + const unsigned char key[16], char *out, size_t out_sz) { const unsigned char *iv = in + SESSION_MAGIC_LEN; size_t clen = in_len - SESSION_MAGIC_LEN - 16; if (clen == 0 || clen % 16) return NCM_ERR_CRYPTO; @@ -246,6 +264,34 @@ int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len, return NCM_OK; } +/* 解密(*legacy 可选:回填 1=命中的是旧版 MAC 派生密钥,调用方据此迁移重加密): + * ① 新密钥(.mkey 派生)——当前写入格式; + * ② 旧密钥(MAC 派生)——兼容升级前落盘的 session.enc,解出即迁移。 */ +static int cookie_decrypt_sel(const unsigned char *in, size_t in_len, + char *out, size_t out_sz, int *legacy) { + if (!in || !out || !out_sz) return NCM_ERR_ARG; + if (legacy) *legacy = 0; + if (in_len < SESSION_MAGIC_LEN + 16 || memcmp(in, SESSION_MAGIC, SESSION_MAGIC_LEN) != 0) + return NCM_ERR_CRYPTO; + + unsigned char key[16]; + if (session_key(SESSION_SALT, key) == 0) { + int rc = cookie_decrypt_key(in, in_len, key, out, out_sz); + if (rc == NCM_OK) return NCM_OK; + if (rc == NCM_ERR_FULL || rc == NCM_ERR_OOM) return rc; + } + if (ncm_store_machine_key(SESSION_SALT, key) == 0) { + int rc = cookie_decrypt_key(in, in_len, key, out, out_sz); + if (rc == NCM_OK) { if (legacy) *legacy = 1; return NCM_OK; } + } + return NCM_ERR_CRYPTO; +} + +int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len, + char *out, size_t out_sz) { + return cookie_decrypt_sel(in, in_len, out, out_sz, NULL); +} + int ncm_store_save_session(const ncm_store_t *st, const char *cookie) { if (!st || !cookie) return NCM_ERR_ARG; unsigned char enc[8192]; @@ -264,7 +310,11 @@ int ncm_store_load_session(const ncm_store_t *st, char *cookie, size_t sz) { char raw[8192]; int n = ncm_store_read_file(path, raw, sizeof raw); if (n <= 0) return NCM_ERR_NOENT; - return ncm_store_cookie_decrypt((const unsigned char *)raw, (size_t)n, cookie, sz); + int legacy = 0; + int rc = cookie_decrypt_sel((const unsigned char *)raw, (size_t)n, cookie, sz, &legacy); + /* 旧版 MAC 密钥解出 → 就近迁移为 .mkey 密钥(失败不回滚:本次仍返回明文会话)。 */ + if (rc == NCM_OK && legacy) (void)ncm_store_save_session(st, cookie); + return rc; } int ncm_store_clear_session(const ncm_store_t *st) { diff --git a/patch_v2/ncm_app/core/ncm_store.h b/patch_v2/ncm_app/core/ncm_store.h index b99de93..26c7493 100644 --- a/patch_v2/ncm_app/core/ncm_store.h +++ b/patch_v2/ncm_app/core/ncm_store.h @@ -3,7 +3,8 @@ * 职责(纯 C,不依赖 UI/SDK): * - 应用数据目录推断(env MEOW_DATA_DIR 优先,否则 /tmp 回退) * - deviceId(52hex) 首生成 + 持久化(风控要求稳定) - * - 机器绑定密钥派生:sha256(MAC + 盐) → AES-128-CBC key(防裸读,非真安全) + * - 设备私有密钥派生(/.mkey + 盐)→ AES-128-CBC key(防裸读,非真安全); + * 旧版 MAC 派生密钥保留兼容解密/自动迁移 * - 登录 cookie 加密落盘/读回(AES-128-CBC + 随机 IV + 魔数头) * - 原子写文件、读取、配置键读取(/.cfg,对齐 SDK meow_config) */ @@ -34,10 +35,13 @@ int ncm_store_get_device_id(ncm_store_t *st, char out[NCM_STORE_ID_MAX]); int ncm_store_set_device_id(ncm_store_t *st, const char id[NCM_STORE_ID_MAX]); /* 机器绑定密钥:sha256(mac + salt) 取前 16 字节作为 AES-128 key。 - * mac 取首选网卡 MAC 小写;NCM_MAC 环境变量可覆盖(测试用)。失败返回非 0。 */ + * mac 取首选网卡 MAC 小写;NCM_MAC 环境变量可覆盖(测试用)。失败返回非 0。 + * ⚠ 仅作旧版 session.enc 的兼容解密(见下):MAC 随网卡出现/消失而变化,勿用于新写入。 */ int ncm_store_machine_key(const char *salt, unsigned char key[16]); -/* cookie 加解密(AES-128-CBC,PKCS7)。注意:这是防裸读模糊,非真安全。 */ +/* cookie 加解密(AES-128-CBC,PKCS7)。注意:这是防裸读模糊,非真安全。 + * M9 起密钥改为设备私有 /.mkey 派生(与网络/网卡状态无关);旧版 MAC 派生 + * 密文仍可解,load_session 命中旧密钥时自动重写为新密钥(透明迁移)。 */ int ncm_store_cookie_encrypt(const char *plain, unsigned char *out, size_t *out_len, size_t cap); int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,