Files
SnowCatQQ/release-tools/build/snowcat-hook.c
T
2026-11-12 06:29:38 +08:00

192 lines
8.8 KiB
C
Raw Blame History

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