文档归位:雪猫 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

File diff suppressed because it is too large. Load diff
+43
View File
@@ -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));
}
})();
+191
View File
@@ -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;
}
+115
View File
@@ -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;
}
+228
View File
@@ -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;
}
+161
View File
@@ -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 又出现了'
}
+212
View File
@@ -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;
}
+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;
}
Binary file not shown.
+182
View File
@@ -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