This commit is contained in:
1 parent
b9655a5ab1
commit
e292824161
122 files changed
+90002
-69
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SnowCat 引导注入
|
||||
* 注入点:QQ 主进程 application.asar/app_launcher/index.js 的模块作用域
|
||||
* 手段:winmm 侧加载 + hook v8::ScriptCompiler::Compile,在源码编译前替换
|
||||
*/
|
||||
;(() => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
let logFile = path.join(os.tmpdir(), 'snowcat-boot.log');
|
||||
const log = (m) => {
|
||||
try {
|
||||
fs.appendFileSync(logFile, new Date().toISOString() + ' [pid ' + process.pid + '] ' + m + '\n');
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
try {
|
||||
// 模块路径形如 ...\resources\app\application.asar\app_launcher,
|
||||
// 需要逐级上溯才能到便携版根目录
|
||||
let dir = __dirname;
|
||||
let target = null;
|
||||
for (let i = 0; i < 8 && dir; i++) {
|
||||
const cand = path.join(dir, 'napcat.mjs');
|
||||
if (fs.existsSync(cand)) { target = cand; break; }
|
||||
const up = path.dirname(dir);
|
||||
if (up === dir) break;
|
||||
dir = up;
|
||||
}
|
||||
|
||||
if (!target) { log('napcat.mjs not found from ' + __dirname); return; }
|
||||
|
||||
logFile = path.join(path.dirname(target), 'nc-boot.log');
|
||||
log('injected; electron=' + (process.versions.electron || '?') + ' node=' + process.versions.node);
|
||||
log('napcat=' + target);
|
||||
|
||||
import(require('url').pathToFileURL(target).href)
|
||||
.then(() => log('napcat imported ok'))
|
||||
.catch((e) => log('import failed: ' + ((e && e.stack) || e)));
|
||||
} catch (e) {
|
||||
log('outer error: ' + ((e && e.stack) || e));
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* snowcat-hook.dll —— 观测版
|
||||
*
|
||||
* 目的:搞清楚 QQ 9.9.37 到底是谁、在什么时机读取 resources\app\package.json,
|
||||
* 以及读取走的是 kernel32!CreateFileW 还是 ntdll!NtCreateFile(NT 层)。
|
||||
*
|
||||
* 只记录,不改内容。结果写到 D:\SnowCat.Portable\hook-trace.txt
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include "MinHook.h"
|
||||
|
||||
#define TRACE_PATH L"D:\\SnowCat.Portable\\hook-trace.txt"
|
||||
|
||||
static HANDLE (WINAPI *origCreateFileW)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) = NULL;
|
||||
static NTSTATUS (NTAPI *origNtOpenFile)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, ULONG, ULONG) = NULL;
|
||||
static NTSTATUS (NTAPI *origNtCreateFile)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK,
|
||||
PLARGE_INTEGER, ULONG, ULONG, ULONG, ULONG, PVOID, ULONG) = NULL;
|
||||
|
||||
static CRITICAL_SECTION g_lock;
|
||||
static volatile LONG g_subsReady = 0;
|
||||
|
||||
static void trace(const wchar_t* api, const wchar_t* path) {
|
||||
EnterCriticalSection(&g_lock);
|
||||
HANDLE h = CreateFileW(TRACE_PATH, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
char buf[2048];
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, path, -1, buf, sizeof(buf) - 1, NULL, NULL);
|
||||
if (n <= 0) { buf[0] = 0; }
|
||||
char line[2304];
|
||||
int len = snprintf(line, sizeof(line), "%s\t%s\n", (const char*)api, buf);
|
||||
DWORD w = 0;
|
||||
SetFilePointer(h, 0, NULL, FILE_END);
|
||||
WriteFile(h, line, (DWORD)len, &w, NULL);
|
||||
CloseHandle(h);
|
||||
}
|
||||
LeaveCriticalSection(&g_lock);
|
||||
}
|
||||
|
||||
static int interesting(const wchar_t* p) {
|
||||
if (!p) return 0;
|
||||
if (wcsstr(p, L"package.json")) return 1;
|
||||
if (wcsstr(p, L"application.asar")) return 1;
|
||||
if (wcsstr(p, L"loadNapCat")) return 1;
|
||||
if (wcsstr(p, L"app_launcher")) return 1;
|
||||
if (wcsstr(p, L"qqnt")) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static wchar_t g_fakePkg[MAX_PATH];
|
||||
static wchar_t g_patchedAsar[MAX_PATH];
|
||||
static wchar_t g_qqDir[MAX_PATH];
|
||||
|
||||
static void buildSubstitutes(void) {
|
||||
// QQ 目录 = QQ.exe 所在目录(反射注入器是父进程拉起的,模块表里主 exe 可查)
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, exePath, MAX_PATH);
|
||||
wchar_t* slash = wcsrchr(exePath, L'\\');
|
||||
if (slash) *slash = 0;
|
||||
wcscpy(g_qqDir, exePath);
|
||||
|
||||
// 假 package.json:优先 snowcat-fake-pkg.json(真字段+main 覆写合并版)
|
||||
wchar_t fakePath[MAX_PATH];
|
||||
swprintf(fakePath, MAX_PATH, L"%s\\snowcat-fake-pkg.json", g_qqDir);
|
||||
WIN32_FILE_ATTRIBUTE_DATA fa2;
|
||||
if (GetFileAttributesExW(fakePath, GetFileExInfoStandard, &fa2)) {
|
||||
wcscpy(g_fakePkg, fakePath);
|
||||
} else {
|
||||
DWORD n = GetEnvironmentVariableW(L"NAPCAT_PATCH_PACKAGE", g_fakePkg, MAX_PATH);
|
||||
if (n == 0) {
|
||||
swprintf(g_fakePkg, MAX_PATH, L"%s\\qqnt.json", g_qqDir);
|
||||
}
|
||||
}
|
||||
// patched asar:snowcat-patched.asar(若存在)
|
||||
swprintf(g_patchedAsar, MAX_PATH, L"%s\\snowcat-patched.asar", g_qqDir);
|
||||
WIN32_FILE_ATTRIBUTE_DATA fa;
|
||||
if (!GetFileAttributesExW(g_patchedAsar, GetFileExInfoStandard, &fa)) {
|
||||
g_patchedAsar[0] = 0; // 无副本则不替换 asar
|
||||
}
|
||||
}
|
||||
|
||||
static int isPkg(const wchar_t* p) {
|
||||
return p && wcsstr(p, L"resources") && wcsstr(p, L"package.json");
|
||||
}
|
||||
static int isAsar(const wchar_t* p) {
|
||||
return p && wcsstr(p, L"application.asar");
|
||||
}
|
||||
|
||||
static HANDLE WINAPI detourCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
if (interesting(lpFileName)) trace(L"CreateFileW", lpFileName);
|
||||
if (g_subsReady) {
|
||||
if (isPkg(lpFileName)) return origCreateFileW(g_fakePkg, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
if (isAsar(lpFileName) && g_patchedAsar[0]) return origCreateFileW(g_patchedAsar, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
return origCreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
static NTSTATUS NTAPI detourNtCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
|
||||
PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess,
|
||||
ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength) {
|
||||
if (g_subsReady && ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
|
||||
wchar_t* bp = ObjectAttributes->ObjectName->Buffer;
|
||||
/* NT 路径 \??\C:\... —— 匹配用宽松 wcsstr 即可 */
|
||||
if (isPkg(bp)) {
|
||||
static UNICODE_STRING us;
|
||||
static wchar_t ntpath[MAX_PATH + 8];
|
||||
swprintf(ntpath, MAX_PATH + 8, L"\\\\??\\\\%s", g_fakePkg);
|
||||
us.Buffer = ntpath;
|
||||
us.Length = (USHORT)(wcslen(ntpath) * 2);
|
||||
us.MaximumLength = us.Length + 2;
|
||||
ObjectAttributes->ObjectName = &us;
|
||||
trace(L"NtCreateFile→FAKE-PKG", bp);
|
||||
return origNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
|
||||
}
|
||||
if (isAsar(bp) && g_patchedAsar[0]) {
|
||||
static UNICODE_STRING us2;
|
||||
static wchar_t ntpath2[MAX_PATH + 8];
|
||||
swprintf(ntpath2, MAX_PATH + 8, L"\\\\??\\\\%s", g_patchedAsar);
|
||||
us2.Buffer = ntpath2;
|
||||
us2.Length = (USHORT)(wcslen(ntpath2) * 2);
|
||||
us2.MaximumLength = us2.Length + 2;
|
||||
ObjectAttributes->ObjectName = &us2;
|
||||
trace(L"NtCreateFile→PATCHED-ASAR", bp);
|
||||
return origNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
|
||||
}
|
||||
}
|
||||
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
|
||||
if (interesting(ObjectAttributes->ObjectName->Buffer)) {
|
||||
trace(L"NtCreateFile", ObjectAttributes->ObjectName->Buffer);
|
||||
}
|
||||
}
|
||||
return origNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize,
|
||||
FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
|
||||
}
|
||||
|
||||
static NTSTATUS NTAPI detourNtOpenFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock,
|
||||
ULONG ShareAccess, ULONG OpenOptions) {
|
||||
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
|
||||
trace(L"NtOpenFile", ObjectAttributes->ObjectName->Buffer);
|
||||
}
|
||||
return origNtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static DWORD WINAPI initThread(LPVOID param) {
|
||||
(void)param;
|
||||
InitializeCriticalSection(&g_lock);
|
||||
buildSubstitutes();
|
||||
g_subsReady = 1;
|
||||
trace(L"===", L"injected");
|
||||
|
||||
if (MH_Initialize() != MH_OK) { trace(L"===ERR", L"MH_Initialize failed"); return 1; }
|
||||
|
||||
HMODULE k32 = GetModuleHandleW(L"kernel32.dll");
|
||||
HMODULE nt = GetModuleHandleW(L"ntdll.dll");
|
||||
void* pCreateFileW = (void*)GetProcAddress(k32, "CreateFileW");
|
||||
void* pNtCreateFile = (void*)GetProcAddress(nt, "NtCreateFile");
|
||||
|
||||
if (pCreateFileW && MH_CreateHook(pCreateFileW, &detourCreateFileW, (LPVOID*)&origCreateFileW) == MH_OK)
|
||||
trace(L"===", L"hook CreateFileW ok");
|
||||
if (pNtCreateFile && MH_CreateHook(pNtCreateFile, &detourNtCreateFile, (LPVOID*)&origNtCreateFile) == MH_OK)
|
||||
trace(L"===", L"hook NtCreateFile ok");
|
||||
|
||||
void* pNtOpenFile = (void*)GetProcAddress(nt, "NtOpenFile");
|
||||
if (pNtOpenFile && MH_CreateHook(pNtOpenFile, &detourNtOpenFile, (LPVOID*)&origNtOpenFile) == MH_OK)
|
||||
trace(L"===", L"hook NtOpenFile ok");
|
||||
|
||||
MH_EnableHook(MH_ALL_HOOKS);
|
||||
trace(L"===", L"hooks enabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
|
||||
(void)reserved;
|
||||
if (reason == DLL_PROCESS_ATTACH) {
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
HANDLE th = CreateThread(NULL, 0, initThread, NULL, 0, NULL);
|
||||
if (th) CloseHandle(th);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* snowcat-inject.exe —— 挂起态注入
|
||||
*
|
||||
* 用法: snowcat-inject.exe <目标exe> <要注入的dll> [附加命令行]
|
||||
* 原理: CreateProcessW(CREATE_SUSPENDED) -> 远程 LoadLibraryW -> ResumeThread
|
||||
* 与 NapCatWinBootMain 相同的注入时机(主线程还没跑任何一条指令)
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc < 3) {
|
||||
wprintf(L"usage: %s <target.exe> <hook.dll> [extra args]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const wchar_t* exeArg = argv[1];
|
||||
const wchar_t* dllArg = argv[2];
|
||||
|
||||
/* 统一成正斜杠/反斜杠都接受:CreateProcessW 对 / 敏感 */
|
||||
static wchar_t exeBuf[32768];
|
||||
static wchar_t dllBuf[32768];
|
||||
wcsncpy(exeBuf, exeArg, 32767); exeBuf[32767] = 0;
|
||||
wcsncpy(dllBuf, dllArg, 32767); dllBuf[32767] = 0;
|
||||
for (wchar_t* p = exeBuf; *p; ++p) if (*p == L'/') *p = L'\\';
|
||||
for (wchar_t* p = dllBuf; *p; ++p) if (*p == L'/') *p = L'\\';
|
||||
const wchar_t* exe = exeBuf;
|
||||
const wchar_t* dll = dllBuf;
|
||||
|
||||
wchar_t cmd[32768];
|
||||
if (argc > 3) {
|
||||
_snwprintf(cmd, 32768, L"\"%s\" %s", exe, argv[3]);
|
||||
} else {
|
||||
_snwprintf(cmd, 32768, L"\"%s\"", exe);
|
||||
}
|
||||
|
||||
wchar_t exeDir[MAX_PATH];
|
||||
wcsncpy(exeDir, exe, MAX_PATH - 1);
|
||||
exeDir[MAX_PATH - 1] = 0;
|
||||
wchar_t* slash = wcsrchr(exeDir, L'\\');
|
||||
if (slash) *slash = 0; else exeDir[0] = 0;
|
||||
|
||||
wprintf(L"[inject] exe=%s\n[inject] dll=%s\n[inject] cmd=%s\n", exe, dll, cmd);
|
||||
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (!CreateProcessW(exe, cmd, NULL, NULL, FALSE, CREATE_SUSPENDED,
|
||||
NULL, exeDir[0] ? exeDir : NULL, &si, &pi)) {
|
||||
wprintf(L"[inject] CreateProcess failed: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
wprintf(L"[inject] suspended pid=%lu\n", pi.dwProcessId);
|
||||
|
||||
SIZE_T len = (wcslen(dll) + 1) * sizeof(wchar_t);
|
||||
LPVOID remote = VirtualAllocEx(pi.hProcess, NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remote) {
|
||||
wprintf(L"[inject] VirtualAllocEx failed: %lu\n", GetLastError());
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
return 1;
|
||||
}
|
||||
if (!WriteProcessMemory(pi.hProcess, remote, dll, len, NULL)) {
|
||||
wprintf(L"[inject] WriteProcessMemory failed: %lu\n", GetLastError());
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
HMODULE k32 = GetModuleHandleW(L"kernel32.dll");
|
||||
LPTHREAD_START_ROUTINE pLoadLibraryW =
|
||||
(LPTHREAD_START_ROUTINE)GetProcAddress(k32, "LoadLibraryW");
|
||||
|
||||
wprintf(L"[inject] local kernel32=%p LoadLibraryW=%p\n", (void*)k32, (void*)pLoadLibraryW);
|
||||
|
||||
/* 对比目标进程里 kernel32 的基址:若不同,说明不能用本地地址做远程线程入口 */
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pi.dwProcessId);
|
||||
if (snap != INVALID_HANDLE_VALUE) {
|
||||
MODULEENTRY32W me;
|
||||
ZeroMemory(&me, sizeof(me));
|
||||
me.dwSize = sizeof(me);
|
||||
if (Module32FirstW(snap, &me)) {
|
||||
do {
|
||||
if (_wcsicmp(me.szModule, L"kernel32.dll") == 0) {
|
||||
wprintf(L"[inject] remote kernel32=%p (delta=%lld)\n",
|
||||
(void*)me.modBaseAddr, (long long)((char*)me.modBaseAddr - (char*)k32));
|
||||
break;
|
||||
}
|
||||
} while (Module32NextW(snap, &me));
|
||||
}
|
||||
CloseHandle(snap);
|
||||
}
|
||||
|
||||
HANDLE th = CreateRemoteThread(pi.hProcess, NULL, 0, pLoadLibraryW, remote, 0, NULL);
|
||||
if (!th) {
|
||||
wprintf(L"[inject] CreateRemoteThread failed: %lu\n", GetLastError());
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
return 1;
|
||||
}
|
||||
WaitForSingleObject(th, 15000);
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeThread(th, &exitCode);
|
||||
wprintf(L"[inject] LoadLibraryW remote result=0x%lX (0=fail)\n", exitCode);
|
||||
CloseHandle(th);
|
||||
|
||||
ResumeThread(pi.hThread);
|
||||
wprintf(L"[inject] resumed\n");
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* snowcat-reflect.exe —— 反射式 DLL 注入(绕过 LdrLoadDll 拦截)
|
||||
*
|
||||
* 不调用目标进程的 LoadLibrary:自己解析 PE、映射节、修重定位、填 IAT,
|
||||
* 最后用一小段 shellcode 调 DllMain(DLL_PROCESS_ATTACH)。
|
||||
*
|
||||
* 用法: snowcat-reflect.exe <target.exe> <payload.dll> [extra args]
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void die(const wchar_t* msg, DWORD err) {
|
||||
wprintf(L"[reflect] %s (err=%lu)\n", msg, err);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
/* 本地加载同名 DLL 取函数地址:系统 DLL 在同 session 内与目标进程基址一致 */
|
||||
static ULONG_PTR localResolve(const char* dllName, const char* funcName) {
|
||||
wchar_t wdll[MAX_PATH];
|
||||
MultiByteToWideChar(CP_ACP, 0, dllName, -1, wdll, MAX_PATH);
|
||||
HMODULE h = LoadLibraryW(wdll); /* 在我们自己进程里加载,不受 QQ 限制 */
|
||||
if (!h) return 0;
|
||||
return (ULONG_PTR)GetProcAddress(h, funcName);
|
||||
}
|
||||
|
||||
|
||||
/* RVA -> file offset(按节表转换) */
|
||||
static DWORD rva2off(BYTE* dllData, IMAGE_NT_HEADERS64* nt, DWORD rva) {
|
||||
IMAGE_SECTION_HEADER* sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
|
||||
if (rva >= sec[i].VirtualAddress &&
|
||||
rva < sec[i].VirtualAddress + sec[i].Misc.VirtualSize) {
|
||||
return sec[i].PointerToRawData + (rva - sec[i].VirtualAddress);
|
||||
}
|
||||
}
|
||||
return rva; /* fallback */
|
||||
}
|
||||
|
||||
int main(int argc, char** argvA) {
|
||||
/* ANSI argv → wchar 转换(mingw 无 wmain) */
|
||||
static wchar_t* wargv[16];
|
||||
static wchar_t wstore[16][32768];
|
||||
if (argc > 16) argc = 16;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
MultiByteToWideChar(CP_ACP, 0, argvA[i], -1, wstore[i], 32767);
|
||||
wargv[i] = wstore[i];
|
||||
}
|
||||
wchar_t** argv = wargv;
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
if (argc < 3) {
|
||||
wprintf(L"usage: %s <target.exe> <payload.dll> [extra args]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static wchar_t exeBuf[32768], dllBuf[32768], workBuf[32768];
|
||||
wcsncpy(exeBuf, argv[1], 32767); exeBuf[32767] = 0;
|
||||
wcsncpy(dllBuf, argv[2], 32767); dllBuf[32767] = 0;
|
||||
for (wchar_t* p = exeBuf; *p; ++p) if (*p == L'/') *p = L'\\';
|
||||
for (wchar_t* p = dllBuf; *p; ++p) if (*p == L'/') *p = L'\\';
|
||||
|
||||
/* ── 读 payload ── */
|
||||
HANDLE hf = CreateFileW(dllBuf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hf == INVALID_HANDLE_VALUE) die(L"open payload failed", GetLastError());
|
||||
DWORD fsize = GetFileSize(hf, NULL);
|
||||
BYTE* dllData = (BYTE*)VirtualAlloc(NULL, fsize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
DWORD read = 0;
|
||||
ReadFile(hf, dllData, fsize, &read, NULL);
|
||||
CloseHandle(hf);
|
||||
|
||||
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)dllData;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) die(L"bad DOS header", 0);
|
||||
IMAGE_NT_HEADERS64* nt = (IMAGE_NT_HEADERS64*)(dllData + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) die(L"bad NT header", 0);
|
||||
if (nt->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) die(L"payload is not x64", 0);
|
||||
|
||||
DWORD sizeOfImage = nt->OptionalHeader.SizeOfImage;
|
||||
ULONG_PTR preferredBase = nt->OptionalHeader.ImageBase;
|
||||
wprintf(L"[reflect] image=%lu KB base=%p entry=%lX\n",
|
||||
sizeOfImage / 1024, (void*)preferredBase, nt->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
/* ── 启动目标(挂起) ── */
|
||||
wchar_t cmd[32768];
|
||||
if (argc > 3) _snwprintf(cmd, 32768, L"\"%s\" %s", exeBuf, argv[3]);
|
||||
else _snwprintf(cmd, 32768, L"\"%s\"", exeBuf);
|
||||
|
||||
wcsncpy(workBuf, exeBuf, 32767);
|
||||
wchar_t* slash = wcsrchr(workBuf, L'\\');
|
||||
if (slash) *slash = 0; else workBuf[0] = 0;
|
||||
|
||||
STARTUPINFOW si; PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
if (!CreateProcessW(exeBuf, cmd, NULL, NULL, FALSE, CREATE_SUSPENDED,
|
||||
NULL, workBuf[0] ? workBuf : NULL, &si, &pi))
|
||||
die(L"CreateProcess failed", GetLastError());
|
||||
wprintf(L"[reflect] suspended pid=%lu\n", pi.dwProcessId);
|
||||
|
||||
/* ── 映射映像 ── */
|
||||
/* ── 映射映像:优先落在首选基址,避免处理重定位 ── */
|
||||
BYTE* remote = (BYTE*)VirtualAllocEx(pi.hProcess, (LPVOID)preferredBase, sizeOfImage,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (remote) {
|
||||
wprintf(L"[reflect] mapped at preferred base\n");
|
||||
} else {
|
||||
remote = (BYTE*)VirtualAllocEx(pi.hProcess, NULL, sizeOfImage,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
}
|
||||
if (!remote) die(L"VirtualAllocEx failed", GetLastError());
|
||||
wprintf(L"[reflect] remote base=%p\n", (void*)remote);
|
||||
|
||||
if (!WriteProcessMemory(pi.hProcess, remote, dllData,
|
||||
nt->OptionalHeader.SizeOfHeaders, NULL))
|
||||
die(L"write headers failed", GetLastError());
|
||||
|
||||
IMAGE_SECTION_HEADER* sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
|
||||
if (sec[i].SizeOfRawData == 0) continue;
|
||||
if (!WriteProcessMemory(pi.hProcess, remote + sec[i].VirtualAddress,
|
||||
dllData + sec[i].PointerToRawData,
|
||||
sec[i].SizeOfRawData, NULL))
|
||||
die(L"write section failed", GetLastError());
|
||||
}
|
||||
|
||||
/* ── 重定位 ── */
|
||||
LONG_PTR delta = (LONG_PTR)(remote - preferredBase);
|
||||
if (delta != 0) {
|
||||
DWORD relocRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
|
||||
DWORD relocSize = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||||
if (!relocRva || !relocSize) {
|
||||
/* x64 代码默认 RIP 相对寻址,纯位置无关,不需要修正 */
|
||||
wprintf(L"[reflect] no reloc table -> treating as position-independent\n");
|
||||
} else {
|
||||
DWORD off = 0;
|
||||
while (off < relocSize) {
|
||||
IMAGE_BASE_RELOCATION* blk = (IMAGE_BASE_RELOCATION*)(dllData + rva2off(dllData, nt, relocRva) + off);
|
||||
if (!blk->SizeOfBlock) break;
|
||||
DWORD count = (blk->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
WORD* entries = (WORD*)((BYTE*)blk + sizeof(IMAGE_BASE_RELOCATION));
|
||||
for (DWORD i = 0; i < count; i++) {
|
||||
int type = entries[i] >> 12;
|
||||
DWORD rva = blk->VirtualAddress + (entries[i] & 0x0FFF);
|
||||
if (type == IMAGE_REL_BASED_DIR64) {
|
||||
ULONG_PTR orig = 0;
|
||||
ReadProcessMemory(pi.hProcess, remote + rva, &orig, sizeof(orig), NULL);
|
||||
ULONG_PTR fixed = orig + delta;
|
||||
WriteProcessMemory(pi.hProcess, remote + rva, &fixed, sizeof(fixed), NULL);
|
||||
}
|
||||
}
|
||||
off += blk->SizeOfBlock;
|
||||
}
|
||||
wprintf(L"[reflect] relocations applied (delta=%lld)\n", (long long)delta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ── 导入表 ── */
|
||||
DWORD impRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
||||
if (impRva) {
|
||||
IMAGE_IMPORT_DESCRIPTOR* imp = (IMAGE_IMPORT_DESCRIPTOR*)(dllData + rva2off(dllData, nt, impRva));
|
||||
wprintf(L"[reflect] imp desc off loaded\n", (unsigned long)rva2off(dllData, nt, impRva));
|
||||
for (; imp->Name; imp++) {
|
||||
wprintf(L"[reflect] imp name rva loaded\n", (unsigned long)imp->Name);
|
||||
const char* dllName = (const char*)(dllData + rva2off(dllData, nt, imp->Name));
|
||||
int resolved = 0, total = 0;
|
||||
DWORD oftOff = rva2off(dllData, nt, imp->OriginalFirstThunk ? imp->OriginalFirstThunk : imp->FirstThunk);
|
||||
ULONG_PTR* thunk = (ULONG_PTR*)(dllData + oftOff);
|
||||
for (int tIdx = 0; tIdx < 256 && *thunk; thunk++, tIdx++) {
|
||||
total++;
|
||||
ULONG_PTR fn = 0;
|
||||
if (*thunk & IMAGE_ORDINAL_FLAG64) {
|
||||
fn = localResolve(dllName, (const char*)(*thunk & 0xFFFF));
|
||||
} else {
|
||||
DWORD ibnOff = rva2off(dllData, nt, (DWORD)*thunk);
|
||||
IMAGE_IMPORT_BY_NAME* ibn = (IMAGE_IMPORT_BY_NAME*)(dllData + ibnOff);
|
||||
fn = localResolve(dllName, (const char*)ibn->Name);
|
||||
}
|
||||
if (fn) {
|
||||
resolved++;
|
||||
ULONG_PTR slotAddr = (ULONG_PTR)(remote + imp->FirstThunk +
|
||||
((BYTE*)thunk - (dllData + oftOff)));
|
||||
WriteProcessMemory(pi.hProcess, (void*)slotAddr, &fn, sizeof(fn), NULL);
|
||||
} else {
|
||||
wprintf(L"[reflect] !! unresolved %S\n", dllName);
|
||||
}
|
||||
}
|
||||
wprintf(L"[reflect] import %S -> %d/%d\n", dllName, resolved, total);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── shellcode: DllMain(remoteBase, DLL_PROCESS_ATTACH, NULL) ── */
|
||||
BYTE sc[64];
|
||||
int n = 0;
|
||||
ULONG_PTR entryAddr = (ULONG_PTR)(remote + nt->OptionalHeader.AddressOfEntryPoint);
|
||||
sc[n++] = 0x48; sc[n++] = 0xB9; /* mov rcx, base */
|
||||
memcpy(sc + n, &remote, 8); n += 8;
|
||||
sc[n++] = 0x48; sc[n++] = 0xBA; /* mov rdx, 1 */
|
||||
DWORD one = 1; memcpy(sc + n, &one, 4); n += 8;
|
||||
sc[n++] = 0x4D; sc[n++] = 0x31; sc[n++] = 0xC0; /* xor r8, r8 */
|
||||
sc[n++] = 0x48; sc[n++] = 0xB8; /* mov rax, entry */
|
||||
memcpy(sc + n, &entryAddr, 8); n += 8;
|
||||
sc[n++] = 0xFF; sc[n++] = 0xD0; /* call rax */
|
||||
sc[n++] = 0xC3; /* ret */
|
||||
|
||||
BYTE* remoteSc = (BYTE*)VirtualAllocEx(pi.hProcess, NULL, sizeof(sc),
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (!remoteSc) die(L"alloc shellcode failed", GetLastError());
|
||||
WriteProcessMemory(pi.hProcess, remoteSc, sc, n, NULL);
|
||||
|
||||
HANDLE th = CreateRemoteThread(pi.hProcess, NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)remoteSc, NULL, 0, NULL);
|
||||
if (!th) die(L"CreateRemoteThread failed", GetLastError());
|
||||
WaitForSingleObject(th, 15000);
|
||||
DWORD code = 0;
|
||||
GetExitCodeThread(th, &code);
|
||||
wprintf(L"[reflect] DllMain thread exit=0x%lX\n", code);
|
||||
CloseHandle(th);
|
||||
|
||||
ResumeThread(pi.hThread);
|
||||
wprintf(L"[reflect] resumed\n");
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
SnowCat QQ 9.9.37 启动器(V8 源码改写通道)
|
||||
|
||||
原理:
|
||||
1) QQ.exe 静态导入 WINMM.dll,且它不在 KnownDLLs 里 → 在 QQ 目录放同名代理即可侧加载
|
||||
2) 代理里 MinHook 掉 QQNT.dll 导出的 v8::ScriptCompiler::Compile*,
|
||||
在"解密后的 JS 正要被编译"这一刻拿到明文源码
|
||||
3) 命中 app_launcher/index.js 时,把 Source::source_string_ 换成"引导脚本 + 原文"
|
||||
4) 引导脚本动态 import napcat.mjs,napcat 在 QQ 主进程内正常启动
|
||||
|
||||
另修一个环境问题:
|
||||
resources\app 里的 dbghelp.dll / dbgeng.dll 是坏的,
|
||||
导致 LightQuic.dll、broadcast_ipc.dll 加载失败(193),
|
||||
进而 wrapper.node 无法 dlopen。把它们禁用后改走系统版本。
|
||||
|
||||
用法:
|
||||
.\snowcat-run.ps1 # 启动
|
||||
.\snowcat-run.ps1 -Stop # 停止
|
||||
.\snowcat-run.ps1 -Status # 只看状态
|
||||
#>
|
||||
param(
|
||||
[string]$QqUin = '<REDACTED>',
|
||||
[string]$Shell = 'D:\SnowCat.Portable\SnowCat.9.9.37-54395.Shell',
|
||||
[switch]$Stop,
|
||||
[switch]$Status
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Build = 'D:\SnowCat.Portable\tools\build'
|
||||
|
||||
function Write-Step($m) { Write-Host "[*] $m" -ForegroundColor Cyan }
|
||||
function Write-Ok($m) { Write-Host "[+] $m" -ForegroundColor Green }
|
||||
function Write-Warn2($m){ Write-Host "[!] $m" -ForegroundColor Yellow }
|
||||
function Write-Err2($m) { Write-Host "[x] $m" -ForegroundColor Red }
|
||||
|
||||
function Get-AppDir([string]$shell) {
|
||||
$vers = Join-Path $shell 'versions'
|
||||
if (-not (Test-Path $vers)) { throw "找不到 versions 目录: $vers" }
|
||||
$cands = Get-ChildItem $vers -Directory | Where-Object {
|
||||
Test-Path (Join-Path $_.FullName 'resources\app\package.json')
|
||||
} | Sort-Object Name -Descending
|
||||
if (-not $cands) { throw "versions 下没有可用的 resources\app" }
|
||||
return (Join-Path $cands[0].FullName 'resources\app')
|
||||
}
|
||||
|
||||
function Get-ServiceState {
|
||||
$code = $null
|
||||
try {
|
||||
$code = (Invoke-WebRequest -Uri 'http://127.0.0.1:6099/webui/' -TimeoutSec 3 -UseBasicParsing -Proxy $null).StatusCode
|
||||
} catch {}
|
||||
$qq = @(Get-Process QQ -ErrorAction SilentlyContinue)
|
||||
[pscustomobject]@{
|
||||
QQ = $qq.Count
|
||||
Webui = if ($code) { $code } else { 'closed' }
|
||||
Online = ($code -eq 200)
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-Qq {
|
||||
Get-Process QQ -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
if ($Stop) {
|
||||
Write-Step '停止 QQ'
|
||||
Stop-Qq
|
||||
Write-Ok '已停止'
|
||||
return
|
||||
}
|
||||
|
||||
if ($Status) {
|
||||
$s = Get-ServiceState
|
||||
Write-Host ("QQ 进程: {0} 6099: {1}" -f $s.QQ, $s.Webui)
|
||||
if ($s.Online) { Write-Ok 'SnowCat WebUI 在线' } else { Write-Warn2 'WebUI 未监听' }
|
||||
return
|
||||
}
|
||||
|
||||
# ── 1. 环境自检 ──────────────────────────────────────────────
|
||||
if (-not (Test-Path (Join-Path $Shell 'QQ.exe'))) { throw "找不到 QQ.exe: $Shell" }
|
||||
$app = Get-AppDir $Shell
|
||||
Write-Step "app 目录: $app"
|
||||
|
||||
# ── 2. 修坏掉的调试 DLL(否则 wrapper.node 无法加载)─────────
|
||||
foreach ($bad in 'dbghelp.dll', 'dbgeng.dll') {
|
||||
$p = Join-Path $app $bad
|
||||
if (Test-Path $p) {
|
||||
$size = (Get-Item $p).Length
|
||||
Move-Item $p "$p.disabled" -Force
|
||||
Write-Ok ("禁用 {0}({1} 字节,无法加载)" -f $bad, $size)
|
||||
}
|
||||
}
|
||||
|
||||
# ── 3. 部署侧加载代理 ────────────────────────────────────────
|
||||
$srcDll = Join-Path $Build 'winmm-v8.dll'
|
||||
if (-not (Test-Path $srcDll)) { throw "缺少构建产物: $srcDll" }
|
||||
Copy-Item $srcDll (Join-Path $Shell 'winmm.dll') -Force
|
||||
Write-Ok ("winmm.dll 已部署({0} KB)" -f [math]::Round((Get-Item $srcDll).Length/1KB))
|
||||
|
||||
# ── 4. 部署引导脚本 ──────────────────────────────────────────
|
||||
$srcBoot = Join-Path $Build 'snowcat-boot.js'
|
||||
$dstBoot = Join-Path $Shell 'snowcat-boot.js'
|
||||
if (Test-Path $srcBoot) {
|
||||
Copy-Item $srcBoot $dstBoot -Force
|
||||
} elseif (-not (Test-Path $dstBoot)) {
|
||||
throw "缺少引导脚本: $dstBoot"
|
||||
}
|
||||
Write-Ok 'snowcat-boot.js 就位'
|
||||
|
||||
# ── 5. 部署 WebUI 静态资源(缺了页面就是一片空白)───────────
|
||||
$staticDir = Join-Path $Shell 'static'
|
||||
if (-not (Test-Path (Join-Path $staticDir 'index.html'))) {
|
||||
$srcStatic = Join-Path $Build 'static-dist'
|
||||
if (Test-Path (Join-Path $srcStatic 'index.html')) {
|
||||
if (Test-Path $staticDir) { Remove-Item $staticDir -Recurse -Force }
|
||||
Copy-Item $srcStatic $staticDir -Recurse -Force
|
||||
Write-Ok 'static/ 已部署(WebUI 前端)'
|
||||
} else {
|
||||
Write-Warn2 "缺少 WebUI 静态资源($staticDir),页面会打不开"
|
||||
}
|
||||
} else {
|
||||
Write-Ok 'static/ 已存在'
|
||||
}
|
||||
|
||||
# ── 6. 启动 ──────────────────────────────────────────────────
|
||||
Stop-Qq
|
||||
foreach ($n in 'https_proxy','HTTPS_PROXY','http_proxy','HTTP_PROXY') {
|
||||
Remove-Item "Env:$n" -ErrorAction SilentlyContinue
|
||||
}
|
||||
Remove-Item (Join-Path $Shell 'nc-boot.log') -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Step "启动 QQ(账号 $QqUin)"
|
||||
$qqArgs = @("--enable-logging")
|
||||
if ($QqUin) { $qqArgs += @("-q", $QqUin) }
|
||||
Start-Process -FilePath (Join-Path $Shell 'QQ.exe') -ArgumentList $qqArgs -WorkingDirectory $Shell
|
||||
|
||||
# ── 7. 等待并报告 ────────────────────────────────────────────
|
||||
$deadline = (Get-Date).AddSeconds(45)
|
||||
$ok = $false
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Milliseconds 800
|
||||
try {
|
||||
$c = (Invoke-WebRequest -Uri 'http://127.0.0.1:6099/webui/' -TimeoutSec 2 -UseBasicParsing -Proxy $null).StatusCode
|
||||
if ($c -eq 200) { $ok = $true; break }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
$bootLog = Join-Path $Shell 'nc-boot.log'
|
||||
if (Test-Path $bootLog) {
|
||||
Write-Host ''
|
||||
Write-Step '注入日志:'
|
||||
Get-Content $bootLog | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
if ($ok) {
|
||||
Write-Ok 'SnowCat WebUI 已就绪 → http://127.0.0.1:6099/webui/'
|
||||
} else {
|
||||
Write-Err2 'WebUI 未在 45 秒内就绪'
|
||||
Write-Warn2 '排查顺序:1) 看上面注入日志 2) QQ 是否被安全软件拦截 3) 是否 dbghelp.dll 又出现了'
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/* winmm 代理:所有导出转发到系统 winmm.dll,DllMain 执行 SnowCat 逻辑 */
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
typedef int BOOL;
|
||||
typedef const unsigned short* LPCWSTR;
|
||||
typedef const char* LPCSTR;
|
||||
|
||||
__declspec(dllimport) void* __stdcall LoadLibraryW(LPCWSTR);
|
||||
__declspec(dllimport) void* __stdcall GetProcAddress(void*, LPCSTR);
|
||||
__declspec(dllimport) HANDLE __stdcall CreateFileW(LPCWSTR, DWORD, DWORD, void*, DWORD, DWORD, HANDLE);
|
||||
__declspec(dllimport) BOOL __stdcall WriteFile(HANDLE, const void*, DWORD, DWORD*, void*);
|
||||
__declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE);
|
||||
__declspec(dllimport) DWORD __stdcall GetTickCount(void);
|
||||
__declspec(dllimport) void __stdcall Sleep(DWORD);
|
||||
__declspec(dllimport) DWORD __stdcall GetCurrentProcessId(void);
|
||||
|
||||
/* -nostdlib 需要 */
|
||||
unsigned int _tls_index = 0;
|
||||
|
||||
void* g_timeGetTime = 0;
|
||||
void* g_timeBeginPeriod = 0;
|
||||
void* g_timeEndPeriod = 0;
|
||||
void* g_midiInAddBuffer = 0;
|
||||
void* g_midiInClose = 0;
|
||||
void* g_midiInGetDevCapsW = 0;
|
||||
void* g_midiInGetNumDevs = 0;
|
||||
void* g_midiInOpen = 0;
|
||||
void* g_midiInPrepareHeader = 0;
|
||||
void* g_midiInReset = 0;
|
||||
void* g_midiInStart = 0;
|
||||
void* g_midiInUnprepareHeader = 0;
|
||||
void* g_midiOutClose = 0;
|
||||
void* g_midiOutGetDevCapsW = 0;
|
||||
void* g_midiOutGetNumDevs = 0;
|
||||
void* g_midiOutLongMsg = 0;
|
||||
void* g_midiOutOpen = 0;
|
||||
void* g_midiOutPrepareHeader = 0;
|
||||
void* g_midiOutReset = 0;
|
||||
void* g_midiOutShortMsg = 0;
|
||||
void* g_midiOutUnprepareHeader = 0;
|
||||
void* g_waveInGetNumDevs = 0;
|
||||
void* g_waveOutClose = 0;
|
||||
void* g_waveOutGetNumDevs = 0;
|
||||
void* g_waveOutOpen = 0;
|
||||
void* g_waveOutPause = 0;
|
||||
void* g_waveOutPrepareHeader = 0;
|
||||
void* g_waveOutReset = 0;
|
||||
void* g_waveOutRestart = 0;
|
||||
void* g_waveOutUnprepareHeader = 0;
|
||||
void* g_waveOutWrite = 0;
|
||||
|
||||
__attribute__((naked)) void sc_timeGetTime(void) {
|
||||
__asm__ __volatile__("jmp *g_timeGetTime(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_timeBeginPeriod(void) {
|
||||
__asm__ __volatile__("jmp *g_timeBeginPeriod(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_timeEndPeriod(void) {
|
||||
__asm__ __volatile__("jmp *g_timeEndPeriod(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInAddBuffer(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInAddBuffer(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInClose(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInClose(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInGetDevCapsW(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInGetDevCapsW(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInGetNumDevs(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInGetNumDevs(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInOpen(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInOpen(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInPrepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInPrepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInReset(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInReset(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInStart(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInStart(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiInUnprepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_midiInUnprepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutClose(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutClose(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutGetDevCapsW(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutGetDevCapsW(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutGetNumDevs(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutGetNumDevs(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutLongMsg(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutLongMsg(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutOpen(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutOpen(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutPrepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutPrepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutReset(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutReset(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutShortMsg(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutShortMsg(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_midiOutUnprepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_midiOutUnprepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveInGetNumDevs(void) {
|
||||
__asm__ __volatile__("jmp *g_waveInGetNumDevs(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutClose(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutClose(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutGetNumDevs(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutGetNumDevs(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutOpen(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutOpen(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutPause(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutPause(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutPrepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutPrepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutReset(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutReset(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutRestart(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutRestart(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutUnprepareHeader(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutUnprepareHeader(%rip)");
|
||||
}
|
||||
__attribute__((naked)) void sc_waveOutWrite(void) {
|
||||
__asm__ __volatile__("jmp *g_waveOutWrite(%rip)");
|
||||
}
|
||||
|
||||
|
||||
static int g_inited = 0;
|
||||
static void* g_real = 0;
|
||||
|
||||
static void appendLog(const char* msg, unsigned long len) {
|
||||
HANDLE f = CreateFileW(L"D:\\SnowCat.Portable\\winmm-proxy.txt",
|
||||
(DWORD)0x40000000 /*GENERIC_WRITE*/, 3 /*FILE_SHARE_READ|WRITE*/,
|
||||
0, 4 /*OPEN_ALWAYS*/, 0x80, 0);
|
||||
if (f != (HANDLE)-1 && f) {
|
||||
DWORD w = 0;
|
||||
WriteFile(f, msg, (DWORD)len, &w, 0);
|
||||
CloseHandle(f);
|
||||
}
|
||||
}
|
||||
|
||||
static void initOnce(void) {
|
||||
if (g_inited) return;
|
||||
g_inited = 1;
|
||||
appendLog("[winmm-proxy] DllMain ATTACH reached\n", 36);
|
||||
g_real = LoadLibraryW(L"C:\\Windows\\System32\\winmm.dll");
|
||||
if (!g_real) {
|
||||
appendLog("[winmm-proxy] LoadLibraryW(system winmm) FAILED\n", 46);
|
||||
return;
|
||||
}
|
||||
appendLog("[winmm-proxy] system winmm loaded, resolving exports\n", 54);
|
||||
|
||||
g_timeGetTime = GetProcAddress(g_real, "timeGetTime");
|
||||
g_timeBeginPeriod = GetProcAddress(g_real, "timeBeginPeriod");
|
||||
g_timeEndPeriod = GetProcAddress(g_real, "timeEndPeriod");
|
||||
g_midiInAddBuffer = GetProcAddress(g_real, "midiInAddBuffer");
|
||||
g_midiInClose = GetProcAddress(g_real, "midiInClose");
|
||||
g_midiInGetDevCapsW = GetProcAddress(g_real, "midiInGetDevCapsW");
|
||||
g_midiInGetNumDevs = GetProcAddress(g_real, "midiInGetNumDevs");
|
||||
g_midiInOpen = GetProcAddress(g_real, "midiInOpen");
|
||||
g_midiInPrepareHeader = GetProcAddress(g_real, "midiInPrepareHeader");
|
||||
g_midiInReset = GetProcAddress(g_real, "midiInReset");
|
||||
g_midiInStart = GetProcAddress(g_real, "midiInStart");
|
||||
g_midiInUnprepareHeader = GetProcAddress(g_real, "midiInUnprepareHeader");
|
||||
g_midiOutClose = GetProcAddress(g_real, "midiOutClose");
|
||||
g_midiOutGetDevCapsW = GetProcAddress(g_real, "midiOutGetDevCapsW");
|
||||
g_midiOutGetNumDevs = GetProcAddress(g_real, "midiOutGetNumDevs");
|
||||
g_midiOutLongMsg = GetProcAddress(g_real, "midiOutLongMsg");
|
||||
g_midiOutOpen = GetProcAddress(g_real, "midiOutOpen");
|
||||
g_midiOutPrepareHeader = GetProcAddress(g_real, "midiOutPrepareHeader");
|
||||
g_midiOutReset = GetProcAddress(g_real, "midiOutReset");
|
||||
g_midiOutShortMsg = GetProcAddress(g_real, "midiOutShortMsg");
|
||||
g_midiOutUnprepareHeader = GetProcAddress(g_real, "midiOutUnprepareHeader");
|
||||
g_waveInGetNumDevs = GetProcAddress(g_real, "waveInGetNumDevs");
|
||||
g_waveOutClose = GetProcAddress(g_real, "waveOutClose");
|
||||
g_waveOutGetNumDevs = GetProcAddress(g_real, "waveOutGetNumDevs");
|
||||
g_waveOutOpen = GetProcAddress(g_real, "waveOutOpen");
|
||||
g_waveOutPause = GetProcAddress(g_real, "waveOutPause");
|
||||
g_waveOutPrepareHeader = GetProcAddress(g_real, "waveOutPrepareHeader");
|
||||
g_waveOutReset = GetProcAddress(g_real, "waveOutReset");
|
||||
g_waveOutRestart = GetProcAddress(g_real, "waveOutRestart");
|
||||
g_waveOutUnprepareHeader = GetProcAddress(g_real, "waveOutUnprepareHeader");
|
||||
g_waveOutWrite = GetProcAddress(g_real, "waveOutWrite");
|
||||
appendLog("[winmm-proxy] all exports resolved\n", 36);
|
||||
}
|
||||
|
||||
BOOL __stdcall DllMain(HANDLE module, DWORD reason, void* reserved) {
|
||||
(void)module; (void)reserved;
|
||||
if (reason == 1) { /* DLL_PROCESS_ATTACH */
|
||||
initOnce();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,182 @@
|
||||
LIBRARY winmm
|
||||
EXPORTS
|
||||
CloseDriver=winmm.CloseDriver
|
||||
DefDriverProc=winmm.DefDriverProc
|
||||
DriverCallback=winmm.DriverCallback
|
||||
DrvGetModuleHandle=winmm.DrvGetModuleHandle
|
||||
GetDriverModuleHandle=winmm.GetDriverModuleHandle
|
||||
OpenDriver=winmm.OpenDriver
|
||||
PlaySound=winmm.PlaySound
|
||||
PlaySoundA=winmm.PlaySoundA
|
||||
PlaySoundW=winmm.PlaySoundW
|
||||
SendDriverMessage=winmm.SendDriverMessage
|
||||
WOWAppExit=winmm.WOWAppExit
|
||||
auxGetDevCapsA=winmm.auxGetDevCapsA
|
||||
auxGetDevCapsW=winmm.auxGetDevCapsW
|
||||
auxGetNumDevs=winmm.auxGetNumDevs
|
||||
auxGetVolume=winmm.auxGetVolume
|
||||
auxOutMessage=winmm.auxOutMessage
|
||||
auxSetVolume=winmm.auxSetVolume
|
||||
joyConfigChanged=winmm.joyConfigChanged
|
||||
joyGetDevCapsA=winmm.joyGetDevCapsA
|
||||
joyGetDevCapsW=winmm.joyGetDevCapsW
|
||||
joyGetNumDevs=winmm.joyGetNumDevs
|
||||
joyGetPos=winmm.joyGetPos
|
||||
joyGetPosEx=winmm.joyGetPosEx
|
||||
joyGetThreshold=winmm.joyGetThreshold
|
||||
joyReleaseCapture=winmm.joyReleaseCapture
|
||||
joySetCapture=winmm.joySetCapture
|
||||
joySetThreshold=winmm.joySetThreshold
|
||||
mciDriverNotify=winmm.mciDriverNotify
|
||||
mciDriverYield=winmm.mciDriverYield
|
||||
mciExecute=winmm.mciExecute
|
||||
mciFreeCommandResource=winmm.mciFreeCommandResource
|
||||
mciGetCreatorTask=winmm.mciGetCreatorTask
|
||||
mciGetDeviceIDA=winmm.mciGetDeviceIDA
|
||||
mciGetDeviceIDFromElementIDA=winmm.mciGetDeviceIDFromElementIDA
|
||||
mciGetDeviceIDFromElementIDW=winmm.mciGetDeviceIDFromElementIDW
|
||||
mciGetDeviceIDW=winmm.mciGetDeviceIDW
|
||||
mciGetDriverData=winmm.mciGetDriverData
|
||||
mciGetErrorStringA=winmm.mciGetErrorStringA
|
||||
mciGetErrorStringW=winmm.mciGetErrorStringW
|
||||
mciGetYieldProc=winmm.mciGetYieldProc
|
||||
mciLoadCommandResource=winmm.mciLoadCommandResource
|
||||
mciSendCommandA=winmm.mciSendCommandA
|
||||
mciSendCommandW=winmm.mciSendCommandW
|
||||
mciSendStringA=winmm.mciSendStringA
|
||||
mciSendStringW=winmm.mciSendStringW
|
||||
mciSetDriverData=winmm.mciSetDriverData
|
||||
mciSetYieldProc=winmm.mciSetYieldProc
|
||||
midiConnect=winmm.midiConnect
|
||||
midiDisconnect=winmm.midiDisconnect
|
||||
midiInAddBuffer=winmm.midiInAddBuffer
|
||||
midiInClose=winmm.midiInClose
|
||||
midiInGetDevCapsA=winmm.midiInGetDevCapsA
|
||||
midiInGetDevCapsW=winmm.midiInGetDevCapsW
|
||||
midiInGetErrorTextA=winmm.midiInGetErrorTextA
|
||||
midiInGetErrorTextW=winmm.midiInGetErrorTextW
|
||||
midiInGetID=winmm.midiInGetID
|
||||
midiInGetNumDevs=winmm.midiInGetNumDevs
|
||||
midiInMessage=winmm.midiInMessage
|
||||
midiInOpen=winmm.midiInOpen
|
||||
midiInPrepareHeader=winmm.midiInPrepareHeader
|
||||
midiInReset=winmm.midiInReset
|
||||
midiInStart=winmm.midiInStart
|
||||
midiInStop=winmm.midiInStop
|
||||
midiInUnprepareHeader=winmm.midiInUnprepareHeader
|
||||
midiOutCacheDrumPatches=winmm.midiOutCacheDrumPatches
|
||||
midiOutCachePatches=winmm.midiOutCachePatches
|
||||
midiOutClose=winmm.midiOutClose
|
||||
midiOutGetDevCapsA=winmm.midiOutGetDevCapsA
|
||||
midiOutGetDevCapsW=winmm.midiOutGetDevCapsW
|
||||
midiOutGetErrorTextA=winmm.midiOutGetErrorTextA
|
||||
midiOutGetErrorTextW=winmm.midiOutGetErrorTextW
|
||||
midiOutGetID=winmm.midiOutGetID
|
||||
midiOutGetNumDevs=winmm.midiOutGetNumDevs
|
||||
midiOutGetVolume=winmm.midiOutGetVolume
|
||||
midiOutLongMsg=winmm.midiOutLongMsg
|
||||
midiOutMessage=winmm.midiOutMessage
|
||||
midiOutOpen=winmm.midiOutOpen
|
||||
midiOutPrepareHeader=winmm.midiOutPrepareHeader
|
||||
midiOutReset=winmm.midiOutReset
|
||||
midiOutSetVolume=winmm.midiOutSetVolume
|
||||
midiOutShortMsg=winmm.midiOutShortMsg
|
||||
midiOutUnprepareHeader=winmm.midiOutUnprepareHeader
|
||||
midiStreamClose=winmm.midiStreamClose
|
||||
midiStreamOpen=winmm.midiStreamOpen
|
||||
midiStreamOut=winmm.midiStreamOut
|
||||
midiStreamPause=winmm.midiStreamPause
|
||||
midiStreamPosition=winmm.midiStreamPosition
|
||||
midiStreamProperty=winmm.midiStreamProperty
|
||||
midiStreamRestart=winmm.midiStreamRestart
|
||||
midiStreamStop=winmm.midiStreamStop
|
||||
mixerClose=winmm.mixerClose
|
||||
mixerGetControlDetailsA=winmm.mixerGetControlDetailsA
|
||||
mixerGetControlDetailsW=winmm.mixerGetControlDetailsW
|
||||
mixerGetDevCapsA=winmm.mixerGetDevCapsA
|
||||
mixerGetDevCapsW=winmm.mixerGetDevCapsW
|
||||
mixerGetID=winmm.mixerGetID
|
||||
mixerGetLineControlsA=winmm.mixerGetLineControlsA
|
||||
mixerGetLineControlsW=winmm.mixerGetLineControlsW
|
||||
mixerGetLineInfoA=winmm.mixerGetLineInfoA
|
||||
mixerGetLineInfoW=winmm.mixerGetLineInfoW
|
||||
mixerGetNumDevs=winmm.mixerGetNumDevs
|
||||
mixerMessage=winmm.mixerMessage
|
||||
mixerOpen=winmm.mixerOpen
|
||||
mixerSetControlDetails=winmm.mixerSetControlDetails
|
||||
mmDrvInstall=winmm.mmDrvInstall
|
||||
mmGetCurrentTask=winmm.mmGetCurrentTask
|
||||
mmTaskBlock=winmm.mmTaskBlock
|
||||
mmTaskCreate=winmm.mmTaskCreate
|
||||
mmTaskSignal=winmm.mmTaskSignal
|
||||
mmTaskYield=winmm.mmTaskYield
|
||||
mmioAdvance=winmm.mmioAdvance
|
||||
mmioAscend=winmm.mmioAscend
|
||||
mmioClose=winmm.mmioClose
|
||||
mmioCreateChunk=winmm.mmioCreateChunk
|
||||
mmioDescend=winmm.mmioDescend
|
||||
mmioFlush=winmm.mmioFlush
|
||||
mmioGetInfo=winmm.mmioGetInfo
|
||||
mmioInstallIOProcA=winmm.mmioInstallIOProcA
|
||||
mmioInstallIOProcW=winmm.mmioInstallIOProcW
|
||||
mmioOpenA=winmm.mmioOpenA
|
||||
mmioOpenW=winmm.mmioOpenW
|
||||
mmioRead=winmm.mmioRead
|
||||
mmioRenameA=winmm.mmioRenameA
|
||||
mmioRenameW=winmm.mmioRenameW
|
||||
mmioSeek=winmm.mmioSeek
|
||||
mmioSendMessage=winmm.mmioSendMessage
|
||||
mmioSetBuffer=winmm.mmioSetBuffer
|
||||
mmioSetInfo=winmm.mmioSetInfo
|
||||
mmioStringToFOURCCA=winmm.mmioStringToFOURCCA
|
||||
mmioStringToFOURCCW=winmm.mmioStringToFOURCCW
|
||||
mmioWrite=winmm.mmioWrite
|
||||
mmsystemGetVersion=winmm.mmsystemGetVersion
|
||||
sndPlaySoundA=winmm.sndPlaySoundA
|
||||
sndPlaySoundW=winmm.sndPlaySoundW
|
||||
timeBeginPeriod=winmm.timeBeginPeriod
|
||||
timeEndPeriod=winmm.timeEndPeriod
|
||||
timeGetDevCaps=winmm.timeGetDevCaps
|
||||
timeGetSystemTime=winmm.timeGetSystemTime
|
||||
timeGetTime=winmm.timeGetTime
|
||||
timeKillEvent=winmm.timeKillEvent
|
||||
timeSetEvent=winmm.timeSetEvent
|
||||
waveInAddBuffer=winmm.waveInAddBuffer
|
||||
waveInClose=winmm.waveInClose
|
||||
waveInGetDevCapsA=winmm.waveInGetDevCapsA
|
||||
waveInGetDevCapsW=winmm.waveInGetDevCapsW
|
||||
waveInGetErrorTextA=winmm.waveInGetErrorTextA
|
||||
waveInGetErrorTextW=winmm.waveInGetErrorTextW
|
||||
waveInGetID=winmm.waveInGetID
|
||||
waveInGetNumDevs=winmm.waveInGetNumDevs
|
||||
waveInGetPosition=winmm.waveInGetPosition
|
||||
waveInMessage=winmm.waveInMessage
|
||||
waveInOpen=winmm.waveInOpen
|
||||
waveInPrepareHeader=winmm.waveInPrepareHeader
|
||||
waveInReset=winmm.waveInReset
|
||||
waveInStart=winmm.waveInStart
|
||||
waveInStop=winmm.waveInStop
|
||||
waveInUnprepareHeader=winmm.waveInUnprepareHeader
|
||||
waveOutBreakLoop=winmm.waveOutBreakLoop
|
||||
waveOutClose=winmm.waveOutClose
|
||||
waveOutGetDevCapsA=winmm.waveOutGetDevCapsA
|
||||
waveOutGetDevCapsW=winmm.waveOutGetDevCapsW
|
||||
waveOutGetErrorTextA=winmm.waveOutGetErrorTextA
|
||||
waveOutGetErrorTextW=winmm.waveOutGetErrorTextW
|
||||
waveOutGetID=winmm.waveOutGetID
|
||||
waveOutGetNumDevs=winmm.waveOutGetNumDevs
|
||||
waveOutGetPitch=winmm.waveOutGetPitch
|
||||
waveOutGetPlaybackRate=winmm.waveOutGetPlaybackRate
|
||||
waveOutGetPosition=winmm.waveOutGetPosition
|
||||
waveOutGetVolume=winmm.waveOutGetVolume
|
||||
waveOutMessage=winmm.waveOutMessage
|
||||
waveOutOpen=winmm.waveOutOpen
|
||||
waveOutPause=winmm.waveOutPause
|
||||
waveOutPrepareHeader=winmm.waveOutPrepareHeader
|
||||
waveOutReset=winmm.waveOutReset
|
||||
waveOutRestart=winmm.waveOutRestart
|
||||
waveOutSetPitch=winmm.waveOutSetPitch
|
||||
waveOutSetPlaybackRate=winmm.waveOutSetPlaybackRate
|
||||
waveOutSetVolume=winmm.waveOutSetVolume
|
||||
waveOutUnprepareHeader=winmm.waveOutUnprepareHeader
|
||||
waveOutWrite=winmm.waveOutWrite
|
||||
Reference in new issue
Block a user