diff --git a/README.MD b/README.MD index f2f79fa..46ee088 100644 --- a/README.MD +++ b/README.MD @@ -168,7 +168,7 @@ sdk.WxDestroySDK() ### 调试日志 ```c - DbgMsg("ListenMessage"); // 封装的 OutputDebugString + util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString OutputDebugString(L"ListenMessage\n"); MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0); ``` 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..2c45632 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,208 @@ 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); + + OutputDebugStringA(buffer.data()); } -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); } + +std::vector parse_wxids(const std::string &wxids) +{ + std::vector wx_members; + std::wstringstream wss(s2w(wxids)); + std::wstring wstr; + while (getline(wss, wstr, L',')) { + wx_members.emplace_back(wstr); } - - 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 wx_members; } + +} // namespace util diff --git a/WeChatFerry/com/util.h b/WeChatFerry/com/util.h index 8f252df..de61c80 100644 --- a/WeChatFerry/com/util.h +++ b/WeChatFerry/com/util.h @@ -1,41 +1,76 @@ #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)); } + +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); + +std::vector parse_wxids(const std::string &wxids); + +} // namespace util 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..d990854 100644 --- a/WeChatFerry/sdk/injector.cpp +++ b/WeChatFerry/sdk/injector.cpp @@ -1,112 +1,108 @@ -#include "framework.h" -#include "psapi.h" -#include -#include +#include "injector.h" -#include "injector.h" -#include "util.h" +#include + +#include "psapi.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); + MessageBoxA(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)) { + MessageBoxA(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) { + MessageBoxA(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) { + MessageBoxA(NULL, "获取 kernel32 失败", "eject_dll", 0); + return false; } FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread"); if (!libAddr) { - MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0); - return NULL; + MessageBoxA(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) { + MessageBoxA(NULL, "FreeLibrary 调用失败!", "eject_dll", 0); return false; } @@ -116,38 +112,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) { + MessageBoxA(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 +147,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) { + MessageBoxA(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); + MessageBoxA(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..e1dbdc8 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,57 @@ #include #include #include + +#include "framework.h" #include #include "injector.h" -#include "sdk.h" #include "util.h" -static BOOL injected = false; +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"; + +static std::optional read_disclaimer_text(const std::string &path) { - std::ifstream file(filePath, std::ios::binary); + std::ifstream file(path, std::ios::binary); if (!file.is_open()) { return std::nullopt; // 文件打开失败 } - std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - return String2Wstring(content); + return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); } -static bool ShowDisclaimer() +static bool show_disclaimer() { - if (std::filesystem::exists(DISCLAIMER_FILE)) { + if (std::filesystem::exists(DISCLAIMER_FLAG)) { return true; } - std::optional disclaimerTextOpt = ReadDisclaimerText(DISCLAIMER_TEXT_FILE); - if (!disclaimerTextOpt.has_value() || disclaimerTextOpt->empty()) { - MessageBox(NULL, L"免责声明文件为空或读取失败。", L"错误", MB_ICONERROR); + auto disclaimerTextOpt = read_disclaimer_text(std::string(DISCLAIMER_TEXT_FILE)); + if (!disclaimerTextOpt || disclaimerTextOpt->empty()) { + MessageBoxA(NULL, "免责声明文件为空或读取失败。", "错误", MB_ICONERROR); return false; } - std::wstring disclaimerText = *disclaimerTextOpt; - - int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2); - + int result + = MessageBoxA(NULL, disclaimerTextOpt->c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2); if (result == IDCANCEL) { - MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION); + MessageBoxA(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION); return false; } - std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc); + std::ofstream flagFile(std::string(DISCLAIMER_FLAG), std::ios::out | std::ios::trunc); if (!flagFile) { - MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR); + MessageBoxA(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR); return false; } flagFile << "User accepted the license agreement."; @@ -62,82 +65,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); + char buffer[MAX_PATH] = { 0 }; + GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH); std::filesystem::path path(buffer); - path.remove_filename(); // 移除文件名,保留目录路径 - + path.remove_filename(); // 只保留目录路径 path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL; if (!std::filesystem::exists(path)) { - MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR); - return L""; + MessageBoxA(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); + MessageBoxA(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); + MessageBoxA(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", std::filesystem::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..3f601af 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..dea6857 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/chatroom_manager.cpp b/WeChatFerry/spy/chatroom_manager.cpp new file mode 100644 index 0000000..4ce74ce --- /dev/null +++ b/WeChatFerry/spy/chatroom_manager.cpp @@ -0,0 +1,115 @@ +#pragma execution_character_set("utf-8") + +#include "chatroom_manager.h" +#include "log.hpp" +#include "pb_util.h" +#include "rpc_helper.h" +#include "util.h" + +using namespace std; +extern QWORD g_WeChatWinDllAddr; + +namespace chatroom +{ +#define OS_GET_CHATROOM_MGR 0x1B83BD0 +#define OS_ADD_MEMBERS 0x2155100 +#define OS_DELETE_MEMBERS 0x2155740 +#define OS_INVITE_MEMBERS 0x2154AE0 + +using get_chatroom_mgr_t = QWORD (*)(); +using add_member_to_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD); +using del_member_from_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD); +using invite_member_to_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD); + +static vector parse_wxids(const string &wxids) +{ + vector wx_members; + wstringstream wss(util::s2w(wxids)); + wstring wstr; + while (getline(wss, wstr, L',')) { + wx_members.emplace_back(wstr); + } + return wx_members; +} + +int add_chatroom_member(const string &roomid, const string &wxids) +{ + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return -1; + } + + get_chatroom_mgr_t get_chatroom_mgr + = reinterpret_cast(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); + add_member_to_chatroom_t add_members + = reinterpret_cast(g_WeChatWinDllAddr + OS_ADD_MEMBERS); + + vector wx_members = parse_wxids(wxids); + auto wx_roomid = util::new_wx_string(roomid); + QWORD p_members = reinterpret_cast(&wx_members.front()); + + return static_cast(add_members(get_chatroom_mgr(), p_members, reinterpret_cast(wx_roomid.get()), 0)); +} + +int del_chatroom_member(const string &roomid, const string &wxids) +{ + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return -1; + } + + get_chatroom_mgr_t get_chatroom_mgr + = reinterpret_cast(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); + del_member_from_chatroom_t del_members + = reinterpret_cast(g_WeChatWinDllAddr + OS_DELETE_MEMBERS); + + vector wx_members = parse_wxids(wxids); + auto wx_roomid = util::new_wx_string(roomid); + QWORD p_members = reinterpret_cast(&wx_members.front()); + + return static_cast(del_members(get_chatroom_mgr(), p_members, reinterpret_cast(wx_roomid.get()))); +} + +int invite_chatroom_member(const string &roomid, const string &wxids) +{ + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return -1; + } + + invite_member_to_chatroom_t invite_members + = reinterpret_cast(g_WeChatWinDllAddr + OS_INVITE_MEMBERS); + + vector wx_members = parse_wxids(wxids); + auto wx_roomid = util::new_wx_string(roomid); + QWORD p_members = reinterpret_cast(&wx_members.front()); + + return static_cast(invite_members(reinterpret_cast(wx_roomid.get()->wptr), p_members, + reinterpret_cast(wx_roomid.get()), 0)); +} + +bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + const std::string wxids = m.wxids; + const std::string roomid = m.roomid; + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = add_chatroom_member(roomid, wxids); }); +} + +bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + const std::string wxids = m.wxids; + const std::string roomid = m.roomid; + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = del_chatroom_member(roomid, wxids); }); +} + +bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len) +{ + const std::string wxids = m.wxids; + const std::string roomid = m.roomid; + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = invite_chatroom_member(roomid, wxids); }); +} + +} // 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 deleted file mode 100644 index 0950d45..0000000 --- a/WeChatFerry/spy/chatroom_mgmt.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "framework.h" -#include -#include - -#include "chatroom_mgmt.h" -#include "log.hpp" -#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 - -typedef QWORD (*GetChatRoomMgr_t)(); -typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*DelMemberFromChatRoom_t)(QWORD, QWORD, QWORD); -typedef QWORD (*InviteMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD); - -int AddChatroomMember(string roomid, string wxids) -{ - int status = -1; - - if (roomid.empty() || wxids.empty()) { - LOG_ERROR("Empty roomid or wxids."); - return status; - } - - GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); - AddMemberToChatRoom_t AddMembers = (AddMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_ADD_MEMBERS); - - vector vMembers; - vector vWxMembers; - wstringstream wss(String2Wstring(wxids)); - while (wss.good()) { - wstring wstr; - getline(wss, wstr, L','); - vMembers.push_back(wstr); - WxString wxMember(vMembers.back()); - vWxMembers.push_back(wxMember); - } - - QWORD temp[2] = { 0 }; - WxString *pWxRoomid = NewWxStringFromStr(roomid); - QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; - - QWORD mgr = GetChatRoomMgr(); - status = (int)AddMembers(mgr, pMembers, (QWORD)pWxRoomid, (QWORD)temp); - return status; -} - -int DelChatroomMember(string roomid, string wxids) -{ - int status = -1; - - if (roomid.empty() || wxids.empty()) { - LOG_ERROR("Empty roomid or wxids."); - return status; - } - - GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); - DelMemberFromChatRoom_t DelMembers = (DelMemberFromChatRoom_t)(g_WeChatWinDllAddr + OS_DELETE_MEMBERS); - - vector vMembers; - vector vWxMembers; - wstringstream wss(String2Wstring(wxids)); - while (wss.good()) { - wstring wstr; - getline(wss, wstr, L','); - vMembers.push_back(wstr); - WxString wxMember(vMembers.back()); - vWxMembers.push_back(wxMember); - } - - WxString *pWxRoomid = NewWxStringFromStr(roomid); - QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; - - QWORD mgr = GetChatRoomMgr(); - status = (int)DelMembers(mgr, pMembers, (QWORD)pWxRoomid); - return status; -} - -int InviteChatroomMember(string roomid, string wxids) -{ - int status = -1; - - if (roomid.empty() || wxids.empty()) { - LOG_ERROR("Empty roomid or wxids."); - return status; - } - - InviteMemberToChatRoom_t InviteMembers = (InviteMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_INVITE_MEMBERS); - - vector vMembers; - vector vWxMembers; - wstringstream wss(String2Wstring(wxids)); - while (wss.good()) { - wstring wstr; - getline(wss, wstr, L','); - vMembers.push_back(wstr); - WxString wxMember(vMembers.back()); - vWxMembers.push_back(wxMember); - } - QWORD temp[2] = { 0 }; - wstring wsRoomid = String2Wstring(roomid); - WxString *pWxRoomid = NewWxStringFromWstr(wsRoomid); - QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; - - 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..0e1ecb3 --- /dev/null +++ b/WeChatFerry/spy/contact_manager.cpp @@ -0,0 +1,224 @@ +#pragma execution_character_set("utf-8") + +#include "contact_manager.h" + +#include "log.hpp" +#include "pb_util.h" +#include "rpc_helper.h" +#include "util.h" + +using namespace std; + +extern QWORD g_WeChatWinDllAddr; + +namespace contact +{ +#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 + +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; + get_contact_mgr_t func_get_contact_mgr + = reinterpret_cast(g_WeChatWinDllAddr + OS_GET_CONTACT_MGR); + get_contact_list_t func_get_contact_list + = reinterpret_cast(g_WeChatWinDllAddr + OS_GET_CONTACT_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 + OS_CONTACT_BIN); + QWORD lenbin = util::get_dword(pstart + OS_CONTACT_BIN_LEN); + + cnt.wxid = util::get_str_by_wstr_addr(pstart + OS_CONTACT_WXID); + cnt.code = util::get_str_by_wstr_addr(pstart + OS_CONTACT_CODE); + cnt.remark = util::get_str_by_wstr_addr(pstart + OS_CONTACT_REMARK); + cnt.name = util::get_str_by_wstr_addr(pstart + OS_CONTACT_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 + OS_CONTACT_GENDER)); + + contacts.push_back(cnt); + pstart += OS_CONTACT_STEP; + } + + return contacts; +} + +int accept_new_friend(const std::string &v3, const std::string &v4, int scene) +{ + int success = -1; +#if 0 + DWORD accept_new_friend_call1 = g_WeChatWinDllAddr + g_WxCalls.anf.call1; + DWORD accept_new_friend_call2 = g_WeChatWinDllAddr + g_WxCalls.anf.call2; + DWORD accept_new_friend_call3 = g_WeChatWinDllAddr + g_WxCalls.anf.call3; + DWORD accept_new_friend_call4 = g_WeChatWinDllAddr + g_WxCalls.anf.call4; + + char buffer[0x40] = { 0 }; + char nullbuffer[0x3CC] = { 0 }; + + LOG_DEBUG("\nv3: {}\nv4: {}\nscene: {}", v3, v4, scene); + + wstring ws_v3 = util::s2w(v3); + wstring ws_v4 = util::s2w(v4); + WxString wx_v3(ws_v3); + WxString wx_v4(ws_v4); + + __asm { + pushad; + pushfd; + lea ecx, buffer; + call accept_new_friend_call1; + mov esi, 0x0; + mov edi, scene; + push esi; + push edi; + sub esp, 0x14; + mov ecx, esp; + lea eax, wx_v4; + push eax; + call accept_new_friend_call2; + sub esp, 0x8; + push 0x0; + lea eax, nullbuffer; + push eax; + lea eax, wx_v3; + push eax; + lea ecx, buffer; + call accept_new_friend_call3; + mov success, eax; + lea ecx, buffer; + call accept_new_friend_call4; + popfd; + popad; + } +#endif + return success; // 成功返回 1 +} + +RpcContact_t get_contact_by_wxid(const string &wxid) +{ + RpcContact_t contact; +#if 0 + char buff[0x440] = { 0 }; + wstring ws_wxid = util::s2w(wxid); + WxString pri(ws_wxid); + + 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 = util::get_str_by_wstr_addr(reinterpret_cast(buff) + g_WxCalls.contact.wxCode); + contact.remark = util::get_str_by_wstr_addr(reinterpret_cast(buff) + g_WxCalls.contact.wxRemark); + contact.name = util::get_str_by_wstr_addr(reinterpret_cast(buff) + g_WxCalls.contact.wxName); + contact.gender = util::get_dword(reinterpret_cast(buff) + 0x148); + + __asm { + PUSHAD + PUSHFD + LEA ECX,buff + CALL free_contact_addr + POPFD + POPAD + } +#endif + return contact; +} + +bool rpc_get_contacts(uint8_t *out, size_t *len) +{ + return fill_response(out, len, [](Response &rsp) { + vector contacts = get_contacts(); + 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) +{ + return fill_response(out, len, [&](Response &rsp) { + vector contacts = { get_contact_by_wxid(wxid) }; + 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; + const string 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..bd5a7f8 --- /dev/null +++ b/WeChatFerry/spy/database_executor.cpp @@ -0,0 +1,292 @@ +#include "database_executor.h" + +#include +#include + +#include "log.hpp" +#include "pb_util.h" +#include "rpc_helper.h" +#include "sqlite3.h" +#include "util.h" + +extern UINT64 g_WeChatWinDllAddr; + +namespace db +{ +#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 + +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 + OFFSET_DB_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(g_WeChatWinDllAddr + OFFSET_DB_INSTANCE); + + get_db_handle(db_instance_addr, OFFSET_DB_MICROMSG); // MicroMsg.db + get_db_handle(db_instance_addr, OFFSET_DB_CHAT_MSG); // ChatMsg.db + get_db_handle(db_instance_addr, OFFSET_DB_MISC); // Misc.db + get_db_handle(db_instance_addr, OFFSET_DB_EMOTION); // Emotion.db + get_db_handle(db_instance_addr, OFFSET_DB_MEDIA); // Media.db + get_db_handle(db_instance_addr, OFFSET_DB_FUNCTION_MSG); // Function.db + + get_msg_db_handle(util::get_qword(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR)); // 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';"; + Sqlite3_exec p_sqlite3_exec = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_EXEC_OFFSET); + 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; + + Sqlite3_prepare func_prepare = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_PREPARE_OFFSET); + Sqlite3_step func_step = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_STEP_OFFSET); + Sqlite3_column_count func_column_count + = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_COLUMN_COUNT_OFFSET); + Sqlite3_column_name func_column_name + = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_COLUMN_NAME_OFFSET); + Sqlite3_column_type func_column_type + = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_COLUMN_TYPE_OFFSET); + Sqlite3_column_blob func_column_blob + = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_COLUMN_BLOB_OFFSET); + Sqlite3_column_bytes func_column_bytes + = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_COLUMN_BYTES_OFFSET); + Sqlite3_finalize func_finalize = reinterpret_cast(g_WeChatWinDllAddr + SQLITE3_FINALIZE_OFFSET); + + 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(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR); + 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(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR); + 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) +{ + return fill_response(out, len, [&](Response &rsp) { + DbNames_t names = get_db_names(); + 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) +{ + return fill_response(out, len, [&](Response &rsp) { + DbTables_t tables = get_db_tables(db); + 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); + return fill_response(out, len, [&](Response &rsp) { + DbRows_t rows = exec_db_query(db, sql); + 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/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..3889c09 --- /dev/null +++ b/WeChatFerry/spy/message_handler.cpp @@ -0,0 +1,280 @@ +#include "message_handler.h" + +#include +#include +#include + +#include "framework.h" + +#include "log.hpp" +#include "pb_util.h" +#include "rpc_helper.h" +#include "userinfo_manager.h" +#include "util.h" + +extern QWORD g_WeChatWinDllAddr; + +#define OS_RECV_MSG_ID 0x30 +#define OS_RECV_MSG_TYPE 0x38 +#define OS_RECV_MSG_SELF 0x3C +#define OS_RECV_MSG_TS 0x44 +#define OS_RECV_MSG_ROOMID 0x48 +#define OS_RECV_MSG_CONTENT 0x88 +#define OS_RECV_MSG_WXID 0x240 +#define OS_RECV_MSG_SIGN 0x260 +#define OS_RECV_MSG_THUMB 0x280 +#define OS_RECV_MSG_EXTRA 0x2A0 +#define OS_RECV_MSG_XML 0x308 +#define OS_RECV_MSG_CALL 0x213ED90 +#define OS_PYQ_MSG_START 0x30 +#define OS_PYQ_MSG_END 0x38 +#define OS_PYQ_MSG_TS 0x38 +#define OS_PYQ_MSG_XML 0x9B8 +#define OS_PYQ_MSG_SENDER 0x18 +#define OS_PYQ_MSG_CONTENT 0x48 +#define OS_PYQ_MSG_CALL 0x2E42C90 +#define OS_WXLOG 0x2613D20 + +namespace message +{ +QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2) +{ + auto &handler = getInstance(); + WxMsg_t wxMsg = {}; + try { + wxMsg.id = util::get_qword(arg2 + OS_RECV_MSG_ID); + wxMsg.type = util::get_dword(arg2 + OS_RECV_MSG_TYPE); + wxMsg.is_self = util::get_dword(arg2 + OS_RECV_MSG_SELF); + wxMsg.ts = util::get_dword(arg2 + OS_RECV_MSG_TS); + wxMsg.content = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_CONTENT); + wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_SIGN); + wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_XML); + wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_ROOMID); + + if (wxMsg.roomid.find("@chatroom") != std::string::npos) { + wxMsg.is_group = true; + wxMsg.sender + = wxMsg.is_self ? userinfo::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_WXID); + } else { + wxMsg.is_group = false; + wxMsg.sender = wxMsg.is_self ? userinfo::get_self_wxid() : wxMsg.roomid; + } + } 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; +} + +void Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3) +{ + auto &handler = getInstance(); + QWORD startAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_START); + QWORD endAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_END); + + if (startAddr == 0) { + return; + } + + 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 + OS_PYQ_MSG_TS); + wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_XML); + wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_SENDER); + wxMsg.content = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_CONTENT); + + { + std::unique_lock lock(handler.mutex_); + handler.msgQueue_.push(wxMsg); + } + + handler.cv_.notify_all(); + startAddr += 0x1618; + } +} + +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; + + funcWxLog = reinterpret_cast(g_WeChatWinDllAddr + OS_WXLOG); + + if (InitializeHook() != MH_OK) return -1; + if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast(&realWxLog)) != MH_OK) return -1; + if (MH_EnableHook(funcWxLog) != MH_OK) return -1; + + isLogging = true; + return 0; +} + +int Handler::DisableLog() +{ + if (!isLogging) return 1; + if (MH_DisableHook(funcWxLog) != MH_OK) return -1; + if (UninitializeHook() != MH_OK) return -1; + isLogging = false; + return 0; +} + +int Handler::ListenMsg() +{ + if (isListeningMsg) return 1; + + funcRecvMsg = reinterpret_cast(g_WeChatWinDllAddr + OS_RECV_MSG_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 = reinterpret_cast(g_WeChatWinDllAddr + OS_PYQ_MSG_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) +{ + return fill_response(out, len, [&](Response &rsp) { + MsgTypes_t types = GetMsgTypes(); + 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..c4227ec --- /dev/null +++ b/WeChatFerry/spy/message_handler.h @@ -0,0 +1,73 @@ +#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); + + 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 void 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..c1cb7c8 --- /dev/null +++ b/WeChatFerry/spy/message_sender.cpp @@ -0,0 +1,363 @@ +#include "message_sender.h" + +#include +#include + +#include "database_executor.h" +#include "log.hpp" +#include "rpc_helper.h" +#include "spy_types.h" +#include "userinfo_manager.h" +#include "util.h" + +extern QWORD g_WeChatWinDllAddr; + +#define OS_NEW 0x1B5E140 +#define OS_FREE 0x1B55850 +#define OS_SEND_MSG_MGR 0x1B53FD0 +#define OS_SEND_TEXT 0x22C6B60 +#define OS_SEND_IMAGE 0x22BC2F0 +#define OS_GET_APP_MSG_MGR 0x1B58F70 +#define OS_SEND_FILE 0x20D0230 +#define OS_RTM_NEW 0x1B5D690 +#define OS_RTM_FREE 0x1B5CA60 +#define OS_SEND_RICH_TEXT 0x20DA210 +#define OS_SEND_PAT_MSG 0x2CAEC00 +#define OS_FORWARD_MSG 0x22C60E0 +#define OS_GET_EMOTION_MGR 0x1BCEF10 +#define OS_SEND_EMOTION 0x21B52D5 +#define OS_XML_BUFSIGN 0x24F0D70 +#define OS_SEND_XML 0x20CF360 + +namespace message +{ + +Sender &Sender::get_instance() +{ + static Sender instance; + return instance; +} + +Sender::Sender() +{ + func_new = reinterpret_cast(g_WeChatWinDllAddr + OS_NEW); + func_free = reinterpret_cast(g_WeChatWinDllAddr + OS_FREE); + func_send_msg_mgr = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_MSG_MGR); + func_send_text = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_TEXT); + func_send_image = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_IMAGE); + func_send_file = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_FILE); + func_send_rich_text = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_RICH_TEXT); + func_send_pat = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_PAT_MSG); + func_forward = reinterpret_cast(g_WeChatWinDllAddr + OS_FORWARD_MSG); + func_send_emotion = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_EMOTION); + func_send_xml = reinterpret_cast(g_WeChatWinDllAddr + OS_SEND_XML); +} + +std::unique_ptr Sender::new_wx_string(const char *str) +{ + return new_wx_string(str ? std::string(str) : std::string()); +} +std::unique_ptr Sender::new_wx_string(const std::string &str) +{ + return std::make_unique(util::s2w(str)); +} + +std::vector Sender::parse_wxids(const string &wxids) +{ + vector wx_members; + wstringstream wss(util::s2w(wxids)); + wstring wstr; + while (getline(wss, wstr, L',')) { + wx_members.emplace_back(wstr); + } + return wx_members; +} + +void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids) +{ + auto wxWxid = new_wx_string(wxid); + auto wxMsg = new_wx_string(msg); + + std::vector wx_at_wxids; + if (!at_wxids.empty()) { + wx_at_wxids = parse_wxids(at_wxids); + } else { + wx_at_wxids.emplace_back(); + } + + QWORD wx_ater_list = reinterpret_cast(&(wx_at_wxids.front())); + + char buffer[0x460] = { 0 }; + func_send_msg_mgr(); + func_send_text(reinterpret_cast(&buffer), reinterpret_cast(wxWxid.get()), + reinterpret_cast(wxMsg.get()), wx_ater_list, 1, 1, 0, 0); + func_free(reinterpret_cast(&buffer)); +} + +void Sender::send_image(const std::string &wxid, const std::string &path) +{ + auto wxWxid = new_wx_string(wxid); + auto wxPath = new_wx_string(path); + + char msg[0x460] = { 0 }; + char msgTmp[0x460] = { 0 }; + QWORD *flag[10] = { nullptr }; + + QWORD tmp1 = 0, tmp2 = 0; + QWORD pMsgTmp = func_new(reinterpret_cast(&msgTmp)); + flag[8] = &tmp1; + flag[9] = &tmp2; + flag[1] = reinterpret_cast(pMsgTmp); + + QWORD pMsg = func_new(reinterpret_cast(&msg)); + QWORD sendMgr = func_send_msg_mgr(); + + func_send_image(sendMgr, pMsg, reinterpret_cast(wxWxid.get()), reinterpret_cast(wxPath.get()), + reinterpret_cast(&flag)); + + func_free(pMsg); + func_free(pMsgTmp); +} + +void Sender::send_file(const std::string &wxid, const std::string &path) +{ + auto wxWxid = new_wx_string(wxid); + auto wxPath = new_wx_string(path); + + char msg[0x460] = { 0 }; + QWORD tmp1[4] = { 0 }; + QWORD tmp2[4] = { 0 }; + QWORD tmp3[4] = { 0 }; + + QWORD pMsg = func_new(reinterpret_cast(&msg)); + QWORD appMgr = func_get_app_mgr(); + + func_send_file(appMgr, pMsg, reinterpret_cast(wxWxid.get()), reinterpret_cast(wxPath.get()), 1, tmp1, + 0, tmp2, 0, tmp3, 0, 0); + + func_free(pMsg); +} + +void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type) +{ + std::unique_ptr buff(new char[0x500]()); + std::unique_ptr buff2(new char[0x500]()); + char nullBuf[0x1C] = { 0 }; + + func_new(reinterpret_cast(buff.get())); + func_new(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(userinfo::get_self_wxid()); + + func_send_xml(reinterpret_cast(buff.get()), reinterpret_cast(wxSender.get()), + reinterpret_cast(wxReceiver.get()), reinterpret_cast(wxXml.get()), + reinterpret_cast(wxPath.get()), reinterpret_cast(nullBuf), type, 0x4, sign, + reinterpret_cast(buff2.get())); + + func_free(reinterpret_cast(buff.get())); + func_free(reinterpret_cast(buff2.get())); +} + +void Sender::send_emotion(const std::string &wxid, const std::string &path) +{ + auto wxWxid = new_wx_string(wxid); + auto wxPath = new_wx_string(path); + + std::unique_ptr buff(new QWORD[8]()); // 0x20 bytes = 8 * QWORD + + if (!buff) { + LOG_ERROR("Out of Memory..."); + return; + } + + QWORD mgr = func_get_emotion_mgr(); + func_send_emotion(mgr, reinterpret_cast(wxPath.get()), reinterpret_cast(buff.get()), + reinterpret_cast(wxWxid.get()), 2, reinterpret_cast(buff.get()), 0, + reinterpret_cast(buff.get())); +} + +int Sender::send_rich_text(const RichText &rt) +{ +#define SRTM_SIZE 0x3F0 + QWORD status = -1; + + char *buff = static_cast(HeapAlloc(GetProcessHeap(), 0, SRTM_SIZE)); + if (!buff) { + LOG_ERROR("Out of Memory..."); + return -1; + } + + memset(buff, 0, SRTM_SIZE); + func_new(reinterpret_cast(buff)); + + auto pReceiver = new_wx_string(rt.receiver); + auto pTitle = new_wx_string(rt.title); + auto pUrl = new_wx_string(rt.url); + auto pThumburl = new_wx_string(rt.thumburl); + auto pDigest = new_wx_string(rt.digest); + auto pAccount = new_wx_string(rt.account); + auto pName = new_wx_string(rt.name); + + memcpy(buff + 0x8, pTitle.get(), sizeof(WxString)); + memcpy(buff + 0x48, pUrl.get(), sizeof(WxString)); + memcpy(buff + 0xB0, pThumburl.get(), sizeof(WxString)); + memcpy(buff + 0xF0, pDigest.get(), sizeof(WxString)); + memcpy(buff + 0x2C0, pAccount.get(), sizeof(WxString)); + memcpy(buff + 0x2E0, pName.get(), sizeof(WxString)); + + QWORD mgr = func_get_app_mgr(); + status = func_send_rich_text(mgr, reinterpret_cast(pReceiver.get()), reinterpret_cast(buff)); + func_free(reinterpret_cast(buff)); + + return static_cast(status); +} + +int Sender::send_pat(const std::string &roomid, const std::string &wxid) +{ + QWORD status = -1; + + auto wxRoomid = new_wx_string(roomid); + auto wxWxid = new_wx_string(wxid); + + status = func_send_pat(reinterpret_cast(wxRoomid.get()), reinterpret_cast(wxWxid.get())); + + return static_cast(status); +} + +int Sender::forward(QWORD msgid, const std::string &receiver) +{ + uint32_t dbIdx = 0; + QWORD localId = 0; + + if (db::get_local_id_and_dbidx(msgid, &localId, &dbIdx) != 0) { + LOG_ERROR("Failed to get localId, Please check id: {}", msgid); + return -1; + } + + LARGE_INTEGER l; + l.HighPart = dbIdx; + l.LowPart = static_cast(localId); + auto wxReceiver = new_wx_string(receiver); + + return static_cast(func_forward(reinterpret_cast(wxReceiver.get()), l.QuadPart, 0x4, 0x0)); +} + +// RPC 方法 +bool Sender::rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (text.msg == nullptr || text.receiver == nullptr || strlen(text.msg) == 0 || strlen(text.receiver) == 0) { + LOG_ERROR("Empty message or receiver."); + rsp.msg.status = -1; + } else { + send_text(text.receiver, text.msg, text.aters ? text.aters : ""); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_image(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_file(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len) +{ + std::string path(file.path); + std::string receiver(file.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (path.empty() || receiver.empty()) { + LOG_ERROR("Empty path or receiver."); + rsp.msg.status = -1; + } else { + send_emotion(receiver, path); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_xml(const XmlMsg &xml, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (xml.content == nullptr || xml.receiver == nullptr) { + LOG_ERROR("Empty content or receiver."); + rsp.msg.status = -1; + } else { + send_xml(xml.receiver, xml.content, xml.path, xml.type); + rsp.msg.status = 0; + } + }); +} + +bool Sender::rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + if (rt.receiver == nullptr) { + LOG_ERROR("Empty receiver."); + rsp.msg.status = -1; + } else { + rsp.msg.status = send_rich_text(rt); + } + }); +} + +bool Sender::rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len) +{ + std::string wxid(pat.wxid); + std::string roomid(pat.roomid); + return fill_response(out, len, [&](Response &rsp) { + if (roomid.empty() || wxid.empty()) { + LOG_ERROR("Empty roomid or wxid."); + rsp.msg.status = -1; + } else { + rsp.msg.status = send_pat(roomid, wxid); + } + }); +} + +bool Sender::rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len) +{ + uint64_t msgid = fm.id; + std::string receiver(fm.receiver); + return fill_response(out, len, [&](Response &rsp) { + if (receiver.empty()) { + LOG_ERROR("Empty receiver."); + rsp.msg.status = -1; + } else { + rsp.msg.status = forward(msgid, receiver); + } + }); +} + +} diff --git a/WeChatFerry/spy/message_sender.h b/WeChatFerry/spy/message_sender.h new file mode 100644 index 0000000..1f0a855 --- /dev/null +++ b/WeChatFerry/spy/message_sender.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include "wcf.pb.h" + +#include "spy_types.h" + +namespace message +{ + +class Sender +{ +public: + static Sender &get_instance(); + + void send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids = ""); + void send_image(const std::string &wxid, const std::string &path); + void send_file(const std::string &wxid, const std::string &path); + void send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type); + void send_emotion(const std::string &wxid, const std::string &path); + int send_rich_text(const RichText &rt); + int send_pat(const std::string &roomid, const std::string &wxid); + int forward(uint64_t msgid, const std::string &receiver); + + // RPC 方法 + bool rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len); + bool rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len); + bool rpc_send_xml(const XmlMsg &rt, uint8_t *out, size_t *len); + bool rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len); + bool rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len); + bool rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len); + +private: + Sender(); + ~Sender() = default; + + Sender(const Sender &) = delete; + Sender &operator=(const Sender &) = delete; + + using New_t = QWORD (*)(QWORD); + using Free_t = QWORD (*)(QWORD); + using SendMsgMgr_t = QWORD (*)(); + using GetAppMgr_t = QWORD (*)(); + using SendText_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + using SendImage_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD); + using SendFile_t + = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD); + using SendRichText_t = QWORD (*)(QWORD, QWORD, QWORD); + using SendPat_t = QWORD (*)(QWORD, QWORD); + using Forward_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD); + using GetEmotionMgr_t = QWORD (*)(); + using SendEmotion_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + using XmlBufSign_t = QWORD (*)(QWORD, QWORD, QWORD); + using SendXml_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + + New_t func_new; + Free_t func_free; + SendMsgMgr_t func_send_msg_mgr; + GetAppMgr_t func_get_app_mgr; + SendText_t func_send_text; + SendImage_t func_send_image; + SendFile_t func_send_file; + SendRichText_t func_send_rich_text; + SendPat_t func_send_pat; + Forward_t func_forward; + GetEmotionMgr_t func_get_emotion_mgr; + SendEmotion_t func_send_emotion; + XmlBufSign_t func_xml_buf_sign; + SendXml_t func_send_xml; + + std::unique_ptr new_wx_string(const char *str); + std::unique_ptr new_wx_string(const std::string &str); + std::vector parse_wxids(const std::string &wxids); +}; +} diff --git a/WeChatFerry/spy/misc_manager.cpp b/WeChatFerry/spy/misc_manager.cpp new file mode 100644 index 0000000..f6f1b3f --- /dev/null +++ b/WeChatFerry/spy/misc_manager.cpp @@ -0,0 +1,415 @@ +#pragma warning(disable : 4244) +#include "misc_manager.h" + +#include +#include + +#include "framework.h" + +#include "codec.h" +#include "database_executor.h" +#include "log.hpp" +#include "message_handler.h" +#include "rpc_helper.h" +#include "spy_types.h" +#include "util.h" + +using namespace std; +namespace fs = std::filesystem; + +extern QWORD g_WeChatWinDllAddr; + +namespace misc +{ +#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 + +using get_sns_data_mgr_t = QWORD (*)(); +using get_sns_timeline_mgr_t = QWORD (*)(); +using get_sns_first_page_t = QWORD (*)(QWORD, QWORD, QWORD); +using get_sns_next_page_scene_t = QWORD (*)(QWORD, QWORD); +using get_chat_mgr_t = QWORD (*)(); +using new_chat_msg_t = QWORD (*)(QWORD); +using free_chat_msg_t = QWORD (*)(QWORD); +using get_pre_download_mgr_t = QWORD (*)(); +using get_mgr_by_prefix_localid_t = QWORD (*)(QWORD, QWORD); +using push_attach_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD); +using get_ocr_manager_t = QWORD (*)(); +using do_ocr_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); + +bool is_logged_in() { return util::get_qword(g_WeChatWinDllAddr + OS_LOGIN_STATUS) != 0; } + +static std::string detect_image_extension(uint8_t header1, uint8_t header2, uint8_t &key) +{ + if ((key = HEADER_PNG1 ^ header1) && (HEADER_PNG2 ^ key) == header2) return ".png"; + if ((key = HEADER_JPG1 ^ header1) && (HEADER_JPG2 ^ key) == header2) return ".jpg"; + if ((key = HEADER_GIF1 ^ header1) && (HEADER_GIF2 ^ key) == header2) return ".gif"; + return ""; +} + +std::string decrypt_image(const fs::path &src, const fs::path &dst_dir) +{ + if (!fs::exists(src)) { + LOG_ERROR("文件不存在: {}", src.string()); + return ""; + } + + std::ifstream in(src, std::ios::binary); + if (!in) { + LOG_ERROR("无法打开文件: {}", src.string()); + return ""; + } + + std::vector buffer(std::istreambuf_iterator(in), {}); + if (buffer.size() < 2) return ""; + + uint8_t key = 0x00; + auto ext = detect_image_extension(buffer[0], buffer[1], key); + if (!ext.empty()) { + LOG_ERROR("无法检测文件类型."); + return ""; + } + + std::for_each(buffer.begin(), buffer.end(), [key](char &c) { c ^= key; }); + + fs::path dst_path = dst_dir / (src.stem().string() + ext); + if (!fs::exists(dst_dir)) fs::create_directories(dst_dir); + + std::ofstream out(dst_path, std::ios::binary); + if (!out) { + LOG_ERROR("写入文件失败: {}", dst_path.string()); + return ""; + } + + out.write(buffer.data(), buffer.size()); + return dst_path.string(); +} + +static int get_first_page() +{ + int status = -1; + + get_sns_data_mgr_t GetSNSDataMgr = (get_sns_data_mgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_DATA_MGR); + get_sns_first_page_t GetSNSFirstPage = (get_sns_first_page_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 get_next_page(QWORD id) +{ + int status = -1; + + get_sns_timeline_mgr_t GetSnsTimeLineMgr = (get_sns_timeline_mgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_TIMELINE_MGR); + get_sns_next_page_scene_t GetSNSNextPageScene + = (get_sns_next_page_scene_t)(g_WeChatWinDllAddr + OS_GET_SNS_NEXT_PAGE); + + QWORD mgr = GetSnsTimeLineMgr(); + status = (int)GetSNSNextPageScene(mgr, id); + + return status; +} + +int refresh_pyq(uint64_t id) +{ + auto &msgHandler = message::Handler::getInstance(); + if (!msgHandler.isPyqListening()) { + LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg"); + return -1; + } + + if (id == 0) { + return get_first_page(); + } + + return get_next_page(id); +} + +/******************************************************************************* + * 都说我不写注释,写一下吧 + * 其实也没啥好写的,就是下载资源 + * 主要介绍一下几个参数: + * id:好理解,消息 id + * thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了 + * extra:图片、文件的路径 + *******************************************************************************/ +int download_attachment(uint64_t id, const fs::path &thumb, const fs::path &extra) +{ + int status = -1; + QWORD localId; + uint32_t dbIdx; + + if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断 + return 0; + } + + if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) { + LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id)); + return status; + } + + new_chat_msg_t NewChatMsg = (new_chat_msg_t)(g_WeChatWinDllAddr + OS_NEW_CHAT_MSG); + free_chat_msg_t FreeChatMsg = (free_chat_msg_t)(g_WeChatWinDllAddr + OS_FREE_CHAT_MSG); + get_chat_mgr_t GetChatMgr = (get_chat_mgr_t)(g_WeChatWinDllAddr + OS_GET_CHAT_MGR); + get_pre_download_mgr_t GetPreDownLoadMgr = (get_pre_download_mgr_t)(g_WeChatWinDllAddr + OS_GET_PRE_DOWNLOAD_MGR); + push_attach_task_t PushAttachTask = (push_attach_task_t)(g_WeChatWinDllAddr + OS_PUSH_ATTACH_TASK); + get_mgr_by_prefix_localid_t GetMgrByPrefixLocalId + = (get_mgr_by_prefix_localid_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("申请内存失败."); + return status; + } + + QWORD pChatMsg = NewChatMsg((QWORD)buff); + GetChatMgr(); + GetMgrByPrefixLocalId(l.QuadPart, pChatMsg); + + QWORD type = util::get_qword(reinterpret_cast(buff) + 0x38); + + fs::path save_path, 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: + LOG_ERROR("不支持的文件类型: {}", type); + return -2; + } + + if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断 + return 0; + } + + LOG_DEBUG("保存路径: {}", save_path.string()); + // 创建父目录,由于路径来源于微信,不做检查 + fs::create_directory(save_path.parent_path()); + + int temp = 1; + auto wx_save_path = util::new_wx_string(save_path.string()); + auto wx_thumb_path = util::new_wx_string(thumb_path.string()); + + memcpy(&buff[0x280], wx_thumb_path.get(), sizeof(WxString)); + memcpy(&buff[0x2A0], wx_save_path.get(), sizeof(WxString)); + memcpy(&buff[0x40C], &temp, sizeof(temp)); + + QWORD mgr = GetPreDownLoadMgr(); + status = (int)PushAttachTask(mgr, pChatMsg, 0, 1); + FreeChatMsg(pChatMsg); + + return status; +} + +std::string get_audio(uint64_t id, const fs::path &dir) +{ + if (!fs::exists(dir)) fs::create_directories(dir); + + fs::path mp3path = dir / (std::to_string(id) + ".mp3"); + if (fs::exists(mp3path)) return mp3path.string(); + + auto silk = db::get_audio_data(id); + if (silk.empty()) { + LOG_ERROR("没有获取到语音数据."); + return ""; + } + + Silk2Mp3(silk, mp3path.string(), 24000); + return mp3path.string(); +} + +std::string get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr) +{ + if (!fs::exists(dir)) fs::create_directories(dir); + + fs::path pcmpath = dir / (std::to_string(id) + ".pcm"); + if (fs::exists(pcmpath)) return pcmpath.string(); + + auto silk = db::get_audio_data(id); + if (silk.empty()) { + LOG_ERROR("没有获取到语音数据."); + return ""; + } + + std::vector pcm; + SilkDecode(silk, pcm, sr); + + std::ofstream out(pcmpath, std::ios::binary); + if (!out) { + LOG_ERROR("创建文件失败: {}", pcmpath.string()); + return ""; + } + + out.write(reinterpret_cast(pcm.data()), pcm.size()); + return pcmpath.string(); +} + +OcrResult_t get_ocr_result(const std::filesystem::path &path) +{ + OcrResult_t ret = { -1, "" }; +#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复 + if (!fs::exists(path)) { + LOG_ERROR("Can not find: {}", path); + return ret; + } + + get_ocr_manager_t GetOCRManager = (get_ocr_manager_t)(g_WeChatWinDllAddr + 0x1D6C3C0); + do_ocr_task_t DoOCRTask = (do_ocr_task_t)(g_WeChatWinDllAddr + 0x2D10BC0); + + QWORD unk1 = 0, unk2 = 0, unused = 0; + QWORD *pUnk1 = &unk1; + QWORD *pUnk2 = &unk2; + // 路径分隔符有要求,必须为 `\` + wstring wsPath = util::s2w(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 = util::get_qword(buff + 0x8); + if (count > 0) { + QWORD header = util::get_qword(buff); + for (QWORD i = 0; i < count; i++) { + QWORD content = util::get_qword(header); + ret.result += util::w2s(get_pp_wstring(content + 0x28)); + ret.result += "\n"; + header = content; + } + } +#endif + return ret; +} + +int revoke_message(uint64_t 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; +} + +std::string get_login_url() +{ + LPVOID targetAddress = reinterpret_cast(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE; + char *dataPtr = *reinterpret_cast(targetAddress); + if (!dataPtr) { + LOG_ERROR("获取二维码失败."); + return ""; + } + return "http://weixin.qq.com/x/" + std::string(dataPtr, 22); +} + +int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid) +{ + // 别想了,这个不实现了 + return -1; +} + +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_audio(const AudioMsg &am, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_audio(am.id, am.dir).c_str(); }); +} + +bool rpc_get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len) +{ + return false; +} + +bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)decrypt_image(dec.src, dec.dst).c_str(); }); +} + +bool rpc_get_login_url(uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_login_url().c_str(); }); +} + +bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len) +{ + return fill_response(out, len, + [&](Response &rsp) { rsp.msg.status = refresh_pyq(id); }); +} + +bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = download_attachment(att.id, att.thumb, att.extra); }); +} + +bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len) +{ + return fill_response(out, len, + [&](Response &rsp) { rsp.msg.status = revoke_message(id); }); +} + +bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + OcrResult_t ret = { -1, "" }; + ret = get_ocr_result(path); + rsp.msg.ocr.status = ret.status; + rsp.msg.ocr.result = (char *)ret.result.c_str(); + }); +} + +bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len) +{ + return fill_response( + out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); }); +} +} // namespace misc diff --git a/WeChatFerry/spy/misc_manager.h b/WeChatFerry/spy/misc_manager.h new file mode 100644 index 0000000..2f995ae --- /dev/null +++ b/WeChatFerry/spy/misc_manager.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "wcf.pb.h" + +#include "pb_types.h" + +namespace misc +{ + +bool is_logged_in(); + +std::string get_audio(uint64_t id, const std::filesystem::path &dir); +std::string get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr); +std::string decrypt_image(const std::filesystem::path &src, const std::filesystem::path &dst); +std::string get_login_url(); + +int refresh_pyq(uint64_t id); +int download_attachment(uint64_t id, const std::filesystem::path &thumb, const std::filesystem::path &extra); +int revoke_message(uint64_t id); + +OcrResult_t get_ocr_result(const std::filesystem::path &path); +int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid); + +// RPC +// clang-format off +bool rpc_is_logged_in(uint8_t *out, size_t *len); +bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len); +bool rpc_get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len); +bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len); +bool rpc_get_login_url(uint8_t *out, size_t *len); +bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len); +bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len); +bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len); +bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len); +bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len); +// clang-format on +} // namespace misc diff --git a/WeChatFerry/spy/receive_msg.cpp b/WeChatFerry/spy/receive_msg.cpp deleted file mode 100644 index e283ce0..0000000 --- a/WeChatFerry/spy/receive_msg.cpp +++ /dev/null @@ -1,378 +0,0 @@ -#pragma execution_character_set("utf-8") - -#include "MinHook.h" -#include "framework.h" -#include -#include -#include - -#include "log.hpp" -#include "receive_msg.h" -#include "user_info.h" -#include "util.h" - -// Defined in rpc_server.cpp -extern bool gIsLogging, gIsListening, gIsListeningPyq; -extern mutex gMutex; -extern condition_variable gCV; -extern queue gMsgQueue; - -// Defined in spy.cpp -extern QWORD g_WeChatWinDllAddr; - -#define OS_RECV_MSG_ID 0x30 -#define OS_RECV_MSG_TYPE 0x38 -#define OS_RECV_MSG_SELF 0x3C -#define OS_RECV_MSG_TS 0x44 -#define OS_RECV_MSG_ROOMID 0x48 -#define OS_RECV_MSG_CONTENT 0x88 -#define OS_RECV_MSG_WXID 0x240 -#define OS_RECV_MSG_SIGN 0x260 -#define OS_RECV_MSG_THUMB 0x280 -#define OS_RECV_MSG_EXTRA 0x2A0 -#define OS_RECV_MSG_XML 0x308 -#define OS_RECV_MSG_CALL 0x213ED90 -#define OS_PYQ_MSG_START 0x30 -#define OS_PYQ_MSG_END 0x38 -#define OS_PYQ_MSG_TS 0x38 -#define OS_PYQ_MSG_XML 0x9B8 -#define OS_PYQ_MSG_SENDER 0x18 -#define OS_PYQ_MSG_CONTENT 0x48 -#define OS_PYQ_MSG_CALL 0x2E42C90 -#define OS_WXLOG 0x2613D20 - -typedef QWORD (*RecvMsg_t)(QWORD, QWORD); -typedef QWORD (*WxLog_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*RecvPyq_t)(QWORD, QWORD, QWORD); - -static RecvMsg_t funcRecvMsg = nullptr; -static RecvMsg_t realRecvMsg = nullptr; -static WxLog_t funcWxLog = nullptr; -static WxLog_t realWxLog = nullptr; -static RecvPyq_t funcRecvPyq = nullptr; -static RecvPyq_t realRecvPyq = nullptr; -static bool isMH_Initialized = false; - -MsgTypes_t GetMsgTypes() -{ - const MsgTypes_t m = { - { 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, "文件" }, - }; - - return m; -} - -static QWORD DispatchMsg(QWORD arg1, QWORD arg2) -{ - WxMsg_t wxMsg = { 0 }; - try { - wxMsg.id = GET_QWORD(arg2 + OS_RECV_MSG_ID); - wxMsg.type = GET_DWORD(arg2 + OS_RECV_MSG_TYPE); - wxMsg.is_self = GET_DWORD(arg2 + OS_RECV_MSG_SELF); - wxMsg.ts = GET_DWORD(arg2 + OS_RECV_MSG_TS); - wxMsg.content = GetStringByWstrAddr(arg2 + OS_RECV_MSG_CONTENT); - wxMsg.sign = GetStringByWstrAddr(arg2 + OS_RECV_MSG_SIGN); - wxMsg.xml = GetStringByWstrAddr(arg2 + OS_RECV_MSG_XML); - - string roomid = GetStringByWstrAddr(arg2 + OS_RECV_MSG_ROOMID); - wxMsg.roomid = roomid; - if (roomid.find("@chatroom") != string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom - wxMsg.is_group = true; - if (wxMsg.is_self) { - wxMsg.sender = GetSelfWxid(); - } else { - wxMsg.sender = GetStringByWstrAddr(arg2 + OS_RECV_MSG_WXID); - } - } else { - wxMsg.is_group = false; - if (wxMsg.is_self) { - wxMsg.sender = GetSelfWxid(); - } else { - wxMsg.sender = roomid; - } - } - - wxMsg.thumb = GetStringByWstrAddr(arg2 + OS_RECV_MSG_THUMB); - if (!wxMsg.thumb.empty()) { - wxMsg.thumb = GetHomePath() + wxMsg.thumb; - replace(wxMsg.thumb.begin(), wxMsg.thumb.end(), '\\', '/'); - } - - wxMsg.extra = GetStringByWstrAddr(arg2 + OS_RECV_MSG_EXTRA); - if (!wxMsg.extra.empty()) { - wxMsg.extra = GetHomePath() + wxMsg.extra; - replace(wxMsg.extra.begin(), wxMsg.extra.end(), '\\', '/'); - } - } catch (const std::exception &e) { - LOG_ERROR(GB2312ToUtf8(e.what())); - } catch (...) { - LOG_ERROR("Unknow exception."); - } - - { - unique_lock lock(gMutex); - gMsgQueue.push(wxMsg); // 推送到队列 - } - - gCV.notify_all(); // 通知各方消息就绪 - return realRecvMsg(arg1, 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) -{ - QWORD p = realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); - if (p == 0 || p == 1) { - return p; - } - - LOG_INFO("【WX】\n{}", GB2312ToUtf8((char *)p)); - - return p; -} - -static void DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3) -{ - QWORD startAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_START); - QWORD endAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_END); - - if (startAddr == 0) { - return; - } - - while (startAddr < endAddr) { - WxMsg_t wxMsg; - - wxMsg.type = 0x00; // 朋友圈消息 - wxMsg.is_self = false; - wxMsg.is_group = false; - wxMsg.id = GET_QWORD(startAddr); - wxMsg.ts = GET_DWORD(startAddr + OS_PYQ_MSG_TS); - wxMsg.xml = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_XML); - wxMsg.sender = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_SENDER); - wxMsg.content = GetStringByWstrAddr(startAddr + OS_PYQ_MSG_CONTENT); - - { - unique_lock lock(gMutex); - gMsgQueue.push(wxMsg); // 推送到队列 - } - - gCV.notify_all(); // 通知各方消息就绪 - - startAddr += 0x1618; - } -} - -static MH_STATUS InitializeHook() -{ - if (isMH_Initialized) { - return MH_OK; - } - MH_STATUS status = MH_Initialize(); - if (status == MH_OK) { - isMH_Initialized = true; - } - return status; -} - -static MH_STATUS UninitializeHook() -{ - if (!isMH_Initialized) { - return MH_OK; - } - if (gIsLogging || gIsListening || gIsListeningPyq) { - return MH_OK; - } - MH_STATUS status = MH_Uninitialize(); - if (status == MH_OK) { - isMH_Initialized = false; - } - return status; -} - -void EnableLog() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsLogging) { - LOG_WARN("gIsLogging"); - return; - } - WxLog_t funcWxLog = (WxLog_t)(g_WeChatWinDllAddr + OS_WXLOG); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast(&realWxLog)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcWxLog); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - gIsLogging = true; -} - -void DisableLog() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsLogging) { - return; - } - - status = MH_DisableHook(funcWxLog); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsLogging = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} - -void ListenMessage() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsListening) { - LOG_WARN("gIsListening"); - return; - } - funcRecvMsg = (RecvMsg_t)(g_WeChatWinDllAddr + OS_RECV_MSG_CALL); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast(&realRecvMsg)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcRecvMsg); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - - gIsListening = true; -} - -void UnListenMessage() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsListening) { - return; - } - - status = MH_DisableHook(funcRecvMsg); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsListening = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} - -void ListenPyq() -{ - MH_STATUS status = MH_UNKNOWN; - if (gIsListeningPyq) { - LOG_WARN("gIsListeningPyq"); - return; - } - funcRecvPyq = (RecvPyq_t)(g_WeChatWinDllAddr + OS_PYQ_MSG_CALL); - - status = InitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Initialize failed: {}", to_string(status)); - return; - } - - status = MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast(&realRecvPyq)); - if (status != MH_OK) { - LOG_ERROR("MH_CreateHook failed: {}", to_string(status)); - return; - } - - status = MH_EnableHook(funcRecvPyq); - if (status != MH_OK) { - LOG_ERROR("MH_EnableHook failed: {}", to_string(status)); - return; - } - - gIsListeningPyq = true; -} - -void UnListenPyq() -{ - MH_STATUS status = MH_UNKNOWN; - if (!gIsListeningPyq) { - return; - } - - status = MH_DisableHook(funcRecvPyq); - if (status != MH_OK) { - LOG_ERROR("MH_DisableHook failed: {}", to_string(status)); - return; - } - - gIsListeningPyq = false; - - status = UninitializeHook(); - if (status != MH_OK) { - LOG_ERROR("MH_Uninitialize failed: {}", to_string(status)); - return; - } -} diff --git a/WeChatFerry/spy/receive_msg.h b/WeChatFerry/spy/receive_msg.h deleted file mode 100644 index 73d8759..0000000 --- a/WeChatFerry/spy/receive_msg.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "pb_types.h" - -void EnableLog(); -void DisableLog(); -void ListenPyq(); -void UnListenPyq(); -void ListenMessage(); -void UnListenMessage(); -MsgTypes_t GetMsgTypes(); diff --git a/WeChatFerry/spy/rpc_helper.h b/WeChatFerry/spy/rpc_helper.h new file mode 100644 index 0000000..963bcf7 --- /dev/null +++ b/WeChatFerry/spy/rpc_helper.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include + +#include "wcf.pb.h" + +#include "log.hpp" +#include "pb_encode.h" +#include "pb_types.h" + +static const std::unordered_map rpc_tag_map + = { { Functions_FUNC_IS_LOGIN, Response_status_tag }, + { Functions_FUNC_GET_SELF_WXID, Response_str_tag }, + { Functions_FUNC_GET_USER_INFO, Response_ui_tag }, + { Functions_FUNC_GET_MSG_TYPES, Response_types_tag }, + { Functions_FUNC_GET_CONTACTS, Response_contacts_tag }, + { Functions_FUNC_GET_DB_NAMES, Response_dbs_tag }, + { Functions_FUNC_GET_DB_TABLES, Response_tables_tag }, + { Functions_FUNC_GET_AUDIO_MSG, Response_str_tag }, + { Functions_FUNC_SEND_TXT, Response_status_tag }, + { Functions_FUNC_SEND_IMG, Response_status_tag }, + { Functions_FUNC_SEND_FILE, Response_status_tag }, + { Functions_FUNC_SEND_XML, Response_status_tag }, + { Functions_FUNC_SEND_RICH_TXT, Response_status_tag }, + { Functions_FUNC_SEND_PAT_MSG, Response_status_tag }, + { Functions_FUNC_FORWARD_MSG, Response_status_tag }, + { Functions_FUNC_SEND_EMOTION, Response_status_tag }, + { Functions_FUNC_ENABLE_RECV_TXT, Response_status_tag }, + { Functions_FUNC_DISABLE_RECV_TXT, Response_status_tag }, + { Functions_FUNC_EXEC_DB_QUERY, Response_rows_tag }, + { Functions_FUNC_REFRESH_PYQ, Response_status_tag }, + { Functions_FUNC_DOWNLOAD_ATTACH, Response_status_tag }, + { Functions_FUNC_GET_CONTACT_INFO, Response_contacts_tag }, + { Functions_FUNC_ACCEPT_FRIEND, Response_status_tag }, + { Functions_FUNC_RECV_TRANSFER, Response_status_tag }, + { Functions_FUNC_REVOKE_MSG, Response_status_tag }, + { Functions_FUNC_REFRESH_QRCODE, Response_str_tag }, + { Functions_FUNC_DECRYPT_IMAGE, Response_str_tag }, + { Functions_FUNC_EXEC_OCR, Response_ocr_tag }, + { Functions_FUNC_ADD_ROOM_MEMBERS, Response_status_tag }, + { Functions_FUNC_DEL_ROOM_MEMBERS, Response_status_tag }, + { Functions_FUNC_INV_ROOM_MEMBERS, Response_status_tag } }; + +template bool fill_response(uint8_t *out, size_t *len, AssignFunc assign) +{ + Response rsp = Response_init_default; + rsp.func = FuncType; + + auto it = rpc_tag_map.find(FuncType); + if (it == rpc_tag_map.end()) { + LOG_ERROR("Unknown function type: {}", magic_enum::enum_name(rsp.func)); + return false; + } + rsp.which_msg = it->second; + + assign(rsp); + + pb_ostream_t stream = pb_ostream_from_buffer(out, *len); + if (!pb_encode(&stream, Response_fields, &rsp)) { + LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + return false; + } + *len = stream.bytes_written; + return true; +} diff --git a/WeChatFerry/spy/rpc_server.cpp b/WeChatFerry/spy/rpc_server.cpp index 1d09e30..d4be76e 100644 --- a/WeChatFerry/spy/rpc_server.cpp +++ b/WeChatFerry/spy/rpc_server.cpp @@ -1,5 +1,7 @@ #pragma warning(disable : 4251) +#include "rpc_server.h" + #include #include #include @@ -18,825 +20,170 @@ #include "wcf.pb.h" -#include "chatroom_mgmt.h" -#include "contact_mgmt.h" -#include "exec_sql.h" -#include "funcs.h" +#include "chatroom_manager.h" +#include "contact_manager.h" +#include "database_executor.h" #include "log.hpp" +#include "message_handler.h" +#include "message_sender.h" +#include "misc_manager.h" #include "pb_types.h" #include "pb_util.h" -#include "receive_msg.h" -#include "rpc_server.h" -#include "send_msg.h" +#include "rpc_helper.h" #include "spy.h" #include "spy_types.h" -#include "user_info.h" +#include "userinfo_manager.h" #include "util.h" -#define URL_SIZE 20 -#define BASE_URL "tcp://0.0.0.0" -#define G_BUF_SIZE (16 * 1024 * 1024) - namespace fs = std::filesystem; -bool gIsLogging = false; -bool gIsListening = false; -bool gIsListeningPyq = false; -mutex gMutex; -condition_variable gCV; -queue gMsgQueue; +constexpr size_t DEFAULT_BUF_SIZE = 16 * 1024 * 1024; -static int lport = 0; -static DWORD lThreadId = 0; -static bool lIsRunning = false; -static nng_socket cmdSock, msgSock; // TODO: 断开检测 -static uint8_t gBuffer[G_BUF_SIZE] = { 0 }; +static int cmdPort = 0; +static bool isRpcRunning = false; +static HANDLE cmdThread = NULL; +static HANDLE msgThread = NULL; +static nng_socket cmdSock = NNG_SOCKET_INITIALIZER; // TODO: 断开检测 +static nng_socket msgSock = NNG_SOCKET_INITIALIZER; // TODO: 断开检测 +auto &handler = message::Handler::getInstance(); +auto &sender = message::Sender::get_instance(); -bool func_is_login(uint8_t *out, size_t *len) +using FunctionHandler = std::function; + +inline std::string build_url(int port) { return "tcp://0.0.0.0:" + std::to_string(port); } + +static void receive_message_callback() { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_IS_LOGIN; - rsp.which_msg = Response_status_tag; - rsp.msg.status = IsLogin(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_self_wxid(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_SELF_WXID; - rsp.which_msg = Response_str_tag; - - string wxid = GetSelfWxid(); - rsp.msg.str = (char *)wxid.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_user_info(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_USER_INFO; - rsp.which_msg = Response_ui_tag; - - UserInfo_t ui = GetUserInfo(); - 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(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_msg_types(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_MSG_TYPES; - rsp.which_msg = Response_types_tag; - - MsgTypes_t types = GetMsgTypes(); - rsp.msg.types.types.funcs.encode = encode_types; - rsp.msg.types.types.arg = &types; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_contacts(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_CONTACTS; - rsp.which_msg = Response_contacts_tag; - - vector contacts = GetContacts(); - rsp.msg.contacts.contacts.funcs.encode = encode_contacts; - rsp.msg.contacts.contacts.arg = &contacts; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_db_names(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_DB_NAMES; - rsp.which_msg = Response_dbs_tag; - - DbNames_t dbnames = GetDbNames(); - rsp.msg.dbs.names.funcs.encode = encode_dbnames; - rsp.msg.dbs.names.arg = &dbnames; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_db_tables(char *db, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_DB_TABLES; - rsp.which_msg = Response_tables_tag; - - DbTables_t tables = GetDbTables(db); - rsp.msg.tables.tables.funcs.encode = encode_tables; - rsp.msg.tables.tables.arg = &tables; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_audio_msg(uint64_t id, char *dir, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_AUDIO_MSG; - rsp.which_msg = Response_str_tag; - string path = ""; - - if (dir == NULL) { - LOG_ERROR("Empty dir."); - } else { - path = GetAudio(id, dir); - } - - rsp.msg.str = (char *)path.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_txt(TextMsg txt, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_TXT; - rsp.which_msg = Response_status_tag; - - if ((txt.msg == NULL) || (txt.receiver == NULL)) { - LOG_ERROR("Empty message or receiver."); - rsp.msg.status = -1; // Empty message or empty receiver - } else { - string msg(txt.msg); - string receiver(txt.receiver); - string aters(txt.aters ? txt.aters : ""); - - SendTextMessage(receiver, msg, aters); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_img(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_IMG; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else if (!fs::exists(String2Wstring(path))) { - LOG_ERROR("Path does not exists: {}", path); - rsp.msg.status = -2; - } else { - SendImageMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_file(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_FILE; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else if (!fs::exists(String2Wstring(path))) { - LOG_ERROR("Path does not exists: {}", path); - rsp.msg.status = -2; - } else { - SendFileMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_emotion(char *path, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_EMOTION; - rsp.which_msg = Response_status_tag; - - if ((path == NULL) || (receiver == NULL)) { - LOG_ERROR("Empty path or receiver."); - rsp.msg.status = -1; - } else { - SendEmotionMessage(receiver, path); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_xml(XmlMsg xml, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_XML; - rsp.which_msg = Response_status_tag; - - if ((xml.content == NULL) || (xml.receiver == NULL)) { - LOG_ERROR("Empty content or receiver."); - rsp.msg.status = -1; - } else { - string receiver(xml.receiver); - string content(xml.content); - string path(xml.path ? xml.path : ""); - uint64_t type = (uint64_t)xml.type; - SendXmlMessage(receiver, content, path, type); - rsp.msg.status = 0; - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_rich_txt(RichText rt, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_RICH_TXT; - rsp.which_msg = Response_status_tag; - - if (rt.receiver == NULL) { - LOG_ERROR("Empty receiver."); - rsp.msg.status = -1; - } else { - RichText_t rtt; - rtt.account = string(rt.account ? rt.account : ""); - rtt.digest = string(rt.digest ? rt.digest : ""); - rtt.name = string(rt.name ? rt.name : ""); - rtt.receiver = string(rt.receiver ? rt.receiver : ""); - rtt.thumburl = string(rt.thumburl ? rt.thumburl : ""); - rtt.title = string(rt.title ? rt.title : ""); - rtt.url = string(rt.url ? rt.url : ""); - - rsp.msg.status = SendRichTextMessage(rtt); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_send_pat_msg(char *roomid, char *wxid, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_SEND_PAT_MSG; - rsp.which_msg = Response_status_tag; - - if ((roomid == NULL) || (wxid == NULL)) { - LOG_ERROR("Empty roomid or wxid."); - rsp.msg.status = -1; - } else { - rsp.msg.status = SendPatMessage(roomid, wxid); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_forward_msg(uint64_t id, char *receiver, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_FORWARD_MSG; - rsp.which_msg = Response_status_tag; - - if (receiver == NULL) { - LOG_ERROR("Empty roomid or wxid."); - rsp.msg.status = -1; - } else { - rsp.msg.status = ForwardMessage(id, receiver); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -static void PushMessage() -{ - static uint8_t buffer[G_BUF_SIZE] = { 0 }; - int rv; Response rsp = Response_init_default; rsp.func = Functions_FUNC_ENABLE_RECV_TXT; rsp.which_msg = Response_wxmsg_tag; + std::vector msgBuffer(DEFAULT_BUF_SIZE); - pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE); + pb_ostream_t stream = pb_ostream_from_buffer(msgBuffer.data(), msgBuffer.size()); - char url[URL_SIZE + 1] = { 0 }; - sprintf_s(url, URL_SIZE, "%s:%d", BASE_URL, lport + 1); + std::string url = build_url(cmdPort + 1); if ((rv = nng_pair1_open(&msgSock)) != 0) { LOG_ERROR("nng_pair0_open error {}", nng_strerror(rv)); return; } - if ((rv = nng_listen(msgSock, url, NULL, 0)) != 0) { + if ((rv = nng_listen(msgSock, url.c_str(), NULL, 0)) != 0) { LOG_ERROR("nng_listen error {}", nng_strerror(rv)); return; } - LOG_INFO("MSG Server listening on {}", url); - if ((rv = nng_setopt_ms(msgSock, NNG_OPT_SENDTIMEO, 2000)) != 0) { + LOG_INFO("MSG Server listening on {}", url.c_str()); + if ((rv = nng_setopt_ms(msgSock, NNG_OPT_SENDTIMEO, 5000)) != 0) { LOG_ERROR("nng_setopt_ms: {}", nng_strerror(rv)); return; } - while (gIsListening) { - unique_lock lock(gMutex); - if (gCV.wait_for(lock, chrono::milliseconds(1000), []() { return !gMsgQueue.empty(); })) { - while (!gMsgQueue.empty()) { - auto wxmsg = gMsgQueue.front(); - rsp.msg.wxmsg.id = wxmsg.id; - rsp.msg.wxmsg.is_self = wxmsg.is_self; - rsp.msg.wxmsg.is_group = wxmsg.is_group; - rsp.msg.wxmsg.type = wxmsg.type; - rsp.msg.wxmsg.ts = wxmsg.ts; - rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str(); - rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str(); - rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str(); - rsp.msg.wxmsg.sign = (char *)wxmsg.sign.c_str(); - rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str(); - rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str(); - rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str(); - gMsgQueue.pop(); - LOG_DEBUG("Push msg: {}", wxmsg.content); - pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - continue; - } + while (handler.isMessageListening()) { + std::unique_lock lock(handler.getMutex()); + std::optional msgOpt; + auto hasMessage = [&]() { + msgOpt = handler.popMessage(); + return msgOpt.has_value(); + }; - rv = nng_send(msgSock, buffer, stream.bytes_written, 0); - if (rv != 0) { - LOG_ERROR("msgSock-nng_send: {}", nng_strerror(rv)); - } - LOG_DEBUG("Send data length {}", stream.bytes_written); + if (handler.getConditionVariable().wait_for(lock, std::chrono::milliseconds(1000), hasMessage)) { + WxMsg_t wxmsg = std::move(msgOpt.value()); + rsp.msg.wxmsg.id = wxmsg.id; + rsp.msg.wxmsg.is_self = wxmsg.is_self; + rsp.msg.wxmsg.is_group = wxmsg.is_group; + rsp.msg.wxmsg.type = wxmsg.type; + rsp.msg.wxmsg.ts = wxmsg.ts; + rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str(); + rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str(); + rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str(); + rsp.msg.wxmsg.sign = (char *)wxmsg.sign.c_str(); + rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str(); + rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str(); + rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str(); + + LOG_DEBUG("Push msg: {}", wxmsg.content); + pb_ostream_t stream = pb_ostream_from_buffer(msgBuffer.data(), msgBuffer.size()); + if (!pb_encode(&stream, Response_fields, &rsp)) { + LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); + continue; + } + + rv = nng_send(msgSock, msgBuffer.data(), stream.bytes_written, 0); + if (rv != 0) { + LOG_ERROR("msgSock-nng_send: {}", nng_strerror(rv)); + } + LOG_DEBUG("Send data length {}", stream.bytes_written); + } + } +} + +static bool rpc_enable_recv_msg(bool pyq, uint8_t *out, size_t *len) +{ + return fill_response(out, len, [&](Response &rsp) { + rsp.msg.status = handler.ListenMsg(); + if (rsp.msg.status == 0) { + if (pyq) { + handler.ListenPyq(); + } + msgThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)receive_message_callback, nullptr, 0, nullptr); + if (msgThread == nullptr) { + rsp.msg.status = GetLastError(); + LOG_ERROR("func_enable_recv_txt failed: {}", rsp.msg.status); } } - } - nng_close(msgSock); + }); } -bool func_enable_recv_txt(bool pyq, uint8_t *out, size_t *len) +static bool rpc_disable_recv_msg(uint8_t *out, size_t *len) { - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ENABLE_RECV_TXT; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if (!gIsListening) { - ListenMessage(); - if (pyq) { - ListenPyq(); + return fill_response(out, len, [](Response &rsp) { + rsp.msg.status = handler.UnListenMsg(); + if (rsp.msg.status == 0) { + handler.UnListenPyq(); + if (msgThread != nullptr) { + TerminateThread(msgThread, 0); + msgThread = nullptr; + } } - HANDLE msgThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PushMessage, NULL, NULL, NULL); - if (msgThread == NULL) { - rsp.msg.status = GetLastError(); - LOG_ERROR("func_enable_recv_txt failed: {}", rsp.msg.status); - } else { - CloseHandle(msgThread); - } - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; + }); } -bool func_disable_recv_txt(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DISABLE_RECV_TXT; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - UnListenPyq(); - UnListenMessage(); // 可能需要1秒之后才能退出,见 PushMessage - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_exec_db_query(char *db, char *sql, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_EXEC_DB_QUERY; - rsp.which_msg = Response_rows_tag; - DbRows_t rows; - - if ((db == NULL) || (sql == NULL)) { - LOG_ERROR("Empty db or sql."); - } else { - rows = ExecDbQuery(db, sql); - } - - rsp.msg.rows.rows.arg = &rows; - rsp.msg.rows.rows.funcs.encode = encode_rows; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_refresh_pyq(uint64_t id, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REFRESH_PYQ; - rsp.which_msg = Response_status_tag; - - rsp.msg.status = RefreshPyq(id); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_download_attach(AttachMsg att, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DOWNLOAD_ATTACH; - rsp.which_msg = Response_status_tag; - - uint64_t id = att.id; - string thumb = string(att.thumb ? att.thumb : ""); - string extra = string(att.extra ? att.extra : ""); - - rsp.msg.status = DownloadAttach(id, thumb, extra); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_revoke_msg(uint64_t id, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REVOKE_MSG; - rsp.which_msg = Response_status_tag; - - rsp.msg.status = RevokeMsg(id); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_refresh_qrcode(uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_REFRESH_QRCODE; - rsp.which_msg = Response_str_tag; - - rsp.msg.str = (char *)GetLoginUrl().c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_receive_transfer(char *wxid, char *tfid, char *taid, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_RECV_TRANSFER; - rsp.which_msg = Response_status_tag; - - if ((wxid == NULL) || (tfid == NULL) || (taid == NULL)) { - rsp.msg.status = -1; - LOG_ERROR("Empty wxid, tfid or taid."); - } else { - rsp.msg.status = ReceiveTransfer(wxid, tfid, taid); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -#if 0 -bool func_accept_friend(char *v3, char *v4, int32_t scene, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ACCEPT_FRIEND; - rsp.which_msg = Response_status_tag; - - if ((v3 == NULL) || (v4 == NULL)) { - rsp.msg.status = -1; - LOG_ERROR("Empty V3 or V4."); - } else { - rsp.msg.status = AcceptNewFriend(v3, v4, scene); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_get_contact_info(string wxid, uint8_t *out, size_t *len) -{ - /*借用 Functions_FUNC_GET_CONTACTS */ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_GET_CONTACT_INFO; - rsp.which_msg = Response_contacts_tag; - - vector contacts; - contacts.push_back(GetContactByWxid(wxid)); - - rsp.msg.contacts.contacts.funcs.encode = encode_contacts; - rsp.msg.contacts.contacts.arg = &contacts; - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} -#endif - -bool func_decrypt_image(DecPath dec, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DECRYPT_IMAGE; - rsp.which_msg = Response_str_tag; - string path = ""; - - if ((dec.src == NULL) || (dec.dst == NULL)) { - LOG_ERROR("Empty src or dst."); - } else { - path = DecryptImage(dec.src, dec.dst); - } - - rsp.msg.str = (char *)path.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_exec_ocr(char *path, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_EXEC_OCR; - rsp.which_msg = Response_ocr_tag; - OcrResult_t ret = { -1, "" }; - - if (path == NULL) { - LOG_ERROR("Empty path."); - } else { - ret = GetOcrResult(path); - } - - rsp.msg.ocr.status = ret.status; - rsp.msg.ocr.result = (char *)ret.result.c_str(); - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - return true; -} - -bool func_add_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_ADD_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = AddChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_del_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_DEL_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = DelChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} - -bool func_invite_room_members(char *roomid, char *wxids, uint8_t *out, size_t *len) -{ - Response rsp = Response_init_default; - rsp.func = Functions_FUNC_INV_ROOM_MEMBERS; - rsp.which_msg = Response_status_tag; - rsp.msg.status = 0; - - if ((roomid == NULL) || (wxids == NULL)) { - LOG_ERROR("Empty roomid or wxids."); - rsp.msg.status = -1; - } else { - rsp.msg.status = InviteChatroomMember(roomid, wxids); - } - - pb_ostream_t stream = pb_ostream_from_buffer(out, *len); - if (!pb_encode(&stream, Response_fields, &rsp)) { - LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream)); - return false; - } - *len = stream.bytes_written; - - return true; -} +const std::unordered_map rpc_function_map = { + // clang-format off + { Functions_FUNC_IS_LOGIN, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_is_logged_in(out, len); } }, + { Functions_FUNC_GET_SELF_WXID, [](const Request &r, uint8_t *out, size_t *len) { return userinfo::rpc_get_self_wxid(out, len); } }, + { Functions_FUNC_GET_USER_INFO, [](const Request &r, uint8_t *out, size_t *len) { return userinfo::rpc_get_user_info(out, len); } }, + { Functions_FUNC_GET_MSG_TYPES, [](const Request &r, uint8_t *out, size_t *len) { return handler.rpc_get_msg_types(out, len); } }, + { Functions_FUNC_GET_CONTACTS, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_get_contacts(out, len); } }, + { Functions_FUNC_GET_DB_NAMES, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_get_db_names(out, len); } }, + { Functions_FUNC_GET_DB_TABLES, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_get_db_tables(r.msg.str, out, len); } }, + { Functions_FUNC_GET_AUDIO_MSG, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_audio(r.msg.am, out, len); } }, + { Functions_FUNC_SEND_TXT, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_text(r.msg.txt, out, len); } }, + { Functions_FUNC_SEND_IMG, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_image(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_FILE, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_file(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_XML, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_xml(r.msg.xml, out, len); } }, + { Functions_FUNC_SEND_EMOTION, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_emotion(r.msg.file, out, len); } }, + { Functions_FUNC_SEND_RICH_TXT, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_rich_text(r.msg.rt, out, len); } }, + { Functions_FUNC_SEND_PAT_MSG, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_send_pat(r.msg.pm, out, len); } }, + { Functions_FUNC_FORWARD_MSG, [](const Request &r, uint8_t *out, size_t *len) { return sender.rpc_forward(r.msg.fm, out, len); } }, + { Functions_FUNC_ENABLE_RECV_TXT, [](const Request &r, uint8_t *out, size_t *len) { return rpc_enable_recv_msg(r.msg.flag, out, len); } }, + { Functions_FUNC_DISABLE_RECV_TXT, [](const Request &r, uint8_t *out, size_t *len) { return rpc_disable_recv_msg(out, len); } }, + { Functions_FUNC_EXEC_DB_QUERY, [](const Request &r, uint8_t *out, size_t *len) { return db::rpc_exec_db_query(r.msg.query, out, len); } }, + { Functions_FUNC_ACCEPT_FRIEND, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_accept_friend(r.msg.v, out, len); } }, + { Functions_FUNC_RECV_TRANSFER, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_receive_transfer(r.msg.tf, out, len); } }, + { Functions_FUNC_REFRESH_PYQ, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_refresh_pyq(r.msg.ui64, out, len); } }, + { Functions_FUNC_DOWNLOAD_ATTACH, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_download_attachment(r.msg.att, out, len); } }, + { Functions_FUNC_GET_CONTACT_INFO, [](const Request &r, uint8_t *out, size_t *len) { return contact::rpc_get_contact_info(r.msg.str, out, len); } }, + { Functions_FUNC_REVOKE_MSG, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_revoke_message(r.msg.ui64, out, len); } }, + { Functions_FUNC_REFRESH_QRCODE, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_login_url(out, len); } }, + { Functions_FUNC_DECRYPT_IMAGE, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_decrypt_image(r.msg.dec, out, len); } }, + { Functions_FUNC_EXEC_OCR, [](const Request &r, uint8_t *out, size_t *len) { return misc::rpc_get_ocr_result(r.msg.str, out, len); } }, + { Functions_FUNC_ADD_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_add_chatroom_member(r.msg.m, out, len); } }, + { Functions_FUNC_DEL_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_delete_chatroom_member(r.msg.m, out, len); } }, + { Functions_FUNC_INV_ROOM_MEMBERS, [](const Request &r, uint8_t *out, size_t *len) { return chatroom::rpc_invite_chatroom_member(r.msg.m, out, len); } }, + // clang-format on +}; static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len) { @@ -851,215 +198,87 @@ static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len LOG_DEBUG("{:#04x}[{}] length: {}", (uint8_t)req.func, magic_enum::enum_name(req.func), in_len); - switch (req.func) { - case Functions_FUNC_IS_LOGIN: { - ret = func_is_login(out, out_len); - break; - } - case Functions_FUNC_GET_SELF_WXID: { - ret = func_get_self_wxid(out, out_len); - break; - } - case Functions_FUNC_GET_USER_INFO: { - ret = func_get_user_info(out, out_len); - break; - } - case Functions_FUNC_GET_MSG_TYPES: { - ret = func_get_msg_types(out, out_len); - break; - } - case Functions_FUNC_GET_CONTACTS: { - ret = func_get_contacts(out, out_len); - break; - } - case Functions_FUNC_GET_DB_NAMES: { - ret = func_get_db_names(out, out_len); - break; - } - case Functions_FUNC_GET_DB_TABLES: { - ret = func_get_db_tables(req.msg.str, out, out_len); - break; - } - case Functions_FUNC_GET_AUDIO_MSG: { - ret = func_get_audio_msg(req.msg.am.id, req.msg.am.dir, out, out_len); - break; - } - case Functions_FUNC_SEND_TXT: { - ret = func_send_txt(req.msg.txt, out, out_len); - break; - } - case Functions_FUNC_SEND_IMG: { - ret = func_send_img(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_FILE: { - ret = func_send_file(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_RICH_TXT: { - ret = func_send_rich_txt(req.msg.rt, out, out_len); - break; - } - case Functions_FUNC_SEND_PAT_MSG: { - ret = func_send_pat_msg(req.msg.pm.roomid, req.msg.pm.wxid, out, out_len); - break; - } - case Functions_FUNC_FORWARD_MSG: { - ret = func_forward_msg(req.msg.fm.id, req.msg.fm.receiver, out, out_len); - break; - } - case Functions_FUNC_SEND_EMOTION: { - ret = func_send_emotion(req.msg.file.path, req.msg.file.receiver, out, out_len); - break; - } -#if 0 - case Functions_FUNC_SEND_XML: { - ret = func_send_xml(req.msg.xml, out, out_len); - break; - } -#endif - case Functions_FUNC_ENABLE_RECV_TXT: { - ret = func_enable_recv_txt(req.msg.flag, out, out_len); - break; - } - case Functions_FUNC_DISABLE_RECV_TXT: { - ret = func_disable_recv_txt(out, out_len); - break; - } - case Functions_FUNC_EXEC_DB_QUERY: { - ret = func_exec_db_query(req.msg.query.db, req.msg.query.sql, out, out_len); - break; - } - case Functions_FUNC_REFRESH_PYQ: { - ret = func_refresh_pyq(req.msg.ui64, out, out_len); - break; - } - case Functions_FUNC_DOWNLOAD_ATTACH: { - ret = func_download_attach(req.msg.att, out, out_len); - break; - } - case Functions_FUNC_RECV_TRANSFER: { - ret = func_receive_transfer(req.msg.tf.wxid, req.msg.tf.tfid, req.msg.tf.taid, out, out_len); - break; - } - case Functions_FUNC_REVOKE_MSG: { - ret = func_revoke_msg(req.msg.ui64, out, out_len); - break; - } - case Functions_FUNC_REFRESH_QRCODE: { - ret = func_refresh_qrcode(out, out_len); - break; - } -#if 0 - case Functions_FUNC_ACCEPT_FRIEND: { - ret = func_accept_friend(req.msg.v.v3, req.msg.v.v4, req.msg.v.scene, out, out_len); - break; - } - case Functions_FUNC_GET_CONTACT_INFO: { - ret = func_get_contact_info(req.msg.str, out, out_len); - break; - } -#endif - case Functions_FUNC_DECRYPT_IMAGE: { - ret = func_decrypt_image(req.msg.dec, out, out_len); - break; - } - case Functions_FUNC_EXEC_OCR: { - ret = func_exec_ocr(req.msg.str, out, out_len); - break; - } - case Functions_FUNC_ADD_ROOM_MEMBERS: { - ret = func_add_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - case Functions_FUNC_DEL_ROOM_MEMBERS: { - ret = func_del_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - case Functions_FUNC_INV_ROOM_MEMBERS: { - ret = func_invite_room_members(req.msg.m.roomid, req.msg.m.wxids, out, out_len); - break; - } - default: { - LOG_ERROR("[UNKNOW FUNCTION]"); - break; - } + auto it = rpc_function_map.find(req.func); + if (it != rpc_function_map.end()) { + ret = it->second(req, out, out_len); + } else { + LOG_ERROR("[未知方法]"); } pb_release(Request_fields, &req); return ret; } -static int RunServer() +static int RunRpcServer() { - int rv = 0; - char url[URL_SIZE + 1] = { 0 }; - sprintf_s(url, URL_SIZE, "%s:%d", BASE_URL, lport); + int rv = 0; + std::string url = build_url(cmdPort); if ((rv = nng_pair1_open(&cmdSock)) != 0) { LOG_ERROR("nng_pair0_open error {}", nng_strerror(rv)); return rv; } - if ((rv = nng_listen(cmdSock, (char *)url, NULL, 0)) != 0) { + if ((rv = nng_listen(cmdSock, url.c_str(), NULL, 0)) != 0) { LOG_ERROR("nng_listen error {}", nng_strerror(rv)); return rv; } - LOG_INFO("CMD Server listening on {}", (char *)url); + LOG_INFO("CMD Server listening on {}", url.c_str()); if ((rv = nng_setopt_ms(cmdSock, NNG_OPT_SENDTIMEO, 1000)) != 0) { LOG_ERROR("nng_setopt_ms error: {}", nng_strerror(rv)); return rv; } - lIsRunning = true; - while (lIsRunning) { + std::vector cmdBuffer(DEFAULT_BUF_SIZE); + isRpcRunning = true; + while (isRpcRunning) { uint8_t *in = NULL; - size_t in_len, out_len = G_BUF_SIZE; + size_t in_len, out_len = cmdBuffer.size(); if ((rv = nng_recv(cmdSock, &in, &in_len, NNG_FLAG_ALLOC)) != 0) { LOG_ERROR("cmdSock-nng_recv error: {}", nng_strerror(rv)); break; } try { // LOG_BUFFER(in, in_len); - if (dispatcher(in, in_len, gBuffer, &out_len)) { + if (dispatcher(in, in_len, cmdBuffer.data(), &out_len)) { LOG_DEBUG("Send data length {}", out_len); - // LOG_BUFFER(gBuffer, out_len); - rv = nng_send(cmdSock, gBuffer, out_len, 0); + // LOG_BUFFER(cmdBuffer.data(), out_len); + rv = nng_send(cmdSock, cmdBuffer.data(), out_len, 0); if (rv != 0) { LOG_ERROR("cmdSock-nng_send: {}", nng_strerror(rv)); } } else { // Error LOG_ERROR("Dispatcher failed..."); - rv = nng_send(cmdSock, gBuffer, 0, 0); + rv = nng_send(cmdSock, cmdBuffer.data(), 0, 0); if (rv != 0) { LOG_ERROR("cmdSock-nng_send: {}", nng_strerror(rv)); } - // break; } } catch (const std::exception &e) { - LOG_ERROR(GB2312ToUtf8(e.what())); + LOG_ERROR(util::gb2312_to_utf8(e.what())); } catch (...) { LOG_ERROR("Unknow exception."); } nng_free(in, in_len); } RpcStopServer(); - LOG_DEBUG("Leave RunServer"); + LOG_DEBUG("Leave RunRpcServer"); return rv; } int RpcStartServer(int port) { - if (lIsRunning) { - return 0; + if (isRpcRunning) { + LOG_WARN("RPC 服务已经启动"); + return 1; } - lport = port; - - HANDLE rpcThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunServer, NULL, NULL, &lThreadId); - if (rpcThread != 0) { - CloseHandle(rpcThread); + cmdPort = port; + cmdThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunRpcServer, NULL, NULL, NULL); + if (cmdThread == NULL) { + LOG_ERROR("CreateThread failed: {}", GetLastError()); + return -1; } #if ENABLE_WX_LOG EnableLog(); @@ -1069,16 +288,29 @@ int RpcStartServer(int port) int RpcStopServer() { - if (lIsRunning) { - nng_close(cmdSock); - nng_close(msgSock); - // UnListenMessage(); - lIsRunning = false; - Sleep(1000); - LOG_INFO("Server stoped."); + if (!isRpcRunning) { + LOG_WARN("RPC 服务未启动"); + return 1; } + + nng_close(cmdSock); + nng_close(msgSock); + handler.UnListenPyq(); + handler.UnListenMsg(); #if ENABLE_WX_LOG DisableLog(); #endif + if (cmdThread != NULL) { + WaitForSingleObject(cmdThread, INFINITE); + CloseHandle(cmdThread); + cmdThread = NULL; + } + + if (msgThread != NULL) { + WaitForSingleObject(msgThread, INFINITE); + CloseHandle(msgThread); + msgThread = NULL; + } + isRpcRunning = false; return 0; } diff --git a/WeChatFerry/spy/send_msg.cpp b/WeChatFerry/spy/send_msg.cpp deleted file mode 100644 index dca37f7..0000000 --- a/WeChatFerry/spy/send_msg.cpp +++ /dev/null @@ -1,273 +0,0 @@ - -#include "framework.h" -#include -#include - -#include "exec_sql.h" -#include "log.hpp" -#include "send_msg.h" -#include "spy_types.h" -#include "util.h" - -extern HANDLE g_hEvent; -extern QWORD g_WeChatWinDllAddr; -extern string GetSelfWxid(); // Defined in spy.cpp - -#define SRTM_SIZE 0x3F0 - -#define OS_NEW 0x1B5E140 -#define OS_FREE 0x1B55850 -#define OS_SEND_MSG_MGR 0x1B53FD0 -#define OS_SEND_TEXT 0x22C6B60 -#define OS_SEND_IMAGE 0x22BC2F0 -#define OS_GET_APP_MSG_MGR 0x1B58F70 -#define OS_SEND_FILE 0x20D0230 -#define OS_RTM_NEW 0x1B5D690 -#define OS_RTM_FREE 0x1B5CA60 -#define OS_SEND_RICH_TEXT 0x20DA210 -#define OS_SEND_PAT_MSG 0x2CAEC00 -#define OS_FORWARD_MSG 0x22C60E0 -#define OS_GET_EMOTION_MGR 0x1BCEF10 -#define OS_SEND_EMOTION 0x21B52D5 -#define OS_XML_BUFSIGN 0x24F0D70 -#define OS_SEND_XML 0x20CF360 - -typedef QWORD (*New_t)(QWORD); -typedef QWORD (*Free_t)(QWORD); -typedef QWORD (*SendMsgMgr_t)(); -typedef QWORD (*GetAppMsgMgr_t)(); -typedef QWORD (*SendTextMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*SendImageMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*SendFileMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *, QWORD, - QWORD); -typedef QWORD (*SendRichTextMsg_t)(QWORD, QWORD, QWORD); -typedef QWORD (*SendPatMsg_t)(QWORD, QWORD); -typedef QWORD (*ForwardMsg_t)(QWORD, QWORD, QWORD, QWORD); -typedef QWORD (*GetEmotionMgr_t)(); -typedef QWORD (*SendEmotion_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); - -typedef QWORD (*XmlBufSign_t)(QWORD, QWORD, QWORD); -typedef QWORD (*SendXmlMsg_t)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD); - -void SendTextMessage(string wxid, string msg, string atWxids) -{ - QWORD success = 0; - wstring wsWxid = String2Wstring(wxid); - wstring wsMsg = String2Wstring(msg); - WxString wxMsg(wsMsg); - WxString wxWxid(wsWxid); - - vector vAtWxids; - vector vWxAtWxids; - if (!atWxids.empty()) { - wstringstream wss(String2Wstring(atWxids)); - while (wss.good()) { - wstring wstr; - getline(wss, wstr, L','); - vAtWxids.push_back(wstr); - WxString wxAtWxid(vAtWxids.back()); - vWxAtWxids.push_back(wxAtWxid); - } - } else { - WxString wxEmpty = WxString(); - vWxAtWxids.push_back(wxEmpty); - } - - QWORD wxAters = (QWORD) & ((RawVector_t *)&vWxAtWxids)->start; - - char buffer[0x460] = { 0 }; - SendMsgMgr_t funcSendMsgMgr = (SendMsgMgr_t)(g_WeChatWinDllAddr + OS_SEND_MSG_MGR); - SendTextMsg_t funcSendTextMsg = (SendTextMsg_t)(g_WeChatWinDllAddr + OS_SEND_TEXT); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - funcSendMsgMgr(); - success = funcSendTextMsg((QWORD)(&buffer), (QWORD)(&wxWxid), (QWORD)(&wxMsg), wxAters, 1, 1, 0, 0); - funcFree((QWORD)(&buffer)); -} - -void SendImageMessage(string wxid, string path) -{ - wstring wsWxid = String2Wstring(wxid); - wstring wsPath = String2Wstring(path); - - WxString wxWxid(wsWxid); - WxString wxPath(wsPath); - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - SendMsgMgr_t funcSendMsgMgr = (SendMsgMgr_t)(g_WeChatWinDllAddr + OS_SEND_MSG_MGR); - SendImageMsg_t funcSendImage = (SendImageMsg_t)(g_WeChatWinDllAddr + OS_SEND_IMAGE); - - char msg[0x460] = { 0 }; - char msgTmp[0x460] = { 0 }; - QWORD *flag[10] = { 0 }; - - QWORD tmp1 = 0, tmp2 = 0; - QWORD pMsgTmp = funcNew((QWORD)(&msgTmp)); - flag[8] = &tmp1; - flag[9] = &tmp2; - flag[1] = (QWORD *)(pMsgTmp); - - QWORD pMsg = funcNew((QWORD)(&msg)); - QWORD sendMgr = funcSendMsgMgr(); - funcSendImage(sendMgr, pMsg, (QWORD)(&wxWxid), (QWORD)(&wxPath), (QWORD)(&flag)); - funcFree(pMsg); - funcFree(pMsgTmp); -} - -void SendFileMessage(string wxid, string path) -{ - wstring wsWxid = String2Wstring(wxid); - wstring wsPath = String2Wstring(path); - - WxString wxWxid(wsWxid); - WxString wxPath(wsPath); - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - GetAppMsgMgr_t funcGetAppMsgMgr = (GetAppMsgMgr_t)(g_WeChatWinDllAddr + OS_GET_APP_MSG_MGR); - SendFileMsg_t funcSendFile = (SendFileMsg_t)(g_WeChatWinDllAddr + OS_SEND_FILE); - - char msg[0x460] = { 0 }; - QWORD tmp1[4] = { 0 }; - QWORD tmp2[4] = { 0 }; - QWORD tmp3[4] = { 0 }; - - QWORD pMsg = funcNew((QWORD)(&msg)); - QWORD appMgr = funcGetAppMsgMgr(); - funcSendFile(appMgr, pMsg, (QWORD)(&wxWxid), (QWORD)(&wxPath), 1, tmp1, 0, tmp2, 0, tmp3, 0, 0); - funcFree(pMsg); -} - -int SendRichTextMessage(RichText_t &rt) -{ // TODO: Fix memory leak - QWORD status = -1; - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_RTM_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_RTM_FREE); - GetAppMsgMgr_t funcGetAppMsgMgr = (GetAppMsgMgr_t)(g_WeChatWinDllAddr + OS_GET_APP_MSG_MGR); - SendRichTextMsg_t funcForwordPublicMsg = (SendRichTextMsg_t)(g_WeChatWinDllAddr + OS_SEND_RICH_TEXT); - - char *buff = (char *)HeapAlloc(GetProcessHeap(), 0, SRTM_SIZE); - if (buff == NULL) { - LOG_ERROR("Out of Memory..."); - return -1; - } - - memset(buff, 0, SRTM_SIZE); - funcNew((QWORD)buff); - WxString *pReceiver = NewWxStringFromStr(rt.receiver); - WxString *pTitle = NewWxStringFromStr(rt.title); - WxString *pUrl = NewWxStringFromStr(rt.url); - WxString *pThumburl = NewWxStringFromStr(rt.thumburl); - WxString *pDigest = NewWxStringFromStr(rt.digest); - WxString *pAccount = NewWxStringFromStr(rt.account); - WxString *pName = NewWxStringFromStr(rt.name); - - memcpy(buff + 0x8, pTitle, sizeof(WxString)); - memcpy(buff + 0x48, pUrl, sizeof(WxString)); - memcpy(buff + 0xB0, pThumburl, sizeof(WxString)); - memcpy(buff + 0xF0, pDigest, sizeof(WxString)); - memcpy(buff + 0x2C0, pAccount, sizeof(WxString)); - memcpy(buff + 0x2E0, pName, sizeof(WxString)); - - QWORD mgr = funcGetAppMsgMgr(); - status = funcForwordPublicMsg(mgr, (QWORD)(pReceiver), (QWORD)(buff)); - funcFree((QWORD)buff); - - return (int)status; -} - -int SendPatMessage(string roomid, string wxid) -{ - QWORD status = -1; - - wstring wsRoomid = String2Wstring(roomid); - wstring wsWxid = String2Wstring(wxid); - WxString wxRoomid(wsRoomid); - WxString wxWxid(wsWxid); - - SendPatMsg_t funcSendPatMsg = (SendPatMsg_t)(g_WeChatWinDllAddr + OS_SEND_PAT_MSG); - - status = funcSendPatMsg((QWORD)(&wxRoomid), (QWORD)(&wxWxid)); - return (int)status; -} - -int ForwardMessage(QWORD msgid, string receiver) -{ - int status = -1; - uint32_t dbIdx = 0; - QWORD localId = 0; - - ForwardMsg_t funcForwardMsg = (ForwardMsg_t)(g_WeChatWinDllAddr + OS_FORWARD_MSG); - if (GetLocalIdandDbidx(msgid, &localId, &dbIdx) != 0) { - LOG_ERROR("Failed to get localId, Please check id: {}", to_string(msgid)); - return status; - } - - WxString *pReceiver = NewWxStringFromStr(receiver); - - LARGE_INTEGER l; - l.HighPart = dbIdx; - l.LowPart = (DWORD)localId; - - status = (int)funcForwardMsg((QWORD)pReceiver, l.QuadPart, 0x4, 0x0); - - return status; -} - -void SendEmotionMessage(string wxid, string path) -{ - GetEmotionMgr_t GetEmotionMgr = (GetEmotionMgr_t)(g_WeChatWinDllAddr + OS_GET_EMOTION_MGR); - SendEmotion_t SendEmotion = (SendEmotion_t)(g_WeChatWinDllAddr + OS_SEND_EMOTION); - - WxString *pWxPath = NewWxStringFromStr(path); - WxString *pWxWxid = NewWxStringFromStr(wxid); - - QWORD *buff = (QWORD *)HeapAlloc(GetProcessHeap(), 0, 0x20); - if (buff == NULL) { - LOG_ERROR("Out of Memory..."); - return; - } - - memset(buff, 0, 0x20); - QWORD mgr = GetEmotionMgr(); - SendEmotion(mgr, (QWORD)pWxPath, (QWORD)buff, (QWORD)pWxWxid, 2, (QWORD)buff, 0, (QWORD)buff); -} - -void SendXmlMessage(string receiver, string xml, string path, QWORD type) -{ - if (g_WeChatWinDllAddr == 0) { - return; - } - - New_t funcNew = (New_t)(g_WeChatWinDllAddr + OS_NEW); - Free_t funcFree = (Free_t)(g_WeChatWinDllAddr + OS_FREE); - - XmlBufSign_t xmlBufSign = (XmlBufSign_t)(g_WeChatWinDllAddr + OS_XML_BUFSIGN); - SendXmlMsg_t sendXmlMsg = (SendXmlMsg_t)(g_WeChatWinDllAddr + OS_SEND_XML); - - char buff[0x500] = { 0 }; - char buff2[0x500] = { 0 }; - char nullBuf[0x1C] = { 0 }; - - QWORD pBuf = (QWORD)(&buff); - QWORD pBuf2 = (QWORD)(&buff2); - - funcNew(pBuf); - funcNew(pBuf2); - - QWORD sbuf[4] = { 0, 0, 0, 0 }; - - QWORD sign = xmlBufSign(pBuf2, (QWORD)(&sbuf), 0x1); - - WxString *pReceiver = NewWxStringFromStr(receiver); - WxString *pXml = NewWxStringFromStr(xml); - WxString *pPath = NewWxStringFromStr(path); - WxString *pSender = NewWxStringFromStr(GetSelfWxid()); - - sendXmlMsg(pBuf, (QWORD)pSender, (QWORD)pReceiver, (QWORD)pXml, (QWORD)pPath, (QWORD)(&nullBuf), type, 0x4, sign, - pBuf2); - - funcFree((QWORD)&buff); - funcFree((QWORD)&buff2); -} diff --git a/WeChatFerry/spy/send_msg.h b/WeChatFerry/spy/send_msg.h deleted file mode 100644 index 0218a5d..0000000 --- a/WeChatFerry/spy/send_msg.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -using namespace std; - -typedef struct { - string name; - string account; - string title; - string digest; - string url; - string thumburl; - string receiver; -} RichText_t; - -void SendTextMessage(string wxid, string msg, string atWxids); -void SendImageMessage(string wxid, string path); -void SendFileMessage(string wxid, string path); -void SendXmlMessage(string receiver, string xml, string path, uint64_t type); -void SendEmotionMessage(string wxid, string path); -int SendRichTextMessage(RichText_t &rt); -int SendPatMessage(string roomid, string wxid); -int ForwardMessage(uint64_t msgid, string receiver); diff --git a/WeChatFerry/spy/spy.cpp b/WeChatFerry/spy/spy.cpp index e179aa0..db59d50 100644 --- a/WeChatFerry/spy/spy.cpp +++ b/WeChatFerry/spy/spy.cpp @@ -1,45 +1,39 @@ -#include +#include "spy.h" + +#include +#include #include "log.hpp" #include "rpc_server.h" -#include "spy.h" #include "util.h" +constexpr std::string_view SUPPORT_VERSION = "3.9.11.25"; + UINT64 g_WeChatWinDllAddr = 0; -static bool IsWxVersionMatched(const wchar_t *version) +int InitSpy(LPVOID args) { - if (wcscmp(version, SUPPORT_VERSION) != 0) { - return false; - } - return true; -} - -void InitSpy(LPVOID args) -{ - - wchar_t version[16] = { 0 }; - PortPath_t *pp = (PortPath_t *)args; + auto *pp = static_cast(args); Log::InitLogger(pp->path); - g_WeChatWinDllAddr = (UINT64)GetModuleHandle(L"WeChatWin.dll"); // 获取wechatWin模块地址 - if (g_WeChatWinDllAddr == 0) { - LOG_ERROR("获取 wechatWin.dll 模块地址失败"); - return; // TODO: 退出进程,避免后面操作失败 + if (auto dll_addr = GetModuleHandle(L"WeChatWin.dll")) { + g_WeChatWinDllAddr = reinterpret_cast(dll_addr); + } else { + LOG_ERROR("获取 WeChatWin.dll 模块地址失败"); + return -1; } - if (!GetWeChatVersion(version)) { // 获取微信版本 - LOG_ERROR("获取微信版本失败"); - return; - } - LOG_INFO("WeChat version: {}", Wstring2String(version).c_str()); - if (!IsWxVersionMatched(version)) { - LOG_ERROR("不支持当前版本"); - MessageBox(NULL, L"不支持当前版本", L"错误", 0); - return; + std::string version = util::get_wechat_version(); + std::string msg = fmt::format("WCF 支持版本: {},当前版本: {}", SUPPORT_VERSION, version); + if (version != SUPPORT_VERSION) { + LOG_ERROR(msg); + MessageBoxA(NULL, msg.c_str(), "微信版本错误", MB_ICONERROR); + return -2; } + LOG_INFO(msg); RpcStartServer(pp->port); + return 0; } void CleanupSpy() diff --git a/WeChatFerry/spy/spy.h b/WeChatFerry/spy/spy.h index 525109a..07825e6 100644 --- a/WeChatFerry/spy/spy.h +++ b/WeChatFerry/spy/spy.h @@ -2,7 +2,5 @@ #include "framework.h" -#define SUPPORT_VERSION L"3.9.11.25" - -void InitSpy(int port); +int InitSpy(int port); void CleanupSpy(); diff --git a/WeChatFerry/spy/spy_types.h b/WeChatFerry/spy/spy_types.h index 7fd1008..56f7c51 100644 --- a/WeChatFerry/spy/spy_types.h +++ b/WeChatFerry/spy/spy_types.h @@ -5,29 +5,42 @@ typedef uint64_t QWORD; -struct WxString { +class WxString +{ + +public: const wchar_t *wptr; DWORD size; DWORD capacity; const char *ptr; DWORD clen; + WxString() { - wptr = NULL; + wptr = nullptr; size = 0; capacity = 0; - ptr = NULL; + ptr = nullptr; clen = 0; } - WxString(std::wstring &ws) + WxString(std::wstring ws) : internal_ws(std::move(ws)) { - wptr = ws.c_str(); - size = (DWORD)ws.size(); - capacity = (DWORD)ws.capacity(); - ptr = NULL; + wptr = internal_ws.c_str(); + size = static_cast(internal_ws.size()); + capacity = static_cast(internal_ws.capacity()); + ptr = nullptr; clen = 0; } + + WxString(const WxString &) = delete; + WxString &operator=(const WxString &) = delete; + + WxString(WxString &&) noexcept = default; + WxString &operator=(WxString &&) noexcept = default; + +private: + std::wstring internal_ws; }; typedef struct RawVector { diff --git a/WeChatFerry/spy/user_info.cpp b/WeChatFerry/spy/user_info.cpp deleted file mode 100644 index a273592..0000000 --- a/WeChatFerry/spy/user_info.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "user_info.h" -#include "log.hpp" -#include "util.h" - -extern UINT64 g_WeChatWinDllAddr; - -#define OS_USER_HOME 0x5932770 -#define OS_USER_WXID 0x595C270 -#define OS_USER_NAME 0x595C3D8 -#define OS_USER_MOBILE 0x595C318 - -static char home[MAX_PATH] = { 0 }; - -string GetHomePath() -{ - if (home[0] == 0) { - string path = Wstring2String(GET_WSTRING(g_WeChatWinDllAddr + OS_USER_HOME)) + "\\WeChat Files\\"; - strncpy_s(home, path.c_str(), path.size()); - } - - return string(home); -} - -string GetSelfWxid() -{ - UINT64 wxidType = 0; - try { - wxidType = GET_UINT64(g_WeChatWinDllAddr + OS_USER_WXID + 0x18); - if (wxidType == 0xF) { - return GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_WXID); - } else { - return GET_STRING(g_WeChatWinDllAddr + OS_USER_WXID); - } - } catch (...) { - LOG_ERROR("wxid type: {:#x}", wxidType); - LOG_BUFFER((uint8_t *)(g_WeChatWinDllAddr + OS_USER_WXID), 20); - return "empty_wxid"; - } -} - -UserInfo_t GetUserInfo() -{ - UserInfo_t ui; - - ui.wxid = GetSelfWxid(); - - UINT64 nameType = GET_UINT64(g_WeChatWinDllAddr + OS_USER_NAME + 0x18); - if (nameType == 0xF) { - ui.name = GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_NAME); - } else { // 0x1F - ui.name = GET_STRING(g_WeChatWinDllAddr + OS_USER_NAME); - } - - ui.mobile = GET_STRING_FROM_P(g_WeChatWinDllAddr + OS_USER_MOBILE); - ui.home = GetHomePath(); - - return ui; -} diff --git a/WeChatFerry/spy/user_info.h b/WeChatFerry/spy/user_info.h deleted file mode 100644 index 8d70dc7..0000000 --- a/WeChatFerry/spy/user_info.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#include "pb_types.h" - -using namespace std; - -string GetHomePath(); -string GetSelfWxid(); - -UserInfo_t GetUserInfo(); diff --git a/WeChatFerry/spy/userinfo_manager.cpp b/WeChatFerry/spy/userinfo_manager.cpp new file mode 100644 index 0000000..eddf0d5 --- /dev/null +++ b/WeChatFerry/spy/userinfo_manager.cpp @@ -0,0 +1,88 @@ +#include "userinfo_manager.h" + +#include +#include + +#include "log.hpp" +#include "rpc_helper.h" +#include "util.h" + +extern UINT64 g_WeChatWinDllAddr; + +namespace userinfo +{ +#define OS_USER_HOME 0x5932770 +#define OS_USER_WXID 0x595C270 +#define OS_USER_NAME 0x595C3D8 +#define OS_USER_MOBILE 0x595C318 + +std::string get_home_path() +{ + static std::once_flag flag; + static std::string home_path; + + std::call_once(flag, [] { + std::string path = util::w2s(util::get_pp_wstring(g_WeChatWinDllAddr + OS_USER_HOME)) + "\\WeChat Files\\"; + home_path = std::filesystem::absolute(path).string(); + }); + + return home_path; +} + +std::string get_self_wxid() +{ + static std::once_flag flag; + static std::string wxid; + + std::call_once(flag, [] { + UINT64 wxid_type = 0; + try { + wxid_type = util::get_qword(g_WeChatWinDllAddr + OS_USER_WXID + 0x18); + if (wxid_type == 0xF) { + wxid = util::get_p_string(g_WeChatWinDllAddr + OS_USER_WXID); + } else { + wxid = util::get_pp_string(g_WeChatWinDllAddr + OS_USER_WXID); + } + + } catch (...) { + LOG_ERROR("Failed to get wxid, type: {:#x}", wxid_type); + LOG_BUFFER(reinterpret_cast(g_WeChatWinDllAddr + OS_USER_WXID), 20); + wxid = "获取wxid失败"; + } + }); + return wxid; +} + +UserInfo_t get_user_info() +{ + UserInfo_t ui; + ui.wxid = get_self_wxid(); + + UINT64 name_type = util::get_qword(g_WeChatWinDllAddr + OS_USER_NAME + 0x18); + ui.name = (name_type == 0xF) ? util::get_p_string(g_WeChatWinDllAddr + OS_USER_NAME) + : util::get_pp_string(g_WeChatWinDllAddr + OS_USER_NAME); + + ui.mobile = util::get_p_string(g_WeChatWinDllAddr + OS_USER_MOBILE); + ui.home = get_home_path(); + + return ui; +} + +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) +{ + return fill_response(out, len, [](Response &rsp) { + UserInfo_t ui = get_user_info(); + 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 userinfo diff --git a/WeChatFerry/spy/userinfo_manager.h b/WeChatFerry/spy/userinfo_manager.h new file mode 100644 index 0000000..6868817 --- /dev/null +++ b/WeChatFerry/spy/userinfo_manager.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "pb_types.h" + +namespace userinfo +{ + +// 获取 WeChat 数据存储路径 +std::string get_home_path(); + +// 获取自身 wxid +std::string get_self_wxid(); + +// 获取用户信息 +UserInfo_t get_user_info(); + +// RPC 方法 +bool rpc_get_self_wxid(uint8_t *out, size_t *len); +bool rpc_get_user_info(uint8_t *out, size_t *len); + +} // namespace userinfo