Merge branch 'lich0821:master' into master

This commit is contained in:
chandler 2025-03-08 18:41:53 +08:00 committed by GitHub
commit 777eb323f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 3300 additions and 3585 deletions

View File

@ -92,7 +92,7 @@ jobs:
- name: 打包输出文件及下载 WeChat 安装包
run: |
New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp"
Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll", "WeChatFerry/Out/DISCLAIMER.md" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe"
shell: pwsh

View File

@ -5,7 +5,7 @@
<details>
<summary><b>⚠️ 免责声明【必读】⚠️</b></summary>
请阅读完整的免责声明:[点击查看](DISCLAIMER.md)
请阅读完整的免责声明:[点击查看](WeChatFerry/DISCLAIMER.md)
</details>
@ -32,7 +32,6 @@
* 发送图片消息
* 发送文件消息
* 发送卡片消息
* 发送 XML 消息
* 发送 GIF 消息
* 拍一拍群友
* 转发消息
@ -168,7 +167,7 @@ sdk.WxDestroySDK()
### 调试日志
```c
DbgMsg("ListenMessage"); // 封装的 OutputDebugString
util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString
OutputDebugString(L"ListenMessage\n");
MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0);
```
@ -205,9 +204,9 @@ WeChatFerry
## 版本更新
### v39.3.5
### v39.4.1
* 代码优化
* 修复乱码问题。
<details><summary>点击查看更多</summary>
@ -219,6 +218,14 @@ WeChatFerry
* `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始
### v39.4.0
* 重构代码,适配 `3.9.12.17`。
### v39.3.5
* 代码优化
### v39.3.4
* 实现获取登录二维码

View File

@ -1,15 +1,18 @@
#pragma once
#ifdef ENABLE_DEBUG_LOG
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#endif
#include <filesystem>
#include <iomanip>
#include <memory>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <string>
#include "util.h"
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#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

View File

@ -1,177 +1,72 @@
#include "Shlwapi.h"
#include "framework.h"
#include "util.h"
#include <codecvt>
#include <filesystem>
#include <locale>
#include <string.h>
#include <optional>
#include <strsafe.h>
#include <tlhelp32.h>
#include <vector>
#include <wchar.h>
#include "framework.h"
#include <Shlwapi.h>
#include <tlhelp32.h>
#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<int>(s.size()), nullptr, 0);
std::wstring ws(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(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<int>(ws.size()), nullptr, 0, nullptr, nullptr);
std::string s(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast<int>(ws.size()), &s[0], size_needed, nullptr, nullptr);
return s;
}
string GB2312ToUtf8(const char *gb2312)
std::string gb2312_to_utf8(const char *gb2312)
{
int size_needed = 0;
if (!gb2312) return "";
size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
wstring ws(size_needed, 0);
int size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, nullptr, 0);
std::wstring ws(size_needed, 0);
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed);
size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, NULL, 0, NULL, NULL);
string s(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, &s[0], size_needed, NULL, NULL);
size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr);
std::string s(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, &s[0], size_needed, nullptr, nullptr);
return s;
}
static int GetWeChatPath(wchar_t *path)
static DWORD get_wechat_pid()
{
int ret = -1;
HKEY hKey = NULL;
// HKEY_CURRENT_USER\Software\Tencent\WeChat InstallPath = xx
if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\Tencent\\WeChat", &hKey)) {
ret = GetLastError();
return ret;
}
DWORD pid = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
DWORD Type = REG_SZ;
DWORD cbData = MAX_PATH * sizeof(WCHAR);
if (ERROR_SUCCESS != RegQueryValueEx(hKey, L"InstallPath", 0, &Type, (LPBYTE)path, &cbData)) {
ret = GetLastError();
goto __exit;
}
if (path != NULL) {
PathAppend(path, WECHAREXE);
}
__exit:
if (hKey) {
RegCloseKey(hKey);
}
return ERROR_SUCCESS;
}
static int GetWeChatWinDLLPath(wchar_t *path)
{
int ret = GetWeChatPath(path);
if (ret != ERROR_SUCCESS) {
return ret;
}
PathRemoveFileSpecW(path);
PathAppendW(path, WECHATWINDLL);
if (!PathFileExists(path)) {
// 微信从大约3.7开始,增加了一层版本目录: [3.7.0.29]
PathRemoveFileSpec(path);
_wfinddata_t findData;
wstring dir = wstring(path) + L"\\[*.*";
intptr_t handle = _wfindfirst(dir.c_str(), &findData);
if (handle == -1) { // 检查是否成功
return -1;
}
wstring dllPath = wstring(path) + L"\\" + findData.name;
wcscpy_s(path, MAX_PATH, dllPath.c_str());
PathAppend(path, WECHATWINDLL);
}
return ret;
}
static bool GetFileVersion(const wchar_t *filePath, wchar_t *version)
{
if (wcslen(filePath) > 0 && PathFileExists(filePath)) {
VS_FIXEDFILEINFO *pVerInfo = NULL;
DWORD dwTemp, dwSize;
BYTE *pData = NULL;
UINT uLen;
dwSize = GetFileVersionInfoSize(filePath, &dwTemp);
if (dwSize == 0) {
return false;
}
pData = new BYTE[dwSize + 1];
if (pData == NULL) {
return false;
}
if (!GetFileVersionInfo(filePath, 0, dwSize, pData)) {
delete[] pData;
return false;
}
if (!VerQueryValue(pData, TEXT("\\"), (void **)&pVerInfo, &uLen)) {
delete[] pData;
return false;
}
UINT64 verMS = pVerInfo->dwFileVersionMS;
UINT64 verLS = pVerInfo->dwFileVersionLS;
UINT64 major = HIWORD(verMS);
UINT64 minor = LOWORD(verMS);
UINT64 build = HIWORD(verLS);
UINT64 revision = LOWORD(verLS);
delete[] pData;
StringCbPrintf(version, 0x20, TEXT("%d.%d.%d.%d"), major, minor, build, revision);
return true;
}
return false;
}
int GetWeChatVersion(wchar_t *version)
{
WCHAR Path[MAX_PATH] = { 0 };
int ret = GetWeChatWinDLLPath(Path);
if (ret != ERROR_SUCCESS) {
return ret;
}
ret = GetFileVersion(Path, version);
return ret;
}
DWORD GetWeChatPid()
{
DWORD pid = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
while (Process32Next(hSnapshot, &pe32)) {
wstring strProcess = pe32.szExeFile;
if (strProcess == WECHAREXE) {
if (pe32.szExeFile == s2w(WECHATEXE)) {
pid = pe32.th32ProcessID;
break;
}
@ -180,168 +75,237 @@ DWORD GetWeChatPid()
return pid;
}
enum class WindowsArchiture { x32, x64 };
static WindowsArchiture GetWindowsArchitecture()
static std::optional<std::string> 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<LPBYTE>(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<std::string> 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<std::string> 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<std::string> 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<BYTE> 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<LPVOID *>(&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<LPCVOID>(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<LPCVOID>(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<char> 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<char> buffer(len + 1);
vsnprintf(buffer.data(), buffer.size(), format, args);
va_end(args);
OutputDebugStringW(s2w(buffer.data()).c_str());
}
WxString *NewWxStringFromStr(const string &str) { return NewWxStringFromWstr(String2Wstring(str)); }
WxString *NewWxStringFromWstr(const wstring &ws)
std::unique_ptr<WxString> 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<WxString> new_wx_string(const std::string &str) { return std::make_unique<WxString>(s2w(str)); }
std::unique_ptr<WxString> new_wx_string(const wchar_t *wstr)
{
return new_wx_string(wstr ? std::wstring(wstr) : std::wstring());
}
std::unique_ptr<WxString> new_wx_string(const std::wstring &wstr) { return std::make_unique<WxString>(wstr); }
AtWxidSplitResult<> parse_wxids(const std::string &atWxids)
{
AtWxidSplitResult<> result;
if (!atWxids.empty()) {
std::wstringstream wss(util::s2w(atWxids));
for (std::wstring wxid; std::getline(wss, wxid, L',');) {
result.wxids.push_back(wxid);
result.wxWxids.emplace_back(result.wxids.back());
}
}
wmemcpy(pWstring, ws.c_str(), ws.size() + 1);
p->wptr = pWstring;
p->size = (DWORD)ws.size();
p->capacity = (DWORD)ws.size();
p->ptr = 0;
p->clen = 0;
return p;
return result;
}
WxString *CreateWxString(const std::string &s)
{
std::wstring ws = util::s2w(s);
WxString *wxStr = reinterpret_cast<WxString *>(HeapAlloc(GetProcessHeap(), 8, sizeof(WxString)));
if (!wxStr) return nullptr;
size_t len = ws.length();
wchar_t *ptr = reinterpret_cast<wchar_t *>(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<DWORD>(ws.size());
wxStr->length = static_cast<DWORD>(ws.length());
wxStr->clen = 0;
wxStr->ptr = nullptr;
return wxStr;
}
void FreeWxString(WxString *wxStr)
{
if (wxStr) {
if (wxStr->wptr) HeapFree(GetProcessHeap(), 8, const_cast<wchar_t *>(wxStr->wptr));
HeapFree(GetProcessHeap(), 8, wxStr);
}
}
} // namespace util

View File

@ -1,41 +1,105 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#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<DWORD *>(addr) : 0; }
inline QWORD get_qword(uint64_t addr) { return addr ? *reinterpret_cast<QWORD *>(addr) : 0; }
inline uint64_t get_uint64(uint64_t addr) { return addr ? *reinterpret_cast<uint64_t *>(addr) : 0; }
inline std::string get_p_string(uint64_t addr) { return addr ? std::string(reinterpret_cast<const char *>(addr)) : ""; }
inline std::string get_p_string(uint64_t addr, size_t len)
{
return addr ? std::string(reinterpret_cast<const char *>(addr), len) : "";
}
inline std::wstring get_p_wstring(uint64_t addr)
{
return addr ? std::wstring(reinterpret_cast<const wchar_t *>(addr)) : L"";
}
inline std::wstring get_p_wstring(uint64_t addr, size_t len)
{
return addr ? std::wstring(reinterpret_cast<const wchar_t *>(addr), len) : L"";
}
inline std::string get_pp_string(uint64_t addr)
{
if (!addr) return "";
const char *ptr = *reinterpret_cast<const char **>(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<const wchar_t **>(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<const char **>(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<const wchar_t **>(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 <typename T> static T *AllocBuffer(size_t count)
{
return reinterpret_cast<T *>(HeapAlloc(GetProcessHeap(), 8, sizeof(T) * count));
}
template <typename T> struct WxStringHolder {
std::wstring ws;
WxString wx;
explicit WxStringHolder(const T &str) : ws(util::s2w(str)), wx(ws) { }
};
template <typename StringT = std::wstring> struct AtWxidSplitResult {
std::vector<StringT> wxids;
std::vector<WxString> wxWxids;
};
WxString *CreateWxString(const std::string &s);
void FreeWxString(WxString *wxStr);
AtWxidSplitResult<> parse_wxids(const std::string &atWxids);
std::unique_ptr<WxString> new_wx_string(const char *str);
std::unique_ptr<WxString> new_wx_string(const wchar_t *wstr);
std::unique_ptr<WxString> new_wx_string(const std::string &str);
std::unique_ptr<WxString> new_wx_string(const std::wstring &wstr);
} // namespace util

View File

@ -68,7 +68,6 @@ bool encode_contacts(pb_ostream_t *stream, const pb_field_t *field, void *const
vector<RpcContact_t> *v = (vector<RpcContact_t> *)*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();

View File

@ -141,7 +141,6 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry</Com
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\com\log.hpp" />
<ClInclude Include="..\com\util.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="injector.h" />

View File

@ -27,9 +27,6 @@
<ClInclude Include="..\com\util.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="..\com\log.hpp">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">

View File

@ -1,112 +1,110 @@
#include "framework.h"
#include "psapi.h"
#include <filesystem>
#include <string>
#include "injector.h"
#include <filesystem>
#include "psapi.h"
#include "injector.h"
#include "util.h"
using namespace std;
HMODULE GetTargetModuleBase(HANDLE process, string dll)
static void handle_injection_error(HANDLE process, LPVOID remote_address, const std::string &error_msg)
{
DWORD cbNeeded;
HMODULE moduleHandleList[512];
BOOL ret = EnumProcessModulesEx(process, moduleHandleList, sizeof(moduleHandleList), &cbNeeded, LIST_MODULES_64BIT);
if (!ret) {
MessageBox(NULL, L"获取模块失败", L"GetTargetModuleBase", 0);
util::MsgBox(NULL, error_msg.c_str(), "Error", MB_ICONERROR);
if (remote_address) {
VirtualFreeEx(process, remote_address, 0, MEM_RELEASE);
}
if (process) {
CloseHandle(process);
}
}
HMODULE get_target_module_base(HANDLE process, const string &dll)
{
DWORD needed;
HMODULE modules[512];
if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) {
util::MsgBox(NULL, "获取模块失败", "get_target_module_base", 0);
return NULL;
}
if (cbNeeded > sizeof(moduleHandleList)) {
MessageBox(NULL, L"模块数量过多", L"GetTargetModuleBase", 0);
return NULL;
}
DWORD processCount = cbNeeded / sizeof(HMODULE);
char moduleName[32];
for (DWORD i = 0; i < processCount; i++) {
GetModuleBaseNameA(process, moduleHandleList[i], moduleName, 32);
if (!strncmp(dll.c_str(), moduleName, dll.size())) {
return moduleHandleList[i];
DWORD count = needed / sizeof(HMODULE);
char module_name[MAX_PATH];
for (DWORD i = 0; i < count; i++) {
GetModuleBaseNameA(process, modules[i], module_name, sizeof(module_name));
if (!strncmp(dll.c_str(), module_name, dll.size())) {
return modules[i];
}
}
return NULL;
}
HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase)
HANDLE inject_dll(DWORD pid, const string &dll_path, HMODULE *injected_base)
{
HANDLE hThread;
SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR);
SIZE_T path_size = dll_path.size() + 1;
// 1. 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess == NULL) {
MessageBox(NULL, L"打开进程失败", L"InjectDll", 0);
if (!hProcess) {
util::MsgBox(NULL, "打开进程失败", "inject_dll", 0);
return NULL;
}
// 2. 在目标进程的内存里开辟空间
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE);
if (pRemoteAddress == NULL) {
MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0);
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteAddress) {
handle_injection_error(hProcess, NULL, "DLL 路径写入失败");
return NULL;
}
// 3. 把 dll 的路径写入到目标进程的内存空间中
WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL);
WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL);
// 3. 创建一个远程线程,让目标进程调用 LoadLibrary
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
if (k32 == NULL) {
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
// 4. 创建一个远程线程,让目标进程调用 LoadLibrary
HMODULE k32 = GetModuleHandleA("kernel32.dll");
if (!k32) {
handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败");
return NULL;
}
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryW");
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA");
if (!libAddr) {
MessageBox(NULL, L"获取 LoadLibrary 失败", L"InjectDll", 0);
handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败");
return NULL;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
if (hThread == NULL) {
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
CloseHandle(hProcess);
MessageBox(NULL, L"CreateRemoteThread 失败", L"InjectDll", 0);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
if (!hThread) {
handle_injection_error(hProcess, pRemoteAddress, "CreateRemoteThread 失败");
return NULL;
}
WaitForSingleObject(hThread, -1);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
*injectedBase = GetTargetModuleBase(hProcess, filesystem::path(Wstring2String(dllPath)).filename().string());
*injected_base = get_target_module_base(hProcess, filesystem::path(dll_path).filename().string());
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
// CloseHandle(hProcess); // Close when exit
return hProcess;
}
bool EjectDll(HANDLE process, HMODULE dllBase)
bool eject_dll(HANDLE process, HMODULE dll_base)
{
HANDLE hThread = NULL;
// 使目标进程调用 FreeLibrary卸载 DLL
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
if (k32 == NULL) {
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
return NULL;
HMODULE k32 = GetModuleHandleA("kernel32.dll");
if (!k32) {
util::MsgBox(NULL, "获取 kernel32 失败", "eject_dll", 0);
return false;
}
FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread");
if (!libAddr) {
MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0);
return NULL;
util::MsgBox(NULL, "获取 FreeLibrary 失败", "eject_dll", 0);
return false;
}
hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dllBase, 0, NULL);
if (hThread == NULL) {
MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0);
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL);
if (!hThread) {
util::MsgBox(NULL, "FreeLibrary 调用失败!", "eject_dll", 0);
return false;
}
@ -116,38 +114,34 @@ bool EjectDll(HANDLE process, HMODULE dllBase)
return true;
}
static UINT64 GetFuncOffset(LPCWSTR dllPath, LPCSTR funcName)
static uint64_t get_func_offset(const string &dll_path, const string &func_name)
{
HMODULE dll = LoadLibrary(dllPath);
if (dll == NULL) {
MessageBox(NULL, L"获取 DLL 失败", L"GetFuncOffset", 0);
HMODULE dll = LoadLibraryA(dll_path.c_str());
if (!dll) {
util::MsgBox(NULL, "获取 DLL 失败", "get_func_offset", 0);
return 0;
}
LPVOID absAddr = GetProcAddress(dll, funcName);
UINT64 offset = (UINT64)absAddr - (UINT64)dll;
LPVOID absAddr = GetProcAddress(dll, func_name.c_str());
uint64_t offset = reinterpret_cast<uint64_t>(absAddr) - reinterpret_cast<uint64_t>(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<uint64_t>(dll_base))) {
return false; // 避免溢出
}
UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
if (pFunc <= (UINT64)dllBase) {
return false;
}
uint64_t pFunc = reinterpret_cast<uint64_t>(dll_base) + offset;
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL);
if (hThread == NULL) {
if (!hThread) {
return false;
}
WaitForSingleObject(hThread, INFINITE);
if (ret != NULL) {
if (ret) {
GetExitCodeThread(hThread, ret);
}
@ -155,35 +149,32 @@ bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcNa
return true;
}
bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
LPDWORD ret)
bool call_dll_func_ex(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name,
LPVOID parameter, size_t size, DWORD *ret)
{
UINT64 offset = GetFuncOffset(dllPath, funcName);
if (offset == 0) {
return false;
uint64_t offset = get_func_offset(dll_path, func_name);
if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast<uint64_t>(dll_base))) {
return false; // 避免溢出
}
UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
if (pFunc <= (UINT64)dllBase) {
uint64_t pFunc = reinterpret_cast<uint64_t>(dll_base) + offset;
LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteAddress) {
util::MsgBox(NULL, "申请内存失败", "call_dll_func_ex", 0);
return false;
}
LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, sz, MEM_COMMIT, PAGE_READWRITE);
if (pRemoteAddress == NULL) {
MessageBox(NULL, L"申请内存失败", L"CallDllFuncEx", 0);
return NULL;
}
WriteProcessMemory(process, pRemoteAddress, parameter, sz, NULL);
WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL);
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL);
if (hThread == NULL) {
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0);
if (!hThread) {
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
util::MsgBox(NULL, "远程调用失败", "call_dll_func_ex", 0);
return false;
}
WaitForSingleObject(hThread, INFINITE);
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
if (ret != NULL) {
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
if (ret) {
GetExitCodeThread(hThread, ret);
}

View File

@ -1,9 +1,11 @@
#pragma once
#include <string>
#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);

View File

@ -1,4 +1,5 @@
#include "framework.h"
#include "sdk.h"
#include <chrono>
#include <filesystem>
#include <fstream>
@ -6,55 +7,67 @@
#include <process.h>
#include <sstream>
#include <thread>
#include "framework.h"
#include <tlhelp32.h>
#include "injector.h"
#include "sdk.h"
#include "util.h"
static BOOL injected = false;
extern "C" IMAGE_DOS_HEADER __ImageBase;
static bool injected = false;
static HANDLE wcProcess = NULL;
static HMODULE spyBase = NULL;
static std::wstring spyDllPath;
static std::string spyDllPath;
constexpr char DISCLAIMER_FILE[] = ".license_accepted.flag";
constexpr char DISCLAIMER_TEXT_FILE[] = "DISCLAIMER.md";
constexpr char WCFSDKDLL[] = "sdk.dll";
constexpr char WCFSPYDLL[] = "spy.dll";
constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll";
static std::optional<std::wstring> ReadDisclaimerText(const char *filePath)
constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag";
constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md";
namespace fs = std::filesystem;
static fs::path get_module_directory()
{
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open()) {
return std::nullopt; // 文件打开失败
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return String2Wstring(content);
char buffer[MAX_PATH] = { 0 };
HMODULE hModule = reinterpret_cast<HMODULE>(&__ImageBase);
GetModuleFileNameA(hModule, buffer, MAX_PATH);
fs::path modulePath(buffer);
return modulePath.parent_path();
}
static bool ShowDisclaimer()
static bool show_disclaimer()
{
if (std::filesystem::exists(DISCLAIMER_FILE)) {
fs::path sdk_path = get_module_directory();
if (fs::exists(sdk_path / DISCLAIMER_FLAG)) {
return true;
}
std::optional<std::wstring> disclaimerTextOpt = ReadDisclaimerText(DISCLAIMER_TEXT_FILE);
if (!disclaimerTextOpt.has_value() || disclaimerTextOpt->empty()) {
MessageBox(NULL, L"免责声明文件为空或读取失败。", L"错误", MB_ICONERROR);
fs::path path = sdk_path / DISCLAIMER_TEXT_FILE;
std::ifstream file(path, std::ios::binary);
if (!file.is_open()) {
util::MsgBox(NULL, "免责声明文件读取失败。", "错误", MB_ICONERROR);
return false;
}
std::wstring disclaimerText = *disclaimerTextOpt;
int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
auto disclaimerText = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (disclaimerText.empty()) {
util::MsgBox(NULL, "免责声明文件为空", "错误", MB_ICONERROR);
return false;
}
int result = util::MsgBox(NULL, disclaimerText.c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
if (result == IDCANCEL) {
MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION);
util::MsgBox(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION);
return false;
}
std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc);
std::ofstream flagFile(sdk_path / DISCLAIMER_FLAG, std::ios::out | std::ios::trunc);
if (!flagFile) {
MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR);
util::MsgBox(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR);
return false;
}
flagFile << "User accepted the license agreement.";
@ -62,82 +75,79 @@ static bool ShowDisclaimer()
return true;
}
static std::wstring GetDllPath(bool debug)
static std::string get_dll_path(bool debug)
{
WCHAR buffer[MAX_PATH] = { 0 };
GetModuleFileName(GetModuleHandle(WCFSDKDLL), buffer, MAX_PATH);
std::filesystem::path path(buffer);
path.remove_filename(); // 移除文件名,保留目录路径
char buffer[MAX_PATH] = { 0 };
GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH);
fs::path path(buffer);
path.remove_filename(); // 只保留目录路径
path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL;
if (!std::filesystem::exists(path)) {
MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR);
return L"";
if (!fs::exists(path)) {
util::MsgBox(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR);
return "";
}
return path.wstring();
return path.string();
}
int WxInitSDK(bool debug, int port)
{
if (!ShowDisclaimer()) {
if (!show_disclaimer()) {
exit(-1); // 用户拒绝协议,退出程序
}
int status = 0;
DWORD wcPid = 0;
spyDllPath = GetDllPath(debug);
spyDllPath = get_dll_path(debug);
if (spyDllPath.empty()) {
return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在
}
status = OpenWeChat(&wcPid);
status = util::open_wechat(wcPid);
if (status != 0) {
MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0);
util::MsgBox(NULL, "打开微信失败", "WxInitSDK", 0);
return status;
}
if (!IsProcessX64(wcPid)) {
MessageBox(NULL, L"只支持 64 位微信", L"WxInitSDK", 0);
return -1;
}
std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开
wcProcess = InjectDll(wcPid, spyDllPath.c_str(), &spyBase);
wcProcess = inject_dll(wcPid, spyDllPath, &spyBase);
if (wcProcess == NULL) {
MessageBox(NULL, L"注入失败", L"WxInitSDK", 0);
util::MsgBox(NULL, "注入失败", "WxInitSDK", 0);
return -1;
}
PortPath_t pp = { 0 };
pp.port = port;
sprintf_s(pp.path, MAX_PATH, "%s", std::filesystem::current_path().string().c_str());
if (!CallDllFuncEx(wcProcess, spyDllPath.c_str(), spyBase, "InitSpy", (LPVOID)&pp, sizeof(PortPath_t), NULL)) {
MessageBox(NULL, L"初始化失败", L"WxInitSDK", 0);
return -1;
}
injected = true;
return 0;
util::PortPath pp = { 0 };
pp.port = port;
snprintf(pp.path, MAX_PATH, "%s", fs::current_path().string().c_str());
status = -3; // TODO: 统一错误码
bool success = call_dll_func_ex(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(util::PortPath),
(DWORD *)&status);
if (!success || status != 0) {
WxDestroySDK();
}
return status;
}
int WxDestroySDK()
{
if (!injected) {
return 1; // 未注入
}
if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) {
return -1;
}
if (!CallDllFunc(wcProcess, spyDllPath.c_str(), spyBase, "CleanupSpy", NULL)) {
if (!eject_dll(wcProcess, spyBase)) {
return -2;
}
if (!EjectDll(wcProcess, spyBase)) {
return -3; // TODO: Unify error codes
}
injected = false;
return 0;
}

View File

@ -247,19 +247,20 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
<ClInclude Include="..\rpc\pb_util.h" />
<ClInclude Include="..\rpc\proto\wcf.pb.h" />
<ClInclude Include="..\smc\codec.h" />
<ClInclude Include="chatroom_mgmt.h" />
<ClInclude Include="funcs.h" />
<ClInclude Include="exec_sql.h" />
<ClInclude Include="chatroom_manager.h" />
<ClInclude Include="misc_manager.h" />
<ClInclude Include="database_executor.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="contact_mgmt.h" />
<ClInclude Include="receive_msg.h" />
<ClInclude Include="contact_manager.h" />
<ClInclude Include="message_handler.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="rpc_helper.h" />
<ClInclude Include="rpc_server.h" />
<ClInclude Include="send_msg.h" />
<ClInclude Include="message_sender.h" />
<ClInclude Include="spy.h" />
<ClInclude Include="spy_types.h" />
<ClInclude Include="sqlite3.h" />
<ClInclude Include="user_info.h" />
<ClInclude Include="account_manager.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\com\util.cpp" />
@ -268,16 +269,16 @@ xcopy /y $(SolutionDir)DISCLAIMER.md $(SolutionDir)..\clients\python\wcferry</Co
<ClCompile Include="..\rpc\nanopb\pb_encode.c" />
<ClCompile Include="..\rpc\pb_util.cpp" />
<ClCompile Include="..\rpc\proto\wcf.pb.c" />
<ClCompile Include="chatroom_mgmt.cpp" />
<ClCompile Include="funcs.cpp" />
<ClCompile Include="chatroom_manager.cpp" />
<ClCompile Include="misc_manager.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="exec_sql.cpp" />
<ClCompile Include="contact_mgmt.cpp" />
<ClCompile Include="receive_msg.cpp" />
<ClCompile Include="database_executor.cpp" />
<ClCompile Include="contact_manager.cpp" />
<ClCompile Include="message_handler.cpp" />
<ClCompile Include="rpc_server.cpp" />
<ClCompile Include="send_msg.cpp" />
<ClCompile Include="message_sender.cpp" />
<ClCompile Include="spy.cpp" />
<ClCompile Include="user_info.cpp" />
<ClCompile Include="account_manager.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\rpc\proto\wcf.proto" />

View File

@ -24,16 +24,16 @@
<ClInclude Include="rpc_server.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="exec_sql.h">
<ClInclude Include="database_executor.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="contact_mgmt.h">
<ClInclude Include="contact_manager.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="receive_msg.h">
<ClInclude Include="message_handler.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="send_msg.h">
<ClInclude Include="message_sender.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="spy.h">
@ -63,16 +63,16 @@
<ClInclude Include="..\rpc\pb_types.h">
<Filter>nnrpc</Filter>
</ClInclude>
<ClInclude Include="chatroom_mgmt.h">
<ClInclude Include="chatroom_manager.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="user_info.h">
<ClInclude Include="account_manager.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="funcs.h">
<ClInclude Include="misc_manager.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="sqlite3.h">
@ -87,6 +87,9 @@
<ClInclude Include="..\com\log.hpp">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="rpc_helper.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
@ -95,16 +98,16 @@
<ClCompile Include="rpc_server.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="exec_sql.cpp">
<ClCompile Include="database_executor.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="contact_mgmt.cpp">
<ClCompile Include="contact_manager.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="receive_msg.cpp">
<ClCompile Include="message_handler.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="send_msg.cpp">
<ClCompile Include="message_sender.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="spy.cpp">
@ -125,13 +128,13 @@
<ClCompile Include="..\rpc\pb_util.cpp">
<Filter>nnrpc</Filter>
</ClCompile>
<ClCompile Include="chatroom_mgmt.cpp">
<ClCompile Include="chatroom_manager.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="user_info.cpp">
<ClCompile Include="account_manager.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="funcs.cpp">
<ClCompile Include="misc_manager.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="..\com\util.cpp">

View File

@ -0,0 +1,110 @@
#include "account_manager.h"
#include <filesystem>
#include "log.hpp"
#include "offsets.h"
#include "rpc_helper.h"
#include "spy.h"
#include "util.h"
namespace account
{
namespace fs = std::filesystem;
namespace OsAcc = Offsets::Account;
using get_account_service_t = QWORD (*)();
using get_data_path_t = QWORD (*)(QWORD);
// 缓存避免重复查询
static std::optional<std::string> cachedWxid;
static std::optional<fs::path> cachedHomePath;
// 清除缓存
static void clear_cached_wxid() { cachedWxid.reset(); }
static void clear_cached_home_path() { cachedHomePath.reset(); }
static uint64_t get_account_service()
{
static auto GetService = Spy::getFunction<get_account_service_t>(OsAcc::SERVICE);
return GetService ? GetService() : 0;
}
static std::string get_string_value(uint64_t base_addr, uint64_t offset)
{
uint64_t type = util::get_qword(base_addr + offset + 0x18);
return (type == 0xF) ? util::get_p_string(base_addr + offset) : util::get_pp_string(base_addr + offset);
}
bool is_logged_in()
{
clear_cached_wxid();
clear_cached_home_path();
uint64_t service_addr = get_account_service();
return service_addr && util::get_qword(service_addr + OsAcc::LOGIN) != 0;
}
fs::path get_home_path()
{
if (cachedHomePath) {
return *cachedHomePath;
}
WxString home;
auto GetDataPath = Spy::getFunction<get_data_path_t>(OsAcc::PATH);
int64_t service_addr = get_account_service();
GetDataPath((QWORD)&home);
if (home.wptr) {
cachedHomePath = util::w2s(std::wstring(home.wptr, home.size));
}
return *cachedHomePath;
}
std::string get_self_wxid()
{
if (cachedWxid) {
return *cachedWxid;
}
uint64_t service_addr = get_account_service();
if (!service_addr) return "";
cachedWxid = get_string_value(service_addr, OsAcc::WXID);
return *cachedWxid;
}
UserInfo_t get_user_info()
{
UserInfo_t ui;
uint64_t service_addr = get_account_service();
if (!service_addr) return ui;
ui.wxid = get_self_wxid();
ui.home = get_home_path().generic_string();
ui.name = get_string_value(service_addr, OsAcc::NAME);
ui.mobile = get_string_value(service_addr, OsAcc::MOBILE);
return ui;
}
bool rpc_is_logged_in(uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_IS_LOGIN>(out, len, [](Response &rsp) { rsp.msg.status = is_logged_in(); });
}
bool rpc_get_self_wxid(uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_GET_SELF_WXID>(
out, len, [](Response &rsp) { rsp.msg.str = (char *)get_self_wxid().c_str(); });
}
bool rpc_get_user_info(uint8_t *out, size_t *len)
{
UserInfo_t ui = get_user_info();
return fill_response<Functions_FUNC_GET_USER_INFO>(out, len, ui, [](Response &rsp, UserInfo_t &ui) {
rsp.msg.ui.wxid = (char *)ui.wxid.c_str();
rsp.msg.ui.name = (char *)ui.name.c_str();
rsp.msg.ui.mobile = (char *)ui.mobile.c_str();
rsp.msg.ui.home = (char *)ui.home.c_str();
});
}
} // namespace account

View File

@ -0,0 +1,29 @@
#pragma once
#include <filesystem>
#include <optional>
#include <string>
#include "pb_types.h"
namespace account
{
// 登录状态
bool is_logged_in();
// 获取 WeChat 数据存储路径
std::filesystem::path get_home_path();
// 获取自身 wxid
std::string get_self_wxid();
// 获取用户信息
UserInfo_t get_user_info();
// RPC 方法
bool rpc_is_logged_in(uint8_t *out, size_t *len);
bool rpc_get_self_wxid(uint8_t *out, size_t *len);
bool rpc_get_user_info(uint8_t *out, size_t *len);
} // namespace account

View File

@ -0,0 +1,87 @@
#pragma execution_character_set("utf-8")
#include "chatroom_manager.h"
#include "log.hpp"
#include "offsets.h"
#include "pb_util.h"
#include "rpc_helper.h"
#include "spy.h"
#include "util.h"
namespace chatroom
{
namespace OsRoom = Offsets::Chatroom;
using get_mgr_t = QWORD (*)();
using add_member_t = QWORD (*)(QWORD, QWORD, WxString *, QWORD);
using delete_member_t = QWORD (*)(QWORD, QWORD, WxString *);
using invite_members_t = QWORD (*)(const wchar_t *, QWORD, WxString *, QWORD);
template <auto FillFunc, typename Func>
bool rpc_chatroom_common(const MemberMgmt &m, uint8_t *out, size_t *len, Func func)
{
int status = -1;
if (m.wxids && m.roomid) {
status = func(m.roomid, m.wxids);
} else {
LOG_ERROR("wxid 和 roomid 不能为空");
}
return fill_response<FillFunc>(out, len, [&](Response &rsp) { rsp.msg.status = status; });
}
int add_chatroom_member(const string &roomid, const string &wxids)
{
auto get_chatroom_mgr = Spy::getFunction<get_mgr_t>(OsRoom::MGR);
auto add_members = Spy::getFunction<add_member_t>(OsRoom::ADD);
WxString *wx_roomid = util::CreateWxString(roomid);
QWORD tmp[2] = { 0 };
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
return static_cast<int>(add_members(get_chatroom_mgr(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
}
int del_chatroom_member(const string &roomid, const string &wxids)
{
auto get_chatroom_mgr = Spy::getFunction<get_mgr_t>(OsRoom::MGR);
auto del_members = Spy::getFunction<delete_member_t>(OsRoom::DEL);
WxString *wx_roomid = util::CreateWxString(roomid);
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
return static_cast<int>(del_members(get_chatroom_mgr(), p_members, wx_roomid));
}
int invite_chatroom_member(const string &roomid, const string &wxids)
{
auto invite_members = Spy::getFunction<invite_members_t>(OsRoom::INV);
wstring ws_roomid = util::s2w(roomid);
WxString *wx_roomid = util::CreateWxString(roomid);
QWORD tmp[2] = { 0 };
auto wx_members = util::parse_wxids(wxids).wxWxids;
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
return static_cast<int>(invite_members(ws_roomid.c_str(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
}
bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
{
return rpc_chatroom_common<Functions_FUNC_ADD_ROOM_MEMBERS>(m, out, len, add_chatroom_member);
}
bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
{
return rpc_chatroom_common<Functions_FUNC_DEL_ROOM_MEMBERS>(m, out, len, del_chatroom_member);
}
bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
{
return rpc_chatroom_common<Functions_FUNC_INV_ROOM_MEMBERS>(m, out, len, invite_chatroom_member);
}
} // namespace chatroom

View File

@ -0,0 +1,24 @@
#pragma once
#include <string>
#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

View File

@ -3,16 +3,16 @@
#include <vector>
#include "chatroom_mgmt.h"
#include "log.hpp"
#include "log.h"
#include "util.h"
using namespace std;
extern QWORD g_WeChatWinDllAddr;
#define OS_GET_CHATROOM_MGR 0x1B83BD0
#define OS_ADD_MEMBERS 0x2155100
#define OS_DELETE_MEMBERS 0x2155740
#define OS_INVITE_MEMBERS 0x2154AE0
#define OS_GET_CHATROOM_MGR 0x1B894E0
#define OS_ADD_MEMBERS 0x215A820
#define OS_DELETE_MEMBERS 0x215AE60
#define OS_INVITE_MEMBERS 0x215A200
typedef QWORD (*GetChatRoomMgr_t)();
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
@ -111,3 +111,4 @@ int InviteChatroomMember(string roomid, string wxids)
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
return status;
}

View File

@ -1,7 +0,0 @@
#pragma once
#include <string>
int AddChatroomMember(std::string roomid, std::string wxids);
int DelChatroomMember(std::string roomid, std::string wxids);
int InviteChatroomMember(std::string roomid, std::string wxids);

View File

@ -0,0 +1,131 @@
#pragma execution_character_set("utf-8")
#include "contact_manager.h"
#include "log.hpp"
#include "offsets.h"
#include "pb_util.h"
#include "rpc_helper.h"
#include "spy.h"
#include "util.h"
using namespace std;
namespace contact
{
namespace OsCon = Offsets::Contact;
using get_contact_mgr_t = QWORD (*)();
using get_contact_list_t = QWORD (*)(QWORD, QWORD);
#define FEAT_LEN 5
static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 };
static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 };
static QWORD find_mem(QWORD start, QWORD end, const void *target, size_t len)
{
uint8_t *p = reinterpret_cast<uint8_t *>(start);
while (reinterpret_cast<QWORD>(p) < end) {
if (memcmp(p, target, len) == 0) {
return reinterpret_cast<QWORD>(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<RpcContact_t> get_contacts()
{
vector<RpcContact_t> contacts;
auto func_get_contact_mgr = Spy::getFunction<get_contact_mgr_t>(OsCon::MGR);
auto func_get_contact_list = Spy::getFunction<get_contact_list_t>(OsCon::LIST);
QWORD mgr = func_get_contact_mgr();
QWORD addr[3] = { 0 };
if (func_get_contact_list(mgr, reinterpret_cast<QWORD>(addr)) != 1) {
LOG_ERROR("get_contacts failed");
return contacts;
}
QWORD pstart = addr[0];
QWORD pend = addr[2];
while (pstart < pend) {
RpcContact_t cnt;
QWORD pbin = util::get_qword(pstart + OsCon::BIN);
QWORD lenbin = util::get_dword(pstart + OsCon::BIN_LEN);
cnt.wxid = util::get_str_by_wstr_addr(pstart + OsCon::WXID);
cnt.code = util::get_str_by_wstr_addr(pstart + OsCon::CODE);
cnt.remark = util::get_str_by_wstr_addr(pstart + OsCon::REMARK);
cnt.name = util::get_str_by_wstr_addr(pstart + OsCon::NAME);
cnt.country = get_cnt_string(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN);
cnt.province = get_cnt_string(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN);
cnt.city = get_cnt_string(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN);
cnt.gender = (pbin == 0) ? 0 : static_cast<DWORD>(*(uint8_t *)(pbin + OsCon::GENDER));
contacts.push_back(cnt);
pstart += OsCon::STEP;
}
return contacts;
}
int accept_new_friend(const std::string &v3, const std::string &v4, int scene)
{
LOG_ERROR("技术太菜,实现不了。");
return -1; // 成功返回 1
}
RpcContact_t get_contact_by_wxid(const string &wxid)
{
RpcContact_t contact;
LOG_ERROR("技术太菜,实现不了。");
return contact;
}
bool rpc_get_contacts(uint8_t *out, size_t *len)
{
vector<RpcContact_t> contacts = get_contacts();
return fill_response<Functions_FUNC_GET_CONTACTS>(out, len, [&](Response &rsp) {
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
rsp.msg.contacts.contacts.arg = &contacts;
});
}
bool rpc_get_contact_info(const string &wxid, uint8_t *out, size_t *len)
{
vector<RpcContact_t> contacts = { get_contact_by_wxid(wxid) };
return fill_response<Functions_FUNC_GET_CONTACT_INFO>(out, len, [&](Response &rsp) {
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
rsp.msg.contacts.contacts.arg = &contacts;
});
}
bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len)
{
const string v3 = v.v3 ? v.v3 : "";
const string v4 = v.v4 ? v.v4 : "";
int scene = v.scene;
return fill_response<Functions_FUNC_ACCEPT_FRIEND>(
out, len, [&](Response &rsp) { rsp.msg.status = accept_new_friend(v3, v4, scene); });
}
} // namespace contact

View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
#include "wcf.pb.h"
#include "pb_types.h"
namespace contact
{
// 获取所有联系人
std::vector<RpcContact_t> 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

View File

@ -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<RpcContact_t> GetContacts()
{
vector<RpcContact_t> 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

View File

@ -1,11 +0,0 @@
#pragma once
#include "string"
#include <vector>
#include "pb_types.h"
vector<RpcContact_t> 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);

View File

@ -0,0 +1,277 @@
#include "database_executor.h"
#include <algorithm>
#include <iterator>
#include "log.hpp"
#include "offsets.h"
#include "pb_util.h"
#include "rpc_helper.h"
#include "spy.h"
#include "sqlite3.h"
#include "util.h"
namespace db
{
namespace OsDb = Offsets::Db;
using db_map_t = std::map<std::string, QWORD>;
static db_map_t db_map;
static void get_db_handle(QWORD base, QWORD offset)
{
auto *wsp = reinterpret_cast<wchar_t *>(*(QWORD *)(base + offset + OsDb::NAME));
std::string dbname = util::w2s(std::wstring(wsp));
db_map[dbname] = util::get_qword(base + offset);
}
static void get_msg_db_handle(QWORD msg_mgr_addr)
{
QWORD db_index = util::get_qword(msg_mgr_addr + 0x68);
QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
for (uint32_t i = 0; i < db_index; i++) {
QWORD db_addr = util::get_qword(p_start + i * 0x08);
if (db_addr) {
// MSGi.db
std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
db_map[dbname] = util::get_qword(db_addr + 0x78);
// MediaMsgi.db
QWORD mmdb_addr = util::get_qword(db_addr + 0x20);
std::string mmdbname = util::w2s(util::get_pp_wstring(mmdb_addr + 0x78));
db_map[mmdbname] = util::get_qword(mmdb_addr + 0x50);
}
}
}
db_map_t get_db_handles()
{
db_map.clear();
QWORD db_instance_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::INSTANCE);
get_db_handle(db_instance_addr, OsDb::MICROMSG); // MicroMsg.db
get_db_handle(db_instance_addr, OsDb::CHAT_MSG); // ChatMsg.db
get_db_handle(db_instance_addr, OsDb::MISC); // Misc.db
get_db_handle(db_instance_addr, OsDb::EMOTION); // Emotion.db
get_db_handle(db_instance_addr, OsDb::MEDIA); // Media.db
get_db_handle(db_instance_addr, OsDb::FUNCTION_MSG); // Function.db
get_msg_db_handle(util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I)); // MSGi.db & MediaMsgi.db
return db_map;
}
DbNames_t get_db_names()
{
if (db_map.empty()) {
db_map = get_db_handles();
}
DbNames_t names;
for (const auto &[k, _] : db_map) {
names.push_back(k);
}
return names;
}
static int cb_get_tables(void *ret, int argc, char **argv, char **azColName)
{
auto *tables = static_cast<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) {
std::string sql(argv[i]);
sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end());
tbl.sql = sql;
}
}
tables->push_back(tbl);
return 0;
}
DbTables_t get_db_tables(const std::string &db)
{
DbTables_t tables;
if (db_map.empty()) {
db_map = get_db_handles();
}
auto it = db_map.find(db);
if (it == db_map.end()) {
return tables;
}
constexpr const char *sql = "SELECT name FROM sqlite_master WHERE type='table';";
auto p_sqlite3_exec = Spy::getFunction<Sqlite3_exec>(OsDb::EXEC);
p_sqlite3_exec(it->second, sql, (Sqlite3_callback)cb_get_tables, (void *)&tables, nullptr);
return tables;
}
DbRows_t exec_db_query(const std::string &db, const std::string &sql)
{
DbRows_t rows;
auto func_prepare = Spy::getFunction<Sqlite3_prepare>(OsDb::PREPARE);
auto func_step = Spy::getFunction<Sqlite3_step>(OsDb::STEP);
auto func_column_count = Spy::getFunction<Sqlite3_column_count>(OsDb::COLUMN_COUNT);
auto func_column_name = Spy::getFunction<Sqlite3_column_name>(OsDb::COLUMN_NAME);
auto func_column_type = Spy::getFunction<Sqlite3_column_type>(OsDb::COLUMN_TYPE);
auto func_column_blob = Spy::getFunction<Sqlite3_column_blob>(OsDb::COLUMN_BLOB);
auto func_column_bytes = Spy::getFunction<Sqlite3_column_bytes>(OsDb::COLUMN_BYTES);
auto func_finalize = Spy::getFunction<Sqlite3_finalize>(OsDb::FINALIZE);
if (db_map.empty()) {
db_map = get_db_handles();
}
auto it = db_map.find(db);
if (it == db_map.end() || it->second == 0) {
LOG_WARN("Empty handle for database '{}', retrying...", db);
db_map = get_db_handles();
it = db_map.find(db);
if (it == db_map.end() || it->second == 0) {
LOG_ERROR("Failed to get handle for database '{}'", db);
return rows;
}
}
QWORD *stmt;
int rc = func_prepare(it->second, sql.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
LOG_ERROR("SQL prepare failed for '{}': error code {}", db, rc);
return rows;
}
while (func_step(stmt) == SQLITE_ROW) {
DbRow_t row;
int col_count = func_column_count(stmt);
for (int i = 0; i < col_count; i++) {
DbField_t field;
field.type = func_column_type(stmt, i);
field.column = func_column_name(stmt, i);
int length = func_column_bytes(stmt, i);
const void *blob = func_column_blob(stmt, i);
if (length > 0 && field.type != SQLITE_NULL) {
field.content.resize(length);
std::memcpy(field.content.data(), blob, length);
}
row.push_back(field);
}
rows.push_back(row);
}
func_finalize(stmt);
return rows;
}
int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx)
{
if (!local_id || !db_idx) {
LOG_ERROR("Invalid pointer arguments!");
return -1;
}
QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
int db_index = static_cast<int>(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<uint32_t>(util::get_qword(util::get_qword(db_addr + 0x28) + 0x1E8) >> 32);
return 0;
}
return -1;
}
std::vector<uint8_t> get_audio_data(uint64_t id)
{
QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
int db_index = static_cast<int>(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<uint8_t>(field.content.begin() + 1, field.content.end());
}
return field.content;
}
return {};
}
bool rpc_get_db_names(uint8_t *out, size_t *len)
{
DbNames_t names = get_db_names();
return fill_response<Functions_FUNC_GET_DB_NAMES>(out, len, [&](Response &rsp) {
rsp.msg.dbs.names.funcs.encode = encode_dbnames;
rsp.msg.dbs.names.arg = &names;
});
}
bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len)
{
DbTables_t tables = get_db_tables(db);
return fill_response<Functions_FUNC_GET_DB_TABLES>(out, len, [&](Response &rsp) {
rsp.msg.tables.tables.funcs.encode = encode_tables;
rsp.msg.tables.tables.arg = &tables;
});
}
bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len)
{
const std::string db(query.db);
const std::string sql(query.sql);
DbRows_t rows = exec_db_query(db, sql);
return fill_response<Functions_FUNC_EXEC_DB_QUERY>(out, len, [&](Response &rsp) {
rsp.msg.rows.rows.funcs.encode = encode_rows;
rsp.msg.rows.rows.arg = &rows;
});
}
} // namespace db

View File

@ -0,0 +1,34 @@
#pragma once
#include <optional>
#include <string>
#include <vector>
#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<uint8_t> 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

View File

@ -1,9 +1,5 @@
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "framework.h"
#include <iostream>
#include <sstream>
#include "spy.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{

View File

@ -1,232 +0,0 @@
#include <iterator>
#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<string, QWORD> 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<uint8_t> 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<uint8_t> rv(field.content.begin() + 1, field.content.end());
return rv;
}
return vector<uint8_t>();
}

View File

@ -1,11 +0,0 @@
#pragma once
#include <vector>
#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<uint8_t> GetAudioData(uint64_t msgid);

View File

@ -1,410 +0,0 @@
#pragma warning(disable : 4244)
#include "framework.h"
#include <filesystem>
#include <fstream>
#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<char> 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<uint8_t> 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<uint8_t> pcm;
vector<uint8_t> 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<QWORD> *pv = (vector<QWORD> *)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<LPBYTE>(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE;
char *dataPtr = *reinterpret_cast<char **>(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;
}

View File

@ -1,15 +0,0 @@
#pragma once
#include "stdint.h"
#include <string>
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);

View File

@ -0,0 +1,281 @@
#include "message_handler.h"
#include <condition_variable>
#include <filesystem>
#include <mutex>
#include <queue>
#include "framework.h"
#include "account_manager.h"
#include "log.hpp"
#include "offsets.h"
#include "pb_util.h"
#include "rpc_helper.h"
#include "spy.h"
#include "util.h"
namespace message
{
namespace fs = std::filesystem;
namespace OsLog = Offsets::Message::Log;
namespace OsRecv = Offsets::Message::Receive;
QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2)
{
auto &handler = getInstance();
WxMsg_t wxMsg = {};
try {
wxMsg.id = util::get_qword(arg2 + OsRecv::ID);
wxMsg.type = util::get_dword(arg2 + OsRecv::TYPE);
wxMsg.is_self = util::get_dword(arg2 + OsRecv::SELF);
wxMsg.ts = util::get_dword(arg2 + OsRecv::TIMESTAMP);
wxMsg.content = util::get_str_by_wstr_addr(arg2 + OsRecv::CONTENT);
wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OsRecv::SIGN);
wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OsRecv::XML);
wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OsRecv::ROOMID);
if (wxMsg.roomid.find("@chatroom") != std::string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom
wxMsg.is_group = true;
wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OsRecv::WXID);
} else {
wxMsg.is_group = false;
wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : wxMsg.roomid;
}
fs::path thumb = util::get_str_by_wstr_addr(arg2 + OsRecv::THUMB);
if (!thumb.empty()) {
wxMsg.thumb = (account::get_home_path() / thumb).generic_string();
}
fs::path extra = util::get_str_by_wstr_addr(arg2 + OsRecv::EXTRA);
if (!extra.empty()) {
wxMsg.extra = (account::get_home_path() / extra).generic_string();
}
LOG_DEBUG("{}", wxMsg.content);
} catch (const std::exception &e) {
LOG_ERROR(util::gb2312_to_utf8(e.what()));
}
{
std::unique_lock<std::mutex> lock(handler.mutex_);
handler.msgQueue_.push(wxMsg); // 推送到队列
}
handler.cv_.notify_all();
return handler.realRecvMsg(arg1, arg2);
}
QWORD Handler::PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
QWORD a10, QWORD a11, QWORD a12)
{
auto &handler = getInstance();
QWORD p = handler.realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
if (p == 0 || p == 1) {
return p;
}
LOG_INFO("【WX】\n{}", util::gb2312_to_utf8((char *)p));
return p;
}
QWORD Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3)
{
auto &handler = getInstance();
QWORD startAddr = *(QWORD *)(arg2 + OsRecv::PYQ_START);
QWORD endAddr = *(QWORD *)(arg2 + OsRecv::PYQ_END);
if (startAddr == 0) {
return 0;
}
while (startAddr < endAddr) {
WxMsg_t wxMsg;
wxMsg.type = 0x00;
wxMsg.is_self = false;
wxMsg.is_group = false;
wxMsg.id = util::get_qword(startAddr);
wxMsg.ts = util::get_dword(startAddr + OsRecv::PYQ_TS);
wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_XML);
wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_SENDER);
wxMsg.content = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_CONTENT);
{
std::unique_lock<std::mutex> lock(handler.mutex_);
handler.msgQueue_.push(wxMsg);
}
handler.cv_.notify_all();
startAddr += 0x1618;
}
return handler.realRecvPyq(arg1, arg2, arg3);
}
Handler &Handler::getInstance()
{
static Handler instance;
return instance;
}
Handler::Handler()
{
isLogging = false;
isListeningMsg = false;
isListeningPyq = false;
}
Handler::~Handler()
{
DisableLog();
UnListenMsg();
UnListenPyq();
}
MsgTypes_t Handler::GetMsgTypes()
{
return { { 0x00, "朋友圈消息" },
{ 0x01, "文字" },
{ 0x03, "图片" },
{ 0x22, "语音" },
{ 0x25, "好友确认" },
{ 0x28, "POSSIBLEFRIEND_MSG" },
{ 0x2A, "名片" },
{ 0x2B, "视频" },
{ 0x2F, "石头剪刀布 | 表情图片" },
{ 0x30, "位置" },
{ 0x31, "共享实时位置、文件、转账、链接" },
{ 0x32, "VOIPMSG" },
{ 0x33, "微信初始化" },
{ 0x34, "VOIPNOTIFY" },
{ 0x35, "VOIPINVITE" },
{ 0x3E, "小视频" },
{ 0x42, "微信红包" },
{ 0x270F, "SYSNOTICE" },
{ 0x2710, "红包、系统消息" },
{ 0x2712, "撤回消息" },
{ 0x100031, "搜狗表情" },
{ 0x1000031, "链接" },
{ 0x1A000031, "微信红包" },
{ 0x20010031, "红包封面" },
{ 0x2D000031, "视频号视频" },
{ 0x2E000031, "视频号名片" },
{ 0x31000031, "引用消息" },
{ 0x37000031, "拍一拍" },
{ 0x3A000031, "视频号直播" },
{ 0x3A100031, "商品链接" },
{ 0x3A200031, "视频号直播" },
{ 0x3E000031, "音乐链接" },
{ 0x41000031, "文件" } };
}
std::optional<WxMsg_t> Handler::popMessage()
{
std::lock_guard<std::mutex> lock(mutex_);
if (msgQueue_.empty()) {
return std::nullopt;
}
WxMsg_t msg = std::move(msgQueue_.front());
msgQueue_.pop();
return msg;
}
int Handler::EnableLog()
{
if (isLogging) return 1;
pLogLevel = reinterpret_cast<uint32_t *>(Spy::WeChatDll.load() + OsLog::LEVEL);
funcWxLog = Spy::getFunction<funcWxLog_t>(OsLog::CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast<LPVOID *>(&realWxLog)) != MH_OK) return -2;
if (MH_EnableHook(funcWxLog) != MH_OK) return -3;
*pLogLevel = 0;
isLogging = true;
return 0;
}
int Handler::DisableLog()
{
if (!isLogging) return 1;
if (MH_DisableHook(funcWxLog) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -2;
*pLogLevel = 6;
isLogging = false;
return 0;
}
int Handler::ListenMsg()
{
if (isListeningMsg) return 1;
funcRecvMsg = Spy::getFunction<funcRecvMsg_t>(OsRecv::CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast<LPVOID *>(&realRecvMsg)) != MH_OK) return -1;
if (MH_EnableHook(funcRecvMsg) != MH_OK) return -1;
isListeningMsg = true;
return 0;
}
int Handler::UnListenMsg()
{
if (!isListeningMsg) return 1;
if (MH_DisableHook(funcRecvMsg) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -1;
isListeningMsg = false;
return 0;
}
int Handler::ListenPyq()
{
if (isListeningPyq) return 1;
funcRecvPyq = Spy::getFunction<funcRecvPyq_t>(OsRecv::PYQ_CALL);
if (InitializeHook() != MH_OK) return -1;
if (MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast<LPVOID *>(&realRecvPyq)) != MH_OK) return -1;
if (MH_EnableHook(funcRecvPyq) != MH_OK) return -1;
isListeningPyq = true;
return 0;
}
int Handler::UnListenPyq()
{
if (!isListeningPyq) return 1;
if (MH_DisableHook(funcRecvPyq) != MH_OK) return -1;
if (UninitializeHook() != MH_OK) return -1;
isListeningPyq = false;
return 0;
}
MH_STATUS Handler::InitializeHook()
{
if (isMH_Initialized) return MH_OK;
MH_STATUS status = MH_Initialize();
if (status == MH_OK) isMH_Initialized = true;
return status;
}
MH_STATUS Handler::UninitializeHook()
{
if (!isMH_Initialized || isLogging || isListeningMsg || isListeningPyq) return MH_OK;
MH_STATUS status = MH_Uninitialize();
if (status == MH_OK) isMH_Initialized = false;
return status;
}
bool Handler::rpc_get_msg_types(uint8_t *out, size_t *len)
{
MsgTypes_t types = GetMsgTypes();
return fill_response<Functions_FUNC_GET_MSG_TYPES>(out, len, [&](Response &rsp) {
rsp.msg.types.types.funcs.encode = encode_types;
rsp.msg.types.types.arg = &types;
});
}
}

View File

@ -0,0 +1,74 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>
#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<WxMsg_t> 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<WxMsg_t> msgQueue_;
std::atomic<bool> isLogging { false };
std::atomic<bool> isListeningMsg { false };
std::atomic<bool> isListeningPyq { false };
using funcRecvMsg_t = QWORD (*)(QWORD, QWORD);
using funcWxLog_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
using funcRecvPyq_t = QWORD (*)(QWORD, QWORD, QWORD);
uint32_t *pLogLevel;
funcWxLog_t funcWxLog, realWxLog;
funcRecvMsg_t funcRecvMsg, realRecvMsg;
funcRecvPyq_t funcRecvPyq, realRecvPyq;
bool isMH_Initialized { false };
MH_STATUS InitializeHook();
MH_STATUS UninitializeHook();
static QWORD DispatchMsg(QWORD arg1, QWORD arg2);
static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
QWORD a10, QWORD a11, QWORD a12);
static QWORD DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3);
};
} // namespace message

View File

@ -0,0 +1,366 @@
#include "message_sender.h"
#include <sstream>
#include <vector>
#include "account_manager.h"
#include "database_executor.h"
#include "log.hpp"
#include "offsets.h"
#include "rpc_helper.h"
#include "spy.h"
#include "spy_types.h"
#include "util.h"
namespace message
{
namespace OsSend = Offsets::Message::Send;
Sender &Sender::get_instance()
{
static Sender instance;
return instance;
}
Sender::Sender()
{
func_new_chat_msg = Spy::getFunction<New_t>(OsSend::INSTANCE);
func_free_chat_msg = Spy::getFunction<Free_t>(OsSend::FREE);
func_send_msg_mgr = Spy::getFunction<SendMsgMgr_t>(OsSend::MGR);
func_send_text = Spy::getFunction<SendText_t>(OsSend::TEXT);
func_send_image = Spy::getFunction<SendImage_t>(OsSend::IMAGE);
func_get_app_mgr = Spy::getFunction<GetAppMgr_t>(OsSend::APP_MGR);
func_send_file = Spy::getFunction<SendFile_t>(OsSend::FILE);
func_new_mmreader = Spy::getFunction<New_t>(OsSend::NEW_MM_READER);
func_free_mmreader = Spy::getFunction<Free_t>(OsSend::FREE_MM_READER);
func_send_rich_text = Spy::getFunction<SendRichText_t>(OsSend::RICH_TEXT);
func_send_pat = Spy::getFunction<SendPat_t>(OsSend::PAT);
func_forward = Spy::getFunction<Forward_t>(OsSend::FORWARD);
func_get_emotion_mgr = Spy::getFunction<GetEmotionMgr_t>(OsSend::EMOTION_MGR);
func_send_emotion = Spy::getFunction<SendEmotion_t>(OsSend::EMOTION);
func_send_xml = Spy::getFunction<SendXml_t>(OsSend::XML);
func_xml_buf_sign = Spy::getFunction<XmlBufSign_t>(OsSend::XML_BUF_SIGN);
}
void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids)
{
util::WxStringHolder<std::string> holderMsg(msg);
util::WxStringHolder<std::string> holderWxid(wxid);
auto wxAtWxids = util::parse_wxids(at_wxids).wxWxids;
QWORD pWxAtWxids = wxAtWxids.empty() ? 0 : reinterpret_cast<QWORD>(&wxAtWxids);
char buffer[1104] = { 0 };
func_send_msg_mgr();
func_send_text(reinterpret_cast<QWORD>(&buffer), &holderWxid.wx, &holderMsg.wx, pWxAtWxids, 1, 1, 0, 0);
func_free_chat_msg(reinterpret_cast<QWORD>(&buffer));
}
void Sender::send_image(const std::string &wxid, const std::string &path)
{
util::WxStringHolder<std::string> holderWxid(wxid);
util::WxStringHolder<std::string> holderPath(path);
char msg[1192] = { 0 };
char msgTmp[1192] = { 0 };
QWORD *flag[10] = { 0 };
QWORD tmp1 = 1, tmp2 = 0, tmp3 = 0;
QWORD pMsgTmp = func_new_chat_msg((QWORD)(&msgTmp));
flag[0] = reinterpret_cast<QWORD *>(tmp1);
flag[1] = reinterpret_cast<QWORD *>(pMsgTmp);
flag[8] = &tmp2;
flag[9] = &tmp3;
QWORD pMsg = func_new_chat_msg((QWORD)(&msg));
QWORD sendMgr = func_send_msg_mgr();
func_send_image(sendMgr, pMsg, &holderWxid.wx, &holderPath.wx, reinterpret_cast<QWORD>(&flag));
func_free_chat_msg(pMsg);
func_free_chat_msg(pMsgTmp);
}
void Sender::send_file(const std::string &wxid, const std::string &path)
{
WxString *wxWxid = util::CreateWxString(wxid);
WxString *wxPath = util::CreateWxString(path);
if (!wxWxid || !wxPath) {
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
return;
}
char *chat_msg = reinterpret_cast<char *>(util::AllocFromHeap(0x460));
if (!chat_msg) {
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
return;
}
QWORD *tmp1 = util::AllocBuffer<QWORD>(4);
QWORD *tmp2 = util::AllocBuffer<QWORD>(4);
QWORD *tmp3 = util::AllocBuffer<QWORD>(4);
if (!tmp1 || !tmp2 || !tmp3) {
func_free_chat_msg(reinterpret_cast<QWORD>(chat_msg));
util::FreeBuffer(chat_msg);
util::FreeBuffer(tmp1);
util::FreeBuffer(tmp2);
util::FreeBuffer(tmp3);
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
return;
}
QWORD app_mgr = func_get_app_mgr();
func_send_file(app_mgr, chat_msg, wxWxid, wxPath, 1, tmp1, 0, tmp2, 0, tmp3, 0, 0xC);
func_free_chat_msg(reinterpret_cast<QWORD>(chat_msg));
util::FreeBuffer(chat_msg);
util::FreeBuffer(tmp1);
util::FreeBuffer(tmp2);
util::FreeBuffer(tmp3);
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
}
void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type)
{
#if 0
std::unique_ptr<char[]> buff(new char[0x500]());
std::unique_ptr<char[]> buff2(new char[0x500]());
char nullBuf[0x1C] = { 0 };
func_new_chat_msg(reinterpret_cast<QWORD>(buff.get()));
func_new_chat_msg(reinterpret_cast<QWORD>(buff2.get()));
QWORD sbuf[4] = { 0, 0, 0, 0 };
QWORD sign = func_xml_buf_sign(reinterpret_cast<QWORD>(buff2.get()), reinterpret_cast<QWORD>(sbuf), 0x1);
auto wxReceiver = new_wx_string(receiver);
auto wxXml = new_wx_string(xml);
auto wxPath = new_wx_string(path);
auto wxSender = new_wx_string(account::get_self_wxid());
func_send_xml(reinterpret_cast<QWORD>(buff.get()), reinterpret_cast<QWORD>(wxSender.get()),
reinterpret_cast<QWORD>(wxReceiver.get()), reinterpret_cast<QWORD>(wxXml.get()),
reinterpret_cast<QWORD>(wxPath.get()), reinterpret_cast<QWORD>(nullBuf), type, 0x4, sign,
reinterpret_cast<QWORD>(buff2.get()));
func_free_chat_msg(reinterpret_cast<QWORD>(buff.get()));
func_free_chat_msg(reinterpret_cast<QWORD>(buff2.get()));
#endif
}
void Sender::send_emotion(const std::string &wxid, const std::string &path)
{
WxString *wxWxid = util::CreateWxString(wxid);
WxString *wxPath = util::CreateWxString(path);
QWORD *buff = util::AllocBuffer<QWORD>(8);
if (!wxWxid || !wxPath || !buff) {
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
util::FreeBuffer(buff);
return;
}
QWORD mgr = func_get_emotion_mgr();
func_send_emotion(mgr, wxPath, buff, wxWxid, 2, buff, 0, buff);
util::FreeBuffer(buff);
util::FreeWxString(wxWxid);
util::FreeWxString(wxPath);
}
int Sender::send_rich_text(const RichText &rt)
{
QWORD status = -1;
char *buff = util::AllocBuffer<char>(0x3F0);
WxString *pReceiver = util::CreateWxString(rt.receiver);
WxString *pTitle = util::CreateWxString(rt.title);
WxString *pUrl = util::CreateWxString(rt.url);
WxString *pThumburl = util::CreateWxString(rt.thumburl);
WxString *pDigest = util::CreateWxString(rt.digest);
WxString *pAccount = util::CreateWxString(rt.account);
WxString *pName = util::CreateWxString(rt.name);
if (!buff || !pReceiver || !pTitle || !pUrl || !pThumburl || !pDigest || !pAccount || !pName) {
util::FreeWxString(pReceiver);
util::FreeWxString(pTitle);
util::FreeWxString(pUrl);
util::FreeWxString(pThumburl);
util::FreeWxString(pDigest);
util::FreeWxString(pAccount);
util::FreeWxString(pName);
util::FreeBuffer(buff);
LOG_ERROR("Out of Memory...");
return static_cast<int>(status);
}
func_new_mmreader(reinterpret_cast<QWORD>(buff));
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));
status = func_send_rich_text(func_get_app_mgr(), pReceiver, buff);
func_free_mmreader(reinterpret_cast<QWORD>(buff));
// TODO: 验证是否有内存泄露
// util::FreeWxString(pReceiver);
// util::FreeWxString(pTitle);
// util::FreeWxString(pUrl);
// util::FreeWxString(pThumburl);
// util::FreeWxString(pDigest);
// util::FreeWxString(pAccount);
// util::FreeWxString(pName);
util::FreeBuffer(buff);
return static_cast<int>(status);
}
int Sender::send_pat(const std::string &roomid, const std::string &wxid)
{
QWORD status = -1;
util::WxStringHolder<std::string> holderRoom(roomid);
util::WxStringHolder<std::string> holderWxid(wxid);
status = func_send_pat(&holderRoom.wx, &holderWxid.wx);
return static_cast<int>(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<DWORD>(localId);
WxString *pReceiver = util::CreateWxString(receiver);
return static_cast<int>(func_forward(pReceiver, l.QuadPart, 0x4, 0x0));
}
// RPC 方法
bool Sender::rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_SEND_TXT>(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<Functions_FUNC_SEND_IMG>(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<Functions_FUNC_SEND_FILE>(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<Functions_FUNC_SEND_EMOTION>(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<Functions_FUNC_SEND_XML>(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 = -1;
}
});
}
bool Sender::rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_SEND_RICH_TXT>(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<Functions_FUNC_SEND_PAT_MSG>(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<Functions_FUNC_FORWARD_MSG>(out, len, [&](Response &rsp) {
if (receiver.empty()) {
LOG_ERROR("Empty receiver.");
rsp.msg.status = -1;
} else {
rsp.msg.status = forward(msgid, receiver);
}
});
}
}

View File

@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#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, WxString *, WxString *, QWORD, QWORD, QWORD, QWORD, QWORD);
using SendImage_t = QWORD (*)(QWORD, QWORD, WxString *, WxString *, QWORD);
using SendFile_t = QWORD (*)(QWORD, char *, WxString *, WxString *, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *,
QWORD, QWORD);
using SendRichText_t = QWORD (*)(QWORD, WxString *, char *);
using SendPat_t = QWORD (*)(WxString *, WxString *);
using Forward_t = QWORD (*)(WxString *, QWORD, QWORD, QWORD);
using GetEmotionMgr_t = QWORD (*)();
using SendEmotion_t = QWORD (*)(QWORD, WxString *, QWORD *, WxString *, 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_chat_msg;
Free_t func_free_chat_msg;
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;
New_t func_new_mmreader;
Free_t func_free_mmreader;
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<WxString> new_wx_string(const char *str);
std::unique_ptr<WxString> new_wx_string(const std::string &str);
};
}

View File

@ -0,0 +1,421 @@
#pragma warning(disable : 4244)
#include "misc_manager.h"
#include <filesystem>
#include <fstream>
#include "framework.h"
#include "codec.h"
#include "database_executor.h"
#include "log.hpp"
#include "message_handler.h"
#include "offsets.h"
#include "rpc_helper.h"
#include "spy.h"
#include "spy_types.h"
#include "util.h"
namespace misc
{
using namespace std;
namespace fs = std::filesystem;
namespace OsMisc = Offsets::Misc;
namespace OsSns = Offsets::Misc::Sns;
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 (*)(char *);
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);
using get_qr_code_mgr_t = QWORD (*)();
struct ImagePattern {
uint8_t header1_candidate;
uint8_t header2_expected;
const char *extension;
};
static constexpr ImagePattern patterns[] = {
{ 0x89, 0x50, ".png" },
{ 0xFF, 0xD8, ".jpg" },
{ 0x47, 0x49, ".gif" },
};
static std::string detect_image_extension(uint8_t header1, uint8_t header2, uint8_t *key)
{
for (const auto &pat : patterns) {
*key = pat.header1_candidate ^ header1;
if ((pat.header2_expected ^ *key) == header2) {
return pat.extension;
}
}
LOG_ERROR("未知类型:{:02x} {:02x}", header1, header2);
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<char> buffer(std::istreambuf_iterator<char>(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.generic_string());
return "";
}
out.write(buffer.data(), buffer.size());
return dst_path.generic_string();
}
static int get_first_page()
{
int status = -1;
auto GetSNSDataMgr = Spy::getFunction<get_sns_data_mgr_t>(OsSns::DATA_MGR);
auto GetSNSFirstPage = Spy::getFunction<get_sns_first_page_t>(OsSns::FIRST);
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;
auto GetSnsTimeLineMgr = Spy::getFunction<get_sns_timeline_mgr_t>(OsSns::TIMELINE);
auto GetSNSNextPageScene = Spy::getFunction<get_sns_next_page_scene_t>(OsSns::NEXT);
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: 通过文件大小来判断
LOG_WARN("文件已存在:{}", extra.generic_string());
return 0;
}
if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) {
LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id));
return status;
}
auto NewChatMsg = Spy::getFunction<new_chat_msg_t>(OsMisc::INSATNCE);
auto FreeChatMsg = Spy::getFunction<free_chat_msg_t>(OsMisc::FREE);
auto GetChatMgr = Spy::getFunction<get_chat_mgr_t>(OsMisc::CHAT_MGR);
auto GetPreDownLoadMgr = Spy::getFunction<get_pre_download_mgr_t>(OsMisc::PRE_DOWNLOAD_MGR);
auto PushAttachTask = Spy::getFunction<push_attach_task_t>(OsMisc::PUSH_ATTACH_TASK);
auto GetMgrByPrefixLocalId = Spy::getFunction<get_mgr_by_prefix_localid_t>(OsMisc::PRE_LOCAL_ID_MGR);
LARGE_INTEGER l;
l.HighPart = dbIdx;
l.LowPart = (DWORD)localId;
char *buff = util::AllocBuffer<char>(0x460);
if (buff == nullptr) {
LOG_ERROR("申请内存失败.");
return status;
}
QWORD pChatMsg = NewChatMsg(buff);
GetChatMgr();
GetMgrByPrefixLocalId(l.QuadPart, pChatMsg);
QWORD type = util::get_qword(reinterpret_cast<QWORD>(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");
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.generic_string());
// 创建父目录,由于路径来源于微信,不做检查
fs::create_directory(save_path.parent_path());
int temp = 1;
auto wx_save_path = util::CreateWxString(save_path.make_preferred().string());
auto wx_thumb_path = util::CreateWxString(thumb_path.make_preferred().string());
memcpy(&buff[0x280], wx_thumb_path, sizeof(WxString));
memcpy(&buff[0x2A0], wx_save_path, sizeof(WxString));
memcpy(&buff[0x40C], &temp, sizeof(temp));
QWORD mgr = GetPreDownLoadMgr();
status = (int)PushAttachTask(mgr, pChatMsg, 0, 1);
FreeChatMsg(pChatMsg);
util::FreeBuffer(buff);
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.generic_string();
auto silk = db::get_audio_data(id);
if (silk.empty()) {
LOG_ERROR("没有获取到语音数据.");
return "";
}
Silk2Mp3(silk, mp3path.generic_string(), 24000);
return mp3path.generic_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.generic_string();
auto silk = db::get_audio_data(id);
if (silk.empty()) {
LOG_ERROR("没有获取到语音数据.");
return "";
}
std::vector<uint8_t> pcm;
SilkDecode(silk, pcm, sr);
std::ofstream out(pcmpath, std::ios::binary);
if (!out) {
LOG_ERROR("创建文件失败: {}", pcmpath.generic_string());
return "";
}
out.write(reinterpret_cast<char *>(pcm.data()), pcm.size());
return pcmpath.generic_string();
}
OcrResult_t get_ocr_result(const fs::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<QWORD> *pv = (vector<QWORD> *)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()
{
std::string uri;
auto get_qr_code_mgr = Spy::getFunction<get_qr_code_mgr_t>(OsMisc::QR_CODE);
uint64_t addr = get_qr_code_mgr() + 0x68;
uint64_t len = *(uint64_t *)(addr + 0x10);
if (len == 0) {
LOG_ERROR("获取二维码失败.");
return uri;
}
if (*(uint64_t *)(addr + 0x18) == 0xF) {
uri = std::string((char *)addr, len);
} else {
uri = std::string(*(char **)(addr), len);
}
return "http://weixin.qq.com/x/" + uri;
}
int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid)
{
LOG_ERROR("技术太菜,实现不了。");
return -1;
}
bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_GET_AUDIO_MSG>(
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 fs::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<Functions_FUNC_DECRYPT_IMAGE>(
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<Functions_FUNC_REFRESH_QRCODE>(
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<Functions_FUNC_REFRESH_PYQ>(out, len,
[&](Response &rsp) { rsp.msg.status = refresh_pyq(id); });
}
bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len)
{
int status = -1;
if (att.thumb || att.extra) {
std::string thumb = att.thumb ? att.thumb : "";
std::string extra = att.extra ? att.extra : "";
status = download_attachment(att.id, thumb, extra);
} else {
LOG_ERROR("文件地址不能全为空");
}
return fill_response<Functions_FUNC_DOWNLOAD_ATTACH>(out, len, [&](Response &rsp) { rsp.msg.status = status; });
}
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len)
{
return fill_response<Functions_FUNC_REVOKE_MSG>(out, len,
[&](Response &rsp) { rsp.msg.status = revoke_message(id); });
}
bool rpc_get_ocr_result(const fs::path &path, uint8_t *out, size_t *len)
{
auto ret = get_ocr_result(path);
return fill_response<Functions_FUNC_EXEC_OCR>(out, len, [&](Response &rsp) {
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<Functions_FUNC_RECV_TRANSFER>(
out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); });
}
} // namespace misc

View File

@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
#include "wcf.pb.h"
#include "pb_types.h"
namespace misc
{
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_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

149
WeChatFerry/spy/offsets.h Normal file
View File

@ -0,0 +1,149 @@
#pragma once
#include <cstdint>
namespace Offsets
{
namespace Account
{
constexpr uint64_t SERVICE = 0x1B58B50; // 账户服务
constexpr uint64_t PATH = 0x25E9090; // 数据路径
constexpr uint64_t WXID = 0x80; // WXID
constexpr uint64_t NAME = 0x1E8; // 昵称
constexpr uint64_t MOBILE = 0x128; // 手机号
constexpr uint64_t LOGIN = 0x7F8; // 登录状态
}
namespace Chatroom
{
constexpr uint64_t MGR = 0x1B86F60;
constexpr uint64_t DEL = 0x2158830;
constexpr uint64_t ADD = 0x21581F0;
constexpr uint64_t INV = 0x2157BD0;
}
namespace Contact
{
constexpr uint64_t MGR = 0x1B44B20;
constexpr uint64_t LIST = 0x21A1E00;
constexpr uint64_t BIN = 0x200;
constexpr uint64_t BIN_LEN = 0x208;
constexpr uint64_t WXID = 0x10;
constexpr uint64_t CODE = 0x30;
constexpr uint64_t REMARK = 0x80;
constexpr uint64_t NAME = 0xA0;
constexpr uint64_t GENDER = 0x0E;
constexpr uint64_t STEP = 0x6A8;
}
namespace Db
{
constexpr uint64_t INSTANCE = 0x59226C8; // 数据库实例地址
constexpr uint64_t MSG_I = 0x5980420; // MSGi.db & MediaMsgi.db
constexpr uint64_t MICROMSG = 0xB8;
constexpr uint64_t CHAT_MSG = 0x2C8;
constexpr uint64_t MISC = 0x5F0;
constexpr uint64_t EMOTION = 0x15F0;
constexpr uint64_t MEDIA = 0xF48;
constexpr uint64_t BIZCHAT_MSG = 0x1AC0;
constexpr uint64_t FUNCTION_MSG = 0x1B98;
constexpr uint64_t NAME = 0x28;
// SQLITE3
constexpr uint64_t EXEC = 0x3A76430;
// constexpr uint64_t BACKUP_INIT = EXEC - 0x1D113E0;
constexpr uint64_t PREPARE = EXEC + 0x7CB0;
// constexpr uint64_t OPEN = EXEC - 0x1CA2430;
// constexpr uint64_t BACKUP_STEP = EXEC - 0x1D110A0;
// constexpr uint64_t BACKUP_REMAINING = EXEC - 0x1D10880;
// constexpr uint64_t BACKUP_PAGECOUNT = EXEC - 0x1D10890;
// constexpr uint64_t BACKUP_FINISH = EXEC - 0x1D10940;
// constexpr uint64_t SLEEP = EXEC - 0x1CA1BB0;
// constexpr uint64_t ERRCODE = EXEC - 0x1CA3770;
// constexpr uint64_t CLOSE = EXEC - 0x1CA4FD0;
constexpr uint64_t STEP = EXEC - 0x3C000;
constexpr uint64_t COLUMN_COUNT = EXEC - 0x3B7E0;
constexpr uint64_t COLUMN_NAME = EXEC - 0x3ADE0;
constexpr uint64_t COLUMN_TYPE = EXEC - 0x3AF90;
constexpr uint64_t COLUMN_BLOB = EXEC - 0x3B7B0;
constexpr uint64_t COLUMN_BYTES = EXEC - 0x3B6C0;
constexpr uint64_t FINALIZE = EXEC - 0x3CF50;
}
namespace Message
{
namespace Log
{
constexpr uint64_t LEVEL = 0x56E4244; // 日志级别
constexpr uint64_t CALL = 0x261B890; // 日志函数
}
namespace Receive
{
constexpr uint64_t CALL = 0x2141E80; // 接收消息 Call
constexpr uint64_t ID = 0x30; // 消息 ID
constexpr uint64_t TYPE = 0x38; // 消息类型
constexpr uint64_t SELF = 0x3C; // 消息是否来自自己
constexpr uint64_t TIMESTAMP = 0x44; // 消息时间戳
constexpr uint64_t ROOMID = 0x48; // 群聊 ID或者发送者 wxid
constexpr uint64_t CONTENT = 0x88; // 消息内容
constexpr uint64_t WXID = 0x240; // 发送者 wxid
constexpr uint64_t SIGN = 0x260; // 消息签名
constexpr uint64_t THUMB = 0x280; // 缩略图路径
constexpr uint64_t EXTRA = 0x2A0; // 原图路径
constexpr uint64_t XML = 0x308; // 消息 XML
constexpr uint64_t PYQ_CALL = 0x2E56080; // 接收朋友圈 Call
constexpr uint64_t PYQ_START = 0x30; // 开始地址
constexpr uint64_t PYQ_END = 0x38; // 结束地址
constexpr uint64_t PYQ_SENDER = 0x18; // 发布者
constexpr uint64_t PYQ_TS = 0x38; // 时间戳
constexpr uint64_t PYQ_CONTENT = 0x48; // 文本内容
constexpr uint64_t PYQ_XML = 0x9B8; // 其他内容
}
namespace Send
{
constexpr uint64_t MGR = 0x1B57350;
constexpr uint64_t INSTANCE = 0x1B614C0;
constexpr uint64_t FREE = 0x1B58BD0;
constexpr uint64_t TEXT = 0x22C9CA0;
constexpr uint64_t IMAGE = 0x22BF430;
constexpr uint64_t APP_MGR = 0x1B5C2F0;
constexpr uint64_t FILE = 0x20D30E0;
constexpr uint64_t XML = 0x0;
constexpr uint64_t XML_BUF_SIGN = 0x0;
constexpr uint64_t EMOTION_MGR = 0x1BD2310;
constexpr uint64_t EMOTION = 0x21B8100;
constexpr uint64_t NEW_MM_READER = 0x1B60A10;
constexpr uint64_t FREE_MM_READER = 0x1B5FDE0;
constexpr uint64_t RICH_TEXT = 0x20DD0C0;
constexpr uint64_t PAT = 0x2CC1E90;
constexpr uint64_t FORWARD = 0x22C9220;
}
}
namespace Misc
{
constexpr uint64_t QR_CODE = 0x2025A80;
constexpr uint64_t INSATNCE = Message::Send::INSTANCE;
constexpr uint64_t FREE = Message::Send::FREE;
constexpr uint64_t CHAT_MGR = 0x1B8AA50;
constexpr uint64_t PRE_LOCAL_ID_MGR = 0x2142BF0;
constexpr uint64_t PRE_DOWNLOAD_MGR = 0x1C12260;
constexpr uint64_t PUSH_ATTACH_TASK = 0x1CE3050;
namespace Sns
{
constexpr uint64_t DATA_MGR = 0x21E52F0;
constexpr uint64_t TIMELINE = 0x2DC6180;
constexpr uint64_t FIRST = 0x2E346C0;
constexpr uint64_t NEXT = 0x2E5A270;
}
}
}

View File

@ -1,378 +0,0 @@
#pragma execution_character_set("utf-8")
#include "MinHook.h"
#include "framework.h"
#include <condition_variable>
#include <mutex>
#include <queue>
#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<WxMsg_t> 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<mutex> 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<mutex> 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<LPVOID *>(&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<LPVOID *>(&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<LPVOID *>(&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;
}
}

View File

@ -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();

View File

@ -0,0 +1,92 @@
#pragma once
#include <unordered_map>
#include <magic_enum/magic_enum.hpp>
#include "wcf.pb.h"
#include "log.hpp"
#include "pb_encode.h"
#include "pb_types.h"
static const std::unordered_map<Functions, int> 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 <Functions FuncType, typename AssignFunc> 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;
}
template <Functions FuncType, typename DataType, typename AssignFunc>
bool fill_response(uint8_t *out, size_t *len, DataType &&data, 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, data);
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,58 @@
#pragma once
#ifdef SPY_EXPORTS
#define SPY_API __declspec(dllexport)
#else
#define SPY_API __declspec(dllimport)
#endif
#include <atomic>
#include <functional>
#include <thread>
#include <unordered_map>
int RpcStartServer(int port);
int RpcStopServer();
#include <nng/nng.h>
#include "wcf.pb.h"
#include "message_handler.h"
#include "message_sender.h"
class RpcServer
{
public:
static RpcServer &getInstance();
static void destroyInstance();
int start(int port = RPC_DEFAULT_PORT);
int stop();
private:
RpcServer(int port = RPC_DEFAULT_PORT);
~RpcServer();
RpcServer(const RpcServer &) = delete;
RpcServer &operator=(const RpcServer &) = delete;
void run_rpc_server();
void on_message_callback();
bool start_message_listener(bool pyq, uint8_t *out, size_t *len);
bool stop_message_listener(uint8_t *out, size_t *len);
bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len);
static std::string build_url(int port);
using FunctionHandler = std::function<bool(const Request &, uint8_t *, size_t *)>;
// 服务器默认端口号和绑定地址
static constexpr int RPC_DEFAULT_PORT = 10086;
static constexpr const char *RPC_SERVER_ADDRESS = "tcp://0.0.0.0";
int port_ = RPC_DEFAULT_PORT;
std::atomic<bool> isRunning_ { false };
std::thread cmdThread_;
std::thread msgThread_;
message::Handler &handler_;
message::Sender &sender_;
struct Deleter {
void operator()(RpcServer *server) const { delete server; }
};
static std::unique_ptr<RpcServer, Deleter> instance_;
static const std::unordered_map<Functions, FunctionHandler> rpcFunctionMap;
};

View File

@ -1,273 +0,0 @@

#include "framework.h"
#include <sstream>
#include <vector>
#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<wstring> vAtWxids;
vector<WxString> 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);
}

View File

@ -1,24 +0,0 @@
#pragma once
#include <string>
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);

Binary file not shown.

View File

@ -1,49 +1,48 @@
#include <filesystem>
#include "spy.h"
#include <filesystem>
#include "log.hpp"
#include "rpc_server.h"
#include "spy.h"
#include "util.h"
UINT64 g_WeChatWinDllAddr = 0;
static bool IsWxVersionMatched(const wchar_t *version)
namespace Spy
{
if (wcscmp(version, SUPPORT_VERSION) != 0) {
return false;
}
return true;
}
void InitSpy(LPVOID args)
int Init(void *args)
{
wchar_t version[16] = { 0 };
PortPath_t *pp = (PortPath_t *)args;
auto *pp = static_cast<util::PortPath *>(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")) {
WeChatDll.store(reinterpret_cast<uint64_t>(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);
util::MsgBox(NULL, msg.c_str(), "微信版本错误", MB_ICONERROR);
return -2;
}
RpcStartServer(pp->port);
LOG_INFO(msg);
RpcServer::getInstance().start(pp->port);
return 0;
}
void CleanupSpy()
void Cleanup()
{
LOG_DEBUG("CleanupSpy");
RpcStopServer();
RpcServer::destroyInstance();
}
}
extern "C" {
__declspec(dllexport) int InitSpy(void *args) { return Spy::Init(args); }
__declspec(dllexport) void CleanupSpy() { Spy::Cleanup(); }
}

View File

@ -1,8 +1,20 @@
#pragma once
#include "framework.h"
#include <atomic>
#include <cstdint>
#include <string_view>
#define SUPPORT_VERSION L"3.9.11.25"
namespace Spy
{
constexpr std::string_view SUPPORT_VERSION = "3.9.12.17";
inline std::atomic<std::uintptr_t> WeChatDll { 0 };
void InitSpy(int port);
void CleanupSpy();
template <typename T> inline T getFunction(std::uintptr_t offset) { return reinterpret_cast<T>(WeChatDll + offset); }
template <typename T> inline T getFunction(std::uintptr_t base, std::uintptr_t offset)
{
return reinterpret_cast<T>(base + offset);
}
int Init(void *args);
void Cleanup();
}

View File

@ -13,7 +13,7 @@
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD>й<EFBFBD>) resources
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 39,3,5,0
PRODUCTVERSION 3,9,11,25
FILEVERSION 39,4,1,0
PRODUCTVERSION 3,9,12,17
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "WeChatFerry"
VALUE "FileDescription", "WeChatFerry"
VALUE "FileVersion", "39.3.5.0"
VALUE "FileVersion", "39.4.1.0"
VALUE "InternalName", "spy.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "spy.dll"
VALUE "ProductName", "WeChatFerry"
VALUE "ProductVersion", "3.9.11.25"
VALUE "ProductVersion", "3.9.12.17"
END
END
BLOCK "VarFileInfo"
@ -83,7 +83,7 @@ BEGIN
END
END
#endif // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD>й<EFBFBD>) resources
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////

View File

@ -5,36 +5,51 @@
typedef uint64_t QWORD;
struct WxString {
class WxString
{
public:
const wchar_t *wptr;
DWORD size;
DWORD capacity;
DWORD length;
const char *ptr;
DWORD clen;
WxString()
WxString() : wptr(nullptr), size(0), length(0), ptr(nullptr), clen(0) { }
explicit WxString(const std::wstring &ws)
: wptr(ws.c_str()), size(static_cast<DWORD>(ws.size())), length(static_cast<DWORD>(ws.length())), ptr(nullptr),
clen(0)
{
wptr = NULL;
size = 0;
capacity = 0;
ptr = NULL;
clen = 0;
}
WxString(std::wstring &ws)
WxString(const WxString &) = delete;
WxString &operator=(const WxString &) = delete;
WxString(WxString &&other) noexcept
: wptr(other.wptr), size(other.size), length(other.length), ptr(other.ptr), clen(other.clen)
{
wptr = ws.c_str();
size = (DWORD)ws.size();
capacity = (DWORD)ws.capacity();
ptr = NULL;
clen = 0;
other.wptr = nullptr;
other.size = 0;
other.length = 0;
other.ptr = nullptr;
other.clen = 0;
}
WxString &operator=(WxString &&other) noexcept
{
if (this != &other) {
wptr = other.wptr;
size = other.size;
length = other.length;
ptr = other.ptr;
clen = other.clen;
other.wptr = nullptr;
other.size = 0;
other.length = 0;
other.ptr = nullptr;
other.clen = 0;
}
return *this;
}
};
typedef struct RawVector {
#ifdef _DEBUG
QWORD head;
#endif
QWORD start;
QWORD finish;
QWORD end;
} RawVector_t;

View File

@ -138,25 +138,6 @@
#define SQLITE_NULL 5
#define SQLITE_TEXT 3
#define SQLITE3_EXEC_OFFSET 0x3A5EDA0
#define SQLITE3_BACKUP_INIT_OFFSET 0x3A18EA0
#define SQLITE3_PREPARE_OFFSET 0x3A66A20
#define SQLITE3_OPEN_OFFSET 0x3A9E210
#define SQLITE3_BACKUP_STEP_OFFSET 0x3A193F0
#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1B26EB0
#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1B26EE0
#define SQLITE3_BACKUP_FINISH_OFFSET 0x3A19AF0
#define SQLITE3_SLEEP_OFFSET 0x3A9EE70
#define SQLITE3_ERRCODE_OFFSET 0x3A9CB10
#define SQLITE3_CLOSE_OFFSET 0x3A9AC70
#define SQLITE3_STEP_OFFSET 0x3A22DA0
#define SQLITE3_COLUMN_COUNT_OFFSET 0x3A235C0
#define SQLITE3_COLUMN_NAME_OFFSET 0x3A23FC0
#define SQLITE3_COLUMN_TYPE_OFFSET 0x3A23E10
#define SQLITE3_COLUMN_BLOB_OFFSET 0x3A235F0
#define SQLITE3_COLUMN_BYTES_OFFSET 0x3A236E0
#define SQLITE3_FINALIZE_OFFSET 0x3A21E50
typedef int (*Sqlite3_callback)(void *, int, char **, char **);
typedef int(__cdecl *Sqlite3_exec)(QWORD, /* An open database */

View File

@ -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;
}

View File

@ -1,12 +0,0 @@
#pragma once
#include <string>
#include "pb_types.h"
using namespace std;
string GetHomePath();
string GetSelfWxid();
UserInfo_t GetUserInfo();

View File

@ -1,2 +1,2 @@
include wcferry/*.dll
include wcferry/*.exe
include wcferry/*.md

View File

@ -44,8 +44,8 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
## 版本更新
### v39.3.3.1
* 修复 `send_xml`
### v39.4.1.0
* 修复乱码问题
<details><summary>点击查看更多</summary>
@ -70,7 +70,6 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
* 发送图片消息
* 发送文件消息
* 发送卡片消息
* 发送 XML 消息
* 发送 GIF 消息
* 拍一拍群友
* 转发消息

18
clients/python/wcferry/DISCLAIMER.md vendored Normal file
View File

@ -0,0 +1,18 @@
# 免责声明
1. **本工具为开源项目,仅提供基础功能,供用户进行合法的学习、研究和非商业用途**。禁止将本工具用于任何违法或侵权行为。
2. **二次开发者的责任**
- 任何基于本工具进行的二次开发、修改或衍生产品,其行为及后果由二次开发者独立承担,与本工具贡献者无关。
- **禁止使用贡献者的姓名、项目名称或相关信息作为二次开发产品的背书或推广手段**
- 建议二次开发者在其衍生产品中添加自己的免责声明,明确责任归属。
3. **用户责任**
- 使用本工具或其衍生产品的所有后果由用户自行承担。原贡献者不对因直接或间接使用本工具而导致的任何损失、责任或争议负责。
4. **法律约束**
- 用户和二次开发者须遵守《中华人民共和国网络安全法》、《中华人民共和国著作权法》等相关法律法规。
- 本工具涉及的所有第三方商标或产品名称,其权利归权利人所有,作者与第三方无任何直接或间接关联。
5. **作者保留权利**
- 本工具作者保留随时修改、更新、删除或终止本工具的权利,无需事先通知或承担任何义务。

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "39.3.3.3"
__version__ = "39.4.1.0"
import atexit
import base64
@ -10,6 +10,7 @@ import logging
import mimetypes
import os
import re
import subprocess
import sys
from queue import Queue
from threading import Thread
@ -70,6 +71,7 @@ class Wcf():
self._dl_path = f"{self._wcf_root}/.dl"
os.makedirs(self._dl_path, exist_ok=True)
self.LOG = logging.getLogger("WCF")
self._set_console_utf8()
self.LOG.info(f"wcferry version: {__version__}")
self.port = port
self.host = host
@ -115,6 +117,13 @@ class Wcf():
def __del__(self) -> None:
self.cleanup()
def _set_console_utf8(self):
try:
subprocess.run("chcp 65001", shell=True, check=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as e:
self.LOG.error(f"修改控制台代码页失败: {e}")
def cleanup(self) -> None:
"""关闭连接,回收资源"""
if not self._is_running:
@ -141,7 +150,9 @@ class Wcf():
data = req.SerializeToString()
self.cmd_socket.send(data)
rsp = wcf_pb2.Response()
rsp.ParseFromString(self.cmd_socket.recv_msg().bytes)
bs = self.cmd_socket.recv_msg().bytes
self.LOG.debug(bs.hex())
rsp.ParseFromString(bs)
return rsp
def is_receiving_msg(self) -> bool:
@ -150,7 +161,6 @@ class Wcf():
def get_qrcode(self) -> str:
"""获取登录二维码,已经登录则返回空字符串"""
raise Exception("Not implemented, yet")
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_REFRESH_QRCODE # FUNC_REFRESH_QRCODE
rsp = self._send_request(req)
@ -398,6 +408,7 @@ class Wcf():
Returns:
int: 0 为成功其他失败
"""
raise Exception("Not implemented, yet")
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_XML # FUNC_SEND_XML
req.xml.receiver = receiver
@ -763,6 +774,7 @@ class Wcf():
Returns:
int: 1 为成功其他失败
"""
raise Exception("Not implemented, yet")
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_REVOKE_MSG # FUNC_REVOKE_MSG
req.ui64 = id

File diff suppressed because one or more lines are too long