diff --git a/.github/workflows/Build-WeChatFerry.yml b/.github/workflows/Build-WeChatFerry.yml
index 1991b84..1305750 100644
--- a/.github/workflows/Build-WeChatFerry.yml
+++ b/.github/workflows/Build-WeChatFerry.yml
@@ -92,7 +92,7 @@ jobs:
- name: 打包输出文件及下载 WeChat 安装包
run: |
New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp"
- Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
+ Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll", "WeChatFerry/Out/DISCLAIMER.md" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe"
shell: pwsh
diff --git a/README.MD b/README.MD
index f2f79fa..741c9fa 100644
--- a/README.MD
+++ b/README.MD
@@ -5,7 +5,7 @@
⚠️ 免责声明【必读】⚠️
-请阅读完整的免责声明:[点击查看](DISCLAIMER.md)
+请阅读完整的免责声明:[点击查看](WeChatFerry/DISCLAIMER.md)
@@ -32,7 +32,6 @@
* 发送图片消息
* 发送文件消息
* 发送卡片消息
-* 发送 XML 消息
* 发送 GIF 消息
* 拍一拍群友
* 转发消息
@@ -168,7 +167,7 @@ sdk.WxDestroySDK()
### 调试日志
```c
- DbgMsg("ListenMessage"); // 封装的 OutputDebugString
+ util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString
OutputDebugString(L"ListenMessage\n");
MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0);
```
@@ -205,9 +204,9 @@ WeChatFerry
## 版本更新
-### v39.3.5
+### v39.4.1
-* 代码优化
+* 修复乱码问题。
点击查看更多
@@ -219,6 +218,14 @@ WeChatFerry
* `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始
+### v39.4.0
+
+* 重构代码,适配 `3.9.12.17`。
+
+### v39.3.5
+
+* 代码优化
+
### v39.3.4
* 实现获取登录二维码
diff --git a/WeChatFerry/com/log.hpp b/WeChatFerry/com/log.hpp
index 4f30b27..4a08c8b 100644
--- a/WeChatFerry/com/log.hpp
+++ b/WeChatFerry/com/log.hpp
@@ -1,15 +1,18 @@
#pragma once
+#ifdef ENABLE_DEBUG_LOG
+#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
+#endif
+
#include
#include
#include
-#include
-#include
-#include
#include
#include
-#include "util.h"
+#include
+#include
+#include
#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)
#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__)
@@ -48,7 +51,7 @@ inline void InitLogger(const std::string &path)
logger = spdlog::rotating_logger_mt(DEFAULT_LOGGER_NAME, filename.string(), DEFAULT_LOGGER_MAX_SIZE,
DEFAULT_LOGGER_MAX_FILES);
} catch (const spdlog::spdlog_ex &ex) {
- MessageBox(NULL, String2Wstring(ex.what()).c_str(), L"Init LOGGER ERROR", MB_ICONERROR);
+ MessageBoxA(NULL, ex.what(), "Init LOGGER ERROR", MB_ICONERROR);
return;
}
@@ -59,11 +62,10 @@ inline void InitLogger(const std::string &path)
spdlog::set_level(spdlog::level::debug);
logger->flush_on(spdlog::level::debug);
#else
- spdlog::set_level(spdlog::level::info);
logger->flush_on(spdlog::level::info);
#endif
- SPDLOG_DEBUG("Logger initialized with default settings.");
+ LOG_DEBUG("InitLogger with debug level");
}
#ifdef ENABLE_DEBUG_LOG
@@ -80,7 +82,7 @@ inline void log_buffer(uint8_t *buffer, size_t len)
}
}
- SPDLOG_DEBUG(oss.str());
+ LOG_DEBUG(oss.str());
}
#endif
diff --git a/WeChatFerry/com/util.cpp b/WeChatFerry/com/util.cpp
index 0134592..b82e82f 100644
--- a/WeChatFerry/com/util.cpp
+++ b/WeChatFerry/com/util.cpp
@@ -1,177 +1,72 @@
-#include "Shlwapi.h"
-#include "framework.h"
+#include "util.h"
+
#include
+#include
#include
-#include
+#include
#include
-#include
-#include
#include
+#include "framework.h"
+#include
+#include
+
#include "log.hpp"
-#include "util.h"
#pragma comment(lib, "shlwapi")
#pragma comment(lib, "Version.lib")
-using namespace std;
+namespace fs = std::filesystem;
-wstring String2Wstring(string s)
+namespace util
{
- 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);
+
+constexpr char WECHATEXE[] = "WeChat.exe";
+constexpr char WECHATWINDLL[] = "WeChatWin.dll";
+
+std::wstring s2w(const std::string &s)
+{
+ if (s.empty()) return std::wstring();
+ int size_needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0);
+ std::wstring ws(size_needed, 0);
+ MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), &ws[0], size_needed);
return ws;
}
-string Wstring2String(wstring ws)
+std::string w2s(const std::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);
+ if (ws.empty()) return std::string();
+ int size_needed
+ = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), nullptr, 0, nullptr, nullptr);
+ std::string s(size_needed, 0);
+ WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), &s[0], size_needed, nullptr, nullptr);
return s;
}
-string GB2312ToUtf8(const char *gb2312)
+std::string gb2312_to_utf8(const char *gb2312)
{
- int size_needed = 0;
+ if (!gb2312) return "";
- size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
- wstring ws(size_needed, 0);
+ int size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, nullptr, 0);
+ std::wstring ws(size_needed, 0);
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed);
- size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, NULL, 0, NULL, NULL);
- string s(size_needed, 0);
- WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, &s[0], size_needed, NULL, NULL);
+ size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr);
+ std::string s(size_needed, 0);
+ WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, &s[0], size_needed, nullptr, nullptr);
return s;
}
-static int GetWeChatPath(wchar_t *path)
+static DWORD get_wechat_pid()
{
- 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 pid = 0;
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
- 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;
- }
-
- UINT64 verMS = pVerInfo->dwFileVersionMS;
- UINT64 verLS = pVerInfo->dwFileVersionLS;
- UINT64 major = HIWORD(verMS);
- UINT64 minor = LOWORD(verMS);
- UINT64 build = HIWORD(verLS);
- UINT64 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;
-}
-
-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) {
+ if (pe32.szExeFile == s2w(WECHATEXE)) {
pid = pe32.th32ProcessID;
break;
}
@@ -180,168 +75,237 @@ DWORD GetWeChatPid()
return pid;
}
-enum class WindowsArchiture { x32, x64 };
-static WindowsArchiture GetWindowsArchitecture()
+static std::optional get_wechat_path()
{
-#ifdef _WIN64
- return WindowsArchiture::x64;
-#else
- return WindowsArchiture::x32;
-#endif
+ HKEY hKey;
+ if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Tencent\\WeChat", 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
+ LOG_ERROR("无法打开注册表项");
+ return std::nullopt;
+ }
+
+ char path[MAX_PATH] = { 0 };
+ DWORD type = REG_SZ;
+ DWORD size = sizeof(path);
+ if (RegQueryValueExA(hKey, "InstallPath", nullptr, &type, reinterpret_cast(path), &size) != ERROR_SUCCESS) {
+ RegCloseKey(hKey);
+ LOG_ERROR("无法读取注册表中的 InstallPath");
+ return std::nullopt;
+ }
+ RegCloseKey(hKey);
+
+ PathAppendA(path, WECHATEXE);
+ return std::string(path);
}
-BOOL IsProcessX64(DWORD pid)
+static std::optional get_wechat_win_dll_path()
{
- BOOL isWow64 = false;
- HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
- if (!hProcess)
- return false;
- BOOL result = IsWow64Process(hProcess, &isWow64);
- CloseHandle(hProcess);
- if (!result)
- return false;
- if (isWow64)
- return false;
- else if (GetWindowsArchitecture() == WindowsArchiture::x32)
- return false;
- else
- return true;
+ auto wechat_path = get_wechat_path();
+ if (!wechat_path) {
+ return std::nullopt;
+ }
+
+ fs::path dll_path = *wechat_path;
+ dll_path = dll_path.parent_path();
+
+ fs::path wechat_dll_path = dll_path / WECHATWINDLL;
+ if (fs::exists(wechat_dll_path)) { // 尝试直接查找 WeChatWin.dll
+ return wechat_dll_path.string();
+ }
+
+ // 微信从(大约)3.7开始,增加了一层版本目录: [3.7.0.29]
+ std::optional found_path;
+ for (const auto &entry : fs::directory_iterator(dll_path)) {
+ if (entry.is_directory()) {
+ fs::path possible_dll = entry.path() / WECHATWINDLL;
+ if (fs::exists(possible_dll)) {
+ found_path = possible_dll.string();
+ break; // 取第一个找到的版本号文件夹
+ }
+ }
+ }
+
+ if (!found_path) {
+ LOG_ERROR("未找到 WeChatWin.dll");
+ }
+
+ return found_path;
}
-int OpenWeChat(DWORD *pid)
+static std::optional get_file_version(const std::string &path)
{
- *pid = GetWeChatPid();
- if (*pid) {
+ if (!PathFileExistsA(path.c_str())) {
+ LOG_ERROR("文件不存在: {}", path);
+ return std::nullopt;
+ }
+
+ DWORD dummy = 0;
+ DWORD size = GetFileVersionInfoSizeA(path.c_str(), &dummy);
+ if (size == 0) {
+ LOG_ERROR("无法获取文件版本信息大小: {}", path);
+ return std::nullopt;
+ }
+
+ std::vector buffer(size);
+ if (!GetFileVersionInfoA(path.c_str(), 0, size, buffer.data())) {
+ LOG_ERROR("无法获取文件版本信息: {}", path);
+ return std::nullopt;
+ }
+
+ VS_FIXEDFILEINFO *ver_info = nullptr;
+ UINT ver_size = 0;
+ if (!VerQueryValueA(buffer.data(), "\\", reinterpret_cast(&ver_info), &ver_size)) {
+ LOG_ERROR("无法获取文件版本信息: {}", path);
+ return std::nullopt;
+ }
+
+ return fmt::format("{}.{}.{}.{}", HIWORD(ver_info->dwFileVersionMS), LOWORD(ver_info->dwFileVersionMS),
+ HIWORD(ver_info->dwFileVersionLS), LOWORD(ver_info->dwFileVersionLS));
+}
+
+std::string get_wechat_version()
+{
+ auto dll_path = get_wechat_win_dll_path();
+ if (!dll_path) {
+ LOG_ERROR("无法获取 WeChatWin.dll 路径");
+ return "";
+ }
+
+ auto version = get_file_version(*dll_path);
+ if (!version) {
+ LOG_ERROR("无法获取 WeChat 版本信息");
+ return "";
+ }
+
+ return *version;
+}
+
+int open_wechat(DWORD &pid)
+{
+ pid = get_wechat_pid();
+ if (pid != 0) {
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;
+ auto wechat_path = util::get_wechat_path();
+ if (!wechat_path) {
+ LOG_ERROR("获取 WeChat 安装路径失败");
+ return ERROR_FILE_NOT_FOUND;
}
- if (!CreateProcess(NULL, Path, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
+ STARTUPINFOA si = { sizeof(si) };
+ PROCESS_INFORMATION pi = {};
+
+ std::string command_line = *wechat_path;
+ if (!CreateProcessA(nullptr, command_line.data(), nullptr, nullptr, FALSE, CREATE_NEW_CONSOLE, nullptr, nullptr,
+ &si, &pi)) {
return GetLastError();
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
- *pid = pi.dwProcessId;
-
+ pid = pi.dwProcessId;
return ERROR_SUCCESS;
}
-size_t GetWstringByAddress(UINT64 addr, wchar_t *buffer, UINT64 buffer_size)
+uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr)
{
- size_t strLength = GET_DWORD(addr + 8);
- if (strLength == 0) {
- return 0;
- } else if (strLength > buffer_size) {
- strLength = buffer_size - 1;
- }
+ uint32_t value = 0;
+ if (!addr || !hProcess) return value;
- wmemcpy_s(buffer, strLength + 1, GET_WSTRING(addr), strLength + 1);
-
- return strLength;
-}
-
-string GetStringByAddress(UINT64 addr)
-{
- size_t strLength = GET_DWORD(addr + 8);
- return Wstring2String(wstring(GET_WSTRING(addr), strLength));
-}
-
-string GetStringByStrAddr(UINT64 addr)
-{
- size_t strLength = GET_DWORD(addr + 8);
- return strLength ? string(GET_STRING(addr), strLength) : string();
-}
-
-string GetStringByWstrAddr(UINT64 addr)
-{
- size_t strLength = GET_DWORD(addr + 8);
- return strLength ? Wstring2String(wstring(GET_WSTRING(addr), strLength)) : string();
-}
-
-UINT32 GetMemoryIntByAddress(HANDLE hProcess, UINT64 addr)
-{
- UINT32 value = 0;
-
- unsigned char data[4] = { 0 };
- if (ReadProcessMemory(hProcess, (LPVOID)addr, data, 4, 0)) {
- value = data[0] & 0xFF;
- value |= ((data[1] << 8) & 0xFF00);
- value |= ((data[2] << 16) & 0xFF0000);
- value |= ((data[3] << 24) & 0xFF000000);
- }
+ ReadProcessMemory(hProcess, reinterpret_cast(addr), &value, sizeof(value), nullptr);
return value;
}
-wstring GetUnicodeInfoByAddress(HANDLE hProcess, UINT64 address)
+std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t address)
{
- wstring value = L"";
+ if (!hProcess || !address) return L"";
- UINT64 strAddress = GetMemoryIntByAddress(hProcess, address);
- UINT64 strLen = GetMemoryIntByAddress(hProcess, address + 0x4);
- if (strLen > 500)
- return value;
+ uint64_t str_address = get_memory_int_by_address(hProcess, address);
+ uint64_t str_len = get_memory_int_by_address(hProcess, address + 0x4);
+ if (str_len > 500) return L"";
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);
+ if (ReadProcessMemory(hProcess, reinterpret_cast(str_address), cValue, (str_len + 1) * sizeof(wchar_t),
+ nullptr)) {
+ return std::wstring(cValue);
}
- return value;
+ return L"";
}
-void DbgMsg(const char *zcFormat, ...)
+void dbg_msg(const char *format, ...)
{
- // initialize use of the variable argument array
- va_list vaArgs;
- va_start(vaArgs, zcFormat);
+ if (!format) return;
- // reliably acquire the size
- // from a copy of the variable argument array
- // and a functionally reliable call to mock the formatting
- va_list vaArgsCopy;
- va_copy(vaArgsCopy, vaArgs);
- const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
- va_end(vaArgsCopy);
+ va_list args;
+ va_start(args, format);
- // return a formatted string without risking memory mismanagement
- // and without assuming any compiler or platform specific behavior
- std::vector zc(iLen + 1);
- std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
- va_end(vaArgs);
- std::string strText(zc.data(), iLen);
+ va_list args_copy;
+ va_copy(args_copy, args);
+ int len = vsnprintf(nullptr, 0, format, args_copy);
+ va_end(args_copy);
- OutputDebugStringA(strText.c_str());
+ std::vector buffer(len + 1);
+ vsnprintf(buffer.data(), buffer.size(), format, args);
+ va_end(args);
+
+ OutputDebugStringW(s2w(buffer.data()).c_str());
}
-WxString *NewWxStringFromStr(const string &str) { return NewWxStringFromWstr(String2Wstring(str)); }
-
-WxString *NewWxStringFromWstr(const wstring &ws)
+std::unique_ptr new_wx_string(const char *str)
{
- WxString *p = (WxString *)HeapAlloc(GetProcessHeap(), 0, sizeof(WxString));
- wchar_t *pWstring = (wchar_t *)HeapAlloc(GetProcessHeap(), 0, (ws.size() + 1) * 2);
- if (p == NULL || pWstring == NULL) {
- LOG_ERROR("Out of Memory...");
- return NULL;
+ return new_wx_string(str ? std::string(str) : std::string());
+}
+
+std::unique_ptr new_wx_string(const std::string &str) { return std::make_unique(s2w(str)); }
+
+std::unique_ptr new_wx_string(const wchar_t *wstr)
+{
+ return new_wx_string(wstr ? std::wstring(wstr) : std::wstring());
+}
+
+std::unique_ptr new_wx_string(const std::wstring &wstr) { return std::make_unique(wstr); }
+
+AtWxidSplitResult<> parse_wxids(const std::string &atWxids)
+{
+ AtWxidSplitResult<> result;
+ if (!atWxids.empty()) {
+ std::wstringstream wss(util::s2w(atWxids));
+ for (std::wstring wxid; std::getline(wss, wxid, L',');) {
+ result.wxids.push_back(wxid);
+ result.wxWxids.emplace_back(result.wxids.back());
+ }
}
-
- wmemcpy(pWstring, ws.c_str(), ws.size() + 1);
- p->wptr = pWstring;
- p->size = (DWORD)ws.size();
- p->capacity = (DWORD)ws.size();
- p->ptr = 0;
- p->clen = 0;
- return p;
+ return result;
}
+
+WxString *CreateWxString(const std::string &s)
+{
+ std::wstring ws = util::s2w(s);
+ WxString *wxStr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(WxString)));
+ if (!wxStr) return nullptr;
+ size_t len = ws.length();
+ wchar_t *ptr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, (len + 1) * sizeof(wchar_t)));
+ if (!ptr) {
+ HeapFree(GetProcessHeap(), 8, wxStr);
+ return nullptr;
+ }
+ wmemcpy(ptr, ws.c_str(), len + 1);
+ wxStr->wptr = ptr;
+ wxStr->size = static_cast(ws.size());
+ wxStr->length = static_cast(ws.length());
+ wxStr->clen = 0;
+ wxStr->ptr = nullptr;
+ return wxStr;
+}
+
+void FreeWxString(WxString *wxStr)
+{
+ if (wxStr) {
+ if (wxStr->wptr) HeapFree(GetProcessHeap(), 8, const_cast(wxStr->wptr));
+ HeapFree(GetProcessHeap(), 8, wxStr);
+ }
+}
+} // namespace util
diff --git a/WeChatFerry/com/util.h b/WeChatFerry/com/util.h
index 8f252df..1998fbb 100644
--- a/WeChatFerry/com/util.h
+++ b/WeChatFerry/com/util.h
@@ -1,41 +1,105 @@
#pragma once
+#include
+#include
#include
+#include
#include "spy_types.h"
-#define WECHAREXE L"WeChat.exe"
-#define WECHATWINDLL L"WeChatWin.dll"
-#define WCFSDKDLL L"sdk.dll"
-#define WCFSPYDLL L"spy.dll"
-#define WCFSPYDLL_DEBUG L"spy_debug.dll"
-
-#define GET_UINT64(addr) ((UINT64) * (UINT64 *)(addr))
-#define GET_DWORD(addr) ((DWORD) * (UINT64 *)(addr))
-#define GET_QWORD(addr) ((UINT64) * (UINT64 *)(addr))
-#define GET_STRING(addr) ((CHAR *)(*(UINT64 *)(addr)))
-#define GET_WSTRING(addr) ((WCHAR *)(*(UINT64 *)(addr)))
-#define GET_STRING_FROM_P(addr) ((CHAR *)(addr))
-#define GET_WSTRING_FROM_P(addr) ((WCHAR *)(addr))
-
-typedef struct PortPath {
+namespace util
+{
+struct PortPath {
int port;
char path[MAX_PATH];
-} PortPath_t;
+};
-DWORD GetWeChatPid();
-BOOL IsProcessX64(DWORD pid);
-int OpenWeChat(DWORD *pid);
-int GetWeChatVersion(wchar_t *version);
-size_t GetWstringByAddress(UINT64 address, wchar_t *buffer, UINT64 buffer_size);
-UINT32 GetMemoryIntByAddress(HANDLE hProcess, UINT64 address);
-std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, UINT64 address);
-std::wstring String2Wstring(std::string s);
-std::string Wstring2String(std::wstring ws);
-std::string GB2312ToUtf8(const char *gb2312);
-std::string GetStringByAddress(UINT64 address);
-std::string GetStringByStrAddr(UINT64 addr);
-std::string GetStringByWstrAddr(UINT64 addr);
-void DbgMsg(const char *zcFormat, ...);
-WxString *NewWxStringFromStr(const std::string &str);
-WxString *NewWxStringFromWstr(const std::wstring &ws);
+DWORD get_wechat_pid();
+int open_wechat(DWORD &pid);
+std::string get_wechat_version();
+uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr);
+std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t addr);
+std::wstring s2w(const std::string &s);
+std::string w2s(const std::wstring &ws);
+std::string gb2312_to_utf8(const char *gb2312);
+void dbg_msg(const char *format, ...);
+
+inline DWORD get_dword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; }
+inline QWORD get_qword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; }
+inline uint64_t get_uint64(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; }
+inline std::string get_p_string(uint64_t addr) { return addr ? std::string(reinterpret_cast(addr)) : ""; }
+inline std::string get_p_string(uint64_t addr, size_t len)
+{
+ return addr ? std::string(reinterpret_cast(addr), len) : "";
+}
+inline std::wstring get_p_wstring(uint64_t addr)
+{
+ return addr ? std::wstring(reinterpret_cast(addr)) : L"";
+}
+inline std::wstring get_p_wstring(uint64_t addr, size_t len)
+{
+ return addr ? std::wstring(reinterpret_cast(addr), len) : L"";
+}
+inline std::string get_pp_string(uint64_t addr)
+{
+ if (!addr) return "";
+
+ const char *ptr = *reinterpret_cast(addr);
+ return (ptr && *ptr) ? std::string(ptr) : "";
+}
+inline std::wstring get_pp_wstring(uint64_t addr)
+{
+ if (!addr) return L"";
+
+ const wchar_t *ptr = *reinterpret_cast(addr);
+ return (ptr && *ptr) ? std::wstring(ptr) : L"";
+}
+inline std::string get_pp_len_string(uint64_t addr)
+{
+ size_t len = get_dword(addr + 8);
+ return (addr && len) ? std::string(*reinterpret_cast(addr), len) : "";
+}
+inline std::wstring get_pp_len_wstring(uint64_t addr)
+{
+ size_t len = get_dword(addr + 8);
+ return (addr && len) ? std::wstring(*reinterpret_cast(addr), len) : L"";
+}
+inline std::string get_str_by_wstr_addr(uint64_t addr) { return w2s(get_pp_len_wstring(addr)); }
+inline void *AllocFromHeap(size_t size) { return HeapAlloc(GetProcessHeap(), 8, size); }
+inline void FreeBuffer(void *buffer)
+{
+ if (buffer) HeapFree(GetProcessHeap(), 8, buffer);
+}
+inline int MsgBox(HWND hWnd, const std::string &text, const std::string &caption = "WCF", UINT uType = MB_OK)
+{
+ std::wstring wText = s2w(text);
+ std::wstring wCaption = s2w(caption);
+ return MessageBoxW(nullptr, wText.c_str(), wCaption.c_str(), uType);
+}
+
+template static T *AllocBuffer(size_t count)
+{
+ return reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(T) * count));
+}
+
+template struct WxStringHolder {
+ std::wstring ws;
+ WxString wx;
+ explicit WxStringHolder(const T &str) : ws(util::s2w(str)), wx(ws) { }
+};
+
+template struct AtWxidSplitResult {
+ std::vector wxids;
+ std::vector wxWxids;
+};
+
+WxString *CreateWxString(const std::string &s);
+void FreeWxString(WxString *wxStr);
+AtWxidSplitResult<> parse_wxids(const std::string &atWxids);
+
+std::unique_ptr new_wx_string(const char *str);
+std::unique_ptr new_wx_string(const wchar_t *wstr);
+std::unique_ptr new_wx_string(const std::string &str);
+std::unique_ptr new_wx_string(const std::wstring &wstr);
+
+} // namespace util
diff --git a/WeChatFerry/rpc/pb_util.cpp b/WeChatFerry/rpc/pb_util.cpp
index 5f8c4bd..5bde0f5 100644
--- a/WeChatFerry/rpc/pb_util.cpp
+++ b/WeChatFerry/rpc/pb_util.cpp
@@ -68,7 +68,6 @@ bool encode_contacts(pb_ostream_t *stream, const pb_field_t *field, void *const
vector *v = (vector *)*arg;
RpcContact message = RpcContact_init_default;
- LOG_DEBUG("encode_contacts[{}]", v->size());
for (auto it = v->begin(); it != v->end(); it++) {
message.wxid.funcs.encode = &encode_string;
message.wxid.arg = (void *)(*it).wxid.c_str();
diff --git a/WeChatFerry/sdk/SDK.vcxproj b/WeChatFerry/sdk/SDK.vcxproj
index 5f549ad..cb0fd9d 100644
--- a/WeChatFerry/sdk/SDK.vcxproj
+++ b/WeChatFerry/sdk/SDK.vcxproj
@@ -141,7 +141,6 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry
-
diff --git a/WeChatFerry/sdk/SDK.vcxproj.filters b/WeChatFerry/sdk/SDK.vcxproj.filters
index 323d545..5adfeab 100644
--- a/WeChatFerry/sdk/SDK.vcxproj.filters
+++ b/WeChatFerry/sdk/SDK.vcxproj.filters
@@ -27,9 +27,6 @@
头文件
-
- 头文件
-
diff --git a/WeChatFerry/sdk/injector.cpp b/WeChatFerry/sdk/injector.cpp
index cc06298..913ba87 100644
--- a/WeChatFerry/sdk/injector.cpp
+++ b/WeChatFerry/sdk/injector.cpp
@@ -1,112 +1,110 @@
-#include "framework.h"
-#include "psapi.h"
-#include
-#include
+#include "injector.h"
+
+#include
+
+#include "psapi.h"
-#include "injector.h"
#include "util.h"
using namespace std;
-HMODULE GetTargetModuleBase(HANDLE process, string dll)
+static void handle_injection_error(HANDLE process, LPVOID remote_address, const std::string &error_msg)
{
- DWORD cbNeeded;
- HMODULE moduleHandleList[512];
- BOOL ret = EnumProcessModulesEx(process, moduleHandleList, sizeof(moduleHandleList), &cbNeeded, LIST_MODULES_64BIT);
- if (!ret) {
- MessageBox(NULL, L"获取模块失败", L"GetTargetModuleBase", 0);
+ util::MsgBox(NULL, error_msg.c_str(), "Error", MB_ICONERROR);
+ if (remote_address) {
+ VirtualFreeEx(process, remote_address, 0, MEM_RELEASE);
+ }
+ if (process) {
+ CloseHandle(process);
+ }
+}
+
+HMODULE get_target_module_base(HANDLE process, const string &dll)
+{
+ DWORD needed;
+ HMODULE modules[512];
+ if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) {
+ util::MsgBox(NULL, "获取模块失败", "get_target_module_base", 0);
return NULL;
}
- if (cbNeeded > sizeof(moduleHandleList)) {
- MessageBox(NULL, L"模块数量过多", L"GetTargetModuleBase", 0);
- return NULL;
- }
- DWORD processCount = cbNeeded / sizeof(HMODULE);
-
- char moduleName[32];
- for (DWORD i = 0; i < processCount; i++) {
- GetModuleBaseNameA(process, moduleHandleList[i], moduleName, 32);
- if (!strncmp(dll.c_str(), moduleName, dll.size())) {
- return moduleHandleList[i];
+ DWORD count = needed / sizeof(HMODULE);
+ char module_name[MAX_PATH];
+ for (DWORD i = 0; i < count; i++) {
+ GetModuleBaseNameA(process, modules[i], module_name, sizeof(module_name));
+ if (!strncmp(dll.c_str(), module_name, dll.size())) {
+ return modules[i];
}
}
return NULL;
}
-HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase)
+HANDLE inject_dll(DWORD pid, const string &dll_path, HMODULE *injected_base)
{
- HANDLE hThread;
- SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR);
+ SIZE_T path_size = dll_path.size() + 1;
// 1. 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
- if (hProcess == NULL) {
- MessageBox(NULL, L"打开进程失败", L"InjectDll", 0);
+ if (!hProcess) {
+ util::MsgBox(NULL, "打开进程失败", "inject_dll", 0);
return NULL;
}
// 2. 在目标进程的内存里开辟空间
- LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE);
- if (pRemoteAddress == NULL) {
- MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0);
+ LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE);
+ if (!pRemoteAddress) {
+ handle_injection_error(hProcess, NULL, "DLL 路径写入失败");
return NULL;
}
// 3. 把 dll 的路径写入到目标进程的内存空间中
- WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL);
+ WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL);
- // 3. 创建一个远程线程,让目标进程调用 LoadLibrary
- HMODULE k32 = GetModuleHandle(L"kernel32.dll");
- if (k32 == NULL) {
- MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
+ // 4. 创建一个远程线程,让目标进程调用 LoadLibrary
+ HMODULE k32 = GetModuleHandleA("kernel32.dll");
+ if (!k32) {
+ handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败");
return NULL;
}
- FARPROC libAddr = GetProcAddress(k32, "LoadLibraryW");
+ FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA");
if (!libAddr) {
- MessageBox(NULL, L"获取 LoadLibrary 失败", L"InjectDll", 0);
+ handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败");
return NULL;
}
- hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
- if (hThread == NULL) {
- VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
- CloseHandle(hProcess);
- MessageBox(NULL, L"CreateRemoteThread 失败", L"InjectDll", 0);
+ HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
+ if (!hThread) {
+ handle_injection_error(hProcess, pRemoteAddress, "CreateRemoteThread 失败");
return NULL;
}
- WaitForSingleObject(hThread, -1);
+ WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
- *injectedBase = GetTargetModuleBase(hProcess, filesystem::path(Wstring2String(dllPath)).filename().string());
+ *injected_base = get_target_module_base(hProcess, filesystem::path(dll_path).filename().string());
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
- // CloseHandle(hProcess); // Close when exit
-
return hProcess;
}
-bool EjectDll(HANDLE process, HMODULE dllBase)
+bool eject_dll(HANDLE process, HMODULE dll_base)
{
- HANDLE hThread = NULL;
-
- // 使目标进程调用 FreeLibrary,卸载 DLL
- HMODULE k32 = GetModuleHandle(L"kernel32.dll");
- if (k32 == NULL) {
- MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
- return NULL;
+ HMODULE k32 = GetModuleHandleA("kernel32.dll");
+ if (!k32) {
+ util::MsgBox(NULL, "获取 kernel32 失败", "eject_dll", 0);
+ return false;
}
FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread");
if (!libAddr) {
- MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0);
- return NULL;
+ util::MsgBox(NULL, "获取 FreeLibrary 失败", "eject_dll", 0);
+ return false;
}
- hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dllBase, 0, NULL);
- if (hThread == NULL) {
- MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0);
+
+ HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL);
+ if (!hThread) {
+ util::MsgBox(NULL, "FreeLibrary 调用失败!", "eject_dll", 0);
return false;
}
@@ -116,38 +114,34 @@ bool EjectDll(HANDLE process, HMODULE dllBase)
return true;
}
-static UINT64 GetFuncOffset(LPCWSTR dllPath, LPCSTR funcName)
+static uint64_t get_func_offset(const string &dll_path, const string &func_name)
{
- HMODULE dll = LoadLibrary(dllPath);
- if (dll == NULL) {
- MessageBox(NULL, L"获取 DLL 失败", L"GetFuncOffset", 0);
+ HMODULE dll = LoadLibraryA(dll_path.c_str());
+ if (!dll) {
+ util::MsgBox(NULL, "获取 DLL 失败", "get_func_offset", 0);
return 0;
}
- LPVOID absAddr = GetProcAddress(dll, funcName);
- UINT64 offset = (UINT64)absAddr - (UINT64)dll;
+ LPVOID absAddr = GetProcAddress(dll, func_name.c_str());
+ uint64_t offset = reinterpret_cast(absAddr) - reinterpret_cast(dll);
FreeLibrary(dll);
return offset;
}
-bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPDWORD ret)
+bool call_dll_func(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name, DWORD *ret)
{
- UINT64 offset = GetFuncOffset(dllPath, funcName);
- if (offset == 0) {
- return false;
+ uint64_t offset = get_func_offset(dll_path, func_name);
+ if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) {
+ return false; // 避免溢出
}
- UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
- if (pFunc <= (UINT64)dllBase) {
- return false;
- }
-
+ uint64_t pFunc = reinterpret_cast(dll_base) + offset;
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL);
- if (hThread == NULL) {
+ if (!hThread) {
return false;
}
WaitForSingleObject(hThread, INFINITE);
- if (ret != NULL) {
+ if (ret) {
GetExitCodeThread(hThread, ret);
}
@@ -155,35 +149,32 @@ bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcNa
return true;
}
-bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
- LPDWORD ret)
+bool call_dll_func_ex(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name,
+ LPVOID parameter, size_t size, DWORD *ret)
{
- UINT64 offset = GetFuncOffset(dllPath, funcName);
- if (offset == 0) {
- return false;
+ uint64_t offset = get_func_offset(dll_path, func_name);
+ if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) {
+ return false; // 避免溢出
}
- UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
- if (pFunc <= (UINT64)dllBase) {
+ uint64_t pFunc = reinterpret_cast(dll_base) + offset;
+ LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE);
+ if (!pRemoteAddress) {
+ util::MsgBox(NULL, "申请内存失败", "call_dll_func_ex", 0);
return false;
}
- LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, sz, MEM_COMMIT, PAGE_READWRITE);
- if (pRemoteAddress == NULL) {
- MessageBox(NULL, L"申请内存失败", L"CallDllFuncEx", 0);
- return NULL;
- }
-
- WriteProcessMemory(process, pRemoteAddress, parameter, sz, NULL);
+ WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL);
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL);
- if (hThread == NULL) {
- VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
- MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0);
+ if (!hThread) {
+ VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
+ util::MsgBox(NULL, "远程调用失败", "call_dll_func_ex", 0);
return false;
}
+
WaitForSingleObject(hThread, INFINITE);
- VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
- if (ret != NULL) {
+ VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
+ if (ret) {
GetExitCodeThread(hThread, ret);
}
diff --git a/WeChatFerry/sdk/injector.h b/WeChatFerry/sdk/injector.h
index 07b4a28..83988a6 100644
--- a/WeChatFerry/sdk/injector.h
+++ b/WeChatFerry/sdk/injector.h
@@ -1,9 +1,11 @@
#pragma once
+#include
+
#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);
-bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
- DWORD *ret);
+HANDLE inject_dll(DWORD pid, const std::string &dll_path, HMODULE *injected_base);
+bool eject_dll(HANDLE process, HMODULE dll_base);
+bool call_dll_func(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func, DWORD *ret);
+bool call_dll_func_ex(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func,
+ LPVOID parameter, size_t size, DWORD *ret);
diff --git a/WeChatFerry/sdk/sdk.cpp b/WeChatFerry/sdk/sdk.cpp
index 8b1c790..8363414 100644
--- a/WeChatFerry/sdk/sdk.cpp
+++ b/WeChatFerry/sdk/sdk.cpp
@@ -1,4 +1,5 @@
-#include "framework.h"
+#include "sdk.h"
+
#include
#include
#include
@@ -6,55 +7,67 @@
#include
#include
#include
+
+#include "framework.h"
#include
#include "injector.h"
-#include "sdk.h"
#include "util.h"
-static BOOL injected = false;
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+static bool injected = false;
static HANDLE wcProcess = NULL;
static HMODULE spyBase = NULL;
-static std::wstring spyDllPath;
+static std::string spyDllPath;
-constexpr char DISCLAIMER_FILE[] = ".license_accepted.flag";
-constexpr char DISCLAIMER_TEXT_FILE[] = "DISCLAIMER.md";
+constexpr char WCFSDKDLL[] = "sdk.dll";
+constexpr char WCFSPYDLL[] = "spy.dll";
+constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll";
-static std::optional ReadDisclaimerText(const char *filePath)
+constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag";
+constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md";
+
+namespace fs = std::filesystem;
+
+static fs::path get_module_directory()
{
- std::ifstream file(filePath, std::ios::binary);
- if (!file.is_open()) {
- return std::nullopt; // 文件打开失败
- }
-
- std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator());
- return String2Wstring(content);
+ char buffer[MAX_PATH] = { 0 };
+ HMODULE hModule = reinterpret_cast(&__ImageBase);
+ GetModuleFileNameA(hModule, buffer, MAX_PATH);
+ fs::path modulePath(buffer);
+ return modulePath.parent_path();
}
-static bool ShowDisclaimer()
+static bool show_disclaimer()
{
- if (std::filesystem::exists(DISCLAIMER_FILE)) {
+ fs::path sdk_path = get_module_directory();
+ if (fs::exists(sdk_path / DISCLAIMER_FLAG)) {
return true;
}
- std::optional disclaimerTextOpt = ReadDisclaimerText(DISCLAIMER_TEXT_FILE);
- if (!disclaimerTextOpt.has_value() || disclaimerTextOpt->empty()) {
- MessageBox(NULL, L"免责声明文件为空或读取失败。", L"错误", MB_ICONERROR);
+ fs::path path = sdk_path / DISCLAIMER_TEXT_FILE;
+ std::ifstream file(path, std::ios::binary);
+ if (!file.is_open()) {
+ util::MsgBox(NULL, "免责声明文件读取失败。", "错误", MB_ICONERROR);
return false;
}
- std::wstring disclaimerText = *disclaimerTextOpt;
-
- int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
+ auto disclaimerText = std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+ if (disclaimerText.empty()) {
+ util::MsgBox(NULL, "免责声明文件为空", "错误", MB_ICONERROR);
+ return false;
+ }
+ int result = util::MsgBox(NULL, disclaimerText.c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
if (result == IDCANCEL) {
- MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION);
+ util::MsgBox(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION);
return false;
}
- std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc);
+ std::ofstream flagFile(sdk_path / DISCLAIMER_FLAG, std::ios::out | std::ios::trunc);
if (!flagFile) {
- MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR);
+ util::MsgBox(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR);
return false;
}
flagFile << "User accepted the license agreement.";
@@ -62,82 +75,79 @@ static bool ShowDisclaimer()
return true;
}
-static std::wstring GetDllPath(bool debug)
+static std::string get_dll_path(bool debug)
{
- WCHAR buffer[MAX_PATH] = { 0 };
- GetModuleFileName(GetModuleHandle(WCFSDKDLL), buffer, MAX_PATH);
-
- std::filesystem::path path(buffer);
- path.remove_filename(); // 移除文件名,保留目录路径
+ char buffer[MAX_PATH] = { 0 };
+ GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH);
+ fs::path path(buffer);
+ path.remove_filename(); // 只保留目录路径
path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL;
- if (!std::filesystem::exists(path)) {
- MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR);
- return L"";
+ if (!fs::exists(path)) {
+ util::MsgBox(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR);
+ return "";
}
- return path.wstring();
+ return path.string();
}
int WxInitSDK(bool debug, int port)
{
- if (!ShowDisclaimer()) {
+ if (!show_disclaimer()) {
exit(-1); // 用户拒绝协议,退出程序
}
int status = 0;
DWORD wcPid = 0;
- spyDllPath = GetDllPath(debug);
+ spyDllPath = get_dll_path(debug);
if (spyDllPath.empty()) {
return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在
}
- status = OpenWeChat(&wcPid);
+ status = util::open_wechat(wcPid);
if (status != 0) {
- MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0);
+ util::MsgBox(NULL, "打开微信失败", "WxInitSDK", 0);
return status;
}
- if (!IsProcessX64(wcPid)) {
- MessageBox(NULL, L"只支持 64 位微信", L"WxInitSDK", 0);
- return -1;
- }
-
std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开
- wcProcess = InjectDll(wcPid, spyDllPath.c_str(), &spyBase);
+ wcProcess = inject_dll(wcPid, spyDllPath, &spyBase);
if (wcProcess == NULL) {
- MessageBox(NULL, L"注入失败", L"WxInitSDK", 0);
+ util::MsgBox(NULL, "注入失败", "WxInitSDK", 0);
return -1;
}
-
- PortPath_t pp = { 0 };
- pp.port = port;
- sprintf_s(pp.path, MAX_PATH, "%s", std::filesystem::current_path().string().c_str());
-
- if (!CallDllFuncEx(wcProcess, spyDllPath.c_str(), spyBase, "InitSpy", (LPVOID)&pp, sizeof(PortPath_t), NULL)) {
- MessageBox(NULL, L"初始化失败", L"WxInitSDK", 0);
- return -1;
- }
-
injected = true;
- return 0;
+
+ util::PortPath pp = { 0 };
+ pp.port = port;
+ snprintf(pp.path, MAX_PATH, "%s", fs::current_path().string().c_str());
+
+ status = -3; // TODO: 统一错误码
+ bool success = call_dll_func_ex(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(util::PortPath),
+ (DWORD *)&status);
+ if (!success || status != 0) {
+ WxDestroySDK();
+ }
+
+ return status;
}
int WxDestroySDK()
{
if (!injected) {
+ return 1; // 未注入
+ }
+
+ if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) {
return -1;
}
- if (!CallDllFunc(wcProcess, spyDllPath.c_str(), spyBase, "CleanupSpy", NULL)) {
+ if (!eject_dll(wcProcess, spyBase)) {
return -2;
}
-
- if (!EjectDll(wcProcess, spyBase)) {
- return -3; // TODO: Unify error codes
- }
+ injected = false;
return 0;
}
diff --git a/WeChatFerry/spy/Spy.vcxproj b/WeChatFerry/spy/Spy.vcxproj
index 6a66d2d..0854bf5 100644
--- a/WeChatFerry/spy/Spy.vcxproj
+++ b/WeChatFerry/spy/Spy.vcxproj
@@ -247,19 +247,20 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry
-
-
-
+
+
+
-
-
+
+
+
-
+
-
+
@@ -268,16 +269,16 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry
-
-
+
+
-
-
-
+
+
+
-
+
-
+
diff --git a/WeChatFerry/spy/Spy.vcxproj.filters b/WeChatFerry/spy/Spy.vcxproj.filters
index 3bf11bb..a2a537b 100644
--- a/WeChatFerry/spy/Spy.vcxproj.filters
+++ b/WeChatFerry/spy/Spy.vcxproj.filters
@@ -24,16 +24,16 @@
头文件
-
+
头文件
-
+
头文件
-
+
头文件
-
+
头文件
@@ -63,16 +63,16 @@
nnrpc
-
+
头文件
-
+
头文件
头文件
-
+
头文件
@@ -87,6 +87,9 @@
头文件
+
+ 头文件
+
@@ -95,16 +98,16 @@
源文件
-
+
源文件
-
+
源文件
-
+
源文件
-
+
源文件
@@ -125,13 +128,13 @@
nnrpc
-
+
源文件
-
+
源文件
-
+
源文件
diff --git a/WeChatFerry/spy/account_manager.cpp b/WeChatFerry/spy/account_manager.cpp
new file mode 100644
index 0000000..b7c0306
--- /dev/null
+++ b/WeChatFerry/spy/account_manager.cpp
@@ -0,0 +1,110 @@
+#include "account_manager.h"
+
+#include
+
+#include "log.hpp"
+#include "offsets.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "util.h"
+
+namespace account
+{
+
+namespace fs = std::filesystem;
+namespace OsAcc = Offsets::Account;
+
+using get_account_service_t = QWORD (*)();
+using get_data_path_t = QWORD (*)(QWORD);
+
+// 缓存避免重复查询
+static std::optional cachedWxid;
+static std::optional cachedHomePath;
+
+// 清除缓存
+static void clear_cached_wxid() { cachedWxid.reset(); }
+static void clear_cached_home_path() { cachedHomePath.reset(); }
+
+static uint64_t get_account_service()
+{
+ static auto GetService = Spy::getFunction(OsAcc::SERVICE);
+ return GetService ? GetService() : 0;
+}
+
+static std::string get_string_value(uint64_t base_addr, uint64_t offset)
+{
+ uint64_t type = util::get_qword(base_addr + offset + 0x18);
+ return (type == 0xF) ? util::get_p_string(base_addr + offset) : util::get_pp_string(base_addr + offset);
+}
+
+bool is_logged_in()
+{
+ clear_cached_wxid();
+ clear_cached_home_path();
+ uint64_t service_addr = get_account_service();
+ return service_addr && util::get_qword(service_addr + OsAcc::LOGIN) != 0;
+}
+
+fs::path get_home_path()
+{
+ if (cachedHomePath) {
+ return *cachedHomePath;
+ }
+ WxString home;
+ auto GetDataPath = Spy::getFunction(OsAcc::PATH);
+ int64_t service_addr = get_account_service();
+ GetDataPath((QWORD)&home);
+ if (home.wptr) {
+ cachedHomePath = util::w2s(std::wstring(home.wptr, home.size));
+ }
+ return *cachedHomePath;
+}
+
+std::string get_self_wxid()
+{
+ if (cachedWxid) {
+ return *cachedWxid;
+ }
+ uint64_t service_addr = get_account_service();
+ if (!service_addr) return "";
+
+ cachedWxid = get_string_value(service_addr, OsAcc::WXID);
+ return *cachedWxid;
+}
+
+UserInfo_t get_user_info()
+{
+ UserInfo_t ui;
+ uint64_t service_addr = get_account_service();
+ if (!service_addr) return ui;
+
+ ui.wxid = get_self_wxid();
+ ui.home = get_home_path().generic_string();
+ ui.name = get_string_value(service_addr, OsAcc::NAME);
+ ui.mobile = get_string_value(service_addr, OsAcc::MOBILE);
+ return ui;
+}
+
+bool rpc_is_logged_in(uint8_t *out, size_t *len)
+{
+ return fill_response(out, len, [](Response &rsp) { rsp.msg.status = is_logged_in(); });
+}
+
+bool rpc_get_self_wxid(uint8_t *out, size_t *len)
+{
+ return fill_response(
+ out, len, [](Response &rsp) { rsp.msg.str = (char *)get_self_wxid().c_str(); });
+}
+
+bool rpc_get_user_info(uint8_t *out, size_t *len)
+{
+ UserInfo_t ui = get_user_info();
+ return fill_response(out, len, ui, [](Response &rsp, UserInfo_t &ui) {
+ rsp.msg.ui.wxid = (char *)ui.wxid.c_str();
+ rsp.msg.ui.name = (char *)ui.name.c_str();
+ rsp.msg.ui.mobile = (char *)ui.mobile.c_str();
+ rsp.msg.ui.home = (char *)ui.home.c_str();
+ });
+}
+
+} // namespace account
diff --git a/WeChatFerry/spy/account_manager.h b/WeChatFerry/spy/account_manager.h
new file mode 100644
index 0000000..4eede78
--- /dev/null
+++ b/WeChatFerry/spy/account_manager.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "pb_types.h"
+
+namespace account
+{
+
+// 登录状态
+bool is_logged_in();
+
+// 获取 WeChat 数据存储路径
+std::filesystem::path get_home_path();
+
+// 获取自身 wxid
+std::string get_self_wxid();
+
+// 获取用户信息
+UserInfo_t get_user_info();
+
+// RPC 方法
+bool rpc_is_logged_in(uint8_t *out, size_t *len);
+bool rpc_get_self_wxid(uint8_t *out, size_t *len);
+bool rpc_get_user_info(uint8_t *out, size_t *len);
+
+} // namespace account
diff --git a/WeChatFerry/spy/chatroom_manager.cpp b/WeChatFerry/spy/chatroom_manager.cpp
new file mode 100644
index 0000000..912bda2
--- /dev/null
+++ b/WeChatFerry/spy/chatroom_manager.cpp
@@ -0,0 +1,87 @@
+#pragma execution_character_set("utf-8")
+
+#include "chatroom_manager.h"
+#include "log.hpp"
+#include "offsets.h"
+#include "pb_util.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "util.h"
+
+namespace chatroom
+{
+namespace OsRoom = Offsets::Chatroom;
+
+using get_mgr_t = QWORD (*)();
+using add_member_t = QWORD (*)(QWORD, QWORD, WxString *, QWORD);
+using delete_member_t = QWORD (*)(QWORD, QWORD, WxString *);
+using invite_members_t = QWORD (*)(const wchar_t *, QWORD, WxString *, QWORD);
+
+template
+bool rpc_chatroom_common(const MemberMgmt &m, uint8_t *out, size_t *len, Func func)
+{
+ int status = -1;
+ if (m.wxids && m.roomid) {
+ status = func(m.roomid, m.wxids);
+ } else {
+ LOG_ERROR("wxid 和 roomid 不能为空");
+ }
+ return fill_response(out, len, [&](Response &rsp) { rsp.msg.status = status; });
+}
+
+int add_chatroom_member(const string &roomid, const string &wxids)
+{
+ auto get_chatroom_mgr = Spy::getFunction(OsRoom::MGR);
+ auto add_members = Spy::getFunction(OsRoom::ADD);
+
+ WxString *wx_roomid = util::CreateWxString(roomid);
+
+ QWORD tmp[2] = { 0 };
+ auto wx_members = util::parse_wxids(wxids).wxWxids;
+ QWORD p_members = reinterpret_cast(&wx_members);
+
+ return static_cast(add_members(get_chatroom_mgr(), p_members, wx_roomid, reinterpret_cast(tmp)));
+}
+
+int del_chatroom_member(const string &roomid, const string &wxids)
+{
+ auto get_chatroom_mgr = Spy::getFunction(OsRoom::MGR);
+ auto del_members = Spy::getFunction(OsRoom::DEL);
+
+ WxString *wx_roomid = util::CreateWxString(roomid);
+ auto wx_members = util::parse_wxids(wxids).wxWxids;
+ QWORD p_members = reinterpret_cast(&wx_members);
+
+ return static_cast(del_members(get_chatroom_mgr(), p_members, wx_roomid));
+}
+
+int invite_chatroom_member(const string &roomid, const string &wxids)
+{
+ auto invite_members = Spy::getFunction(OsRoom::INV);
+
+ wstring ws_roomid = util::s2w(roomid);
+ WxString *wx_roomid = util::CreateWxString(roomid);
+
+ QWORD tmp[2] = { 0 };
+ auto wx_members = util::parse_wxids(wxids).wxWxids;
+ QWORD p_members = reinterpret_cast(&wx_members);
+
+ return static_cast(invite_members(ws_roomid.c_str(), p_members, wx_roomid, reinterpret_cast(tmp)));
+}
+
+bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
+{
+ return rpc_chatroom_common(m, out, len, add_chatroom_member);
+}
+
+bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
+{
+ return rpc_chatroom_common(m, out, len, del_chatroom_member);
+}
+
+bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
+{
+ return rpc_chatroom_common(m, out, len, invite_chatroom_member);
+}
+
+} // namespace chatroom
diff --git a/WeChatFerry/spy/chatroom_manager.h b/WeChatFerry/spy/chatroom_manager.h
new file mode 100644
index 0000000..3be999d
--- /dev/null
+++ b/WeChatFerry/spy/chatroom_manager.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include
+
+#include "wcf.pb.h"
+
+namespace chatroom
+{
+
+// 添加成员到群聊
+int add_chatroom_member(const std::string &roomid, const std::string &wxids);
+
+// 从群聊中移除成员
+int del_chatroom_member(const std::string &roomid, const std::string &wxids);
+
+// 邀请成员加入群聊
+int invite_chatroom_member(const std::string &roomid, const std::string &wxids);
+
+// RPC 方法
+bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len);
+bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len);
+bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len);
+
+} // namespace chatroom
diff --git a/WeChatFerry/spy/chatroom_mgmt.cpp b/WeChatFerry/spy/chatroom_mgmt.cpp
index 0950d45..ce36eac 100644
--- a/WeChatFerry/spy/chatroom_mgmt.cpp
+++ b/WeChatFerry/spy/chatroom_mgmt.cpp
@@ -3,16 +3,16 @@
#include
#include "chatroom_mgmt.h"
-#include "log.hpp"
+#include "log.h"
#include "util.h"
using namespace std;
extern QWORD g_WeChatWinDllAddr;
-#define OS_GET_CHATROOM_MGR 0x1B83BD0
-#define OS_ADD_MEMBERS 0x2155100
-#define OS_DELETE_MEMBERS 0x2155740
-#define OS_INVITE_MEMBERS 0x2154AE0
+#define OS_GET_CHATROOM_MGR 0x1B894E0
+#define OS_ADD_MEMBERS 0x215A820
+#define OS_DELETE_MEMBERS 0x215AE60
+#define OS_INVITE_MEMBERS 0x215A200
typedef QWORD (*GetChatRoomMgr_t)();
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
@@ -111,3 +111,4 @@ int InviteChatroomMember(string roomid, string wxids)
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
return status;
}
+
diff --git a/WeChatFerry/spy/chatroom_mgmt.h b/WeChatFerry/spy/chatroom_mgmt.h
deleted file mode 100644
index da1eabf..0000000
--- a/WeChatFerry/spy/chatroom_mgmt.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include
-
-int AddChatroomMember(std::string roomid, std::string wxids);
-int DelChatroomMember(std::string roomid, std::string wxids);
-int InviteChatroomMember(std::string roomid, std::string wxids);
diff --git a/WeChatFerry/spy/contact_manager.cpp b/WeChatFerry/spy/contact_manager.cpp
new file mode 100644
index 0000000..64728ea
--- /dev/null
+++ b/WeChatFerry/spy/contact_manager.cpp
@@ -0,0 +1,131 @@
+#pragma execution_character_set("utf-8")
+
+#include "contact_manager.h"
+
+#include "log.hpp"
+#include "offsets.h"
+#include "pb_util.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "util.h"
+
+using namespace std;
+
+namespace contact
+{
+namespace OsCon = Offsets::Contact;
+
+using get_contact_mgr_t = QWORD (*)();
+using get_contact_list_t = QWORD (*)(QWORD, QWORD);
+
+#define FEAT_LEN 5
+static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
+static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 };
+static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 };
+
+static QWORD find_mem(QWORD start, QWORD end, const void *target, size_t len)
+{
+ uint8_t *p = reinterpret_cast(start);
+ while (reinterpret_cast(p) < end) {
+ if (memcmp(p, target, len) == 0) {
+ return reinterpret_cast(p);
+ }
+ p++;
+ }
+ return 0;
+}
+
+static string get_cnt_string(QWORD start, QWORD end, const uint8_t *feat, size_t len)
+{
+ QWORD pfeat = find_mem(start, end, feat, len);
+ if (pfeat == 0) {
+ return "";
+ }
+
+ DWORD lfeat = util::get_dword(pfeat + len);
+ if (lfeat <= 2) {
+ return "";
+ }
+
+ return util::w2s(util::get_p_wstring(pfeat + FEAT_LEN + 4, lfeat));
+}
+
+vector get_contacts()
+{
+ vector contacts;
+ auto func_get_contact_mgr = Spy::getFunction(OsCon::MGR);
+ auto func_get_contact_list = Spy::getFunction(OsCon::LIST);
+
+ QWORD mgr = func_get_contact_mgr();
+ QWORD addr[3] = { 0 };
+ if (func_get_contact_list(mgr, reinterpret_cast(addr)) != 1) {
+ LOG_ERROR("get_contacts failed");
+ return contacts;
+ }
+
+ QWORD pstart = addr[0];
+ QWORD pend = addr[2];
+ while (pstart < pend) {
+ RpcContact_t cnt;
+ QWORD pbin = util::get_qword(pstart + OsCon::BIN);
+ QWORD lenbin = util::get_dword(pstart + OsCon::BIN_LEN);
+
+ cnt.wxid = util::get_str_by_wstr_addr(pstart + OsCon::WXID);
+ cnt.code = util::get_str_by_wstr_addr(pstart + OsCon::CODE);
+ cnt.remark = util::get_str_by_wstr_addr(pstart + OsCon::REMARK);
+ cnt.name = util::get_str_by_wstr_addr(pstart + OsCon::NAME);
+
+ cnt.country = get_cnt_string(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN);
+ cnt.province = get_cnt_string(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN);
+ cnt.city = get_cnt_string(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN);
+
+ cnt.gender = (pbin == 0) ? 0 : static_cast(*(uint8_t *)(pbin + OsCon::GENDER));
+
+ contacts.push_back(cnt);
+ pstart += OsCon::STEP;
+ }
+
+ return contacts;
+}
+
+int accept_new_friend(const std::string &v3, const std::string &v4, int scene)
+{
+ LOG_ERROR("技术太菜,实现不了。");
+ return -1; // 成功返回 1
+}
+
+RpcContact_t get_contact_by_wxid(const string &wxid)
+{
+ RpcContact_t contact;
+ LOG_ERROR("技术太菜,实现不了。");
+ return contact;
+}
+
+bool rpc_get_contacts(uint8_t *out, size_t *len)
+{
+ vector contacts = get_contacts();
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
+ rsp.msg.contacts.contacts.arg = &contacts;
+ });
+}
+
+bool rpc_get_contact_info(const string &wxid, uint8_t *out, size_t *len)
+{
+ vector contacts = { get_contact_by_wxid(wxid) };
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
+ rsp.msg.contacts.contacts.arg = &contacts;
+ });
+}
+
+bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len)
+{
+ const string v3 = v.v3 ? v.v3 : "";
+ const string v4 = v.v4 ? v.v4 : "";
+ int scene = v.scene;
+ return fill_response(
+ out, len, [&](Response &rsp) { rsp.msg.status = accept_new_friend(v3, v4, scene); });
+}
+
+} // namespace contact
diff --git a/WeChatFerry/spy/contact_manager.h b/WeChatFerry/spy/contact_manager.h
new file mode 100644
index 0000000..440091e
--- /dev/null
+++ b/WeChatFerry/spy/contact_manager.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include
+#include
+
+#include "wcf.pb.h"
+
+#include "pb_types.h"
+
+namespace contact
+{
+
+// 获取所有联系人
+std::vector get_contacts();
+
+// 根据 wxid 获取联系人信息
+RpcContact_t get_contact_by_wxid(const std::string &wxid);
+
+// 接受好友请求
+int accept_new_friend(const std::string &v3, const std::string &v4, int scene);
+
+// 发送好友请求
+// int add_friend_by_wxid(const std::string &wxid, const std::string &msg);
+
+// RPC 方法
+bool rpc_get_contacts(uint8_t *out, size_t *len);
+bool rpc_get_contact_info(const std::string &wxid, uint8_t *out, size_t *len);
+bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len);
+
+} // namespace contact
diff --git a/WeChatFerry/spy/contact_mgmt.cpp b/WeChatFerry/spy/contact_mgmt.cpp
deleted file mode 100644
index 84f916a..0000000
--- a/WeChatFerry/spy/contact_mgmt.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-#pragma execution_character_set("utf-8")
-
-#include "contact_mgmt.h"
-#include "log.hpp"
-#include "util.h"
-
-using namespace std;
-extern QWORD g_WeChatWinDllAddr;
-
-#define OS_GET_CONTACT_MGR 0x1B417A0
-#define OS_GET_CONTACT_LIST 0x219ED10
-#define OS_CONTACT_BIN 0x200
-#define OS_CONTACT_BIN_LEN 0x208
-#define OS_CONTACT_WXID 0x10
-#define OS_CONTACT_CODE 0x30
-#define OS_CONTACT_REMARK 0x80
-#define OS_CONTACT_NAME 0xA0
-#define OS_CONTACT_GENDER 0x0E
-#define OS_CONTACT_STEP 0x6A8
-
-typedef QWORD (*GetContactMgr_t)();
-typedef QWORD (*GetContactList_t)(QWORD, QWORD);
-
-#define FEAT_LEN 5
-static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
-static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 };
-static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 };
-
-static QWORD FindMem(QWORD start, QWORD end, const void *target, size_t len)
-{
- uint8_t *p = (uint8_t *)start;
- while ((QWORD)p < end) {
- if (memcmp((void *)p, target, len) == 0) {
- return (QWORD)p;
- }
- p++;
- }
-
- return 0;
-}
-
-static string GetCntString(QWORD start, QWORD end, const uint8_t *feat, size_t len)
-{
- QWORD pfeat = FindMem(start, end, feat, len);
- if (pfeat == 0) {
- return "";
- }
-
- DWORD lfeat = GET_DWORD(pfeat + len);
- if (lfeat <= 2) {
- return "";
- }
-
- return Wstring2String(wstring(GET_WSTRING_FROM_P(pfeat + FEAT_LEN + 4), lfeat));
-}
-
-vector GetContacts()
-{
- vector contacts;
- GetContactMgr_t funcGetContactMgr = (GetContactMgr_t)(g_WeChatWinDllAddr + OS_GET_CONTACT_MGR);
- GetContactList_t funcGetContactList = (GetContactList_t)(g_WeChatWinDllAddr + OS_GET_CONTACT_LIST);
-
- QWORD mgr = funcGetContactMgr();
- QWORD addr[3] = { 0 };
- if (funcGetContactList(mgr, (QWORD)addr) != 1) {
- LOG_ERROR("GetContacts failed");
- return contacts;
- }
-
- QWORD pstart = (QWORD)addr[0];
- QWORD pend = (QWORD)addr[2];
- while (pstart < pend) {
- RpcContact_t cnt;
- QWORD pbin = GET_QWORD(pstart + OS_CONTACT_BIN);
- QWORD lenbin = GET_DWORD(pstart + OS_CONTACT_BIN_LEN);
-
- cnt.wxid = GetStringByWstrAddr(pstart + OS_CONTACT_WXID);
- cnt.code = GetStringByWstrAddr(pstart + OS_CONTACT_CODE);
- cnt.remark = GetStringByWstrAddr(pstart + OS_CONTACT_REMARK);
- cnt.name = GetStringByWstrAddr(pstart + OS_CONTACT_NAME);
-
- cnt.country = GetCntString(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN);
- cnt.province = GetCntString(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN);
- cnt.city = GetCntString(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN);
-
- if (pbin == 0) {
- cnt.gender = 0;
- } else {
- cnt.gender = (DWORD) * (uint8_t *)(pbin + OS_CONTACT_GENDER);
- }
-
- contacts.push_back(cnt);
- pstart += OS_CONTACT_STEP;
- }
-
- return contacts;
-}
-
-#if 0
-int AcceptNewFriend(string v3, string v4, int scene)
-{
- int success = 0;
-
- DWORD acceptNewFriendCall1 = g_WeChatWinDllAddr + g_WxCalls.anf.call1;
- DWORD acceptNewFriendCall2 = g_WeChatWinDllAddr + g_WxCalls.anf.call2;
- DWORD acceptNewFriendCall3 = g_WeChatWinDllAddr + g_WxCalls.anf.call3;
- DWORD acceptNewFriendCall4 = g_WeChatWinDllAddr + g_WxCalls.anf.call4;
-
- char buffer[0x40] = { 0 };
- char nullbuffer[0x3CC] = { 0 };
-
- LOG_DEBUG("\nv3: {}\nv4: {}\nscene: {}", v3, v4, scene);
-
- wstring wsV3 = String2Wstring(v3);
- wstring wsV4 = String2Wstring(v4);
- WxString wxV3(wsV3);
- WxString wxV4(wsV4);
-
- __asm {
- pushad;
- pushfd;
- lea ecx, buffer;
- call acceptNewFriendCall1;
- mov esi, 0x0;
- mov edi, scene;
- push esi;
- push edi;
- sub esp, 0x14;
- mov ecx, esp;
- lea eax, wxV4;
- push eax;
- call acceptNewFriendCall2;
- sub esp, 0x8;
- push 0x0;
- lea eax, nullbuffer;
- push eax;
- lea eax, wxV3;
- push eax;
- lea ecx, buffer;
- call acceptNewFriendCall3;
- mov success, eax;
- lea ecx, buffer;
- call acceptNewFriendCall4;
- popfd;
- popad;
- }
-
- return success; // 成功返回 1
-}
-
-/*没啥用,非好友获取不到*/
-RpcContact_t GetContactByWxid(string wxid)
-{
- RpcContact_t contact;
- char buff[0x440] = { 0 };
- wstring wsWxid = String2Wstring(wxid);
- WxString pri(wsWxid);
- DWORD contact_mgr_addr = g_WeChatWinDllAddr + 0x75A4A0;
- DWORD get_contact_addr = g_WeChatWinDllAddr + 0xC04E00;
- DWORD free_contact_addr = g_WeChatWinDllAddr + 0xEA7880;
- __asm {
- PUSHAD
- PUSHFD
- CALL contact_mgr_addr
- LEA ECX,buff
- PUSH ECX
- LEA ECX,pri
- PUSH ECX
- MOV ECX,EAX
- CALL get_contact_addr
- POPFD
- POPAD
- }
-
- contact.wxid = wxid;
- contact.code = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxCode);
- contact.remark = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxRemark);
- contact.name = GetStringByWstrAddr((DWORD)buff + g_WxCalls.contact.wxName);
- contact.gender = GET_DWORD((DWORD)buff + 0x148);
-
- __asm {
- PUSHAD
- PUSHFD
- LEA ECX,buff
- CALL free_contact_addr
- POPFD
- POPAD
- }
-
- return contact;
-}
-#endif
diff --git a/WeChatFerry/spy/contact_mgmt.h b/WeChatFerry/spy/contact_mgmt.h
deleted file mode 100644
index 460bf89..0000000
--- a/WeChatFerry/spy/contact_mgmt.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#include "string"
-#include
-
-#include "pb_types.h"
-
-vector GetContacts();
-int AcceptNewFriend(std::string v3, std::string v4, int scene);
-int AddFriendByWxid(std::string wxid, std::string msg);
-RpcContact_t GetContactByWxid(std::string wxid);
diff --git a/WeChatFerry/spy/database_executor.cpp b/WeChatFerry/spy/database_executor.cpp
new file mode 100644
index 0000000..42e0d9a
--- /dev/null
+++ b/WeChatFerry/spy/database_executor.cpp
@@ -0,0 +1,277 @@
+#include "database_executor.h"
+
+#include
+#include
+
+#include "log.hpp"
+#include "offsets.h"
+#include "pb_util.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "sqlite3.h"
+#include "util.h"
+
+namespace db
+{
+namespace OsDb = Offsets::Db;
+
+using db_map_t = std::map;
+static db_map_t db_map;
+
+static void get_db_handle(QWORD base, QWORD offset)
+{
+ auto *wsp = reinterpret_cast(*(QWORD *)(base + offset + OsDb::NAME));
+ std::string dbname = util::w2s(std::wstring(wsp));
+ db_map[dbname] = util::get_qword(base + offset);
+}
+
+static void get_msg_db_handle(QWORD msg_mgr_addr)
+{
+ QWORD db_index = util::get_qword(msg_mgr_addr + 0x68);
+ QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
+ for (uint32_t i = 0; i < db_index; i++) {
+ QWORD db_addr = util::get_qword(p_start + i * 0x08);
+ if (db_addr) {
+ // MSGi.db
+ std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
+ db_map[dbname] = util::get_qword(db_addr + 0x78);
+
+ // MediaMsgi.db
+ QWORD mmdb_addr = util::get_qword(db_addr + 0x20);
+ std::string mmdbname = util::w2s(util::get_pp_wstring(mmdb_addr + 0x78));
+ db_map[mmdbname] = util::get_qword(mmdb_addr + 0x50);
+ }
+ }
+}
+
+db_map_t get_db_handles()
+{
+ db_map.clear();
+ QWORD db_instance_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::INSTANCE);
+
+ get_db_handle(db_instance_addr, OsDb::MICROMSG); // MicroMsg.db
+ get_db_handle(db_instance_addr, OsDb::CHAT_MSG); // ChatMsg.db
+ get_db_handle(db_instance_addr, OsDb::MISC); // Misc.db
+ get_db_handle(db_instance_addr, OsDb::EMOTION); // Emotion.db
+ get_db_handle(db_instance_addr, OsDb::MEDIA); // Media.db
+ get_db_handle(db_instance_addr, OsDb::FUNCTION_MSG); // Function.db
+
+ get_msg_db_handle(util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I)); // MSGi.db & MediaMsgi.db
+ return db_map;
+}
+
+DbNames_t get_db_names()
+{
+ if (db_map.empty()) {
+ db_map = get_db_handles();
+ }
+
+ DbNames_t names;
+ for (const auto &[k, _] : db_map) {
+ names.push_back(k);
+ }
+ return names;
+}
+
+static int cb_get_tables(void *ret, int argc, char **argv, char **azColName)
+{
+ auto *tables = static_cast(ret);
+ DbTable_t tbl;
+ for (int i = 0; i < argc; i++) {
+ if (strcmp(azColName[i], "name") == 0) {
+ tbl.name = argv[i] ? argv[i] : "";
+ } else if (strcmp(azColName[i], "sql") == 0) {
+ std::string sql(argv[i]);
+ sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end());
+ tbl.sql = sql;
+ }
+ }
+ tables->push_back(tbl);
+ return 0;
+}
+
+DbTables_t get_db_tables(const std::string &db)
+{
+ DbTables_t tables;
+ if (db_map.empty()) {
+ db_map = get_db_handles();
+ }
+
+ auto it = db_map.find(db);
+ if (it == db_map.end()) {
+ return tables;
+ }
+
+ constexpr const char *sql = "SELECT name FROM sqlite_master WHERE type='table';";
+ auto p_sqlite3_exec = Spy::getFunction(OsDb::EXEC);
+ p_sqlite3_exec(it->second, sql, (Sqlite3_callback)cb_get_tables, (void *)&tables, nullptr);
+
+ return tables;
+}
+
+DbRows_t exec_db_query(const std::string &db, const std::string &sql)
+{
+ DbRows_t rows;
+
+ auto func_prepare = Spy::getFunction(OsDb::PREPARE);
+ auto func_step = Spy::getFunction(OsDb::STEP);
+ auto func_column_count = Spy::getFunction(OsDb::COLUMN_COUNT);
+ auto func_column_name = Spy::getFunction(OsDb::COLUMN_NAME);
+ auto func_column_type = Spy::getFunction(OsDb::COLUMN_TYPE);
+ auto func_column_blob = Spy::getFunction(OsDb::COLUMN_BLOB);
+ auto func_column_bytes = Spy::getFunction(OsDb::COLUMN_BYTES);
+ auto func_finalize = Spy::getFunction(OsDb::FINALIZE);
+
+ if (db_map.empty()) {
+ db_map = get_db_handles();
+ }
+
+ auto it = db_map.find(db);
+ if (it == db_map.end() || it->second == 0) {
+ LOG_WARN("Empty handle for database '{}', retrying...", db);
+ db_map = get_db_handles();
+ it = db_map.find(db);
+ if (it == db_map.end() || it->second == 0) {
+ LOG_ERROR("Failed to get handle for database '{}'", db);
+ return rows;
+ }
+ }
+
+ QWORD *stmt;
+ int rc = func_prepare(it->second, sql.c_str(), -1, &stmt, nullptr);
+ if (rc != SQLITE_OK) {
+ LOG_ERROR("SQL prepare failed for '{}': error code {}", db, rc);
+ return rows;
+ }
+
+ while (func_step(stmt) == SQLITE_ROW) {
+ DbRow_t row;
+ int col_count = func_column_count(stmt);
+ for (int i = 0; i < col_count; i++) {
+ DbField_t field;
+ field.type = func_column_type(stmt, i);
+ field.column = func_column_name(stmt, i);
+
+ int length = func_column_bytes(stmt, i);
+ const void *blob = func_column_blob(stmt, i);
+ if (length > 0 && field.type != SQLITE_NULL) {
+ field.content.resize(length);
+ std::memcpy(field.content.data(), blob, length);
+ }
+ row.push_back(field);
+ }
+ rows.push_back(row);
+ }
+
+ func_finalize(stmt);
+ return rows;
+}
+
+int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx)
+{
+ if (!local_id || !db_idx) {
+ LOG_ERROR("Invalid pointer arguments!");
+ return -1;
+ }
+
+ QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
+ int db_index = static_cast(util::get_qword(msg_mgr_addr + 0x68)); // 总不能 int 还不够吧?
+ QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
+
+ *db_idx = 0;
+ for (int i = db_index - 1; i >= 0; i--) { // 从后往前遍历
+ QWORD db_addr = util::get_qword(p_start + i * 0x08);
+ if (!db_addr) {
+ continue;
+ }
+
+ std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
+ db_map[dbname] = util::get_qword(db_addr + 0x78);
+
+ std::string sql = "SELECT localId FROM MSG WHERE MsgSvrID=" + std::to_string(id) + ";";
+ DbRows_t rows = exec_db_query(dbname, sql);
+
+ if (rows.empty() || rows.front().empty()) {
+ continue;
+ }
+
+ const DbField_t &field = rows.front().front();
+ if (field.column != "localId" || field.type != SQLITE_INTEGER) {
+ continue;
+ }
+
+ std::string id_str(field.content.begin(), field.content.end());
+ try {
+ *local_id = std::stoull(id_str);
+ } catch (const std::exception &e) {
+ LOG_ERROR("Failed to parse localId: {}", e.what());
+ continue;
+ }
+
+ *db_idx = static_cast(util::get_qword(util::get_qword(db_addr + 0x28) + 0x1E8) >> 32);
+ return 0;
+ }
+
+ return -1;
+}
+
+std::vector get_audio_data(uint64_t id)
+{
+ QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
+ int db_index = static_cast(util::get_qword(msg_mgr_addr + 0x68));
+
+ std::string sql = "SELECT Buf FROM Media WHERE Reserved0=" + std::to_string(id) + ";";
+ for (int i = db_index - 1; i >= 0; i--) {
+ std::string dbname = "MediaMSG" + std::to_string(i) + ".db";
+ DbRows_t rows = exec_db_query(dbname, sql);
+
+ if (rows.empty() || rows.front().empty()) {
+ continue;
+ }
+
+ const DbField_t &field = rows.front().front();
+ if (field.column != "Buf" || field.content.empty()) {
+ continue;
+ }
+
+ // 首字节为 0x02,估计是混淆用的?去掉。
+ if (field.content.front() == 0x02) {
+ return std::vector(field.content.begin() + 1, field.content.end());
+ }
+
+ return field.content;
+ }
+
+ return {};
+}
+
+bool rpc_get_db_names(uint8_t *out, size_t *len)
+{
+ DbNames_t names = get_db_names();
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.dbs.names.funcs.encode = encode_dbnames;
+ rsp.msg.dbs.names.arg = &names;
+ });
+}
+
+bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len)
+{
+ DbTables_t tables = get_db_tables(db);
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.tables.tables.funcs.encode = encode_tables;
+ rsp.msg.tables.tables.arg = &tables;
+ });
+}
+
+bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len)
+{
+ const std::string db(query.db);
+ const std::string sql(query.sql);
+ DbRows_t rows = exec_db_query(db, sql);
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.rows.rows.funcs.encode = encode_rows;
+ rsp.msg.rows.rows.arg = &rows;
+ });
+}
+
+} // namespace db
diff --git a/WeChatFerry/spy/database_executor.h b/WeChatFerry/spy/database_executor.h
new file mode 100644
index 0000000..0d50373
--- /dev/null
+++ b/WeChatFerry/spy/database_executor.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "wcf.pb.h"
+
+#include "pb_types.h"
+
+namespace db
+{
+
+// 获取数据库名称列表
+DbNames_t get_db_names();
+
+// 获取指定数据库的表列表
+DbTables_t get_db_tables(const std::string &db);
+
+// 执行 SQL 查询
+DbRows_t exec_db_query(const std::string &db, const std::string &sql);
+
+// 获取本地消息 ID 和数据库索引
+int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx);
+
+// 获取音频数据
+std::vector get_audio_data(uint64_t msg_id);
+
+// RPC 方法
+bool rpc_get_db_names(uint8_t *out, size_t *len);
+bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len);
+bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len);
+
+} // namespace db
diff --git a/WeChatFerry/spy/dllmain.cpp b/WeChatFerry/spy/dllmain.cpp
index 580ec4c..66475e4 100644
--- a/WeChatFerry/spy/dllmain.cpp
+++ b/WeChatFerry/spy/dllmain.cpp
@@ -1,9 +1,5 @@
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "framework.h"
-#include
-#include
-
-#include "spy.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
diff --git a/WeChatFerry/spy/exec_sql.cpp b/WeChatFerry/spy/exec_sql.cpp
deleted file mode 100644
index 834c856..0000000
--- a/WeChatFerry/spy/exec_sql.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-#include
-
-#include "exec_sql.h"
-#include "log.hpp"
-#include "sqlite3.h"
-#include "util.h"
-
-#define OFFSET_DB_INSTANCE 0x5902000
-#define OFFSET_DB_MICROMSG 0xB8
-#define OFFSET_DB_CHAT_MSG 0x2C8
-#define OFFSET_DB_MISC 0x5F0
-#define OFFSET_DB_EMOTION 0x15F0
-#define OFFSET_DB_MEDIA 0xF48
-#define OFFSET_DB_BIZCHAT_MSG 0x1A70
-#define OFFSET_DB_FUNCTION_MSG 0x1B98
-#define OFFSET_DB_NAME 0x28
-#define OFFSET_DB_MSG_MGR 0x595F900
-
-extern UINT64 g_WeChatWinDllAddr;
-
-typedef map dbMap_t;
-static dbMap_t dbMap;
-
-static void GetDbHandle(QWORD base, QWORD offset)
-{
- wchar_t *wsp = (wchar_t *)(*(QWORD *)(base + offset + OFFSET_DB_NAME));
- string dbname = Wstring2String(wstring(wsp));
- dbMap[dbname] = GET_QWORD(base + offset);
-}
-
-static void GetMsgDbHandle(QWORD msgMgrAddr)
-{
- QWORD dbIndex = GET_QWORD(msgMgrAddr + 0x68);
- QWORD pStart = GET_QWORD(msgMgrAddr + 0x50);
- for (uint32_t i = 0; i < dbIndex; i++) {
- QWORD dbAddr = GET_QWORD(pStart + i * 0x08);
- if (dbAddr) {
- // MSGi.db
- string dbname = Wstring2String(GET_WSTRING(dbAddr));
- dbMap[dbname] = GET_QWORD(dbAddr + 0x78);
-
- // MediaMsgi.db
- QWORD mmdbAddr = GET_QWORD(dbAddr + 0x20);
- string mmdbname = Wstring2String(GET_WSTRING(mmdbAddr + 0x78));
- dbMap[mmdbname] = GET_QWORD(mmdbAddr + 0x50);
- }
- }
-}
-
-dbMap_t GetDbHandles()
-{
- dbMap.clear();
-
- QWORD dbInstanceAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_INSTANCE);
-
- GetDbHandle(dbInstanceAddr, OFFSET_DB_MICROMSG); // MicroMsg.db
- GetDbHandle(dbInstanceAddr, OFFSET_DB_CHAT_MSG); // ChatMsg.db
- GetDbHandle(dbInstanceAddr, OFFSET_DB_MISC); // Misc.db
- GetDbHandle(dbInstanceAddr, OFFSET_DB_EMOTION); // Emotion.db
- GetDbHandle(dbInstanceAddr, OFFSET_DB_MEDIA); // Media.db
- GetDbHandle(dbInstanceAddr, OFFSET_DB_FUNCTION_MSG); // Function.db
-
- GetMsgDbHandle(GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR)); // MSGi.db & MediaMsgi.db
-
- return dbMap;
-}
-
-DbNames_t GetDbNames()
-{
- DbNames_t names;
- if (dbMap.empty()) {
- dbMap = GetDbHandles();
- }
-
- for (auto &[k, v] : dbMap) {
- names.push_back(k);
- }
-
- return names;
-}
-
-static int cbGetTables(void *ret, int argc, char **argv, char **azColName)
-{
- DbTables_t *tbls = (DbTables_t *)ret;
- DbTable_t tbl;
- for (int i = 0; i < argc; i++) {
- if (strcmp(azColName[i], "name") == 0) {
- tbl.name = argv[i] ? argv[i] : "";
- } else if (strcmp(azColName[i], "sql") == 0) {
- string sql(argv[i]);
- sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end());
- tbl.sql = sql.c_str();
- }
- }
- tbls->push_back(tbl);
- return 0;
-}
-
-DbTables_t GetDbTables(const string db)
-{
- DbTables_t tables;
- if (dbMap.empty()) {
- dbMap = GetDbHandles();
- }
-
- auto it = dbMap.find(db);
- if (it == dbMap.end()) {
- return tables; // DB not found
- }
-
- const char *sql = "select name, sql from sqlite_master where type=\"table\";";
- Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)(g_WeChatWinDllAddr + SQLITE3_EXEC_OFFSET);
-
- p_Sqlite3_exec(it->second, sql, (Sqlite3_callback)cbGetTables, (void *)&tables, 0);
-
- return tables;
-}
-
-DbRows_t ExecDbQuery(const string db, const string sql)
-{
- DbRows_t rows;
- Sqlite3_prepare func_prepare = (Sqlite3_prepare)(g_WeChatWinDllAddr + SQLITE3_PREPARE_OFFSET);
- Sqlite3_step func_step = (Sqlite3_step)(g_WeChatWinDllAddr + SQLITE3_STEP_OFFSET);
- Sqlite3_column_count func_column_count = (Sqlite3_column_count)(g_WeChatWinDllAddr + SQLITE3_COLUMN_COUNT_OFFSET);
- Sqlite3_column_name func_column_name = (Sqlite3_column_name)(g_WeChatWinDllAddr + SQLITE3_COLUMN_NAME_OFFSET);
- Sqlite3_column_type func_column_type = (Sqlite3_column_type)(g_WeChatWinDllAddr + SQLITE3_COLUMN_TYPE_OFFSET);
- Sqlite3_column_blob func_column_blob = (Sqlite3_column_blob)(g_WeChatWinDllAddr + SQLITE3_COLUMN_BLOB_OFFSET);
- Sqlite3_column_bytes func_column_bytes = (Sqlite3_column_bytes)(g_WeChatWinDllAddr + SQLITE3_COLUMN_BYTES_OFFSET);
- Sqlite3_finalize func_finalize = (Sqlite3_finalize)(g_WeChatWinDllAddr + SQLITE3_FINALIZE_OFFSET);
-
- if (dbMap.empty()) {
- dbMap = GetDbHandles();
- }
-
- QWORD *stmt;
- QWORD handle = dbMap[db];
- if (handle == 0) {
- LOG_WARN("Empty handle, retrying...");
- dbMap = GetDbHandles();
- }
-
- int rc = func_prepare(dbMap[db], sql.c_str(), -1, &stmt, 0);
- if (rc != SQLITE_OK) {
- return rows;
- }
-
- while (func_step(stmt) == SQLITE_ROW) {
- DbRow_t row;
- int col_count = func_column_count(stmt);
- for (int i = 0; i < col_count; i++) {
- DbField_t field;
- field.type = func_column_type(stmt, i);
- field.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.content.reserve(length);
- copy((uint8_t *)blob, (uint8_t *)blob + length, back_inserter(field.content));
- }
- row.push_back(field);
- }
- rows.push_back(row);
- }
- return rows;
-}
-
-int GetLocalIdandDbidx(uint64_t id, uint64_t *localId, uint32_t *dbIdx)
-{
- QWORD msgMgrAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR);
- int dbIndex = (int)GET_QWORD(msgMgrAddr + 0x68); // 总不能 int 还不够吧?
- QWORD pStart = GET_QWORD(msgMgrAddr + 0x50);
-
- *dbIdx = 0;
- for (int i = dbIndex - 1; i >= 0; i--) { // 从后往前遍历
- QWORD dbAddr = GET_QWORD(pStart + i * 0x08);
- if (dbAddr) {
- string dbname = Wstring2String(GET_WSTRING(dbAddr));
- dbMap[dbname] = GET_QWORD(dbAddr + 0x78);
- string sql = "SELECT localId FROM MSG WHERE MsgSvrID=" + to_string(id) + ";";
- DbRows_t rows = ExecDbQuery(dbname, sql);
- if (rows.empty()) {
- continue;
- }
- DbRow_t row = rows.front();
- if (row.empty()) {
- continue;
- }
- DbField_t field = row.front();
- if ((field.column.compare("localId") != 0) && (field.type != 1)) {
- continue;
- }
-
- *localId = strtoull((const char *)(field.content.data()), NULL, 10);
- *dbIdx = (uint32_t)(GET_QWORD(GET_QWORD(dbAddr + 0x28) + 0x1E8) >> 32);
-
- return 0;
- }
- }
-
- return -1;
-}
-
-vector GetAudioData(uint64_t id)
-{
- QWORD msgMgrAddr = GET_QWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR);
- int dbIndex = (int)GET_QWORD(msgMgrAddr + 0x68);
-
- string sql = "SELECT Buf FROM Media WHERE Reserved0=" + to_string(id) + ";";
- for (int i = dbIndex - 1; i >= 0; i--) {
- string dbname = "MediaMSG" + to_string(i) + ".db";
- DbRows_t rows = ExecDbQuery(dbname, sql);
- if (rows.empty()) {
- continue;
- }
- DbRow_t row = rows.front();
- if (row.empty()) {
- continue;
- }
- DbField_t field = row.front();
- if (field.column.compare("Buf") != 0) {
- continue;
- }
-
- // 首字节为 0x02,估计是混淆用的?去掉。
- vector rv(field.content.begin() + 1, field.content.end());
-
- return rv;
- }
-
- return vector();
-}
diff --git a/WeChatFerry/spy/exec_sql.h b/WeChatFerry/spy/exec_sql.h
deleted file mode 100644
index ea7fc8f..0000000
--- a/WeChatFerry/spy/exec_sql.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#include
-
-#include "pb_types.h"
-
-DbNames_t GetDbNames();
-DbTables_t GetDbTables(const string db);
-DbRows_t ExecDbQuery(const string db, const string sql);
-int GetLocalIdandDbidx(uint64_t id, uint64_t *localId, uint32_t *dbIdx);
-vector GetAudioData(uint64_t msgid);
diff --git a/WeChatFerry/spy/funcs.cpp b/WeChatFerry/spy/funcs.cpp
deleted file mode 100644
index c12f9b5..0000000
--- a/WeChatFerry/spy/funcs.cpp
+++ /dev/null
@@ -1,410 +0,0 @@
-#pragma warning(disable : 4244)
-
-#include "framework.h"
-#include
-#include
-
-#include "codec.h"
-#include "exec_sql.h"
-#include "funcs.h"
-#include "log.hpp"
-#include "spy_types.h"
-#include "util.h"
-
-using namespace std;
-namespace fs = std::filesystem;
-
-extern bool gIsListeningPyq;
-extern QWORD g_WeChatWinDllAddr;
-
-#define HEADER_PNG1 0x89
-#define HEADER_PNG2 0x50
-#define HEADER_JPG1 0xFF
-#define HEADER_JPG2 0xD8
-#define HEADER_GIF1 0x47
-#define HEADER_GIF2 0x49
-
-#define OS_LOGIN_STATUS 0x595C9E8
-#define OS_GET_SNS_DATA_MGR 0x21E2200
-#define OS_GET_SNS_FIRST_PAGE 0x2E212D0
-#define OS_GET_SNS_TIMELINE_MGR 0x2DB3390
-#define OS_GET_SNS_NEXT_PAGE 0x2EC8970
-#define OS_NEW_CHAT_MSG 0x1B5E140
-#define OS_FREE_CHAT_MSG 0x1B55850
-#define OS_GET_CHAT_MGR 0x1B876C0
-#define OS_GET_MGR_BY_PREFIX_LOCAL_ID 0x213FB00
-#define OS_GET_PRE_DOWNLOAD_MGR 0x1C0EE70
-#define OS_PUSH_ATTACH_TASK 0x1CDF4E0
-#define OS_LOGIN_QR_CODE 0x59620D8
-
-typedef QWORD (*GetSNSDataMgr_t)();
-typedef QWORD (*GetSnsTimeLineMgr_t)();
-typedef QWORD (*GetSNSFirstPage_t)(QWORD, QWORD, QWORD);
-typedef QWORD (*GetSNSNextPageScene_t)(QWORD, QWORD);
-typedef QWORD (*GetChatMgr_t)();
-typedef QWORD (*NewChatMsg_t)(QWORD);
-typedef QWORD (*FreeChatMsg_t)(QWORD);
-typedef QWORD (*GetPreDownLoadMgr_t)();
-typedef QWORD (*GetMgrByPrefixLocalId_t)(QWORD, QWORD);
-typedef QWORD (*PushAttachTask_t)(QWORD, QWORD, QWORD, QWORD);
-typedef QWORD (*GetOCRManager_t)();
-typedef QWORD (*DoOCRTask_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
-
-int IsLogin(void) { return (int)GET_QWORD(g_WeChatWinDllAddr + OS_LOGIN_STATUS); }
-
-static string get_key(uint8_t header1, uint8_t header2, uint8_t *key)
-{
- // PNG?
- *key = HEADER_PNG1 ^ header1;
- if ((HEADER_PNG2 ^ *key) == header2) {
- return ".png";
- }
-
- // JPG?
- *key = HEADER_JPG1 ^ header1;
- if ((HEADER_JPG2 ^ *key) == header2) {
- return ".jpg";
- }
-
- // GIF?
- *key = HEADER_GIF1 ^ header1;
- if ((HEADER_GIF2 ^ *key) == header2) {
- return ".gif";
- }
-
- return ""; // 错误
-}
-
-string DecryptImage(string src, string dir)
-{
- if (!fs::exists(src)) {
- LOG_ERROR("File not exists: {}", src);
- return "";
- }
-
- ifstream in(src.c_str(), ios::binary);
- if (!in.is_open()) {
- LOG_ERROR("Failed to read file {}", src);
- return "";
- }
-
- filebuf *pfb = in.rdbuf();
- size_t size = pfb->pubseekoff(0, ios::end, ios::in);
- pfb->pubseekpos(0, ios::in);
-
- vector buff;
- buff.reserve(size);
- char *pBuf = buff.data();
- pfb->sgetn(pBuf, size);
- in.close();
-
- uint8_t key = 0x00;
- string ext = get_key(pBuf[0], pBuf[1], &key);
- if (ext.empty()) {
- LOG_ERROR("Failed to get key.");
- return "";
- }
-
- for (size_t i = 0; i < size; i++) {
- pBuf[i] ^= key;
- }
-
- string dst = "";
-
- try {
- if (dir.empty()) {
- dst = fs::path(src).replace_extension(ext).string();
- } else {
- dst = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/");
-
- // 判断dir文件夹是否存在,若不存在则创建(否则将无法创建出文件)
- if (!fs::exists(dst)) {//判断该文件夹是否存在
- bool success = fs::create_directories(dst); //Windows创建文件夹
- if (!success) { //创建失败
- LOG_ERROR("Failed to mkdir:{}", dst);
- return "";
- }
- }
-
- dst += fs::path(src).stem().string() + ext;
- }
-
- replace(dst.begin(), dst.end(), '\\', '/');
- } catch (const std::exception &e) {
- LOG_ERROR(GB2312ToUtf8(e.what()));
- } catch (...) {
- LOG_ERROR("Unknow exception.");
- return "";
- }
-
- ofstream out(dst.c_str(), ios::binary);
- if (!out.is_open()) {
- LOG_ERROR("Failed to write file {}", dst);
- return "";
- }
-
- out.write(pBuf, size);
- out.close();
-
- return dst;
-}
-
-static int GetFirstPage()
-{
- int status = -1;
-
- GetSNSDataMgr_t GetSNSDataMgr = (GetSNSDataMgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_DATA_MGR);
- GetSNSFirstPage_t GetSNSFirstPage = (GetSNSFirstPage_t)(g_WeChatWinDllAddr + OS_GET_SNS_FIRST_PAGE);
-
- QWORD buff[16] = { 0 };
- QWORD mgr = GetSNSDataMgr();
- status = (int)GetSNSFirstPage(mgr, (QWORD)buff, 1);
-
- return status;
-}
-
-static int GetNextPage(QWORD id)
-{
- int status = -1;
-
- GetSnsTimeLineMgr_t GetSnsTimeLineMgr = (GetSnsTimeLineMgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_TIMELINE_MGR);
- GetSNSNextPageScene_t GetSNSNextPageScene = (GetSNSNextPageScene_t)(g_WeChatWinDllAddr + OS_GET_SNS_NEXT_PAGE);
-
- QWORD mgr = GetSnsTimeLineMgr();
- status = (int)GetSNSNextPageScene(mgr, id);
-
- return status;
-}
-
-int RefreshPyq(QWORD id)
-{
- if (!gIsListeningPyq) {
- LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg");
- return -1;
- }
-
- if (id == 0) {
- return GetFirstPage();
- }
-
- return GetNextPage(id);
-}
-
-/*******************************************************************************
- * 都说我不写注释,写一下吧
- * 其实也没啥好写的,就是下载资源
- * 主要介绍一下几个参数:
- * id:好理解,消息 id
- * thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了
- * extra:图片、文件的路径
- *******************************************************************************/
-int DownloadAttach(QWORD id, string thumb, string extra)
-{
- int status = -1;
- QWORD localId;
- uint32_t dbIdx;
-
- if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断
- return 0;
- }
-
- if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) {
- LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id));
- return status;
- }
-
- NewChatMsg_t NewChatMsg = (NewChatMsg_t)(g_WeChatWinDllAddr + OS_NEW_CHAT_MSG);
- FreeChatMsg_t FreeChatMsg = (FreeChatMsg_t)(g_WeChatWinDllAddr + OS_FREE_CHAT_MSG);
- GetChatMgr_t GetChatMgr = (GetChatMgr_t)(g_WeChatWinDllAddr + OS_GET_CHAT_MGR);
- GetPreDownLoadMgr_t GetPreDownLoadMgr = (GetPreDownLoadMgr_t)(g_WeChatWinDllAddr + OS_GET_PRE_DOWNLOAD_MGR);
- PushAttachTask_t PushAttachTask = (PushAttachTask_t)(g_WeChatWinDllAddr + OS_PUSH_ATTACH_TASK);
- GetMgrByPrefixLocalId_t GetMgrByPrefixLocalId
- = (GetMgrByPrefixLocalId_t)(g_WeChatWinDllAddr + OS_GET_MGR_BY_PREFIX_LOCAL_ID);
-
- LARGE_INTEGER l;
- l.HighPart = dbIdx;
- l.LowPart = (DWORD)localId;
-
- char *buff = (char *)HeapAlloc(GetProcessHeap(), 0, 0x460);
- if (buff == nullptr) {
- LOG_ERROR("Failed to allocate memory.");
- return status;
- }
-
- QWORD pChatMsg = NewChatMsg((QWORD)buff);
- GetChatMgr();
- GetMgrByPrefixLocalId(l.QuadPart, pChatMsg);
-
- QWORD type = GET_QWORD(buff + 0x38);
-
- string save_path = "";
- string thumb_path = "";
-
- switch (type) {
- case 0x03: { // Image: extra
- save_path = extra;
- break;
- }
- case 0x3E:
- case 0x2B: { // Video: thumb
- thumb_path = thumb;
- save_path = fs::path(thumb).replace_extension("mp4").string();
- break;
- }
- case 0x31: { // File: extra
- save_path = extra;
- break;
- }
- default:
- break;
- }
-
- if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断
- return 0;
- }
-
- LOG_DEBUG("path: {}", save_path);
- // 创建父目录,由于路径来源于微信,不做检查
- fs::create_directory(fs::path(save_path).parent_path().string());
-
- int temp = 1;
- WxString *pSavePath = NewWxStringFromStr(save_path);
- WxString *pThumbPath = NewWxStringFromStr(thumb_path);
-
- memcpy(&buff[0x280], pThumbPath, sizeof(WxString));
- memcpy(&buff[0x2A0], pSavePath, sizeof(WxString));
- memcpy(&buff[0x40C], &temp, sizeof(temp));
-
- QWORD mgr = GetPreDownLoadMgr();
- status = (int)PushAttachTask(mgr, pChatMsg, 0, 1);
- FreeChatMsg(pChatMsg);
-
- return status;
-}
-
-string GetAudio(QWORD id, string dir)
-{
- string mp3path = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/");
- mp3path += to_string(id) + ".mp3";
- replace(mp3path.begin(), mp3path.end(), '\\', '/');
- if (fs::exists(mp3path)) { // 不重复下载
- return mp3path;
- }
-
- vector silk = GetAudioData(id);
- if (silk.size() == 0) {
- LOG_ERROR("Empty audio data.");
- return "";
- }
-
- Silk2Mp3(silk, mp3path, 24000);
-
- return mp3path;
-}
-
-string GetPCMAudio(uint64_t id, string dir, int32_t sr)
-{
- string pcmpath = (dir.back() == '\\' || dir.back() == '/') ? dir : (dir + "/");
- pcmpath += to_string(id) + ".pcm";
- replace(pcmpath.begin(), pcmpath.end(), '\\', '/');
- if (fs::exists(pcmpath)) { // 不重复下载
- return pcmpath;
- }
- vector pcm;
- vector silk = GetAudioData(id);
- if (silk.size() == 0) {
- LOG_ERROR("Empty audio data.");
- return "";
- }
-
- SilkDecode(silk, pcm, sr);
- errno_t err;
- FILE* fPCM;
- err = fopen_s(&fPCM, pcmpath.c_str(), "wb");
- if (err != 0) {
- printf("Error: could not open input file %s\n", pcmpath.c_str());
- exit(0);
- }
-
- fwrite(pcm.data(), sizeof(uint8_t), pcm.size(), fPCM);
- fclose(fPCM);
-
- return pcmpath;
-}
-
-
-OcrResult_t GetOcrResult(string path)
-{
- OcrResult_t ret = { -1, "" };
-#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复
- if (!fs::exists(path)) {
- LOG_ERROR("Can not find: {}", path);
- return ret;
- }
-
- GetOCRManager_t GetOCRManager = (GetOCRManager_t)(g_WeChatWinDllAddr + 0x1D6C3C0);
- DoOCRTask_t DoOCRTask = (DoOCRTask_t)(g_WeChatWinDllAddr + 0x2D10BC0);
-
- QWORD unk1 = 0, unk2 = 0, unused = 0;
- QWORD *pUnk1 = &unk1;
- QWORD *pUnk2 = &unk2;
- // 路径分隔符有要求,必须为 `\`
- wstring wsPath = String2Wstring(fs::path(path).make_preferred().string());
- WxString wxPath(wsPath);
- vector *pv = (vector *)HeapAlloc(GetProcessHeap(), 0, 0x20);
- RawVector_t *pRv = (RawVector_t *)pv;
- pRv->finish = pRv->start;
- char buff[0x98] = { 0 };
- memcpy(buff, &pRv->start, sizeof(QWORD));
-
- QWORD mgr = GetOCRManager();
- ret.status = (int)DoOCRTask(mgr, (QWORD)&wxPath, unused, (QWORD)buff, (QWORD)&pUnk1, (QWORD)&pUnk2);
-
- QWORD count = GET_QWORD(buff + 0x8);
- if (count > 0) {
- QWORD header = GET_QWORD(buff);
- for (QWORD i = 0; i < count; i++) {
- QWORD content = GET_QWORD(header);
- ret.result += Wstring2String(GET_WSTRING(content + 0x28));
- ret.result += "\n";
- header = content;
- }
- }
-#endif
- return ret;
-}
-
-int RevokeMsg(QWORD id)
-{
- int status = -1;
-#if 0 // 这个挺鸡肋的,因为自己发的消息没法直接获得 msgid,就这样吧
- QWORD localId;
- uint32_t dbIdx;
- if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) {
- LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id));
- return status;
- }
-#endif
- return status;
-}
-
-string GetLoginUrl()
-{
- LPVOID targetAddress = reinterpret_cast(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE;
-
- char *dataPtr = *reinterpret_cast(targetAddress); // 读取指针内容
- if (!dataPtr) {
- LOG_ERROR("Failed to get login url");
- return "error";
- }
-
- // 读取字符串内容
- std::string data(dataPtr, 22);
- return "http://weixin.qq.com/x/" + data;
-}
-
-int ReceiveTransfer(string wxid, string transferid, string transactionid)
-{
- // 别想了,这个不实现了
- return -1;
-}
diff --git a/WeChatFerry/spy/funcs.h b/WeChatFerry/spy/funcs.h
deleted file mode 100644
index cc0f89d..0000000
--- a/WeChatFerry/spy/funcs.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-#include "stdint.h"
-#include
-
-int IsLogin(void);
-std::string GetAudio(uint64_t id, std::string dir);
-std::string GetPCMAudio(uint64_t id, std::string dir, int32_t sr);
-std::string DecryptImage(std::string src, std::string dst);
-int RefreshPyq(uint64_t id);
-int DownloadAttach(uint64_t id, std::string thumb, std::string extra);
-int RevokeMsg(uint64_t id);
-OcrResult_t GetOcrResult(std::string path);
-string GetLoginUrl();
-int ReceiveTransfer(std::string wxid, std::string transferid, std::string transactionid);
diff --git a/WeChatFerry/spy/message_handler.cpp b/WeChatFerry/spy/message_handler.cpp
new file mode 100644
index 0000000..9537bf1
--- /dev/null
+++ b/WeChatFerry/spy/message_handler.cpp
@@ -0,0 +1,281 @@
+#include "message_handler.h"
+
+#include
+#include
+#include
+#include
+
+#include "framework.h"
+
+#include "account_manager.h"
+#include "log.hpp"
+#include "offsets.h"
+#include "pb_util.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "util.h"
+
+namespace message
+{
+
+namespace fs = std::filesystem;
+
+namespace OsLog = Offsets::Message::Log;
+namespace OsRecv = Offsets::Message::Receive;
+
+QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2)
+{
+ auto &handler = getInstance();
+ WxMsg_t wxMsg = {};
+ try {
+ wxMsg.id = util::get_qword(arg2 + OsRecv::ID);
+ wxMsg.type = util::get_dword(arg2 + OsRecv::TYPE);
+ wxMsg.is_self = util::get_dword(arg2 + OsRecv::SELF);
+ wxMsg.ts = util::get_dword(arg2 + OsRecv::TIMESTAMP);
+ wxMsg.content = util::get_str_by_wstr_addr(arg2 + OsRecv::CONTENT);
+ wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OsRecv::SIGN);
+ wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OsRecv::XML);
+ wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OsRecv::ROOMID);
+
+ if (wxMsg.roomid.find("@chatroom") != std::string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom
+ wxMsg.is_group = true;
+ wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OsRecv::WXID);
+ } else {
+ wxMsg.is_group = false;
+ wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : wxMsg.roomid;
+ }
+
+ fs::path thumb = util::get_str_by_wstr_addr(arg2 + OsRecv::THUMB);
+ if (!thumb.empty()) {
+ wxMsg.thumb = (account::get_home_path() / thumb).generic_string();
+ }
+
+ fs::path extra = util::get_str_by_wstr_addr(arg2 + OsRecv::EXTRA);
+ if (!extra.empty()) {
+ wxMsg.extra = (account::get_home_path() / extra).generic_string();
+ }
+ LOG_DEBUG("{}", wxMsg.content);
+ } catch (const std::exception &e) {
+ LOG_ERROR(util::gb2312_to_utf8(e.what()));
+ }
+
+ {
+ std::unique_lock lock(handler.mutex_);
+ handler.msgQueue_.push(wxMsg); // 推送到队列
+ }
+
+ handler.cv_.notify_all();
+ return handler.realRecvMsg(arg1, arg2);
+}
+
+QWORD Handler::PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
+ QWORD a10, QWORD a11, QWORD a12)
+{
+ auto &handler = getInstance();
+
+ QWORD p = handler.realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
+ if (p == 0 || p == 1) {
+ return p;
+ }
+
+ LOG_INFO("【WX】\n{}", util::gb2312_to_utf8((char *)p));
+ return p;
+}
+
+QWORD Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3)
+{
+ auto &handler = getInstance();
+ QWORD startAddr = *(QWORD *)(arg2 + OsRecv::PYQ_START);
+ QWORD endAddr = *(QWORD *)(arg2 + OsRecv::PYQ_END);
+
+ if (startAddr == 0) {
+ return 0;
+ }
+
+ while (startAddr < endAddr) {
+ WxMsg_t wxMsg;
+
+ wxMsg.type = 0x00;
+ wxMsg.is_self = false;
+ wxMsg.is_group = false;
+ wxMsg.id = util::get_qword(startAddr);
+ wxMsg.ts = util::get_dword(startAddr + OsRecv::PYQ_TS);
+ wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_XML);
+ wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_SENDER);
+ wxMsg.content = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_CONTENT);
+
+ {
+ std::unique_lock lock(handler.mutex_);
+ handler.msgQueue_.push(wxMsg);
+ }
+
+ handler.cv_.notify_all();
+ startAddr += 0x1618;
+ }
+
+ return handler.realRecvPyq(arg1, arg2, arg3);
+}
+
+Handler &Handler::getInstance()
+{
+ static Handler instance;
+ return instance;
+}
+
+Handler::Handler()
+{
+ isLogging = false;
+ isListeningMsg = false;
+ isListeningPyq = false;
+}
+
+Handler::~Handler()
+{
+ DisableLog();
+ UnListenMsg();
+ UnListenPyq();
+}
+
+MsgTypes_t Handler::GetMsgTypes()
+{
+ return { { 0x00, "朋友圈消息" },
+ { 0x01, "文字" },
+ { 0x03, "图片" },
+ { 0x22, "语音" },
+ { 0x25, "好友确认" },
+ { 0x28, "POSSIBLEFRIEND_MSG" },
+ { 0x2A, "名片" },
+ { 0x2B, "视频" },
+ { 0x2F, "石头剪刀布 | 表情图片" },
+ { 0x30, "位置" },
+ { 0x31, "共享实时位置、文件、转账、链接" },
+ { 0x32, "VOIPMSG" },
+ { 0x33, "微信初始化" },
+ { 0x34, "VOIPNOTIFY" },
+ { 0x35, "VOIPINVITE" },
+ { 0x3E, "小视频" },
+ { 0x42, "微信红包" },
+ { 0x270F, "SYSNOTICE" },
+ { 0x2710, "红包、系统消息" },
+ { 0x2712, "撤回消息" },
+ { 0x100031, "搜狗表情" },
+ { 0x1000031, "链接" },
+ { 0x1A000031, "微信红包" },
+ { 0x20010031, "红包封面" },
+ { 0x2D000031, "视频号视频" },
+ { 0x2E000031, "视频号名片" },
+ { 0x31000031, "引用消息" },
+ { 0x37000031, "拍一拍" },
+ { 0x3A000031, "视频号直播" },
+ { 0x3A100031, "商品链接" },
+ { 0x3A200031, "视频号直播" },
+ { 0x3E000031, "音乐链接" },
+ { 0x41000031, "文件" } };
+}
+
+std::optional Handler::popMessage()
+{
+ std::lock_guard lock(mutex_);
+ if (msgQueue_.empty()) {
+ return std::nullopt;
+ }
+ WxMsg_t msg = std::move(msgQueue_.front());
+ msgQueue_.pop();
+ return msg;
+}
+
+int Handler::EnableLog()
+{
+ if (isLogging) return 1;
+
+ pLogLevel = reinterpret_cast(Spy::WeChatDll.load() + OsLog::LEVEL);
+ funcWxLog = Spy::getFunction(OsLog::CALL);
+
+ if (InitializeHook() != MH_OK) return -1;
+ if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast(&realWxLog)) != MH_OK) return -2;
+ if (MH_EnableHook(funcWxLog) != MH_OK) return -3;
+
+ *pLogLevel = 0;
+ isLogging = true;
+ return 0;
+}
+
+int Handler::DisableLog()
+{
+ if (!isLogging) return 1;
+ if (MH_DisableHook(funcWxLog) != MH_OK) return -1;
+ if (UninitializeHook() != MH_OK) return -2;
+ *pLogLevel = 6;
+ isLogging = false;
+ return 0;
+}
+
+int Handler::ListenMsg()
+{
+ if (isListeningMsg) return 1;
+
+ funcRecvMsg = Spy::getFunction(OsRecv::CALL);
+ if (InitializeHook() != MH_OK) return -1;
+ if (MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast(&realRecvMsg)) != MH_OK) return -1;
+ if (MH_EnableHook(funcRecvMsg) != MH_OK) return -1;
+
+ isListeningMsg = true;
+ return 0;
+}
+
+int Handler::UnListenMsg()
+{
+ if (!isListeningMsg) return 1;
+ if (MH_DisableHook(funcRecvMsg) != MH_OK) return -1;
+ if (UninitializeHook() != MH_OK) return -1;
+ isListeningMsg = false;
+ return 0;
+}
+
+int Handler::ListenPyq()
+{
+ if (isListeningPyq) return 1;
+
+ funcRecvPyq = Spy::getFunction(OsRecv::PYQ_CALL);
+ if (InitializeHook() != MH_OK) return -1;
+ if (MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast(&realRecvPyq)) != MH_OK) return -1;
+ if (MH_EnableHook(funcRecvPyq) != MH_OK) return -1;
+
+ isListeningPyq = true;
+ return 0;
+}
+
+int Handler::UnListenPyq()
+{
+ if (!isListeningPyq) return 1;
+ if (MH_DisableHook(funcRecvPyq) != MH_OK) return -1;
+ if (UninitializeHook() != MH_OK) return -1;
+ isListeningPyq = false;
+ return 0;
+}
+
+MH_STATUS Handler::InitializeHook()
+{
+ if (isMH_Initialized) return MH_OK;
+ MH_STATUS status = MH_Initialize();
+ if (status == MH_OK) isMH_Initialized = true;
+ return status;
+}
+
+MH_STATUS Handler::UninitializeHook()
+{
+ if (!isMH_Initialized || isLogging || isListeningMsg || isListeningPyq) return MH_OK;
+ MH_STATUS status = MH_Uninitialize();
+ if (status == MH_OK) isMH_Initialized = false;
+ return status;
+}
+
+bool Handler::rpc_get_msg_types(uint8_t *out, size_t *len)
+{
+ MsgTypes_t types = GetMsgTypes();
+ return fill_response(out, len, [&](Response &rsp) {
+ rsp.msg.types.types.funcs.encode = encode_types;
+ rsp.msg.types.types.arg = &types;
+ });
+}
+}
diff --git a/WeChatFerry/spy/message_handler.h b/WeChatFerry/spy/message_handler.h
new file mode 100644
index 0000000..53e3d1a
--- /dev/null
+++ b/WeChatFerry/spy/message_handler.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include "MinHook.h"
+
+#include "pb_types.h"
+#include "spy_types.h"
+
+namespace message
+{
+
+class Handler
+{
+public:
+ static Handler &getInstance();
+
+ // 0: 成功, -1: 失败, 1: 已经开启
+ int EnableLog();
+ int DisableLog();
+ int ListenPyq();
+ int UnListenPyq();
+ int ListenMsg();
+ int UnListenMsg();
+
+ MsgTypes_t GetMsgTypes();
+
+ bool isLoggingEnabled() const { return isLogging.load(); }
+ bool isMessageListening() const { return isListeningMsg.load(); }
+ bool isPyqListening() const { return isListeningPyq.load(); }
+
+ std::optional popMessage();
+ std::condition_variable &getConditionVariable() { return cv_; };
+ std::mutex &getMutex() { return mutex_; };
+
+ bool rpc_get_msg_types(uint8_t *out, size_t *len);
+
+private:
+ Handler();
+ ~Handler();
+
+ mutable std::mutex mutex_;
+ std::condition_variable cv_;
+ std::queue msgQueue_;
+
+ std::atomic isLogging { false };
+ std::atomic isListeningMsg { false };
+ std::atomic isListeningPyq { false };
+
+ using funcRecvMsg_t = QWORD (*)(QWORD, QWORD);
+ using funcWxLog_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
+ using funcRecvPyq_t = QWORD (*)(QWORD, QWORD, QWORD);
+
+ uint32_t *pLogLevel;
+ funcWxLog_t funcWxLog, realWxLog;
+ funcRecvMsg_t funcRecvMsg, realRecvMsg;
+ funcRecvPyq_t funcRecvPyq, realRecvPyq;
+
+ bool isMH_Initialized { false };
+
+ MH_STATUS InitializeHook();
+ MH_STATUS UninitializeHook();
+
+ static QWORD DispatchMsg(QWORD arg1, QWORD arg2);
+ static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
+ QWORD a10, QWORD a11, QWORD a12);
+ static QWORD DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3);
+};
+
+} // namespace message
diff --git a/WeChatFerry/spy/message_sender.cpp b/WeChatFerry/spy/message_sender.cpp
new file mode 100644
index 0000000..2cbfac3
--- /dev/null
+++ b/WeChatFerry/spy/message_sender.cpp
@@ -0,0 +1,366 @@
+#include "message_sender.h"
+
+#include
+#include
+
+#include "account_manager.h"
+#include "database_executor.h"
+#include "log.hpp"
+#include "offsets.h"
+#include "rpc_helper.h"
+#include "spy.h"
+#include "spy_types.h"
+#include "util.h"
+
+namespace message
+{
+
+namespace OsSend = Offsets::Message::Send;
+
+Sender &Sender::get_instance()
+{
+ static Sender instance;
+ return instance;
+}
+
+Sender::Sender()
+{
+ func_new_chat_msg = Spy::getFunction(OsSend::INSTANCE);
+ func_free_chat_msg = Spy::getFunction(OsSend::FREE);
+ func_send_msg_mgr = Spy::getFunction(OsSend::MGR);
+ func_send_text = Spy::getFunction(OsSend::TEXT);
+ func_send_image = Spy::getFunction(OsSend::IMAGE);
+ func_get_app_mgr = Spy::getFunction(OsSend::APP_MGR);
+ func_send_file = Spy::getFunction(OsSend::FILE);
+ func_new_mmreader = Spy::getFunction(OsSend::NEW_MM_READER);
+ func_free_mmreader = Spy::getFunction(OsSend::FREE_MM_READER);
+ func_send_rich_text = Spy::getFunction(OsSend::RICH_TEXT);
+ func_send_pat = Spy::getFunction(OsSend::PAT);
+ func_forward = Spy::getFunction(OsSend::FORWARD);
+ func_get_emotion_mgr = Spy::getFunction(OsSend::EMOTION_MGR);
+ func_send_emotion = Spy::getFunction(OsSend::EMOTION);
+ func_send_xml = Spy::getFunction(OsSend::XML);
+ func_xml_buf_sign = Spy::getFunction(OsSend::XML_BUF_SIGN);
+}
+
+void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids)
+{
+ util::WxStringHolder holderMsg(msg);
+ util::WxStringHolder holderWxid(wxid);
+
+ auto wxAtWxids = util::parse_wxids(at_wxids).wxWxids;
+ QWORD pWxAtWxids = wxAtWxids.empty() ? 0 : reinterpret_cast(&wxAtWxids);
+
+ char buffer[1104] = { 0 };
+ func_send_msg_mgr();
+ func_send_text(reinterpret_cast(&buffer), &holderWxid.wx, &holderMsg.wx, pWxAtWxids, 1, 1, 0, 0);
+ func_free_chat_msg(reinterpret_cast(&buffer));
+}
+
+void Sender::send_image(const std::string &wxid, const std::string &path)
+{
+ util::WxStringHolder holderWxid(wxid);
+ util::WxStringHolder holderPath(path);
+
+ char msg[1192] = { 0 };
+ char msgTmp[1192] = { 0 };
+ QWORD *flag[10] = { 0 };
+
+ QWORD tmp1 = 1, tmp2 = 0, tmp3 = 0;
+ QWORD pMsgTmp = func_new_chat_msg((QWORD)(&msgTmp));
+ flag[0] = reinterpret_cast(tmp1);
+ flag[1] = reinterpret_cast(pMsgTmp);
+ flag[8] = &tmp2;
+ flag[9] = &tmp3;
+
+ QWORD pMsg = func_new_chat_msg((QWORD)(&msg));
+ QWORD sendMgr = func_send_msg_mgr();
+ func_send_image(sendMgr, pMsg, &holderWxid.wx, &holderPath.wx, reinterpret_cast(&flag));
+
+ func_free_chat_msg(pMsg);
+ func_free_chat_msg(pMsgTmp);
+}
+
+void Sender::send_file(const std::string &wxid, const std::string &path)
+{
+ WxString *wxWxid = util::CreateWxString(wxid);
+ WxString *wxPath = util::CreateWxString(path);
+ if (!wxWxid || !wxPath) {
+ util::FreeWxString(wxWxid);
+ util::FreeWxString(wxPath);
+ return;
+ }
+
+ char *chat_msg = reinterpret_cast(util::AllocFromHeap(0x460));
+ if (!chat_msg) {
+ util::FreeWxString(wxWxid);
+ util::FreeWxString(wxPath);
+ return;
+ }
+
+ QWORD *tmp1 = util::AllocBuffer(4);
+ QWORD *tmp2 = util::AllocBuffer(4);
+ QWORD *tmp3 = util::AllocBuffer(4);
+ if (!tmp1 || !tmp2 || !tmp3) {
+ func_free_chat_msg(reinterpret_cast(chat_msg));
+ util::FreeBuffer(chat_msg);
+ util::FreeBuffer(tmp1);
+ util::FreeBuffer(tmp2);
+ util::FreeBuffer(tmp3);
+ util::FreeWxString(wxWxid);
+ util::FreeWxString(wxPath);
+ return;
+ }
+
+ QWORD app_mgr = func_get_app_mgr();
+ func_send_file(app_mgr, chat_msg, wxWxid, wxPath, 1, tmp1, 0, tmp2, 0, tmp3, 0, 0xC);
+ func_free_chat_msg(reinterpret_cast(chat_msg));
+
+ util::FreeBuffer(chat_msg);
+ util::FreeBuffer(tmp1);
+ util::FreeBuffer(tmp2);
+ util::FreeBuffer(tmp3);
+ util::FreeWxString(wxWxid);
+ util::FreeWxString(wxPath);
+}
+
+void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type)
+{
+#if 0
+ std::unique_ptr buff(new char[0x500]());
+ std::unique_ptr buff2(new char[0x500]());
+ char nullBuf[0x1C] = { 0 };
+
+ func_new_chat_msg(reinterpret_cast(buff.get()));
+ func_new_chat_msg(reinterpret_cast(buff2.get()));
+
+ QWORD sbuf[4] = { 0, 0, 0, 0 };
+ QWORD sign = func_xml_buf_sign(reinterpret_cast(buff2.get()), reinterpret_cast(sbuf), 0x1);
+
+ auto wxReceiver = new_wx_string(receiver);
+ auto wxXml = new_wx_string(xml);
+ auto wxPath = new_wx_string(path);
+ auto wxSender = new_wx_string(account::get_self_wxid());
+
+ func_send_xml(reinterpret_cast(buff.get()), reinterpret_cast(wxSender.get()),
+ reinterpret_cast