From 820b4afa2db274aed6c6c24bf09fe3bb63969789 Mon Sep 17 00:00:00 2001 From: Changhua Date: Sat, 21 Mar 2026 23:10:35 +0800 Subject: [PATCH 01/55] feat: switch to 3.9.12.56 --- WeChatFerry/.editorconfig | 7 + WeChatFerry/CMakeLists.txt | 2 +- WeChatFerry/WeChatFerry.sln | 30 +- WeChatFerry/com/framework.h | 4 +- WeChatFerry/com/log.hpp | 89 +-- WeChatFerry/com/util.cpp | 311 +--------- WeChatFerry/com/util.h | 106 +--- WeChatFerry/rpc/pb_types.h | 3 +- WeChatFerry/rpc/pb_util.cpp | 1 + WeChatFerry/rpc/proto/wcf.proto | 27 +- WeChatFerry/rpc/tool/proto/nanopb_pb2.py | 52 +- WeChatFerry/sdk/CMakeLists.txt | 4 +- WeChatFerry/sdk/SDK.vcxproj | 130 +++-- WeChatFerry/sdk/SDK.vcxproj.filters | 17 +- WeChatFerry/sdk/framework.h | 3 + WeChatFerry/sdk/injector.cpp | 179 +++--- WeChatFerry/sdk/injector.h | 12 +- WeChatFerry/sdk/sdk.cpp | 196 +++---- WeChatFerry/sdk/sdk.def | 3 + WeChatFerry/sdk/sdk.h | 6 +- WeChatFerry/smc/Codec.lib | Bin 5511876 -> 5497034 bytes WeChatFerry/smc/libCodec.a | Bin 454392 -> 0 bytes WeChatFerry/smc/libmp3lame.a | Bin 443158 -> 0 bytes WeChatFerry/spy/CMakeLists.txt | 14 +- WeChatFerry/spy/Spy.vcxproj | 260 +++++---- WeChatFerry/spy/Spy.vcxproj.filters | 80 +-- WeChatFerry/spy/account_manager.cpp | 89 +-- WeChatFerry/spy/account_manager.h | 17 +- WeChatFerry/spy/chatroom_manager.cpp | 245 ++++++-- WeChatFerry/spy/chatroom_manager.h | 2 + WeChatFerry/spy/com/framework.h | 5 + WeChatFerry/spy/com/log.hpp | 73 +++ WeChatFerry/spy/com/util.cpp | 301 ++++++++++ WeChatFerry/spy/com/util.h | 76 +++ WeChatFerry/spy/contact_manager.cpp | 218 ++++--- WeChatFerry/spy/contact_manager.h | 17 +- WeChatFerry/spy/database_executor.cpp | 249 ++++---- WeChatFerry/spy/database_executor.h | 8 +- WeChatFerry/spy/dllmain.cpp | 4 + WeChatFerry/spy/message_handler.cpp | 282 ---------- WeChatFerry/spy/message_handler.h | 74 --- WeChatFerry/spy/message_receiver.cpp | 291 ++++++++++ WeChatFerry/spy/message_receiver.h | 17 + WeChatFerry/spy/message_sender.cpp | 686 +++++++++++++++-------- WeChatFerry/spy/message_sender.h | 98 +--- WeChatFerry/spy/misc_manager.cpp | 685 +++++++++++++--------- WeChatFerry/spy/misc_manager.h | 40 +- WeChatFerry/spy/offsets.h | 318 ++++++----- WeChatFerry/spy/rpc_helper.h | 89 ++- WeChatFerry/spy/rpc_server.cpp | 472 +++++++--------- WeChatFerry/spy/rpc_server.h | 64 +-- WeChatFerry/spy/spy.aps | Bin 2612 -> 2612 bytes WeChatFerry/spy/spy.cpp | 79 ++- WeChatFerry/spy/spy.def | 3 + WeChatFerry/spy/spy.h | 29 +- WeChatFerry/spy/spy.rc | 12 +- WeChatFerry/spy/spy_types.h | 243 ++++++-- WeChatFerry/spy/sqlite3.h | 75 ++- WeChatFerry/wcf/main.cpp | 34 ++ WeChatFerry/wcf/wcf.vcxproj | 177 ++++++ WeChatFerry/wcf/wcf.vcxproj.filters | 45 ++ WeChatFerry/wcf/wcf.vcxproj.user | 4 + 62 files changed, 3694 insertions(+), 2963 deletions(-) create mode 100644 WeChatFerry/.editorconfig create mode 100644 WeChatFerry/sdk/framework.h create mode 100644 WeChatFerry/sdk/sdk.def delete mode 100644 WeChatFerry/smc/libCodec.a delete mode 100644 WeChatFerry/smc/libmp3lame.a create mode 100644 WeChatFerry/spy/com/framework.h create mode 100644 WeChatFerry/spy/com/log.hpp create mode 100644 WeChatFerry/spy/com/util.cpp create mode 100644 WeChatFerry/spy/com/util.h delete mode 100644 WeChatFerry/spy/message_handler.cpp delete mode 100644 WeChatFerry/spy/message_handler.h create mode 100644 WeChatFerry/spy/message_receiver.cpp create mode 100644 WeChatFerry/spy/message_receiver.h create mode 100644 WeChatFerry/spy/spy.def create mode 100644 WeChatFerry/wcf/main.cpp create mode 100644 WeChatFerry/wcf/wcf.vcxproj create mode 100644 WeChatFerry/wcf/wcf.vcxproj.filters create mode 100644 WeChatFerry/wcf/wcf.vcxproj.user diff --git a/WeChatFerry/.editorconfig b/WeChatFerry/.editorconfig new file mode 100644 index 00000000..46da929a --- /dev/null +++ b/WeChatFerry/.editorconfig @@ -0,0 +1,7 @@ +[*] +end_of_line = lf +charset = utf-8-bom +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/WeChatFerry/CMakeLists.txt b/WeChatFerry/CMakeLists.txt index 0abeef38..738e2c4e 100644 --- a/WeChatFerry/CMakeLists.txt +++ b/WeChatFerry/CMakeLists.txt @@ -40,7 +40,7 @@ add_link_options( # Include directories include_directories( - ${CMAKE_SOURCE_DIR}/com + ${CMAKE_SOURCE_DIR}/spy/com ${CMAKE_SOURCE_DIR}/rpc ${CMAKE_SOURCE_DIR}/rpc/nanopb ${CMAKE_SOURCE_DIR}/rpc/proto diff --git a/WeChatFerry/WeChatFerry.sln b/WeChatFerry/WeChatFerry.sln index 02ba879a..8e49373a 100644 --- a/WeChatFerry/WeChatFerry.sln +++ b/WeChatFerry/WeChatFerry.sln @@ -10,23 +10,27 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdk", "sdk\sdk.vcxproj", "{ {4DE80B82-5F6A-4C4C-9D16-1574308110FA} = {4DE80B82-5F6A-4C4C-9D16-1574308110FA} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wcf", "wcf\wcf.vcxproj", "{02747CE0-AD9F-4812-B019-FCF9867F7514}" + ProjectSection(ProjectDependencies) = postProject + {4DE80B82-5F6A-4C4C-9D16-1574308110FA} = {4DE80B82-5F6A-4C4C-9D16-1574308110FA} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Dev|x64 = Dev|x64 - Release|x64 = Release|x64 + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Debug|x64.ActiveCfg = Debug|x64 - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Debug|x64.Build.0 = Debug|x64 - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Dev|x64.ActiveCfg = Dev|x64 - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Dev|x64.Build.0 = Dev|x64 - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x64.ActiveCfg = Release|x64 - {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x64.Build.0 = Release|x64 - {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Debug|x64.ActiveCfg = Debug|x64 - {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Dev|x64.ActiveCfg = Dev|x64 - {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x64.ActiveCfg = Release|x64 - {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x64.Build.0 = Release|x64 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Debug|x86.ActiveCfg = Debug|Win32 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Debug|x86.Build.0 = Debug|Win32 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x86.ActiveCfg = Release|Win32 + {4DE80B82-5F6A-4C4C-9D16-1574308110FA}.Release|x86.Build.0 = Release|Win32 + {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Debug|x86.ActiveCfg = Release|Win32 + {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x86.ActiveCfg = Release|Win32 + {ABFCB647-137F-478B-A73E-F0B1E3ADC215}.Release|x86.Build.0 = Release|Win32 + {02747CE0-AD9F-4812-B019-FCF9867F7514}.Debug|x86.ActiveCfg = Release|Win32 + {02747CE0-AD9F-4812-B019-FCF9867F7514}.Release|x86.ActiveCfg = Release|Win32 + {02747CE0-AD9F-4812-B019-FCF9867F7514}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WeChatFerry/com/framework.h b/WeChatFerry/com/framework.h index 80cbbc9b..8d8692c7 100644 --- a/WeChatFerry/com/framework.h +++ b/WeChatFerry/com/framework.h @@ -1,5 +1,3 @@ #pragma once -#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 -// Windows 头文件 -#include +#include "../spy/com/framework.h" diff --git a/WeChatFerry/com/log.hpp b/WeChatFerry/com/log.hpp index a8fa8f80..03a18a1d 100644 --- a/WeChatFerry/com/log.hpp +++ b/WeChatFerry/com/log.hpp @@ -1,90 +1,3 @@ #pragma once -#ifdef ENABLE_DEBUG_LOG -#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG -#endif - -#include -#include -#include -#include -#include - -#include -#include -#include -#include "framework.h" - -#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__) -#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__) -#define LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__) -#define LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__) - -#ifdef ENABLE_DEBUG_LOG -#define LOG_BUFFER(buf, len) Log::log_buffer((buf), (len)) -#else -#define LOG_BUFFER(...) (void)0 -#endif - -namespace Log -{ -inline constexpr char DEFAULT_LOGGER_NAME[] = "WCF"; -inline constexpr char DEFAULT_LOGGER_FILE[] = "logs/wcf.txt"; -inline constexpr size_t DEFAULT_LOGGER_MAX_SIZE = 1024 * 1024 * 10; // 10MB -inline constexpr size_t DEFAULT_LOGGER_MAX_FILES = 10; - -inline void InitLogger(const std::string &path) -{ - static std::shared_ptr logger = nullptr; - - if (logger != nullptr) { - return; // 已初始化 - } - - std::filesystem::path filename = std::filesystem::path(path) / DEFAULT_LOGGER_FILE; - std::filesystem::path logDir = filename.parent_path(); - - if (!std::filesystem::exists(logDir)) { - std::filesystem::create_directories(logDir); - } - - try { - logger = spdlog::rotating_logger_mt(DEFAULT_LOGGER_NAME, filename.string(), DEFAULT_LOGGER_MAX_SIZE, - DEFAULT_LOGGER_MAX_FILES); - } catch (const spdlog::spdlog_ex &ex) { - MessageBoxA(NULL, ex.what(), "Init LOGGER ERROR", MB_ICONERROR); - return; - } - - spdlog::set_default_logger(logger); - logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%s::%#::%!] %v"); - -#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG - spdlog::set_level(spdlog::level::debug); - logger->flush_on(spdlog::level::debug); -#else - logger->flush_on(spdlog::level::info); -#endif - - LOG_DEBUG("InitLogger with debug level"); -} - -#ifdef ENABLE_DEBUG_LOG -inline void log_buffer(uint8_t *buffer, size_t len) -{ - constexpr size_t BUF_SIZE = 1024 * 1024; - std::ostringstream oss; - - oss << "BUF@" << static_cast(buffer) << "[" << len << "]: "; - for (size_t i = 0; i < len; ++i) { - oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(buffer[i]) << " "; - if (oss.tellp() > BUF_SIZE - 3) { - break; // 防止缓冲区溢出 - } - } - - LOG_DEBUG(oss.str()); -} -#endif - -} // namespace Log +#include "../spy/com/log.hpp" diff --git a/WeChatFerry/com/util.cpp b/WeChatFerry/com/util.cpp index f5a83763..6ec111f4 100644 --- a/WeChatFerry/com/util.cpp +++ b/WeChatFerry/com/util.cpp @@ -1,310 +1 @@ -#include "util.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "log.hpp" - -#pragma comment(lib, "shlwapi") -#pragma comment(lib, "Version.lib") - -namespace fs = std::filesystem; - -namespace util -{ - -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; -} - -std::string w2s(const std::wstring &ws) -{ - 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; -} - -std::string gb2312_to_utf8(const char *gb2312) -{ - if (!gb2312) return ""; - - 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.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 DWORD get_wechat_pid() -{ - DWORD pid = 0; - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot == INVALID_HANDLE_VALUE) return 0; - - PROCESSENTRY32W pe32 = { sizeof(PROCESSENTRY32W) }; - while (Process32NextW(hSnapshot, &pe32)) { - if (pe32.szExeFile == s2w(WECHATEXE)) { - pid = pe32.th32ProcessID; - break; - } - } - CloseHandle(hSnapshot); - return pid; -} - -static std::optional get_wechat_path() -{ - 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); -} - -static std::optional get_wechat_win_dll_path() -{ - 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; -} - -static std::optional get_file_version(const std::string &path) -{ - 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; - } - - auto wechat_path = util::get_wechat_path(); - if (!wechat_path) { - LOG_ERROR("获取 WeChat 安装路径失败"); - return ERROR_FILE_NOT_FOUND; - } - - 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; - return ERROR_SUCCESS; -} - -uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr) -{ - uint32_t value = 0; - if (!addr || !hProcess) return value; - - ReadProcessMemory(hProcess, reinterpret_cast(addr), &value, sizeof(value), nullptr); - - return value; -} - -std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t address) -{ - if (!hProcess || !address) return L""; - - 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 }; - if (ReadProcessMemory(hProcess, reinterpret_cast(str_address), cValue, (str_len + 1) * sizeof(wchar_t), - nullptr)) { - return std::wstring(cValue); - } - - return L""; -} - -void dbg_msg(const char *format, ...) -{ - if (!format) return; - - va_list args; - va_start(args, format); - - va_list args_copy; - va_copy(args_copy, args); - int len = vsnprintf(nullptr, 0, format, args_copy); - va_end(args_copy); - - std::vector buffer(len + 1); - vsnprintf(buffer.data(), buffer.size(), format, args); - va_end(args); - - OutputDebugStringW(s2w(buffer.data()).c_str()); -} - -std::unique_ptr new_wx_string(const char *str) -{ - return new_wx_string(str ? std::string(str) : std::string()); -} - -std::unique_ptr new_wx_string(const std::string &str) { return std::make_unique(s2w(str)); } - -std::unique_ptr new_wx_string(const wchar_t *wstr) -{ - return new_wx_string(wstr ? std::wstring(wstr) : std::wstring()); -} - -std::unique_ptr new_wx_string(const std::wstring &wstr) { return std::make_unique(wstr); } - -AtWxidSplitResult<> parse_wxids(const std::string &atWxids) -{ - AtWxidSplitResult<> result; - if (!atWxids.empty()) { - std::wstringstream wss(util::s2w(atWxids)); - for (std::wstring wxid; std::getline(wss, wxid, L',');) { - result.wxids.push_back(wxid); - result.wxWxids.emplace_back(result.wxids.back()); - } - } - return result; -} - -WxString *CreateWxString(const std::string &s) -{ - std::wstring ws = util::s2w(s); - WxString *wxStr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(WxString))); - if (!wxStr) return nullptr; - size_t len = ws.length(); - wchar_t *ptr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, (len + 1) * sizeof(wchar_t))); - if (!ptr) { - HeapFree(GetProcessHeap(), 8, wxStr); - return nullptr; - } - wmemcpy(ptr, ws.c_str(), len + 1); - wxStr->wptr = ptr; - wxStr->size = static_cast(ws.size()); - wxStr->length = static_cast(ws.length()); - wxStr->clen = 0; - wxStr->ptr = nullptr; - return wxStr; -} - -void FreeWxString(WxString *wxStr) -{ - if (wxStr) { - if (wxStr->wptr) HeapFree(GetProcessHeap(), 8, const_cast(wxStr->wptr)); - HeapFree(GetProcessHeap(), 8, wxStr); - } -} -} // namespace util +#include "../spy/com/util.cpp" diff --git a/WeChatFerry/com/util.h b/WeChatFerry/com/util.h index 6e83b014..d43ff270 100644 --- a/WeChatFerry/com/util.h +++ b/WeChatFerry/com/util.h @@ -1,105 +1,3 @@ -#pragma once +#pragma once -#include -#include -#include -#include - -#include "spy_types.h" - -namespace util -{ -struct PortPath { - int port; - char path[MAX_PATH]; -}; - -static DWORD get_wechat_pid(); -int open_wechat(DWORD &pid); -std::string get_wechat_version(); -uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr); -std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t addr); -std::wstring s2w(const std::string &s); -std::string w2s(const std::wstring &ws); -std::string gb2312_to_utf8(const char *gb2312); -void dbg_msg(const char *format, ...); - -inline DWORD get_dword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } -inline QWORD get_qword(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } -inline uint64_t get_uint64(uint64_t addr) { return addr ? *reinterpret_cast(addr) : 0; } -inline std::string get_p_string(uint64_t addr) { return addr ? std::string(reinterpret_cast(addr)) : ""; } -inline std::string get_p_string(uint64_t addr, size_t len) -{ - return addr ? std::string(reinterpret_cast(addr), len) : ""; -} -inline std::wstring get_p_wstring(uint64_t addr) -{ - return addr ? std::wstring(reinterpret_cast(addr)) : L""; -} -inline std::wstring get_p_wstring(uint64_t addr, size_t len) -{ - return addr ? std::wstring(reinterpret_cast(addr), len) : L""; -} -inline std::string get_pp_string(uint64_t addr) -{ - if (!addr) return ""; - - const char *ptr = *reinterpret_cast(addr); - return (ptr && *ptr) ? std::string(ptr) : ""; -} -inline std::wstring get_pp_wstring(uint64_t addr) -{ - if (!addr) return L""; - - const wchar_t *ptr = *reinterpret_cast(addr); - return (ptr && *ptr) ? std::wstring(ptr) : L""; -} -inline std::string get_pp_len_string(uint64_t addr) -{ - size_t len = get_dword(addr + 8); - return (addr && len) ? std::string(*reinterpret_cast(addr), len) : ""; -} -inline std::wstring get_pp_len_wstring(uint64_t addr) -{ - size_t len = get_dword(addr + 8); - return (addr && len) ? std::wstring(*reinterpret_cast(addr), len) : L""; -} -inline std::string get_str_by_wstr_addr(uint64_t addr) { return w2s(get_pp_len_wstring(addr)); } -inline void *AllocFromHeap(size_t size) { return HeapAlloc(GetProcessHeap(), 8, size); } -inline void FreeBuffer(void *buffer) -{ - if (buffer) HeapFree(GetProcessHeap(), 8, buffer); -} -inline int MsgBox(HWND hWnd, const std::string &text, const std::string &caption = "WCF", UINT uType = MB_OK) -{ - std::wstring wText = s2w(text); - std::wstring wCaption = s2w(caption); - return MessageBoxW(nullptr, wText.c_str(), wCaption.c_str(), uType); -} - -template static T *AllocBuffer(size_t count) -{ - return reinterpret_cast(HeapAlloc(GetProcessHeap(), 8, sizeof(T) * count)); -} - -template struct WxStringHolder { - std::wstring ws; - WxString wx; - explicit WxStringHolder(const T &str) : ws(util::s2w(str)), wx(ws) { } -}; - -template struct AtWxidSplitResult { - std::vector wxids; - std::vector wxWxids; -}; - -WxString *CreateWxString(const std::string &s); -void FreeWxString(WxString *wxStr); -AtWxidSplitResult<> parse_wxids(const std::string &atWxids); - -std::unique_ptr new_wx_string(const char *str); -std::unique_ptr new_wx_string(const wchar_t *wstr); -std::unique_ptr new_wx_string(const std::string &str); -std::unique_ptr new_wx_string(const std::wstring &wstr); - -} // namespace util +#include "../spy/com/util.h" diff --git a/WeChatFerry/rpc/pb_types.h b/WeChatFerry/rpc/pb_types.h index 82f5459b..897b8af3 100644 --- a/WeChatFerry/rpc/pb_types.h +++ b/WeChatFerry/rpc/pb_types.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -55,7 +55,6 @@ typedef struct { string name; string mobile; string home; - string alias; } UserInfo_t; typedef struct { diff --git a/WeChatFerry/rpc/pb_util.cpp b/WeChatFerry/rpc/pb_util.cpp index 5bde0f56..5f8c4bd7 100644 --- a/WeChatFerry/rpc/pb_util.cpp +++ b/WeChatFerry/rpc/pb_util.cpp @@ -68,6 +68,7 @@ bool encode_contacts(pb_ostream_t *stream, const pb_field_t *field, void *const vector *v = (vector *)*arg; RpcContact message = RpcContact_init_default; + LOG_DEBUG("encode_contacts[{}]", v->size()); for (auto it = v->begin(); it != v->end(); it++) { message.wxid.funcs.encode = &encode_string; message.wxid.arg = (void *)(*it).wxid.c_str(); diff --git a/WeChatFerry/rpc/proto/wcf.proto b/WeChatFerry/rpc/proto/wcf.proto index ad44e9e6..270e5e66 100644 --- a/WeChatFerry/rpc/proto/wcf.proto +++ b/WeChatFerry/rpc/proto/wcf.proto @@ -1,4 +1,4 @@ -syntax = "proto3"; +syntax = "proto3"; package wcf; option java_package = "com.iamteer"; @@ -36,7 +36,6 @@ enum Functions { FUNC_ADD_ROOM_MEMBERS = 0x70; FUNC_DEL_ROOM_MEMBERS = 0x71; FUNC_INV_ROOM_MEMBERS = 0x72; - FUNC_SHUTDOWN = 0xFF; } message Request @@ -118,7 +117,7 @@ message XmlMsg string receiver = 1; // 消息接收人 string content = 2; // xml 内容 string path = 3; // 图片路径 - uint64 type = 4; // 消息类型 + int32 type = 4; // 消息类型 } message MsgTypes { map types = 1; } @@ -179,7 +178,6 @@ message UserInfo string name = 2; // 昵称 string mobile = 3; // 手机号 string home = 4; // 文件/图片等父路径 - string alias = 5; // 修改后的wxid } message DecPath @@ -236,24 +234,3 @@ message ForwardMsg uint64 id = 1 [ jstype = JS_STRING ]; // 待转发消息 ID string receiver = 2; // 转发接收目标,群为 roomId,个人为 wxid } - -message RoomData -{ - message RoomMember - { - string wxid = 1; - optional string name = 2; // 群昵称 - int32 state = 3; - } - - repeated RoomMember members = 1; - - optional int32 field_2 = 2; - int32 field_3 = 3; - optional int32 field_4 = 4; - int32 capacity = 5; - optional string field_6 = 6; - int32 field_7 = 7; - int32 field_8 = 8; - repeated string admins = 9; // 管理员 -} diff --git a/WeChatFerry/rpc/tool/proto/nanopb_pb2.py b/WeChatFerry/rpc/tool/proto/nanopb_pb2.py index 5db30586..eea6b3e3 100644 --- a/WeChatFerry/rpc/tool/proto/nanopb_pb2.py +++ b/WeChatFerry/rpc/tool/proto/nanopb_pb2.py @@ -2,11 +2,9 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: nanopb.proto """Generated protocol buffer code.""" -from google.protobuf.internal import enum_type_wrapper +from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) @@ -18,52 +16,8 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"\xa4\x07\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x12\n\nmax_length\x18\x0e \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05\x12&\n\x08int_size\x18\x07 \x01(\x0e\x32\x08.IntSize:\nIS_DEFAULT\x12$\n\x04type\x18\x03 \x01(\x0e\x32\n.FieldType:\nFT_DEFAULT\x12\x18\n\nlong_names\x18\x04 \x01(\x08:\x04true\x12\x1c\n\rpacked_struct\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0bpacked_enum\x18\n \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0cskip_message\x18\x06 \x01(\x08:\x05\x66\x61lse\x12\x18\n\tno_unions\x18\x08 \x01(\x08:\x05\x66\x61lse\x12\r\n\x05msgid\x18\t \x01(\r\x12\x1e\n\x0f\x61nonymous_oneof\x18\x0b \x01(\x08:\x05\x66\x61lse\x12\x15\n\x06proto3\x18\x0c \x01(\x08:\x05\x66\x61lse\x12#\n\x14proto3_singular_msgs\x18\x15 \x01(\x08:\x05\x66\x61lse\x12\x1d\n\x0e\x65num_to_string\x18\r \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0c\x66ixed_length\x18\x0f \x01(\x08:\x05\x66\x61lse\x12\x1a\n\x0b\x66ixed_count\x18\x10 \x01(\x08:\x05\x66\x61lse\x12\x1e\n\x0fsubmsg_callback\x18\x16 \x01(\x08:\x05\x66\x61lse\x12/\n\x0cmangle_names\x18\x11 \x01(\x0e\x32\x11.TypenameMangling:\x06M_NONE\x12(\n\x11\x63\x61llback_datatype\x18\x12 \x01(\t:\rpb_callback_t\x12\x34\n\x11\x63\x61llback_function\x18\x13 \x01(\t:\x19pb_default_field_callback\x12\x30\n\x0e\x64\x65scriptorsize\x18\x14 \x01(\x0e\x32\x0f.DescriptorSize:\x07\x44S_AUTO\x12\x1a\n\x0b\x64\x65\x66\x61ult_has\x18\x17 \x01(\x08:\x05\x66\x61lse\x12\x0f\n\x07include\x18\x18 \x03(\t\x12\x0f\n\x07\x65xclude\x18\x1a \x03(\t\x12\x0f\n\x07package\x18\x19 \x01(\t\x12\x41\n\rtype_override\x18\x1b \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.Type\x12\x19\n\x0bsort_by_tag\x18\x1c \x01(\x08:\x04true\x12.\n\rfallback_type\x18\x1d \x01(\x0e\x32\n.FieldType:\x0b\x46T_CALLBACK*i\n\tFieldType\x12\x0e\n\nFT_DEFAULT\x10\x00\x12\x0f\n\x0b\x46T_CALLBACK\x10\x01\x12\x0e\n\nFT_POINTER\x10\x04\x12\r\n\tFT_STATIC\x10\x02\x12\r\n\tFT_IGNORE\x10\x03\x12\r\n\tFT_INLINE\x10\x05*D\n\x07IntSize\x12\x0e\n\nIS_DEFAULT\x10\x00\x12\x08\n\x04IS_8\x10\x08\x12\t\n\x05IS_16\x10\x10\x12\t\n\x05IS_32\x10 \x12\t\n\x05IS_64\x10@*Z\n\x10TypenameMangling\x12\n\n\x06M_NONE\x10\x00\x12\x13\n\x0fM_STRIP_PACKAGE\x10\x01\x12\r\n\tM_FLATTEN\x10\x02\x12\x16\n\x12M_PACKAGE_INITIALS\x10\x03*E\n\x0e\x44\x65scriptorSize\x12\x0b\n\x07\x44S_AUTO\x10\x00\x12\x08\n\x04\x44S_1\x10\x01\x12\x08\n\x04\x44S_2\x10\x02\x12\x08\n\x04\x44S_4\x10\x04\x12\x08\n\x04\x44S_8\x10\x08:E\n\x0enanopb_fileopt\x12\x1c.google.protobuf.FileOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:G\n\rnanopb_msgopt\x12\x1f.google.protobuf.MessageOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:E\n\x0enanopb_enumopt\x12\x1c.google.protobuf.EnumOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptionsB\x1a\n\x18\x66i.kapsi.koti.jpa.nanopb') -_FIELDTYPE = DESCRIPTOR.enum_types_by_name['FieldType'] -FieldType = enum_type_wrapper.EnumTypeWrapper(_FIELDTYPE) -_INTSIZE = DESCRIPTOR.enum_types_by_name['IntSize'] -IntSize = enum_type_wrapper.EnumTypeWrapper(_INTSIZE) -_TYPENAMEMANGLING = DESCRIPTOR.enum_types_by_name['TypenameMangling'] -TypenameMangling = enum_type_wrapper.EnumTypeWrapper(_TYPENAMEMANGLING) -_DESCRIPTORSIZE = DESCRIPTOR.enum_types_by_name['DescriptorSize'] -DescriptorSize = enum_type_wrapper.EnumTypeWrapper(_DESCRIPTORSIZE) -FT_DEFAULT = 0 -FT_CALLBACK = 1 -FT_POINTER = 4 -FT_STATIC = 2 -FT_IGNORE = 3 -FT_INLINE = 5 -IS_DEFAULT = 0 -IS_8 = 8 -IS_16 = 16 -IS_32 = 32 -IS_64 = 64 -M_NONE = 0 -M_STRIP_PACKAGE = 1 -M_FLATTEN = 2 -M_PACKAGE_INITIALS = 3 -DS_AUTO = 0 -DS_1 = 1 -DS_2 = 2 -DS_4 = 4 -DS_8 = 8 - -NANOPB_FILEOPT_FIELD_NUMBER = 1010 -nanopb_fileopt = DESCRIPTOR.extensions_by_name['nanopb_fileopt'] -NANOPB_MSGOPT_FIELD_NUMBER = 1010 -nanopb_msgopt = DESCRIPTOR.extensions_by_name['nanopb_msgopt'] -NANOPB_ENUMOPT_FIELD_NUMBER = 1010 -nanopb_enumopt = DESCRIPTOR.extensions_by_name['nanopb_enumopt'] -NANOPB_FIELD_NUMBER = 1010 -nanopb = DESCRIPTOR.extensions_by_name['nanopb'] - -_NANOPBOPTIONS = DESCRIPTOR.message_types_by_name['NanoPBOptions'] -NanoPBOptions = _reflection.GeneratedProtocolMessageType('NanoPBOptions', (_message.Message,), { - 'DESCRIPTOR' : _NANOPBOPTIONS, - '__module__' : 'nanopb_pb2' - # @@protoc_insertion_point(class_scope:NanoPBOptions) - }) -_sym_db.RegisterMessage(NanoPBOptions) - +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'nanopb_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: google_dot_protobuf_dot_descriptor__pb2.FileOptions.RegisterExtension(nanopb_fileopt) google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(nanopb_msgopt) diff --git a/WeChatFerry/sdk/CMakeLists.txt b/WeChatFerry/sdk/CMakeLists.txt index 158a4412..4ed735ba 100644 --- a/WeChatFerry/sdk/CMakeLists.txt +++ b/WeChatFerry/sdk/CMakeLists.txt @@ -12,8 +12,8 @@ add_library(sdk SHARED sdk.def # Common files - ${CMAKE_SOURCE_DIR}/com/util.cpp - ${CMAKE_SOURCE_DIR}/com/util.h + ${CMAKE_SOURCE_DIR}/spy/com/util.cpp + ${CMAKE_SOURCE_DIR}/spy/com/util.h ) target_link_libraries(sdk PRIVATE diff --git a/WeChatFerry/sdk/SDK.vcxproj b/WeChatFerry/sdk/SDK.vcxproj index 054ecc1f..58b39c78 100644 --- a/WeChatFerry/sdk/SDK.vcxproj +++ b/WeChatFerry/sdk/SDK.vcxproj @@ -1,12 +1,16 @@ - + Debug - x64 + Win32 - - Dev + + Release + Win32 + + + Debug x64 @@ -22,13 +26,20 @@ 10.0 - + DynamicLibrary true v142 Unicode - + + DynamicLibrary + false + v142 + true + Unicode + + DynamicLibrary true v142 @@ -46,51 +57,85 @@ - + + + + - + - + true + $(ProjectDir)..\_build\$(ProjectName)\$(Configuration)\ - + + false + $(ProjectDir)..\_build\$(ProjectName)\$(Configuration)\ + + true false - + true - x64-windows-static + x86-windows-static - - true - - + Level3 true - _DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + WIN32;_DEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h - /utf-8 %(AdditionalOptions) Windows true false - - + sdk.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp17 + $(ProjectDir);$(ProjectDir)..\spy\com;C:\Tools\vcpkg\installed\x86-windows-static\include + MultiThreaded + + + Windows + true + true + true + false + sdk.def + + if not exist "$(ProjectDir)..\Out" md "$(ProjectDir)..\Out" +xcopy /y "$(OutDir)$(TargetFileName)" "$(ProjectDir)..\Out\" + + + Copy files + - + Level3 true @@ -98,14 +143,12 @@ true Use pch.h - /utf-8 %(AdditionalOptions) Windows true false - - + sdk.def @@ -114,48 +157,39 @@ true true true - WIN32;NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + NDEBUG;SDK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - NotUsing - - - stdcpp17 - $(SolutionDir)com;$(SolutionDir)spy;C:\Tools\vcpkg\installed\x64-windows-static\include - - true - MultiThreaded - /utf-8 %(AdditionalOptions) + Use + pch.h Windows true true - false + true false - - + sdk.def - - xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)Out -xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry - - - Copy files - - - + + - + + + + + + + - \ No newline at end of file + diff --git a/WeChatFerry/sdk/SDK.vcxproj.filters b/WeChatFerry/sdk/SDK.vcxproj.filters index 9c2e881b..231cbdd8 100644 --- a/WeChatFerry/sdk/SDK.vcxproj.filters +++ b/WeChatFerry/sdk/SDK.vcxproj.filters @@ -15,16 +15,16 @@ - + 头文件 头文件 - + 头文件 - + 头文件 @@ -32,14 +32,19 @@ 源文件 + + 源文件 + 源文件 源文件 - + + + 源文件 - + - \ No newline at end of file + diff --git a/WeChatFerry/sdk/framework.h b/WeChatFerry/sdk/framework.h new file mode 100644 index 00000000..8d8692c7 --- /dev/null +++ b/WeChatFerry/sdk/framework.h @@ -0,0 +1,3 @@ +#pragma once + +#include "../spy/com/framework.h" diff --git a/WeChatFerry/sdk/injector.cpp b/WeChatFerry/sdk/injector.cpp index 6eb935ed..9146f496 100644 --- a/WeChatFerry/sdk/injector.cpp +++ b/WeChatFerry/sdk/injector.cpp @@ -1,110 +1,70 @@ #include "injector.h" -#include +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); -#include "psapi.h" - -#include "util.h" - -using namespace std; - -static void handle_injection_error(HANDLE process, LPVOID remote_address, const std::string &error_msg) +static void ShowErrorMessage(DWORD dwError, HANDLE hProcess) { - util::MsgBox(NULL, error_msg.c_str(), "Error", MB_ICONERROR); - if (remote_address) { - VirtualFreeEx(process, remote_address, 0, MEM_RELEASE); - } - if (process) { - CloseHandle(process); - } -} - -HMODULE get_target_module_base(HANDLE process, const string &dll) -{ - DWORD needed; - HMODULE modules[512]; - if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) { - util::MsgBox(NULL, "获取模块失败", "get_target_module_base", 0); - return NULL; - } - - 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]; + BOOL bIsWow64 = FALSE; + WCHAR szErrorMessage[256] = { 0 }; + LPFN_ISWOW64PROCESS fnIsWow64Process + = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + if (fnIsWow64Process != NULL && fnIsWow64Process(hProcess, &bIsWow64)) { + if (bIsWow64) { + wsprintf(szErrorMessage, L"LoadLibrary 调用失败,请检查应用版本/位数。错误码: %lu", dwError); } } - return NULL; + wsprintf(szErrorMessage, L"LoadLibrary 调用失败。错误码: %lu", dwError); + MessageBox(NULL, szErrorMessage, L"InjectDll", 0); } -HANDLE inject_dll(DWORD pid, const string &dll_path, HMODULE *injected_base) +HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase) { - SIZE_T path_size = dll_path.size() + 1; - + HANDLE hThread; + SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR); // 1. 打开目标进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - if (!hProcess) { - util::MsgBox(NULL, "打开进程失败", "inject_dll", 0); + if (hProcess == NULL) { + MessageBox(NULL, L"打开进程失败", L"InjectDll", 0); return NULL; } // 2. 在目标进程的内存里开辟空间 - LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE); - if (!pRemoteAddress) { - handle_injection_error(hProcess, NULL, "DLL 路径写入失败"); + LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE); + if (pRemoteAddress == NULL) { + MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0); return NULL; } // 3. 把 dll 的路径写入到目标进程的内存空间中 - WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL); + WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL); - // 4. 创建一个远程线程,让目标进程调用 LoadLibrary - HMODULE k32 = GetModuleHandleA("kernel32.dll"); - if (!k32) { - handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败"); - return NULL; - } + // 3. 创建一个远程线程,让目标进程调用 LoadLibrary + hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, 0, NULL); + if (hThread == NULL) { + ShowErrorMessage(GetLastError(), hProcess); + VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE); + CloseHandle(hProcess); - FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA"); - if (!libAddr) { - handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败"); return NULL; } - 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, INFINITE); + WaitForSingleObject(hThread, -1); + GetExitCodeThread(hThread, (LPDWORD)injectedBase); CloseHandle(hThread); - - *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 eject_dll(HANDLE process, HMODULE dll_base) +bool EjectDll(HANDLE process, HMODULE dllBase) { - HMODULE k32 = GetModuleHandleA("kernel32.dll"); - if (!k32) { - util::MsgBox(NULL, "获取 kernel32 失败", "eject_dll", 0); - return false; - } + HANDLE hThread = NULL; - FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread"); - if (!libAddr) { - util::MsgBox(NULL, "获取 FreeLibrary 失败", "eject_dll", 0); - return false; - } - - HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL); - if (!hThread) { - util::MsgBox(NULL, "FreeLibrary 调用失败!", "eject_dll", 0); + // 使目标进程调用 FreeLibrary,卸载 DLL + hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)dllBase, 0, NULL); + if (hThread == NULL) { + MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0); return false; } @@ -114,34 +74,34 @@ bool eject_dll(HANDLE process, HMODULE dll_base) return true; } -static uint64_t get_func_offset(const string &dll_path, const string &func_name) +static void *GetFuncAddr(LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName) { - HMODULE dll = LoadLibraryA(dll_path.c_str()); - if (!dll) { - util::MsgBox(NULL, "获取 DLL 失败", "get_func_offset", 0); - return 0; + HMODULE hLoaded = LoadLibrary(dllPath); + if (hLoaded == NULL) { + return NULL; } - LPVOID absAddr = reinterpret_cast(GetProcAddress(dll, func_name.c_str())); - uint64_t offset = reinterpret_cast(absAddr) - reinterpret_cast(dll); - FreeLibrary(dll); + void *absAddr = GetProcAddress(hLoaded, funcName); + DWORD offset = (DWORD)absAddr - (DWORD)hLoaded; - return offset; + FreeLibrary(hLoaded); + + return (void *)((DWORD)dllBase + offset); } -bool call_dll_func(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name, DWORD *ret) +bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, DWORD *ret) { - uint64_t offset = get_func_offset(dll_path, func_name); - if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) { - return false; // 避免溢出 + void *pFunc = GetFuncAddr(dllPath, dllBase, funcName); + if (pFunc == NULL) { + 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) { + + HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, parameter, 0, NULL); + if (hThread == NULL) { return false; } WaitForSingleObject(hThread, INFINITE); - if (ret) { + if (ret != NULL) { GetExitCodeThread(hThread, ret); } @@ -149,32 +109,31 @@ bool call_dll_func(HANDLE process, const string &dll_path, HMODULE dll_base, con return true; } -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) +bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz, + DWORD *ret) { - uint64_t offset = get_func_offset(dll_path, func_name); - if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast(dll_base))) { - return false; // 避免溢出 - } - uint64_t pFunc = reinterpret_cast(dll_base) + offset; - LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE); - if (!pRemoteAddress) { - util::MsgBox(NULL, "申请内存失败", "call_dll_func_ex", 0); + void *pFunc = GetFuncAddr(dllPath, dllBase, funcName); + if (pFunc == NULL) { return false; } - WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL); + 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); HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL); - if (!hThread) { - VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE); - util::MsgBox(NULL, "远程调用失败", "call_dll_func_ex", 0); + if (hThread == NULL) { + VirtualFree(pRemoteAddress, 0, MEM_RELEASE); + MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0); return false; } - WaitForSingleObject(hThread, INFINITE); - VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE); - if (ret) { + VirtualFree(pRemoteAddress, 0, MEM_RELEASE); + if (ret != NULL) { GetExitCodeThread(hThread, ret); } diff --git a/WeChatFerry/sdk/injector.h b/WeChatFerry/sdk/injector.h index 83988a6c..e212a0e5 100644 --- a/WeChatFerry/sdk/injector.h +++ b/WeChatFerry/sdk/injector.h @@ -1,11 +1,9 @@ #pragma once -#include - #include "framework.h" -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); +HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase); +bool EjectDll(HANDLE process, HMODULE dllBase); +bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, DWORD *ret); +bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz, + DWORD *ret); diff --git a/WeChatFerry/sdk/sdk.cpp b/WeChatFerry/sdk/sdk.cpp index 831a8559..5fb8e6e3 100644 --- a/WeChatFerry/sdk/sdk.cpp +++ b/WeChatFerry/sdk/sdk.cpp @@ -1,161 +1,125 @@ -#include "sdk.h" - -#include +#include "Shlwapi.h" +#include "framework.h" #include -#include -#include #include -#include -#include - -#include "framework.h" #include #include "injector.h" +#include "sdk.h" #include "util.h" -extern "C" IMAGE_DOS_HEADER __ImageBase; - -static bool injected = false; -static HANDLE wcProcess = NULL; -static HMODULE spyBase = NULL; -static std::string spyDllPath; - -//区分MSVC和MinGW -#ifdef _MSC_VER -constexpr char WCFSDKDLL[] = "sdk.dll"; -constexpr char WCFSPYDLL[] = "spy.dll"; -constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll"; -#else -constexpr char WCFSDKDLL[] = "libsdk.dll"; -constexpr char WCFSPYDLL[] = "libspy.dll"; -constexpr char WCFSPYDLL_DEBUG[] = "libspyd.dll"; -#endif - -constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag"; -constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md"; +#define WCF_LOCK ".wcf.lock" -namespace fs = std::filesystem; - -static fs::path get_module_directory() -{ - char buffer[MAX_PATH] = { 0 }; - HMODULE hModule = reinterpret_cast(&__ImageBase); - GetModuleFileNameA(hModule, buffer, MAX_PATH); - fs::path modulePath(buffer); - return modulePath.parent_path(); -} +static bool debugMode = false; +static HANDLE wcProcess = NULL; +static HMODULE spyBase = NULL; +static WCHAR spyDllPath[MAX_PATH] = { 0 }; -static bool show_disclaimer() +static int GetDllPath(bool debug, wchar_t *dllPath) { - fs::path sdk_path = get_module_directory(); - if (fs::exists(sdk_path / DISCLAIMER_FLAG)) { - return true; + GetModuleFileName(GetModuleHandle(WECHATSDKDLL), spyDllPath, MAX_PATH); + PathRemoveFileSpec(spyDllPath); + if (debug) { + PathAppend(spyDllPath, WECHATINJECTDLL_DEBUG); + } else { + PathAppend(spyDllPath, WECHATINJECTDLL); } - fs::path path = sdk_path / DISCLAIMER_TEXT_FILE; - std::ifstream file(path, std::ios::binary); - if (!file.is_open()) { - util::MsgBox(NULL, "免责声明文件读取失败。", "错误", MB_ICONERROR); - return false; + if (!PathFileExists(spyDllPath)) { + MessageBox(NULL, spyDllPath, L"文件不存在", 0); + return ERROR_FILE_NOT_FOUND; } - auto disclaimerText = std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - if (disclaimerText.empty()) { - util::MsgBox(NULL, "免责声明文件为空", "错误", MB_ICONERROR); - return false; - } - - int result = util::MsgBox(NULL, disclaimerText.c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2); - if (result == IDCANCEL) { - util::MsgBox(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION); - return false; - } - - std::ofstream flagFile(sdk_path / DISCLAIMER_FLAG, std::ios::out | std::ios::trunc); - if (!flagFile) { - util::MsgBox(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR); - return false; - } - flagFile << "User accepted the license agreement."; - - return true; + return 0; } -static std::string get_dll_path(bool debug) +int WxInitSDK(bool debug, int port) { - char buffer[MAX_PATH] = { 0 }; - GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH); - - fs::path path(buffer); - path.remove_filename(); // 只保留目录路径 - path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL; - - if (!fs::exists(path)) { - util::MsgBox(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR); - return ""; - } - - return path.string(); -} -extern "C" { -__declspec(dllexport) int WxInitSDK(bool debug, int port) -{ - if (!show_disclaimer()) { - exit(-1); // 用户拒绝协议,退出程序 - } - int status = 0; - DWORD wcPid = 0; + uint32_t wcPid = 0; - spyDllPath = get_dll_path(debug); - if (spyDllPath.empty()) { - return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在 + status = GetDllPath(debug, spyDllPath); + if (status != 0) { + return status; } - status = util::open_wechat(wcPid); + status = util::open_wechat(&wcPid); if (status != 0) { - util::MsgBox(NULL, "打开微信失败", "WxInitSDK", 0); + MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0); return status; } - std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开 - wcProcess = inject_dll(wcPid, spyDllPath, &spyBase); + Sleep(2000); // 等待微信打开 + wcProcess = InjectDll(wcPid, spyDllPath, &spyBase); if (wcProcess == NULL) { - util::MsgBox(NULL, "注入失败", "WxInitSDK", 0); + MessageBox(NULL, L"注入失败", L"WxInitSDK", 0); return -1; } - injected = true; - util::PortPath pp = { 0 }; - pp.port = port; - snprintf(pp.path, MAX_PATH, "%s", fs::current_path().string().c_str()); + PortPath_t pp = { 0 }; + pp.port = port; + sprintf_s(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(); + if (!CallDllFuncEx(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(PortPath_t), NULL)) { + MessageBox(NULL, L"初始化失败", L"WxInitSDK", 0); + return -1; } - return status; +#ifdef WCF + FILE *fd = fopen(WCF_LOCK, "wb"); + if (fd == NULL) { + MessageBox(NULL, L"无法打开lock文件", L"WxInitSDK", 0); + return -2; + } + fwrite((uint8_t *)&debug, sizeof(debug), 1, fd); + fwrite((uint8_t *)&spyBase, sizeof(spyBase), 1, fd); + fclose(fd); +#endif + debugMode = debug; + return 0; } -__declspec(dllexport) int WxDestroySDK() +int WxDestroySDK() { - if (!injected) { - return 1; // 未注入 + int status = 0; +#ifdef WCF + bool debug; + uint32_t pid = util::get_wechat_pid(); + if (pid == 0) { + MessageBox(NULL, L"微信未运行", L"WxDestroySDK", 0); + return status; } - if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) { + wcProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if (wcProcess == NULL) { + MessageBox(NULL, L"微信未运行", L"WxDestroySDK", 0); return -1; } - if (!eject_dll(wcProcess, spyBase)) { + FILE *fd = fopen(WCF_LOCK, "rb"); + if (fd == NULL) { + MessageBox(NULL, L"无法打开lock文件", L"WxDestroySDK", 0); return -2; } - injected = false; + fread((uint8_t *)&debug, sizeof(debug), 1, fd); + fread((uint8_t *)&spyBase, sizeof(spyBase), 1, fd); + fclose(fd); + status = GetDllPath(debug, spyDllPath); +#else + status = GetDllPath(debugMode, spyDllPath); +#endif + + if (status != 0) { + return status; + } + + if (!CallDllFunc(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL, NULL)) { + return -1; + } + + if (!EjectDll(wcProcess, spyBase)) { + return -1; // TODO: Unify error codes + } return 0; } -} \ No newline at end of file diff --git a/WeChatFerry/sdk/sdk.def b/WeChatFerry/sdk/sdk.def new file mode 100644 index 00000000..d8c9be9f --- /dev/null +++ b/WeChatFerry/sdk/sdk.def @@ -0,0 +1,3 @@ +EXPORTS + WxInitSDK + WxDestroySDK diff --git a/WeChatFerry/sdk/sdk.h b/WeChatFerry/sdk/sdk.h index 2002d03f..0ebec297 100644 --- a/WeChatFerry/sdk/sdk.h +++ b/WeChatFerry/sdk/sdk.h @@ -1,6 +1,4 @@ #pragma once -extern "C" { -__declspec(dllexport) int WxInitSDK(bool debug, int port); -__declspec(dllexport) int WxDestroySDK(); -} \ No newline at end of file +int WxInitSDK(bool debug, int port); +int WxDestroySDK(); diff --git a/WeChatFerry/smc/Codec.lib b/WeChatFerry/smc/Codec.lib index b9943959d7ca7f96fa5040cbc649d6beff0bf35a..937e75625d291f5d22bfee3327d3da61580b2aad 100755 GIT binary patch literal 5497034 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