commit
a166aedf4f
@ -168,7 +168,7 @@ sdk.WxDestroySDK()
|
||||
|
||||
### 调试日志
|
||||
```c
|
||||
DbgMsg("ListenMessage"); // 封装的 OutputDebugString
|
||||
util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString
|
||||
OutputDebugString(L"ListenMessage\n");
|
||||
MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0);
|
||||
```
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,208 @@ 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);
|
||||
|
||||
OutputDebugStringA(buffer.data());
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
std::vector<WxString> parse_wxids(const std::string &wxids)
|
||||
{
|
||||
std::vector<WxString> wx_members;
|
||||
std::wstringstream wss(s2w(wxids));
|
||||
std::wstring wstr;
|
||||
while (getline(wss, wstr, L',')) {
|
||||
wx_members.emplace_back(wstr);
|
||||
}
|
||||
|
||||
wmemcpy(pWstring, ws.c_str(), ws.size() + 1);
|
||||
p->wptr = pWstring;
|
||||
p->size = (DWORD)ws.size();
|
||||
p->capacity = (DWORD)ws.size();
|
||||
p->ptr = 0;
|
||||
p->clen = 0;
|
||||
return p;
|
||||
return wx_members;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
@ -1,41 +1,76 @@
|
||||
#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)); }
|
||||
|
||||
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);
|
||||
|
||||
std::vector<WxString> parse_wxids(const std::string &wxids);
|
||||
|
||||
} // namespace util
|
||||
|
@ -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" />
|
||||
|
@ -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">
|
||||
|
@ -1,112 +1,108 @@
|
||||
#include "framework.h"
|
||||
#include "psapi.h"
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "injector.h"
|
||||
|
||||
#include "injector.h"
|
||||
#include "util.h"
|
||||
#include <filesystem>
|
||||
|
||||
#include "psapi.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
HMODULE GetTargetModuleBase(HANDLE process, string dll)
|
||||
static void handle_injection_error(HANDLE process, LPVOID remote_address, const std::string &error_msg)
|
||||
{
|
||||
DWORD cbNeeded;
|
||||
HMODULE moduleHandleList[512];
|
||||
BOOL ret = EnumProcessModulesEx(process, moduleHandleList, sizeof(moduleHandleList), &cbNeeded, LIST_MODULES_64BIT);
|
||||
if (!ret) {
|
||||
MessageBox(NULL, L"获取模块失败", L"GetTargetModuleBase", 0);
|
||||
MessageBoxA(NULL, error_msg.c_str(), "Error", MB_ICONERROR);
|
||||
if (remote_address) {
|
||||
VirtualFreeEx(process, remote_address, 0, MEM_RELEASE);
|
||||
}
|
||||
if (process) {
|
||||
CloseHandle(process);
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE get_target_module_base(HANDLE process, const string &dll)
|
||||
{
|
||||
DWORD needed;
|
||||
HMODULE modules[512];
|
||||
if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) {
|
||||
MessageBoxA(NULL, "获取模块失败", "get_target_module_base", 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (cbNeeded > sizeof(moduleHandleList)) {
|
||||
MessageBox(NULL, L"模块数量过多", L"GetTargetModuleBase", 0);
|
||||
return NULL;
|
||||
}
|
||||
DWORD processCount = cbNeeded / sizeof(HMODULE);
|
||||
|
||||
char moduleName[32];
|
||||
for (DWORD i = 0; i < processCount; i++) {
|
||||
GetModuleBaseNameA(process, moduleHandleList[i], moduleName, 32);
|
||||
if (!strncmp(dll.c_str(), moduleName, dll.size())) {
|
||||
return moduleHandleList[i];
|
||||
DWORD count = needed / sizeof(HMODULE);
|
||||
char module_name[MAX_PATH];
|
||||
for (DWORD i = 0; i < count; i++) {
|
||||
GetModuleBaseNameA(process, modules[i], module_name, sizeof(module_name));
|
||||
if (!strncmp(dll.c_str(), module_name, dll.size())) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase)
|
||||
HANDLE inject_dll(DWORD pid, const string &dll_path, HMODULE *injected_base)
|
||||
{
|
||||
HANDLE hThread;
|
||||
SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR);
|
||||
SIZE_T path_size = dll_path.size() + 1;
|
||||
|
||||
// 1. 打开目标进程
|
||||
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||
if (hProcess == NULL) {
|
||||
MessageBox(NULL, L"打开进程失败", L"InjectDll", 0);
|
||||
if (!hProcess) {
|
||||
MessageBoxA(NULL, "打开进程失败", "inject_dll", 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 2. 在目标进程的内存里开辟空间
|
||||
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE);
|
||||
if (pRemoteAddress == NULL) {
|
||||
MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0);
|
||||
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE);
|
||||
if (!pRemoteAddress) {
|
||||
handle_injection_error(hProcess, NULL, "DLL 路径写入失败");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 3. 把 dll 的路径写入到目标进程的内存空间中
|
||||
WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL);
|
||||
WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL);
|
||||
|
||||
// 3. 创建一个远程线程,让目标进程调用 LoadLibrary
|
||||
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
|
||||
if (k32 == NULL) {
|
||||
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
|
||||
// 4. 创建一个远程线程,让目标进程调用 LoadLibrary
|
||||
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||
if (!k32) {
|
||||
handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryW");
|
||||
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA");
|
||||
if (!libAddr) {
|
||||
MessageBox(NULL, L"获取 LoadLibrary 失败", L"InjectDll", 0);
|
||||
handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
|
||||
if (hThread == NULL) {
|
||||
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
MessageBox(NULL, L"CreateRemoteThread 失败", L"InjectDll", 0);
|
||||
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
|
||||
if (!hThread) {
|
||||
handle_injection_error(hProcess, pRemoteAddress, "CreateRemoteThread 失败");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hThread, -1);
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
CloseHandle(hThread);
|
||||
|
||||
*injectedBase = GetTargetModuleBase(hProcess, filesystem::path(Wstring2String(dllPath)).filename().string());
|
||||
*injected_base = get_target_module_base(hProcess, filesystem::path(dll_path).filename().string());
|
||||
|
||||
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
|
||||
// CloseHandle(hProcess); // Close when exit
|
||||
|
||||
return hProcess;
|
||||
}
|
||||
|
||||
bool EjectDll(HANDLE process, HMODULE dllBase)
|
||||
bool eject_dll(HANDLE process, HMODULE dll_base)
|
||||
{
|
||||
HANDLE hThread = NULL;
|
||||
|
||||
// 使目标进程调用 FreeLibrary,卸载 DLL
|
||||
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
|
||||
if (k32 == NULL) {
|
||||
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
|
||||
return NULL;
|
||||
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||
if (!k32) {
|
||||
MessageBoxA(NULL, "获取 kernel32 失败", "eject_dll", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread");
|
||||
if (!libAddr) {
|
||||
MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0);
|
||||
return NULL;
|
||||
MessageBoxA(NULL, "获取 FreeLibrary 失败", "eject_dll", 0);
|
||||
return false;
|
||||
}
|
||||
hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dllBase, 0, NULL);
|
||||
if (hThread == NULL) {
|
||||
MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0);
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL);
|
||||
if (!hThread) {
|
||||
MessageBoxA(NULL, "FreeLibrary 调用失败!", "eject_dll", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -116,38 +112,34 @@ bool EjectDll(HANDLE process, HMODULE dllBase)
|
||||
return true;
|
||||
}
|
||||
|
||||
static UINT64 GetFuncOffset(LPCWSTR dllPath, LPCSTR funcName)
|
||||
static uint64_t get_func_offset(const string &dll_path, const string &func_name)
|
||||
{
|
||||
HMODULE dll = LoadLibrary(dllPath);
|
||||
if (dll == NULL) {
|
||||
MessageBox(NULL, L"获取 DLL 失败", L"GetFuncOffset", 0);
|
||||
HMODULE dll = LoadLibraryA(dll_path.c_str());
|
||||
if (!dll) {
|
||||
MessageBoxA(NULL, "获取 DLL 失败", "get_func_offset", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LPVOID absAddr = GetProcAddress(dll, funcName);
|
||||
UINT64 offset = (UINT64)absAddr - (UINT64)dll;
|
||||
LPVOID absAddr = GetProcAddress(dll, func_name.c_str());
|
||||
uint64_t offset = reinterpret_cast<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 +147,32 @@ bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcNa
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
|
||||
LPDWORD ret)
|
||||
bool call_dll_func_ex(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name,
|
||||
LPVOID parameter, size_t size, DWORD *ret)
|
||||
{
|
||||
UINT64 offset = GetFuncOffset(dllPath, funcName);
|
||||
if (offset == 0) {
|
||||
return false;
|
||||
uint64_t offset = get_func_offset(dll_path, func_name);
|
||||
if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast<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) {
|
||||
MessageBoxA(NULL, "申请内存失败", "call_dll_func_ex", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, sz, MEM_COMMIT, PAGE_READWRITE);
|
||||
if (pRemoteAddress == NULL) {
|
||||
MessageBox(NULL, L"申请内存失败", L"CallDllFuncEx", 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WriteProcessMemory(process, pRemoteAddress, parameter, sz, NULL);
|
||||
WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL);
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL);
|
||||
if (hThread == NULL) {
|
||||
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
|
||||
MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0);
|
||||
if (!hThread) {
|
||||
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
|
||||
MessageBoxA(NULL, "远程调用失败", "call_dll_func_ex", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
|
||||
if (ret != NULL) {
|
||||
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
|
||||
if (ret) {
|
||||
GetExitCodeThread(hThread, ret);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "framework.h"
|
||||
#include "sdk.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@ -6,55 +7,57 @@
|
||||
#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;
|
||||
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";
|
||||
|
||||
static std::optional<std::string> read_disclaimer_text(const std::string &path)
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return std::nullopt; // 文件打开失败
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
return String2Wstring(content);
|
||||
return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
static bool ShowDisclaimer()
|
||||
static bool show_disclaimer()
|
||||
{
|
||||
if (std::filesystem::exists(DISCLAIMER_FILE)) {
|
||||
if (std::filesystem::exists(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);
|
||||
auto disclaimerTextOpt = read_disclaimer_text(std::string(DISCLAIMER_TEXT_FILE));
|
||||
if (!disclaimerTextOpt || disclaimerTextOpt->empty()) {
|
||||
MessageBoxA(NULL, "免责声明文件为空或读取失败。", "错误", MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring disclaimerText = *disclaimerTextOpt;
|
||||
|
||||
int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
|
||||
|
||||
int result
|
||||
= MessageBoxA(NULL, disclaimerTextOpt->c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
|
||||
if (result == IDCANCEL) {
|
||||
MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION);
|
||||
MessageBoxA(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc);
|
||||
std::ofstream flagFile(std::string(DISCLAIMER_FLAG), std::ios::out | std::ios::trunc);
|
||||
if (!flagFile) {
|
||||
MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR);
|
||||
MessageBoxA(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
flagFile << "User accepted the license agreement.";
|
||||
@ -62,82 +65,79 @@ static bool ShowDisclaimer()
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::wstring GetDllPath(bool debug)
|
||||
static std::string get_dll_path(bool debug)
|
||||
{
|
||||
WCHAR buffer[MAX_PATH] = { 0 };
|
||||
GetModuleFileName(GetModuleHandle(WCFSDKDLL), buffer, MAX_PATH);
|
||||
char buffer[MAX_PATH] = { 0 };
|
||||
GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH);
|
||||
|
||||
std::filesystem::path path(buffer);
|
||||
path.remove_filename(); // 移除文件名,保留目录路径
|
||||
|
||||
path.remove_filename(); // 只保留目录路径
|
||||
path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL;
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR);
|
||||
return L"";
|
||||
MessageBoxA(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR);
|
||||
return "";
|
||||
}
|
||||
|
||||
return path.wstring();
|
||||
return path.string();
|
||||
}
|
||||
|
||||
int WxInitSDK(bool debug, int port)
|
||||
{
|
||||
if (!ShowDisclaimer()) {
|
||||
if (!show_disclaimer()) {
|
||||
exit(-1); // 用户拒绝协议,退出程序
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
DWORD wcPid = 0;
|
||||
|
||||
spyDllPath = GetDllPath(debug);
|
||||
spyDllPath = get_dll_path(debug);
|
||||
if (spyDllPath.empty()) {
|
||||
return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在
|
||||
}
|
||||
|
||||
status = OpenWeChat(&wcPid);
|
||||
status = util::open_wechat(wcPid);
|
||||
if (status != 0) {
|
||||
MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0);
|
||||
MessageBoxA(NULL, "打开微信失败", "WxInitSDK", 0);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!IsProcessX64(wcPid)) {
|
||||
MessageBox(NULL, L"只支持 64 位微信", L"WxInitSDK", 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开
|
||||
wcProcess = InjectDll(wcPid, spyDllPath.c_str(), &spyBase);
|
||||
wcProcess = inject_dll(wcPid, spyDllPath, &spyBase);
|
||||
if (wcProcess == NULL) {
|
||||
MessageBox(NULL, L"注入失败", L"WxInitSDK", 0);
|
||||
MessageBoxA(NULL, "注入失败", "WxInitSDK", 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
PortPath_t pp = { 0 };
|
||||
pp.port = port;
|
||||
sprintf_s(pp.path, MAX_PATH, "%s", std::filesystem::current_path().string().c_str());
|
||||
|
||||
if (!CallDllFuncEx(wcProcess, spyDllPath.c_str(), spyBase, "InitSpy", (LPVOID)&pp, sizeof(PortPath_t), NULL)) {
|
||||
MessageBox(NULL, L"初始化失败", L"WxInitSDK", 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
injected = true;
|
||||
return 0;
|
||||
|
||||
util::PortPath pp = { 0 };
|
||||
pp.port = port;
|
||||
snprintf(pp.path, MAX_PATH, "%s", std::filesystem::current_path().string().c_str());
|
||||
|
||||
status = -3; // TODO: 统一错误码
|
||||
bool success = call_dll_func_ex(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(util::PortPath),
|
||||
(DWORD *)&status);
|
||||
if (!success || status != 0) {
|
||||
WxDestroySDK();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int WxDestroySDK()
|
||||
{
|
||||
if (!injected) {
|
||||
return 1; // 未注入
|
||||
}
|
||||
|
||||
if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!CallDllFunc(wcProcess, spyDllPath.c_str(), spyBase, "CleanupSpy", NULL)) {
|
||||
if (!eject_dll(wcProcess, spyBase)) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (!EjectDll(wcProcess, spyBase)) {
|
||||
return -3; // TODO: Unify error codes
|
||||
}
|
||||
injected = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -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="userinfo_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="userinfo_manager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\rpc\proto\wcf.proto" />
|
||||
|
@ -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="userinfo_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="userinfo_manager.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="funcs.cpp">
|
||||
<ClCompile Include="misc_manager.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\com\util.cpp">
|
||||
|
115
WeChatFerry/spy/chatroom_manager.cpp
Normal file
115
WeChatFerry/spy/chatroom_manager.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#pragma execution_character_set("utf-8")
|
||||
|
||||
#include "chatroom_manager.h"
|
||||
#include "log.hpp"
|
||||
#include "pb_util.h"
|
||||
#include "rpc_helper.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
namespace chatroom
|
||||
{
|
||||
#define OS_GET_CHATROOM_MGR 0x1B83BD0
|
||||
#define OS_ADD_MEMBERS 0x2155100
|
||||
#define OS_DELETE_MEMBERS 0x2155740
|
||||
#define OS_INVITE_MEMBERS 0x2154AE0
|
||||
|
||||
using get_chatroom_mgr_t = QWORD (*)();
|
||||
using add_member_to_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD);
|
||||
using del_member_from_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||
using invite_member_to_chatroom_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD);
|
||||
|
||||
static vector<WxString> parse_wxids(const string &wxids)
|
||||
{
|
||||
vector<WxString> wx_members;
|
||||
wstringstream wss(util::s2w(wxids));
|
||||
wstring wstr;
|
||||
while (getline(wss, wstr, L',')) {
|
||||
wx_members.emplace_back(wstr);
|
||||
}
|
||||
return wx_members;
|
||||
}
|
||||
|
||||
int add_chatroom_member(const string &roomid, const string &wxids)
|
||||
{
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_chatroom_mgr_t get_chatroom_mgr
|
||||
= reinterpret_cast<get_chatroom_mgr_t>(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
|
||||
add_member_to_chatroom_t add_members
|
||||
= reinterpret_cast<add_member_to_chatroom_t>(g_WeChatWinDllAddr + OS_ADD_MEMBERS);
|
||||
|
||||
vector<WxString> wx_members = parse_wxids(wxids);
|
||||
auto wx_roomid = util::new_wx_string(roomid);
|
||||
QWORD p_members = reinterpret_cast<QWORD>(&wx_members.front());
|
||||
|
||||
return static_cast<int>(add_members(get_chatroom_mgr(), p_members, reinterpret_cast<QWORD>(wx_roomid.get()), 0));
|
||||
}
|
||||
|
||||
int del_chatroom_member(const string &roomid, const string &wxids)
|
||||
{
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_chatroom_mgr_t get_chatroom_mgr
|
||||
= reinterpret_cast<get_chatroom_mgr_t>(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
|
||||
del_member_from_chatroom_t del_members
|
||||
= reinterpret_cast<del_member_from_chatroom_t>(g_WeChatWinDllAddr + OS_DELETE_MEMBERS);
|
||||
|
||||
vector<WxString> wx_members = parse_wxids(wxids);
|
||||
auto wx_roomid = util::new_wx_string(roomid);
|
||||
QWORD p_members = reinterpret_cast<QWORD>(&wx_members.front());
|
||||
|
||||
return static_cast<int>(del_members(get_chatroom_mgr(), p_members, reinterpret_cast<QWORD>(wx_roomid.get())));
|
||||
}
|
||||
|
||||
int invite_chatroom_member(const string &roomid, const string &wxids)
|
||||
{
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
invite_member_to_chatroom_t invite_members
|
||||
= reinterpret_cast<invite_member_to_chatroom_t>(g_WeChatWinDllAddr + OS_INVITE_MEMBERS);
|
||||
|
||||
vector<WxString> wx_members = parse_wxids(wxids);
|
||||
auto wx_roomid = util::new_wx_string(roomid);
|
||||
QWORD p_members = reinterpret_cast<QWORD>(&wx_members.front());
|
||||
|
||||
return static_cast<int>(invite_members(reinterpret_cast<QWORD>(wx_roomid.get()->wptr), p_members,
|
||||
reinterpret_cast<QWORD>(wx_roomid.get()), 0));
|
||||
}
|
||||
|
||||
bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||
{
|
||||
const std::string wxids = m.wxids;
|
||||
const std::string roomid = m.roomid;
|
||||
return fill_response<Functions_FUNC_ADD_ROOM_MEMBERS>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = add_chatroom_member(roomid, wxids); });
|
||||
}
|
||||
|
||||
bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||
{
|
||||
const std::string wxids = m.wxids;
|
||||
const std::string roomid = m.roomid;
|
||||
return fill_response<Functions_FUNC_DEL_ROOM_MEMBERS>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = del_chatroom_member(roomid, wxids); });
|
||||
}
|
||||
|
||||
bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||
{
|
||||
const std::string wxids = m.wxids;
|
||||
const std::string roomid = m.roomid;
|
||||
return fill_response<Functions_FUNC_INV_ROOM_MEMBERS>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = invite_chatroom_member(roomid, wxids); });
|
||||
}
|
||||
|
||||
} // namespace chatroom
|
24
WeChatFerry/spy/chatroom_manager.h
Normal file
24
WeChatFerry/spy/chatroom_manager.h
Normal 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
|
@ -1,113 +0,0 @@
|
||||
#include "framework.h"
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "chatroom_mgmt.h"
|
||||
#include "log.hpp"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
#define OS_GET_CHATROOM_MGR 0x1B83BD0
|
||||
#define OS_ADD_MEMBERS 0x2155100
|
||||
#define OS_DELETE_MEMBERS 0x2155740
|
||||
#define OS_INVITE_MEMBERS 0x2154AE0
|
||||
|
||||
typedef QWORD (*GetChatRoomMgr_t)();
|
||||
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
|
||||
typedef QWORD (*DelMemberFromChatRoom_t)(QWORD, QWORD, QWORD);
|
||||
typedef QWORD (*InviteMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
|
||||
|
||||
int AddChatroomMember(string roomid, string wxids)
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return status;
|
||||
}
|
||||
|
||||
GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
|
||||
AddMemberToChatRoom_t AddMembers = (AddMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_ADD_MEMBERS);
|
||||
|
||||
vector<wstring> vMembers;
|
||||
vector<WxString> vWxMembers;
|
||||
wstringstream wss(String2Wstring(wxids));
|
||||
while (wss.good()) {
|
||||
wstring wstr;
|
||||
getline(wss, wstr, L',');
|
||||
vMembers.push_back(wstr);
|
||||
WxString wxMember(vMembers.back());
|
||||
vWxMembers.push_back(wxMember);
|
||||
}
|
||||
|
||||
QWORD temp[2] = { 0 };
|
||||
WxString *pWxRoomid = NewWxStringFromStr(roomid);
|
||||
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
|
||||
|
||||
QWORD mgr = GetChatRoomMgr();
|
||||
status = (int)AddMembers(mgr, pMembers, (QWORD)pWxRoomid, (QWORD)temp);
|
||||
return status;
|
||||
}
|
||||
|
||||
int DelChatroomMember(string roomid, string wxids)
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return status;
|
||||
}
|
||||
|
||||
GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR);
|
||||
DelMemberFromChatRoom_t DelMembers = (DelMemberFromChatRoom_t)(g_WeChatWinDllAddr + OS_DELETE_MEMBERS);
|
||||
|
||||
vector<wstring> vMembers;
|
||||
vector<WxString> vWxMembers;
|
||||
wstringstream wss(String2Wstring(wxids));
|
||||
while (wss.good()) {
|
||||
wstring wstr;
|
||||
getline(wss, wstr, L',');
|
||||
vMembers.push_back(wstr);
|
||||
WxString wxMember(vMembers.back());
|
||||
vWxMembers.push_back(wxMember);
|
||||
}
|
||||
|
||||
WxString *pWxRoomid = NewWxStringFromStr(roomid);
|
||||
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
|
||||
|
||||
QWORD mgr = GetChatRoomMgr();
|
||||
status = (int)DelMembers(mgr, pMembers, (QWORD)pWxRoomid);
|
||||
return status;
|
||||
}
|
||||
|
||||
int InviteChatroomMember(string roomid, string wxids)
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
if (roomid.empty() || wxids.empty()) {
|
||||
LOG_ERROR("Empty roomid or wxids.");
|
||||
return status;
|
||||
}
|
||||
|
||||
InviteMemberToChatRoom_t InviteMembers = (InviteMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_INVITE_MEMBERS);
|
||||
|
||||
vector<wstring> vMembers;
|
||||
vector<WxString> vWxMembers;
|
||||
wstringstream wss(String2Wstring(wxids));
|
||||
while (wss.good()) {
|
||||
wstring wstr;
|
||||
getline(wss, wstr, L',');
|
||||
vMembers.push_back(wstr);
|
||||
WxString wxMember(vMembers.back());
|
||||
vWxMembers.push_back(wxMember);
|
||||
}
|
||||
QWORD temp[2] = { 0 };
|
||||
wstring wsRoomid = String2Wstring(roomid);
|
||||
WxString *pWxRoomid = NewWxStringFromWstr(wsRoomid);
|
||||
QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start;
|
||||
|
||||
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
|
||||
return status;
|
||||
}
|
@ -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);
|
224
WeChatFerry/spy/contact_manager.cpp
Normal file
224
WeChatFerry/spy/contact_manager.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
#pragma execution_character_set("utf-8")
|
||||
|
||||
#include "contact_manager.h"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "pb_util.h"
|
||||
#include "rpc_helper.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
namespace contact
|
||||
{
|
||||
#define OS_GET_CONTACT_MGR 0x1B417A0
|
||||
#define OS_GET_CONTACT_LIST 0x219ED10
|
||||
#define OS_CONTACT_BIN 0x200
|
||||
#define OS_CONTACT_BIN_LEN 0x208
|
||||
#define OS_CONTACT_WXID 0x10
|
||||
#define OS_CONTACT_CODE 0x30
|
||||
#define OS_CONTACT_REMARK 0x80
|
||||
#define OS_CONTACT_NAME 0xA0
|
||||
#define OS_CONTACT_GENDER 0x0E
|
||||
#define OS_CONTACT_STEP 0x6A8
|
||||
|
||||
using get_contact_mgr_t = QWORD (*)();
|
||||
using get_contact_list_t = QWORD (*)(QWORD, QWORD);
|
||||
|
||||
#define FEAT_LEN 5
|
||||
static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
|
||||
static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 };
|
||||
static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 };
|
||||
|
||||
static QWORD find_mem(QWORD start, QWORD end, const void *target, size_t len)
|
||||
{
|
||||
uint8_t *p = reinterpret_cast<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;
|
||||
get_contact_mgr_t func_get_contact_mgr
|
||||
= reinterpret_cast<get_contact_mgr_t>(g_WeChatWinDllAddr + OS_GET_CONTACT_MGR);
|
||||
get_contact_list_t func_get_contact_list
|
||||
= reinterpret_cast<get_contact_list_t>(g_WeChatWinDllAddr + OS_GET_CONTACT_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 + OS_CONTACT_BIN);
|
||||
QWORD lenbin = util::get_dword(pstart + OS_CONTACT_BIN_LEN);
|
||||
|
||||
cnt.wxid = util::get_str_by_wstr_addr(pstart + OS_CONTACT_WXID);
|
||||
cnt.code = util::get_str_by_wstr_addr(pstart + OS_CONTACT_CODE);
|
||||
cnt.remark = util::get_str_by_wstr_addr(pstart + OS_CONTACT_REMARK);
|
||||
cnt.name = util::get_str_by_wstr_addr(pstart + OS_CONTACT_NAME);
|
||||
|
||||
cnt.country = get_cnt_string(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN);
|
||||
cnt.province = get_cnt_string(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN);
|
||||
cnt.city = get_cnt_string(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN);
|
||||
|
||||
cnt.gender = (pbin == 0) ? 0 : static_cast<DWORD>(*(uint8_t *)(pbin + OS_CONTACT_GENDER));
|
||||
|
||||
contacts.push_back(cnt);
|
||||
pstart += OS_CONTACT_STEP;
|
||||
}
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
int accept_new_friend(const std::string &v3, const std::string &v4, int scene)
|
||||
{
|
||||
int success = -1;
|
||||
#if 0
|
||||
DWORD accept_new_friend_call1 = g_WeChatWinDllAddr + g_WxCalls.anf.call1;
|
||||
DWORD accept_new_friend_call2 = g_WeChatWinDllAddr + g_WxCalls.anf.call2;
|
||||
DWORD accept_new_friend_call3 = g_WeChatWinDllAddr + g_WxCalls.anf.call3;
|
||||
DWORD accept_new_friend_call4 = g_WeChatWinDllAddr + g_WxCalls.anf.call4;
|
||||
|
||||
char buffer[0x40] = { 0 };
|
||||
char nullbuffer[0x3CC] = { 0 };
|
||||
|
||||
LOG_DEBUG("\nv3: {}\nv4: {}\nscene: {}", v3, v4, scene);
|
||||
|
||||
wstring ws_v3 = util::s2w(v3);
|
||||
wstring ws_v4 = util::s2w(v4);
|
||||
WxString wx_v3(ws_v3);
|
||||
WxString wx_v4(ws_v4);
|
||||
|
||||
__asm {
|
||||
pushad;
|
||||
pushfd;
|
||||
lea ecx, buffer;
|
||||
call accept_new_friend_call1;
|
||||
mov esi, 0x0;
|
||||
mov edi, scene;
|
||||
push esi;
|
||||
push edi;
|
||||
sub esp, 0x14;
|
||||
mov ecx, esp;
|
||||
lea eax, wx_v4;
|
||||
push eax;
|
||||
call accept_new_friend_call2;
|
||||
sub esp, 0x8;
|
||||
push 0x0;
|
||||
lea eax, nullbuffer;
|
||||
push eax;
|
||||
lea eax, wx_v3;
|
||||
push eax;
|
||||
lea ecx, buffer;
|
||||
call accept_new_friend_call3;
|
||||
mov success, eax;
|
||||
lea ecx, buffer;
|
||||
call accept_new_friend_call4;
|
||||
popfd;
|
||||
popad;
|
||||
}
|
||||
#endif
|
||||
return success; // 成功返回 1
|
||||
}
|
||||
|
||||
RpcContact_t get_contact_by_wxid(const string &wxid)
|
||||
{
|
||||
RpcContact_t contact;
|
||||
#if 0
|
||||
char buff[0x440] = { 0 };
|
||||
wstring ws_wxid = util::s2w(wxid);
|
||||
WxString pri(ws_wxid);
|
||||
|
||||
DWORD contact_mgr_addr = g_WeChatWinDllAddr + 0x75A4A0;
|
||||
DWORD get_contact_addr = g_WeChatWinDllAddr + 0xC04E00;
|
||||
DWORD free_contact_addr = g_WeChatWinDllAddr + 0xEA7880;
|
||||
|
||||
__asm {
|
||||
PUSHAD
|
||||
PUSHFD
|
||||
CALL contact_mgr_addr
|
||||
LEA ECX,buff
|
||||
PUSH ECX
|
||||
LEA ECX,pri
|
||||
PUSH ECX
|
||||
MOV ECX,EAX
|
||||
CALL get_contact_addr
|
||||
POPFD
|
||||
POPAD
|
||||
}
|
||||
|
||||
contact.wxid = wxid;
|
||||
contact.code = util::get_str_by_wstr_addr(reinterpret_cast<DWORD>(buff) + g_WxCalls.contact.wxCode);
|
||||
contact.remark = util::get_str_by_wstr_addr(reinterpret_cast<DWORD>(buff) + g_WxCalls.contact.wxRemark);
|
||||
contact.name = util::get_str_by_wstr_addr(reinterpret_cast<DWORD>(buff) + g_WxCalls.contact.wxName);
|
||||
contact.gender = util::get_dword(reinterpret_cast<DWORD>(buff) + 0x148);
|
||||
|
||||
__asm {
|
||||
PUSHAD
|
||||
PUSHFD
|
||||
LEA ECX,buff
|
||||
CALL free_contact_addr
|
||||
POPFD
|
||||
POPAD
|
||||
}
|
||||
#endif
|
||||
return contact;
|
||||
}
|
||||
|
||||
bool rpc_get_contacts(uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_CONTACTS>(out, len, [](Response &rsp) {
|
||||
vector<RpcContact_t> contacts = get_contacts();
|
||||
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
|
||||
rsp.msg.contacts.contacts.arg = &contacts;
|
||||
});
|
||||
}
|
||||
|
||||
bool rpc_get_contact_info(const string &wxid, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_CONTACT_INFO>(out, len, [&](Response &rsp) {
|
||||
vector<RpcContact_t> contacts = { get_contact_by_wxid(wxid) };
|
||||
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
|
||||
rsp.msg.contacts.contacts.arg = &contacts;
|
||||
});
|
||||
}
|
||||
|
||||
bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len)
|
||||
{
|
||||
const string v3 = v.v3;
|
||||
const string v4 = v.v4;
|
||||
int scene = v.scene;
|
||||
return fill_response<Functions_FUNC_ACCEPT_FRIEND>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = accept_new_friend(v3, v4, scene); });
|
||||
}
|
||||
|
||||
} // namespace contact
|
30
WeChatFerry/spy/contact_manager.h
Normal file
30
WeChatFerry/spy/contact_manager.h
Normal 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
|
@ -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
|
@ -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);
|
292
WeChatFerry/spy/database_executor.cpp
Normal file
292
WeChatFerry/spy/database_executor.cpp
Normal file
@ -0,0 +1,292 @@
|
||||
#include "database_executor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "pb_util.h"
|
||||
#include "rpc_helper.h"
|
||||
#include "sqlite3.h"
|
||||
#include "util.h"
|
||||
|
||||
extern UINT64 g_WeChatWinDllAddr;
|
||||
|
||||
namespace db
|
||||
{
|
||||
#define OFFSET_DB_INSTANCE 0x5902000
|
||||
#define OFFSET_DB_MICROMSG 0xB8
|
||||
#define OFFSET_DB_CHAT_MSG 0x2C8
|
||||
#define OFFSET_DB_MISC 0x5F0
|
||||
#define OFFSET_DB_EMOTION 0x15F0
|
||||
#define OFFSET_DB_MEDIA 0xF48
|
||||
#define OFFSET_DB_BIZCHAT_MSG 0x1A70
|
||||
#define OFFSET_DB_FUNCTION_MSG 0x1B98
|
||||
#define OFFSET_DB_NAME 0x28
|
||||
#define OFFSET_DB_MSG_MGR 0x595F900
|
||||
|
||||
using db_map_t = std::map<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 + OFFSET_DB_NAME));
|
||||
std::string dbname = util::w2s(std::wstring(wsp));
|
||||
db_map[dbname] = util::get_qword(base + offset);
|
||||
}
|
||||
|
||||
static void get_msg_db_handle(QWORD msg_mgr_addr)
|
||||
{
|
||||
QWORD db_index = util::get_qword(msg_mgr_addr + 0x68);
|
||||
QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
|
||||
for (uint32_t i = 0; i < db_index; i++) {
|
||||
QWORD db_addr = util::get_qword(p_start + i * 0x08);
|
||||
if (db_addr) {
|
||||
// MSGi.db
|
||||
std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
|
||||
db_map[dbname] = util::get_qword(db_addr + 0x78);
|
||||
|
||||
// MediaMsgi.db
|
||||
QWORD mmdb_addr = util::get_qword(db_addr + 0x20);
|
||||
std::string mmdbname = util::w2s(util::get_pp_wstring(mmdb_addr + 0x78));
|
||||
db_map[mmdbname] = util::get_qword(mmdb_addr + 0x50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db_map_t get_db_handles()
|
||||
{
|
||||
db_map.clear();
|
||||
QWORD db_instance_addr = util::get_qword(g_WeChatWinDllAddr + OFFSET_DB_INSTANCE);
|
||||
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_MICROMSG); // MicroMsg.db
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_CHAT_MSG); // ChatMsg.db
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_MISC); // Misc.db
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_EMOTION); // Emotion.db
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_MEDIA); // Media.db
|
||||
get_db_handle(db_instance_addr, OFFSET_DB_FUNCTION_MSG); // Function.db
|
||||
|
||||
get_msg_db_handle(util::get_qword(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR)); // MSGi.db & MediaMsgi.db
|
||||
|
||||
return db_map;
|
||||
}
|
||||
|
||||
DbNames_t get_db_names()
|
||||
{
|
||||
if (db_map.empty()) {
|
||||
db_map = get_db_handles();
|
||||
}
|
||||
|
||||
DbNames_t names;
|
||||
for (const auto &[k, _] : db_map) {
|
||||
names.push_back(k);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
static int cb_get_tables(void *ret, int argc, char **argv, char **azColName)
|
||||
{
|
||||
auto *tables = static_cast<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';";
|
||||
Sqlite3_exec p_sqlite3_exec = reinterpret_cast<Sqlite3_exec>(g_WeChatWinDllAddr + SQLITE3_EXEC_OFFSET);
|
||||
p_sqlite3_exec(it->second, sql, (Sqlite3_callback)cb_get_tables, (void *)&tables, nullptr);
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
DbRows_t exec_db_query(const std::string &db, const std::string &sql)
|
||||
{
|
||||
DbRows_t rows;
|
||||
|
||||
Sqlite3_prepare func_prepare = reinterpret_cast<Sqlite3_prepare>(g_WeChatWinDllAddr + SQLITE3_PREPARE_OFFSET);
|
||||
Sqlite3_step func_step = reinterpret_cast<Sqlite3_step>(g_WeChatWinDllAddr + SQLITE3_STEP_OFFSET);
|
||||
Sqlite3_column_count func_column_count
|
||||
= reinterpret_cast<Sqlite3_column_count>(g_WeChatWinDllAddr + SQLITE3_COLUMN_COUNT_OFFSET);
|
||||
Sqlite3_column_name func_column_name
|
||||
= reinterpret_cast<Sqlite3_column_name>(g_WeChatWinDllAddr + SQLITE3_COLUMN_NAME_OFFSET);
|
||||
Sqlite3_column_type func_column_type
|
||||
= reinterpret_cast<Sqlite3_column_type>(g_WeChatWinDllAddr + SQLITE3_COLUMN_TYPE_OFFSET);
|
||||
Sqlite3_column_blob func_column_blob
|
||||
= reinterpret_cast<Sqlite3_column_blob>(g_WeChatWinDllAddr + SQLITE3_COLUMN_BLOB_OFFSET);
|
||||
Sqlite3_column_bytes func_column_bytes
|
||||
= reinterpret_cast<Sqlite3_column_bytes>(g_WeChatWinDllAddr + SQLITE3_COLUMN_BYTES_OFFSET);
|
||||
Sqlite3_finalize func_finalize = reinterpret_cast<Sqlite3_finalize>(g_WeChatWinDllAddr + SQLITE3_FINALIZE_OFFSET);
|
||||
|
||||
if (db_map.empty()) {
|
||||
db_map = get_db_handles();
|
||||
}
|
||||
|
||||
auto it = db_map.find(db);
|
||||
if (it == db_map.end() || it->second == 0) {
|
||||
LOG_WARN("Empty handle for database '{}', retrying...", db);
|
||||
db_map = get_db_handles();
|
||||
it = db_map.find(db);
|
||||
if (it == db_map.end() || it->second == 0) {
|
||||
LOG_ERROR("Failed to get handle for database '{}'", db);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
QWORD *stmt;
|
||||
int rc = func_prepare(it->second, sql.c_str(), -1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOG_ERROR("SQL prepare failed for '{}': error code {}", db, rc);
|
||||
return rows;
|
||||
}
|
||||
|
||||
while (func_step(stmt) == SQLITE_ROW) {
|
||||
DbRow_t row;
|
||||
int col_count = func_column_count(stmt);
|
||||
for (int i = 0; i < col_count; i++) {
|
||||
DbField_t field;
|
||||
field.type = func_column_type(stmt, i);
|
||||
field.column = func_column_name(stmt, i);
|
||||
|
||||
int length = func_column_bytes(stmt, i);
|
||||
const void *blob = func_column_blob(stmt, i);
|
||||
if (length > 0 && field.type != SQLITE_NULL) {
|
||||
field.content.resize(length);
|
||||
std::memcpy(field.content.data(), blob, length);
|
||||
}
|
||||
row.push_back(field);
|
||||
}
|
||||
rows.push_back(row);
|
||||
}
|
||||
|
||||
func_finalize(stmt);
|
||||
return rows;
|
||||
}
|
||||
|
||||
int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx)
|
||||
{
|
||||
if (!local_id || !db_idx) {
|
||||
LOG_ERROR("Invalid pointer arguments!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
QWORD msg_mgr_addr = util::get_qword(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR);
|
||||
int db_index = static_cast<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(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR);
|
||||
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)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_DB_NAMES>(out, len, [&](Response &rsp) {
|
||||
DbNames_t names = get_db_names();
|
||||
rsp.msg.dbs.names.funcs.encode = encode_dbnames;
|
||||
rsp.msg.dbs.names.arg = &names;
|
||||
});
|
||||
}
|
||||
|
||||
bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_DB_TABLES>(out, len, [&](Response &rsp) {
|
||||
DbTables_t tables = get_db_tables(db);
|
||||
rsp.msg.tables.tables.funcs.encode = encode_tables;
|
||||
rsp.msg.tables.tables.arg = &tables;
|
||||
});
|
||||
}
|
||||
|
||||
bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len)
|
||||
{
|
||||
const std::string db(query.db);
|
||||
const std::string sql(query.sql);
|
||||
return fill_response<Functions_FUNC_EXEC_DB_QUERY>(out, len, [&](Response &rsp) {
|
||||
DbRows_t rows = exec_db_query(db, sql);
|
||||
rsp.msg.rows.rows.funcs.encode = encode_rows;
|
||||
rsp.msg.rows.rows.arg = &rows;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace db
|
34
WeChatFerry/spy/database_executor.h
Normal file
34
WeChatFerry/spy/database_executor.h
Normal 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
|
@ -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>();
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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);
|
280
WeChatFerry/spy/message_handler.cpp
Normal file
280
WeChatFerry/spy/message_handler.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
#include "message_handler.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "framework.h"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "pb_util.h"
|
||||
#include "rpc_helper.h"
|
||||
#include "userinfo_manager.h"
|
||||
#include "util.h"
|
||||
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
#define OS_RECV_MSG_ID 0x30
|
||||
#define OS_RECV_MSG_TYPE 0x38
|
||||
#define OS_RECV_MSG_SELF 0x3C
|
||||
#define OS_RECV_MSG_TS 0x44
|
||||
#define OS_RECV_MSG_ROOMID 0x48
|
||||
#define OS_RECV_MSG_CONTENT 0x88
|
||||
#define OS_RECV_MSG_WXID 0x240
|
||||
#define OS_RECV_MSG_SIGN 0x260
|
||||
#define OS_RECV_MSG_THUMB 0x280
|
||||
#define OS_RECV_MSG_EXTRA 0x2A0
|
||||
#define OS_RECV_MSG_XML 0x308
|
||||
#define OS_RECV_MSG_CALL 0x213ED90
|
||||
#define OS_PYQ_MSG_START 0x30
|
||||
#define OS_PYQ_MSG_END 0x38
|
||||
#define OS_PYQ_MSG_TS 0x38
|
||||
#define OS_PYQ_MSG_XML 0x9B8
|
||||
#define OS_PYQ_MSG_SENDER 0x18
|
||||
#define OS_PYQ_MSG_CONTENT 0x48
|
||||
#define OS_PYQ_MSG_CALL 0x2E42C90
|
||||
#define OS_WXLOG 0x2613D20
|
||||
|
||||
namespace message
|
||||
{
|
||||
QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2)
|
||||
{
|
||||
auto &handler = getInstance();
|
||||
WxMsg_t wxMsg = {};
|
||||
try {
|
||||
wxMsg.id = util::get_qword(arg2 + OS_RECV_MSG_ID);
|
||||
wxMsg.type = util::get_dword(arg2 + OS_RECV_MSG_TYPE);
|
||||
wxMsg.is_self = util::get_dword(arg2 + OS_RECV_MSG_SELF);
|
||||
wxMsg.ts = util::get_dword(arg2 + OS_RECV_MSG_TS);
|
||||
wxMsg.content = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_CONTENT);
|
||||
wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_SIGN);
|
||||
wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_XML);
|
||||
wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_ROOMID);
|
||||
|
||||
if (wxMsg.roomid.find("@chatroom") != std::string::npos) {
|
||||
wxMsg.is_group = true;
|
||||
wxMsg.sender
|
||||
= wxMsg.is_self ? userinfo::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OS_RECV_MSG_WXID);
|
||||
} else {
|
||||
wxMsg.is_group = false;
|
||||
wxMsg.sender = wxMsg.is_self ? userinfo::get_self_wxid() : wxMsg.roomid;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LOG_ERROR(util::gb2312_to_utf8(e.what()));
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<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;
|
||||
}
|
||||
|
||||
void Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3)
|
||||
{
|
||||
auto &handler = getInstance();
|
||||
QWORD startAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_START);
|
||||
QWORD endAddr = *(QWORD *)(arg2 + OS_PYQ_MSG_END);
|
||||
|
||||
if (startAddr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (startAddr < endAddr) {
|
||||
WxMsg_t wxMsg;
|
||||
|
||||
wxMsg.type = 0x00;
|
||||
wxMsg.is_self = false;
|
||||
wxMsg.is_group = false;
|
||||
wxMsg.id = util::get_qword(startAddr);
|
||||
wxMsg.ts = util::get_dword(startAddr + OS_PYQ_MSG_TS);
|
||||
wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_XML);
|
||||
wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_SENDER);
|
||||
wxMsg.content = util::get_str_by_wstr_addr(startAddr + OS_PYQ_MSG_CONTENT);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(handler.mutex_);
|
||||
handler.msgQueue_.push(wxMsg);
|
||||
}
|
||||
|
||||
handler.cv_.notify_all();
|
||||
startAddr += 0x1618;
|
||||
}
|
||||
}
|
||||
|
||||
Handler &Handler::getInstance()
|
||||
{
|
||||
static Handler instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Handler::Handler()
|
||||
{
|
||||
isLogging = false;
|
||||
isListeningMsg = false;
|
||||
isListeningPyq = false;
|
||||
}
|
||||
|
||||
Handler::~Handler()
|
||||
{
|
||||
DisableLog();
|
||||
UnListenMsg();
|
||||
UnListenPyq();
|
||||
}
|
||||
|
||||
MsgTypes_t Handler::GetMsgTypes()
|
||||
{
|
||||
return { { 0x00, "朋友圈消息" },
|
||||
{ 0x01, "文字" },
|
||||
{ 0x03, "图片" },
|
||||
{ 0x22, "语音" },
|
||||
{ 0x25, "好友确认" },
|
||||
{ 0x28, "POSSIBLEFRIEND_MSG" },
|
||||
{ 0x2A, "名片" },
|
||||
{ 0x2B, "视频" },
|
||||
{ 0x2F, "石头剪刀布 | 表情图片" },
|
||||
{ 0x30, "位置" },
|
||||
{ 0x31, "共享实时位置、文件、转账、链接" },
|
||||
{ 0x32, "VOIPMSG" },
|
||||
{ 0x33, "微信初始化" },
|
||||
{ 0x34, "VOIPNOTIFY" },
|
||||
{ 0x35, "VOIPINVITE" },
|
||||
{ 0x3E, "小视频" },
|
||||
{ 0x42, "微信红包" },
|
||||
{ 0x270F, "SYSNOTICE" },
|
||||
{ 0x2710, "红包、系统消息" },
|
||||
{ 0x2712, "撤回消息" },
|
||||
{ 0x100031, "搜狗表情" },
|
||||
{ 0x1000031, "链接" },
|
||||
{ 0x1A000031, "微信红包" },
|
||||
{ 0x20010031, "红包封面" },
|
||||
{ 0x2D000031, "视频号视频" },
|
||||
{ 0x2E000031, "视频号名片" },
|
||||
{ 0x31000031, "引用消息" },
|
||||
{ 0x37000031, "拍一拍" },
|
||||
{ 0x3A000031, "视频号直播" },
|
||||
{ 0x3A100031, "商品链接" },
|
||||
{ 0x3A200031, "视频号直播" },
|
||||
{ 0x3E000031, "音乐链接" },
|
||||
{ 0x41000031, "文件" } };
|
||||
}
|
||||
|
||||
std::optional<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;
|
||||
|
||||
funcWxLog = reinterpret_cast<funcWxLog_t>(g_WeChatWinDllAddr + OS_WXLOG);
|
||||
|
||||
if (InitializeHook() != MH_OK) return -1;
|
||||
if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast<LPVOID *>(&realWxLog)) != MH_OK) return -1;
|
||||
if (MH_EnableHook(funcWxLog) != MH_OK) return -1;
|
||||
|
||||
isLogging = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Handler::DisableLog()
|
||||
{
|
||||
if (!isLogging) return 1;
|
||||
if (MH_DisableHook(funcWxLog) != MH_OK) return -1;
|
||||
if (UninitializeHook() != MH_OK) return -1;
|
||||
isLogging = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Handler::ListenMsg()
|
||||
{
|
||||
if (isListeningMsg) return 1;
|
||||
|
||||
funcRecvMsg = reinterpret_cast<funcRecvMsg_t>(g_WeChatWinDllAddr + OS_RECV_MSG_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 = reinterpret_cast<funcRecvPyq_t>(g_WeChatWinDllAddr + OS_PYQ_MSG_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)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_MSG_TYPES>(out, len, [&](Response &rsp) {
|
||||
MsgTypes_t types = GetMsgTypes();
|
||||
rsp.msg.types.types.funcs.encode = encode_types;
|
||||
rsp.msg.types.types.arg = &types;
|
||||
});
|
||||
}
|
||||
}
|
73
WeChatFerry/spy/message_handler.h
Normal file
73
WeChatFerry/spy/message_handler.h
Normal file
@ -0,0 +1,73 @@
|
||||
#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);
|
||||
|
||||
funcWxLog_t funcWxLog, realWxLog;
|
||||
funcRecvMsg_t funcRecvMsg, realRecvMsg;
|
||||
funcRecvPyq_t funcRecvPyq, realRecvPyq;
|
||||
|
||||
bool isMH_Initialized { false };
|
||||
|
||||
MH_STATUS InitializeHook();
|
||||
MH_STATUS UninitializeHook();
|
||||
|
||||
static QWORD DispatchMsg(QWORD arg1, QWORD arg2);
|
||||
static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
|
||||
QWORD a10, QWORD a11, QWORD a12);
|
||||
static void DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3);
|
||||
};
|
||||
|
||||
} // namespace message
|
363
WeChatFerry/spy/message_sender.cpp
Normal file
363
WeChatFerry/spy/message_sender.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
#include "message_sender.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "database_executor.h"
|
||||
#include "log.hpp"
|
||||
#include "rpc_helper.h"
|
||||
#include "spy_types.h"
|
||||
#include "userinfo_manager.h"
|
||||
#include "util.h"
|
||||
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
#define OS_NEW 0x1B5E140
|
||||
#define OS_FREE 0x1B55850
|
||||
#define OS_SEND_MSG_MGR 0x1B53FD0
|
||||
#define OS_SEND_TEXT 0x22C6B60
|
||||
#define OS_SEND_IMAGE 0x22BC2F0
|
||||
#define OS_GET_APP_MSG_MGR 0x1B58F70
|
||||
#define OS_SEND_FILE 0x20D0230
|
||||
#define OS_RTM_NEW 0x1B5D690
|
||||
#define OS_RTM_FREE 0x1B5CA60
|
||||
#define OS_SEND_RICH_TEXT 0x20DA210
|
||||
#define OS_SEND_PAT_MSG 0x2CAEC00
|
||||
#define OS_FORWARD_MSG 0x22C60E0
|
||||
#define OS_GET_EMOTION_MGR 0x1BCEF10
|
||||
#define OS_SEND_EMOTION 0x21B52D5
|
||||
#define OS_XML_BUFSIGN 0x24F0D70
|
||||
#define OS_SEND_XML 0x20CF360
|
||||
|
||||
namespace message
|
||||
{
|
||||
|
||||
Sender &Sender::get_instance()
|
||||
{
|
||||
static Sender instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Sender::Sender()
|
||||
{
|
||||
func_new = reinterpret_cast<New_t>(g_WeChatWinDllAddr + OS_NEW);
|
||||
func_free = reinterpret_cast<Free_t>(g_WeChatWinDllAddr + OS_FREE);
|
||||
func_send_msg_mgr = reinterpret_cast<SendMsgMgr_t>(g_WeChatWinDllAddr + OS_SEND_MSG_MGR);
|
||||
func_send_text = reinterpret_cast<SendText_t>(g_WeChatWinDllAddr + OS_SEND_TEXT);
|
||||
func_send_image = reinterpret_cast<SendImage_t>(g_WeChatWinDllAddr + OS_SEND_IMAGE);
|
||||
func_send_file = reinterpret_cast<SendFile_t>(g_WeChatWinDllAddr + OS_SEND_FILE);
|
||||
func_send_rich_text = reinterpret_cast<SendRichText_t>(g_WeChatWinDllAddr + OS_SEND_RICH_TEXT);
|
||||
func_send_pat = reinterpret_cast<SendPat_t>(g_WeChatWinDllAddr + OS_SEND_PAT_MSG);
|
||||
func_forward = reinterpret_cast<Forward_t>(g_WeChatWinDllAddr + OS_FORWARD_MSG);
|
||||
func_send_emotion = reinterpret_cast<SendEmotion_t>(g_WeChatWinDllAddr + OS_SEND_EMOTION);
|
||||
func_send_xml = reinterpret_cast<SendXml_t>(g_WeChatWinDllAddr + OS_SEND_XML);
|
||||
}
|
||||
|
||||
std::unique_ptr<WxString> Sender::new_wx_string(const char *str)
|
||||
{
|
||||
return new_wx_string(str ? std::string(str) : std::string());
|
||||
}
|
||||
std::unique_ptr<WxString> Sender::new_wx_string(const std::string &str)
|
||||
{
|
||||
return std::make_unique<WxString>(util::s2w(str));
|
||||
}
|
||||
|
||||
std::vector<WxString> Sender::parse_wxids(const string &wxids)
|
||||
{
|
||||
vector<WxString> wx_members;
|
||||
wstringstream wss(util::s2w(wxids));
|
||||
wstring wstr;
|
||||
while (getline(wss, wstr, L',')) {
|
||||
wx_members.emplace_back(wstr);
|
||||
}
|
||||
return wx_members;
|
||||
}
|
||||
|
||||
void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids)
|
||||
{
|
||||
auto wxWxid = new_wx_string(wxid);
|
||||
auto wxMsg = new_wx_string(msg);
|
||||
|
||||
std::vector<WxString> wx_at_wxids;
|
||||
if (!at_wxids.empty()) {
|
||||
wx_at_wxids = parse_wxids(at_wxids);
|
||||
} else {
|
||||
wx_at_wxids.emplace_back();
|
||||
}
|
||||
|
||||
QWORD wx_ater_list = reinterpret_cast<QWORD>(&(wx_at_wxids.front()));
|
||||
|
||||
char buffer[0x460] = { 0 };
|
||||
func_send_msg_mgr();
|
||||
func_send_text(reinterpret_cast<QWORD>(&buffer), reinterpret_cast<QWORD>(wxWxid.get()),
|
||||
reinterpret_cast<QWORD>(wxMsg.get()), wx_ater_list, 1, 1, 0, 0);
|
||||
func_free(reinterpret_cast<QWORD>(&buffer));
|
||||
}
|
||||
|
||||
void Sender::send_image(const std::string &wxid, const std::string &path)
|
||||
{
|
||||
auto wxWxid = new_wx_string(wxid);
|
||||
auto wxPath = new_wx_string(path);
|
||||
|
||||
char msg[0x460] = { 0 };
|
||||
char msgTmp[0x460] = { 0 };
|
||||
QWORD *flag[10] = { nullptr };
|
||||
|
||||
QWORD tmp1 = 0, tmp2 = 0;
|
||||
QWORD pMsgTmp = func_new(reinterpret_cast<QWORD>(&msgTmp));
|
||||
flag[8] = &tmp1;
|
||||
flag[9] = &tmp2;
|
||||
flag[1] = reinterpret_cast<QWORD *>(pMsgTmp);
|
||||
|
||||
QWORD pMsg = func_new(reinterpret_cast<QWORD>(&msg));
|
||||
QWORD sendMgr = func_send_msg_mgr();
|
||||
|
||||
func_send_image(sendMgr, pMsg, reinterpret_cast<QWORD>(wxWxid.get()), reinterpret_cast<QWORD>(wxPath.get()),
|
||||
reinterpret_cast<QWORD>(&flag));
|
||||
|
||||
func_free(pMsg);
|
||||
func_free(pMsgTmp);
|
||||
}
|
||||
|
||||
void Sender::send_file(const std::string &wxid, const std::string &path)
|
||||
{
|
||||
auto wxWxid = new_wx_string(wxid);
|
||||
auto wxPath = new_wx_string(path);
|
||||
|
||||
char msg[0x460] = { 0 };
|
||||
QWORD tmp1[4] = { 0 };
|
||||
QWORD tmp2[4] = { 0 };
|
||||
QWORD tmp3[4] = { 0 };
|
||||
|
||||
QWORD pMsg = func_new(reinterpret_cast<QWORD>(&msg));
|
||||
QWORD appMgr = func_get_app_mgr();
|
||||
|
||||
func_send_file(appMgr, pMsg, reinterpret_cast<QWORD>(wxWxid.get()), reinterpret_cast<QWORD>(wxPath.get()), 1, tmp1,
|
||||
0, tmp2, 0, tmp3, 0, 0);
|
||||
|
||||
func_free(pMsg);
|
||||
}
|
||||
|
||||
void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type)
|
||||
{
|
||||
std::unique_ptr<char[]> buff(new char[0x500]());
|
||||
std::unique_ptr<char[]> buff2(new char[0x500]());
|
||||
char nullBuf[0x1C] = { 0 };
|
||||
|
||||
func_new(reinterpret_cast<QWORD>(buff.get()));
|
||||
func_new(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(userinfo::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(reinterpret_cast<QWORD>(buff.get()));
|
||||
func_free(reinterpret_cast<QWORD>(buff2.get()));
|
||||
}
|
||||
|
||||
void Sender::send_emotion(const std::string &wxid, const std::string &path)
|
||||
{
|
||||
auto wxWxid = new_wx_string(wxid);
|
||||
auto wxPath = new_wx_string(path);
|
||||
|
||||
std::unique_ptr<QWORD[]> buff(new QWORD[8]()); // 0x20 bytes = 8 * QWORD
|
||||
|
||||
if (!buff) {
|
||||
LOG_ERROR("Out of Memory...");
|
||||
return;
|
||||
}
|
||||
|
||||
QWORD mgr = func_get_emotion_mgr();
|
||||
func_send_emotion(mgr, reinterpret_cast<QWORD>(wxPath.get()), reinterpret_cast<QWORD>(buff.get()),
|
||||
reinterpret_cast<QWORD>(wxWxid.get()), 2, reinterpret_cast<QWORD>(buff.get()), 0,
|
||||
reinterpret_cast<QWORD>(buff.get()));
|
||||
}
|
||||
|
||||
int Sender::send_rich_text(const RichText &rt)
|
||||
{
|
||||
#define SRTM_SIZE 0x3F0
|
||||
QWORD status = -1;
|
||||
|
||||
char *buff = static_cast<char *>(HeapAlloc(GetProcessHeap(), 0, SRTM_SIZE));
|
||||
if (!buff) {
|
||||
LOG_ERROR("Out of Memory...");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buff, 0, SRTM_SIZE);
|
||||
func_new(reinterpret_cast<QWORD>(buff));
|
||||
|
||||
auto pReceiver = new_wx_string(rt.receiver);
|
||||
auto pTitle = new_wx_string(rt.title);
|
||||
auto pUrl = new_wx_string(rt.url);
|
||||
auto pThumburl = new_wx_string(rt.thumburl);
|
||||
auto pDigest = new_wx_string(rt.digest);
|
||||
auto pAccount = new_wx_string(rt.account);
|
||||
auto pName = new_wx_string(rt.name);
|
||||
|
||||
memcpy(buff + 0x8, pTitle.get(), sizeof(WxString));
|
||||
memcpy(buff + 0x48, pUrl.get(), sizeof(WxString));
|
||||
memcpy(buff + 0xB0, pThumburl.get(), sizeof(WxString));
|
||||
memcpy(buff + 0xF0, pDigest.get(), sizeof(WxString));
|
||||
memcpy(buff + 0x2C0, pAccount.get(), sizeof(WxString));
|
||||
memcpy(buff + 0x2E0, pName.get(), sizeof(WxString));
|
||||
|
||||
QWORD mgr = func_get_app_mgr();
|
||||
status = func_send_rich_text(mgr, reinterpret_cast<QWORD>(pReceiver.get()), reinterpret_cast<QWORD>(buff));
|
||||
func_free(reinterpret_cast<QWORD>(buff));
|
||||
|
||||
return static_cast<int>(status);
|
||||
}
|
||||
|
||||
int Sender::send_pat(const std::string &roomid, const std::string &wxid)
|
||||
{
|
||||
QWORD status = -1;
|
||||
|
||||
auto wxRoomid = new_wx_string(roomid);
|
||||
auto wxWxid = new_wx_string(wxid);
|
||||
|
||||
status = func_send_pat(reinterpret_cast<QWORD>(wxRoomid.get()), reinterpret_cast<QWORD>(wxWxid.get()));
|
||||
|
||||
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);
|
||||
auto wxReceiver = new_wx_string(receiver);
|
||||
|
||||
return static_cast<int>(func_forward(reinterpret_cast<QWORD>(wxReceiver.get()), 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 = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
80
WeChatFerry/spy/message_sender.h
Normal file
80
WeChatFerry/spy/message_sender.h
Normal file
@ -0,0 +1,80 @@
|
||||
#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, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||
using SendImage_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||
using SendFile_t
|
||||
= QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD);
|
||||
using SendRichText_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||
using SendPat_t = QWORD (*)(QWORD, QWORD);
|
||||
using Forward_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD);
|
||||
using GetEmotionMgr_t = QWORD (*)();
|
||||
using SendEmotion_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||
using XmlBufSign_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||
using SendXml_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||
|
||||
New_t func_new;
|
||||
Free_t func_free;
|
||||
SendMsgMgr_t func_send_msg_mgr;
|
||||
GetAppMgr_t func_get_app_mgr;
|
||||
SendText_t func_send_text;
|
||||
SendImage_t func_send_image;
|
||||
SendFile_t func_send_file;
|
||||
SendRichText_t func_send_rich_text;
|
||||
SendPat_t func_send_pat;
|
||||
Forward_t func_forward;
|
||||
GetEmotionMgr_t func_get_emotion_mgr;
|
||||
SendEmotion_t func_send_emotion;
|
||||
XmlBufSign_t func_xml_buf_sign;
|
||||
SendXml_t func_send_xml;
|
||||
|
||||
std::unique_ptr<WxString> new_wx_string(const char *str);
|
||||
std::unique_ptr<WxString> new_wx_string(const std::string &str);
|
||||
std::vector<WxString> parse_wxids(const std::string &wxids);
|
||||
};
|
||||
}
|
415
WeChatFerry/spy/misc_manager.cpp
Normal file
415
WeChatFerry/spy/misc_manager.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
#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 "rpc_helper.h"
|
||||
#include "spy_types.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
extern QWORD g_WeChatWinDllAddr;
|
||||
|
||||
namespace misc
|
||||
{
|
||||
#define HEADER_PNG1 0x89
|
||||
#define HEADER_PNG2 0x50
|
||||
#define HEADER_JPG1 0xFF
|
||||
#define HEADER_JPG2 0xD8
|
||||
#define HEADER_GIF1 0x47
|
||||
#define HEADER_GIF2 0x49
|
||||
|
||||
#define OS_LOGIN_STATUS 0x595C9E8
|
||||
#define OS_GET_SNS_DATA_MGR 0x21E2200
|
||||
#define OS_GET_SNS_FIRST_PAGE 0x2E212D0
|
||||
#define OS_GET_SNS_TIMELINE_MGR 0x2DB3390
|
||||
#define OS_GET_SNS_NEXT_PAGE 0x2EC8970
|
||||
#define OS_NEW_CHAT_MSG 0x1B5E140
|
||||
#define OS_FREE_CHAT_MSG 0x1B55850
|
||||
#define OS_GET_CHAT_MGR 0x1B876C0
|
||||
#define OS_GET_MGR_BY_PREFIX_LOCAL_ID 0x213FB00
|
||||
#define OS_GET_PRE_DOWNLOAD_MGR 0x1C0EE70
|
||||
#define OS_PUSH_ATTACH_TASK 0x1CDF4E0
|
||||
#define OS_LOGIN_QR_CODE 0x59620D8
|
||||
|
||||
using get_sns_data_mgr_t = QWORD (*)();
|
||||
using get_sns_timeline_mgr_t = QWORD (*)();
|
||||
using get_sns_first_page_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||
using get_sns_next_page_scene_t = QWORD (*)(QWORD, QWORD);
|
||||
using get_chat_mgr_t = QWORD (*)();
|
||||
using new_chat_msg_t = QWORD (*)(QWORD);
|
||||
using free_chat_msg_t = QWORD (*)(QWORD);
|
||||
using get_pre_download_mgr_t = QWORD (*)();
|
||||
using get_mgr_by_prefix_localid_t = QWORD (*)(QWORD, QWORD);
|
||||
using push_attach_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD);
|
||||
using get_ocr_manager_t = QWORD (*)();
|
||||
using do_ocr_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||
|
||||
bool is_logged_in() { return util::get_qword(g_WeChatWinDllAddr + OS_LOGIN_STATUS) != 0; }
|
||||
|
||||
static std::string detect_image_extension(uint8_t header1, uint8_t header2, uint8_t &key)
|
||||
{
|
||||
if ((key = HEADER_PNG1 ^ header1) && (HEADER_PNG2 ^ key) == header2) return ".png";
|
||||
if ((key = HEADER_JPG1 ^ header1) && (HEADER_JPG2 ^ key) == header2) return ".jpg";
|
||||
if ((key = HEADER_GIF1 ^ header1) && (HEADER_GIF2 ^ key) == header2) return ".gif";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string decrypt_image(const fs::path &src, const fs::path &dst_dir)
|
||||
{
|
||||
if (!fs::exists(src)) {
|
||||
LOG_ERROR("文件不存在: {}", src.string());
|
||||
return "";
|
||||
}
|
||||
|
||||
std::ifstream in(src, std::ios::binary);
|
||||
if (!in) {
|
||||
LOG_ERROR("无法打开文件: {}", src.string());
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<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.string());
|
||||
return "";
|
||||
}
|
||||
|
||||
out.write(buffer.data(), buffer.size());
|
||||
return dst_path.string();
|
||||
}
|
||||
|
||||
static int get_first_page()
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
get_sns_data_mgr_t GetSNSDataMgr = (get_sns_data_mgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_DATA_MGR);
|
||||
get_sns_first_page_t GetSNSFirstPage = (get_sns_first_page_t)(g_WeChatWinDllAddr + OS_GET_SNS_FIRST_PAGE);
|
||||
|
||||
QWORD buff[16] = { 0 };
|
||||
QWORD mgr = GetSNSDataMgr();
|
||||
status = (int)GetSNSFirstPage(mgr, (QWORD)buff, 1);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int get_next_page(QWORD id)
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
get_sns_timeline_mgr_t GetSnsTimeLineMgr = (get_sns_timeline_mgr_t)(g_WeChatWinDllAddr + OS_GET_SNS_TIMELINE_MGR);
|
||||
get_sns_next_page_scene_t GetSNSNextPageScene
|
||||
= (get_sns_next_page_scene_t)(g_WeChatWinDllAddr + OS_GET_SNS_NEXT_PAGE);
|
||||
|
||||
QWORD mgr = GetSnsTimeLineMgr();
|
||||
status = (int)GetSNSNextPageScene(mgr, id);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int refresh_pyq(uint64_t id)
|
||||
{
|
||||
auto &msgHandler = message::Handler::getInstance();
|
||||
if (!msgHandler.isPyqListening()) {
|
||||
LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
return get_first_page();
|
||||
}
|
||||
|
||||
return get_next_page(id);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* 都说我不写注释,写一下吧
|
||||
* 其实也没啥好写的,就是下载资源
|
||||
* 主要介绍一下几个参数:
|
||||
* id:好理解,消息 id
|
||||
* thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了
|
||||
* extra:图片、文件的路径
|
||||
*******************************************************************************/
|
||||
int download_attachment(uint64_t id, const fs::path &thumb, const fs::path &extra)
|
||||
{
|
||||
int status = -1;
|
||||
QWORD localId;
|
||||
uint32_t dbIdx;
|
||||
|
||||
if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) {
|
||||
LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id));
|
||||
return status;
|
||||
}
|
||||
|
||||
new_chat_msg_t NewChatMsg = (new_chat_msg_t)(g_WeChatWinDllAddr + OS_NEW_CHAT_MSG);
|
||||
free_chat_msg_t FreeChatMsg = (free_chat_msg_t)(g_WeChatWinDllAddr + OS_FREE_CHAT_MSG);
|
||||
get_chat_mgr_t GetChatMgr = (get_chat_mgr_t)(g_WeChatWinDllAddr + OS_GET_CHAT_MGR);
|
||||
get_pre_download_mgr_t GetPreDownLoadMgr = (get_pre_download_mgr_t)(g_WeChatWinDllAddr + OS_GET_PRE_DOWNLOAD_MGR);
|
||||
push_attach_task_t PushAttachTask = (push_attach_task_t)(g_WeChatWinDllAddr + OS_PUSH_ATTACH_TASK);
|
||||
get_mgr_by_prefix_localid_t GetMgrByPrefixLocalId
|
||||
= (get_mgr_by_prefix_localid_t)(g_WeChatWinDllAddr + OS_GET_MGR_BY_PREFIX_LOCAL_ID);
|
||||
|
||||
LARGE_INTEGER l;
|
||||
l.HighPart = dbIdx;
|
||||
l.LowPart = (DWORD)localId;
|
||||
|
||||
char *buff = (char *)HeapAlloc(GetProcessHeap(), 0, 0x460);
|
||||
if (buff == nullptr) {
|
||||
LOG_ERROR("申请内存失败.");
|
||||
return status;
|
||||
}
|
||||
|
||||
QWORD pChatMsg = NewChatMsg((QWORD)buff);
|
||||
GetChatMgr();
|
||||
GetMgrByPrefixLocalId(l.QuadPart, pChatMsg);
|
||||
|
||||
QWORD type = util::get_qword(reinterpret_cast<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").string();
|
||||
break;
|
||||
}
|
||||
case 0x31: { // File: extra
|
||||
save_path = extra;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR("不支持的文件类型: {}", type);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_DEBUG("保存路径: {}", save_path.string());
|
||||
// 创建父目录,由于路径来源于微信,不做检查
|
||||
fs::create_directory(save_path.parent_path());
|
||||
|
||||
int temp = 1;
|
||||
auto wx_save_path = util::new_wx_string(save_path.string());
|
||||
auto wx_thumb_path = util::new_wx_string(thumb_path.string());
|
||||
|
||||
memcpy(&buff[0x280], wx_thumb_path.get(), sizeof(WxString));
|
||||
memcpy(&buff[0x2A0], wx_save_path.get(), sizeof(WxString));
|
||||
memcpy(&buff[0x40C], &temp, sizeof(temp));
|
||||
|
||||
QWORD mgr = GetPreDownLoadMgr();
|
||||
status = (int)PushAttachTask(mgr, pChatMsg, 0, 1);
|
||||
FreeChatMsg(pChatMsg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
std::string get_audio(uint64_t id, const fs::path &dir)
|
||||
{
|
||||
if (!fs::exists(dir)) fs::create_directories(dir);
|
||||
|
||||
fs::path mp3path = dir / (std::to_string(id) + ".mp3");
|
||||
if (fs::exists(mp3path)) return mp3path.string();
|
||||
|
||||
auto silk = db::get_audio_data(id);
|
||||
if (silk.empty()) {
|
||||
LOG_ERROR("没有获取到语音数据.");
|
||||
return "";
|
||||
}
|
||||
|
||||
Silk2Mp3(silk, mp3path.string(), 24000);
|
||||
return mp3path.string();
|
||||
}
|
||||
|
||||
std::string get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr)
|
||||
{
|
||||
if (!fs::exists(dir)) fs::create_directories(dir);
|
||||
|
||||
fs::path pcmpath = dir / (std::to_string(id) + ".pcm");
|
||||
if (fs::exists(pcmpath)) return pcmpath.string();
|
||||
|
||||
auto silk = db::get_audio_data(id);
|
||||
if (silk.empty()) {
|
||||
LOG_ERROR("没有获取到语音数据.");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<uint8_t> pcm;
|
||||
SilkDecode(silk, pcm, sr);
|
||||
|
||||
std::ofstream out(pcmpath, std::ios::binary);
|
||||
if (!out) {
|
||||
LOG_ERROR("创建文件失败: {}", pcmpath.string());
|
||||
return "";
|
||||
}
|
||||
|
||||
out.write(reinterpret_cast<char *>(pcm.data()), pcm.size());
|
||||
return pcmpath.string();
|
||||
}
|
||||
|
||||
OcrResult_t get_ocr_result(const std::filesystem::path &path)
|
||||
{
|
||||
OcrResult_t ret = { -1, "" };
|
||||
#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复
|
||||
if (!fs::exists(path)) {
|
||||
LOG_ERROR("Can not find: {}", path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
get_ocr_manager_t GetOCRManager = (get_ocr_manager_t)(g_WeChatWinDllAddr + 0x1D6C3C0);
|
||||
do_ocr_task_t DoOCRTask = (do_ocr_task_t)(g_WeChatWinDllAddr + 0x2D10BC0);
|
||||
|
||||
QWORD unk1 = 0, unk2 = 0, unused = 0;
|
||||
QWORD *pUnk1 = &unk1;
|
||||
QWORD *pUnk2 = &unk2;
|
||||
// 路径分隔符有要求,必须为 `\`
|
||||
wstring wsPath = util::s2w(fs::path(path).make_preferred().string());
|
||||
WxString wxPath(wsPath);
|
||||
vector<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()
|
||||
{
|
||||
LPVOID targetAddress = reinterpret_cast<LPBYTE>(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE;
|
||||
char *dataPtr = *reinterpret_cast<char **>(targetAddress);
|
||||
if (!dataPtr) {
|
||||
LOG_ERROR("获取二维码失败.");
|
||||
return "";
|
||||
}
|
||||
return "http://weixin.qq.com/x/" + std::string(dataPtr, 22);
|
||||
}
|
||||
|
||||
int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid)
|
||||
{
|
||||
// 别想了,这个不实现了
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool rpc_is_logged_in(uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_IS_LOGIN>(out, len, [](Response &rsp) { rsp.msg.status = is_logged_in(); });
|
||||
}
|
||||
|
||||
bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<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 std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<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)
|
||||
{
|
||||
return fill_response<Functions_FUNC_DOWNLOAD_ATTACH>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = download_attachment(att.id, att.thumb, att.extra); });
|
||||
}
|
||||
|
||||
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_REVOKE_MSG>(out, len,
|
||||
[&](Response &rsp) { rsp.msg.status = revoke_message(id); });
|
||||
}
|
||||
|
||||
bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_EXEC_OCR>(out, len, [&](Response &rsp) {
|
||||
OcrResult_t ret = { -1, "" };
|
||||
ret = get_ocr_result(path);
|
||||
rsp.msg.ocr.status = ret.status;
|
||||
rsp.msg.ocr.result = (char *)ret.result.c_str();
|
||||
});
|
||||
}
|
||||
|
||||
bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<Functions_FUNC_RECV_TRANSFER>(
|
||||
out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); });
|
||||
}
|
||||
} // namespace misc
|
43
WeChatFerry/spy/misc_manager.h
Normal file
43
WeChatFerry/spy/misc_manager.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wcf.pb.h"
|
||||
|
||||
#include "pb_types.h"
|
||||
|
||||
namespace misc
|
||||
{
|
||||
|
||||
bool is_logged_in();
|
||||
|
||||
std::string get_audio(uint64_t id, const std::filesystem::path &dir);
|
||||
std::string get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr);
|
||||
std::string decrypt_image(const std::filesystem::path &src, const std::filesystem::path &dst);
|
||||
std::string get_login_url();
|
||||
|
||||
int refresh_pyq(uint64_t id);
|
||||
int download_attachment(uint64_t id, const std::filesystem::path &thumb, const std::filesystem::path &extra);
|
||||
int revoke_message(uint64_t id);
|
||||
|
||||
OcrResult_t get_ocr_result(const std::filesystem::path &path);
|
||||
int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid);
|
||||
|
||||
// RPC
|
||||
// clang-format off
|
||||
bool rpc_is_logged_in(uint8_t *out, size_t *len);
|
||||
bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len);
|
||||
bool rpc_get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len);
|
||||
bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len);
|
||||
bool rpc_get_login_url(uint8_t *out, size_t *len);
|
||||
bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len);
|
||||
bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len);
|
||||
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len);
|
||||
bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len);
|
||||
bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len);
|
||||
// clang-format on
|
||||
} // namespace misc
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
67
WeChatFerry/spy/rpc_helper.h
Normal file
67
WeChatFerry/spy/rpc_helper.h
Normal file
@ -0,0 +1,67 @@
|
||||
#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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
@ -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);
|
@ -1,45 +1,39 @@
|
||||
#include <filesystem>
|
||||
#include "spy.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "rpc_server.h"
|
||||
#include "spy.h"
|
||||
#include "util.h"
|
||||
|
||||
constexpr std::string_view SUPPORT_VERSION = "3.9.11.25";
|
||||
|
||||
UINT64 g_WeChatWinDllAddr = 0;
|
||||
|
||||
static bool IsWxVersionMatched(const wchar_t *version)
|
||||
int InitSpy(LPVOID args)
|
||||
{
|
||||
if (wcscmp(version, SUPPORT_VERSION) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitSpy(LPVOID args)
|
||||
{
|
||||
|
||||
wchar_t version[16] = { 0 };
|
||||
PortPath_t *pp = (PortPath_t *)args;
|
||||
auto *pp = static_cast<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")) {
|
||||
g_WeChatWinDllAddr = reinterpret_cast<UINT64>(dll_addr);
|
||||
} else {
|
||||
LOG_ERROR("获取 WeChatWin.dll 模块地址失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!GetWeChatVersion(version)) { // 获取微信版本
|
||||
LOG_ERROR("获取微信版本失败");
|
||||
return;
|
||||
}
|
||||
LOG_INFO("WeChat version: {}", Wstring2String(version).c_str());
|
||||
if (!IsWxVersionMatched(version)) {
|
||||
LOG_ERROR("不支持当前版本");
|
||||
MessageBox(NULL, L"不支持当前版本", L"错误", 0);
|
||||
return;
|
||||
std::string version = util::get_wechat_version();
|
||||
std::string msg = fmt::format("WCF 支持版本: {},当前版本: {}", SUPPORT_VERSION, version);
|
||||
if (version != SUPPORT_VERSION) {
|
||||
LOG_ERROR(msg);
|
||||
MessageBoxA(NULL, msg.c_str(), "微信版本错误", MB_ICONERROR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
LOG_INFO(msg);
|
||||
RpcStartServer(pp->port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CleanupSpy()
|
||||
|
@ -2,7 +2,5 @@
|
||||
|
||||
#include "framework.h"
|
||||
|
||||
#define SUPPORT_VERSION L"3.9.11.25"
|
||||
|
||||
void InitSpy(int port);
|
||||
int InitSpy(int port);
|
||||
void CleanupSpy();
|
||||
|
@ -5,29 +5,42 @@
|
||||
|
||||
typedef uint64_t QWORD;
|
||||
|
||||
struct WxString {
|
||||
class WxString
|
||||
{
|
||||
|
||||
public:
|
||||
const wchar_t *wptr;
|
||||
DWORD size;
|
||||
DWORD capacity;
|
||||
const char *ptr;
|
||||
DWORD clen;
|
||||
|
||||
WxString()
|
||||
{
|
||||
wptr = NULL;
|
||||
wptr = nullptr;
|
||||
size = 0;
|
||||
capacity = 0;
|
||||
ptr = NULL;
|
||||
ptr = nullptr;
|
||||
clen = 0;
|
||||
}
|
||||
|
||||
WxString(std::wstring &ws)
|
||||
WxString(std::wstring ws) : internal_ws(std::move(ws))
|
||||
{
|
||||
wptr = ws.c_str();
|
||||
size = (DWORD)ws.size();
|
||||
capacity = (DWORD)ws.capacity();
|
||||
ptr = NULL;
|
||||
wptr = internal_ws.c_str();
|
||||
size = static_cast<DWORD>(internal_ws.size());
|
||||
capacity = static_cast<DWORD>(internal_ws.capacity());
|
||||
ptr = nullptr;
|
||||
clen = 0;
|
||||
}
|
||||
|
||||
WxString(const WxString &) = delete;
|
||||
WxString &operator=(const WxString &) = delete;
|
||||
|
||||
WxString(WxString &&) noexcept = default;
|
||||
WxString &operator=(WxString &&) noexcept = default;
|
||||
|
||||
private:
|
||||
std::wstring internal_ws;
|
||||
};
|
||||
|
||||
typedef struct RawVector {
|
||||
|
@ -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;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "pb_types.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
string GetHomePath();
|
||||
string GetSelfWxid();
|
||||
|
||||
UserInfo_t GetUserInfo();
|
88
WeChatFerry/spy/userinfo_manager.cpp
Normal file
88
WeChatFerry/spy/userinfo_manager.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "userinfo_manager.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "rpc_helper.h"
|
||||
#include "util.h"
|
||||
|
||||
extern UINT64 g_WeChatWinDllAddr;
|
||||
|
||||
namespace userinfo
|
||||
{
|
||||
#define OS_USER_HOME 0x5932770
|
||||
#define OS_USER_WXID 0x595C270
|
||||
#define OS_USER_NAME 0x595C3D8
|
||||
#define OS_USER_MOBILE 0x595C318
|
||||
|
||||
std::string get_home_path()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
static std::string home_path;
|
||||
|
||||
std::call_once(flag, [] {
|
||||
std::string path = util::w2s(util::get_pp_wstring(g_WeChatWinDllAddr + OS_USER_HOME)) + "\\WeChat Files\\";
|
||||
home_path = std::filesystem::absolute(path).string();
|
||||
});
|
||||
|
||||
return home_path;
|
||||
}
|
||||
|
||||
std::string get_self_wxid()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
static std::string wxid;
|
||||
|
||||
std::call_once(flag, [] {
|
||||
UINT64 wxid_type = 0;
|
||||
try {
|
||||
wxid_type = util::get_qword(g_WeChatWinDllAddr + OS_USER_WXID + 0x18);
|
||||
if (wxid_type == 0xF) {
|
||||
wxid = util::get_p_string(g_WeChatWinDllAddr + OS_USER_WXID);
|
||||
} else {
|
||||
wxid = util::get_pp_string(g_WeChatWinDllAddr + OS_USER_WXID);
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
LOG_ERROR("Failed to get wxid, type: {:#x}", wxid_type);
|
||||
LOG_BUFFER(reinterpret_cast<uint8_t *>(g_WeChatWinDllAddr + OS_USER_WXID), 20);
|
||||
wxid = "获取wxid失败";
|
||||
}
|
||||
});
|
||||
return wxid;
|
||||
}
|
||||
|
||||
UserInfo_t get_user_info()
|
||||
{
|
||||
UserInfo_t ui;
|
||||
ui.wxid = get_self_wxid();
|
||||
|
||||
UINT64 name_type = util::get_qword(g_WeChatWinDllAddr + OS_USER_NAME + 0x18);
|
||||
ui.name = (name_type == 0xF) ? util::get_p_string(g_WeChatWinDllAddr + OS_USER_NAME)
|
||||
: util::get_pp_string(g_WeChatWinDllAddr + OS_USER_NAME);
|
||||
|
||||
ui.mobile = util::get_p_string(g_WeChatWinDllAddr + OS_USER_MOBILE);
|
||||
ui.home = get_home_path();
|
||||
|
||||
return ui;
|
||||
}
|
||||
|
||||
bool rpc_get_self_wxid(uint8_t *out, size_t *len)
|
||||
{
|
||||
return fill_response<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)
|
||||
{
|
||||
return fill_response<Functions_FUNC_GET_USER_INFO>(out, len, [](Response &rsp) {
|
||||
UserInfo_t ui = get_user_info();
|
||||
rsp.msg.ui.wxid = (char *)ui.wxid.c_str();
|
||||
rsp.msg.ui.name = (char *)ui.name.c_str();
|
||||
rsp.msg.ui.mobile = (char *)ui.mobile.c_str();
|
||||
rsp.msg.ui.home = (char *)ui.home.c_str();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace userinfo
|
24
WeChatFerry/spy/userinfo_manager.h
Normal file
24
WeChatFerry/spy/userinfo_manager.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "pb_types.h"
|
||||
|
||||
namespace userinfo
|
||||
{
|
||||
|
||||
// 获取 WeChat 数据存储路径
|
||||
std::string get_home_path();
|
||||
|
||||
// 获取自身 wxid
|
||||
std::string get_self_wxid();
|
||||
|
||||
// 获取用户信息
|
||||
UserInfo_t get_user_info();
|
||||
|
||||
// RPC 方法
|
||||
bool rpc_get_self_wxid(uint8_t *out, size_t *len);
|
||||
bool rpc_get_user_info(uint8_t *out, size_t *len);
|
||||
|
||||
} // namespace userinfo
|
Loading…
Reference in New Issue
Block a user