commit 16754c32794baed2ccde97a982512621a6072a40 Author: Changhua Date: Fri Feb 12 23:21:57 2021 +0800 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d02e348 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d8705a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.* +!.gitignore +!.editorconfig + +# VS2019 build files +Debug/ +Release/ +x64/ + +# RPC generated files +*_c.c +*_s.c +*_h.h diff --git a/App/App.cpp b/App/App.cpp new file mode 100644 index 0000000..c0ae879 --- /dev/null +++ b/App/App.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include "sdk.h" +/* +#pragma comment(lib, "SDK.lib") +等效为在属性,链接,输入中添加该依赖 +*/ + +int onTextMsg(WxMessage_t msg) +{ + try { + wcout << msg.id << L" msgType: " << msg.type << L", msgSource: " << msg.source << L", isSelf: " << msg.self + << endl; + wcout << msg.wxId << L"[" << msg.roomId << L"]" << L" >> " << msg.content << endl; + wcout << L"msgSourceXml: " << msg.xml << endl; + } catch (...) { + wcout << "something wrong..." << endl; + } + wcout.flush(); + return 0; +} + +int main() +{ + DWORD status = 0; + wstring wxid = L"filehelper"; // 微信ID + wstring at_wxid = L""; + wstring content = L"这里填写消息内容"; + + //_setmode(_fileno(stdout), _O_WTEXT); // 没有这个wcout遇到一些字符会导致console卡死,用了会导致脱离控制台 + _wsetlocale(LC_ALL, L"chs"); // 这是个大坑,不设置中文直接不见了。。。 + + // 获取消息类型 + const MsgTypesMap_t WxMsgTypes = WxGetMsgTypes(); + for (auto it = WxMsgTypes.begin(); it != WxMsgTypes.end(); ++it) { + wcout << it->first << L": " << it->second << endl; + } + + status = WxInitSDK(); + wcout << L"WxInitSDK: " << status << endl; + if (status != 0) { + return 0; + } + + wcout << L"Message: 接收通知中......" << endl; + WxSetTextMsgCb(onTextMsg); + + // 测试消息发送 + WxSendTextMsg(wxid, at_wxid, content); + + while (1) { + Sleep(10000); // 休眠,释放CPU + } +} diff --git a/App/App.py b/App/App.py new file mode 100644 index 0000000..0fa695d --- /dev/null +++ b/App/App.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- + +import time +import wcferry as sdk + + +def main(): + print(dir(sdk)) # 查看SDK支持的方法和属性 + help(sdk.WxSetTextMsgCb) # 查看某方法的情况 + help(sdk.WxMessage) # 查看消息结构 + WxMsgTypes = sdk.WxGetMsgTypes() # 获取消息类型 + print(WxMsgTypes) # 查看消息类型 + + # 初始化SDK,如果成功,返回0;否则失败 + status = sdk.WxInitSDK() + if status != 0: + print("初始化失败") + exit(-1) + + print("初始化成功") + + time.sleep(2) + print("发送文本消息......") + sdk.WxSendTextMsg("filehelper", "", "message from WeChatFerry...") + + # 接收消息。先定义消息处理回调 + def OnTextMsg(msg: sdk.WxMessage): + if msg.self == 1: # 忽略自己发的消息 + return 0 + + s = "" + msgType = WxMsgTypes.get(msg.type, '未知消息类型') + if msg.source == 0: + s += f"收到来自好友[{msg.wxId}]的{msgType}消息:" + else: + s += f"收到来自群[{msg.roomId}]的[{msg.wxId}]的{msgType}消息:" + + s += f"\r\n{msg.content}" + if msg.type != 0x01: + s += f"\r\n{msg.xml}" + + print(f"\n{s}") + + return 0 + + print("Message: 接收通知中......") + sdk.WxSetTextMsgCb(OnTextMsg) # 设置回调,接收消息 + while True: + time.sleep(1) + + +if __name__ == '__main__': + main() diff --git a/App/App.vcxproj b/App/App.vcxproj new file mode 100644 index 0000000..49b955a --- /dev/null +++ b/App/App.vcxproj @@ -0,0 +1,163 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + 16.0 + Win32Proj + {44c1a579-22a7-4ba3-a618-45bd01600294} + App + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + /utf-8 %(AdditionalOptions) + $(SolutionDir)Rpc + + + Console + true + $(OutDir)SDK.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)SDK;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(OutDir)SDK.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + md wx +xcopy /y $(OutDir)Spy.dll $(OutDir)wx\ +xcopy /y $(OutDir)SDK.dll $(OutDir)wx\ +xcopy /y $(OutDir)wcferry.pyd $(OutDir)wx\ +xcopy /y $(OutDir)App.exe $(OutDir)wx\ +xcopy /y $(SolutionDir)App\App.py $(OutDir)wx\ + + + Copy output + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + \ No newline at end of file diff --git a/App/App.vcxproj.filters b/App/App.vcxproj.filters new file mode 100644 index 0000000..f5199e4 --- /dev/null +++ b/App/App.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + \ No newline at end of file diff --git a/App/App.vcxproj.user b/App/App.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/App/App.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..27d6e7e --- /dev/null +++ b/README.MD @@ -0,0 +1,28 @@ +# WeChatFerry +传送信息往返微信的摆渡车。 + +## 快速开始 +1. 使用 VS2019 编译。 +2. 打开 `CMD`,运行 `App.exe` + +## 项目结构 +### Spy +间谍模块,注入到微信中,通过 RPC 做消息转发工作。 + +### SDK +RPC 的客户端,封装接口,供其他方调用。 + +### SDKpy +用于生成 Python 接口。为编译该项目,需要做一些配置: +1. 添加附加包含目录 +* Python 头:`C:\Program Files (x86)\Python37-32\Include` +* Pybind11 头:`C:\Projs\.pyenv\pybind11\lib\site-packages\pybind11\include` +*注*: + 1. pybind11 可以通过 Python 安装(本工程安装到虚拟环境里了):`pip install pybind11` + 2. 然后通过命令查找:`python -m pybind11 --includes` + +2. 添加 Python 的库 +* `C:\Program Files (x86)\Python37-32\libs` + +### App +示例应用,介绍如何调用 SDK。 diff --git a/Rpc/rpc.idl b/Rpc/rpc.idl new file mode 100644 index 0000000..d18fb71 --- /dev/null +++ b/Rpc/rpc.idl @@ -0,0 +1,16 @@ +[ + uuid(ed838ecd-8a1e-4da7-bfda-9f2d12d07893), + version(1.0), + implicit_handle(handle_t hSpyBinding), +] + +interface ISpy +{ + import "rpc_types.h"; + + int IsLogin(); + int SendTextMsg([ in, string ] const wchar_t *wxid, [ in, string ] const wchar_t *at_wxid, [ in, string ] const wchar_t *msg); + + void EnableReceiveMsg(); + [callback] int ReceiveMsg([ in ] RpcMessage_t *msg); +}; diff --git a/Rpc/rpc_memory.cpp b/Rpc/rpc_memory.cpp new file mode 100644 index 0000000..01eb680 --- /dev/null +++ b/Rpc/rpc_memory.cpp @@ -0,0 +1,9 @@ +#include + +/******************************************************/ +/* MIDL allocate and free */ +/******************************************************/ + +void __RPC_FAR *__RPC_USER midl_user_allocate(size_t len) { return (malloc(len)); } + +void __RPC_USER midl_user_free(void __RPC_FAR *ptr) { free(ptr); } diff --git a/Rpc/rpc_types.h b/Rpc/rpc_types.h new file mode 100644 index 0000000..b897a96 --- /dev/null +++ b/Rpc/rpc_types.h @@ -0,0 +1,18 @@ +#pragma once + +#define MSG_SIZE_MSG_ID 64 +#define MSG_SIZE_MSG_XML 4096 +#define MSG_SIZE_WXID 64 +#define MSG_SIZE_ROOMID 64 +#define MSG_SIZE_CONTENT 16385 + +typedef struct RpcMessage { + int self; // 是否自己发的消息:0=否,1=是 + int type; // 消息类型 + int source; // 消息来源:0=好友消息,1=群消息 + wchar_t id[MSG_SIZE_MSG_ID]; // 消息ID + wchar_t xml[MSG_SIZE_MSG_XML]; // 群其他消息 + wchar_t wxId[MSG_SIZE_WXID]; // 发送人微信ID + wchar_t roomId[MSG_SIZE_ROOMID]; // 群ID + wchar_t content[MSG_SIZE_CONTENT]; // 消息内容,MAC版最大:16384,即16KB +} RpcMessage_t; diff --git a/SDK/SDK.vcxproj b/SDK/SDK.vcxproj new file mode 100644 index 0000000..f16a252 --- /dev/null +++ b/SDK/SDK.vcxproj @@ -0,0 +1,181 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {707f2dcd-1001-42a7-b20e-b85b1bbab228} + SDK + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + /utf-8 %(AdditionalOptions) + $(SolutionDir)Rpc + + + Windows + true + false + sdk.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(SolutionDir)Rpc + + + Windows + true + true + true + false + sdk.def + + + + + Level3 + true + _DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + sdk.def + + + + + Level3 + true + true + true + NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + sdk.def + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SDK/SDK.vcxproj.filters b/SDK/SDK.vcxproj.filters new file mode 100644 index 0000000..848686a --- /dev/null +++ b/SDK/SDK.vcxproj.filters @@ -0,0 +1,59 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + + + 源文件 + + + \ No newline at end of file diff --git a/SDK/SDK.vcxproj.user b/SDK/SDK.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/SDK/SDK.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/SDK/dllmain.cpp b/SDK/dllmain.cpp new file mode 100644 index 0000000..13cd500 --- /dev/null +++ b/SDK/dllmain.cpp @@ -0,0 +1,21 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "framework.h" +#include + +extern RPC_STATUS RpcConnectServer(); +extern RPC_STATUS RpcDisconnectServer(); + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: { + RpcDisconnectServer(); + break; + } + } + return TRUE; +} diff --git a/SDK/framework.h b/SDK/framework.h new file mode 100644 index 0000000..e526216 --- /dev/null +++ b/SDK/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/SDK/injector.cpp b/SDK/injector.cpp new file mode 100644 index 0000000..5512c5f --- /dev/null +++ b/SDK/injector.cpp @@ -0,0 +1,653 @@ +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "injector.h" +#include +#include +#include +#include + +typedef BOOL(WINAPI *IsWow64Process2_t)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine); + +static DWORD page_size = 0; +static size_t func_LoadLibraryW; +static size_t func_FreeLibrary; +static size_t func_GetLastError; +static char errmsg[512]; +static injector_t *g_injector; + +#ifdef _M_AMD64 +static const char x64_code_template[] = + // ---------- call LoadLibraryW ---------- + /* 0000: */ "\x48\x83\xEC\x28" // sub rsp,28h + /* 0004: */ "\xFF\x15\x3E\x00\x00\x00" // call LoadLibraryW + // 0x0000003e = X64_ADDR_LoadLibraryW - (0x0004 + 6) + /* 000A: */ "\x48\x85\xC0" // test rax,rax + /* 000D: */ "\x74\x0B" // je L1 + /* 000F: */ "\x48\x89\x05\xEA\x0F\x00\x00" // mov [load_address], rax + // 0x00000fea = 0x1000 - (0x000F + 7) + /* 0016: */ "\x33\xC0" // xor eax,eax + /* 0018: */ "\xEB\x06" // jmp L2 + /* 001A: L1: */ "\xFF\x15\x38\x00\x00\x00" // call GetLastError + // 0x00000038 = X64_ADDR_GetLastError - (0x001A + 6) + /* 0020: L2: */ "\x48\x83\xC4\x28" // add rsp,28h + /* 0024: */ "\xC3" // ret + +// ---------- call FreeLibrary ---------- +#define X64_UNINJECTION_CODE_OFFSET 0x25 + /* 0025: */ "\x48\x83\xEC\x28" // sub rsp,28h + /* 0029: */ "\xFF\x15\x21\x00\x00\x00" // call FreeLibrary + // 0x00000021 = X64_ADDR_FreeLibrary - (0x0029 + 6) + /* 002F: */ "\x85\xC0" // test eax,eax + /* 0031: */ "\x74\x04" // je L1 + /* 0033: */ "\x33\xC0" // xor eax,eax + /* 0035: */ "\xEB\x06" // jmp L2 + /* 0037: L1: */ "\xFF\x15\x1B\x00\x00\x00" // call GetLastError + // 0x0000001B = X64_ADDR_GetLastError - (0x0037 + 6) + /* 003D: L2: */ "\x48\x83\xC4\x28" // add rsp,28h + /* 0041: */ "\xC3" // ret + + // padding + /* 0042: */ "\x90\x90\x90\x90\x90\x90" + +// ---------- literal pool ---------- +#define X64_ADDR_LoadLibraryW 0x0048 + /* 0048: */ "\x90\x90\x90\x90\x90\x90\x90\x90" +#define X64_ADDR_FreeLibrary 0x0050 + /* 0050: */ "\x90\x90\x90\x90\x90\x90\x90\x90" +#define X64_ADDR_GetLastError 0x0058 + /* 0058: */ "\x90\x90\x90\x90\x90\x90\x90\x90"; + +#define X64_CODE_SIZE 0x0060 +#endif + +#if defined(_M_AMD64) || defined(_M_IX86) +static const char x86_code_template[] = + // ---------- call LoadLibraryW ---------- + /* 0000: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4] +#define X86_CALL_LoadLibraryW 0x0004 + /* 0004: */ "\xE8\x00\x00\x00\x00" // call LoadLibraryW@4 + /* 0009: */ "\x85\xC0" // test eax,eax + /* 000B: */ "\x74\x09" // je L1 +#define X86_MOV_EAX 0x000D + /* 000D: */ "\xA3\x00\x00\x00\x00" // mov dword ptr [load_address], eax + /* 0012: */ "\x33\xC0" // xor eax,eax + /* 0014: */ "\xEB\x05" // jmp L2 +#define X86_CALL_GetLastError1 0x0016 + /* 0016: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0 + /* 001B: L2: */ "\xC2\x04\x00" // ret 4 + +// ---------- call FreeLibrary ---------- +#define X86_UNINJECTION_CODE_OFFSET 0x001E + /* 001E: */ "\xFF\x74\x24\x04" // push dword ptr [esp+4] +#define X86_CALL_FreeLibrary 0x0022 + /* 0022: */ "\xE8\x00\x00\x00\x00" // call FreeLibrary@4 + /* 0027: */ "\x85\xC0" // test eax,eax + /* 0029: */ "\x74\x04" // je L1 + /* 002B: */ "\x33\xC0" // xor eax,eax + /* 002D: */ "\xEB\x05" // jmp L2 +#define X86_CALL_GetLastError2 0x002F + /* 002F: L1: */ "\xE8\x00\x00\x00\x00" // call GetLastError@0 + /* 0034: L2: */ "\xC2\x04\x00" // ret 4 + ; + +#define X86_CODE_SIZE 0x0037 +#endif + +#ifdef _M_AMD64 +#define CURRENT_ARCH "x64" +#define CODE_SIZE X64_CODE_SIZE +#endif + +#ifdef _M_IX86 +#define CURRENT_ARCH "x86" +#define CODE_SIZE X86_CODE_SIZE +#endif + +static void set_errmsg(const char *format, ...); +static const char *w32strerr(DWORD err); +static USHORT process_arch(HANDLE hProcess); +static const char *arch_name(USHORT arch); + +struct injector { + HANDLE hProcess; + char *remote_mem; + char *injection_code; + char *uninjection_code; +}; + +static BOOL init(void) +{ + SYSTEM_INFO si; + HANDLE hToken; + LUID luid; + TOKEN_PRIVILEGES tp; + HMODULE kernel32 = GetModuleHandleA("kernel32"); + if (kernel32 == 0) { + return FALSE; + } + GetSystemInfo(&si); + page_size = si.dwPageSize; + func_LoadLibraryW = (size_t)GetProcAddress(kernel32, "LoadLibraryW"); + func_FreeLibrary = (size_t)GetProcAddress(kernel32, "FreeLibrary"); + func_GetLastError = (size_t)GetProcAddress(kernel32, "GetLastError"); + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { + return FALSE; + } + if (!LookupPrivilegeValue(0, SE_DEBUG_NAME, &luid)) { + CloseHandle(hToken); + return FALSE; + } + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) { + CloseHandle(hToken); + return FALSE; + } + CloseHandle(hToken); + return TRUE; +} + +#if defined(_M_AMD64) +static int cmp_func(const void *context, const void *key, const void *datum) +{ + ptrdiff_t rva_to_va = (ptrdiff_t)context; + const char *k = (const char *)key; + const char *d = (const char *)(rva_to_va + *(const DWORD *)datum); + return strcmp(k, d); +} + +static int funcaddr(DWORD pid, size_t *load_library, size_t *free_library, size_t *get_last_error) +{ + HANDLE hSnapshot; + MODULEENTRY32W me; + BOOL ok; + HANDLE hFile = INVALID_HANDLE_VALUE; + HANDLE hFileMapping = NULL; + void *base = NULL; + IMAGE_NT_HEADERS *nt_hdrs; + ULONG exp_size; + const IMAGE_EXPORT_DIRECTORY *exp; + const DWORD *names, *name, *funcs; + const WORD *ordinals; + ptrdiff_t rva_to_va; + int rv = INJERR_OTHER; + + /* Get the full path of kernel32.dll. */ +retry: + hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); + if (hSnapshot == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + switch (err) { + case ERROR_BAD_LENGTH: + goto retry; + case ERROR_ACCESS_DENIED: + rv = INJERR_PERMISSION; + break; + case ERROR_INVALID_PARAMETER: + rv = INJERR_NO_PROCESS; + break; + default: + rv = INJERR_OTHER; + } + set_errmsg("CreateToolhelp32Snapshot error: %s", w32strerr(err)); + return rv; + } + me.dwSize = sizeof(me); + for (ok = Module32FirstW(hSnapshot, &me); ok; ok = Module32NextW(hSnapshot, &me)) { + if (wcsicmp(me.szModule, L"kernel32.dll") == 0) { + break; + } + } + CloseHandle(hSnapshot); + if (!ok) { + set_errmsg("kernel32.dll could not be found."); + return INJERR_OTHER; + } + + /* Get the export directory in the kernel32.dll. */ + hFile = CreateFileW(me.szExePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + set_errmsg("failed to open file %s: %s", me.szExePath, w32strerr(GetLastError())); + goto exit; + } + hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hFileMapping == NULL) { + set_errmsg("failed to create file mapping of %s: %s", me.szExePath, w32strerr(GetLastError())); + goto exit; + } + base = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0); + if (base == NULL) { + set_errmsg("failed to map file %s to memory: %s", me.szExePath, w32strerr(GetLastError())); + goto exit; + } + nt_hdrs = ImageNtHeader(base); + if (nt_hdrs == NULL) { + set_errmsg("ImageNtHeader error: %s", w32strerr(GetLastError())); + goto exit; + } + exp = (const IMAGE_EXPORT_DIRECTORY *)ImageDirectoryEntryToDataEx(base, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, + &exp_size, NULL); + if (exp == NULL) { + set_errmsg("ImageDirectoryEntryToDataEx error: %s", w32strerr(GetLastError())); + goto exit; + } + if (exp->NumberOfNames == 0) { + set_errmsg("No export entires are not found."); + goto exit; + } + names = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNames, NULL); + if (names == NULL) { + set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); + goto exit; + } + ordinals = (const WORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfNameOrdinals, NULL); + if (ordinals == NULL) { + set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); + goto exit; + } + funcs = (const DWORD *)ImageRvaToVa(nt_hdrs, base, exp->AddressOfFunctions, NULL); + if (funcs == NULL) { + set_errmsg("ImageRvaToVa error: %s", w32strerr(GetLastError())); + goto exit; + } + rva_to_va = (size_t)ImageRvaToVa(nt_hdrs, base, names[0], NULL) - (size_t)names[0]; + + /* Find the address of LoadLibraryW */ + name = bsearch_s((void *)"LoadLibraryW", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); + if (name == NULL) { + set_errmsg("Could not find the address of LoadLibraryW"); + goto exit; + } + *load_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; + + /* Find the address of FreeLibrary */ + name = bsearch_s((void *)"FreeLibrary", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); + if (name == NULL) { + set_errmsg("Could not find the address of FreeLibrary"); + goto exit; + } + *free_library = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; + + /* Find the address of GetLastError */ + name = bsearch_s((void *)"GetLastError", names, exp->NumberOfNames, sizeof(DWORD), cmp_func, (void *)rva_to_va); + if (name == NULL) { + set_errmsg("Could not find the address of GetLastError"); + goto exit; + } + *get_last_error = (size_t)me.modBaseAddr + funcs[ordinals[name - names]]; + rv = 0; +exit: + if (base != NULL) { + UnmapViewOfFile(base); + } + if (hFileMapping != NULL) { + CloseHandle(hFileMapping); + } + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + } + return rv; +} +#endif + +int cki_attach(injector_t **injector_out, DWORD pid) +{ + injector_t *injector; + DWORD dwDesiredAccess = PROCESS_QUERY_LIMITED_INFORMATION | /* for IsWow64Process() */ + PROCESS_CREATE_THREAD | /* for CreateRemoteThread() */ + PROCESS_VM_OPERATION | /* for VirtualAllocEx() */ + PROCESS_VM_READ | /* for ReadProcessMemory() */ + PROCESS_VM_WRITE; /* for WriteProcessMemory() */ + USHORT arch; + DWORD old_protect; + SIZE_T written; + int rv; + char code[CODE_SIZE]; + size_t code_size; + size_t load_library, free_library, get_last_error; + + if (page_size == 0) { + init(); + } + + load_library = func_LoadLibraryW; + free_library = func_FreeLibrary; + get_last_error = func_GetLastError; + + injector = (injector_t *)calloc(1, sizeof(injector_t)); + if (injector == NULL) { + set_errmsg("malloc error: %s", strerror(errno)); + return INJERR_NO_MEMORY; + } + injector->hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); + if (injector->hProcess == NULL) { + DWORD err = GetLastError(); + set_errmsg("OpenProcess error: %s", w32strerr(err)); + switch (err) { + case ERROR_ACCESS_DENIED: + rv = INJERR_PERMISSION; + break; + case ERROR_INVALID_PARAMETER: + rv = INJERR_NO_PROCESS; + break; + default: + rv = INJERR_OTHER; + } + goto error_exit; + } + + arch = process_arch(injector->hProcess); + switch (arch) { +#ifdef _M_AMD64 + case IMAGE_FILE_MACHINE_AMD64: + break; + case IMAGE_FILE_MACHINE_I386: + rv = funcaddr(pid, &load_library, &free_library, &get_last_error); + if (rv != 0) { + goto error_exit; + } + break; +#endif + +#ifdef _M_IX86 + case IMAGE_FILE_MACHINE_I386: + break; +#endif + default: + set_errmsg("%s target process isn't supported by %s process.", arch_name(arch), CURRENT_ARCH); + rv = INJERR_UNSUPPORTED_TARGET; + goto error_exit; + } + + injector->remote_mem + = (char *)VirtualAllocEx(injector->hProcess, NULL, 2 * page_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (injector->remote_mem == NULL) { + set_errmsg("VirtualAllocEx error: %s", w32strerr(GetLastError())); + rv = INJERR_OTHER; + goto error_exit; + } + + injector->injection_code = injector->remote_mem; + switch (arch) { +#ifdef _M_AMD64 + case IMAGE_FILE_MACHINE_AMD64: /* x64 */ + memcpy(code, x64_code_template, X64_CODE_SIZE); + code_size = X64_CODE_SIZE; + *(size_t *)(code + X64_ADDR_LoadLibraryW) = load_library; + *(size_t *)(code + X64_ADDR_FreeLibrary) = free_library; + *(size_t *)(code + X64_ADDR_GetLastError) = get_last_error; + injector->uninjection_code = injector->remote_mem + X64_UNINJECTION_CODE_OFFSET; + break; +#endif + +#if defined(_M_AMD64) || defined(_M_IX86) + case IMAGE_FILE_MACHINE_I386: /* x86 */ + memcpy(code, x86_code_template, X86_CODE_SIZE); + code_size = X86_CODE_SIZE; +#define FIX_CALL_RELATIVE(addr, offset) \ + *(uint32_t *)(code + offset + 1) = addr - ((uint32_t)(size_t)injector->remote_mem + offset + 5) + FIX_CALL_RELATIVE(load_library, X86_CALL_LoadLibraryW); + FIX_CALL_RELATIVE(free_library, X86_CALL_FreeLibrary); + FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError1); + FIX_CALL_RELATIVE(get_last_error, X86_CALL_GetLastError2); + *(uint32_t *)(code + X86_MOV_EAX + 1) = (uint32_t)(size_t)injector->remote_mem + page_size; + injector->uninjection_code = injector->remote_mem + X86_UNINJECTION_CODE_OFFSET; + break; +#endif + default: + set_errmsg("Never reach here: arch=0x%x", arch); + rv = INJERR_OTHER; + goto error_exit; + } + + if (!WriteProcessMemory(injector->hProcess, injector->remote_mem, code, code_size, &written)) { + set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError())); + rv = INJERR_OTHER; + goto error_exit; + } + + if (!VirtualProtectEx(injector->hProcess, injector->remote_mem, page_size, PAGE_EXECUTE_READ, &old_protect)) { + set_errmsg("VirtualProtectEx error: %s", w32strerr(GetLastError())); + rv = INJERR_OTHER; + goto error_exit; + } + + *injector_out = injector; + return 0; + +error_exit: + cki_detach(injector); + return rv; +} + +int cki_inject(injector_t *injector, const char *path, void **handle) +{ + DWORD pathlen = (DWORD)strlen(path); + wchar_t *wpath; + DWORD wpathlen; + + if (pathlen == 0) { + set_errmsg("The specified path is empty."); + return INJERR_FILE_NOT_FOUND; + } + if (pathlen > MAX_PATH) { + set_errmsg("too long file path: %s", path); + return INJERR_FILE_NOT_FOUND; + } + + wpath = (wchar_t *)_alloca((pathlen + 1) * sizeof(wchar_t)); + wpathlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, path, pathlen, wpath, pathlen + 1); + wpath[wpathlen] = L'\0'; + return cki_inject_w(injector, wpath, handle); +} + +int cki_inject_w(injector_t *injector, const wchar_t *path, void **handle) +{ + struct { + void *load_address; + wchar_t fullpath[MAX_PATH]; + } data = { + NULL, + }; + DWORD pathlen; + SIZE_T written; + HANDLE hThread; + DWORD err; + + pathlen = GetFullPathNameW(path, MAX_PATH, data.fullpath, NULL); + if (pathlen > MAX_PATH) { + set_errmsg("too long file path: %S", path); + return INJERR_FILE_NOT_FOUND; + } + if (pathlen == 0) { + set_errmsg("failed to get the full path: %S", path); + return INJERR_FILE_NOT_FOUND; + } + if (!WriteProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(data), &written)) { + set_errmsg("WriteProcessMemory error: %s", w32strerr(GetLastError())); + return INJERR_OTHER; + } + hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->injection_code, + injector->remote_mem + page_size + sizeof(void *), 0, NULL); + if (hThread == NULL) { + set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError())); + return INJERR_OTHER; + } + WaitForSingleObject(hThread, INFINITE); + GetExitCodeThread(hThread, &err); + CloseHandle(hThread); + if (err != 0) { + set_errmsg("LoadLibrary in the target process failed: %s", w32strerr(err)); + return INJERR_ERROR_IN_TARGET; + } + if (!ReadProcessMemory(injector->hProcess, injector->remote_mem + page_size, &data, sizeof(void *), &written)) { + set_errmsg("ReadProcessMemory error: %s", w32strerr(GetLastError())); + return INJERR_OTHER; + } + if (handle != NULL) { + *handle = data.load_address; + } + return 0; +} + +int cki_uninject(injector_t *injector, void *handle) +{ + HANDLE hThread; + DWORD err; + + hThread = CreateRemoteThread(injector->hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)injector->uninjection_code, + handle, 0, NULL); + if (hThread == NULL) { + set_errmsg("CreateRemoteThread error: %s", w32strerr(GetLastError())); + return INJERR_OTHER; + } + WaitForSingleObject(hThread, INFINITE); + GetExitCodeThread(hThread, &err); + CloseHandle(hThread); + if (err != 0) { + set_errmsg("FreeLibrary in the target process failed: %s", w32strerr(err)); + return INJERR_ERROR_IN_TARGET; + } + return 0; +} + +int cki_detach(injector_t *injector) +{ + if (injector->remote_mem != NULL) { + VirtualFreeEx(injector->hProcess, injector->remote_mem, 0, MEM_RELEASE); + } + if (injector->hProcess != NULL) { + CloseHandle(injector->hProcess); + } + free(injector); + return 0; +} + +const char *cki_error(void) { return errmsg; } + +static void set_errmsg(const char *format, ...) +{ + va_list ap; + int rv; + + va_start(ap, format); + rv = vsnprintf(errmsg, sizeof(errmsg), format, ap); + va_end(ap); + if (rv == -1 || rv >= sizeof(errmsg)) { + errmsg[sizeof(errmsg) - 1] = '\0'; + } +} + +static const char *w32strerr(DWORD err) +{ + static char errmsg[512]; + DWORD len; + + len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errmsg, sizeof(errmsg), NULL); + if (len > 0) { + while (len > 0) { + char c = errmsg[len - 1]; + if (c == ' ' || c == '\n' || c == '\r') { + len--; + } else { + break; + } + } + errmsg[len] = '\0'; + } else if ((int)err >= 0) { + sprintf(errmsg, "win32 error code %d", err); + } else { + sprintf(errmsg, "win32 error code 0x%x", err); + } + return errmsg; +} + +static USHORT process_arch(HANDLE hProcess) +{ + static IsWow64Process2_t IsWow64Process2_func = (IsWow64Process2_t)-1; + if (IsWow64Process2_func == (IsWow64Process2_t)-1) { + IsWow64Process2_func = (IsWow64Process2_t)GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process2"); + } + if (IsWow64Process2_func != NULL) { + /* Windows 10 */ + USHORT process_machine; + USHORT native_machine; + if (IsWow64Process2_func(hProcess, &process_machine, &native_machine)) { + if (process_machine != IMAGE_FILE_MACHINE_UNKNOWN) { + return process_machine; + } else { + return native_machine; + } + } + } else { + /* Windows 8.1 or earlier */ + /* arch will be either x86 or x64. */ +#ifdef _M_AMD64 + BOOL is_wow64_proc; + if (IsWow64Process(hProcess, &is_wow64_proc)) { + if (is_wow64_proc) { + return IMAGE_FILE_MACHINE_I386; + } else { + return IMAGE_FILE_MACHINE_AMD64; + } + } +#endif +#ifdef _M_IX86 + BOOL is_wow64_proc; + if (IsWow64Process(GetCurrentProcess(), &is_wow64_proc)) { + if (!is_wow64_proc) { + /* Run on 32-bit Windows */ + return IMAGE_FILE_MACHINE_I386; + } + /* Run on Windows x64 */ + if (IsWow64Process(hProcess, &is_wow64_proc)) { + if (is_wow64_proc) { + return IMAGE_FILE_MACHINE_I386; + } else { + return IMAGE_FILE_MACHINE_AMD64; + } + } + } +#endif + } + return IMAGE_FILE_MACHINE_UNKNOWN; +} + +static const char *arch_name(USHORT arch) +{ + switch (arch) { + case IMAGE_FILE_MACHINE_AMD64: + return "x64"; + case IMAGE_FILE_MACHINE_I386: + return "x86"; + default: + return "unknown"; + } +} + +BOOL InjectDll(DWORD pid, const WCHAR *dllpath) +{ + if (cki_attach(&g_injector, pid) != 0) { + printf("%s\n", cki_error()); + return FALSE; + } + if (cki_inject_w(g_injector, dllpath, NULL) == 0) { + return TRUE; + } else { + fprintf(stderr, " %s\n", cki_error()); + return FALSE; + } +} + +BOOL EnjectDll(DWORD pid, const WCHAR *dllname) +{ + if (cki_detach(g_injector) == 0) { + return TRUE; + } + return FALSE; +} diff --git a/SDK/injector.h b/SDK/injector.h new file mode 100644 index 0000000..6dc4d0c --- /dev/null +++ b/SDK/injector.h @@ -0,0 +1,40 @@ +#ifndef __INJECTOR_H__ +#define __INJECTOR_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define INJERR_SUCCESS 0 +#define INJERR_OTHER -1 +#define INJERR_NO_MEMORY -2 +#define INJERR_NO_PROCESS -3 +#define INJERR_NO_LIBRARY -4 +#define INJERR_NO_FUNCTION -4 +#define INJERR_ERROR_IN_TARGET -5 +#define INJERR_FILE_NOT_FOUND -6 +#define INJERR_INVALID_MEMORY_AREA -7 +#define INJERR_PERMISSION -8 +#define INJERR_UNSUPPORTED_TARGET -9 +#define INJERR_INVALID_ELF_FORMAT -10 +#define INJERR_WAIT_TRACEE -11 + +typedef struct injector injector_t; + +int cki_attach(injector_t **injector, DWORD pid); +int cki_inject(injector_t *injector, const char *path, void **handle); +int cki_inject_w(injector_t *injector, const wchar_t *path, void **handle); +int cki_uninject(injector_t *injector, void *handle); +int cki_detach(injector_t *injector); +const char *cki_error(void); + +BOOL InjectDll(DWORD pid, const WCHAR *dllpath); +BOOL EnjectDll(DWORD pid, const WCHAR *dllname); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif diff --git a/SDK/sdk.cpp b/SDK/sdk.cpp new file mode 100644 index 0000000..b21f61f --- /dev/null +++ b/SDK/sdk.cpp @@ -0,0 +1,282 @@ +#include "Shlwapi.h" +#include "framework.h" +#include +#include +#include +#include +#include +#include + +#include "../Rpc/rpc_h.h" +#pragma comment(lib, "Rpcrt4.lib") + +#include "injector.h" +#include "rpc_types.h" +#include "sdk.h" +#include "util.h" + +static HANDLE hEvent; +static std::queue MsgQueue; +static RPC_WSTR pszStringBinding = NULL; +static std::function cbReceiveTextMsg; +static const MsgTypesMap_t WxMsgTypes = MsgTypesMap_t { { 0x01, L"文字" }, + { 0x03, L"图片" }, + { 0x22, L"语音" }, + { 0x25, L"好友确认" }, + { 0x28, L"POSSIBLEFRIEND_MSG" }, + { 0x2A, L"名片" }, + { 0x2B, L"视频" }, + { 0x2F, L"石头剪刀布 | 表情图片" }, + { 0x30, L"位置" }, + { 0x31, L"共享实时位置、文件、转账、链接" }, + { 0x32, L"VOIPMSG" }, + { 0x33, L"微信初始化" }, + { 0x34, L"VOIPNOTIFY" }, + { 0x35, L"VOIPINVITE" }, + { 0x3E, L"小视频" }, + { 0x270F, L"SYSNOTICE" }, + { 0x2710, L"红包、系统消息" }, + { 0x2712, L"撤回消息" } }; + +RPC_STATUS RpcConnectServer() +{ + RPC_STATUS status = 0; + // Creates a string binding handle. + status = RpcStringBindingCompose(NULL, // UUID to bind to + reinterpret_cast((RPC_WSTR)L"ncalrpc"), // Use TCP/IP protocol + NULL, // TCP/IP network address to use + reinterpret_cast((RPC_WSTR)L"tmp_endpoint"), // TCP/IP port to use + NULL, // Protocol dependent network options to use + &pszStringBinding); // String binding output + + if (status) + return status; + + /* Validates the format of the string binding handle and converts it to a binding handle. + pszStringBinding: The string binding to validate + hSpyBinding: Put the result in the implicit binding(defined in the IDL file) + */ + status = RpcBindingFromStringBinding(pszStringBinding, &hSpyBinding); + + return status; +} + +RPC_STATUS RpcDisconnectServer() +{ + RPC_STATUS status; + // Free the memory allocated by a string + status = RpcStringFree(&pszStringBinding); + if (status) + return status; + + // Releases binding handle resources and disconnects from the server + status = RpcBindingFree(&hSpyBinding); + + return status; +} + +int WxInitSDK() +{ + int loginFlag = 0; + unsigned long ulCode = 0; + DWORD status = 0; + DWORD pid = 0; + WCHAR DllPath[MAX_PATH] = { 0 }; + + GetModuleFileNameW(GetModuleHandleW(WECHATSDKDLL), DllPath, MAX_PATH); + PathRemoveFileSpecW(DllPath); + PathAppendW(DllPath, WECHATINJECTDLL); + + if (!PathFileExistsW(DllPath)) { + return ERROR_FILE_NOT_FOUND; + } + + status = OpenWeChat(&pid); + if (status != 0) { + return status; + } + + if (!InjectDll(pid, DllPath)) { + return -1; + } + + RpcConnectServer(); + + while (!loginFlag) { + RpcTryExcept + { + // 查询登录状态 + loginFlag = client_IsLogin(); + } + RpcExcept(1) + { + ulCode = RpcExceptionCode(); + printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); + } + RpcEndExcept + + Sleep(1000); + } + + return ERROR_SUCCESS; +} + +static unsigned int __stdcall waitForMsg(void *p) +{ + RpcMessage_t *rpcMsg; + while (true) { + // 中断式,兼顾及时性和CPU使用率 + WaitForSingleObject(hEvent, INFINITE); // 等待消息 + while (!MsgQueue.empty()) { + rpcMsg = (RpcMessage_t *)&MsgQueue.front(); + WxMessage_t msg; + msg.id = wstring(rpcMsg->id); + msg.self = rpcMsg->self; + msg.type = rpcMsg->type; + msg.source = rpcMsg->source; + msg.xml = wstring(rpcMsg->xml); + msg.wxId = wstring(rpcMsg->wxId); + msg.roomId = wstring(rpcMsg->roomId); + msg.content = wstring(rpcMsg->content); + + try { + cbReceiveTextMsg(msg); // 调用接收消息回调 + } catch (...) { + printf("callback error...\n"); + } + MsgQueue.pop(); + } + ResetEvent(hEvent); + } + + return 0; +} + +static unsigned int __stdcall innerWxSetTextMsgCb(void *p) +{ + unsigned long ulCode = 0; + RpcTryExcept + { + // 建立RPC通道,让服务端能够调用客户端的回调函数。(该接口会被服务端阻塞直到异常退出) + client_EnableReceiveMsg(); + } + RpcExcept(1) + { + ulCode = RpcExceptionCode(); + printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); + } + RpcEndExcept + + return 0; +} + +int WxSetTextMsgCb(const std::function &onMsg) +{ + if (onMsg) { + HANDLE msgThread; + cbReceiveTextMsg = onMsg; + hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + msgThread = (HANDLE)_beginthreadex(NULL, 0, waitForMsg, NULL, 0, NULL); + if (msgThread == NULL) { + printf("Failed to create message listening thread.\n"); + return -2; + } + CloseHandle(msgThread); + + msgThread = (HANDLE)_beginthreadex(NULL, 0, innerWxSetTextMsgCb, NULL, 0, NULL); + if (msgThread == NULL) { + printf("Failed to create innerWxRecvTextMsg.\n"); + return -2; + } + CloseHandle(msgThread); + + return 0; + } + + printf("Empty Callback.\n"); + return -1; +} + +int server_ReceiveMsg(RpcMessage_t *rpcMsg) +{ + MsgQueue.push(*rpcMsg); // 发送消息 + SetEvent(hEvent); // 发送消息通知 + return 0; +} + +static int innerWxSendTextMsg(const wchar_t *wxid, const wchar_t *at_wxid, const wchar_t *msg) +{ + int ret = 0; + unsigned long ulCode = 0; + + RpcTryExcept { ret = client_SendTextMsg(wxid, at_wxid, msg); } + RpcExcept(1) + { + ulCode = RpcExceptionCode(); + printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); + } + RpcEndExcept + + return ret; +} + +int WxSendTextMsg(wstring wxid, wstring at_wxid, wstring msg) +{ + return innerWxSendTextMsg(wxid.c_str(), at_wxid.c_str(), msg.c_str()); +} + +static int getAddrHandle(DWORD *addr, HANDLE *handle) +{ + DWORD processID = 0; + wstring processName = L"WeChat.exe"; + wstring moduleName = L"WeChatWin.dll"; + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; + while (Process32Next(hSnapshot, &pe32)) { + wstring strProcess = pe32.szExeFile; + if (strProcess == processName) { + processID = pe32.th32ProcessID; + break; + } + } + CloseHandle(hSnapshot); + if (processID == 0) { + printf("Failed to get Process ID\r\n"); + return -1; + } + + HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processID); + if (hProcessSnapshot == INVALID_HANDLE_VALUE) { + printf("Failed to get Process Snapshot\r\n"); + return -2; + } + + MODULEENTRY32 me32; + SecureZeroMemory(&me32, sizeof(MODULEENTRY32)); + me32.dwSize = sizeof(MODULEENTRY32); + while (Module32Next(hProcessSnapshot, &me32)) { + me32.dwSize = sizeof(MODULEENTRY32); + + if (!wcscmp(me32.szModule, moduleName.c_str())) { + *addr = (DWORD)me32.modBaseAddr; + break; + } + } + + CloseHandle(hProcessSnapshot); + if (*addr == 0) { + printf("Failed to get Module Address\r\n"); + return -3; + } + + *handle = OpenProcess(PROCESS_VM_READ, FALSE, processID); + if (*handle == 0) { + printf("Failed to open Process\r\n"); + return -4; + } + + return 0; +} + +MsgTypesMap_t WxGetMsgTypes() { return WxMsgTypes; } diff --git a/SDK/sdk.def b/SDK/sdk.def new file mode 100644 index 0000000..c73f152 --- /dev/null +++ b/SDK/sdk.def @@ -0,0 +1,5 @@ +EXPORTS + WxInitSDK + WxSetTextMsgCb + WxSendTextMsg + WxGetMsgTypes diff --git a/SDK/sdk.h b/SDK/sdk.h new file mode 100644 index 0000000..268485f --- /dev/null +++ b/SDK/sdk.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +using namespace std; + +typedef struct WxMessage { + int self; // 是否自己发的消息:0=否,1=是 + int type; // 消息类型 + int source; // 消息来源:0=好友消息,1=群消息 + wstring id; // 消息ID + wstring xml; // 群其他消息 + wstring wxId; // 发送人微信ID + wstring roomId; // 群ID + wstring content; // 消息内容,MAC版最大:16384,即16KB +} WxMessage_t; + +typedef map MsgTypesMap_t; + +int WxInitSDK(); +int WxSetTextMsgCb(const std::function &onMsg); +int WxSendTextMsg(wstring wxid, wstring at_wxid, wstring msg); +MsgTypesMap_t WxGetMsgTypes(); diff --git a/SDK/util.cpp b/SDK/util.cpp new file mode 100644 index 0000000..30baa61 --- /dev/null +++ b/SDK/util.cpp @@ -0,0 +1,191 @@ +#include "Shlwapi.h" +#include "framework.h" +#include +#include +#include + +#include "util.h" + +#pragma comment(lib, "shlwapi") +#pragma comment(lib, "Version.lib") + +using namespace std; + +int GetWeChatPath(wchar_t *path); +int GetWeChatWinDLLPath(wchar_t *path); +int GetWeChatVersion(wchar_t *version); +bool GetFileVersion(const wchar_t *filePath, wchar_t *version); + +int GetWeChatPath(wchar_t *path) +{ + int ret = -1; + HKEY hKey = NULL; + // HKEY_CURRENT_USER\Software\Tencent\WeChat InstallPath = xx + if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\Tencent\\WeChat", &hKey)) { + ret = GetLastError(); + return ret; + } + + DWORD Type = REG_SZ; + DWORD cbData = MAX_PATH * sizeof(WCHAR); + if (ERROR_SUCCESS != RegQueryValueEx(hKey, L"InstallPath", 0, &Type, (LPBYTE)path, &cbData)) { + ret = GetLastError(); + goto __exit; + } + + if (path != NULL) { + PathAppend(path, WECHAREXE); + } + +__exit: + if (hKey) { + RegCloseKey(hKey); + } + + return ERROR_SUCCESS; +} + +int GetWeChatWinDLLPath(wchar_t *path) +{ + int ret = GetWeChatPath(path); + if (ret != ERROR_SUCCESS) { + return ret; + } + + PathRemoveFileSpecW(path); + PathAppendW(path, WECHATWINDLL); + + return ret; +} + +int GetWeChatVersion(wchar_t *version) +{ + WCHAR Path[MAX_PATH] = { 0 }; + + int ret = GetWeChatWinDLLPath(Path); + if (ret != ERROR_SUCCESS) { + return ret; + } + + ret = GetFileVersion(Path, version); + + return ret; +} + +bool GetFileVersion(const wchar_t *filePath, wchar_t *version) +{ + if (wcslen(filePath) > 0 && PathFileExists(filePath)) { + VS_FIXEDFILEINFO *pVerInfo = NULL; + DWORD dwTemp, dwSize; + BYTE *pData = NULL; + UINT uLen; + + dwSize = GetFileVersionInfoSize(filePath, &dwTemp); + if (dwSize == 0) { + return false; + } + + pData = new BYTE[dwSize + 1]; + if (pData == NULL) { + return false; + } + + if (!GetFileVersionInfo(filePath, 0, dwSize, pData)) { + delete[] pData; + return false; + } + + if (!VerQueryValue(pData, TEXT("\\"), (void **)&pVerInfo, &uLen)) { + delete[] pData; + return false; + } + + DWORD verMS = pVerInfo->dwFileVersionMS; + DWORD verLS = pVerInfo->dwFileVersionLS; + DWORD major = HIWORD(verMS); + DWORD minor = LOWORD(verMS); + DWORD build = HIWORD(verLS); + DWORD revision = LOWORD(verLS); + delete[] pData; + + StringCbPrintf(version, 0x20, TEXT("%d.%d.%d.%d"), major, minor, build, revision); + + return true; + } + + return false; +} + +int OpenWeChat(DWORD *pid) +{ + int ret = -1; + STARTUPINFO si = { sizeof(si) }; + PROCESS_INFORMATION pi = { 0 }; + + WCHAR Path[MAX_PATH] = { 0 }; + ret = GetWeChatPath(Path); + if (ERROR_SUCCESS != ret) { + return ret; + } + + if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { + ret = GetLastError(); + return ret; + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + *pid = pi.dwProcessId; + + ret = ERROR_SUCCESS; + + return ret; +} + +int GetWstringByAddress(DWORD address, wchar_t *buffer, DWORD buffer_size) +{ + DWORD strLength = GET_DWORD(address + 4); + if (strLength == 0) { + return 0; + } else if (strLength > buffer_size) { + strLength = buffer_size - 1; + } + + wmemcpy_s(buffer, strLength + 1, GET_WSTRING(address), strLength + 1); + + return strLength; +} + +DWORD GetMemoryIntByAddress(HANDLE hProcess, DWORD address) +{ + DWORD value = 0; + + unsigned char data[4] = { 0 }; + if (ReadProcessMemory(hProcess, (LPVOID)address, data, 4, 0)) { + value = data[0] & 0xFF; + value |= ((data[1] << 8) & 0xFF00); + value |= ((data[2] << 16) & 0xFF0000); + value |= ((data[3] << 24) & 0xFF000000); + } + + return value; +} + +wstring GetUnicodeInfoByAddress(HANDLE hProcess, DWORD address) +{ + wstring value = L""; + + DWORD strAddress = GetMemoryIntByAddress(hProcess, address); + DWORD strLen = GetMemoryIntByAddress(hProcess, address + 0x4); + if (strLen > 500) + return value; + + wchar_t cValue[500] = { 0 }; + memset(cValue, 0, sizeof(cValue) / sizeof(wchar_t)); + if (ReadProcessMemory(hProcess, (LPVOID)strAddress, cValue, (strLen + 1) * 2, 0)) { + value = wstring(cValue); + } + + return value; +} diff --git a/SDK/util.h b/SDK/util.h new file mode 100644 index 0000000..19084e1 --- /dev/null +++ b/SDK/util.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#define WECHAREXE L"WeChat.exe" +#define WECHATWINDLL L"WeChatWin.dll" +#define WECHATSDKDLL L"SDK.dll" +#define WECHATINJECTDLL L"Spy.dll" + +#define GET_DWORD(addr) ((DWORD) * (DWORD *)(addr)) +#define GET_STRING(addr) ((CHAR *)(addr)) +#define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr))) + +int OpenWeChat(DWORD *pid); +int GetWeChatPath(wchar_t *path); +int GetWeChatWinDLLPath(wchar_t *path); +int GetWeChatVersion(wchar_t *version); +bool GetFileVersion(const wchar_t *filePath, wchar_t *version); +int GetWstringByAddress(DWORD address, wchar_t *buffer, DWORD buffer_size); +DWORD GetMemoryIntByAddress(HANDLE hProcess, DWORD address); +std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, DWORD address); diff --git a/SDKpy/SDKpy.vcxproj b/SDKpy/SDKpy.vcxproj new file mode 100644 index 0000000..939c586 --- /dev/null +++ b/SDKpy/SDKpy.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {121ad245-72fb-47a6-99d6-5427b74fa7b9} + SDKpy + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + .pyd + wcferry + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;SDKPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;SDKPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(SolutionDir)SDK;$(SolutionDir)Rpc;C:\Program Files (x86)\Python37-32\Include;C:\Projs\.pyenv\pybind11\lib\site-packages\pybind11\include; + + + Windows + true + true + true + false + C:\Program Files (x86)\Python37-32\libs; + $(OutDir)SDK.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;SDKPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;SDKPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/SDKpy/SDKpy.vcxproj.filters b/SDKpy/SDKpy.vcxproj.filters new file mode 100644 index 0000000..ead8662 --- /dev/null +++ b/SDKpy/SDKpy.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + + + 源文件 + + + \ No newline at end of file diff --git a/SDKpy/SDKpy.vcxproj.user b/SDKpy/SDKpy.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/SDKpy/SDKpy.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/SDKpy/framework.h b/SDKpy/framework.h new file mode 100644 index 0000000..3f0fc4a --- /dev/null +++ b/SDKpy/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/SDKpy/sdkpy.cpp b/SDKpy/sdkpy.cpp new file mode 100644 index 0000000..598697d --- /dev/null +++ b/SDKpy/sdkpy.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "sdk.h" + +namespace py = pybind11; + +int WxSetTextMsgCbPy(const std::function &onMsg) { return WxSetTextMsgCb(onMsg); } + +PYBIND11_MODULE(wcferry, m) +{ + m.doc() = "SDK python API"; + + py::class_(m, "WxMessage") + .def_readonly("id", &WxMessage::id) + .def_readonly("self", &WxMessage::self) + .def_readonly("type", &WxMessage::type) + .def_readonly("source", &WxMessage::source) + .def_readonly("xml", &WxMessage::xml) + .def_readonly("wxId", &WxMessage::wxId) + .def_readonly("roomId", &WxMessage::roomId) + .def_readonly("content", &WxMessage::content); + + m.def("WxInitSDK", &WxInitSDK); + m.def("WxSetTextMsgCb", &WxSetTextMsgCbPy); + m.def("WxSendTextMsg", &WxSendTextMsg); + m.def("WxGetMsgTypes", &WxGetMsgTypes, py::return_value_policy::reference); + +#ifdef VERSION_INFO + m.attr("__version__") = VERSION_INFO; +#else + m.attr("__version__") = "dev"; +#endif +} diff --git a/Spy/Spy.vcxproj b/Spy/Spy.vcxproj new file mode 100644 index 0000000..2b4569a --- /dev/null +++ b/Spy/Spy.vcxproj @@ -0,0 +1,200 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {4a0a22f7-0b1c-4565-a443-c43fac6c6396} + Spy + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(SolutionDir)Rpc;$(SolutionDir)SDK;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + Windows + true + false + + + /app_config /prefix client "client_" server "server_" + $(SolutionDir)Rpc;%(AdditionalIncludeDirectories) + + + + + Level3 + true + true + true + WIN32;NDEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(SolutionDir)Rpc;$(SolutionDir)SDK;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + Windows + true + true + true + false + + + /prefix client "client_" server "server_" + $(SolutionDir)Rpc;%(AdditionalIncludeDirectories) + + + + + Level3 + true + _DEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)Rpc + /app_config /ms_ext /prefix client "client_" server "server_" + $(SolutionDir)Rpc;%(AdditionalIncludeDirectories) + $(SolutionDir)Rpc + /app_config /ms_ext /prefix client "client_" server "server_" %(AdditionalOptions) + + + + + + \ No newline at end of file diff --git a/Spy/Spy.vcxproj.filters b/Spy/Spy.vcxproj.filters new file mode 100644 index 0000000..d34335b --- /dev/null +++ b/Spy/Spy.vcxproj.filters @@ -0,0 +1,86 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {37eb8a5c-e792-4c10-a858-9abc84f79f80} + + + + + 头文件 + + + 头文件 + + + 头文件 + + + Rpc + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + + + Rpc + + + \ No newline at end of file diff --git a/Spy/Spy.vcxproj.user b/Spy/Spy.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/Spy/Spy.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Spy/dllmain.cpp b/Spy/dllmain.cpp new file mode 100644 index 0000000..01dc863 --- /dev/null +++ b/Spy/dllmain.cpp @@ -0,0 +1,37 @@ +#include + +#include "monitor.h" +#include "rpc_server.h" + +extern HANDLE g_hEvent; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: { + // MessageBox(NULL, L"RpcStartServer", L"Hey", 0); + if (InitDLL() != 0) { + // Exit + FreeLibraryAndExitThread(hModule, 0); + } + HANDLE rpcThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RpcStartServer, hModule, NULL, 0); + if (rpcThread != 0) { + CloseHandle(rpcThread); + } + g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建消息句柄 + HANDLE mThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Monitor, hModule, NULL, 0); + if (mThread != 0) { + CloseHandle(mThread); + } + break; + } + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: { + RpcStopServer(); + break; + } + } + return TRUE; +} diff --git a/Spy/framework.h b/Spy/framework.h new file mode 100644 index 0000000..e526216 --- /dev/null +++ b/Spy/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/Spy/load_calls.cpp b/Spy/load_calls.cpp new file mode 100644 index 0000000..17e827c --- /dev/null +++ b/Spy/load_calls.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "load_calls.h" + +std::map wxCalls { + { L"3.0.0.57", + { 0x1874F38, // Login Status + { 0x1856BF0, 0x1856A8C, 0x1856AC0 }, // User Info: wxid, nickname, mobile + 0x38D8A0, // Send Message + // Receive Message: + // Hook, call, type, self, id, msgXml, roomId, wxId, content + { 0x36A350, 0x36A5A0, 0x30, 0x34, 0x164, 0x1A4, 0x40, 0x150, 0x68 } } } +}; + +int LoadCalls(const wchar_t *version, WxCalls_t *calls) +{ + auto iter = wxCalls.find(version); + if (iter == wxCalls.end()) { + return -1; + } + + memcpy_s(calls, sizeof(WxCalls_t), &(iter->second), sizeof(WxCalls_t)); + + return 0; +} diff --git a/Spy/load_calls.h b/Spy/load_calls.h new file mode 100644 index 0000000..e95dae0 --- /dev/null +++ b/Spy/load_calls.h @@ -0,0 +1,5 @@ +#pragma once + +#include "spy_types.h" + +int LoadCalls(const wchar_t *version, WxCalls_t *calls); diff --git a/Spy/monitor.cpp b/Spy/monitor.cpp new file mode 100644 index 0000000..50d4c92 --- /dev/null +++ b/Spy/monitor.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "load_calls.h" +#include "monitor.h" +#include "receive_msg.h" +#include "util.h" + +HANDLE g_hEvent = NULL; +WxCalls_t g_WxCalls = { 0 }; +RpcMessage_t *g_pMsg = NULL; // Find a palce to free +DWORD g_WeChatWinDllAddr = 0; + +int InitDLL(void) +{ + wchar_t version[16] = { 0 }; + + g_WeChatWinDllAddr = (DWORD)LoadLibrary(L"WeChatWin.dll"); //获取wechatWin模块地址 + if (g_WeChatWinDllAddr == 0) { + MessageBox(NULL, L"获取wechatWin.dll模块地址", L"错误", 0); + return -1; + } + + if (!GetWeChatVersion(version)) { //获取微信版本 + MessageBox(NULL, L"获取微信版本失败", L"错误", 0); + return -2; + } + + if (LoadCalls(version, &g_WxCalls) != 0) { //加载微信版本对应的Call地址 + MessageBox(NULL, L"不支持当前版本", L"错误", 0); + return -3; + } + + g_pMsg = new RpcMessage_t; + g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + return 0; +} + +DWORD WINAPI Monitor(HMODULE hModule) +{ + ListenMessage(); + + return TRUE; +} + +int IsLogin(void) +{ + if (g_WeChatWinDllAddr == 0) { + return 0; + } + + return (int)GET_DWORD(g_WeChatWinDllAddr + g_WxCalls.login); +} diff --git a/Spy/monitor.h b/Spy/monitor.h new file mode 100644 index 0000000..c227d21 --- /dev/null +++ b/Spy/monitor.h @@ -0,0 +1,7 @@ +#pragma once + +#include "framework.h" + +int InitDLL(void); +DWORD WINAPI Monitor(HMODULE hModule); +int IsLogin(); diff --git a/Spy/receive_msg.cpp b/Spy/receive_msg.cpp new file mode 100644 index 0000000..ab562b9 --- /dev/null +++ b/Spy/receive_msg.cpp @@ -0,0 +1,79 @@ +#include "framework.h" + +#include "load_calls.h" +#include "receive_msg.h" +#include "spy_types.h" +#include "util.h" + +extern HANDLE g_hEvent; +extern RpcMessage_t *g_pMsg; +extern WxCalls_t g_WxCalls; +extern DWORD g_WeChatWinDllAddr; + +MsgQueue_t g_MsgQueue; +DWORD reg_buffer = 0; +DWORD recvMsgCallAddr = 0; +DWORD recvMsgJumpBackAddr = 0; + +void DispatchMsg(DWORD reg) +{ + DWORD **p = (DWORD **)reg; //消息结构基址 + + memset(g_pMsg, 0, sizeof(RpcMessage_t)); + + g_pMsg->type = GET_DWORD(**p + g_WxCalls.recvMsg.type); + g_pMsg->self = GET_DWORD(**p + g_WxCalls.recvMsg.isSelf); + + GetWstringByAddress(**p + g_WxCalls.recvMsg.msgId, g_pMsg->id, MSG_SIZE_MSG_ID); + GetWstringByAddress(**p + g_WxCalls.recvMsg.msgXml, g_pMsg->xml, MSG_SIZE_MSG_XML); + + if (wcsstr(g_pMsg->xml, L"") == NULL) { + // g_pMsg.roomId = {0}; + GetWstringByAddress(**p + g_WxCalls.recvMsg.roomId, g_pMsg->wxId, MSG_SIZE_WXID); + } else { + g_pMsg->source = 1; + GetWstringByAddress(**p + g_WxCalls.recvMsg.roomId, g_pMsg->roomId, MSG_SIZE_ROOMID); + GetWstringByAddress(**p + g_WxCalls.recvMsg.wxId, g_pMsg->wxId, MSG_SIZE_WXID); + } + GetWstringByAddress(**p + g_WxCalls.recvMsg.content, g_pMsg->content, MSG_SIZE_CONTENT); + g_MsgQueue.push(*g_pMsg); // 发送消息 + SetEvent(g_hEvent); // 发送消息通知 +} + +__declspec(naked) void RecieveMsgHook() +{ + __asm { + push ebp // 保护现场 + add ebp, 0x3C // 地址为 ebp + 0x3C + mov reg_buffer, ebp //把值复制出来 + pop ebp // 还原现场 + } + + DispatchMsg(reg_buffer); + + __asm + { + call recvMsgCallAddr // 这个为被覆盖的call + jmp recvMsgJumpBackAddr // 跳回被HOOK指令的下一条指令 + } +} + +void ListenMessage() +{ + // MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0); + if (g_WeChatWinDllAddr == 0) { + return; + } + + DWORD hookAddress = g_WeChatWinDllAddr + g_WxCalls.recvMsg.hook; + recvMsgCallAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.call; + recvMsgJumpBackAddr = hookAddress + 5; + + BYTE jmpCode[5] = { 0 }; + jmpCode[0] = 0xE9; + + *(DWORD *)&jmpCode[1] = (DWORD)RecieveMsgHook - hookAddress - 5; + + // 6FB6A350 E8 4B020000 call WeChatWi .6FB6A5A0; + WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddress, jmpCode, 5, 0); +} \ No newline at end of file diff --git a/Spy/receive_msg.h b/Spy/receive_msg.h new file mode 100644 index 0000000..b309f0d --- /dev/null +++ b/Spy/receive_msg.h @@ -0,0 +1,3 @@ +#pragma once + +void ListenMessage(); \ No newline at end of file diff --git a/Spy/rpc_server.cpp b/Spy/rpc_server.cpp new file mode 100644 index 0000000..3ba12e4 --- /dev/null +++ b/Spy/rpc_server.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "monitor.h" +#include "rpc_server.h" +#include "send_msg.h" +#include "spy_types.h" + +#include "../Rpc/rpc_h.h" +#pragma comment(lib, "Rpcrt4.lib") + +extern HANDLE g_hEvent; +extern MsgQueue_t g_MsgQueue; + +int server_IsLogin() { return IsLogin(); } + +void server_EnableReceiveMsg() +{ + unsigned long ulCode = 0; + + RpcTryExcept + { + // 调用客户端的回调函数 + while (true) { + // 中断式,兼顾及时性和CPU使用率 + WaitForSingleObject(g_hEvent, INFINITE); // 等待消息 + while (!g_MsgQueue.empty()) { + client_ReceiveMsg((RpcMessage_t *)&g_MsgQueue.front()); // 调用接收消息回调 + g_MsgQueue.pop(); + } + ResetEvent(g_hEvent); + } + } + RpcExcept(1) + { + ulCode = RpcExceptionCode(); + printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode); + } + RpcEndExcept +} + +int server_SendTextMsg(const wchar_t *wxid, const wchar_t *at_wxid, const wchar_t *msg) +{ + SendTextMessage(wxid, at_wxid, msg); + + return 0; +} + +RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE /*hInterface*/, void * /*pBindingHandle*/) +{ + return RPC_S_OK; // Always allow anyone. +} + +int RpcStartServer(HMODULE hModule) +{ + RPC_STATUS status; + // Uses the protocol combined with the endpoint for receiving + // remote procedure calls. + status = RpcServerUseProtseqEp(reinterpret_cast((RPC_WSTR)L"ncalrpc"), // Use TCP/IP protocol + RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Backlog queue length for TCP/IP. + reinterpret_cast((RPC_WSTR)L"tmp_endpoint"), // TCP/IP port to use + NULL // No security + ); + + if (status) + return status; + + // Registers the interface and auto listen + // Equal to RpcServerRegisterIf + RpcServerListen + status = RpcServerRegisterIf2(server_ISpy_v1_0_s_ifspec, // Interface to register. + NULL, // Use the MIDL generated entry-point vector. + NULL, // Use the MIDL generated entry-point vector. + RPC_IF_ALLOW_LOCAL_ONLY | RPC_IF_AUTOLISTEN, // Forces use of security callback. + RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Use default number of concurrent calls. + (unsigned)-1, // Infinite max size of incoming data blocks. + SecurityCallback); // Naive security callback. + + while (1) { + Sleep(10000); // 休眠,释放CPU + } + + return 0; +} + +int RpcStopServer(void) +{ + RPC_STATUS status; + status = RpcMgmtStopServerListening(NULL); + if (status) + return status; + + status = RpcServerUnregisterIf(NULL, NULL, FALSE); + return status; +} diff --git a/Spy/rpc_server.h b/Spy/rpc_server.h new file mode 100644 index 0000000..c0a24aa --- /dev/null +++ b/Spy/rpc_server.h @@ -0,0 +1,5 @@ +#pragma once +#include "framework.h" + +int RpcStartServer(HMODULE hModule); +int RpcStopServer(void); diff --git a/Spy/send_msg.cpp b/Spy/send_msg.cpp new file mode 100644 index 0000000..fbbc6ee --- /dev/null +++ b/Spy/send_msg.cpp @@ -0,0 +1,49 @@ +#include "framework.h" +#include + +#include "spy_types.h" + +extern HANDLE g_hEvent; +extern WxCalls_t g_WxCalls; +extern RpcMessage_t *g_pMsg; +extern DWORD g_WeChatWinDllAddr; + +using namespace std; + +void SendTextMessage(const wchar_t *wxid, const wchar_t *at_wxid, const wchar_t *msg) +{ + if (g_WeChatWinDllAddr == 0) { + return; + } + char buffer[0x5F0] = { 0 }; + TextStruct_t txtWxid = { 0 }; + TextStruct_t txtAtWxid = { 0 }; + TextStruct_t txtMsg = { 0 }; + + wstring wsWxid = wxid; + wstring wsAtWxid = at_wxid; + wstring wsMsg = msg; + + // 发送消息Call地址 = 微信基址 + 偏移 + DWORD sendCallAddress = g_WeChatWinDllAddr + g_WxCalls.sendTextMsg; + + txtWxid.text = (wchar_t *)wsWxid.c_str(); + txtWxid.size = wsWxid.size(); + txtWxid.capacity = wsWxid.capacity(); + + txtMsg.text = (wchar_t *)wsMsg.c_str(); + txtMsg.size = wsMsg.size(); + txtMsg.capacity = wsMsg.capacity(); + + __asm { + lea edx, txtWxid + lea edi, txtAtWxid + lea ebx, txtMsg + push 0x01 + push edi + push ebx + lea ecx, buffer; + call sendCallAddress + add esp, 0xC + } +} diff --git a/Spy/send_msg.h b/Spy/send_msg.h new file mode 100644 index 0000000..408c13e --- /dev/null +++ b/Spy/send_msg.h @@ -0,0 +1,3 @@ +#pragma once + +void SendTextMessage(const wchar_t *wxid, const wchar_t *at_wxid, const wchar_t *msg); diff --git a/Spy/spy_types.h b/Spy/spy_types.h new file mode 100644 index 0000000..a103470 --- /dev/null +++ b/Spy/spy_types.h @@ -0,0 +1,46 @@ +#pragma once + +#include "framework.h" +#include + +#include "rpc_types.h" + +typedef struct UserInfoCall { + DWORD wxid; + DWORD nickName; + DWORD mobile; +} UserInfoCall_t; + +typedef struct RecvMsg { + DWORD hook; // Hook地址 + DWORD call; // Call地址 + DWORD type; // 消息类型地址 + DWORD isSelf; // 是否自己发送标志地址 + DWORD msgId; // 消息ID地址 + DWORD msgXml; // 消息xml内容地址 + DWORD roomId; // 群聊时,为群ID;私聊时,为微信ID + DWORD wxId; // 私聊时,为空;群群时,为发送者微信ID + DWORD content; // 消息内容地址 +} RecvMsg_t; + +typedef struct SendImg { + DWORD call1; + DWORD call2; + DWORD call3; +} SendImg_t; + +typedef struct WxCalls { + DWORD login; // 登录状态 + UserInfoCall_t ui; // 用户信息 + DWORD sendTextMsg; // 发送消息 + RecvMsg_t recvMsg; // 接收消息 +} WxCalls_t; + +typedef struct TextStruct { + wchar_t *text; + DWORD size; + DWORD capacity; + char fill[8]; +} TextStruct_t; + +typedef std::queue MsgQueue_t; diff --git a/WeChatFerry.sln b/WeChatFerry.sln new file mode 100644 index 0000000..9af122e --- /dev/null +++ b/WeChatFerry.sln @@ -0,0 +1,72 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Spy", "Spy\Spy.vcxproj", "{4A0A22F7-0B1C-4565-A443-C43FAC6C6396}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDK", "SDK\SDK.vcxproj", "{707F2DCD-1001-42A7-B20E-B85B1BBAB228}" + ProjectSection(ProjectDependencies) = postProject + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} = {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "App", "App\App.vcxproj", "{44C1A579-22A7-4BA3-A618-45BD01600294}" + ProjectSection(ProjectDependencies) = postProject + {121AD245-72FB-47A6-99D6-5427B74FA7B9} = {121AD245-72FB-47A6-99D6-5427B74FA7B9} + {707F2DCD-1001-42A7-B20E-B85B1BBAB228} = {707F2DCD-1001-42A7-B20E-B85B1BBAB228} + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} = {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDKpy", "SDKpy\SDKpy.vcxproj", "{121AD245-72FB-47A6-99D6-5427B74FA7B9}" + ProjectSection(ProjectDependencies) = postProject + {707F2DCD-1001-42A7-B20E-B85B1BBAB228} = {707F2DCD-1001-42A7-B20E-B85B1BBAB228} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Debug|x64.ActiveCfg = Debug|x64 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Debug|x64.Build.0 = Debug|x64 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Debug|x86.ActiveCfg = Debug|Win32 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Debug|x86.Build.0 = Debug|Win32 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Release|x64.ActiveCfg = Release|x64 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Release|x64.Build.0 = Release|x64 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Release|x86.ActiveCfg = Release|Win32 + {4A0A22F7-0B1C-4565-A443-C43FAC6C6396}.Release|x86.Build.0 = Release|Win32 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Debug|x64.ActiveCfg = Debug|x64 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Debug|x64.Build.0 = Debug|x64 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Debug|x86.ActiveCfg = Debug|Win32 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Debug|x86.Build.0 = Debug|Win32 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Release|x64.ActiveCfg = Release|x64 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Release|x64.Build.0 = Release|x64 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Release|x86.ActiveCfg = Release|Win32 + {707F2DCD-1001-42A7-B20E-B85B1BBAB228}.Release|x86.Build.0 = Release|Win32 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Debug|x64.ActiveCfg = Debug|x64 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Debug|x64.Build.0 = Debug|x64 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Debug|x86.ActiveCfg = Debug|Win32 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Debug|x86.Build.0 = Debug|Win32 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Release|x64.ActiveCfg = Release|x64 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Release|x64.Build.0 = Release|x64 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Release|x86.ActiveCfg = Release|Win32 + {44C1A579-22A7-4BA3-A618-45BD01600294}.Release|x86.Build.0 = Release|Win32 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Debug|x64.ActiveCfg = Debug|x64 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Debug|x64.Build.0 = Debug|x64 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Debug|x86.ActiveCfg = Debug|Win32 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Debug|x86.Build.0 = Debug|Win32 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Release|x64.ActiveCfg = Release|x64 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Release|x64.Build.0 = Release|x64 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Release|x86.ActiveCfg = Release|Win32 + {121AD245-72FB-47A6-99D6-5427B74FA7B9}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {569E7E61-8FD2-43B5-8F34-C61D6417F905} + EndGlobalSection +EndGlobal