Files
MeowOS-For-R3P2/patch_v2/ncm_app/demo/https_unikey.c
T

257 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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;
}