fix(ncm): weapi挂死根因修复 (agent-207, 6缺陷叠加) — ①超时改CLOCK_MONOTONIC(墙钟NTP回拨致poll超长阻塞) ②响应头解析隔行跳行漏Content-Length ③请求头缓冲1KB→8KB ④cookie值截断192B致MUSIC_U不完整被当游客(改ptr+len不截断+按名去重) ⑤split处NUL覆盖body首字节 ⑥Content-Length:0直接DONE; status_check硬上限6s; 803后worker线程取uid/nick落盘(修uid恒空)
ncm→bf6d2a79 已部署; 真机1次验证(风控合规): status_check 721ms rc=0 uid=408996274; user_playlist 2.5s rc=0 count=64
This commit is contained in:
1 parent
4ff8c8baa3
commit
a651df0e9b
19 files changed
+466
-71
No files matched your search
@@ -0,0 +1,29 @@
|
||||
/* dev_repro.c — 真机复现 login_status_check(weapi)挂死
|
||||
* 用与 App 相同的 core 库:ncm_ctx_init 载入 deviceId/cookie,然后 ncm_login_status_check。
|
||||
* 观察返回码与耗时;若 >10min 即复现挂死。
|
||||
* 用法: ./dev_repro [data_dir] [cacert]
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_http.h"
|
||||
#include "ncm_common.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *data_dir = argc > 1 ? argv[1] : "/usr/data/mnt/sd_0/MeowData/os.meow.ncm/data";
|
||||
const char *cacert = argc > 2 ? argv[2] : "/usr/data/mnt/sd_0/MeowAPPs/os.meow.ncm/cacert.pem";
|
||||
|
||||
ncm_ctx_t ctx;
|
||||
ncm_ctx_init(&ctx, data_dir);
|
||||
printf("device_id=%s\n", ctx.device_id);
|
||||
printf("cookie_len=%zu cookie=%s\n", strlen(ctx.cookie), ctx.cookie);
|
||||
ncm_http_set_verify_default(1, cacert);
|
||||
|
||||
char uid[32], nick[128];
|
||||
long t0 = (long)ncm_now_ms();
|
||||
printf("status_check start...\n");
|
||||
int rc = ncm_login_status_check(ctx.device_id, ctx.cookie, uid, sizeof uid, nick, sizeof nick);
|
||||
long dt = (long)ncm_now_ms() - t0;
|
||||
printf("status_check rc=%d elapsed_ms=%ld uid=%s nick=%s\n", rc, dt, uid, nick);
|
||||
return rc == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* dev_repro2.c — 真机 dump weapi account/get 响应的真实分段(Content-Length/chunked/body_len)。
|
||||
* 用大缓冲 64KB,避免 -11 FULL;打印解析出的 status/content_length/chunked/body_len。
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_req.h"
|
||||
#include "ncm_http.h"
|
||||
#include "ncm_common.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *data_dir = argc > 1 ? argv[1] : "/usr/data/mnt/sd_0/MeowData/os.meow.ncm/data";
|
||||
const char *cacert = argc > 2 ? argv[2] : "/usr/data/mnt/sd_0/MeowAPPs/os.meow.ncm/cacert.pem";
|
||||
|
||||
ncm_ctx_t ctx;
|
||||
ncm_ctx_init(&ctx, data_dir);
|
||||
printf("device_id=%s cookie_len=%zu\n", ctx.device_id, strlen(ctx.cookie));
|
||||
ncm_http_set_verify_default(1, cacert);
|
||||
|
||||
char body[NCM_REQ_BODY_MAX], hdrs[NCM_REQ_HDRS_MAX], url[NCM_REQ_URL_MAX];
|
||||
int rc = ncm_req_build(ctx.device_id, ctx.cookie, "/api/w/nuser/account/get", "{}",
|
||||
NCM_CRYPT_WEAPI, body, sizeof body, hdrs, sizeof hdrs, url, sizeof url);
|
||||
if (rc != 0) { printf("req_build rc=%d\n", rc); return 1; }
|
||||
printf("URL=%s\nbody_len=%zu hdrs_len=%zu\n", url, strlen(body), strlen(hdrs));
|
||||
|
||||
ncm_http_req_t req; memset(&req, 0, sizeof req);
|
||||
req.url = url; req.method = "POST"; req.body = body; req.body_len = strlen(body);
|
||||
req.extra_hdrs = hdrs; req.verify_ca = NCM_HTTP_VERIFY_DEFAULT; req.timeout_ms = 15000;
|
||||
|
||||
static char respbuf[65536];
|
||||
ncm_http_resp_t resp;
|
||||
long t0 = (long)ncm_now_ms();
|
||||
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
||||
long dt = (long)ncm_now_ms() - t0;
|
||||
printf("http_request rc=%d elapsed_ms=%ld\n", rc, dt);
|
||||
printf("status=%d content_length=%zu chunked=%d\n", resp.status, resp.content_length, resp.chunked);
|
||||
printf("headers_len=%zu\n", resp.headers_len);
|
||||
printf("body_len=%zu\n", resp.body_len);
|
||||
if (resp.body_len) printf("body_head:\n%.*s\n", (int)(resp.body_len < 800 ? resp.body_len : 800), resp.body);
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
/* host_fullcookie.c — 用完整原始 cookie 直发 weapi account/get,验证服务器是否识别会话。
|
||||
* 绕过 ncm_req_build 的 ck_parse 截断:手写 Cookie 头 + 只用 ncm_crypto_weapi 生成 body。
|
||||
* 用法: ./host_fullcookie '<完整cookie>' [cacert]
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_crypto.h"
|
||||
#include "ncm_http.h"
|
||||
#include "ncm_common.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) { fprintf(stderr, "usage: %s '<cookie>'\n", argv[0]); return 1; }
|
||||
const char *cookie = argv[1];
|
||||
ncm_http_set_verify_default(1, argc > 2 ? argv[2] : NULL);
|
||||
|
||||
char params[1024], encsk[300];
|
||||
int rc = ncm_crypto_weapi("{\"csrf_token\":\"\"}", params, sizeof params, encsk, sizeof encsk);
|
||||
if (rc != NCM_OK) { printf("weapi crypto rc=%d\n", rc); return 1; }
|
||||
char p_enc[1100];
|
||||
if (ncm_url_encode(params, p_enc, sizeof p_enc) < 0) { printf("urlencode fail\n"); return 1; }
|
||||
char body[1400];
|
||||
snprintf(body, sizeof body, "params=%s&encSecKey=%s", p_enc, encsk);
|
||||
|
||||
char hdrs[2048];
|
||||
snprintf(hdrs, sizeof hdrs,
|
||||
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"Cookie: %s\r\n"
|
||||
"Referer: https://music.163.com", cookie);
|
||||
|
||||
ncm_http_req_t req; memset(&req, 0, sizeof req);
|
||||
req.url = "https://music.163.com/weapi/w/nuser/account/get";
|
||||
req.method = "POST"; req.body = body; req.body_len = strlen(body);
|
||||
req.extra_hdrs = hdrs; req.verify_ca = NCM_HTTP_VERIFY_DEFAULT; req.timeout_ms = 20000;
|
||||
char respbuf[65536]; ncm_http_resp_t resp;
|
||||
long t0 = (long)ncm_now_ms();
|
||||
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
||||
long dt = (long)ncm_now_ms() - t0;
|
||||
printf("rc=%d elapsed_ms=%ld status=%d cl=%zu chunked=%d body_len=%zu\n",
|
||||
rc, dt, resp.status, resp.content_length, resp.chunked, resp.body_len);
|
||||
if (resp.body_len) printf("body:\n%.*s\n", (int)resp.body_len, resp.body);
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
/* host_repro.c — 复现 weapi login_status_check 断帧挂死/断头问题(宿主)
|
||||
* 用与 App 完全相同的 core 库路径:ncm_req_build(WEAPI) -> ncm_http_request。
|
||||
* 用法: ./host_repro [cookie]
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_req.h"
|
||||
#include "ncm_http.h"
|
||||
#include "ncm_common.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void dump_resp(const char *tag, const ncm_http_resp_t *r) {
|
||||
printf("---- %s ----\n", tag);
|
||||
printf("status=%d content_length=%zu chunked=%d ct=%s\n",
|
||||
r->status, r->content_length, r->chunked,
|
||||
r->has_content_type ? r->content_type : "(none)");
|
||||
printf("headers(%zu):\n%s\n", r->headers_len, r->headers);
|
||||
printf("body_len=%zu\n", r->body_len);
|
||||
if (r->body_len) printf("body:\n%.*s\n", (int)r->body_len, r->body);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *cookie = argc > 1 ? argv[1] : "__csrf=";
|
||||
const char *device = "68DFA27096B7A5DC2CB393C77D8C194FDC0459B743081D091EBD";
|
||||
|
||||
/* 同 App:用全局默认 CA(verify=1 + 系统CA) */
|
||||
ncm_http_set_verify_default(1, NULL);
|
||||
|
||||
char body[NCM_REQ_BODY_MAX], hdrs[NCM_REQ_HDRS_MAX], url[NCM_REQ_URL_MAX];
|
||||
int rc = ncm_req_build(device, cookie, "/api/w/nuser/account/get", "{}",
|
||||
NCM_CRYPT_WEAPI, body, sizeof body,
|
||||
hdrs, sizeof hdrs, url, sizeof url);
|
||||
printf("req_build rc=%d\n", rc);
|
||||
if (rc != NCM_OK) return 1;
|
||||
printf("URL: %s\n", url);
|
||||
printf("BODY(len=%zu): %.*s\n\n", strlen(body), (int)strlen(body), body);
|
||||
printf("HDRS:\n%s\n\n", hdrs);
|
||||
|
||||
ncm_http_req_t req;
|
||||
memset(&req, 0, sizeof req);
|
||||
req.url = url;
|
||||
req.method = "POST";
|
||||
req.body = body;
|
||||
req.body_len = strlen(body);
|
||||
req.extra_hdrs = hdrs;
|
||||
req.verify_ca = NCM_HTTP_VERIFY_DEFAULT;
|
||||
req.timeout_ms = 15000;
|
||||
|
||||
char respbuf[8192];
|
||||
ncm_http_resp_t resp;
|
||||
long t0 = (long)ncm_now_ms();
|
||||
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
||||
long dt = (long)ncm_now_ms() - t0;
|
||||
printf("ncm_http_request rc=%d elapsed_ms=%ld\n", rc, dt);
|
||||
if (rc != NCM_OK) return 2;
|
||||
dump_resp("weapi account/get", &resp);
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
/* monotonic_test.c — 宿主验证 ncm_now_ms(REALTIME) 与 ncm_monotonic_ms(MONOTONIC)。
|
||||
* 演示:用墙钟算 deadline 在「NTP 拨钟回退 Δ」时,(deadline-now) 被放大成巨大 rem;
|
||||
* 用单调钟则不受影响(每次至多等剩余 deadline)。
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_common.h"
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void) {
|
||||
int64_t w0 = ncm_now_ms();
|
||||
int64_t m0 = ncm_monotonic_ms();
|
||||
usleep(50 * 1000);
|
||||
int64_t w1 = ncm_now_ms();
|
||||
int64_t m1 = ncm_monotonic_ms();
|
||||
printf("REALTIME advance: %lld ms\n", (long long)(w1 - w0));
|
||||
printf("MONOTONIC advance: %lld ms\n", (long long)(m1 - m0));
|
||||
if (w1 <= w0 || m1 <= m0) { printf("FAIL: clock not advancing\n"); return 1; }
|
||||
|
||||
/* 模拟 NTP 把墙钟拨回 Δ=6h 后,(deadline-now) 的变化 */
|
||||
int64_t base_w = w0;
|
||||
int64_t base_m = m0;
|
||||
int64_t tmo = 15000;
|
||||
int64_t dline_w = base_w + tmo, dline_m = base_m + tmo;
|
||||
int64_t jump = 6 * 3600 * 1000; /* 拨回 6 小时 */
|
||||
|
||||
/* REALTIME:now 回退 jump → rem 巨大 */
|
||||
int64_t now_w = base_w - jump;
|
||||
uint32_t rem_w = (uint32_t)(dline_w - now_w);
|
||||
/* MONOTONIC:now 不受跳变影响 */
|
||||
int64_t now_m = base_m;
|
||||
uint32_t rem_m = (uint32_t)(dline_m - now_m);
|
||||
printf("REALTIME rem after -6h jump = %u ms (%.1f h)\n", rem_w, rem_w / 3600000.0);
|
||||
printf("MONOTONIC rem after -6h jump = %u ms (%.2f s)\n", rem_m, rem_m / 1000.0);
|
||||
if (rem_w > rem_m) printf("OK: REALTIME overflow confirmed, MONOTONIC unaffected\n");
|
||||
else { printf("FAIL: expected REALTIME rem to blow up\n"); return 1; }
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* verify_home.c — 最终确认:复刻 App home 的完整链路(status_check 取 uid → user_playlist 拉歌单)。
|
||||
* 用与 App 完全相同的固定 core 库。真机只跑一次(风控:真实 cookie 请求严格受限)。
|
||||
* 用法: ./verify_home [data_dir] [cacert]
|
||||
*/
|
||||
#include "ncm.h"
|
||||
#include "ncm_http.h"
|
||||
#include "ncm_common.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *data_dir = argc > 1 ? argv[1] : "/usr/data/mnt/sd_0/MeowData/os.meow.ncm/data";
|
||||
const char *cacert = argc > 2 ? argv[2] : "/usr/data/mnt/sd_0/MeowAPPs/os.meow.ncm/cacert.pem";
|
||||
|
||||
ncm_ctx_t ctx;
|
||||
ncm_ctx_init(&ctx, data_dir);
|
||||
printf("device_id=%s cookie_len=%zu\n", ctx.device_id, strlen(ctx.cookie));
|
||||
ncm_http_set_verify_default(1, cacert);
|
||||
|
||||
/* 1) status_check 取 uid/nickname */
|
||||
char uid[32], nick[128];
|
||||
long t0 = (long)ncm_now_ms();
|
||||
int rc = ncm_login_status_check(ctx.device_id, ctx.cookie, uid, sizeof uid, nick, sizeof nick);
|
||||
long dt = (long)ncm_now_ms() - t0;
|
||||
printf("status_check rc=%d elapsed_ms=%ld uid=%s nick=%s\n", rc, dt, uid, nick);
|
||||
if (rc != NCM_OK) { printf("VERIFY FAIL: status_check not OK\n"); return 1; }
|
||||
|
||||
/* 2) user_playlist 拉歌单 */
|
||||
ncm_playlist_t pls[64];
|
||||
int n = 0;
|
||||
t0 = (long)ncm_now_ms();
|
||||
rc = ncm_api_user_playlist(ctx.device_id, ctx.cookie, uid, 30, 0, pls, 64, &n);
|
||||
dt = (long)ncm_now_ms() - t0;
|
||||
printf("user_playlist rc=%d elapsed_ms=%ld count=%d\n", rc, dt, n);
|
||||
if (rc != NCM_OK) { printf("VERIFY FAIL: user_playlist not OK\n"); return 1; }
|
||||
for (int i = 0; i < n && i < 8; i++)
|
||||
printf(" [%d] %s (%d tracks)\n", i, pls[i].name, pls[i].track_count);
|
||||
printf("VERIFY OK: user=%s playlists=%d\n", uid, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user