diff --git a/.gitignore b/.gitignore index 3d8705a..4e3a0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,14 @@ Debug/ Release/ x64/ -# RPC generated files -*_c.c -*_s.c -*_h.h +# Generated files +*.pb.h +*.pb.cc +sdk.dll +spy.dll +build/ +logs/ + +*_pb2.py +*_pb2_grpc.py +__pycache__ diff --git a/App/App.cpp b/App/App.cpp deleted file mode 100644 index 5d7194f..0000000 --- a/App/App.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include -#include -#include -#include -#include - -#include "sdk.h" -/* -#pragma comment(lib, "SDK.lib") -等效为在属性,链接,输入中添加该依赖 -*/ - -typedef map sqlTypes_t; -static sqlTypes_t sqlTypes - = sqlTypes_t { { 1, L"INTEGER" }, { 2, L"FLOAT" }, { 3, L"TEXT" }, { 4, L"BLOB" }, { 5, L"NULL" } }; - -void printContacts(ContactMap_t contacts) -{ - wprintf(L"contacts number: %ld\n", contacts.size()); - for (auto it = contacts.begin(); it != contacts.end(); it++) { - wprintf(L"%s\t%s\t%s\t%s\t%s\t%s\t%s\r\n", it->second.wxId.c_str(), it->second.wxCode.c_str(), - it->second.wxName.c_str(), it->second.wxGender.c_str(), it->second.wxCountry.c_str(), - it->second.wxProvince.c_str(), it->second.wxCity.c_str()); - } -} - -void printDbNames(vector vDbs) -{ - wprintf(L"db numbers: %ld\n", vDbs.size()); - for (auto it = vDbs.begin(); it != vDbs.end(); it++) { - wprintf(L"%s\n", (*it).c_str()); - } -} - -void printDbTables(DbTableVector_t tables) -{ - wprintf(L"table numbers: %ld\n", tables.size()); - for (auto it = tables.begin(); it != tables.end(); it++) { - wprintf(L"%s\n%s\n\n", (it->table).c_str(), (it->sql).c_str()); - } -} - -void printDbResults(SqlRetVector_t vvResults) -{ - int rows = vvResults.size(); - printf("vvResults.size: %d\n", rows); - rows = 0; - for (auto vv = vvResults.begin(); vv != vvResults.end(); vv++) { - printf("Row %d\n", rows++); - for (auto v = vv->begin(); v != vv->end(); v++) { - wprintf(L"%s[%s]: ", v->column.c_str(), sqlTypes[v->type].c_str()); - switch (v->type) { - case 1: { - printf("%d\n", stoi(v->content.c_str())); - break; - } - case 2: { - printf("%f\n", stof(v->content.c_str())); - break; - } - case 3: { - printf("%s\n", v->content.c_str()); - break; - } - case 4: { - byte *p = (byte *)(v->content.c_str()); - for (unsigned int i = 0; i < v->content.size(); i++) { - printf("%02X ", p[i]); - } - printf("\n"); - break; - } - default: { - printf("\n"); - break; - } - } - } - } - printf("\n"); -} - -int onTextMsg(WxMessage_t msg) -{ - wprintf(L"%s msgType: %d, msgSource: %d, isSelf: %d\n", msg.id.c_str(), msg.type, msg.source, msg.self); - wprintf(L"%s[%s] >> %s\n", msg.wxId.c_str(), msg.roomId.c_str(), msg.content.c_str()); - wprintf(L"msgSourceXml: %s\n", msg.xml.c_str()); - - return 0; -} - -int main() -{ - DWORD status = 0; - wstring wxid = L"filehelper"; // 微信ID - wstring at_wxid = L""; - wstring content = L"这里填写消息内容"; - wstring img_path = L"test.jpg"; - - setlocale(LC_ALL, "chs"); // 这是个大坑,不设置中文直接不见了。。。 - - wprintf(L"WxInitSDK: "); - status = WxInitSDK(); - wprintf(L"%d\n", status); - if (status != 0) { - return 0; - } - - // 自己的 wxid - wstring selfWxid = WxGetSelfWxid(); - wprintf(L"本号WXID:%s\n", selfWxid.c_str()); - - // 获取消息类型 - wprintf(L"获取消息类型\n"); - const MsgTypesMap_t WxMsgTypes = WxGetMsgTypes(); - for (auto it = WxMsgTypes.begin(); it != WxMsgTypes.end(); ++it) { - wprintf(L"%d: %s\n", it->first, it->second.c_str()); - } - Sleep(1000); // 等待1秒 - - wprintf(L"Message: 接收通知中......\n"); - WxEnableRecvMsg(onTextMsg); - Sleep(1000); // 等待1秒 - - // 测试发送消息 - wprintf(L"测试发送消息\n"); - WxSendTextMsg(wxid, content, at_wxid); - Sleep(1000); // 等待1秒 - - // 测试发送照片 - wprintf(L"测试发送照片\n"); - WxSendImageMsg(wxid, img_path); - Sleep(1000); // 等待1秒 - - // 测试获取联系人 - auto mContact = WxGetContacts(); - printContacts(mContact); - Sleep(1000); // 等待1秒 - - // 测试获取数据库名 - auto vDbNames = WxGetDbNames(); - printDbNames(vDbNames); - Sleep(1000); // 等待1秒 - - // 测试获取数据库中的表 - auto vDbTables = WxGetDbTables(L"MicroMsg.db"); - printDbTables(vDbTables); - Sleep(1000); // 等待1秒 - - // 测试执行 SQL - auto vvResults = WxExecDbQuery(L"MicroMsg.db", L"SELECT * FROM Contact LIMIT 1;"); - printDbResults(vvResults); - - while (1) { - Sleep(10000); // 休眠,释放CPU - } -} diff --git a/App/App.py b/App/App.py deleted file mode 100644 index e2a39aa..0000000 --- a/App/App.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- - -import time -import wcferry as sdk - - -def main(): - help(sdk) # 查看SDK支持的方法和属性 - - # 初始化SDK,如果成功,返回0;否则失败 - status = sdk.WxInitSDK() - if status != 0: - print("初始化失败") - exit(-1) - - print("初始化成功") - WxMsgTypes = sdk.WxGetMsgTypes() # 获取消息类型 - print(WxMsgTypes) # 查看消息类型 - - time.sleep(2) - print("打印通讯录......") - contacts = sdk.WxGetContacts() - for k, v in contacts.items(): - print(k, v.wxCode, v.wxName, v.wxCountry, v.wxProvince, v.wxCity, v.wxGender) - - time.sleep(2) - print("发送文本消息......") - sdk.WxSendTextMsg("filehelper", "message from WeChatFerry...") # 往文件传输助手发消息 - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry...") # 往群里发消息(需要改成正确的 ID,下同) - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry... @ ", "wxid_xxxxxxxxxxxx") # 往群里发消息,@某人 - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry... @ ", "notify@all") # 往群里发消息,@所有人 - - time.sleep(2) - print("发送图片消息......") - sdk.WxSendImageMsg("filehelper", "test.jpg") - - dbs = sdk.WxGetDbNames() - for db in dbs: - print(db) - - tables = sdk.WxGetDbTables(dbs[0]) - for t in tables: - print(f"{t.table}\n{t.sql}\n\n") - - # 接收消息。先定义消息处理回调 - def OnTextMsg(msg: sdk.WxMessage): - def getName(id): - contact = contacts.get(id) - if contact is None: - return id - return contact.wxName - - s = "收到" - if msg.self == 1: # 忽略自己发的消息 - s += f"来自自己的消息" - print(f"\n{s}") - return 0 - - msgType = WxMsgTypes.get(msg.type, '未知类型') - nickName = getName(msg.wxId) - if msg.source == 1: - groupName = getName(msg.roomId) - s += f"来自群[{groupName}]的[{nickName}]的{msgType}消息:" - else: - s += f"来自[{nickName}]的{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.WxEnableRecvMsg(OnTextMsg) # 设置回调,接收消息 - - while True: - time.sleep(1) - - -if __name__ == '__main__': - main() diff --git a/App/App.vcxproj.filters b/App/App.vcxproj.filters deleted file mode 100644 index f5199e4..0000000 --- a/App/App.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {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/test.jpg b/App/test.jpg deleted file mode 100644 index 14aa7a7..0000000 Binary files a/App/test.jpg and /dev/null differ diff --git a/README.MD b/README.MD index 87d0d0a..6f4c1d7 100644 --- a/README.MD +++ b/README.MD @@ -3,35 +3,75 @@ 欢迎加群交流,后台回复 `WeChatFerry `: -![碲矿](碲矿.jpeg) +![碲矿](TEQuant.jpeg) ## 快速开始 -1. 使用 VS2019 编译。 -2. 打开 `CMD`,运行 `App.exe` +### 安装开发环境 +参见 [A gRPC Demo](https://github.com/lich0821/gRpcDemo/blob/wx/README.MD)。 +#### 安装 vcpkg +* 安装,参考[Vcpkg: 总览](https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md)。 +```sh +cd C:\Tools +git clone https://github.com/microsoft/vcpkg +.\vcpkg\bootstrap-vcpkg.bat +``` + +* 添加全局配置: +环境变量增加 `vcpkg` 所在路径(本文为:`C:\Tools\vcpkg`)。 + +#### 安装 gRPC 相关组件 +```sh +vcpkg install grpc:x86-windows-static +vcpkg install protobuf[zlib]:x86-windows-static +vcpkg integrate install +``` + +#### 安装 VS2019 + +### 生成编解码文件和接口文件 +```sh +cd WeChatFerry/proto/ +protoc --cpp_out=. -I=. wcf.proto +protoc --grpc_out=. --plugin=protoc-gen-grpc="C:\Tools\vcpkg\packages\grpc_x64-windows\tools\grpc\grpc_cpp_plugin.exe" -I=. wcf.proto +``` + +执行 `tree .`,可见生成了四个文件: +```txt +. +├── wcf.grpc.pb.cc +├── wcf.grpc.pb.h +├── wcf.pb.cc +├── wcf.pb.h +└── wcf.proto +``` + +### 编译 +使用 VS2019 打开工程,编译即可。 + +### 运行 +双击 `Release` 里的 `cpp.exe`。如果出现中文乱码,则需要将编码改为 `UTF-8`。 ## 项目结构 -### Spy + +### cpp +C++ 示例应用,介绍如何使用 WeChatFerry。 + +### proto +RPC 消息及接口定义。 + +### python +Python 客户端。 + +### sdk +负责将 `spy` 注入微信进程,并启动 gRPC 服务端。 + +### 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。 - ## 版本更新 +### v3.7.0.30-gRPC(2022.10.15) +将 RPC 框架切换为 gRPC! + ### v3.7.0.30-8(2022.09.25) * 获取登录账号微信 ID diff --git a/Rpc/rpc.idl b/Rpc/rpc.idl deleted file mode 100644 index 351adca..0000000 --- a/Rpc/rpc.idl +++ /dev/null @@ -1,75 +0,0 @@ -[ - uuid(ed838ecd-8a1e-4da7-bfda-9f2d12d07893), - version(1.0), - implicit_handle(handle_t hSpyBinding), -] - -interface ISpy -{ - import "oaidl.idl"; - - typedef struct RpcMessage { - int self; // 是否自己发的消息:0=否,1=是 - int type; // 消息类型 - int source; // 消息来源:0=好友消息,1=群消息 - BSTR id; // 消息ID - BSTR xml; // 群其他消息 - BSTR wxId; // 发送人微信ID - BSTR roomId; // 群ID - BSTR content; // 消息内容,MAC版最大:16384,即16KB - } RpcMessage_t; - - // 模拟 map - typedef struct RpcIntBstrPair { - int key; - BSTR value; - } RpcIntBstrPair_t; - typedef RpcIntBstrPair_t *PRpcIntBstrPair; - typedef RpcIntBstrPair_t **PPRpcIntBstrPair; - - typedef struct RpcContact { - BSTR wxId; // 微信ID - BSTR wxCode; // 微信号 - BSTR wxName; // 微信昵称 - BSTR wxCountry; // 国家 - BSTR wxProvince; // 省/州 - BSTR wxCity; // 城市 - BSTR wxGender; // 性别 - } RpcContact_t; - typedef RpcContact_t *PRpcContact; - typedef RpcContact_t **PPRpcContact; - - typedef struct RpcTables { - BSTR table; // 表名 - BSTR sql; // 建表 SQL - } RpcTables_t; - typedef RpcTables_t *PRpcTables; - typedef RpcTables_t **PPRpcTables; - - typedef struct RpcSqlResult { - int type; - BSTR column; - BSTR content; - } RpcSqlResult_t; - typedef RpcSqlResult_t *PRpcSqlResult; - typedef RpcSqlResult_t **PPRpcSqlResult; - typedef RpcSqlResult_t ***PPPRpcSqlResult; - - int IsLogin(); - int GetSelfWxId([ out, string ] wchar_t wxid[20]); - int SendTextMsg([ in, string ] const wchar_t *wxid, [ in, string ] const wchar_t *msg, - [ in, unique, string ] const wchar_t *atWxids); - int SendImageMsg([ in, string ] const wchar_t *wxid, [ in, string ] const wchar_t *path); - int GetMsgTypes([out] int *pNum, [ out, size_is(, *pNum) ] PPRpcIntBstrPair *msgTypes); - int GetContacts([out] int *pNum, [ out, size_is(, *pNum) ] PPRpcContact *contacts); - int GetDbNames([out] int *pNum, [ out, size_is(, *pNum) ] BSTR **dbs); - int GetDbTables([ in, string ] const wchar_t *db, [out] int *pNum, [ out, size_is(, *pNum) ] PPRpcTables *tbls); - int ExecDbQuery([ in, string ] const wchar_t *db, - [ in, string ] const wchar_t *sql, [out] int *pRow, [out] int *pCol, - [ out, size_is(, *pRow, *pCol) ] PPPRpcSqlResult *ret); - BOOL AcceptNewFriend([in, string] const wchar_t *v3, [in, string] const wchar_t *v4); - - void EnableReceiveMsg(); - void DisableReceiveMsg(); - [callback] int ReceiveMsg([in] RpcMessage_t rpcMsg); -}; diff --git a/Rpc/rpc_memory.cpp b/Rpc/rpc_memory.cpp deleted file mode 100644 index 01eb680..0000000 --- a/Rpc/rpc_memory.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#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/SDK/framework.h b/SDK/framework.h deleted file mode 100644 index e526216..0000000 --- a/SDK/framework.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 -// Windows 头文件 -#include diff --git a/SDK/injector.cpp b/SDK/injector.cpp deleted file mode 100644 index 7b7ee70..0000000 --- a/SDK/injector.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "injector.h" - -int InjectDll(DWORD pid, const WCHAR *dllPath) -{ - HANDLE hThread; - DWORD dwWriteSize = 0; - // 1. 获取目标进程,并在目标进程的内存里开辟空间 - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE); - - // 2. 把 dll 的路径写入到目标进程的内存空间中 - if (pRemoteAddress) { - WriteProcessMemory(hProcess, pRemoteAddress, dllPath, wcslen(dllPath) * 2 + 2, &dwWriteSize); - } else { - MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0); - return -1; - } - - // 3. 创建一个远程线程,让目标进程调用 LoadLibrary - hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, NULL, NULL); - if (hThread) { - WaitForSingleObject(hThread, -1); - } else { - MessageBox(NULL, L"LoadLibrary 调用失败", L"InjectDll", 0); - return -2; - } - CloseHandle(hThread); - VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); - CloseHandle(hProcess); - return 0; -} - -int EjectDll(DWORD pid, const WCHAR *dllPath) -{ - DWORD dwHandle, dwID; - HANDLE hThread = NULL; - DWORD dwWriteSize = 0; - - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE); - - if (pRemoteAddress) - WriteProcessMemory(hProcess, pRemoteAddress, dllPath, wcslen(dllPath) * 2 + 2, &dwWriteSize); - else { - MessageBox(NULL, L"DLL 路径写入失败", L"EjectDll", 0); - return -1; - } - hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetModuleHandleW, pRemoteAddress, 0, &dwID); - if (hThread) { - WaitForSingleObject(hThread, INFINITE); - GetExitCodeThread(hThread, &dwHandle); - } else { - MessageBox(NULL, L"GetModuleHandleW 调用失败!", L"EjectDll", 0); - return -2; - } - CloseHandle(hThread); - - // 使目标进程调用 FreeLibrary,卸载 DLL - hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)dwHandle, 0, &dwID); - if (hThread) { - WaitForSingleObject(hThread, INFINITE); - } else { - MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0); - return -3; - } - CloseHandle(hThread); - VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); - CloseHandle(hProcess); - return 0; -} diff --git a/SDK/injector.h b/SDK/injector.h deleted file mode 100644 index f46ad9d..0000000 --- a/SDK/injector.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "framework.h" - -int InjectDll(DWORD pid, const WCHAR* dllPath); -int EjectDll(DWORD pid, const WCHAR* dllPath); diff --git a/SDK/rpc_client.cpp b/SDK/rpc_client.cpp deleted file mode 100644 index 5e1ad3c..0000000 --- a/SDK/rpc_client.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#include "rpc_client.h" -#include "sdk.h" -#include "util.h" - -static RPC_WSTR pszStringBinding = NULL; -extern std::function g_cbReceiveTextMsg; - -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"wcferry"), // 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); - - // Releases binding handle resources and disconnects from the server - status = RpcBindingFree(&hSpyBinding); - - return status; -} - -int RpcEnableReceiveMsg() -{ - unsigned long ulCode = 0; - RpcTryExcept - { - // 建立RPC通道,让服务端能够调用客户端的回调函数。(该接口会被服务端阻塞直到异常退出) - client_EnableReceiveMsg(); - } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcEnableReceiveMsg exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return 0; -} - -int RpcDisableReceiveMsg() -{ - unsigned long ulCode = 0; - RpcTryExcept - { - // UnHook Message receiving - client_DisableReceiveMsg(); - } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcDisableReceiveMsg exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return 0; -} - -int RpcIsLogin() -{ - int loginFlag = 0; - unsigned long ulCode = 0; - RpcTryExcept - { - // 查询登录状态 - loginFlag = client_IsLogin(); - } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcIsLogin exception 0x%lx = %ld\n", ulCode, ulCode); - return -1; - } - RpcEndExcept; - - return loginFlag; -} - -int RpcGetSelfWxId(wchar_t wxid[20]) -{ - int ret = -1; - unsigned long ulCode = 0; - RpcTryExcept { ret = client_GetSelfWxId(wxid); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcIsLogin exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return ret; -} - -int RpcSendTextMsg(const wchar_t *wxid, const wchar_t *msg, const wchar_t *atWxids) -{ - int ret = 0; - unsigned long ulCode = 0; - - RpcTryExcept { ret = client_SendTextMsg(wxid, msg, atWxids); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcSendTextMsg exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return ret; -} - -int RpcSendImageMsg(const wchar_t *wxid, const wchar_t *path) -{ - int ret = 0; - unsigned long ulCode = 0; - - RpcTryExcept { ret = client_SendImageMsg(wxid, path); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcSendImageMsg exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return ret; -} - -PPRpcIntBstrPair RpcGetMsgTypes(int *pNum) -{ - int ret = 0; - unsigned long ulCode = 0; - PPRpcIntBstrPair ppRpcMsgTypes = NULL; - - RpcTryExcept { ret = client_GetMsgTypes(pNum, &ppRpcMsgTypes); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcGetMsgTypes exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - if (ret != 0) { - printf("GetMsgTypes Failed: %d\n", ret); - return NULL; - } - - return ppRpcMsgTypes; -} - -PPRpcContact RpcGetContacts(int *pNum) -{ - int ret = 0; - unsigned long ulCode = 0; - PPRpcContact ppRpcContacts = NULL; - - RpcTryExcept { ret = client_GetContacts(pNum, &ppRpcContacts); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcGetContacts exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - if (ret != 0) { - printf("GetContacts Failed: %d\n", ret); - return NULL; - } - - return ppRpcContacts; -} - -BSTR *RpcGetDbNames(int *pNum) -{ - int ret = 0; - unsigned long ulCode = 0; - BSTR *pBstr = NULL; - - RpcTryExcept { ret = client_GetDbNames(pNum, &pBstr); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcGetDbNames exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - if (ret != 0) { - printf("RpcGetDbNames Failed: %d\n", ret); - return NULL; - } - - return pBstr; -} - -PPRpcTables RpcGetDbTables(const wchar_t *db, int *pNum) -{ - int ret = 0; - unsigned long ulCode = 0; - PPRpcTables ppRpcTables = NULL; - - RpcTryExcept { ret = client_GetDbTables(db, pNum, &ppRpcTables); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcGetDbTables exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - if (ret != 0) { - printf("RpcGetDbTables Failed: %d\n", ret); - return NULL; - } - - return ppRpcTables; -} - -PPPRpcSqlResult RpcExecDbQuery(const wchar_t *db, const wchar_t *sql, int *pRow, int *pCol) -{ - int ret = 0; - unsigned long ulCode = 0; - PPPRpcSqlResult pppRpcSqlResult = NULL; - - RpcTryExcept { ret = client_ExecDbQuery(db, sql, pRow, pCol, &pppRpcSqlResult); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("RpcExecDbQuery exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - if (ret != 0) { - printf("RpcExecDbQuery Failed: %d\n", ret); - return NULL; - } - - return pppRpcSqlResult; -} - -BOOL AcceptNewFriend(const wchar_t *v3, const wchar_t *v4) -{ - BOOL ret = 0; - unsigned long ulCode = 0; - - RpcTryExcept { ret = client_AcceptNewFriend(v3, v4); } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("AcceptNewFriend exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept; - - return ret; -} - -int server_ReceiveMsg(RpcMessage_t rpcMsg) -{ - WxMessage_t msg; - GetRpcMessage(&msg, rpcMsg); - try { - g_cbReceiveTextMsg(msg); // 调用接收消息回调 - } catch (...) { - printf("callback error...\n"); - } - - return 0; -} diff --git a/SDK/rpc_client.h b/SDK/rpc_client.h deleted file mode 100644 index b292e57..0000000 --- a/SDK/rpc_client.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "rpc_h.h" - -RPC_STATUS RpcConnectServer(); -RPC_STATUS RpcDisconnectServer(); - -int RpcEnableReceiveMsg(); -int RpcDisableReceiveMsg(); -int RpcIsLogin(); -int RpcGetSelfWxId(wchar_t wxid[20]); -int RpcSendTextMsg(const wchar_t *wxid, const wchar_t *msg, const wchar_t *atWxids); -int RpcSendImageMsg(const wchar_t *wxid, const wchar_t *path); -PPRpcIntBstrPair RpcGetMsgTypes(int *pNum); -PPRpcContact RpcGetContacts(int *pNum); -BSTR *RpcGetDbNames(int *pNum); -PPRpcTables RpcGetDbTables(const wchar_t *db, int *pNum); -PPPRpcSqlResult RpcExecDbQuery(const wchar_t *db, const wchar_t *sql, int *row, int *col); -BOOL AcceptNewFriend(const wchar_t *v3, const wchar_t *v4); diff --git a/SDK/sdk.cpp b/SDK/sdk.cpp deleted file mode 100644 index e3628fb..0000000 --- a/SDK/sdk.cpp +++ /dev/null @@ -1,273 +0,0 @@ -#include "Shlwapi.h" -#include "framework.h" -#include -#include -#include -#include -#include -#include - -#include "injector.h" -#include "rpc_client.h" -#include "sdk.h" -#include "util.h" - -std::function g_cbReceiveTextMsg; - -static DWORD WeChatPID = 0; -static WCHAR SpyDllPath[MAX_PATH] = { 0 }; - -int WxInitSDK() -{ - int status = 0; - unsigned long ulCode = 0; - - GetModuleFileName(GetModuleHandle(WECHATSDKDLL), SpyDllPath, MAX_PATH); - PathRemoveFileSpec(SpyDllPath); - PathAppend(SpyDllPath, WECHATINJECTDLL); - - if (!PathFileExists(SpyDllPath)) { - return ERROR_FILE_NOT_FOUND; - } - - status = OpenWeChat(&WeChatPID); - if (status != 0) { - return status; - } - - Sleep(2000); // 等待微信打开 - if (InjectDll(WeChatPID, SpyDllPath)) { - return -1; - } - - Sleep(1000); // 等待SPY就绪 - status = RpcConnectServer(); - if (status != 0) { - printf("RpcConnectServer: %d\n", status); - return -1; - } - - do { - status = RpcIsLogin(); - if (status == -1) { - return status; - } else if (status == 1) { - break; - } - Sleep(1000); - } while (1); - - return ERROR_SUCCESS; -} - -int WxDestroySDK() -{ - WxDisableRecvMsg(); - RpcDisconnectServer(); - // 关闭 RPC,但不卸载 DLL,方便下次使用。 - // EjectDll(WeChatPID, SpyDllPath); - - return ERROR_SUCCESS; -} - -int WxEnableRecvMsg(const std::function &onMsg) -{ - if (onMsg) { - HANDLE msgThread; - g_cbReceiveTextMsg = onMsg; - - msgThread = (HANDLE)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RpcEnableReceiveMsg, 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 WxDisableRecvMsg() -{ - RpcDisableReceiveMsg(); - return -1; -} - -int WxSendTextMsg(wstring wxid, wstring msg, wstring atWxids) -{ - return RpcSendTextMsg(wxid.c_str(), msg.c_str(), atWxids.c_str()); -} - -int WxSendImageMsg(wstring wxid, wstring path) { return RpcSendImageMsg(wxid.c_str(), path.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; -} - -wstring WxGetSelfWxid() -{ - wchar_t wxid[20] = { 0 }; - RpcGetSelfWxId(wxid); - return wstring(wxid); -} - -MsgTypesMap_t WxGetMsgTypes() -{ - static MsgTypesMap_t WxMsgTypes; - if (WxMsgTypes.empty()) { - int size = 0; - PPRpcIntBstrPair pp = RpcGetMsgTypes(&size); - for (int i = 0; i < size; i++) { - WxMsgTypes.insert(make_pair(pp[i]->key, GetWstringFromBstr(pp[i]->value))); - midl_user_free(pp[i]); - } - if (pp) { - midl_user_free(pp); - } - } - - return WxMsgTypes; -} - -ContactMap_t WxGetContacts() -{ - ContactMap_t mContact; - int size = 0; - PPRpcContact pp = RpcGetContacts(&size); - for (int i = 0; i < size; i++) { - WxContact_t contact; - contact.wxId = GetWstringFromBstr(pp[i]->wxId); - contact.wxCode = GetWstringFromBstr(pp[i]->wxCode); - contact.wxName = GetWstringFromBstr(pp[i]->wxName); - contact.wxCountry = GetWstringFromBstr(pp[i]->wxCountry); - contact.wxProvince = GetWstringFromBstr(pp[i]->wxProvince); - contact.wxCity = GetWstringFromBstr(pp[i]->wxCity); - contact.wxGender = GetWstringFromBstr(pp[i]->wxGender); - - mContact.insert(make_pair(contact.wxId, contact)); - midl_user_free(pp[i]); - } - - if (pp) { - midl_user_free(pp); - } - - return mContact; -} - -std::vector WxGetDbNames() -{ - std::vector vDbs; - int size = 0; - BSTR *pBstr = RpcGetDbNames(&size); - for (int i = 0; i < size; i++) { - vDbs.push_back(GetWstringFromBstr(pBstr[i])); - } - - if (pBstr) { - midl_user_free(pBstr); - } - - return vDbs; -} - -DbTableVector_t WxGetDbTables(wstring db) -{ - DbTableVector_t vTables; - int size = 0; - PPRpcTables pp = RpcGetDbTables(db.c_str(), &size); - for (int i = 0; i < size; i++) { - WxDbTable_t tbl; - tbl.table = GetWstringFromBstr(pp[i]->table); - tbl.sql = GetWstringFromBstr(pp[i]->sql); - - vTables.push_back(tbl); - midl_user_free(pp[i]); - } - - if (pp) { - midl_user_free(pp); - } - - return vTables; -} - -SqlRetVector_t WxExecDbQuery(wstring db, wstring sql) -{ - int row, col = 0; - PPPRpcSqlResult ppp = RpcExecDbQuery(db.c_str(), sql.c_str(), &row, &col); - vector> vvResults; - for (int r = 0; r < row; r++) { - vector vResult; - for (int c = 0; c < col; c++) { - WxSqlResult_t result = { 0 }; - result.type = ppp[r][c]->type; - result.column = GetWstringFromBstr(ppp[r][c]->column); - result.content = GetBytesFromBstr(ppp[r][c]->content); - vResult.push_back(result); - midl_user_free(ppp[r][c]); - } - vvResults.push_back(vResult); - midl_user_free(ppp[r]); - } - if (ppp) { - midl_user_free(ppp); - } - - return vvResults; -} - -BOOL WxAcceptNewFriend(wstring v3, wstring v4) { return AcceptNewFriend(v3.c_str(), v4.c_str()); } diff --git a/SDK/sdk.def b/SDK/sdk.def deleted file mode 100644 index 771843a..0000000 --- a/SDK/sdk.def +++ /dev/null @@ -1,14 +0,0 @@ -EXPORTS - WxInitSDK - WxDestroySDK - WxEnableRecvMsg - WxDisableRecvMsg - WxSendTextMsg - WxGetSelfWxid - WxGetMsgTypes - WxSendImageMsg - WxGetContacts - WxGetDbNames - WxGetDbTables - WxExecDbQuery - WxAcceptNewFriend diff --git a/SDK/sdk.h b/SDK/sdk.h deleted file mode 100644 index ad28923..0000000 --- a/SDK/sdk.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "framework.h" -#include -#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 struct WxContact { - wstring wxId; // 微信ID - wstring wxCode; // 微信号 - wstring wxName; // 微信昵称 - wstring wxCountry; // 国家 - wstring wxProvince; // 省/州 - wstring wxCity; // 城市 - wstring wxGender; // 性别 -} WxContact_t; - -typedef struct WxDbTable { - wstring table; // 表名 - wstring sql; // 建表 SQL -} WxDbTable_t; - -typedef struct WxSqlResult { - int type; - wstring column; - string content; -} WxSqlResult_t; - -typedef map MsgTypesMap_t; -typedef map ContactMap_t; -typedef vector DbTableVector_t; -typedef vector> SqlRetVector_t; - -int WxInitSDK(); -int WxDestroySDK(); -int WxEnableRecvMsg(const std::function &onMsg); -int WxDisableRecvMsg(); -int WxSendTextMsg(wstring wxid, wstring msg, wstring vAtWxids); -int WxSendImageMsg(wstring wxid, wstring path); -wstring WxGetSelfWxid(); -ContactMap_t WxGetContacts(); -MsgTypesMap_t WxGetMsgTypes(); -vector WxGetDbNames(); -DbTableVector_t WxGetDbTables(wstring db); -SqlRetVector_t WxExecDbQuery(wstring db, wstring sql); -BOOL WxAcceptNewFriend(wstring v3, wstring v4); diff --git a/SDKpy/SDKpy.vcxproj b/SDKpy/SDKpy.vcxproj deleted file mode 100644 index 438af2e..0000000 --- a/SDKpy/SDKpy.vcxproj +++ /dev/null @@ -1,170 +0,0 @@ - - - - - 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.user b/SDKpy/SDKpy.vcxproj.user deleted file mode 100644 index 0f14913..0000000 --- a/SDKpy/SDKpy.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/SDKpy/sdkpy.cpp b/SDKpy/sdkpy.cpp deleted file mode 100644 index d388e1a..0000000 --- a/SDKpy/sdkpy.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include -#include - -#include "sdk.h" -#include "util.h" - -namespace py = pybind11; - -int WxEnableRecvMsgPy(const std::function &onMsg) { return WxEnableRecvMsg(onMsg); } - -py::object WxExecDbQueryPy(std::wstring db, std::wstring sql) -{ - py::list results; - SqlRetVector_t cResults = WxExecDbQuery(db, sql); - for (auto vv = cResults.begin(); vv != cResults.end(); vv++) { - py::dict row; - for (auto v = vv->begin(); v != vv->end(); v++) { - switch (v->type) { - case 1: { // SQLITE_INTEGER - row[py::cast(v->column)] = stoi(v->content); - break; - } - case 2: { // SQLITE_FLOAT - row[py::cast(v->column)] = stof(v->content); - break; - } - case 3: { // SQLITE_TEXT - row[py::cast(v->column)] = String2Wstring(v->content); - break; - } - case 4: { // SQLITE_BLOB - row[py::cast(v->column)] = py::bytes(v->content.c_str(), v->content.size()); - break; - } - default: { - row[py::cast(v->column)] = ""; - break; - } - } - } - results.append(row); - } - return results; -} - -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); - - py::class_(m, "WxContact") - .def_readonly("wxId", &WxContact::wxId) - .def_readonly("wxCode", &WxContact::wxCode) - .def_readonly("wxName", &WxContact::wxName) - .def_readonly("wxCountry", &WxContact::wxCountry) - .def_readonly("wxProvince", &WxContact::wxProvince) - .def_readonly("wxCity", &WxContact::wxCity) - .def_readonly("wxGender", &WxContact::wxGender); - - py::class_(m, "WxDbTable").def_readonly("table", &WxDbTable::table).def_readonly("sql", &WxDbTable::sql); - - m.def("WxInitSDK", &WxInitSDK, "Initiate SDK. Return 0 on success,else on failure."); - m.def("WxEnableRecvMsg", &WxEnableRecvMsgPy, "Enable message receiving and provide a callback", py::arg("onMsg")); - m.def("WxDisableRecvMsg", &WxDisableRecvMsg, "Disable message receiving."); - m.def("WxSendTextMsg", &WxSendTextMsg, "Send text message.", py::arg("wxid"), py::arg("msg"), - py::arg("atWxids") = L""); - m.def("WxSendImageMsg", &WxSendImageMsg, "Send image message.", py::arg("wxid"), py::arg("path")); - m.def("WxGetSelfWxid", &WxGetSelfWxid, "Get Self Wxid."); - m.def("WxGetContacts", &WxGetContacts, py::return_value_policy::reference, "Get contact list."); - m.def("WxGetMsgTypes", &WxGetMsgTypes, py::return_value_policy::reference, "Get message types."); - m.def("WxGetDbNames", &WxGetDbNames, py::return_value_policy::reference, "Get DB names."); - m.def("WxGetDbTables", &WxGetDbTables, py::return_value_policy::reference, "Get DB tables.", py::arg("db")); - m.def("WxExecDbQuery", &WxExecDbQueryPy, py::return_value_policy::reference, "Get DB tables.", py::arg("db"), - py::arg("sql")); - m.def("WxAcceptNewFriend", &WxAcceptNewFriend, "Accept new friend application.", py::arg("v3"), py::arg("v4")); - -#ifdef VERSION_INFO - m.attr("__version__") = VERSION_INFO; -#else - m.attr("__version__") = "dev"; -#endif -} diff --git a/Spy/dllmain.cpp b/Spy/dllmain.cpp deleted file mode 100644 index 1d1cf21..0000000 --- a/Spy/dllmain.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "framework.h" - -#include "spy.h" - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: { - //MessageBox(NULL, L"InitSpy", L"DllMain", 0); - InitSpy(); - break; - } - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - case DLL_PROCESS_DETACH: { - //MessageBox(NULL, L"DestroySpy", L"DllMain", 0); - DestroySpy(hModule); - break; - } - } - return TRUE; -} diff --git a/Spy/exec_sql.h b/Spy/exec_sql.h deleted file mode 100644 index 70b67fb..0000000 --- a/Spy/exec_sql.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -#include "rpc_h.h" - -std::vector GetDbNames(); -std::vector GetDbTables(std::wstring db); -std::vector> ExecDbQuery(std::wstring db, std::wstring sql); diff --git a/Spy/framework.h b/Spy/framework.h deleted file mode 100644 index e526216..0000000 --- a/Spy/framework.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 -// Windows 头文件 -#include diff --git a/Spy/get_contacts.cpp b/Spy/get_contacts.cpp deleted file mode 100644 index 8282e2c..0000000 --- a/Spy/get_contacts.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "get_contacts.h" -#include "load_calls.h" -#include "util.h" - -extern WxCalls_t g_WxCalls; -extern DWORD g_WeChatWinDllAddr; - -std::vector GetContacts() -{ - int gender = 0; - vector vContacts; - DWORD baseAddr = g_WeChatWinDllAddr + g_WxCalls.contact.base; - DWORD tempAddr = GET_DWORD(baseAddr); - DWORD head = GET_DWORD(tempAddr + g_WxCalls.contact.head); - DWORD node = GET_DWORD(head); - - while (node != head) { - RpcContact_t rpcContact = { 0 }; - rpcContact.wxId = GetBstrByAddress(node + g_WxCalls.contact.wxId); - rpcContact.wxCode = GetBstrByAddress(node + g_WxCalls.contact.wxCode); - rpcContact.wxName = GetBstrByAddress(node + g_WxCalls.contact.wxName); - rpcContact.wxCountry = GetBstrByAddress(node + g_WxCalls.contact.wxCountry); - rpcContact.wxProvince = GetBstrByAddress(node + g_WxCalls.contact.wxProvince); - rpcContact.wxCity = GetBstrByAddress(node + g_WxCalls.contact.wxCity); - - gender = GET_DWORD(node + g_WxCalls.contact.wxGender); - if (gender == 1) - rpcContact.wxGender = SysAllocString(L"男"); - else if (gender == 2) - rpcContact.wxGender = SysAllocString(L"女"); - else - rpcContact.wxGender = SysAllocString(L"未知"); - - vContacts.push_back(rpcContact); - node = GET_DWORD(node); - } - - return vContacts; -} diff --git a/Spy/get_contacts.h b/Spy/get_contacts.h deleted file mode 100644 index 5662225..0000000 --- a/Spy/get_contacts.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "rpc_h.h" - -std::vector GetContacts(); diff --git a/Spy/receive_msg.cpp b/Spy/receive_msg.cpp deleted file mode 100644 index ce63083..0000000 --- a/Spy/receive_msg.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#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; -static BOOL isListened = false; -static DWORD reg_buffer = 0; -static DWORD recvMsgHookAddr = 0; -static DWORD recvMsgCallAddr = 0; -static DWORD recvMsgJumpBackAddr = 0; -static CHAR recvMsgBackupCode[5] = { 0 }; -static RpcMessage_t lMsg = { 0 }; - -extern const MsgTypesMap_t g_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"撤回消息" } }; - -void HookAddress(DWORD hookAddr, LPVOID funcAddr, CHAR recvMsgBackupCode[5]) -{ - //组装跳转数据 - BYTE jmpCode[5] = { 0 }; - jmpCode[0] = 0xE9; - - //计算偏移 - *(DWORD *)&jmpCode[1] = (DWORD)funcAddr - hookAddr - 5; - - // 备份原来的代码 - ReadProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, recvMsgBackupCode, 5, 0); - // 写入新的代码 - WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, jmpCode, 5, 0); -} - -void UnHookAddress(DWORD hookAddr, CHAR restoreCode[5]) -{ - WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, restoreCode, 5, 0); -} - -void DispatchMsg(DWORD reg) -{ - DWORD *p = (DWORD *)reg; //消息结构基址 - - memset(&lMsg, 0, sizeof(RpcMessage_t)); - - lMsg.type = GET_DWORD(*p + g_WxCalls.recvMsg.type); - lMsg.self = GET_DWORD(*p + g_WxCalls.recvMsg.isSelf); - lMsg.id = GetBstrByAddress(*p + g_WxCalls.recvMsg.msgId); - lMsg.xml = GetBstrByAddress(*p + g_WxCalls.recvMsg.msgXml); - - // 群里的系统消息,xml 为空 - if ((lMsg.xml == NULL) || (wcsstr(lMsg.xml, L"") != NULL)) { - lMsg.source = 1; - lMsg.wxId = GetBstrByAddress(*p + g_WxCalls.recvMsg.wxId); - lMsg.roomId = GetBstrByAddress(*p + g_WxCalls.recvMsg.roomId); - } else { - lMsg.wxId = GetBstrByAddress(*p + g_WxCalls.recvMsg.roomId); - } - lMsg.content = GetBstrByAddress(*p + g_WxCalls.recvMsg.content); - g_MsgQueue.push(lMsg); // 发送消息 - SetEvent(g_hEvent); // 发送消息通知 -} - -__declspec(naked) void RecieveMsgFunc() -{ - __asm { - mov reg_buffer, edi //把值复制出来 - } - - DispatchMsg(reg_buffer); - - __asm - { - call recvMsgCallAddr // 这个为被覆盖的call - jmp recvMsgJumpBackAddr // 跳回被HOOK指令的下一条指令 - } -} - -void ListenMessage() -{ - // MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0); - if (isListened || (g_WeChatWinDllAddr == 0)) { - return; - } - - recvMsgHookAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.hook; - recvMsgCallAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.call; - recvMsgJumpBackAddr = recvMsgHookAddr + 5; - - HookAddress(recvMsgHookAddr, RecieveMsgFunc, recvMsgBackupCode); - isListened = true; -} - -void UnListenMessage() -{ - if (!isListened) { - return; - } - UnHookAddress(recvMsgHookAddr, recvMsgBackupCode); - isListened = false; -} diff --git a/Spy/receive_msg.h b/Spy/receive_msg.h deleted file mode 100644 index 4390490..0000000 --- a/Spy/receive_msg.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -void ListenMessage(); -void UnListenMessage(); diff --git a/Spy/rpc_server.cpp b/Spy/rpc_server.cpp deleted file mode 100644 index e6b1172..0000000 --- a/Spy/rpc_server.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include -#include - -#include "accept_new_friend.h" -#include "exec_sql.h" -#include "get_contacts.h" -#include "receive_msg.h" -#include "rpc_h.h" -#include "rpc_server.h" -#include "sdk.h" -#include "send_msg.h" -#include "spy.h" -#include "spy_types.h" -#include "util.h" - -using namespace std; - -extern int IsLogin(void); // Defined in spy.cpp -extern wstring GetSelfWxid(); // Defined in spy.cpp -extern HANDLE g_hEvent; // New message signal -extern BOOL g_rpcKeepAlive; // Keep RPC server thread running -extern MsgQueue_t g_MsgQueue; // Queue for message -extern const MsgTypesMap_t g_WxMsgTypes; // Map of WeChat Message types - -static BOOL listenMsgFlag = false; - -RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE /*hInterface*/, void * /*pBindingHandle*/) -{ - return RPC_S_OK; // Always allow anyone. -} - -int RpcStartServer() -{ - 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"wcferry"), // 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 (g_rpcKeepAlive) { - Sleep(1000); // 休眠,释放CPU - } - - return 0; -} - -int RpcStopServer() -{ - RPC_STATUS status; - - UnListenMessage(); - - listenMsgFlag = false; - g_rpcKeepAlive = false; - status = RpcMgmtStopServerListening(NULL); - if (status) - return status; - - status = RpcServerUnregisterIf(server_ISpy_v1_0_s_ifspec, NULL, 0); - return status; -} - -int server_IsLogin() { return IsLogin(); } - -int server_GetSelfWxId(wchar_t wxid[20]) { return wcscpy_s(wxid, 20, GetSelfWxid().c_str()); } - -void server_EnableReceiveMsg() -{ - unsigned long ulCode = 0; - ListenMessage(); - listenMsgFlag = true; - RpcTryExcept - { - // 调用客户端的回调函数 - while (listenMsgFlag) { - // 中断式,兼顾及时性和CPU使用率 - WaitForSingleObject(g_hEvent, INFINITE); // 等待消息 - while (!g_MsgQueue.empty()) { - client_ReceiveMsg(g_MsgQueue.front()); // 调用接收消息回调 - g_MsgQueue.pop(); - } - ResetEvent(g_hEvent); - } - } - RpcExcept(1) - { - ulCode = RpcExceptionCode(); - printf("server_EnableReceiveMsg exception 0x%lx = %ld\n", ulCode, ulCode); - } - RpcEndExcept -} - -void server_DisableReceiveMsg() -{ - UnListenMessage(); - listenMsgFlag = false; -} - -int server_SendTextMsg(const wchar_t *wxid, const wchar_t *msg, const wchar_t *atWxids) -{ - SendTextMessage(wxid, msg, atWxids); - - return 0; -} - -int server_SendImageMsg(const wchar_t *wxid, const wchar_t *path) -{ - SendImageMessage(wxid, path); - - return 0; -} - -int server_GetMsgTypes(int *pNum, PPRpcIntBstrPair *msgTypes) -{ - *pNum = g_WxMsgTypes.size(); - PPRpcIntBstrPair pp = (PPRpcIntBstrPair)midl_user_allocate(*pNum * sizeof(PRpcIntBstrPair)); - if (pp == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for pp\n"); - return -2; - } - int index = 0; - for (auto it = g_WxMsgTypes.begin(); it != g_WxMsgTypes.end(); it++) { - PRpcIntBstrPair p = (PRpcIntBstrPair)midl_user_allocate(sizeof(RpcIntBstrPair_t)); - if (p == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for p\n"); - return -3; - } - - p->key = it->first; - p->value = SysAllocString(it->second.c_str()); - pp[index++] = p; - } - - *msgTypes = pp; - - return 0; -} - -int server_GetContacts(int *pNum, PPRpcContact *contacts) -{ - vector vContacts = GetContacts(); - - *pNum = vContacts.size(); - PPRpcContact pp = (PPRpcContact)midl_user_allocate(*pNum * sizeof(PRpcContact)); - if (pp == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for pp\n"); - return -2; - } - - int index = 0; - for (auto it = vContacts.begin(); it != vContacts.end(); it++) { - PRpcContact p = (PRpcContact)midl_user_allocate(sizeof(RpcContact_t)); - if (p == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for p\n"); - return -3; - } - - p->wxId = it->wxId; - p->wxCode = it->wxCode; - p->wxName = it->wxName; - p->wxCountry = it->wxCountry; - p->wxProvince = it->wxProvince; - p->wxCity = it->wxCity; - p->wxGender = it->wxGender; - - pp[index++] = p; - } - - *contacts = pp; - - return 0; -} - -int server_GetDbNames(int *pNum, BSTR **dbs) -{ - vector vDbs = GetDbNames(); - *pNum = vDbs.size(); - BSTR *pp = (BSTR *)midl_user_allocate(*pNum * sizeof(BSTR)); - if (pp == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for pp\n"); - return -2; - } - - int index = 0; - for (auto it = vDbs.begin(); it != vDbs.end(); it++) { - pp[index++] = GetBstrFromWstring(*it); - } - - *dbs = pp; - - return 0; -} - -int server_GetDbTables(const wchar_t *db, int *pNum, PPRpcTables *tbls) -{ - vector tables = GetDbTables(db); - *pNum = tables.size(); - PPRpcTables pp = (PPRpcTables)midl_user_allocate(*pNum * sizeof(PRpcTables)); - if (pp == NULL) { - printf("server_GetMsgTypes midl_user_allocate Failed for pp\n"); - return -2; - } - - int index = 0; - for (auto it = tables.begin(); it != tables.end(); it++) { - PRpcTables p = (PRpcTables)midl_user_allocate(sizeof(RpcTables_t)); - if (p == NULL) { - printf("server_GetDbTables midl_user_allocate Failed for p\n"); - return -3; - } - - p->table = it->table; - p->sql = it->sql; - pp[index++] = p; - } - - *tbls = pp; - - return 0; -} - -int server_ExecDbQuery(const wchar_t *db, const wchar_t *sql, int *pRow, int *pCol, PPPRpcSqlResult *ret) -{ - vector> vvSqlResult = ExecDbQuery(db, sql); - if (vvSqlResult.empty()) { - *pRow = *pCol = 0; - ret = NULL; - return -1; - } - *pRow = vvSqlResult.size(); - *pCol = vvSqlResult[0].size(); - PPPRpcSqlResult ppp = (PPPRpcSqlResult)midl_user_allocate(*pRow * sizeof(PPRpcSqlResult)); - if (ppp == NULL) { - printf("server_ExecDbQuery midl_user_allocate Failed for ppp\n"); - return -2; - } - - for (int r = 0; r < *pRow; r++) { - PPRpcSqlResult pp = (PPRpcSqlResult)midl_user_allocate(*pCol * sizeof(PRpcSqlResult)); - if (pp == NULL) { - midl_user_free(ppp); - printf("server_ExecDbQuery midl_user_allocate Failed for pp\n"); - return -2; - } - - for (int c = 0; c < *pCol; c++) { - PRpcSqlResult p = (PRpcSqlResult)midl_user_allocate(sizeof(RpcSqlResult_t)); - if (p == NULL) { - midl_user_free(pp); - printf("server_ExecDbQuery midl_user_allocate Failed for p\n"); - return -2; - } - - p->type = vvSqlResult[r][c].type; - p->column = vvSqlResult[r][c].column; - p->content = vvSqlResult[r][c].content; - - pp[c] = p; - } - - ppp[r] = pp; - } - - *ret = ppp; - - return 0; -} - -BOOL server_AcceptNewFriend(const wchar_t *v3, const wchar_t *v4) { return AcceptNewFriend(v3, v4); } diff --git a/Spy/rpc_server.h b/Spy/rpc_server.h deleted file mode 100644 index cc1e9bb..0000000 --- a/Spy/rpc_server.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -int RpcStartServer(); -int RpcStopServer(); diff --git a/Spy/spy.cpp b/Spy/spy.cpp deleted file mode 100644 index c61093f..0000000 --- a/Spy/spy.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "load_calls.h" -#include "rpc_server.h" -#include "spy.h" -#include "util.h" - -HANDLE g_hEvent = NULL; -BOOL g_rpcKeepAlive = false; -WxCalls_t g_WxCalls = { 0 }; -DWORD g_WeChatWinDllAddr = 0; - -void InitSpy() -{ - wchar_t version[16] = { 0 }; - - g_WeChatWinDllAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll"); //获取wechatWin模块地址 - if (g_WeChatWinDllAddr == 0) { - MessageBox(NULL, L"获取wechatWin.dll模块地址失败", L"错误", 0); - return; - } - - if (!GetWeChatVersion(version)) { //获取微信版本 - MessageBox(NULL, L"获取微信版本失败", L"错误", 0); - return; - } - - if (LoadCalls(version, &g_WxCalls) != 0) { //加载微信版本对应的Call地址 - MessageBox(NULL, L"不支持当前版本", L"错误", 0); - return; - } - - g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - HANDLE rpcThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RpcStartServer, NULL, NULL, 0); - if (rpcThread != 0) { - CloseHandle(rpcThread); - } -} - -void DestroySpy(HMODULE hModule) -{ - RpcStopServer(); - FreeLibraryAndExitThread(hModule, 0); -} - -int IsLogin(void) { return (int)GET_DWORD(g_WeChatWinDllAddr + g_WxCalls.login); } - -wstring GetSelfWxid() { return String2Wstring(GET_STRING(g_WeChatWinDllAddr + g_WxCalls.ui.wxid)); } diff --git a/碲矿.jpeg b/TEQuant.jpeg similarity index 100% rename from 碲矿.jpeg rename to TEQuant.jpeg diff --git a/WeChatFerry.sln b/WeChatFerry.sln index 9af122e..4c077b5 100644 --- a/WeChatFerry.sln +++ b/WeChatFerry.sln @@ -1,72 +1,36 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +VisualStudioVersion = 16.0.32802.440 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Spy", "Spy\Spy.vcxproj", "{4A0A22F7-0B1C-4565-A443-C43FAC6C6396}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spy", "spy\spy.vcxproj", "{4DE80B82-5F6A-4C4C-9D16-1574308110FA}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDK", "SDK\SDK.vcxproj", "{707F2DCD-1001-42A7-B20E-B85B1BBAB228}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpp", "cpp\cpp.vcxproj", "{2FC45612-A106-42D1-871F-1DE704095B2C}" ProjectSection(ProjectDependencies) = postProject - {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} = {4A0A22F7-0B1C-4565-A443-C43FAC6C6396} + {ABFCB647-137F-478B-A73E-F0B1E3ADC215} = {ABFCB647-137F-478B-A73E-F0B1E3ADC215} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "App", "App\App.vcxproj", "{44C1A579-22A7-4BA3-A618-45BD01600294}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdk", "sdk\sdk.vcxproj", "{ABFCB647-137F-478B-A73E-F0B1E3ADC215}" 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} + {4DE80B82-5F6A-4C4C-9D16-1574308110FA} = {4DE80B82-5F6A-4C4C-9D16-1574308110FA} 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 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x86.ActiveCfg = Release|Win32 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x86.Build.0 = Release|Win32 + {2FC45612-A106-42D1-871F-1DE704095B2C}.Release|x86.ActiveCfg = Release|Win32 + {2FC45612-A106-42D1-871F-1DE704095B2C}.Release|x86.Build.0 = Release|Win32 + {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x86.ActiveCfg = Release|Win32 + {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {569E7E61-8FD2-43B5-8F34-C61D6417F905} + SolutionGuid = {76A678AA-570C-4CB7-B58F-3B2C170ACAC0} EndGlobalSection EndGlobal diff --git a/App/App.vcxproj b/cpp/cpp.vcxproj similarity index 83% rename from App/App.vcxproj rename to cpp/cpp.vcxproj index 21802c7..06cfcf6 100644 --- a/App/App.vcxproj +++ b/cpp/cpp.vcxproj @@ -1,164 +1,172 @@ - - - - - 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\ -xcopy /y $(SolutionDir)App\test.jpg $(OutDir)wx\ - - - Copy output - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {2fc45612-a106-42d1-871f-1de704095b2c} + cpp + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + true + x86-windows-static + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)/spy;$(SolutionDir)/rpc;C:\Tools\vcpkg\installed\x86-windows-static\include;%(AdditionalIncludeDirectories) + 4251 + stdcpp17 + MultiThreaded + + + Console + true + true + true + $(OutDir)sdk.lib;iphlpapi.lib;wsock32.lib;ws2_32.lib;crypt32.lib;%(AdditionalDependencies) + + + xcopy /y $(OutDir)cpp.exe $(OutDir)out\cpp + + + Copy files + + + + + 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/SDKpy/SDKpy.vcxproj.filters b/cpp/cpp.vcxproj.filters similarity index 61% rename from SDKpy/SDKpy.vcxproj.filters rename to cpp/cpp.vcxproj.filters index fd4a0d2..48daa59 100644 --- a/SDKpy/SDKpy.vcxproj.filters +++ b/cpp/cpp.vcxproj.filters @@ -1,36 +1,47 @@ - - - - - {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 - - - - - 头文件 - - - 头文件 - - - 头文件 - - - - - 源文件 - - - 源文件 - - + + + + + {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 + + + {0b2f5211-3264-4e9b-8f6d-9f2b51780156} + + + + + proto + + + proto + + + 头文件 + + + + + 源文件 + + + proto + + + proto + + + + + proto + + \ No newline at end of file diff --git a/App/App.vcxproj.user b/cpp/cpp.vcxproj.user similarity index 92% rename from App/App.vcxproj.user rename to cpp/cpp.vcxproj.user index 0f14913..88a5509 100644 --- a/App/App.vcxproj.user +++ b/cpp/cpp.vcxproj.user @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/cpp/main.cpp b/cpp/main.cpp new file mode 100644 index 0000000..693e416 --- /dev/null +++ b/cpp/main.cpp @@ -0,0 +1,525 @@ +/* +RPC Client +*/ +#pragma warning(disable : 4251) +#pragma execution_character_set("utf-8") + +#include +#include +#include + +#include + +#include "../proto/wcf.grpc.pb.h" +#include "../sdk/sdk.h" + +using namespace std; + +using grpc::Channel; +using grpc::ClientContext; +using grpc::Status; + +using wcf::Contact; +using wcf::Contacts; +using wcf::DbField; +using wcf::DbNames; +using wcf::DbQuery; +using wcf::DbRow; +using wcf::DbRows; +using wcf::DbTable; +using wcf::DbTables; +using wcf::Empty; +using wcf::ImageMsg; +using wcf::MsgTypes; +using wcf::Response; +using wcf::String; +using wcf::TextMsg; +using wcf::Verification; +using wcf::Wcf; +using wcf::WxMsg; + +class WcfClient +{ +public: + static WcfClient &Instance(string host_port) + { + static WcfClient instance(grpc::CreateChannel(host_port, grpc::InsecureChannelCredentials())); + return instance; + } + + ~WcfClient() + { + cout << "~WcfClient()" << endl; + this->DisableRecvMsg(); + WxDestroySDK(); + } + + int IsLogin() + { + Empty empty; + Response rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcIsLogin(&context, &empty, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "IsLogin rpc failed." << endl; + } + + return rsp.status(); + } + + string GetSelfWxid() + { + Empty empty; + String rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcGetSelfWxid(&context, &empty, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "GetSelfWxid rpc failed." << endl; + } + + return rsp.str(); + } + + void EnableRecvMsg(function msg_handle_cb) + { + class Reader : public grpc::ClientReadReactor + { + public: + Reader(Wcf::Stub *stub, function msg_handle_cb) + : msg_handle_cb_(msg_handle_cb) + { + stub->async()->RpcEnableRecvMsg(&context_, &empty_, this); + StartRead(&msg_); + StartCall(); + } + + void OnReadDone(bool ok) override + { + if (ok) { + try { + msg_handle_cb_(msg_); + } catch (...) { + cout << "OnMsg wrong..." << endl; + } + StartRead(&msg_); + } + } + + void OnDone(const Status &s) override + { + unique_lock l(mu_); + status_ = s; + done_ = true; + cv_.notify_one(); + } + + Status Await() + { + unique_lock l(mu_); + cv_.wait(l, [this] { return done_; }); + return move(status_); + } + + private: + Empty empty_; + WxMsg msg_; + ClientContext context_; + + mutex mu_; + Status status_; + bool done_ = false; + condition_variable cv_; + + function msg_handle_cb_; + }; + + Reader reader(stub_.get(), msg_handle_cb); + Status status = reader.Await(); + + if (!status.ok()) { + cout << "GetMessage rpc failed." << endl; + } + } + + int DisableRecvMsg() + { + Empty empty; + Response rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcDisableRecvMsg(&context, &empty, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "DisableRecvMsg rpc failed." << endl; + } + + return rsp.status(); + } + + int SendTextMsg(string msg, string receiver, string atusers) + { + Response rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + TextMsg t_msg; + t_msg.set_msg(msg); + t_msg.set_receiver(receiver); + t_msg.set_aters(atusers); + + stub_->async()->RpcSendTextMsg(&context, &t_msg, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "SendTextMsg rpc failed." << endl; + rsp.set_status(-999); // TODO: Unify error code + } + + return rsp.status(); + } + + int SendImageMsg(string path, string receiver) + { + Response rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + ImageMsg i_msg; + i_msg.set_path(path); + i_msg.set_receiver(receiver); + + stub_->async()->RpcSendImageMsg(&context, &i_msg, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "SendImageMsg rpc failed." << endl; + rsp.set_status(-999); // TODO: Unify error code + } + + return rsp.status(); + } + + MsgTypes GetMsgTypes(void) + { + Empty empty; + MsgTypes mt; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcGetMsgTypes(&context, &empty, &mt, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "GetMsgTypes rpc failed." << endl; + } + + return mt; + } + + Contacts GetContacts(void) + { + Empty empty; + Contacts contacts; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcGetContacts(&context, &empty, &contacts, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "GetContacts rpc failed." << endl; + } + + return contacts; + } + + DbNames GetDbNames(void) + { + Empty empty; + DbNames names; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + stub_->async()->RpcGetDbNames(&context, &empty, &names, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "GetDbNames rpc failed." << endl; + } + + return names; + } + + DbTables GetDbTables(string db) + { + DbTables tables; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + String s_db; + s_db.set_str(db); + + stub_->async()->RpcGetDbTables(&context, &s_db, &tables, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "GetDbTables rpc failed." << endl; + } + + return tables; + } + + DbRows ExecDbQuery(string db, string sql) + { + DbRows rows; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + DbQuery query; + query.set_db(db); + query.set_sql(sql); + + stub_->async()->RpcExecDbQuery(&context, &query, &rows, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "ExecDbQuery rpc failed." << endl; + } + + return rows; + } + + int AcceptNewFriend(string v3, string v4) + { + Response rsp; + ClientContext context; + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + + Verification v; + v.set_v3(v3); + v.set_v4(v4); + + stub_->async()->RpcAcceptNewFriend(&context, &v, &rsp, [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + std::unique_lock lock(mu); + cv.wait(lock, [&done] { return done; }); + + if (!status.ok()) { + cout << "ExecDbQuery rpc failed." << endl; + rsp.set_status(-999); // TODO: Unify error code + } + + return rsp.status(); + } + +private: + unique_ptr stub_; + WcfClient(shared_ptr channel) + : stub_(Wcf::NewStub(channel)) + { + WxInitSDK(); + } +}; + +int OnMsg(WxMsg msg) +{ + cout << "Got Message: \n" + << msg.is_self() << ", " << msg.is_group() << ", " << msg.type() << ", " << msg.id() << ", " << msg.xml() + << ", " << msg.sender() << ", " << msg.roomid() << ", " << msg.content() << endl; + return 0; +} + +volatile sig_atomic_t gStop; +void handler(int s) +{ + cout << "Ctrl + C" << endl; + gStop = 1; +} + +int main(int argc, char **argv) +{ + int ret; + + signal(SIGINT, handler); + + WcfClient &client = WcfClient::Instance("localhost:10086"); + + cout << "IsLogin: " << client.IsLogin() << endl; + cout << "Self Wxid: " << client.GetSelfWxid() << endl; + + ret = client.SendTextMsg("来自CPP的消息!", "filehelper", ""); + cout << "SendTextMsg: " << ret << endl; + + ret = client.SendImageMsg("TEQuant.jpeg", "filehelper"); + cout << "SendImageMsg: " << ret << endl; + + MsgTypes mts = client.GetMsgTypes(); + cout << "GetMsgTypes: " << mts.types_size() << endl; + map m(mts.types().begin(), mts.types().end()); + for (auto &[k, v] : m) { + cout << k << ": " << v << endl; + } + + Contacts cnts = client.GetContacts(); + cout << "GetContacts: " << cnts.contacts().size() << endl; + vector vcnts(cnts.contacts().begin(), cnts.contacts().end()); + for (auto &c : vcnts) { + string gender = ""; + if (c.gender() == 1) { + gender = "男"; + } else if (c.gender() == 2) { + gender = "女"; + } + cout << c.wxid() << "\t" << c.code() << "\t" << c.name() << "\t" << c.country() << "\t" << c.province() << "\t" + << c.city() << "\t" << gender << endl; + } + + DbNames db = client.GetDbNames(); + cout << "GetDbNames: " << db.names().size() << endl; + vector dbs(db.names().begin(), db.names().end()); + for (auto &name : dbs) { + cout << name << endl; + } + + DbTables tbls = client.GetDbTables("db"); + cout << "GetDbTables: " << tbls.tables().size() << endl; + vector vtbls(tbls.tables().begin(), tbls.tables().end()); + for (auto &tbl : vtbls) { + cout << tbl.name() << "\n" << tbl.sql() << endl; + } + + DbRows r = client.ExecDbQuery("MicroMsg.db", "SELECT * FROM Contact LIMIT 1;"); + cout << "ExecDbQuery: " << r.rows().size() << endl; + vector vrows(r.rows().begin(), r.rows().end()); + for (auto &row : vrows) { + vector vfields(row.fields().begin(), row.fields().end()); + for (auto &field : vfields) + cout << field.column() << "[" << field.type() << "]: " << field.content() << endl; + } + + // 需要正确的 v3、v4 才能调用 + // ret = client.AcceptNewFriend("v3", "v4"); + // cout << "AcceptNewFriend: " << ret << endl; + + function cb = OnMsg; + thread t1 = thread([&]() { client.EnableRecvMsg(cb); }); + + while (!gStop) { + Sleep(1000); + } + + cout << "Cleanup" << endl; + client.DisableRecvMsg(); + + system("pause"); + client.~WcfClient(); + + return 0; +} diff --git a/proto/wcf.proto b/proto/wcf.proto new file mode 100644 index 0000000..b327bf8 --- /dev/null +++ b/proto/wcf.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package wcf; + +service Wcf { + rpc RpcIsLogin(Empty) returns (Response) {} + rpc RpcGetSelfWxid(Empty) returns (String) {} + rpc RpcEnableRecvMsg(Empty) returns (stream WxMsg) {} + rpc RpcDisableRecvMsg(Empty) returns (Response) {} + rpc RpcSendTextMsg(TextMsg) returns (Response) {} + rpc RpcSendImageMsg(ImageMsg) returns (Response) {} + rpc RpcGetMsgTypes(Empty) returns (MsgTypes) {} + rpc RpcGetContacts(Empty) returns (Contacts) {} + rpc RpcGetDbNames(Empty) returns (DbNames) {} + rpc RpcGetDbTables(String) returns (DbTables) {} + rpc RpcExecDbQuery(DbQuery) returns (DbRows) {} + rpc RpcAcceptNewFriend(Verification) returns (Response) {} +} + +message Empty {} + +message WxMsg { + bool is_self = 1; // 是否自己发送的 + bool is_group = 2; // 是否群消息 + int32 type = 3; // 消息类型 + string id = 4; // 消息 id + string xml = 5; // 消息 xml + string sender = 6; // 消息发送者 + string roomid = 7; // 群 id(如果是群消息的话) + string content = 8; // 消息内容 +} + +message Response { + int32 status = 1; // 状态码 +} + +message TextMsg { + string msg = 1; // 要发送的消息内容 + string receiver = 2; // 消息接收人,当为群时可@ + string aters = 3; // 要@的人列表,逗号分隔 +} + +message ImageMsg { + string path = 1; // 要发送的图片的路径 + string receiver = 2; // 消息接收人 +} + +message MsgTypes { map types = 1; } + +message Contact { + string wxid = 1; // 微信 id + string code = 2; // 微信号 + string name = 3; // 微信昵称 + string country = 4; // 国家 + string province = 5; // 省/州 + string city = 6; // 城市 + int32 gender = 7; // 性别 +} +message Contacts { repeated Contact contacts = 1; } + +message DbNames { repeated string names = 1; } + +message String { string str = 1; } + +message DbTable { + string name = 1; // 表名 + string sql = 2; // 建表 SQL +} +message DbTables { repeated DbTable tables = 1; } + +message DbQuery { + string db = 1; // 目标数据库 + string sql = 2; // 查询 SQL +} + +message DbField { + int32 type = 1; // 字段类型 + string column = 2; // 字段名称 + bytes content = 3; // 字段内容 +} +message DbRow { repeated DbField fields = 1; } +message DbRows { repeated DbRow rows = 1; } + +message Verification { + string v3 = 1; + string v4 = 2; +} diff --git a/python/README.MD b/python/README.MD new file mode 100644 index 0000000..39576d1 --- /dev/null +++ b/python/README.MD @@ -0,0 +1,26 @@ +# WeChatFerry Python 客户端 +代码于 `Python3.7` 环境开发。 + +## 配置环境 +```sh +# 创建虚拟环境 +python -m venv .env +# 激活虚拟环境 +source .env/Scripts/activate +# 升级 pip +pip install --upgrade pip +# 安装依赖包 +pip install grpcio grpcio-tools +``` + +## 运行 +```sh +# 启动客户端 +python demo.py +``` + +## 重新生成 gRPC 文件 +```sh +cd wcf +python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I=../ wcf.proto +``` diff --git a/python/demo.py b/python/demo.py new file mode 100644 index 0000000..6310bbe --- /dev/null +++ b/python/demo.py @@ -0,0 +1,49 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- + +import logging +import signal +from time import sleep + +from wcf.client import Wcf + + +def main(): + logging.info("Start demo...") + wcf = Wcf() + + def handler(sig, frame): + wcf.cleanup() + exit(0) + + signal.signal(signal.SIGINT, handler) + sleep(1) # Slow down + print(f"Is Login: {True if wcf.is_login() else False}") + print(f"SelfWxid: {wcf.get_self_wxid()}") + + sleep(1) + wcf.enable_recv_msg(print) + # wcf.disable_recv_msg() # Call anytime when you don't want to receive messages + + ret = wcf.send_text("Hello world.", "filehelper") + print(f"send_text: {ret}") + + ret = wcf.send_image("TEQuant.jpeg", "filehelper") + print(f"send_image: {ret}") + + print(f"Message types:\n{wcf.get_msg_types()}") + print(f"Contacts:\n{wcf.get_contacts()}") + + print(f"DBs:\n{wcf.get_dbs()}") + print(f"Tables:\n{wcf.get_tables('db')}") + print(f"Results:\n{wcf.query_sql('MicroMsg.db', 'SELECT * FROM Contact LIMIT 1;')}") + + # wcf.accept_new_friend("v3", "v4") # 需要真正的 V3、V4 信息 + + # Keep running to receive messages + wcf.keep_running() + + +if __name__ == "__main__": + logging.basicConfig(level='DEBUG') + main() diff --git a/python/wcf/client.py b/python/wcf/client.py new file mode 100644 index 0000000..542612c --- /dev/null +++ b/python/wcf/client.py @@ -0,0 +1,202 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- + +import atexit +import ctypes +import logging +import os +import re +import sys +from threading import Thread +from time import sleep +from typing import Any, List, Callable, Optional + +import grpc + +WCF_ROOT = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, WCF_ROOT) +import wcf_pb2 # noqa +import wcf_pb2_grpc # noqa + + +class Wcf(): + """WeChatFerry, a tool to play WeChat.""" + class WxMsg(): + """微信消息""" + + def __init__(self, msg: wcf_pb2.WxMsg) -> None: + self._is_self = msg.is_self + self._is_group = msg.is_group + self.type = msg.type + self.id = msg.id + self.xml = msg.xml + self.sender = msg.sender + self.roomid = msg.roomid + self.content = msg.content + + def __str__(self) -> str: + s = f"{self.sender}[{self.roomid}]\t{self.id}-{self.type}-{self.xml.replace(chr(10), '').replace(chr(9),'')}\n" + s += self.content + return s + + def from_self(self) -> bool: + """是否自己发的消息""" + return self._is_self == 1 + + def from_group(self) -> bool: + """是否群聊消息""" + return self._is_group + + def is_at(self, wxid) -> bool: + """是否被@:群消息,在@名单里,并且不是@所有人""" + return self.from_group() and re.findall( + f".*({wxid}).*", self.xml) and not re.findall(r"@(?:所有人|all)", self.xml) + + def is_text(self) -> bool: + """是否文本消息""" + return self.type == 1 + + def __init__(self, host_port: str = "localhost:10086") -> None: + self._enable_recv_msg = False + self.LOG = logging.getLogger("WCF") + self._sdk = ctypes.cdll.LoadLibrary(f"{WCF_ROOT}/sdk.dll") + if self._sdk.WxInitSDK() != 0: + self.LOG.error("初始化失败!") + return + + self._channel = grpc.insecure_channel(host_port) + self._stub = wcf_pb2_grpc.WcfStub(self._channel) + atexit.register(self.disable_recv_msg) # 退出的时候停止消息接收,防止内存泄露 + self._is_running = True + self.contacts = [] + self._SQL_TYPES = {1: int, 2: float, 3: lambda x: x.decode("utf-8"), 4: bytes, 5: lambda x: None} + self.self_wxid = self.get_self_wxid() + + def __del__(self) -> None: + self.cleanup() + + def cleanup(self) -> None: + """停止 gRPC,关闭连接,回收资源""" + if not self._is_running: + return + + self.disable_recv_msg() + self._channel.close() + self._sdk.WxDestroySDK() + handle = self._sdk._handle + del self._sdk + ctypes.windll.kernel32.FreeLibrary(handle) + self._is_running = False + + def keep_running(self): + """阻塞进程,让 RPC 一直维持连接""" + try: + while True: + sleep(1) + except Exception as e: + self.cleanup() + + def is_login(self) -> int: + """是否已经登录""" + rsp = self._stub.RpcIsLogin(wcf_pb2.Empty()) + return rsp.status + + def get_self_wxid(self) -> str: + """获取登录账户的 wxid""" + rsp = self._stub.RpcGetSelfWxid(wcf_pb2.Empty()) + return rsp.str + + def _rpc_get_message(self, func): + rsps = self._stub.RpcEnableRecvMsg(wcf_pb2.Empty()) + try: + for rsp in rsps: + func(self.WxMsg(rsp)) + except Exception as e: + self.LOG.error(f"RpcEnableRecvMsg: {e}") + finally: + self.disable_recv_msg() + + def enable_recv_msg(self, callback: Callable[[WxMsg], None] = None) -> bool: + """设置接收消息回调""" + if self._enable_recv_msg: + return True + + if callback is None: + return False + + self._enable_recv_msg = True + # 阻塞,把控制权交给用户 + # self._rpc_get_message(callback) + + # 不阻塞,启动一个新的线程来接收消息 + Thread(target=self._rpc_get_message, name="GetMessage", args=(callback,), daemon=True).start() + + return True + + def disable_recv_msg(self) -> int: + """停止接收消息""" + if not self._enable_recv_msg: + return -1 + + rsp = self._stub.RpcDisableRecvMsg(wcf_pb2.Empty()) + if rsp.status == 0: + self._enable_recv_msg = False + + return rsp.status + + def send_text(self, msg: str, receiver: str, aters: Optional[str] = "") -> int: + """发送文本消息""" + rsp = self._stub.RpcSendTextMsg(wcf_pb2.TextMsg(msg=msg, receiver=receiver, aters=aters)) + return rsp.status + + def send_image(self, path: str, receiver: str) -> int: + """发送图片""" + rsp = self._stub.RpcSendImageMsg(wcf_pb2.ImageMsg(path=path, receiver=receiver)) + return rsp.status + + def get_msg_types(self) -> dict: + """获取所有消息类型""" + rsp = self._stub.RpcGetMsgTypes(wcf_pb2.Empty()) + return dict(sorted(dict(rsp.types).items())) + + def get_contacts(self) -> List[dict]: + """获取完整通讯录""" + rsp = self._stub.RpcGetContacts(wcf_pb2.Empty()) + for cnt in rsp.contacts: + gender = "" + if cnt.gender == 1: + gender = "男" + elif cnt.gender == 2: + gender = "女" + self.contacts.append({"wxid": cnt.wxid, "code": cnt.code, "name": cnt.name, + "country": cnt.country, "province": cnt.province, "city": cnt.city, "gender": gender}) + return self.contacts + + def get_dbs(self) -> List[str]: + """获取所有数据库""" + rsp = self._stub.RpcGetDbNames(wcf_pb2.Empty()) + return rsp.names + + def get_tables(self, db: str) -> List[dict]: + """获取 db 中所有表""" + tables = [] + rsp = self._stub.RpcGetDbTables(wcf_pb2.String(str=db)) + for tbl in rsp.tables: + tables.append({"name": tbl.name, "sql": tbl.sql.replace("\t", "")}) + return tables + + def query_sql(self, db: str, sql: str) -> List[dict]: + """执行 SQL""" + result = [] + rsp = self._stub.RpcExecDbQuery(wcf_pb2.DbQuery(db=db, sql=sql)) + for r in rsp.rows: + row = {} + for f in r.fields: + row[f.column] = self._SQL_TYPES[f.type](f.content) + result.append(row) + return result + + def accept_new_friend(self, v3: str, v4: str) -> int: + """通过好友验证""" + rsp = self._stub.RpcAcceptNewFriend(wcf_pb2.Verification(v3=v3, v4=v4)) + return rsp.status diff --git a/SDK/SDK.vcxproj b/sdk/SDK.vcxproj similarity index 83% rename from SDK/SDK.vcxproj rename to sdk/SDK.vcxproj index cafe36f..9d6e915 100644 --- a/SDK/SDK.vcxproj +++ b/sdk/SDK.vcxproj @@ -21,8 +21,8 @@ 16.0 Win32Proj - {707f2dcd-1001-42a7-b20e-b85b1bbab228} - SDK + {abfcb647-137f-478b-a73e-f0b1e3adc215} + sdk 10.0 @@ -82,16 +82,18 @@ false + + true + x86-windows-static + Level3 true WIN32;_DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - NotUsing + Use pch.h - /utf-8 %(AdditionalOptions) - $(SolutionDir)Rpc Windows @@ -109,8 +111,11 @@ WIN32;NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing - pch.h - $(SolutionDir)Rpc + + + stdcpp17 + $(SolutionDir)spy;C:\Tools\vcpkg\installed\x86-windows-static\include + MultiThreaded Windows @@ -119,8 +124,19 @@ true false sdk.def - Rpcrt4.lib;%(AdditionalDependencies) + + xcopy /y $(OutDir)sdk.dll $(OutDir)out\cpp +xcopy /y $(OutDir)sdk.dll $(OutDir)out\python\wcf +xcopy /y $(SolutionDir)TEQuant.jpeg $(OutDir)out\cpp +xcopy /y $(SolutionDir)TEQuant.jpeg $(OutDir)out\python +xcopy /y $(SolutionDir)python\demo.py $(OutDir)out\python +xcopy /y $(SolutionDir)python\README.MD $(OutDir)out\python +xcopy /y $(SolutionDir)python\wcf\*.py $(OutDir)out\python\wcf + + + Copy files + @@ -159,21 +175,18 @@ - + + - - - - + + - - diff --git a/SDK/SDK.vcxproj.filters b/sdk/SDK.vcxproj.filters similarity index 77% rename from SDK/SDK.vcxproj.filters rename to sdk/SDK.vcxproj.filters index 66ebf04..58eff8d 100644 --- a/SDK/SDK.vcxproj.filters +++ b/sdk/SDK.vcxproj.filters @@ -21,41 +21,32 @@ 头文件 - + 头文件 - + 头文件 头文件 - - 头文件 - 源文件 + + 源文件 + + + 源文件 + 源文件 - - 源文件 - - - 源文件 - - - 源文件 - 源文件 - - 源文件 - diff --git a/SDK/SDK.vcxproj.user b/sdk/SDK.vcxproj.user similarity index 100% rename from SDK/SDK.vcxproj.user rename to sdk/SDK.vcxproj.user diff --git a/sdk/dllmain.cpp b/sdk/dllmain.cpp new file mode 100644 index 0000000..1d8e3d4 --- /dev/null +++ b/sdk/dllmain.cpp @@ -0,0 +1,14 @@ +// dllmain.cpp : 定义 DLL 应用程序的入口点。 +#include "framework.h" + +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: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/SDKpy/framework.h b/sdk/framework.h similarity index 100% rename from SDKpy/framework.h rename to sdk/framework.h diff --git a/sdk/injector.cpp b/sdk/injector.cpp new file mode 100644 index 0000000..80a0751 --- /dev/null +++ b/sdk/injector.cpp @@ -0,0 +1,90 @@ +#include "injector.h" + +HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase) +{ + HANDLE hThread; + SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR); + // 1. 打开目标进程 + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if (hProcess == NULL) { + MessageBox(NULL, L"打开进程失败", L"InjectDll", 0); + return NULL; + } + + // 2. 在目标进程的内存里开辟空间 + LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE); + if (pRemoteAddress == NULL) { + MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0); + return NULL; + } + + // 3. 把 dll 的路径写入到目标进程的内存空间中 + WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL); + + // 3. 创建一个远程线程,让目标进程调用 LoadLibrary + hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, 0, NULL); + if (hThread == NULL) { + MessageBox(NULL, L"LoadLibrary 调用失败", L"InjectDll", 0); + return NULL; + } + + WaitForSingleObject(hThread, -1); + GetExitCodeThread(hThread, (LPDWORD)injectedBase); + CloseHandle(hThread); + VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); + // CloseHandle(hProcess); // Close when exit + + return hProcess; +} + +bool EjectDll(HANDLE process, HMODULE dllBase) +{ + HANDLE hThread = NULL; + + // 使目标进程调用 FreeLibrary,卸载 DLL + hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)dllBase, 0, NULL); + if (hThread == NULL) { + MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0); + return false; + } + + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + CloseHandle(process); + return true; +} + +static void *GetFuncAddr(LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName) +{ + HMODULE hLoaded = LoadLibrary(dllPath); + if (hLoaded == NULL) { + return NULL; + } + + void *absAddr = GetProcAddress(hLoaded, funcName); + DWORD offset = (DWORD)absAddr - (DWORD)hLoaded; + + FreeLibrary(hLoaded); + + return (void *)((DWORD)dllBase + offset); +} + +bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, DWORD *ret) +{ + void *pFunc = GetFuncAddr(dllPath, dllBase, funcName); + if (pFunc == NULL) { + return false; + } + + HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL); + if (hThread == NULL) { + return false; + } + WaitForSingleObject(hThread, INFINITE); + if (ret != NULL) { + GetExitCodeThread(hThread, ret); + } + + CloseHandle(hThread); + return true; +} diff --git a/sdk/injector.h b/sdk/injector.h new file mode 100644 index 0000000..4f0f900 --- /dev/null +++ b/sdk/injector.h @@ -0,0 +1,7 @@ +#pragma once + +#include "framework.h" + +HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase); +bool EjectDll(HANDLE process, HMODULE dllBase); +bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, DWORD *ret); diff --git a/sdk/sdk.cpp b/sdk/sdk.cpp new file mode 100644 index 0000000..76834ad --- /dev/null +++ b/sdk/sdk.cpp @@ -0,0 +1,73 @@ +#include "Shlwapi.h" +#include "framework.h" +#include +#include + +#include "injector.h" +#include "log.h" +#include "sdk.h" +#include "util.h" + +static DWORD wcPid = 0; +static HANDLE wcProcess = NULL; +static HMODULE spyBase = NULL; +static WCHAR spyDllPath[MAX_PATH] = { 0 }; + +int WxInitSDK() +{ + int status = 0; + InitLogger(); + LOG_INFO("WxInitSDK."); + GetModuleFileName(GetModuleHandle(WECHATSDKDLL), spyDllPath, MAX_PATH); + PathRemoveFileSpec(spyDllPath); + PathAppend(spyDllPath, WECHATINJECTDLL); + + if (!PathFileExists(spyDllPath)) { + LOG_ERROR("DLL does not exists."); + return ERROR_FILE_NOT_FOUND; + } + + status = OpenWeChat(&wcPid); + if (status != 0) { + LOG_ERROR("OpenWeChat failed: {}.", status); + return status; + } + + Sleep(2000); // 等待微信打开 + wcProcess = InjectDll(wcPid, spyDllPath, &spyBase); + if (wcProcess == NULL) { + LOG_ERROR("Failed to Inject DLL into WeChat."); + return -1; + } + + if (!CallDllFunc(wcProcess, spyDllPath, spyBase, "InitSpy", NULL)) { + LOG_ERROR("Failed to InitSpy."); + return -1; + } + + do { + if (!CallDllFunc(wcProcess, spyDllPath, spyBase, "IsLogin", (DWORD *)&status)) { + LOG_ERROR("Failed to check login status."); + return -1; + } + Sleep(1000); + } while (status == 0); + + return 0; +} + +int WxDestroySDK() +{ + LOG_INFO("WxDestroySDK"); + if (!CallDllFunc(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) { + LOG_ERROR("Failed to CleanupSpy."); + return -1; + } + + if (!EjectDll(wcProcess, spyBase)) { + LOG_ERROR("Failed to Eject DLL."); + return -1; // TODO: Unify error codes + } + + return 0; +} diff --git a/sdk/sdk.def b/sdk/sdk.def new file mode 100644 index 0000000..e601ae1 --- /dev/null +++ b/sdk/sdk.def @@ -0,0 +1,3 @@ +EXPORTS + WxInitSDK + WxDestroySDK diff --git a/sdk/sdk.h b/sdk/sdk.h new file mode 100644 index 0000000..d982143 --- /dev/null +++ b/sdk/sdk.h @@ -0,0 +1,4 @@ +#pragma once + +int WxInitSDK(); +int WxDestroySDK(); diff --git a/Spy/Spy.vcxproj b/spy/Spy.vcxproj similarity index 75% rename from Spy/Spy.vcxproj rename to spy/Spy.vcxproj index 3567e04..3d8a1c8 100644 --- a/Spy/Spy.vcxproj +++ b/spy/Spy.vcxproj @@ -21,9 +21,11 @@ 16.0 Win32Proj - {4a0a22f7-0b1c-4565-a443-c43fac6c6396} - Spy + {4de80b82-5f6a-4c4c-9d16-1574308110fa} + spy 10.0 + x86-windows-static + x64-windows-static @@ -75,6 +77,7 @@ false + false true @@ -82,26 +85,24 @@ false + + true + Level3 true WIN32;_DEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - NotUsing + Use pch.h - $(SolutionDir)Rpc;$(SolutionDir)SDK;%(AdditionalIncludeDirectories) - /utf-8 %(AdditionalOptions) Windows true false + spy.def - - /app_config /prefix client "client_" server "server_" - $(SolutionDir)Rpc;%(AdditionalIncludeDirectories) - @@ -112,9 +113,13 @@ WIN32;NDEBUG;SPY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing - pch.h - $(SolutionDir)Rpc;$(SolutionDir)SDK;%(AdditionalIncludeDirectories) - /utf-8 %(AdditionalOptions) + + + $(SolutionDir)rpc;C:\Tools\vcpkg\installed\x86-windows-static\include + + 4251 + MultiThreaded + stdcpp17 Windows @@ -122,12 +127,28 @@ true true false - Rpcrt4.lib;%(AdditionalDependencies) + iphlpapi.lib;wsock32.lib;ws2_32.lib;crypt32.lib;%(AdditionalDependencies) + spy.def - - /prefix client "client_" server "server_" - $(SolutionDir)Rpc;%(AdditionalIncludeDirectories) - + + cd $(OutDir) +md out\cpp +md out\python\wcf +xcopy /y $(OutDir)spy.dll $(OutDir)out\cpp +xcopy /y $(OutDir)spy.dll $(OutDir)out\python\wcf +xcopy /y $(SolutionDir)proto\wcf.proto $(OutDir)out\python + + + Copy spy.dll + + + cd $(SolutionDir)proto +protoc -I=. --cpp_out=. wcf.proto +protoc -I=. --grpc_out=. --plugin=protoc-gen-grpc="C:\Tools\vcpkg\packages\grpc_x64-windows\tools\grpc\grpc_cpp_plugin.exe" wcf.proto + + + gRPC + @@ -142,6 +163,7 @@ Windows true false + spy.def @@ -161,44 +183,43 @@ true true false + spy.def - - + + + + - - - + + + + - - $(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) - + + diff --git a/Spy/Spy.vcxproj.filters b/spy/Spy.vcxproj.filters similarity index 77% rename from Spy/Spy.vcxproj.filters rename to spy/Spy.vcxproj.filters index 1772c6d..786764b 100644 --- a/Spy/Spy.vcxproj.filters +++ b/spy/Spy.vcxproj.filters @@ -13,24 +13,30 @@ {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} + + {5da8b2ee-3201-441c-b42f-a420e9aad78b} 头文件 - - 头文件 - 头文件 - + 头文件 - + + 头文件 + + + 头文件 + + + 头文件 + + 头文件 @@ -39,39 +45,42 @@ 头文件 - - 头文件 - - - 头文件 - - - 头文件 - 头文件 - + 头文件 + + 头文件 + + + proto + + + proto + 源文件 - - 源文件 - - - 源文件 - 源文件 - + 源文件 - + + 源文件 + + + 源文件 + + + 源文件 + + 源文件 @@ -80,22 +89,25 @@ 源文件 - - 源文件 - - - 源文件 - 源文件 - + 源文件 + + proto + + + proto + - - Rpc - + + 源文件 + + + proto + \ No newline at end of file diff --git a/Spy/Spy.vcxproj.user b/spy/Spy.vcxproj.user similarity index 100% rename from Spy/Spy.vcxproj.user rename to spy/Spy.vcxproj.user diff --git a/Spy/accept_new_friend.cpp b/spy/accept_new_friend.cpp similarity index 100% rename from Spy/accept_new_friend.cpp rename to spy/accept_new_friend.cpp diff --git a/Spy/accept_new_friend.h b/spy/accept_new_friend.h similarity index 100% rename from Spy/accept_new_friend.h rename to spy/accept_new_friend.h diff --git a/SDK/dllmain.cpp b/spy/dllmain.cpp similarity index 68% rename from SDK/dllmain.cpp rename to spy/dllmain.cpp index a378c45..580ec4c 100644 --- a/SDK/dllmain.cpp +++ b/spy/dllmain.cpp @@ -1,19 +1,18 @@ -// dllmain.cpp : 定义 DLL 应用程序的入口点。 +// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "framework.h" - -#include "sdk.h" - -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: { - WxDestroySDK(); // 默认退出时清理 SDK - break; - } - } - return TRUE; -} +#include +#include + +#include "spy.h" + +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: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/Spy/exec_sql.cpp b/spy/exec_sql.cpp similarity index 76% rename from Spy/exec_sql.cpp rename to spy/exec_sql.cpp index 4d82c22..7d92c46 100644 --- a/Spy/exec_sql.cpp +++ b/spy/exec_sql.cpp @@ -48,7 +48,7 @@ using namespace std; extern WxCalls_t g_WxCalls; extern DWORD g_WeChatWinDllAddr; -typedef map dbMap_t; +typedef map dbMap_t; static dbMap_t dbMap; // 回调函数指针 @@ -72,16 +72,15 @@ typedef int(__cdecl *Sqlite3_finalize)(DWORD *); static int cbGetTables(void *ret, int argc, char **argv, char **azColName) { - vector *p = (vector *)ret; - RpcTables_t tbl = { 0 }; + wcf::DbTables *tbls = (wcf::DbTables *)ret; + wcf::DbTable *tbl = tbls->add_tables(); for (int i = 0; i < argc; i++) { if (strcmp(azColName[i], "name") == 0) { - tbl.table = argv[i] ? GetBstrFromString(argv[i]) : NULL; + tbl->set_name(argv[i] ? argv[i] : ""); } else if (strcmp(azColName[i], "sql") == 0) { - tbl.sql = argv[i] ? GetBstrFromString(argv[i]) : NULL; + tbl->set_sql(argv[i] ? argv[i] : ""); } } - p->push_back(tbl); return 0; } @@ -96,7 +95,7 @@ dbMap_t GetDbHandles() DWORD sqlHandleEndAddr = *(DWORD *)(sqlHandleBaseAddr + g_WxCalls.sql.end); while (sqlHandleBeginAddr < sqlHandleEndAddr) { DWORD dwHandle = *(DWORD *)sqlHandleBeginAddr; - wstring dbName = wstring((wchar_t *)(*(DWORD *)(dwHandle + g_WxCalls.sql.name))); + string dbName = Wstring2String(wstring((wchar_t *)(*(DWORD *)(dwHandle + g_WxCalls.sql.name)))); DWORD handle = *(DWORD *)(dwHandle + g_WxCalls.sql.slot); if (handle) { dbMap[dbName] = handle; @@ -107,40 +106,37 @@ dbMap_t GetDbHandles() return dbMap; } -vector GetDbNames() +void GetDbNames(wcf::DbNames *names) { - vector vDbs; if (dbMap.empty()) { dbMap = GetDbHandles(); } - for (auto it = dbMap.begin(); it != dbMap.end(); it++) { - vDbs.push_back(it->first); + + for (auto &[k, v] : dbMap) { + auto *name = names->add_names(); + name->assign(k); } - return vDbs; } -vector GetDbTables(wstring db) +void GetDbTables(const string db, wcf::DbTables *tables) { - vector vTables; - const char *sql = "select * from sqlite_master where type=\"table\";"; - Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)(g_WeChatWinDllAddr + g_WxCalls.sql.exec); - if (dbMap.empty()) { dbMap = GetDbHandles(); } auto it = dbMap.find(db); - if (it != dbMap.end()) { - p_Sqlite3_exec(it->second, sql, (sqlite3_callback)cbGetTables, &vTables, 0); + if (it == dbMap.end()) { + return; // DB not found } - return vTables; + const char *sql = "select name, sql from sqlite_master where type=\"table\";"; + Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)(g_WeChatWinDllAddr + g_WxCalls.sql.exec); + + p_Sqlite3_exec(it->second, sql, (sqlite3_callback)cbGetTables, tables, 0); } -vector> ExecDbQuery(wstring db, wstring sql) +void ExecDbQuery(const string db, const string sql, wcf::DbRows *rows) { - vector> vvSqlResult; - Sqlite3_prepare func_prepare = (Sqlite3_prepare)(g_WeChatWinDllAddr + 0x14227F0); Sqlite3_step func_step = (Sqlite3_step)(g_WeChatWinDllAddr + 0x13EA780); Sqlite3_column_count func_column_count = (Sqlite3_column_count)(g_WeChatWinDllAddr + 0x13EACD0); @@ -155,29 +151,23 @@ vector> ExecDbQuery(wstring db, wstring sql) } DWORD *stmt; - int rc = func_prepare(dbMap[db], Wstring2String(sql).c_str(), -1, &stmt, 0); + int rc = func_prepare(dbMap[db], sql.c_str(), -1, &stmt, 0); if (rc != SQLITE_OK) { - return vvSqlResult; + return; } - wchar_t buffer[128] = { 0 }; while (func_step(stmt) == SQLITE_ROW) { - vector vResult; - int col_count = func_column_count(stmt); + wcf::DbRow *row = rows->add_rows(); + int col_count = func_column_count(stmt); for (int i = 0; i < col_count; i++) { - RpcSqlResult_t result = { 0 }; - result.type = func_column_type(stmt, i); - result.column = GetBstrFromString(func_column_name(stmt, i)); - int length = func_column_bytes(stmt, i); - const void *blob = func_column_blob(stmt, i); - if (length && (result.type != 5)) { - result.content = GetBstrFromByteArray((byte *)blob, length); + wcf::DbField *field = row->add_fields(); + field->set_type(func_column_type(stmt, i)); + field->set_column(func_column_name(stmt, i)); + int length = func_column_bytes(stmt, i); + const void *blob = func_column_blob(stmt, i); + if (length && (field->type() != 5)) { + field->set_content(string((char *)blob, length)); } - - vResult.push_back(result); } - vvSqlResult.push_back(vResult); } - - return vvSqlResult; } diff --git a/spy/exec_sql.h b/spy/exec_sql.h new file mode 100644 index 0000000..5ccd284 --- /dev/null +++ b/spy/exec_sql.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "../proto/wcf.grpc.pb.h" + +void GetDbNames(wcf::DbNames *names); +void GetDbTables(const std::string db, wcf::DbTables *tables); +void ExecDbQuery(const std::string db, const std::string sql, wcf::DbRows *rows); diff --git a/spy/framework.h b/spy/framework.h new file mode 100644 index 0000000..3f0fc4a --- /dev/null +++ b/spy/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include diff --git a/spy/get_contacts.cpp b/spy/get_contacts.cpp new file mode 100644 index 0000000..6ed84b6 --- /dev/null +++ b/spy/get_contacts.cpp @@ -0,0 +1,31 @@ +#pragma execution_character_set("utf-8") + +#include "get_contacts.h" +#include "load_calls.h" +#include "util.h" + +extern WxCalls_t g_WxCalls; +extern DWORD g_WeChatWinDllAddr; + +bool GetContacts(wcf::Contacts *contacts) +{ + DWORD baseAddr = g_WeChatWinDllAddr + g_WxCalls.contact.base; + DWORD tempAddr = GET_DWORD(baseAddr); + DWORD head = GET_DWORD(tempAddr + g_WxCalls.contact.head); + DWORD node = GET_DWORD(head); + + while (node != head) { + wcf::Contact *cnt = contacts->add_contacts(); + cnt->set_wxid(GetStringByAddress(node + g_WxCalls.contact.wxId)); + cnt->set_code(GetStringByAddress(node + g_WxCalls.contact.wxCode)); + cnt->set_name(GetStringByAddress(node + g_WxCalls.contact.wxName)); + cnt->set_country(GetStringByAddress(node + g_WxCalls.contact.wxCountry)); + cnt->set_province(GetStringByAddress(node + g_WxCalls.contact.wxProvince)); + cnt->set_city(GetStringByAddress(node + g_WxCalls.contact.wxCity)); + cnt->set_gender(GET_DWORD(node + g_WxCalls.contact.wxGender)); + + node = GET_DWORD(node); + } + + return true; +} diff --git a/spy/get_contacts.h b/spy/get_contacts.h new file mode 100644 index 0000000..4805148 --- /dev/null +++ b/spy/get_contacts.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../proto/wcf.grpc.pb.h" + +bool GetContacts(wcf::Contacts *contacts); diff --git a/Spy/load_calls.cpp b/spy/load_calls.cpp similarity index 100% rename from Spy/load_calls.cpp rename to spy/load_calls.cpp diff --git a/Spy/load_calls.h b/spy/load_calls.h similarity index 100% rename from Spy/load_calls.h rename to spy/load_calls.h diff --git a/spy/log.cpp b/spy/log.cpp new file mode 100644 index 0000000..9fff914 --- /dev/null +++ b/spy/log.cpp @@ -0,0 +1,21 @@ +#include "log.h" + +#define LOGGER_NAME "WCF" +#define LOGGER_FILE_NAME "logs/wcf.txt" +#define LOGGER_MAX_SIZE 1024 * 1024 * 10 // 10M +#define LOGGER_MAX_FILES 10 // 10 files + +void InitLogger() +{ + static std::shared_ptr gLogger = nullptr; + if (gLogger != nullptr) { + return; + } + + gLogger = spdlog::rotating_logger_mt(LOGGER_NAME, LOGGER_FILE_NAME, LOGGER_MAX_SIZE, LOGGER_MAX_FILES); + // gLogger = spdlog::stdout_color_mt("console"); + + spdlog::set_default_logger(gLogger); + gLogger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%s::%#::%!] %v"); + gLogger->flush_on(spdlog::level::info); +} diff --git a/spy/log.h b/spy/log.h new file mode 100644 index 0000000..1a989de --- /dev/null +++ b/spy/log.h @@ -0,0 +1,14 @@ +#pragma once + +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" + +extern std::shared_ptr gLogger; + +#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__); +#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__); +#define LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__); +#define LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__); + +void InitLogger(); diff --git a/spy/receive_msg.cpp b/spy/receive_msg.cpp new file mode 100644 index 0000000..83132c0 --- /dev/null +++ b/spy/receive_msg.cpp @@ -0,0 +1,143 @@ +#pragma execution_character_set("utf-8") + +#include + +#include "framework.h" + +#include "load_calls.h" +#include "receive_msg.h" +#include "spy_types.h" +#include "util.h" + +using namespace std; + +using wcf::MsgTypes; +using wcf::WxMsg; + +extern bool gIsListening; +extern mutex gMutex; +extern queue gMsgQueue; +extern condition_variable gCv; +extern WxCalls_t g_WxCalls; +extern DWORD g_WeChatWinDllAddr; + +static DWORD reg_buffer = 0; +static DWORD recvMsgHookAddr = 0; +static DWORD recvMsgCallAddr = 0; +static DWORD recvMsgJumpBackAddr = 0; +static CHAR recvMsgBackupCode[5] = { 0 }; + +void GetMsgTypes(MsgTypes *types) +{ + const map m = { { 0x01, "文字" }, + { 0x03, "图片" }, + { 0x22, "语音" }, + { 0x25, "好友确认" }, + { 0x28, "POSSIBLEFRIEND_MSG" }, + { 0x2A, "名片" }, + { 0x2B, "视频" }, + { 0x2F, "石头剪刀布 | 表情图片" }, + { 0x30, "位置" }, + { 0x31, "共享实时位置、文件、转账、链接" }, + { 0x32, "VOIPMSG" }, + { 0x33, "微信初始化" }, + { 0x34, "VOIPNOTIFY" }, + { 0x35, "VOIPINVITE" }, + { 0x3E, "小视频" }, + { 0x270F, "SYSNOTICE" }, + { 0x2710, "红包、系统消息" }, + { 0x2712, "撤回消息" } }; + + for (auto &[k, v] : m) { + (*(types->mutable_types()))[k] = v; + } +} + +void HookAddress(DWORD hookAddr, LPVOID funcAddr, CHAR recvMsgBackupCode[5]) +{ + //组装跳转数据 + BYTE jmpCode[5] = { 0 }; + jmpCode[0] = 0xE9; + + //计算偏移 + *(DWORD *)&jmpCode[1] = (DWORD)funcAddr - hookAddr - 5; + + // 备份原来的代码 + ReadProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, recvMsgBackupCode, 5, 0); + // 写入新的代码 + WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, jmpCode, 5, 0); +} + +void UnHookAddress(DWORD hookAddr, CHAR restoreCode[5]) +{ + WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, restoreCode, 5, 0); +} + +void DispatchMsg(DWORD reg) +{ + WxMsg wxMsg; + DWORD *p = (DWORD *)reg; //消息结构基址 + + wxMsg.set_type(GET_DWORD(*p + g_WxCalls.recvMsg.type)); + wxMsg.set_is_self(GET_DWORD(*p + g_WxCalls.recvMsg.isSelf)); + wxMsg.set_id(GetStringByAddress(*p + g_WxCalls.recvMsg.msgId)); + wxMsg.set_xml(GetStringByAddress(*p + g_WxCalls.recvMsg.msgXml)); + + // 群里的系统消息,xml 为空 + if ((wxMsg.xml().empty()) || (strstr(wxMsg.xml().c_str(), "") != NULL)) { + wxMsg.set_is_group(true); + wxMsg.set_sender(GetStringByAddress(*p + g_WxCalls.recvMsg.wxId)); + wxMsg.set_roomid(GetStringByAddress(*p + g_WxCalls.recvMsg.roomId)); + } else { + wxMsg.set_is_group(false); + wxMsg.set_sender(GetStringByAddress(*p + g_WxCalls.recvMsg.roomId)); + } + wxMsg.set_content(GetStringByAddress(*p + g_WxCalls.recvMsg.content)); + + // 推送到队列 + unique_lock lock(gMutex); + gMsgQueue.push(wxMsg); + lock.unlock(); + + // 通知各方消息就绪 + gCv.notify_all(); +} + +__declspec(naked) void RecieveMsgFunc() +{ + __asm { + mov reg_buffer, edi //把值复制出来 + } + + DispatchMsg(reg_buffer); + + __asm + { + call recvMsgCallAddr // 这个为被覆盖的call + jmp recvMsgJumpBackAddr // 跳回被HOOK指令的下一条指令 + } +} + +void ListenMessage() +{ + // MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0); + if (gIsListening || (g_WeChatWinDllAddr == 0)) { + return; + } + + recvMsgHookAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.hook; + recvMsgCallAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.call; + recvMsgJumpBackAddr = recvMsgHookAddr + 5; + + HookAddress(recvMsgHookAddr, RecieveMsgFunc, recvMsgBackupCode); + gIsListening = true; +} + +void UnListenMessage() +{ + if (!gIsListening) { + return; + } + UnHookAddress(recvMsgHookAddr, recvMsgBackupCode); + gIsListening = false; +} diff --git a/spy/receive_msg.h b/spy/receive_msg.h new file mode 100644 index 0000000..028afe2 --- /dev/null +++ b/spy/receive_msg.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../proto/wcf.grpc.pb.h" + +void ListenMessage(); +void UnListenMessage(); +void GetMsgTypes(wcf::MsgTypes *types); diff --git a/spy/rpc_server.cpp b/spy/rpc_server.cpp new file mode 100644 index 0000000..d0164e6 --- /dev/null +++ b/spy/rpc_server.cpp @@ -0,0 +1,284 @@ +#pragma warning(disable : 4251) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../proto/wcf.grpc.pb.h" + +#include "accept_new_friend.h" +#include "exec_sql.h" +#include "get_contacts.h" +#include "log.h" +#include "receive_msg.h" +#include "rpc_server.h" +#include "send_msg.h" +#include "spy.h" +#include "spy_types.h" +#include "util.h" + +extern int IsLogin(void); // Defined in spy.cpp +extern std::string GetSelfWxid(); // Defined in spy.cpp + +using namespace std; + +using grpc::CallbackServerContext; +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerUnaryReactor; +using grpc::ServerWriteReactor; +using grpc::Status; + +using wcf::Contacts; +using wcf::DbField; +using wcf::DbNames; +using wcf::DbQuery; +using wcf::DbRow; +using wcf::DbRows; +using wcf::DbTable; +using wcf::DbTables; +using wcf::Empty; +using wcf::ImageMsg; +using wcf::MsgTypes; +using wcf::Response; +using wcf::String; +using wcf::TextMsg; +using wcf::Verification; +using wcf::Wcf; +using wcf::WxMsg; + +mutex gMutex; +queue gMsgQueue; +condition_variable gCv; +bool gIsListening; + +class WcfImpl final : public Wcf::CallbackService +{ +public: + explicit WcfImpl() { } + + ServerUnaryReactor *RpcIsLogin(CallbackServerContext *context, const Empty *empty, Response *rsp) override + { + int ret = IsLogin(); + rsp->set_status(ret); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } + + ServerUnaryReactor *RpcGetSelfWxid(CallbackServerContext *context, const Empty *empty, String *rsp) override + { + string wxid = GetSelfWxid(); + rsp->set_str(wxid); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } + + ServerWriteReactor *RpcEnableRecvMsg(CallbackServerContext *context, const Empty *empty) override + { + class Getter : public ServerWriteReactor + { + public: + Getter() + { + LOG_INFO("Enable message listening.") + ListenMessage(); // gIsListening = true; + NextWrite(); + } + void OnDone() override { delete this; } + void OnWriteDone(bool /*ok*/) override { NextWrite(); } + + private: + void NextWrite() + { + unique_lock lock(gMutex); + gCv.wait(lock, [&] { return !gMsgQueue.empty(); }); + tmp_ = gMsgQueue.front(); + gMsgQueue.pop(); + lock.unlock(); + if (gIsListening) { + StartWrite(&tmp_); + } else { + LOG_INFO("Disable message listening.") + Finish(Status::OK); // 结束本次通信 + } + } + WxMsg tmp_; // 如果将它放到 NextWrite 内部,StartWrite 调用时可能已经出了作用域 + }; + + return new Getter(); + } + + ServerUnaryReactor *RpcDisableRecvMsg(CallbackServerContext *context, const Empty *empty, Response *rsp) override + { + if (gIsListening) { + UnListenMessage(); // gIsListening = false; + // 发送消息,触发 NextWrite 的 Finish + WxMsg wxMsg; + unique_lock lock(gMutex); + gMsgQueue.push(wxMsg); + lock.unlock(); + gCv.notify_all(); + } + + rsp->set_status(0); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } + + ServerUnaryReactor *RpcSendTextMsg(CallbackServerContext *context, const TextMsg *msg, Response *rsp) override + { + wstring wswxid = String2Wstring(msg->receiver()); + wstring wsmsg = String2Wstring(msg->msg()); + wstring wsatusers = String2Wstring(msg->aters()); + + SendTextMessage(wswxid.c_str(), wsmsg.c_str(), wsatusers.c_str()); + rsp->set_status(0); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } + + ServerUnaryReactor *RpcSendImageMsg(CallbackServerContext *context, const ImageMsg *msg, Response *rsp) override + { + wstring wswxid = String2Wstring(msg->receiver()); + wstring wspath = String2Wstring(msg->path()); + + SendImageMessage(wswxid.c_str(), wspath.c_str()); + rsp->set_status(0); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } + + ServerUnaryReactor *RpcGetMsgTypes(CallbackServerContext *context, const Empty *empty, MsgTypes *rsp) override + { + GetMsgTypes(rsp); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + + return reactor; + } + + ServerUnaryReactor *RpcGetContacts(CallbackServerContext *context, const Empty *empty, Contacts *rsp) override + { + bool ret = GetContacts(rsp); + auto *reactor = context->DefaultReactor(); + if (ret) { + reactor->Finish(Status::OK); + } else { + reactor->Finish(Status::CANCELLED); + } + + return reactor; + } + + ServerUnaryReactor *RpcGetDbNames(CallbackServerContext *context, const Empty *empty, DbNames *rsp) override + { + GetDbNames(rsp); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + + return reactor; + } + + ServerUnaryReactor *RpcGetDbTables(CallbackServerContext *context, const String *db, DbTables *rsp) override + { + GetDbTables(db->str(), rsp); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + + return reactor; + } + + ServerUnaryReactor *RpcExecDbQuery(CallbackServerContext *context, const DbQuery *query, DbRows *rsp) override + { + ExecDbQuery(query->db(), query->sql(), rsp); + auto *reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + + return reactor; + } + + ServerUnaryReactor *RpcAcceptNewFriend(CallbackServerContext *context, const Verification *v, + Response *rsp) override + { + bool ret = AcceptNewFriend(String2Wstring(v->v3()), String2Wstring(v->v4())); + auto *reactor = context->DefaultReactor(); + if (ret) { + rsp->set_status(0); + reactor->Finish(Status::OK); + } else { + LOG_ERROR("AcceptNewFriend failed.") + rsp->set_status(-1); // TODO: Unify error code + reactor->Finish(Status::CANCELLED); + } + + return reactor; + } +}; + +static DWORD lThreadId = 0; +static bool lIsRunning = false; +static ServerBuilder lBuilder; + +static unique_ptr &GetServer() +{ + static unique_ptr server(lBuilder.BuildAndStart()); + + return server; +} + +static int RunServer() +{ + string server_address("localhost:10086"); + WcfImpl service; + + lBuilder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + lBuilder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIME_MS, 2000); + lBuilder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 3000); + lBuilder.AddChannelArgument(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1); + lBuilder.RegisterService(&service); + + unique_ptr &server = GetServer(); + LOG_INFO("Server listening on {}", server_address); + LOG_DEBUG("server: {}", fmt::ptr(server)); + lIsRunning = true; + server->Wait(); + + return 0; +} + +int RpcStartServer() +{ + HANDLE rpcThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunServer, NULL, NULL, &lThreadId); + if (rpcThread != 0) { + CloseHandle(rpcThread); + } + + return 0; +} + +int RpcStopServer() +{ + if (lIsRunning) { + UnListenMessage(); + unique_ptr &server = GetServer(); + LOG_DEBUG("server: {}", fmt::ptr(server)); + server->Shutdown(); + LOG_INFO("Server stoped."); + } + + return 0; +} diff --git a/spy/rpc_server.h b/spy/rpc_server.h new file mode 100644 index 0000000..32a2126 --- /dev/null +++ b/spy/rpc_server.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef SPY_EXPORTS +#define SPY_API __declspec(dllexport) +#else +#define SPY_API __declspec(dllimport) +#endif + +int RpcStartServer(); +int RpcStopServer(); diff --git a/Spy/send_msg.cpp b/spy/send_msg.cpp similarity index 95% rename from Spy/send_msg.cpp rename to spy/send_msg.cpp index 8825cc2..5405838 100644 --- a/Spy/send_msg.cpp +++ b/spy/send_msg.cpp @@ -3,11 +3,11 @@ #include #include +#include "send_msg.h" #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; diff --git a/Spy/send_msg.h b/spy/send_msg.h similarity index 100% rename from Spy/send_msg.h rename to spy/send_msg.h diff --git a/spy/spy.cpp b/spy/spy.cpp new file mode 100644 index 0000000..65d53e4 --- /dev/null +++ b/spy/spy.cpp @@ -0,0 +1,41 @@ +#include "spy.h" +#include "load_calls.h" +#include "log.h" +#include "rpc_server.h" +#include "util.h" + +WxCalls_t g_WxCalls = { 0 }; +DWORD g_WeChatWinDllAddr = 0; + +void InitSpy() +{ + wchar_t version[16] = { 0 }; + InitLogger(); + g_WeChatWinDllAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll"); //获取wechatWin模块地址 + if (g_WeChatWinDllAddr == 0) { + LOG_ERROR("获取wechatWin.dll模块地址失败"); + return; + } + + if (!GetWeChatVersion(version)) { //获取微信版本 + LOG_ERROR("获取微信版本失败"); + return; + } + + if (LoadCalls(version, &g_WxCalls) != 0) { //加载微信版本对应的Call地址 + LOG_ERROR("不支持当前版本"); + return; + } + + RpcStartServer(); +} + +void CleanupSpy() +{ + RpcStopServer(); + // FreeLibraryAndExitThread(hModule, 0); +} + +int IsLogin(void) { return (int)GET_DWORD(g_WeChatWinDllAddr + g_WxCalls.login); } + +std::string GetSelfWxid() { return GET_STRING(g_WeChatWinDllAddr + g_WxCalls.ui.wxid); } diff --git a/spy/spy.def b/spy/spy.def new file mode 100644 index 0000000..d97c252 --- /dev/null +++ b/spy/spy.def @@ -0,0 +1,4 @@ +EXPORTS + InitSpy + CleanupSpy + IsLogin diff --git a/Spy/spy.h b/spy/spy.h similarity index 60% rename from Spy/spy.h rename to spy/spy.h index 09ae7ef..690b024 100644 --- a/Spy/spy.h +++ b/spy/spy.h @@ -3,4 +3,5 @@ #include "framework.h" void InitSpy(); -void DestroySpy(HMODULE hModule); +void CleanupSpy(); +int IsLogin(void); diff --git a/Spy/spy_types.h b/spy/spy_types.h similarity index 90% rename from Spy/spy_types.h rename to spy/spy_types.h index df0ace8..78486e5 100644 --- a/Spy/spy_types.h +++ b/spy/spy_types.h @@ -1,9 +1,7 @@ #pragma once #include "framework.h" -#include - -#include "rpc_h.h" +#include "../proto/wcf.grpc.pb.h" typedef struct UserInfoCall { DWORD wxid; @@ -74,5 +72,3 @@ typedef struct TextStruct { DWORD capacity; char fill[8]; } TextStruct_t; - -typedef std::queue MsgQueue_t; diff --git a/SDK/util.cpp b/spy/util.cpp similarity index 68% rename from SDK/util.cpp rename to spy/util.cpp index faecc7d..2ccbd2f 100644 --- a/SDK/util.cpp +++ b/spy/util.cpp @@ -1,312 +1,246 @@ -#include "Shlwapi.h" -#include "framework.h" -#include -#include -#include -#include -#include -#include - -#include "util.h" - -#pragma comment(lib, "shlwapi") -#pragma comment(lib, "Version.lib") - -using namespace std; - -static wstring_convert, wchar_t> S_WS_Converter; - -wstring String2Wstring(string s) { return S_WS_Converter.from_bytes(s); } -string Wstring2String(wstring ws) { return S_WS_Converter.to_bytes(ws); } - -static 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; -} - -static int GetWeChatWinDLLPath(wchar_t *path) -{ - int ret = GetWeChatPath(path); - if (ret != ERROR_SUCCESS) { - return ret; - } - - PathRemoveFileSpecW(path); - PathAppendW(path, WECHATWINDLL); - if (!PathFileExists(path)) { - // 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29] - PathRemoveFileSpec(path); - _wfinddata_t findData; - wstring dir = wstring(path) + L"\\[*.*"; - intptr_t handle = _wfindfirst(dir.c_str(), &findData); - if (handle == -1) { // 检查是否成功 - return -1; - } - wstring dllPath = wstring(path) + L"\\" + findData.name; - wcscpy_s(path, MAX_PATH, dllPath.c_str()); - PathAppend(path, WECHATWINDLL); - } - - return ret; -} - -static 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 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; -} - -static DWORD GetWeChatPid() -{ - DWORD pid = 0; - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; - while (Process32Next(hSnapshot, &pe32)) { - wstring strProcess = pe32.szExeFile; - if (strProcess == WECHAREXE) { - pid = pe32.th32ProcessID; - break; - } - } - CloseHandle(hSnapshot); - return pid; -} - -int OpenWeChat(DWORD *pid) -{ - *pid = GetWeChatPid(); - if (*pid) { - return ERROR_SUCCESS; - } - - int ret = -1; - STARTUPINFO si = { sizeof(si) }; - WCHAR Path[MAX_PATH] = { 0 }; - PROCESS_INFORMATION pi = { 0 }; - - ret = GetWeChatPath(Path); - if (ERROR_SUCCESS != ret) { - return ret; - } - - if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { - return GetLastError(); - } - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - - *pid = pi.dwProcessId; - - return ERROR_SUCCESS; -} - -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; -} - -BSTR GetBstrByAddress(DWORD address) -{ - wchar_t *p = GET_WSTRING(address); - if (p == NULL) { - return NULL; - } - - return SysAllocStringLen(GET_WSTRING(address), GET_DWORD(address + 4)); -} - -wstring GetWstringFromBstr(BSTR p) -{ - wstring ws = L""; - if (p != NULL) { - ws = wstring(p); - SysFreeString(p); - } - return ws; -} - -BSTR GetBstrFromString(const char *str) -{ - int wslen = MultiByteToWideChar(CP_ACP, 0, str, strlen(str), 0, 0); - BSTR bstr = SysAllocStringLen(0, wslen); - MultiByteToWideChar(CP_ACP, 0, str, strlen(str), bstr, wslen); - - return bstr; -} - -BSTR GetBstrFromWstring(wstring ws) -{ - if (!ws.empty()) { - return SysAllocStringLen(ws.data(), ws.size()); - } - return NULL; -} - -BSTR GetBstrFromStringBuffer(const char *str, int length) -{ - int wslen = MultiByteToWideChar(CP_ACP, 0, str, length, 0, 0); - BSTR bstr = SysAllocStringLen(0, wslen); - MultiByteToWideChar(CP_ACP, 0, str, length, bstr, wslen); - - return bstr; -} - -BSTR GetBstrFromByteArray(const byte *b, int len) -{ - BSTR bstr = SysAllocStringLen(0, len); - if (bstr == NULL) { - return NULL; - } - memcpy((byte *)bstr, b, len); - - return bstr; -} - -string GetBytesFromBstr(BSTR bstr) -{ - string s = ""; - if (bstr) { - int len = SysStringByteLen(bstr) / 2; - char *tmp = new char[len]; - char *p = (char *)bstr; - for (int i = 0; i < len; i++) { - tmp[i] = p[i]; - } - SysFreeString(bstr); - s = string(tmp, len); - delete[] tmp; - } - - return s; -} - -void GetRpcMessage(WxMessage_t *wxMsg, RpcMessage_t rpcMsg) -{ - wxMsg->self = rpcMsg.self; - wxMsg->type = rpcMsg.type; - wxMsg->source = rpcMsg.source; - wxMsg->id = GetWstringFromBstr(rpcMsg.id); - wxMsg->xml = GetWstringFromBstr(rpcMsg.xml); - wxMsg->wxId = GetWstringFromBstr(rpcMsg.wxId); - wxMsg->roomId = GetWstringFromBstr(rpcMsg.roomId); - wxMsg->content = GetWstringFromBstr(rpcMsg.content); -} - -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; -} +#include "Shlwapi.h" +#include "framework.h" +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#pragma comment(lib, "shlwapi") +#pragma comment(lib, "Version.lib") + +using namespace std; + +wstring String2Wstring(string s) +{ + if (s.empty()) + return wstring(); + int size_needed = MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), NULL, 0); + wstring ws(size_needed, 0); + MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), &ws[0], size_needed); + return ws; +} + +string Wstring2String(wstring ws) +{ + if (ws.empty()) + return string(); + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), NULL, 0, NULL, NULL); + string s(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), &s[0], size_needed, NULL, NULL); + return s; +} + +static 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; +} + +static int GetWeChatWinDLLPath(wchar_t *path) +{ + int ret = GetWeChatPath(path); + if (ret != ERROR_SUCCESS) { + return ret; + } + + PathRemoveFileSpecW(path); + PathAppendW(path, WECHATWINDLL); + if (!PathFileExists(path)) { + // 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29] + PathRemoveFileSpec(path); + _wfinddata_t findData; + wstring dir = wstring(path) + L"\\[*.*"; + intptr_t handle = _wfindfirst(dir.c_str(), &findData); + if (handle == -1) { // 检查是否成功 + return -1; + } + wstring dllPath = wstring(path) + L"\\" + findData.name; + wcscpy_s(path, MAX_PATH, dllPath.c_str()); + PathAppend(path, WECHATWINDLL); + } + + return ret; +} + +static 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 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; +} + +static DWORD GetWeChatPid() +{ + DWORD pid = 0; + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; + while (Process32Next(hSnapshot, &pe32)) { + wstring strProcess = pe32.szExeFile; + if (strProcess == WECHAREXE) { + pid = pe32.th32ProcessID; + break; + } + } + CloseHandle(hSnapshot); + return pid; +} + +int OpenWeChat(DWORD *pid) +{ + *pid = GetWeChatPid(); + if (*pid) { + return ERROR_SUCCESS; + } + + int ret = -1; + STARTUPINFO si = { sizeof(si) }; + WCHAR Path[MAX_PATH] = { 0 }; + PROCESS_INFORMATION pi = { 0 }; + + ret = GetWeChatPath(Path); + if (ERROR_SUCCESS != ret) { + return ret; + } + + if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { + return GetLastError(); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + *pid = pi.dwProcessId; + + return ERROR_SUCCESS; +} + +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; +} + +string GetStringByAddress(DWORD address) +{ + DWORD strLength = GET_DWORD(address + 4); + return Wstring2String(wstring(GET_WSTRING(address), 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/spy/util.h similarity index 55% rename from SDK/util.h rename to spy/util.h index 911be37..26542a2 100644 --- a/SDK/util.h +++ b/spy/util.h @@ -1,31 +1,21 @@ -#pragma once - -#include - -#include "rpc_h.h" -#include "sdk.h" - -#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 *)(*(DWORD *)(addr))) -#define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr))) - -int OpenWeChat(DWORD *pid); -int GetWeChatVersion(wchar_t *version); -int GetWstringByAddress(DWORD address, wchar_t *buffer, DWORD buffer_size); -void GetRpcMessage(WxMessage_t *wxMsg, RpcMessage_t rpcMsg); -DWORD GetMemoryIntByAddress(HANDLE hProcess, DWORD address); -BSTR GetBstrByAddress(DWORD address); -BSTR GetBstrFromString(const char *str); -BSTR GetBstrFromWstring(std::wstring ws); -BSTR GetBstrFromByteArray(const byte *b, int len); -BSTR GetBstrFromStringBuffer(const char *str, int length); -std::string GetBytesFromBstr(BSTR bstr); -std::wstring GetWstringFromBstr(BSTR bstr); -std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, DWORD address); -std::wstring String2Wstring(std::string s); -std::string Wstring2String(std::wstring ws); +#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 *)(*(DWORD *)(addr))) +#define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr))) + +int OpenWeChat(DWORD *pid); +int GetWeChatVersion(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); +std::wstring String2Wstring(std::string s); +std::string Wstring2String(std::wstring ws); +std::string GetStringByAddress(DWORD address);