文档归位:雪猫 README 门面 + 公告/制作文档/发布工具并入仓库根
Release Tag / validate-version (push) Waiting to run

This commit is contained in:
天天的鸟蛋蛋 committed 2026-11-12 06:29:38 +08:00
1 parent b9655a5ab1
commit e292824161
122 files changed
+90002 -69

No files matched your search

+421
View File
@@ -0,0 +1,421 @@
/*
* winmm 侧加载 + V8 编译期源码改写
*
* 背景:QQ 9.9.37 的 asar 内 JS 全部逐文件加密,静态解不开;
* 但解密后的内容最终要交给 V8 编译 —— 在 ScriptCompiler::Compile*
* 入口拿到 v8::ScriptCompiler::Source*,其首字段就是 Local<String>。
*
* 能力:
* - 抓取:把每个被编译的脚本以明文落到 SC_V8_DIR
* - 改写:命中 SC_V8_MATCH 的脚本文本,前面插入 SC_V8_PATCH 的内容
* (用 String::NewFromUtf8 造新串,替换 Source::source_string_)
*
* ABI 备注:实测 MaybeLocal<T> 走 sret(rcx 为返回槽),故 Source* 在第三参;
* NewFromUtf8 同样按 sret 调用,可用 SC_V8_SRET=0 切换回 RAX 变体。
*/
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include "v8syms.h"
#include "MinHook.h"
extern void winmm_stubs_init(HMODULE real);
static HMODULE g_self = NULL;
#define MAX_PATCH (1 << 20)
/* ───────────── 配置 ───────────── */
static CRITICAL_SECTION g_lock;
static wchar_t g_dir[MAX_PATH] = L"D:\\SnowCat.Portable\\v8dump";
static volatile LONG g_seq = 0;
static volatile LONG g_calls = 0;
static int g_on = 0; /* 抓取开关(默认关,SC_V8_DUMP=1 才开) */
static int g_probe = 0;
static int g_off = 0; /* Source 内 Local<String> 偏移(实测 0) */
static int g_arg = 3; /* 哪个入参是 Source*(实测第三参) */
static int g_sret = 1; /* MaybeLocal 是否走 sret */
static char* g_patch = NULL; /* 要插入的 JS */
static int g_patchLen = 0;
static char g_match[128] = "internal_index";
static volatile LONG g_patched = 0;
/* ───────────── 日志 ───────────── */
static void logLine(const char* fmt, ...) {
char buf[4096];
va_list ap; va_start(ap, fmt);
int n = _vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
va_end(ap);
if (n < 0) return;
buf[n++] = '\n'; buf[n] = 0;
EnterCriticalSection(&g_lock);
wchar_t path[MAX_PATH];
_snwprintf(path, MAX_PATH, L"%s\\_index.txt", g_dir);
HANDLE h = CreateFileW(path, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
DWORD w = 0;
SetFilePointer(h, 0, NULL, FILE_END);
WriteFile(h, buf, (DWORD)n, &w, NULL);
CloseHandle(h);
}
LeaveCriticalSection(&g_lock);
}
/* ───────────── V8 符号 ───────────── */
typedef void* (*pfn_Isolate_GetCurrent)(void);
typedef void (*pfn_Utf8Value_ctor)(void* self, void* iso, void* localValue);
typedef const char* (*pfn_Utf8Value_ptr)(void* self);
typedef size_t (*pfn_Utf8Value_len)(void* self);
typedef void (*pfn_Utf8Value_dtor)(void* self);
typedef unsigned char (*pfn_Value_IsString)(void* self);
typedef void (*pfn_NewFromUtf8_sret)(void* sret, void* iso, const char* data, int type, int len);
typedef void* (*pfn_NewFromUtf8_rax)(void* iso, const char* data, int type, int len);
static pfn_Isolate_GetCurrent v8_GetCurrent;
static pfn_Utf8Value_ctor v8_Utf8Ctor;
static pfn_Utf8Value_ptr v8_Utf8Ptr;
static pfn_Utf8Value_len v8_Utf8Len;
static pfn_Utf8Value_dtor v8_Utf8Dtor;
static pfn_Value_IsString v8_IsString;
static pfn_NewFromUtf8_sret v8_NewFromUtf8;
static int readable(const void* p, size_t n) {
MEMORY_BASIC_INFORMATION mbi;
if (!p) return 0;
if (VirtualQuery(p, &mbi, sizeof(mbi)) != sizeof(mbi)) return 0;
if (mbi.State != MEM_COMMIT) return 0;
if (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) return 0;
return ((const BYTE*)p + n) <= ((const BYTE*)mbi.BaseAddress + mbi.RegionSize);
}
static void writeDump(const char* txt, size_t len, const char* tag) {
LONG idx = InterlockedIncrement(&g_seq);
wchar_t path[MAX_PATH];
_snwprintf(path, MAX_PATH, L"%s\\%lu_%05ld.js", g_dir, GetCurrentProcessId(), idx);
HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
DWORD w = 0;
WriteFile(h, txt, (DWORD)len, &w, NULL);
CloseHandle(h);
}
char head[100];
size_t i = 0;
for (; i < sizeof(head) - 1 && i < len; i++) {
char c = txt[i];
head[i] = (c >= 32 && c < 127) ? c : '.';
}
head[i] = 0;
logLine("%lu\t%s\tlen=%llu\t%s", GetCurrentProcessId(), tag, (unsigned long long)len, head);
}
/* 造一个 v8::String,返回 Local<String> 的裸指针 */
static void* makeString(void* iso, const char* data, int len) {
if (!v8_NewFromUtf8) return NULL;
unsigned char slot[64];
memset(slot, 0, sizeof(slot));
if (g_sret) {
v8_NewFromUtf8(slot, iso, data, 0, len);
return *(void**)slot;
} else {
pfn_NewFromUtf8_rax fn = (pfn_NewFromUtf8_rax)v8_NewFromUtf8;
return fn(iso, data, 0, len);
}
}
/* 命中则把源码替换成 patch + 原文 */
static int tryPatch(void* src, const char* txt, size_t len) {
if (!g_patch || g_patchLen <= 0 || !txt || len == 0) return 0;
if (len > (4u << 20)) return 0;
if (!strstr(txt, g_match)) return 0;
void* iso = v8_GetCurrent ? v8_GetCurrent() : NULL;
if (!iso) return 0;
int total = g_patchLen + 1 + (int)len;
char* merged = (char*)malloc((size_t)total + 1);
if (!merged) return 0;
memcpy(merged, g_patch, (size_t)g_patchLen);
merged[g_patchLen] = '\n';
memcpy(merged + g_patchLen + 1, txt, len);
merged[total] = 0;
void* s = makeString(iso, merged, total);
free(merged);
if (!s) { logLine("%lu\tPATCHFAIL\tmakeString null", GetCurrentProcessId()); return 0; }
*(void**)((char*)src + g_off) = s;
InterlockedIncrement(&g_patched);
logLine("%lu\tPATCHED\thit=%s origLen=%llu newLen=%d", GetCurrentProcessId(), g_match,
(unsigned long long)len, total);
return 1;
}
static unsigned char* holderPtr(void) {
static __thread unsigned char holder[128];
return holder;
}
static void handle(void* a1, void* a2, void* a3, void* a4, void* a5, const char* tag) {
if (!g_on && !g_patch) return;
LONG call = InterlockedIncrement(&g_calls);
if (g_probe) {
if (call > 12) return;
logLine("%lu\tPROBE\t%s\tcall=%ld a1=%p a2=%p a3=%p a4=%p a5=%p",
GetCurrentProcessId(), tag, call, a1, a2, a3, a4, a5);
return;
}
void* bases[5] = { a1, a2, a3, a4, a5 };
if (g_arg < 1 || g_arg > 5) return;
void* src = bases[g_arg - 1];
if (!src) return;
const char* txt = NULL; size_t len = 0;
unsigned char* h = holderPtr();
memset(h, 0, 128);
void* iso = v8_GetCurrent ? v8_GetCurrent() : NULL;
if (iso) {
void* cand = NULL;
if (readable(src, (size_t)g_off + 8)) cand = *(void**)((char*)src + g_off);
if (cand && !((uintptr_t)cand & 7) && readable(cand, 16) && v8_IsString && v8_IsString(cand)) {
v8_Utf8Ctor(h, iso, cand);
const char* t = v8_Utf8Ptr(h);
size_t l = v8_Utf8Len(h);
if (t && l > 0 && l < (8u << 20)) { txt = t; len = l; }
}
}
if (txt) {
if (g_on) writeDump(txt, len, tag);
tryPatch(src, txt, len);
}
if (iso && v8_Utf8Len) v8_Utf8Dtor(h);
}
/* ───────────── Detour(纯 void* 透传) ───────────── */
typedef uint64_t (*fn5)(void*, void*, void*, void*, void*);
typedef uint64_t (*fn8)(void*, void*, void*, void*, void*, void*, void*, void*);
static fn5 orig_Compile;
static fn5 orig_CompileUnbound;
static fn5 orig_CompileModule;
static fn8 orig_CompileFunction;
static uint64_t det_Compile(void* a1, void* a2, void* a3, void* a4, void* a5) {
handle(a1, a2, a3, a4, a5, "Compile");
return orig_Compile(a1, a2, a3, a4, a5);
}
static uint64_t det_CompileUnbound(void* a1, void* a2, void* a3, void* a4, void* a5) {
handle(a1, a2, a3, a4, a5, "Unbound");
return orig_CompileUnbound(a1, a2, a3, a4, a5);
}
static uint64_t det_CompileModule(void* a1, void* a2, void* a3, void* a4, void* a5) {
handle(a1, a2, a3, a4, a5, "Module");
return orig_CompileModule(a1, a2, a3, a4, a5);
}
static uint64_t det_CompileFunction(void* a1, void* a2, void* a3, void* a4,
void* a5, void* a6, void* a7, void* a8) {
handle(a1, a2, a3, a4, a5, "Function");
return orig_CompileFunction(a1, a2, a3, a4, a5, a6, a7, a8);
}
/* ───────────── 初始化 ───────────── */
static int loadPatchFile(const wchar_t* path) {
HANDLE h = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) return 0;
DWORD sz = GetFileSize(h, NULL);
if (sz == 0 || sz > MAX_PATCH) { CloseHandle(h); return 0; }
g_patch = (char*)malloc(sz + 1);
if (!g_patch) { CloseHandle(h); return 0; }
DWORD rd = 0;
ReadFile(h, g_patch, sz, &rd, NULL);
CloseHandle(h);
g_patch[rd] = 0;
g_patchLen = (int)rd;
return 1;
}
/* ── 进程内 dlopen 自检(排查 wrapper.node 加载失败) ── */
static DWORD WINAPI probeThread(LPVOID param) {
wchar_t path[MAX_PATH];
DWORD delay = 8000;
if (!GetEnvironmentVariableW(L"SC_TEST_LOAD", path, MAX_PATH)) return 0;
{
wchar_t d[32];
if (GetEnvironmentVariableW(L"SC_TEST_DELAY", d, 32) > 0) delay = (DWORD)wcstoul(d, NULL, 10);
}
Sleep(delay);
wchar_t appDir[MAX_PATH];
wcscpy(appDir, path);
wchar_t* s1 = wcsrchr(appDir, L'\\');
if (s1) *s1 = 0;
/* 时间轴测试:在不同时刻尝试加载,看是否随时间从成功变成 193 */
if (GetEnvironmentVariableW(L"SC_TEST_SERIES", path, MAX_PATH) > 0) {
const DWORD marks[] = { 1200, 2500, 4000, 6000, 9000, 13000, 18000 };
DWORD prev = 0;
for (int i = 0; i < 7; i++) {
Sleep(marks[i] - prev); prev = marks[i];
HMODULE m = LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
logLine("%lu\tSERIES\tt=%lu ok=%d err=%lu", GetCurrentProcessId(), marks[i], m != NULL, GetLastError());
if (m) FreeLibrary(m);
}
return 0;
}
HMODULE pre = GetModuleHandleW(L"wrapper.node");
logLine("%lu\tLOADTEST\talready=%p", GetCurrentProcessId(), (void*)pre);
SetDllDirectoryW(appDir);
HMODULE h = LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
logLine("%lu\tLOADTEST\tpath=%ls ok=%d err=%lu", GetCurrentProcessId(), path, h != NULL, GetLastError());
if (!h) {
HMODULE h2 = LoadLibraryW(path);
logLine("%lu\tLOADTEST2\tplain ok=%d err=%lu", GetCurrentProcessId(), h2 != NULL, GetLastError());
}
/* 依赖扫描:把 app 目录里的 dll/node 逐个加载,暴露 193 的真凶 */
if (GetEnvironmentVariableW(L"SC_TEST_DEPS", appDir, MAX_PATH) > 0) {
wchar_t pat[MAX_PATH];
_snwprintf(pat, MAX_PATH, L"%s\\*.d*", appDir);
WIN32_FIND_DATAW fd;
HANDLE ff = FindFirstFileW(pat, &fd);
int tried = 0, failed = 0;
if (ff != INVALID_HANDLE_VALUE) {
do {
const wchar_t* nm = fd.cFileName;
size_t L = wcslen(nm);
if (L < 5) continue;
if (_wcsicmp(nm, L"wrapper.node") == 0 || _wcsicmp(nm, L"major.node") == 0) continue;
wchar_t full[MAX_PATH];
_snwprintf(full, MAX_PATH, L"%s\\%s", appDir, nm);
HMODULE m = LoadLibraryExW(full, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
tried++;
if (!m) {
DWORD e = GetLastError();
failed++;
logLine("%lu\tDEPFAIL\t%ls\terr=%lu", GetCurrentProcessId(), nm, e);
}
} while (FindNextFileW(ff, &fd));
FindClose(ff);
}
logLine("%lu\tDEPSUM\ttried=%d failed=%d", GetCurrentProcessId(), tried, failed);
/* 依赖预载完,再试一次 wrapper.node */
HMODULE h3 = LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
logLine("%lu\tLOADTEST3\tafter-deps ok=%d err=%lu", GetCurrentProcessId(), h3 != NULL, GetLastError());
}
return 0;
}
static DWORD WINAPI initThread(LPVOID param) {
(void)param;
InitializeCriticalSection(&g_lock);
wchar_t patchPath[MAX_PATH] = { 0 };
wchar_t match[128] = { 0 };
{
wchar_t v[32];
if (GetEnvironmentVariableW(L"SC_V8_DUMP", v, 32) > 0 && v[0] == L'1') g_on = 1;
if (GetEnvironmentVariableW(L"SC_V8_PROBE", v, 32) > 0 && v[0] == L'1') g_probe = 1;
if (GetEnvironmentVariableW(L"SC_V8_OFF", v, 32) > 0) g_off = (int)wcstol(v, NULL, 0);
if (GetEnvironmentVariableW(L"SC_V8_ARG", v, 32) > 0) g_arg = (int)wcstol(v, NULL, 0);
if (GetEnvironmentVariableW(L"SC_V8_SRET", v, 32) > 0) g_sret = (v[0] != L'0');
GetEnvironmentVariableW(L"SC_V8_DIR", g_dir, MAX_PATH);
GetEnvironmentVariableW(L"SC_V8_PATCH", patchPath, MAX_PATH);
if (GetEnvironmentVariableW(L"SC_V8_MATCH", match, 128) > 0) {
WideCharToMultiByte(CP_UTF8, 0, match, -1, g_match, sizeof(g_match), NULL, NULL);
}
CreateDirectoryW(g_dir, NULL);
}
wchar_t sys[MAX_PATH];
GetSystemDirectoryW(sys, MAX_PATH);
wcscat(sys, L"\\winmm.dll");
HMODULE real = LoadLibraryW(sys);
if (!real) { logLine("-1\tERR\tload system winmm failed"); return 1; }
winmm_stubs_init(real);
if (!patchPath[0] && g_self) {
wchar_t dllPath[MAX_PATH];
if (GetModuleFileNameW(g_self, dllPath, MAX_PATH)) {
wchar_t* sl = wcsrchr(dllPath, L'\\');
if (sl) {
*sl = 0;
_snwprintf(patchPath, MAX_PATH, L"%s\\snowcat-boot.js", dllPath);
}
}
}
int havePatch = 0;
if (patchPath[0]) {
havePatch = loadPatchFile(patchPath);
logLine("%lu\tCFG\tpatch=%d bytes=%d match=%s", GetCurrentProcessId(), havePatch, g_patchLen, g_match);
}
HMODULE qqnt = NULL;
for (int i = 0; i < 600; i++) {
qqnt = GetModuleHandleW(L"QQNT.dll");
if (qqnt) break;
Sleep(50);
}
if (!qqnt) { logLine("-1\tERR\tQQNT.dll never loaded"); return 1; }
v8_GetCurrent = (pfn_Isolate_GetCurrent)GetProcAddress(qqnt, SYM_ISOLATE_GET_CURRENT);
v8_Utf8Ctor = (pfn_Utf8Value_ctor)GetProcAddress(qqnt, SYM_UTF8VALUE_CTOR);
v8_Utf8Ptr = (pfn_Utf8Value_ptr)GetProcAddress(qqnt, SYM_UTF8VALUE_PTR);
v8_Utf8Len = (pfn_Utf8Value_len)GetProcAddress(qqnt, SYM_UTF8VALUE_LEN);
v8_Utf8Dtor = (pfn_Utf8Value_dtor)GetProcAddress(qqnt, SYM_UTF8VALUE_DTOR);
v8_IsString = (pfn_Value_IsString)GetProcAddress(qqnt, "?IsString@Value@v8@@QEBA_NXZ");
v8_NewFromUtf8 = (pfn_NewFromUtf8_sret)GetProcAddress(qqnt, SYM_STRING_NEWFROMUTF8);
if (!v8_GetCurrent || !v8_Utf8Ctor || !v8_Utf8Ptr || !v8_Utf8Len || !v8_IsString) {
logLine("-1\tERR\tv8 symbols missing");
return 1;
}
if (MH_Initialize() != MH_OK) { logLine("-1\tERR\tMH_Initialize failed"); return 1; }
struct { const char* name; void* det; void** orig; const char* tag; } hooks[] = {
{ SYM_COMPILE, (void*)det_Compile, (void**)&orig_Compile, "Compile" },
{ SYM_COMPILE_UNBOUND, (void*)det_CompileUnbound, (void**)&orig_CompileUnbound, "Unbound" },
{ SYM_COMPILE_MODULE, (void*)det_CompileModule, (void**)&orig_CompileModule, "Module" },
{ SYM_COMPILE_FUNCTION, (void*)det_CompileFunction, (void**)&orig_CompileFunction, "Function" },
};
int ok = 0;
for (int i = 0; i < 4; i++) {
void* p = (void*)GetProcAddress(qqnt, hooks[i].name);
if (!p) { logLine("%lu\tMISS\t%s", GetCurrentProcessId(), hooks[i].tag); continue; }
if (MH_CreateHook(p, hooks[i].det, hooks[i].orig) == MH_OK) ok++;
else logLine("%lu\tHOOKFAIL\t%s", GetCurrentProcessId(), hooks[i].tag);
}
if (!ok) { logLine("-1\tERR\tno hook installed"); return 1; }
MH_EnableHook(MH_ALL_HOOKS);
logLine("%lu\tREADY\thooks=%d dump=%d patch=%d off=%d arg=%d sret=%d",
GetCurrentProcessId(), ok, g_on, havePatch, g_off, g_arg, g_sret);
return 0;
}
BOOL WINAPI DllMain(HMODULE module, DWORD reason, LPVOID reserved) {
(void)reserved;
if (reason == DLL_PROCESS_ATTACH) {
g_self = module;
DisableThreadLibraryCalls(module);
HANDLE th = CreateThread(NULL, 0, initThread, NULL, 0, NULL);
if (th) CloseHandle(th);
HANDLE th2 = CreateThread(NULL, 0, probeThread, NULL, 0, NULL);
if (th2) CloseHandle(th2);
}
return TRUE;
}