Merge branch 'lich0821:master' into master
This commit is contained in:
commit
777eb323f2
2
.github/workflows/Build-WeChatFerry.yml
vendored
2
.github/workflows/Build-WeChatFerry.yml
vendored
@ -92,7 +92,7 @@ jobs:
|
|||||||
- name: 打包输出文件及下载 WeChat 安装包
|
- name: 打包输出文件及下载 WeChat 安装包
|
||||||
run: |
|
run: |
|
||||||
New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp"
|
New-Item -ItemType Directory -Force -Path "WeChatFerry/tmp"
|
||||||
Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
|
Compress-Archive -Path "WeChatFerry/Out/sdk.dll", "WeChatFerry/Out/spy.dll", "WeChatFerry/Out/spy_debug.dll", "WeChatFerry/Out/DISCLAIMER.md" -DestinationPath "WeChatFerry/tmp/v${{ env.version }}.zip"
|
||||||
Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe"
|
Invoke-WebRequest -Uri "https://github.com/tom-snow/wechat-windows-versions/releases/download/v${{ env.wechat_version }}/WeChatSetup-${{ env.wechat_version }}.exe" -OutFile "WeChatFerry/tmp/WeChatSetup-${{ env.wechat_version }}.exe"
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
|
17
README.MD
17
README.MD
@ -5,7 +5,7 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><b>⚠️ 免责声明【必读】⚠️</b></summary>
|
<summary><b>⚠️ 免责声明【必读】⚠️</b></summary>
|
||||||
|
|
||||||
请阅读完整的免责声明:[点击查看](DISCLAIMER.md)
|
请阅读完整的免责声明:[点击查看](WeChatFerry/DISCLAIMER.md)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -32,7 +32,6 @@
|
|||||||
* 发送图片消息
|
* 发送图片消息
|
||||||
* 发送文件消息
|
* 发送文件消息
|
||||||
* 发送卡片消息
|
* 发送卡片消息
|
||||||
* 发送 XML 消息
|
|
||||||
* 发送 GIF 消息
|
* 发送 GIF 消息
|
||||||
* 拍一拍群友
|
* 拍一拍群友
|
||||||
* 转发消息
|
* 转发消息
|
||||||
@ -168,7 +167,7 @@ sdk.WxDestroySDK()
|
|||||||
|
|
||||||
### 调试日志
|
### 调试日志
|
||||||
```c
|
```c
|
||||||
DbgMsg("ListenMessage"); // 封装的 OutputDebugString
|
util::dbg_msg("ListenMessage"); // 封装的 OutputDebugString
|
||||||
OutputDebugString(L"ListenMessage\n");
|
OutputDebugString(L"ListenMessage\n");
|
||||||
MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0);
|
MessageBox(NULL, L"ListenMessage", L"ListenMessage", 0);
|
||||||
```
|
```
|
||||||
@ -205,9 +204,9 @@ WeChatFerry
|
|||||||
|
|
||||||
## 版本更新
|
## 版本更新
|
||||||
|
|
||||||
### v39.3.5
|
### v39.4.1
|
||||||
|
|
||||||
* 代码优化
|
* 修复乱码问题。
|
||||||
|
|
||||||
<details><summary>点击查看更多</summary>
|
<details><summary>点击查看更多</summary>
|
||||||
|
|
||||||
@ -219,6 +218,14 @@ WeChatFerry
|
|||||||
* `y` 是 `WeChatFerry` 的版本,从 0 开始
|
* `y` 是 `WeChatFerry` 的版本,从 0 开始
|
||||||
* `z` 是各客户端的版本,从 0 开始
|
* `z` 是各客户端的版本,从 0 开始
|
||||||
|
|
||||||
|
### v39.4.0
|
||||||
|
|
||||||
|
* 重构代码,适配 `3.9.12.17`。
|
||||||
|
|
||||||
|
### v39.3.5
|
||||||
|
|
||||||
|
* 代码优化
|
||||||
|
|
||||||
### v39.3.4
|
### v39.3.4
|
||||||
|
|
||||||
* 实现获取登录二维码
|
* 实现获取登录二维码
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef ENABLE_DEBUG_LOG
|
||||||
|
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <spdlog/sinks/rotating_file_sink.h>
|
|
||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#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_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)
|
||||||
#define LOG_INFO(...) SPDLOG_INFO(__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,
|
logger = spdlog::rotating_logger_mt(DEFAULT_LOGGER_NAME, filename.string(), DEFAULT_LOGGER_MAX_SIZE,
|
||||||
DEFAULT_LOGGER_MAX_FILES);
|
DEFAULT_LOGGER_MAX_FILES);
|
||||||
} catch (const spdlog::spdlog_ex &ex) {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,11 +62,10 @@ inline void InitLogger(const std::string &path)
|
|||||||
spdlog::set_level(spdlog::level::debug);
|
spdlog::set_level(spdlog::level::debug);
|
||||||
logger->flush_on(spdlog::level::debug);
|
logger->flush_on(spdlog::level::debug);
|
||||||
#else
|
#else
|
||||||
spdlog::set_level(spdlog::level::info);
|
|
||||||
logger->flush_on(spdlog::level::info);
|
logger->flush_on(spdlog::level::info);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SPDLOG_DEBUG("Logger initialized with default settings.");
|
LOG_DEBUG("InitLogger with debug level");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_DEBUG_LOG
|
#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
|
#endif
|
||||||
|
|
||||||
|
@ -1,177 +1,72 @@
|
|||||||
#include "Shlwapi.h"
|
#include "util.h"
|
||||||
#include "framework.h"
|
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
#include <filesystem>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <string.h>
|
#include <optional>
|
||||||
#include <strsafe.h>
|
#include <strsafe.h>
|
||||||
#include <tlhelp32.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#include "framework.h"
|
||||||
|
#include <Shlwapi.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#pragma comment(lib, "shlwapi")
|
#pragma comment(lib, "shlwapi")
|
||||||
#pragma comment(lib, "Version.lib")
|
#pragma comment(lib, "Version.lib")
|
||||||
|
|
||||||
using namespace std;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
wstring String2Wstring(string s)
|
namespace util
|
||||||
{
|
{
|
||||||
if (s.empty())
|
|
||||||
return wstring();
|
constexpr char WECHATEXE[] = "WeChat.exe";
|
||||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), NULL, 0);
|
constexpr char WECHATWINDLL[] = "WeChatWin.dll";
|
||||||
wstring ws(size_needed, 0);
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, &s[0], (int)s.size(), &ws[0], size_needed);
|
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;
|
return ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Wstring2String(wstring ws)
|
std::string w2s(const std::wstring &ws)
|
||||||
{
|
{
|
||||||
if (ws.empty())
|
if (ws.empty()) return std::string();
|
||||||
return string();
|
int size_needed
|
||||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), NULL, 0, NULL, NULL);
|
= WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast<int>(ws.size()), nullptr, 0, nullptr, nullptr);
|
||||||
string s(size_needed, 0);
|
std::string s(size_needed, 0);
|
||||||
WideCharToMultiByte(CP_UTF8, 0, &ws[0], (int)ws.size(), &s[0], size_needed, NULL, NULL);
|
WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast<int>(ws.size()), &s[0], size_needed, nullptr, nullptr);
|
||||||
return s;
|
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);
|
int size_needed = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, nullptr, 0);
|
||||||
wstring ws(size_needed, 0);
|
std::wstring ws(size_needed, 0);
|
||||||
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed);
|
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, &ws[0], size_needed);
|
||||||
|
|
||||||
size_needed = WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, NULL, 0, NULL, NULL);
|
size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||||
string s(size_needed, 0);
|
std::string s(size_needed, 0);
|
||||||
WideCharToMultiByte(CP_UTF8, 0, &ws[0], -1, &s[0], size_needed, NULL, NULL);
|
WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, &s[0], size_needed, nullptr, nullptr);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int GetWeChatPath(wchar_t *path)
|
static DWORD get_wechat_pid()
|
||||||
{
|
{
|
||||||
int ret = -1;
|
DWORD pid = 0;
|
||||||
HKEY hKey = NULL;
|
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
// HKEY_CURRENT_USER\Software\Tencent\WeChat InstallPath = xx
|
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
|
||||||
if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, L"Software\\Tencent\\WeChat", &hKey)) {
|
|
||||||
ret = GetLastError();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) };
|
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
|
||||||
while (Process32Next(hSnapshot, &pe32)) {
|
while (Process32Next(hSnapshot, &pe32)) {
|
||||||
wstring strProcess = pe32.szExeFile;
|
if (pe32.szExeFile == s2w(WECHATEXE)) {
|
||||||
if (strProcess == WECHAREXE) {
|
|
||||||
pid = pe32.th32ProcessID;
|
pid = pe32.th32ProcessID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -180,168 +75,237 @@ DWORD GetWeChatPid()
|
|||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class WindowsArchiture { x32, x64 };
|
static std::optional<std::string> get_wechat_path()
|
||||||
static WindowsArchiture GetWindowsArchitecture()
|
|
||||||
{
|
{
|
||||||
#ifdef _WIN64
|
HKEY hKey;
|
||||||
return WindowsArchiture::x64;
|
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Tencent\\WeChat", 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
|
||||||
#else
|
LOG_ERROR("无法打开注册表项");
|
||||||
return WindowsArchiture::x32;
|
return std::nullopt;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
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;
|
auto wechat_path = get_wechat_path();
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
if (!wechat_path) {
|
||||||
if (!hProcess)
|
return std::nullopt;
|
||||||
return false;
|
}
|
||||||
BOOL result = IsWow64Process(hProcess, &isWow64);
|
|
||||||
CloseHandle(hProcess);
|
fs::path dll_path = *wechat_path;
|
||||||
if (!result)
|
dll_path = dll_path.parent_path();
|
||||||
return false;
|
|
||||||
if (isWow64)
|
fs::path wechat_dll_path = dll_path / WECHATWINDLL;
|
||||||
return false;
|
if (fs::exists(wechat_dll_path)) { // 尝试直接查找 WeChatWin.dll
|
||||||
else if (GetWindowsArchitecture() == WindowsArchiture::x32)
|
return wechat_dll_path.string();
|
||||||
return false;
|
}
|
||||||
else
|
|
||||||
return true;
|
// 微信从(大约)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 (!PathFileExistsA(path.c_str())) {
|
||||||
if (*pid) {
|
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;
|
return ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = -1;
|
auto wechat_path = util::get_wechat_path();
|
||||||
STARTUPINFO si = { sizeof(si) };
|
if (!wechat_path) {
|
||||||
WCHAR Path[MAX_PATH] = { 0 };
|
LOG_ERROR("获取 WeChat 安装路径失败");
|
||||||
PROCESS_INFORMATION pi = { 0 };
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
ret = GetWeChatPath(Path);
|
|
||||||
if (ERROR_SUCCESS != ret) {
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
return GetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
CloseHandle(pi.hProcess);
|
CloseHandle(pi.hProcess);
|
||||||
|
|
||||||
*pid = pi.dwProcessId;
|
pid = pi.dwProcessId;
|
||||||
|
|
||||||
return ERROR_SUCCESS;
|
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);
|
uint32_t value = 0;
|
||||||
if (strLength == 0) {
|
if (!addr || !hProcess) return value;
|
||||||
return 0;
|
|
||||||
} else if (strLength > buffer_size) {
|
|
||||||
strLength = buffer_size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
wmemcpy_s(buffer, strLength + 1, GET_WSTRING(addr), strLength + 1);
|
ReadProcessMemory(hProcess, reinterpret_cast<LPCVOID>(addr), &value, sizeof(value), nullptr);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
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_t str_address = get_memory_int_by_address(hProcess, address);
|
||||||
UINT64 strLen = GetMemoryIntByAddress(hProcess, address + 0x4);
|
uint64_t str_len = get_memory_int_by_address(hProcess, address + 0x4);
|
||||||
if (strLen > 500)
|
if (str_len > 500) return L"";
|
||||||
return value;
|
|
||||||
|
|
||||||
wchar_t cValue[500] = { 0 };
|
wchar_t cValue[500] = { 0 };
|
||||||
memset(cValue, 0, sizeof(cValue) / sizeof(wchar_t));
|
if (ReadProcessMemory(hProcess, reinterpret_cast<LPCVOID>(str_address), cValue, (str_len + 1) * sizeof(wchar_t),
|
||||||
if (ReadProcessMemory(hProcess, (LPVOID)strAddress, cValue, (strLen + 1) * 2, 0)) {
|
nullptr)) {
|
||||||
value = wstring(cValue);
|
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
|
if (!format) return;
|
||||||
va_list vaArgs;
|
|
||||||
va_start(vaArgs, zcFormat);
|
|
||||||
|
|
||||||
// reliably acquire the size
|
va_list args;
|
||||||
// from a copy of the variable argument array
|
va_start(args, format);
|
||||||
// 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);
|
|
||||||
|
|
||||||
// return a formatted string without risking memory mismanagement
|
va_list args_copy;
|
||||||
// and without assuming any compiler or platform specific behavior
|
va_copy(args_copy, args);
|
||||||
std::vector<char> zc(iLen + 1);
|
int len = vsnprintf(nullptr, 0, format, args_copy);
|
||||||
std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
|
va_end(args_copy);
|
||||||
va_end(vaArgs);
|
|
||||||
std::string strText(zc.data(), iLen);
|
|
||||||
|
|
||||||
OutputDebugStringA(strText.c_str());
|
std::vector<char> buffer(len + 1);
|
||||||
|
vsnprintf(buffer.data(), buffer.size(), format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
OutputDebugStringW(s2w(buffer.data()).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
WxString *NewWxStringFromStr(const string &str) { return NewWxStringFromWstr(String2Wstring(str)); }
|
std::unique_ptr<WxString> new_wx_string(const char *str)
|
||||||
|
|
||||||
WxString *NewWxStringFromWstr(const wstring &ws)
|
|
||||||
{
|
{
|
||||||
WxString *p = (WxString *)HeapAlloc(GetProcessHeap(), 0, sizeof(WxString));
|
return new_wx_string(str ? std::string(str) : std::string());
|
||||||
wchar_t *pWstring = (wchar_t *)HeapAlloc(GetProcessHeap(), 0, (ws.size() + 1) * 2);
|
}
|
||||||
if (p == NULL || pWstring == NULL) {
|
|
||||||
LOG_ERROR("Out of Memory...");
|
std::unique_ptr<WxString> new_wx_string(const std::string &str) { return std::make_unique<WxString>(s2w(str)); }
|
||||||
return NULL;
|
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const wchar_t *wstr)
|
||||||
|
{
|
||||||
|
return new_wx_string(wstr ? std::wstring(wstr) : std::wstring());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const std::wstring &wstr) { return std::make_unique<WxString>(wstr); }
|
||||||
|
|
||||||
|
AtWxidSplitResult<> parse_wxids(const std::string &atWxids)
|
||||||
|
{
|
||||||
|
AtWxidSplitResult<> result;
|
||||||
|
if (!atWxids.empty()) {
|
||||||
|
std::wstringstream wss(util::s2w(atWxids));
|
||||||
|
for (std::wstring wxid; std::getline(wss, wxid, L',');) {
|
||||||
|
result.wxids.push_back(wxid);
|
||||||
|
result.wxWxids.emplace_back(result.wxids.back());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WxString *CreateWxString(const std::string &s)
|
||||||
|
{
|
||||||
|
std::wstring ws = util::s2w(s);
|
||||||
|
WxString *wxStr = reinterpret_cast<WxString *>(HeapAlloc(GetProcessHeap(), 8, sizeof(WxString)));
|
||||||
|
if (!wxStr) return nullptr;
|
||||||
|
size_t len = ws.length();
|
||||||
|
wchar_t *ptr = reinterpret_cast<wchar_t *>(HeapAlloc(GetProcessHeap(), 8, (len + 1) * sizeof(wchar_t)));
|
||||||
|
if (!ptr) {
|
||||||
|
HeapFree(GetProcessHeap(), 8, wxStr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
wmemcpy(ptr, ws.c_str(), len + 1);
|
||||||
|
wxStr->wptr = ptr;
|
||||||
|
wxStr->size = static_cast<DWORD>(ws.size());
|
||||||
|
wxStr->length = static_cast<DWORD>(ws.length());
|
||||||
|
wxStr->clen = 0;
|
||||||
|
wxStr->ptr = nullptr;
|
||||||
|
return wxStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeWxString(WxString *wxStr)
|
||||||
|
{
|
||||||
|
if (wxStr) {
|
||||||
|
if (wxStr->wptr) HeapFree(GetProcessHeap(), 8, const_cast<wchar_t *>(wxStr->wptr));
|
||||||
|
HeapFree(GetProcessHeap(), 8, wxStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace util
|
||||||
|
@ -1,41 +1,105 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "spy_types.h"
|
#include "spy_types.h"
|
||||||
|
|
||||||
#define WECHAREXE L"WeChat.exe"
|
namespace util
|
||||||
#define WECHATWINDLL L"WeChatWin.dll"
|
{
|
||||||
#define WCFSDKDLL L"sdk.dll"
|
struct PortPath {
|
||||||
#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 {
|
|
||||||
int port;
|
int port;
|
||||||
char path[MAX_PATH];
|
char path[MAX_PATH];
|
||||||
} PortPath_t;
|
};
|
||||||
|
|
||||||
DWORD GetWeChatPid();
|
DWORD get_wechat_pid();
|
||||||
BOOL IsProcessX64(DWORD pid);
|
int open_wechat(DWORD &pid);
|
||||||
int OpenWeChat(DWORD *pid);
|
std::string get_wechat_version();
|
||||||
int GetWeChatVersion(wchar_t *version);
|
uint32_t get_memory_int_by_address(HANDLE hProcess, uint64_t addr);
|
||||||
size_t GetWstringByAddress(UINT64 address, wchar_t *buffer, UINT64 buffer_size);
|
std::wstring get_unicode_info_by_address(HANDLE hProcess, uint64_t addr);
|
||||||
UINT32 GetMemoryIntByAddress(HANDLE hProcess, UINT64 address);
|
std::wstring s2w(const std::string &s);
|
||||||
std::wstring GetUnicodeInfoByAddress(HANDLE hProcess, UINT64 address);
|
std::string w2s(const std::wstring &ws);
|
||||||
std::wstring String2Wstring(std::string s);
|
std::string gb2312_to_utf8(const char *gb2312);
|
||||||
std::string Wstring2String(std::wstring ws);
|
void dbg_msg(const char *format, ...);
|
||||||
std::string GB2312ToUtf8(const char *gb2312);
|
|
||||||
std::string GetStringByAddress(UINT64 address);
|
inline DWORD get_dword(uint64_t addr) { return addr ? *reinterpret_cast<DWORD *>(addr) : 0; }
|
||||||
std::string GetStringByStrAddr(UINT64 addr);
|
inline QWORD get_qword(uint64_t addr) { return addr ? *reinterpret_cast<QWORD *>(addr) : 0; }
|
||||||
std::string GetStringByWstrAddr(UINT64 addr);
|
inline uint64_t get_uint64(uint64_t addr) { return addr ? *reinterpret_cast<uint64_t *>(addr) : 0; }
|
||||||
void DbgMsg(const char *zcFormat, ...);
|
inline std::string get_p_string(uint64_t addr) { return addr ? std::string(reinterpret_cast<const char *>(addr)) : ""; }
|
||||||
WxString *NewWxStringFromStr(const std::string &str);
|
inline std::string get_p_string(uint64_t addr, size_t len)
|
||||||
WxString *NewWxStringFromWstr(const std::wstring &ws);
|
{
|
||||||
|
return addr ? std::string(reinterpret_cast<const char *>(addr), len) : "";
|
||||||
|
}
|
||||||
|
inline std::wstring get_p_wstring(uint64_t addr)
|
||||||
|
{
|
||||||
|
return addr ? std::wstring(reinterpret_cast<const wchar_t *>(addr)) : L"";
|
||||||
|
}
|
||||||
|
inline std::wstring get_p_wstring(uint64_t addr, size_t len)
|
||||||
|
{
|
||||||
|
return addr ? std::wstring(reinterpret_cast<const wchar_t *>(addr), len) : L"";
|
||||||
|
}
|
||||||
|
inline std::string get_pp_string(uint64_t addr)
|
||||||
|
{
|
||||||
|
if (!addr) return "";
|
||||||
|
|
||||||
|
const char *ptr = *reinterpret_cast<const char **>(addr);
|
||||||
|
return (ptr && *ptr) ? std::string(ptr) : "";
|
||||||
|
}
|
||||||
|
inline std::wstring get_pp_wstring(uint64_t addr)
|
||||||
|
{
|
||||||
|
if (!addr) return L"";
|
||||||
|
|
||||||
|
const wchar_t *ptr = *reinterpret_cast<const wchar_t **>(addr);
|
||||||
|
return (ptr && *ptr) ? std::wstring(ptr) : L"";
|
||||||
|
}
|
||||||
|
inline std::string get_pp_len_string(uint64_t addr)
|
||||||
|
{
|
||||||
|
size_t len = get_dword(addr + 8);
|
||||||
|
return (addr && len) ? std::string(*reinterpret_cast<const char **>(addr), len) : "";
|
||||||
|
}
|
||||||
|
inline std::wstring get_pp_len_wstring(uint64_t addr)
|
||||||
|
{
|
||||||
|
size_t len = get_dword(addr + 8);
|
||||||
|
return (addr && len) ? std::wstring(*reinterpret_cast<const wchar_t **>(addr), len) : L"";
|
||||||
|
}
|
||||||
|
inline std::string get_str_by_wstr_addr(uint64_t addr) { return w2s(get_pp_len_wstring(addr)); }
|
||||||
|
inline void *AllocFromHeap(size_t size) { return HeapAlloc(GetProcessHeap(), 8, size); }
|
||||||
|
inline void FreeBuffer(void *buffer)
|
||||||
|
{
|
||||||
|
if (buffer) HeapFree(GetProcessHeap(), 8, buffer);
|
||||||
|
}
|
||||||
|
inline int MsgBox(HWND hWnd, const std::string &text, const std::string &caption = "WCF", UINT uType = MB_OK)
|
||||||
|
{
|
||||||
|
std::wstring wText = s2w(text);
|
||||||
|
std::wstring wCaption = s2w(caption);
|
||||||
|
return MessageBoxW(nullptr, wText.c_str(), wCaption.c_str(), uType);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> static T *AllocBuffer(size_t count)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<T *>(HeapAlloc(GetProcessHeap(), 8, sizeof(T) * count));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> struct WxStringHolder {
|
||||||
|
std::wstring ws;
|
||||||
|
WxString wx;
|
||||||
|
explicit WxStringHolder(const T &str) : ws(util::s2w(str)), wx(ws) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename StringT = std::wstring> struct AtWxidSplitResult {
|
||||||
|
std::vector<StringT> wxids;
|
||||||
|
std::vector<WxString> wxWxids;
|
||||||
|
};
|
||||||
|
|
||||||
|
WxString *CreateWxString(const std::string &s);
|
||||||
|
void FreeWxString(WxString *wxStr);
|
||||||
|
AtWxidSplitResult<> parse_wxids(const std::string &atWxids);
|
||||||
|
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const char *str);
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const wchar_t *wstr);
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const std::string &str);
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const std::wstring &wstr);
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
@ -68,7 +68,6 @@ bool encode_contacts(pb_ostream_t *stream, const pb_field_t *field, void *const
|
|||||||
vector<RpcContact_t> *v = (vector<RpcContact_t> *)*arg;
|
vector<RpcContact_t> *v = (vector<RpcContact_t> *)*arg;
|
||||||
RpcContact message = RpcContact_init_default;
|
RpcContact message = RpcContact_init_default;
|
||||||
|
|
||||||
LOG_DEBUG("encode_contacts[{}]", v->size());
|
|
||||||
for (auto it = v->begin(); it != v->end(); it++) {
|
for (auto it = v->begin(); it != v->end(); it++) {
|
||||||
message.wxid.funcs.encode = &encode_string;
|
message.wxid.funcs.encode = &encode_string;
|
||||||
message.wxid.arg = (void *)(*it).wxid.c_str();
|
message.wxid.arg = (void *)(*it).wxid.c_str();
|
||||||
|
@ -141,7 +141,6 @@ xcopy /y $(OutDir)$(TargetFileName) $(SolutionDir)..\clients\python\wcferry</Com
|
|||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\com\log.hpp" />
|
|
||||||
<ClInclude Include="..\com\util.h" />
|
<ClInclude Include="..\com\util.h" />
|
||||||
<ClInclude Include="framework.h" />
|
<ClInclude Include="framework.h" />
|
||||||
<ClInclude Include="injector.h" />
|
<ClInclude Include="injector.h" />
|
||||||
|
@ -27,9 +27,6 @@
|
|||||||
<ClInclude Include="..\com\util.h">
|
<ClInclude Include="..\com\util.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\com\log.hpp">
|
|
||||||
<Filter>头文件</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="dllmain.cpp">
|
<ClCompile Include="dllmain.cpp">
|
||||||
|
@ -1,112 +1,110 @@
|
|||||||
#include "framework.h"
|
#include "injector.h"
|
||||||
#include "psapi.h"
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
|
||||||
|
#include "psapi.h"
|
||||||
|
|
||||||
#include "injector.h"
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using namespace std;
|
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;
|
util::MsgBox(NULL, error_msg.c_str(), "Error", MB_ICONERROR);
|
||||||
HMODULE moduleHandleList[512];
|
if (remote_address) {
|
||||||
BOOL ret = EnumProcessModulesEx(process, moduleHandleList, sizeof(moduleHandleList), &cbNeeded, LIST_MODULES_64BIT);
|
VirtualFreeEx(process, remote_address, 0, MEM_RELEASE);
|
||||||
if (!ret) {
|
}
|
||||||
MessageBox(NULL, L"获取模块失败", L"GetTargetModuleBase", 0);
|
if (process) {
|
||||||
|
CloseHandle(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE get_target_module_base(HANDLE process, const string &dll)
|
||||||
|
{
|
||||||
|
DWORD needed;
|
||||||
|
HMODULE modules[512];
|
||||||
|
if (!EnumProcessModulesEx(process, modules, sizeof(modules), &needed, LIST_MODULES_64BIT)) {
|
||||||
|
util::MsgBox(NULL, "获取模块失败", "get_target_module_base", 0);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cbNeeded > sizeof(moduleHandleList)) {
|
DWORD count = needed / sizeof(HMODULE);
|
||||||
MessageBox(NULL, L"模块数量过多", L"GetTargetModuleBase", 0);
|
char module_name[MAX_PATH];
|
||||||
return NULL;
|
for (DWORD i = 0; i < count; i++) {
|
||||||
}
|
GetModuleBaseNameA(process, modules[i], module_name, sizeof(module_name));
|
||||||
DWORD processCount = cbNeeded / sizeof(HMODULE);
|
if (!strncmp(dll.c_str(), module_name, dll.size())) {
|
||||||
|
return modules[i];
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
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 path_size = dll_path.size() + 1;
|
||||||
SIZE_T cszDLL = (wcslen(dllPath) + 1) * sizeof(WCHAR);
|
|
||||||
|
|
||||||
// 1. 打开目标进程
|
// 1. 打开目标进程
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||||||
if (hProcess == NULL) {
|
if (!hProcess) {
|
||||||
MessageBox(NULL, L"打开进程失败", L"InjectDll", 0);
|
util::MsgBox(NULL, "打开进程失败", "inject_dll", 0);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 在目标进程的内存里开辟空间
|
// 2. 在目标进程的内存里开辟空间
|
||||||
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, cszDLL, MEM_COMMIT, PAGE_READWRITE);
|
LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, path_size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
if (pRemoteAddress == NULL) {
|
if (!pRemoteAddress) {
|
||||||
MessageBox(NULL, L"DLL 路径写入失败", L"InjectDll", 0);
|
handle_injection_error(hProcess, NULL, "DLL 路径写入失败");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 把 dll 的路径写入到目标进程的内存空间中
|
// 3. 把 dll 的路径写入到目标进程的内存空间中
|
||||||
WriteProcessMemory(hProcess, pRemoteAddress, dllPath, cszDLL, NULL);
|
WriteProcessMemory(hProcess, pRemoteAddress, dll_path.c_str(), path_size, NULL);
|
||||||
|
|
||||||
// 3. 创建一个远程线程,让目标进程调用 LoadLibrary
|
// 4. 创建一个远程线程,让目标进程调用 LoadLibrary
|
||||||
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
|
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||||
if (k32 == NULL) {
|
if (!k32) {
|
||||||
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
|
handle_injection_error(hProcess, pRemoteAddress, "获取 kernel32 失败");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryW");
|
FARPROC libAddr = GetProcAddress(k32, "LoadLibraryA");
|
||||||
if (!libAddr) {
|
if (!libAddr) {
|
||||||
MessageBox(NULL, L"获取 LoadLibrary 失败", L"InjectDll", 0);
|
handle_injection_error(hProcess, pRemoteAddress, "获取 LoadLibrary 失败");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
|
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, pRemoteAddress, 0, NULL);
|
||||||
if (hThread == NULL) {
|
if (!hThread) {
|
||||||
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
|
handle_injection_error(hProcess, pRemoteAddress, "CreateRemoteThread 失败");
|
||||||
CloseHandle(hProcess);
|
|
||||||
MessageBox(NULL, L"CreateRemoteThread 失败", L"InjectDll", 0);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitForSingleObject(hThread, -1);
|
WaitForSingleObject(hThread, INFINITE);
|
||||||
CloseHandle(hThread);
|
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);
|
VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
|
||||||
// CloseHandle(hProcess); // Close when exit
|
|
||||||
|
|
||||||
return hProcess;
|
return hProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EjectDll(HANDLE process, HMODULE dllBase)
|
bool eject_dll(HANDLE process, HMODULE dll_base)
|
||||||
{
|
{
|
||||||
HANDLE hThread = NULL;
|
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||||
|
if (!k32) {
|
||||||
// 使目标进程调用 FreeLibrary,卸载 DLL
|
util::MsgBox(NULL, "获取 kernel32 失败", "eject_dll", 0);
|
||||||
HMODULE k32 = GetModuleHandle(L"kernel32.dll");
|
return false;
|
||||||
if (k32 == NULL) {
|
|
||||||
MessageBox(NULL, L"获取 kernel32 失败", L"InjectDll", 0);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread");
|
FARPROC libAddr = GetProcAddress(k32, "FreeLibraryAndExitThread");
|
||||||
if (!libAddr) {
|
if (!libAddr) {
|
||||||
MessageBox(NULL, L"获取 FreeLibrary 失败", L"InjectDll", 0);
|
util::MsgBox(NULL, "获取 FreeLibrary 失败", "eject_dll", 0);
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dllBase, 0, NULL);
|
|
||||||
if (hThread == NULL) {
|
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)libAddr, (LPVOID)dll_base, 0, NULL);
|
||||||
MessageBox(NULL, L"FreeLibrary 调用失败!", L"EjectDll", 0);
|
if (!hThread) {
|
||||||
|
util::MsgBox(NULL, "FreeLibrary 调用失败!", "eject_dll", 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,38 +114,34 @@ bool EjectDll(HANDLE process, HMODULE dllBase)
|
|||||||
return true;
|
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);
|
HMODULE dll = LoadLibraryA(dll_path.c_str());
|
||||||
if (dll == NULL) {
|
if (!dll) {
|
||||||
MessageBox(NULL, L"获取 DLL 失败", L"GetFuncOffset", 0);
|
util::MsgBox(NULL, "获取 DLL 失败", "get_func_offset", 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
LPVOID absAddr = GetProcAddress(dll, funcName);
|
LPVOID absAddr = GetProcAddress(dll, func_name.c_str());
|
||||||
UINT64 offset = (UINT64)absAddr - (UINT64)dll;
|
uint64_t offset = reinterpret_cast<uint64_t>(absAddr) - reinterpret_cast<uint64_t>(dll);
|
||||||
FreeLibrary(dll);
|
FreeLibrary(dll);
|
||||||
|
|
||||||
return offset;
|
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);
|
uint64_t offset = get_func_offset(dll_path, func_name);
|
||||||
if (offset == 0) {
|
if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast<uint64_t>(dll_base))) {
|
||||||
return false;
|
return false; // 避免溢出
|
||||||
}
|
}
|
||||||
UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
|
uint64_t pFunc = reinterpret_cast<uint64_t>(dll_base) + offset;
|
||||||
if (pFunc <= (UINT64)dllBase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL);
|
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, NULL, 0, NULL);
|
||||||
if (hThread == NULL) {
|
if (!hThread) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WaitForSingleObject(hThread, INFINITE);
|
WaitForSingleObject(hThread, INFINITE);
|
||||||
if (ret != NULL) {
|
if (ret) {
|
||||||
GetExitCodeThread(hThread, ret);
|
GetExitCodeThread(hThread, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,35 +149,32 @@ bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcNa
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
|
bool call_dll_func_ex(HANDLE process, const string &dll_path, HMODULE dll_base, const string &func_name,
|
||||||
LPDWORD ret)
|
LPVOID parameter, size_t size, DWORD *ret)
|
||||||
{
|
{
|
||||||
UINT64 offset = GetFuncOffset(dllPath, funcName);
|
uint64_t offset = get_func_offset(dll_path, func_name);
|
||||||
if (offset == 0) {
|
if (offset == 0 || offset > (UINT64_MAX - reinterpret_cast<uint64_t>(dll_base))) {
|
||||||
return false;
|
return false; // 避免溢出
|
||||||
}
|
}
|
||||||
UINT64 pFunc = (UINT64)dllBase + GetFuncOffset(dllPath, funcName);
|
uint64_t pFunc = reinterpret_cast<uint64_t>(dll_base) + offset;
|
||||||
if (pFunc <= (UINT64)dllBase) {
|
LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (!pRemoteAddress) {
|
||||||
|
util::MsgBox(NULL, "申请内存失败", "call_dll_func_ex", 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LPVOID pRemoteAddress = VirtualAllocEx(process, NULL, sz, MEM_COMMIT, PAGE_READWRITE);
|
WriteProcessMemory(process, pRemoteAddress, parameter, size, NULL);
|
||||||
if (pRemoteAddress == NULL) {
|
|
||||||
MessageBox(NULL, L"申请内存失败", L"CallDllFuncEx", 0);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteProcessMemory(process, pRemoteAddress, parameter, sz, NULL);
|
|
||||||
|
|
||||||
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL);
|
HANDLE hThread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, pRemoteAddress, 0, NULL);
|
||||||
if (hThread == NULL) {
|
if (!hThread) {
|
||||||
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
|
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
|
||||||
MessageBox(NULL, L"远程调用失败", L"CallDllFuncEx", 0);
|
util::MsgBox(NULL, "远程调用失败", "call_dll_func_ex", 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitForSingleObject(hThread, INFINITE);
|
WaitForSingleObject(hThread, INFINITE);
|
||||||
VirtualFree(pRemoteAddress, 0, MEM_RELEASE);
|
VirtualFreeEx(process, pRemoteAddress, 0, MEM_RELEASE);
|
||||||
if (ret != NULL) {
|
if (ret) {
|
||||||
GetExitCodeThread(hThread, ret);
|
GetExitCodeThread(hThread, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
|
|
||||||
HANDLE InjectDll(DWORD pid, LPCWSTR dllPath, HMODULE *injectedBase);
|
HANDLE inject_dll(DWORD pid, const std::string &dll_path, HMODULE *injected_base);
|
||||||
bool EjectDll(HANDLE process, HMODULE dllBase);
|
bool eject_dll(HANDLE process, HMODULE dll_base);
|
||||||
bool CallDllFunc(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, DWORD *ret);
|
bool call_dll_func(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func, DWORD *ret);
|
||||||
bool CallDllFuncEx(HANDLE process, LPCWSTR dllPath, HMODULE dllBase, LPCSTR funcName, LPVOID parameter, size_t sz,
|
bool call_dll_func_ex(HANDLE process, const std::string &dll_path, HMODULE dll_base, const std::string &func,
|
||||||
DWORD *ret);
|
LPVOID parameter, size_t size, DWORD *ret);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "framework.h"
|
#include "sdk.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -6,55 +7,67 @@
|
|||||||
#include <process.h>
|
#include <process.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "framework.h"
|
||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
|
|
||||||
#include "injector.h"
|
#include "injector.h"
|
||||||
#include "sdk.h"
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
static BOOL injected = false;
|
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||||
|
|
||||||
|
static bool injected = false;
|
||||||
static HANDLE wcProcess = NULL;
|
static HANDLE wcProcess = NULL;
|
||||||
static HMODULE spyBase = NULL;
|
static HMODULE spyBase = NULL;
|
||||||
static std::wstring spyDllPath;
|
static std::string spyDllPath;
|
||||||
|
|
||||||
constexpr char DISCLAIMER_FILE[] = ".license_accepted.flag";
|
constexpr char WCFSDKDLL[] = "sdk.dll";
|
||||||
constexpr char DISCLAIMER_TEXT_FILE[] = "DISCLAIMER.md";
|
constexpr char WCFSPYDLL[] = "spy.dll";
|
||||||
|
constexpr char WCFSPYDLL_DEBUG[] = "spy_debug.dll";
|
||||||
|
|
||||||
static std::optional<std::wstring> ReadDisclaimerText(const char *filePath)
|
constexpr std::string_view DISCLAIMER_FLAG = ".license_accepted.flag";
|
||||||
|
constexpr std::string_view DISCLAIMER_TEXT_FILE = "DISCLAIMER.md";
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
static fs::path get_module_directory()
|
||||||
{
|
{
|
||||||
std::ifstream file(filePath, std::ios::binary);
|
char buffer[MAX_PATH] = { 0 };
|
||||||
if (!file.is_open()) {
|
HMODULE hModule = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||||
return std::nullopt; // 文件打开失败
|
GetModuleFileNameA(hModule, buffer, MAX_PATH);
|
||||||
}
|
fs::path modulePath(buffer);
|
||||||
|
return modulePath.parent_path();
|
||||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
||||||
return String2Wstring(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ShowDisclaimer()
|
static bool show_disclaimer()
|
||||||
{
|
{
|
||||||
if (std::filesystem::exists(DISCLAIMER_FILE)) {
|
fs::path sdk_path = get_module_directory();
|
||||||
|
if (fs::exists(sdk_path / DISCLAIMER_FLAG)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::wstring> disclaimerTextOpt = ReadDisclaimerText(DISCLAIMER_TEXT_FILE);
|
fs::path path = sdk_path / DISCLAIMER_TEXT_FILE;
|
||||||
if (!disclaimerTextOpt.has_value() || disclaimerTextOpt->empty()) {
|
std::ifstream file(path, std::ios::binary);
|
||||||
MessageBox(NULL, L"免责声明文件为空或读取失败。", L"错误", MB_ICONERROR);
|
if (!file.is_open()) {
|
||||||
|
util::MsgBox(NULL, "免责声明文件读取失败。", "错误", MB_ICONERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring disclaimerText = *disclaimerTextOpt;
|
auto disclaimerText = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
if (disclaimerText.empty()) {
|
||||||
int result = MessageBox(NULL, disclaimerText.c_str(), L"免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
|
util::MsgBox(NULL, "免责声明文件为空", "错误", MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = util::MsgBox(NULL, disclaimerText.c_str(), "免责声明", MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2);
|
||||||
if (result == IDCANCEL) {
|
if (result == IDCANCEL) {
|
||||||
MessageBox(NULL, L"您拒绝了免责声明,程序将退出。", L"提示", MB_ICONINFORMATION);
|
util::MsgBox(NULL, "您拒绝了免责声明,程序将退出。", "提示", MB_ICONINFORMATION);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream flagFile(DISCLAIMER_FILE, std::ios::out | std::ios::trunc);
|
std::ofstream flagFile(sdk_path / DISCLAIMER_FLAG, std::ios::out | std::ios::trunc);
|
||||||
if (!flagFile) {
|
if (!flagFile) {
|
||||||
MessageBox(NULL, L"无法创建协议标志文件。", L"错误", MB_ICONERROR);
|
util::MsgBox(NULL, "无法创建协议标志文件。", "错误", MB_ICONERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
flagFile << "User accepted the license agreement.";
|
flagFile << "User accepted the license agreement.";
|
||||||
@ -62,82 +75,79 @@ static bool ShowDisclaimer()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::wstring GetDllPath(bool debug)
|
static std::string get_dll_path(bool debug)
|
||||||
{
|
{
|
||||||
WCHAR buffer[MAX_PATH] = { 0 };
|
char buffer[MAX_PATH] = { 0 };
|
||||||
GetModuleFileName(GetModuleHandle(WCFSDKDLL), buffer, MAX_PATH);
|
GetModuleFileNameA(GetModuleHandleA(WCFSDKDLL), buffer, MAX_PATH);
|
||||||
|
|
||||||
std::filesystem::path path(buffer);
|
|
||||||
path.remove_filename(); // 移除文件名,保留目录路径
|
|
||||||
|
|
||||||
|
fs::path path(buffer);
|
||||||
|
path.remove_filename(); // 只保留目录路径
|
||||||
path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL;
|
path /= debug ? WCFSPYDLL_DEBUG : WCFSPYDLL;
|
||||||
|
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!fs::exists(path)) {
|
||||||
MessageBox(NULL, path.c_str(), L"文件不存在", MB_ICONERROR);
|
util::MsgBox(NULL, path.string().c_str(), "文件不存在", MB_ICONERROR);
|
||||||
return L"";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.wstring();
|
return path.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
int WxInitSDK(bool debug, int port)
|
int WxInitSDK(bool debug, int port)
|
||||||
{
|
{
|
||||||
if (!ShowDisclaimer()) {
|
if (!show_disclaimer()) {
|
||||||
exit(-1); // 用户拒绝协议,退出程序
|
exit(-1); // 用户拒绝协议,退出程序
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = 0;
|
int status = 0;
|
||||||
DWORD wcPid = 0;
|
DWORD wcPid = 0;
|
||||||
|
|
||||||
spyDllPath = GetDllPath(debug);
|
spyDllPath = get_dll_path(debug);
|
||||||
if (spyDllPath.empty()) {
|
if (spyDllPath.empty()) {
|
||||||
return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在
|
return ERROR_FILE_NOT_FOUND; // DLL 文件路径不存在
|
||||||
}
|
}
|
||||||
|
|
||||||
status = OpenWeChat(&wcPid);
|
status = util::open_wechat(wcPid);
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
MessageBox(NULL, L"打开微信失败", L"WxInitSDK", 0);
|
util::MsgBox(NULL, "打开微信失败", "WxInitSDK", 0);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsProcessX64(wcPid)) {
|
|
||||||
MessageBox(NULL, L"只支持 64 位微信", L"WxInitSDK", 0);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待微信打开
|
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) {
|
if (wcProcess == NULL) {
|
||||||
MessageBox(NULL, L"注入失败", L"WxInitSDK", 0);
|
util::MsgBox(NULL, "注入失败", "WxInitSDK", 0);
|
||||||
return -1;
|
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;
|
injected = true;
|
||||||
return 0;
|
|
||||||
|
util::PortPath pp = { 0 };
|
||||||
|
pp.port = port;
|
||||||
|
snprintf(pp.path, MAX_PATH, "%s", fs::current_path().string().c_str());
|
||||||
|
|
||||||
|
status = -3; // TODO: 统一错误码
|
||||||
|
bool success = call_dll_func_ex(wcProcess, spyDllPath, spyBase, "InitSpy", (LPVOID)&pp, sizeof(util::PortPath),
|
||||||
|
(DWORD *)&status);
|
||||||
|
if (!success || status != 0) {
|
||||||
|
WxDestroySDK();
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WxDestroySDK()
|
int WxDestroySDK()
|
||||||
{
|
{
|
||||||
if (!injected) {
|
if (!injected) {
|
||||||
|
return 1; // 未注入
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!call_dll_func(wcProcess, spyDllPath, spyBase, "CleanupSpy", NULL)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CallDllFunc(wcProcess, spyDllPath.c_str(), spyBase, "CleanupSpy", NULL)) {
|
if (!eject_dll(wcProcess, spyBase)) {
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
injected = false;
|
||||||
if (!EjectDll(wcProcess, spyBase)) {
|
|
||||||
return -3; // TODO: Unify error codes
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
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\pb_util.h" />
|
||||||
<ClInclude Include="..\rpc\proto\wcf.pb.h" />
|
<ClInclude Include="..\rpc\proto\wcf.pb.h" />
|
||||||
<ClInclude Include="..\smc\codec.h" />
|
<ClInclude Include="..\smc\codec.h" />
|
||||||
<ClInclude Include="chatroom_mgmt.h" />
|
<ClInclude Include="chatroom_manager.h" />
|
||||||
<ClInclude Include="funcs.h" />
|
<ClInclude Include="misc_manager.h" />
|
||||||
<ClInclude Include="exec_sql.h" />
|
<ClInclude Include="database_executor.h" />
|
||||||
<ClInclude Include="framework.h" />
|
<ClInclude Include="framework.h" />
|
||||||
<ClInclude Include="contact_mgmt.h" />
|
<ClInclude Include="contact_manager.h" />
|
||||||
<ClInclude Include="receive_msg.h" />
|
<ClInclude Include="message_handler.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
|
<ClInclude Include="rpc_helper.h" />
|
||||||
<ClInclude Include="rpc_server.h" />
|
<ClInclude Include="rpc_server.h" />
|
||||||
<ClInclude Include="send_msg.h" />
|
<ClInclude Include="message_sender.h" />
|
||||||
<ClInclude Include="spy.h" />
|
<ClInclude Include="spy.h" />
|
||||||
<ClInclude Include="spy_types.h" />
|
<ClInclude Include="spy_types.h" />
|
||||||
<ClInclude Include="sqlite3.h" />
|
<ClInclude Include="sqlite3.h" />
|
||||||
<ClInclude Include="user_info.h" />
|
<ClInclude Include="account_manager.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\com\util.cpp" />
|
<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\nanopb\pb_encode.c" />
|
||||||
<ClCompile Include="..\rpc\pb_util.cpp" />
|
<ClCompile Include="..\rpc\pb_util.cpp" />
|
||||||
<ClCompile Include="..\rpc\proto\wcf.pb.c" />
|
<ClCompile Include="..\rpc\proto\wcf.pb.c" />
|
||||||
<ClCompile Include="chatroom_mgmt.cpp" />
|
<ClCompile Include="chatroom_manager.cpp" />
|
||||||
<ClCompile Include="funcs.cpp" />
|
<ClCompile Include="misc_manager.cpp" />
|
||||||
<ClCompile Include="dllmain.cpp" />
|
<ClCompile Include="dllmain.cpp" />
|
||||||
<ClCompile Include="exec_sql.cpp" />
|
<ClCompile Include="database_executor.cpp" />
|
||||||
<ClCompile Include="contact_mgmt.cpp" />
|
<ClCompile Include="contact_manager.cpp" />
|
||||||
<ClCompile Include="receive_msg.cpp" />
|
<ClCompile Include="message_handler.cpp" />
|
||||||
<ClCompile Include="rpc_server.cpp" />
|
<ClCompile Include="rpc_server.cpp" />
|
||||||
<ClCompile Include="send_msg.cpp" />
|
<ClCompile Include="message_sender.cpp" />
|
||||||
<ClCompile Include="spy.cpp" />
|
<ClCompile Include="spy.cpp" />
|
||||||
<ClCompile Include="user_info.cpp" />
|
<ClCompile Include="account_manager.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\rpc\proto\wcf.proto" />
|
<None Include="..\rpc\proto\wcf.proto" />
|
||||||
|
@ -24,16 +24,16 @@
|
|||||||
<ClInclude Include="rpc_server.h">
|
<ClInclude Include="rpc_server.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="exec_sql.h">
|
<ClInclude Include="database_executor.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="contact_mgmt.h">
|
<ClInclude Include="contact_manager.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="receive_msg.h">
|
<ClInclude Include="message_handler.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="send_msg.h">
|
<ClInclude Include="message_sender.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="spy.h">
|
<ClInclude Include="spy.h">
|
||||||
@ -63,16 +63,16 @@
|
|||||||
<ClInclude Include="..\rpc\pb_types.h">
|
<ClInclude Include="..\rpc\pb_types.h">
|
||||||
<Filter>nnrpc</Filter>
|
<Filter>nnrpc</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="chatroom_mgmt.h">
|
<ClInclude Include="chatroom_manager.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="user_info.h">
|
<ClInclude Include="account_manager.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="resource.h">
|
<ClInclude Include="resource.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="funcs.h">
|
<ClInclude Include="misc_manager.h">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="sqlite3.h">
|
<ClInclude Include="sqlite3.h">
|
||||||
@ -87,6 +87,9 @@
|
|||||||
<ClInclude Include="..\com\log.hpp">
|
<ClInclude Include="..\com\log.hpp">
|
||||||
<Filter>头文件</Filter>
|
<Filter>头文件</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="rpc_helper.h">
|
||||||
|
<Filter>头文件</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="dllmain.cpp">
|
<ClCompile Include="dllmain.cpp">
|
||||||
@ -95,16 +98,16 @@
|
|||||||
<ClCompile Include="rpc_server.cpp">
|
<ClCompile Include="rpc_server.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="exec_sql.cpp">
|
<ClCompile Include="database_executor.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="contact_mgmt.cpp">
|
<ClCompile Include="contact_manager.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="receive_msg.cpp">
|
<ClCompile Include="message_handler.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="send_msg.cpp">
|
<ClCompile Include="message_sender.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="spy.cpp">
|
<ClCompile Include="spy.cpp">
|
||||||
@ -125,13 +128,13 @@
|
|||||||
<ClCompile Include="..\rpc\pb_util.cpp">
|
<ClCompile Include="..\rpc\pb_util.cpp">
|
||||||
<Filter>nnrpc</Filter>
|
<Filter>nnrpc</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="chatroom_mgmt.cpp">
|
<ClCompile Include="chatroom_manager.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="user_info.cpp">
|
<ClCompile Include="account_manager.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="funcs.cpp">
|
<ClCompile Include="misc_manager.cpp">
|
||||||
<Filter>源文件</Filter>
|
<Filter>源文件</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\com\util.cpp">
|
<ClCompile Include="..\com\util.cpp">
|
||||||
|
110
WeChatFerry/spy/account_manager.cpp
Normal file
110
WeChatFerry/spy/account_manager.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#include "account_manager.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace account
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
namespace OsAcc = Offsets::Account;
|
||||||
|
|
||||||
|
using get_account_service_t = QWORD (*)();
|
||||||
|
using get_data_path_t = QWORD (*)(QWORD);
|
||||||
|
|
||||||
|
// 缓存避免重复查询
|
||||||
|
static std::optional<std::string> cachedWxid;
|
||||||
|
static std::optional<fs::path> cachedHomePath;
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
static void clear_cached_wxid() { cachedWxid.reset(); }
|
||||||
|
static void clear_cached_home_path() { cachedHomePath.reset(); }
|
||||||
|
|
||||||
|
static uint64_t get_account_service()
|
||||||
|
{
|
||||||
|
static auto GetService = Spy::getFunction<get_account_service_t>(OsAcc::SERVICE);
|
||||||
|
return GetService ? GetService() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string get_string_value(uint64_t base_addr, uint64_t offset)
|
||||||
|
{
|
||||||
|
uint64_t type = util::get_qword(base_addr + offset + 0x18);
|
||||||
|
return (type == 0xF) ? util::get_p_string(base_addr + offset) : util::get_pp_string(base_addr + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_logged_in()
|
||||||
|
{
|
||||||
|
clear_cached_wxid();
|
||||||
|
clear_cached_home_path();
|
||||||
|
uint64_t service_addr = get_account_service();
|
||||||
|
return service_addr && util::get_qword(service_addr + OsAcc::LOGIN) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path get_home_path()
|
||||||
|
{
|
||||||
|
if (cachedHomePath) {
|
||||||
|
return *cachedHomePath;
|
||||||
|
}
|
||||||
|
WxString home;
|
||||||
|
auto GetDataPath = Spy::getFunction<get_data_path_t>(OsAcc::PATH);
|
||||||
|
int64_t service_addr = get_account_service();
|
||||||
|
GetDataPath((QWORD)&home);
|
||||||
|
if (home.wptr) {
|
||||||
|
cachedHomePath = util::w2s(std::wstring(home.wptr, home.size));
|
||||||
|
}
|
||||||
|
return *cachedHomePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_self_wxid()
|
||||||
|
{
|
||||||
|
if (cachedWxid) {
|
||||||
|
return *cachedWxid;
|
||||||
|
}
|
||||||
|
uint64_t service_addr = get_account_service();
|
||||||
|
if (!service_addr) return "";
|
||||||
|
|
||||||
|
cachedWxid = get_string_value(service_addr, OsAcc::WXID);
|
||||||
|
return *cachedWxid;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfo_t get_user_info()
|
||||||
|
{
|
||||||
|
UserInfo_t ui;
|
||||||
|
uint64_t service_addr = get_account_service();
|
||||||
|
if (!service_addr) return ui;
|
||||||
|
|
||||||
|
ui.wxid = get_self_wxid();
|
||||||
|
ui.home = get_home_path().generic_string();
|
||||||
|
ui.name = get_string_value(service_addr, OsAcc::NAME);
|
||||||
|
ui.mobile = get_string_value(service_addr, OsAcc::MOBILE);
|
||||||
|
return ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_is_logged_in(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_IS_LOGIN>(out, len, [](Response &rsp) { rsp.msg.status = is_logged_in(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_self_wxid(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_GET_SELF_WXID>(
|
||||||
|
out, len, [](Response &rsp) { rsp.msg.str = (char *)get_self_wxid().c_str(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_user_info(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
UserInfo_t ui = get_user_info();
|
||||||
|
return fill_response<Functions_FUNC_GET_USER_INFO>(out, len, ui, [](Response &rsp, UserInfo_t &ui) {
|
||||||
|
rsp.msg.ui.wxid = (char *)ui.wxid.c_str();
|
||||||
|
rsp.msg.ui.name = (char *)ui.name.c_str();
|
||||||
|
rsp.msg.ui.mobile = (char *)ui.mobile.c_str();
|
||||||
|
rsp.msg.ui.home = (char *)ui.home.c_str();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace account
|
29
WeChatFerry/spy/account_manager.h
Normal file
29
WeChatFerry/spy/account_manager.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "pb_types.h"
|
||||||
|
|
||||||
|
namespace account
|
||||||
|
{
|
||||||
|
|
||||||
|
// 登录状态
|
||||||
|
bool is_logged_in();
|
||||||
|
|
||||||
|
// 获取 WeChat 数据存储路径
|
||||||
|
std::filesystem::path get_home_path();
|
||||||
|
|
||||||
|
// 获取自身 wxid
|
||||||
|
std::string get_self_wxid();
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
UserInfo_t get_user_info();
|
||||||
|
|
||||||
|
// RPC 方法
|
||||||
|
bool rpc_is_logged_in(uint8_t *out, size_t *len);
|
||||||
|
bool rpc_get_self_wxid(uint8_t *out, size_t *len);
|
||||||
|
bool rpc_get_user_info(uint8_t *out, size_t *len);
|
||||||
|
|
||||||
|
} // namespace account
|
87
WeChatFerry/spy/chatroom_manager.cpp
Normal file
87
WeChatFerry/spy/chatroom_manager.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#pragma execution_character_set("utf-8")
|
||||||
|
|
||||||
|
#include "chatroom_manager.h"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "pb_util.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace chatroom
|
||||||
|
{
|
||||||
|
namespace OsRoom = Offsets::Chatroom;
|
||||||
|
|
||||||
|
using get_mgr_t = QWORD (*)();
|
||||||
|
using add_member_t = QWORD (*)(QWORD, QWORD, WxString *, QWORD);
|
||||||
|
using delete_member_t = QWORD (*)(QWORD, QWORD, WxString *);
|
||||||
|
using invite_members_t = QWORD (*)(const wchar_t *, QWORD, WxString *, QWORD);
|
||||||
|
|
||||||
|
template <auto FillFunc, typename Func>
|
||||||
|
bool rpc_chatroom_common(const MemberMgmt &m, uint8_t *out, size_t *len, Func func)
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
if (m.wxids && m.roomid) {
|
||||||
|
status = func(m.roomid, m.wxids);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("wxid 和 roomid 不能为空");
|
||||||
|
}
|
||||||
|
return fill_response<FillFunc>(out, len, [&](Response &rsp) { rsp.msg.status = status; });
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_chatroom_member(const string &roomid, const string &wxids)
|
||||||
|
{
|
||||||
|
auto get_chatroom_mgr = Spy::getFunction<get_mgr_t>(OsRoom::MGR);
|
||||||
|
auto add_members = Spy::getFunction<add_member_t>(OsRoom::ADD);
|
||||||
|
|
||||||
|
WxString *wx_roomid = util::CreateWxString(roomid);
|
||||||
|
|
||||||
|
QWORD tmp[2] = { 0 };
|
||||||
|
auto wx_members = util::parse_wxids(wxids).wxWxids;
|
||||||
|
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
|
||||||
|
|
||||||
|
return static_cast<int>(add_members(get_chatroom_mgr(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int del_chatroom_member(const string &roomid, const string &wxids)
|
||||||
|
{
|
||||||
|
auto get_chatroom_mgr = Spy::getFunction<get_mgr_t>(OsRoom::MGR);
|
||||||
|
auto del_members = Spy::getFunction<delete_member_t>(OsRoom::DEL);
|
||||||
|
|
||||||
|
WxString *wx_roomid = util::CreateWxString(roomid);
|
||||||
|
auto wx_members = util::parse_wxids(wxids).wxWxids;
|
||||||
|
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
|
||||||
|
|
||||||
|
return static_cast<int>(del_members(get_chatroom_mgr(), p_members, wx_roomid));
|
||||||
|
}
|
||||||
|
|
||||||
|
int invite_chatroom_member(const string &roomid, const string &wxids)
|
||||||
|
{
|
||||||
|
auto invite_members = Spy::getFunction<invite_members_t>(OsRoom::INV);
|
||||||
|
|
||||||
|
wstring ws_roomid = util::s2w(roomid);
|
||||||
|
WxString *wx_roomid = util::CreateWxString(roomid);
|
||||||
|
|
||||||
|
QWORD tmp[2] = { 0 };
|
||||||
|
auto wx_members = util::parse_wxids(wxids).wxWxids;
|
||||||
|
QWORD p_members = reinterpret_cast<QWORD>(&wx_members);
|
||||||
|
|
||||||
|
return static_cast<int>(invite_members(ws_roomid.c_str(), p_members, wx_roomid, reinterpret_cast<QWORD>(tmp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_add_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return rpc_chatroom_common<Functions_FUNC_ADD_ROOM_MEMBERS>(m, out, len, add_chatroom_member);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_delete_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return rpc_chatroom_common<Functions_FUNC_DEL_ROOM_MEMBERS>(m, out, len, del_chatroom_member);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_invite_chatroom_member(const MemberMgmt &m, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return rpc_chatroom_common<Functions_FUNC_INV_ROOM_MEMBERS>(m, out, len, invite_chatroom_member);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatroom
|
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
|
@ -3,16 +3,16 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "chatroom_mgmt.h"
|
#include "chatroom_mgmt.h"
|
||||||
#include "log.hpp"
|
#include "log.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
extern QWORD g_WeChatWinDllAddr;
|
extern QWORD g_WeChatWinDllAddr;
|
||||||
|
|
||||||
#define OS_GET_CHATROOM_MGR 0x1B83BD0
|
#define OS_GET_CHATROOM_MGR 0x1B894E0
|
||||||
#define OS_ADD_MEMBERS 0x2155100
|
#define OS_ADD_MEMBERS 0x215A820
|
||||||
#define OS_DELETE_MEMBERS 0x2155740
|
#define OS_DELETE_MEMBERS 0x215AE60
|
||||||
#define OS_INVITE_MEMBERS 0x2154AE0
|
#define OS_INVITE_MEMBERS 0x215A200
|
||||||
|
|
||||||
typedef QWORD (*GetChatRoomMgr_t)();
|
typedef QWORD (*GetChatRoomMgr_t)();
|
||||||
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
|
typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD);
|
||||||
@ -111,3 +111,4 @@ int InviteChatroomMember(string roomid, string wxids)
|
|||||||
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
|
status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp);
|
||||||
return status;
|
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);
|
|
131
WeChatFerry/spy/contact_manager.cpp
Normal file
131
WeChatFerry/spy/contact_manager.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#pragma execution_character_set("utf-8")
|
||||||
|
|
||||||
|
#include "contact_manager.h"
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "pb_util.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace contact
|
||||||
|
{
|
||||||
|
namespace OsCon = Offsets::Contact;
|
||||||
|
|
||||||
|
using get_contact_mgr_t = QWORD (*)();
|
||||||
|
using get_contact_list_t = QWORD (*)(QWORD, QWORD);
|
||||||
|
|
||||||
|
#define FEAT_LEN 5
|
||||||
|
static const uint8_t FEAT_COUNTRY[FEAT_LEN] = { 0xA4, 0xD9, 0x02, 0x4A, 0x18 };
|
||||||
|
static const uint8_t FEAT_PROVINCE[FEAT_LEN] = { 0xE2, 0xEA, 0xA8, 0xD1, 0x18 };
|
||||||
|
static const uint8_t FEAT_CITY[FEAT_LEN] = { 0x1D, 0x02, 0x5B, 0xBF, 0x18 };
|
||||||
|
|
||||||
|
static QWORD find_mem(QWORD start, QWORD end, const void *target, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t *p = reinterpret_cast<uint8_t *>(start);
|
||||||
|
while (reinterpret_cast<QWORD>(p) < end) {
|
||||||
|
if (memcmp(p, target, len) == 0) {
|
||||||
|
return reinterpret_cast<QWORD>(p);
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string get_cnt_string(QWORD start, QWORD end, const uint8_t *feat, size_t len)
|
||||||
|
{
|
||||||
|
QWORD pfeat = find_mem(start, end, feat, len);
|
||||||
|
if (pfeat == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD lfeat = util::get_dword(pfeat + len);
|
||||||
|
if (lfeat <= 2) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return util::w2s(util::get_p_wstring(pfeat + FEAT_LEN + 4, lfeat));
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<RpcContact_t> get_contacts()
|
||||||
|
{
|
||||||
|
vector<RpcContact_t> contacts;
|
||||||
|
auto func_get_contact_mgr = Spy::getFunction<get_contact_mgr_t>(OsCon::MGR);
|
||||||
|
auto func_get_contact_list = Spy::getFunction<get_contact_list_t>(OsCon::LIST);
|
||||||
|
|
||||||
|
QWORD mgr = func_get_contact_mgr();
|
||||||
|
QWORD addr[3] = { 0 };
|
||||||
|
if (func_get_contact_list(mgr, reinterpret_cast<QWORD>(addr)) != 1) {
|
||||||
|
LOG_ERROR("get_contacts failed");
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD pstart = addr[0];
|
||||||
|
QWORD pend = addr[2];
|
||||||
|
while (pstart < pend) {
|
||||||
|
RpcContact_t cnt;
|
||||||
|
QWORD pbin = util::get_qword(pstart + OsCon::BIN);
|
||||||
|
QWORD lenbin = util::get_dword(pstart + OsCon::BIN_LEN);
|
||||||
|
|
||||||
|
cnt.wxid = util::get_str_by_wstr_addr(pstart + OsCon::WXID);
|
||||||
|
cnt.code = util::get_str_by_wstr_addr(pstart + OsCon::CODE);
|
||||||
|
cnt.remark = util::get_str_by_wstr_addr(pstart + OsCon::REMARK);
|
||||||
|
cnt.name = util::get_str_by_wstr_addr(pstart + OsCon::NAME);
|
||||||
|
|
||||||
|
cnt.country = get_cnt_string(pbin, pbin + lenbin, FEAT_COUNTRY, FEAT_LEN);
|
||||||
|
cnt.province = get_cnt_string(pbin, pbin + lenbin, FEAT_PROVINCE, FEAT_LEN);
|
||||||
|
cnt.city = get_cnt_string(pbin, pbin + lenbin, FEAT_CITY, FEAT_LEN);
|
||||||
|
|
||||||
|
cnt.gender = (pbin == 0) ? 0 : static_cast<DWORD>(*(uint8_t *)(pbin + OsCon::GENDER));
|
||||||
|
|
||||||
|
contacts.push_back(cnt);
|
||||||
|
pstart += OsCon::STEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
int accept_new_friend(const std::string &v3, const std::string &v4, int scene)
|
||||||
|
{
|
||||||
|
LOG_ERROR("技术太菜,实现不了。");
|
||||||
|
return -1; // 成功返回 1
|
||||||
|
}
|
||||||
|
|
||||||
|
RpcContact_t get_contact_by_wxid(const string &wxid)
|
||||||
|
{
|
||||||
|
RpcContact_t contact;
|
||||||
|
LOG_ERROR("技术太菜,实现不了。");
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_contacts(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
vector<RpcContact_t> contacts = get_contacts();
|
||||||
|
return fill_response<Functions_FUNC_GET_CONTACTS>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
|
||||||
|
rsp.msg.contacts.contacts.arg = &contacts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_contact_info(const string &wxid, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
vector<RpcContact_t> contacts = { get_contact_by_wxid(wxid) };
|
||||||
|
return fill_response<Functions_FUNC_GET_CONTACT_INFO>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.contacts.contacts.funcs.encode = encode_contacts;
|
||||||
|
rsp.msg.contacts.contacts.arg = &contacts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_accept_friend(const Verification &v, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
const string v3 = v.v3 ? v.v3 : "";
|
||||||
|
const string v4 = v.v4 ? v.v4 : "";
|
||||||
|
int scene = v.scene;
|
||||||
|
return fill_response<Functions_FUNC_ACCEPT_FRIEND>(
|
||||||
|
out, len, [&](Response &rsp) { rsp.msg.status = accept_new_friend(v3, v4, scene); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace contact
|
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);
|
|
277
WeChatFerry/spy/database_executor.cpp
Normal file
277
WeChatFerry/spy/database_executor.cpp
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
#include "database_executor.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "pb_util.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "sqlite3.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace db
|
||||||
|
{
|
||||||
|
namespace OsDb = Offsets::Db;
|
||||||
|
|
||||||
|
using db_map_t = std::map<std::string, QWORD>;
|
||||||
|
static db_map_t db_map;
|
||||||
|
|
||||||
|
static void get_db_handle(QWORD base, QWORD offset)
|
||||||
|
{
|
||||||
|
auto *wsp = reinterpret_cast<wchar_t *>(*(QWORD *)(base + offset + OsDb::NAME));
|
||||||
|
std::string dbname = util::w2s(std::wstring(wsp));
|
||||||
|
db_map[dbname] = util::get_qword(base + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_msg_db_handle(QWORD msg_mgr_addr)
|
||||||
|
{
|
||||||
|
QWORD db_index = util::get_qword(msg_mgr_addr + 0x68);
|
||||||
|
QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
|
||||||
|
for (uint32_t i = 0; i < db_index; i++) {
|
||||||
|
QWORD db_addr = util::get_qword(p_start + i * 0x08);
|
||||||
|
if (db_addr) {
|
||||||
|
// MSGi.db
|
||||||
|
std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
|
||||||
|
db_map[dbname] = util::get_qword(db_addr + 0x78);
|
||||||
|
|
||||||
|
// MediaMsgi.db
|
||||||
|
QWORD mmdb_addr = util::get_qword(db_addr + 0x20);
|
||||||
|
std::string mmdbname = util::w2s(util::get_pp_wstring(mmdb_addr + 0x78));
|
||||||
|
db_map[mmdbname] = util::get_qword(mmdb_addr + 0x50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db_map_t get_db_handles()
|
||||||
|
{
|
||||||
|
db_map.clear();
|
||||||
|
QWORD db_instance_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::INSTANCE);
|
||||||
|
|
||||||
|
get_db_handle(db_instance_addr, OsDb::MICROMSG); // MicroMsg.db
|
||||||
|
get_db_handle(db_instance_addr, OsDb::CHAT_MSG); // ChatMsg.db
|
||||||
|
get_db_handle(db_instance_addr, OsDb::MISC); // Misc.db
|
||||||
|
get_db_handle(db_instance_addr, OsDb::EMOTION); // Emotion.db
|
||||||
|
get_db_handle(db_instance_addr, OsDb::MEDIA); // Media.db
|
||||||
|
get_db_handle(db_instance_addr, OsDb::FUNCTION_MSG); // Function.db
|
||||||
|
|
||||||
|
get_msg_db_handle(util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I)); // MSGi.db & MediaMsgi.db
|
||||||
|
return db_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbNames_t get_db_names()
|
||||||
|
{
|
||||||
|
if (db_map.empty()) {
|
||||||
|
db_map = get_db_handles();
|
||||||
|
}
|
||||||
|
|
||||||
|
DbNames_t names;
|
||||||
|
for (const auto &[k, _] : db_map) {
|
||||||
|
names.push_back(k);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cb_get_tables(void *ret, int argc, char **argv, char **azColName)
|
||||||
|
{
|
||||||
|
auto *tables = static_cast<DbTables_t *>(ret);
|
||||||
|
DbTable_t tbl;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
if (strcmp(azColName[i], "name") == 0) {
|
||||||
|
tbl.name = argv[i] ? argv[i] : "";
|
||||||
|
} else if (strcmp(azColName[i], "sql") == 0) {
|
||||||
|
std::string sql(argv[i]);
|
||||||
|
sql.erase(std::remove(sql.begin(), sql.end(), '\t'), sql.end());
|
||||||
|
tbl.sql = sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tables->push_back(tbl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbTables_t get_db_tables(const std::string &db)
|
||||||
|
{
|
||||||
|
DbTables_t tables;
|
||||||
|
if (db_map.empty()) {
|
||||||
|
db_map = get_db_handles();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = db_map.find(db);
|
||||||
|
if (it == db_map.end()) {
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char *sql = "SELECT name FROM sqlite_master WHERE type='table';";
|
||||||
|
auto p_sqlite3_exec = Spy::getFunction<Sqlite3_exec>(OsDb::EXEC);
|
||||||
|
p_sqlite3_exec(it->second, sql, (Sqlite3_callback)cb_get_tables, (void *)&tables, nullptr);
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbRows_t exec_db_query(const std::string &db, const std::string &sql)
|
||||||
|
{
|
||||||
|
DbRows_t rows;
|
||||||
|
|
||||||
|
auto func_prepare = Spy::getFunction<Sqlite3_prepare>(OsDb::PREPARE);
|
||||||
|
auto func_step = Spy::getFunction<Sqlite3_step>(OsDb::STEP);
|
||||||
|
auto func_column_count = Spy::getFunction<Sqlite3_column_count>(OsDb::COLUMN_COUNT);
|
||||||
|
auto func_column_name = Spy::getFunction<Sqlite3_column_name>(OsDb::COLUMN_NAME);
|
||||||
|
auto func_column_type = Spy::getFunction<Sqlite3_column_type>(OsDb::COLUMN_TYPE);
|
||||||
|
auto func_column_blob = Spy::getFunction<Sqlite3_column_blob>(OsDb::COLUMN_BLOB);
|
||||||
|
auto func_column_bytes = Spy::getFunction<Sqlite3_column_bytes>(OsDb::COLUMN_BYTES);
|
||||||
|
auto func_finalize = Spy::getFunction<Sqlite3_finalize>(OsDb::FINALIZE);
|
||||||
|
|
||||||
|
if (db_map.empty()) {
|
||||||
|
db_map = get_db_handles();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = db_map.find(db);
|
||||||
|
if (it == db_map.end() || it->second == 0) {
|
||||||
|
LOG_WARN("Empty handle for database '{}', retrying...", db);
|
||||||
|
db_map = get_db_handles();
|
||||||
|
it = db_map.find(db);
|
||||||
|
if (it == db_map.end() || it->second == 0) {
|
||||||
|
LOG_ERROR("Failed to get handle for database '{}'", db);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD *stmt;
|
||||||
|
int rc = func_prepare(it->second, sql.c_str(), -1, &stmt, nullptr);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
LOG_ERROR("SQL prepare failed for '{}': error code {}", db, rc);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (func_step(stmt) == SQLITE_ROW) {
|
||||||
|
DbRow_t row;
|
||||||
|
int col_count = func_column_count(stmt);
|
||||||
|
for (int i = 0; i < col_count; i++) {
|
||||||
|
DbField_t field;
|
||||||
|
field.type = func_column_type(stmt, i);
|
||||||
|
field.column = func_column_name(stmt, i);
|
||||||
|
|
||||||
|
int length = func_column_bytes(stmt, i);
|
||||||
|
const void *blob = func_column_blob(stmt, i);
|
||||||
|
if (length > 0 && field.type != SQLITE_NULL) {
|
||||||
|
field.content.resize(length);
|
||||||
|
std::memcpy(field.content.data(), blob, length);
|
||||||
|
}
|
||||||
|
row.push_back(field);
|
||||||
|
}
|
||||||
|
rows.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
func_finalize(stmt);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_local_id_and_dbidx(uint64_t id, uint64_t *local_id, uint32_t *db_idx)
|
||||||
|
{
|
||||||
|
if (!local_id || !db_idx) {
|
||||||
|
LOG_ERROR("Invalid pointer arguments!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
|
||||||
|
int db_index = static_cast<int>(util::get_qword(msg_mgr_addr + 0x68)); // 总不能 int 还不够吧?
|
||||||
|
QWORD p_start = util::get_qword(msg_mgr_addr + 0x50);
|
||||||
|
|
||||||
|
*db_idx = 0;
|
||||||
|
for (int i = db_index - 1; i >= 0; i--) { // 从后往前遍历
|
||||||
|
QWORD db_addr = util::get_qword(p_start + i * 0x08);
|
||||||
|
if (!db_addr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string dbname = util::w2s(util::get_pp_wstring(db_addr));
|
||||||
|
db_map[dbname] = util::get_qword(db_addr + 0x78);
|
||||||
|
|
||||||
|
std::string sql = "SELECT localId FROM MSG WHERE MsgSvrID=" + std::to_string(id) + ";";
|
||||||
|
DbRows_t rows = exec_db_query(dbname, sql);
|
||||||
|
|
||||||
|
if (rows.empty() || rows.front().empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DbField_t &field = rows.front().front();
|
||||||
|
if (field.column != "localId" || field.type != SQLITE_INTEGER) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string id_str(field.content.begin(), field.content.end());
|
||||||
|
try {
|
||||||
|
*local_id = std::stoull(id_str);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
LOG_ERROR("Failed to parse localId: {}", e.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*db_idx = static_cast<uint32_t>(util::get_qword(util::get_qword(db_addr + 0x28) + 0x1E8) >> 32);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> get_audio_data(uint64_t id)
|
||||||
|
{
|
||||||
|
QWORD msg_mgr_addr = util::get_qword(Spy::WeChatDll.load() + OsDb::MSG_I);
|
||||||
|
int db_index = static_cast<int>(util::get_qword(msg_mgr_addr + 0x68));
|
||||||
|
|
||||||
|
std::string sql = "SELECT Buf FROM Media WHERE Reserved0=" + std::to_string(id) + ";";
|
||||||
|
for (int i = db_index - 1; i >= 0; i--) {
|
||||||
|
std::string dbname = "MediaMSG" + std::to_string(i) + ".db";
|
||||||
|
DbRows_t rows = exec_db_query(dbname, sql);
|
||||||
|
|
||||||
|
if (rows.empty() || rows.front().empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DbField_t &field = rows.front().front();
|
||||||
|
if (field.column != "Buf" || field.content.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首字节为 0x02,估计是混淆用的?去掉。
|
||||||
|
if (field.content.front() == 0x02) {
|
||||||
|
return std::vector<uint8_t>(field.content.begin() + 1, field.content.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_db_names(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
DbNames_t names = get_db_names();
|
||||||
|
return fill_response<Functions_FUNC_GET_DB_NAMES>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.dbs.names.funcs.encode = encode_dbnames;
|
||||||
|
rsp.msg.dbs.names.arg = &names;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_db_tables(const std::string &db, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
DbTables_t tables = get_db_tables(db);
|
||||||
|
return fill_response<Functions_FUNC_GET_DB_TABLES>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.tables.tables.funcs.encode = encode_tables;
|
||||||
|
rsp.msg.tables.tables.arg = &tables;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_exec_db_query(const DbQuery query, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
const std::string db(query.db);
|
||||||
|
const std::string sql(query.sql);
|
||||||
|
DbRows_t rows = exec_db_query(db, sql);
|
||||||
|
return fill_response<Functions_FUNC_EXEC_DB_QUERY>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.rows.rows.funcs.encode = encode_rows;
|
||||||
|
rsp.msg.rows.rows.arg = &rows;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace db
|
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,9 +1,5 @@
|
|||||||
// dllmain.cpp : 定义 DLL 应用程序的入口点。
|
// dllmain.cpp : 定义 DLL 应用程序的入口点。
|
||||||
#include "framework.h"
|
#include "framework.h"
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "spy.h"
|
|
||||||
|
|
||||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
|
281
WeChatFerry/spy/message_handler.cpp
Normal file
281
WeChatFerry/spy/message_handler.cpp
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
#include "message_handler.h"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "framework.h"
|
||||||
|
|
||||||
|
#include "account_manager.h"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "pb_util.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace message
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace OsLog = Offsets::Message::Log;
|
||||||
|
namespace OsRecv = Offsets::Message::Receive;
|
||||||
|
|
||||||
|
QWORD Handler::DispatchMsg(QWORD arg1, QWORD arg2)
|
||||||
|
{
|
||||||
|
auto &handler = getInstance();
|
||||||
|
WxMsg_t wxMsg = {};
|
||||||
|
try {
|
||||||
|
wxMsg.id = util::get_qword(arg2 + OsRecv::ID);
|
||||||
|
wxMsg.type = util::get_dword(arg2 + OsRecv::TYPE);
|
||||||
|
wxMsg.is_self = util::get_dword(arg2 + OsRecv::SELF);
|
||||||
|
wxMsg.ts = util::get_dword(arg2 + OsRecv::TIMESTAMP);
|
||||||
|
wxMsg.content = util::get_str_by_wstr_addr(arg2 + OsRecv::CONTENT);
|
||||||
|
wxMsg.sign = util::get_str_by_wstr_addr(arg2 + OsRecv::SIGN);
|
||||||
|
wxMsg.xml = util::get_str_by_wstr_addr(arg2 + OsRecv::XML);
|
||||||
|
wxMsg.roomid = util::get_str_by_wstr_addr(arg2 + OsRecv::ROOMID);
|
||||||
|
|
||||||
|
if (wxMsg.roomid.find("@chatroom") != std::string::npos) { // 群 ID 的格式为 xxxxxxxxxxx@chatroom
|
||||||
|
wxMsg.is_group = true;
|
||||||
|
wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : util::get_str_by_wstr_addr(arg2 + OsRecv::WXID);
|
||||||
|
} else {
|
||||||
|
wxMsg.is_group = false;
|
||||||
|
wxMsg.sender = wxMsg.is_self ? account::get_self_wxid() : wxMsg.roomid;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path thumb = util::get_str_by_wstr_addr(arg2 + OsRecv::THUMB);
|
||||||
|
if (!thumb.empty()) {
|
||||||
|
wxMsg.thumb = (account::get_home_path() / thumb).generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path extra = util::get_str_by_wstr_addr(arg2 + OsRecv::EXTRA);
|
||||||
|
if (!extra.empty()) {
|
||||||
|
wxMsg.extra = (account::get_home_path() / extra).generic_string();
|
||||||
|
}
|
||||||
|
LOG_DEBUG("{}", wxMsg.content);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
LOG_ERROR(util::gb2312_to_utf8(e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(handler.mutex_);
|
||||||
|
handler.msgQueue_.push(wxMsg); // 推送到队列
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.cv_.notify_all();
|
||||||
|
return handler.realRecvMsg(arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD Handler::PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
|
||||||
|
QWORD a10, QWORD a11, QWORD a12)
|
||||||
|
{
|
||||||
|
auto &handler = getInstance();
|
||||||
|
|
||||||
|
QWORD p = handler.realWxLog(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
|
||||||
|
if (p == 0 || p == 1) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("【WX】\n{}", util::gb2312_to_utf8((char *)p));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD Handler::DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3)
|
||||||
|
{
|
||||||
|
auto &handler = getInstance();
|
||||||
|
QWORD startAddr = *(QWORD *)(arg2 + OsRecv::PYQ_START);
|
||||||
|
QWORD endAddr = *(QWORD *)(arg2 + OsRecv::PYQ_END);
|
||||||
|
|
||||||
|
if (startAddr == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (startAddr < endAddr) {
|
||||||
|
WxMsg_t wxMsg;
|
||||||
|
|
||||||
|
wxMsg.type = 0x00;
|
||||||
|
wxMsg.is_self = false;
|
||||||
|
wxMsg.is_group = false;
|
||||||
|
wxMsg.id = util::get_qword(startAddr);
|
||||||
|
wxMsg.ts = util::get_dword(startAddr + OsRecv::PYQ_TS);
|
||||||
|
wxMsg.xml = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_XML);
|
||||||
|
wxMsg.sender = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_SENDER);
|
||||||
|
wxMsg.content = util::get_str_by_wstr_addr(startAddr + OsRecv::PYQ_CONTENT);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(handler.mutex_);
|
||||||
|
handler.msgQueue_.push(wxMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.cv_.notify_all();
|
||||||
|
startAddr += 0x1618;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.realRecvPyq(arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler &Handler::getInstance()
|
||||||
|
{
|
||||||
|
static Handler instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler::Handler()
|
||||||
|
{
|
||||||
|
isLogging = false;
|
||||||
|
isListeningMsg = false;
|
||||||
|
isListeningPyq = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler::~Handler()
|
||||||
|
{
|
||||||
|
DisableLog();
|
||||||
|
UnListenMsg();
|
||||||
|
UnListenPyq();
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgTypes_t Handler::GetMsgTypes()
|
||||||
|
{
|
||||||
|
return { { 0x00, "朋友圈消息" },
|
||||||
|
{ 0x01, "文字" },
|
||||||
|
{ 0x03, "图片" },
|
||||||
|
{ 0x22, "语音" },
|
||||||
|
{ 0x25, "好友确认" },
|
||||||
|
{ 0x28, "POSSIBLEFRIEND_MSG" },
|
||||||
|
{ 0x2A, "名片" },
|
||||||
|
{ 0x2B, "视频" },
|
||||||
|
{ 0x2F, "石头剪刀布 | 表情图片" },
|
||||||
|
{ 0x30, "位置" },
|
||||||
|
{ 0x31, "共享实时位置、文件、转账、链接" },
|
||||||
|
{ 0x32, "VOIPMSG" },
|
||||||
|
{ 0x33, "微信初始化" },
|
||||||
|
{ 0x34, "VOIPNOTIFY" },
|
||||||
|
{ 0x35, "VOIPINVITE" },
|
||||||
|
{ 0x3E, "小视频" },
|
||||||
|
{ 0x42, "微信红包" },
|
||||||
|
{ 0x270F, "SYSNOTICE" },
|
||||||
|
{ 0x2710, "红包、系统消息" },
|
||||||
|
{ 0x2712, "撤回消息" },
|
||||||
|
{ 0x100031, "搜狗表情" },
|
||||||
|
{ 0x1000031, "链接" },
|
||||||
|
{ 0x1A000031, "微信红包" },
|
||||||
|
{ 0x20010031, "红包封面" },
|
||||||
|
{ 0x2D000031, "视频号视频" },
|
||||||
|
{ 0x2E000031, "视频号名片" },
|
||||||
|
{ 0x31000031, "引用消息" },
|
||||||
|
{ 0x37000031, "拍一拍" },
|
||||||
|
{ 0x3A000031, "视频号直播" },
|
||||||
|
{ 0x3A100031, "商品链接" },
|
||||||
|
{ 0x3A200031, "视频号直播" },
|
||||||
|
{ 0x3E000031, "音乐链接" },
|
||||||
|
{ 0x41000031, "文件" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<WxMsg_t> Handler::popMessage()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (msgQueue_.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
WxMsg_t msg = std::move(msgQueue_.front());
|
||||||
|
msgQueue_.pop();
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::EnableLog()
|
||||||
|
{
|
||||||
|
if (isLogging) return 1;
|
||||||
|
|
||||||
|
pLogLevel = reinterpret_cast<uint32_t *>(Spy::WeChatDll.load() + OsLog::LEVEL);
|
||||||
|
funcWxLog = Spy::getFunction<funcWxLog_t>(OsLog::CALL);
|
||||||
|
|
||||||
|
if (InitializeHook() != MH_OK) return -1;
|
||||||
|
if (MH_CreateHook(funcWxLog, &PrintWxLog, reinterpret_cast<LPVOID *>(&realWxLog)) != MH_OK) return -2;
|
||||||
|
if (MH_EnableHook(funcWxLog) != MH_OK) return -3;
|
||||||
|
|
||||||
|
*pLogLevel = 0;
|
||||||
|
isLogging = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::DisableLog()
|
||||||
|
{
|
||||||
|
if (!isLogging) return 1;
|
||||||
|
if (MH_DisableHook(funcWxLog) != MH_OK) return -1;
|
||||||
|
if (UninitializeHook() != MH_OK) return -2;
|
||||||
|
*pLogLevel = 6;
|
||||||
|
isLogging = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::ListenMsg()
|
||||||
|
{
|
||||||
|
if (isListeningMsg) return 1;
|
||||||
|
|
||||||
|
funcRecvMsg = Spy::getFunction<funcRecvMsg_t>(OsRecv::CALL);
|
||||||
|
if (InitializeHook() != MH_OK) return -1;
|
||||||
|
if (MH_CreateHook(funcRecvMsg, &DispatchMsg, reinterpret_cast<LPVOID *>(&realRecvMsg)) != MH_OK) return -1;
|
||||||
|
if (MH_EnableHook(funcRecvMsg) != MH_OK) return -1;
|
||||||
|
|
||||||
|
isListeningMsg = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::UnListenMsg()
|
||||||
|
{
|
||||||
|
if (!isListeningMsg) return 1;
|
||||||
|
if (MH_DisableHook(funcRecvMsg) != MH_OK) return -1;
|
||||||
|
if (UninitializeHook() != MH_OK) return -1;
|
||||||
|
isListeningMsg = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::ListenPyq()
|
||||||
|
{
|
||||||
|
if (isListeningPyq) return 1;
|
||||||
|
|
||||||
|
funcRecvPyq = Spy::getFunction<funcRecvPyq_t>(OsRecv::PYQ_CALL);
|
||||||
|
if (InitializeHook() != MH_OK) return -1;
|
||||||
|
if (MH_CreateHook(funcRecvPyq, &DispatchPyq, reinterpret_cast<LPVOID *>(&realRecvPyq)) != MH_OK) return -1;
|
||||||
|
if (MH_EnableHook(funcRecvPyq) != MH_OK) return -1;
|
||||||
|
|
||||||
|
isListeningPyq = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Handler::UnListenPyq()
|
||||||
|
{
|
||||||
|
if (!isListeningPyq) return 1;
|
||||||
|
if (MH_DisableHook(funcRecvPyq) != MH_OK) return -1;
|
||||||
|
if (UninitializeHook() != MH_OK) return -1;
|
||||||
|
isListeningPyq = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MH_STATUS Handler::InitializeHook()
|
||||||
|
{
|
||||||
|
if (isMH_Initialized) return MH_OK;
|
||||||
|
MH_STATUS status = MH_Initialize();
|
||||||
|
if (status == MH_OK) isMH_Initialized = true;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
MH_STATUS Handler::UninitializeHook()
|
||||||
|
{
|
||||||
|
if (!isMH_Initialized || isLogging || isListeningMsg || isListeningPyq) return MH_OK;
|
||||||
|
MH_STATUS status = MH_Uninitialize();
|
||||||
|
if (status == MH_OK) isMH_Initialized = false;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Handler::rpc_get_msg_types(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
MsgTypes_t types = GetMsgTypes();
|
||||||
|
return fill_response<Functions_FUNC_GET_MSG_TYPES>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.types.types.funcs.encode = encode_types;
|
||||||
|
rsp.msg.types.types.arg = &types;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
WeChatFerry/spy/message_handler.h
Normal file
74
WeChatFerry/spy/message_handler.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "MinHook.h"
|
||||||
|
|
||||||
|
#include "pb_types.h"
|
||||||
|
#include "spy_types.h"
|
||||||
|
|
||||||
|
namespace message
|
||||||
|
{
|
||||||
|
|
||||||
|
class Handler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Handler &getInstance();
|
||||||
|
|
||||||
|
// 0: 成功, -1: 失败, 1: 已经开启
|
||||||
|
int EnableLog();
|
||||||
|
int DisableLog();
|
||||||
|
int ListenPyq();
|
||||||
|
int UnListenPyq();
|
||||||
|
int ListenMsg();
|
||||||
|
int UnListenMsg();
|
||||||
|
|
||||||
|
MsgTypes_t GetMsgTypes();
|
||||||
|
|
||||||
|
bool isLoggingEnabled() const { return isLogging.load(); }
|
||||||
|
bool isMessageListening() const { return isListeningMsg.load(); }
|
||||||
|
bool isPyqListening() const { return isListeningPyq.load(); }
|
||||||
|
|
||||||
|
std::optional<WxMsg_t> popMessage();
|
||||||
|
std::condition_variable &getConditionVariable() { return cv_; };
|
||||||
|
std::mutex &getMutex() { return mutex_; };
|
||||||
|
|
||||||
|
bool rpc_get_msg_types(uint8_t *out, size_t *len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Handler();
|
||||||
|
~Handler();
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::condition_variable cv_;
|
||||||
|
std::queue<WxMsg_t> msgQueue_;
|
||||||
|
|
||||||
|
std::atomic<bool> isLogging { false };
|
||||||
|
std::atomic<bool> isListeningMsg { false };
|
||||||
|
std::atomic<bool> isListeningPyq { false };
|
||||||
|
|
||||||
|
using funcRecvMsg_t = QWORD (*)(QWORD, QWORD);
|
||||||
|
using funcWxLog_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||||
|
using funcRecvPyq_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||||
|
|
||||||
|
uint32_t *pLogLevel;
|
||||||
|
funcWxLog_t funcWxLog, realWxLog;
|
||||||
|
funcRecvMsg_t funcRecvMsg, realRecvMsg;
|
||||||
|
funcRecvPyq_t funcRecvPyq, realRecvPyq;
|
||||||
|
|
||||||
|
bool isMH_Initialized { false };
|
||||||
|
|
||||||
|
MH_STATUS InitializeHook();
|
||||||
|
MH_STATUS UninitializeHook();
|
||||||
|
|
||||||
|
static QWORD DispatchMsg(QWORD arg1, QWORD arg2);
|
||||||
|
static QWORD PrintWxLog(QWORD a1, QWORD a2, QWORD a3, QWORD a4, QWORD a5, QWORD a6, QWORD a7, QWORD a8, QWORD a9,
|
||||||
|
QWORD a10, QWORD a11, QWORD a12);
|
||||||
|
static QWORD DispatchPyq(QWORD arg1, QWORD arg2, QWORD arg3);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace message
|
366
WeChatFerry/spy/message_sender.cpp
Normal file
366
WeChatFerry/spy/message_sender.cpp
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
#include "message_sender.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "account_manager.h"
|
||||||
|
#include "database_executor.h"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "spy_types.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace message
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace OsSend = Offsets::Message::Send;
|
||||||
|
|
||||||
|
Sender &Sender::get_instance()
|
||||||
|
{
|
||||||
|
static Sender instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sender::Sender()
|
||||||
|
{
|
||||||
|
func_new_chat_msg = Spy::getFunction<New_t>(OsSend::INSTANCE);
|
||||||
|
func_free_chat_msg = Spy::getFunction<Free_t>(OsSend::FREE);
|
||||||
|
func_send_msg_mgr = Spy::getFunction<SendMsgMgr_t>(OsSend::MGR);
|
||||||
|
func_send_text = Spy::getFunction<SendText_t>(OsSend::TEXT);
|
||||||
|
func_send_image = Spy::getFunction<SendImage_t>(OsSend::IMAGE);
|
||||||
|
func_get_app_mgr = Spy::getFunction<GetAppMgr_t>(OsSend::APP_MGR);
|
||||||
|
func_send_file = Spy::getFunction<SendFile_t>(OsSend::FILE);
|
||||||
|
func_new_mmreader = Spy::getFunction<New_t>(OsSend::NEW_MM_READER);
|
||||||
|
func_free_mmreader = Spy::getFunction<Free_t>(OsSend::FREE_MM_READER);
|
||||||
|
func_send_rich_text = Spy::getFunction<SendRichText_t>(OsSend::RICH_TEXT);
|
||||||
|
func_send_pat = Spy::getFunction<SendPat_t>(OsSend::PAT);
|
||||||
|
func_forward = Spy::getFunction<Forward_t>(OsSend::FORWARD);
|
||||||
|
func_get_emotion_mgr = Spy::getFunction<GetEmotionMgr_t>(OsSend::EMOTION_MGR);
|
||||||
|
func_send_emotion = Spy::getFunction<SendEmotion_t>(OsSend::EMOTION);
|
||||||
|
func_send_xml = Spy::getFunction<SendXml_t>(OsSend::XML);
|
||||||
|
func_xml_buf_sign = Spy::getFunction<XmlBufSign_t>(OsSend::XML_BUF_SIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sender::send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids)
|
||||||
|
{
|
||||||
|
util::WxStringHolder<std::string> holderMsg(msg);
|
||||||
|
util::WxStringHolder<std::string> holderWxid(wxid);
|
||||||
|
|
||||||
|
auto wxAtWxids = util::parse_wxids(at_wxids).wxWxids;
|
||||||
|
QWORD pWxAtWxids = wxAtWxids.empty() ? 0 : reinterpret_cast<QWORD>(&wxAtWxids);
|
||||||
|
|
||||||
|
char buffer[1104] = { 0 };
|
||||||
|
func_send_msg_mgr();
|
||||||
|
func_send_text(reinterpret_cast<QWORD>(&buffer), &holderWxid.wx, &holderMsg.wx, pWxAtWxids, 1, 1, 0, 0);
|
||||||
|
func_free_chat_msg(reinterpret_cast<QWORD>(&buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sender::send_image(const std::string &wxid, const std::string &path)
|
||||||
|
{
|
||||||
|
util::WxStringHolder<std::string> holderWxid(wxid);
|
||||||
|
util::WxStringHolder<std::string> holderPath(path);
|
||||||
|
|
||||||
|
char msg[1192] = { 0 };
|
||||||
|
char msgTmp[1192] = { 0 };
|
||||||
|
QWORD *flag[10] = { 0 };
|
||||||
|
|
||||||
|
QWORD tmp1 = 1, tmp2 = 0, tmp3 = 0;
|
||||||
|
QWORD pMsgTmp = func_new_chat_msg((QWORD)(&msgTmp));
|
||||||
|
flag[0] = reinterpret_cast<QWORD *>(tmp1);
|
||||||
|
flag[1] = reinterpret_cast<QWORD *>(pMsgTmp);
|
||||||
|
flag[8] = &tmp2;
|
||||||
|
flag[9] = &tmp3;
|
||||||
|
|
||||||
|
QWORD pMsg = func_new_chat_msg((QWORD)(&msg));
|
||||||
|
QWORD sendMgr = func_send_msg_mgr();
|
||||||
|
func_send_image(sendMgr, pMsg, &holderWxid.wx, &holderPath.wx, reinterpret_cast<QWORD>(&flag));
|
||||||
|
|
||||||
|
func_free_chat_msg(pMsg);
|
||||||
|
func_free_chat_msg(pMsgTmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sender::send_file(const std::string &wxid, const std::string &path)
|
||||||
|
{
|
||||||
|
WxString *wxWxid = util::CreateWxString(wxid);
|
||||||
|
WxString *wxPath = util::CreateWxString(path);
|
||||||
|
if (!wxWxid || !wxPath) {
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *chat_msg = reinterpret_cast<char *>(util::AllocFromHeap(0x460));
|
||||||
|
if (!chat_msg) {
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD *tmp1 = util::AllocBuffer<QWORD>(4);
|
||||||
|
QWORD *tmp2 = util::AllocBuffer<QWORD>(4);
|
||||||
|
QWORD *tmp3 = util::AllocBuffer<QWORD>(4);
|
||||||
|
if (!tmp1 || !tmp2 || !tmp3) {
|
||||||
|
func_free_chat_msg(reinterpret_cast<QWORD>(chat_msg));
|
||||||
|
util::FreeBuffer(chat_msg);
|
||||||
|
util::FreeBuffer(tmp1);
|
||||||
|
util::FreeBuffer(tmp2);
|
||||||
|
util::FreeBuffer(tmp3);
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD app_mgr = func_get_app_mgr();
|
||||||
|
func_send_file(app_mgr, chat_msg, wxWxid, wxPath, 1, tmp1, 0, tmp2, 0, tmp3, 0, 0xC);
|
||||||
|
func_free_chat_msg(reinterpret_cast<QWORD>(chat_msg));
|
||||||
|
|
||||||
|
util::FreeBuffer(chat_msg);
|
||||||
|
util::FreeBuffer(tmp1);
|
||||||
|
util::FreeBuffer(tmp2);
|
||||||
|
util::FreeBuffer(tmp3);
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sender::send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type)
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
std::unique_ptr<char[]> buff(new char[0x500]());
|
||||||
|
std::unique_ptr<char[]> buff2(new char[0x500]());
|
||||||
|
char nullBuf[0x1C] = { 0 };
|
||||||
|
|
||||||
|
func_new_chat_msg(reinterpret_cast<QWORD>(buff.get()));
|
||||||
|
func_new_chat_msg(reinterpret_cast<QWORD>(buff2.get()));
|
||||||
|
|
||||||
|
QWORD sbuf[4] = { 0, 0, 0, 0 };
|
||||||
|
QWORD sign = func_xml_buf_sign(reinterpret_cast<QWORD>(buff2.get()), reinterpret_cast<QWORD>(sbuf), 0x1);
|
||||||
|
|
||||||
|
auto wxReceiver = new_wx_string(receiver);
|
||||||
|
auto wxXml = new_wx_string(xml);
|
||||||
|
auto wxPath = new_wx_string(path);
|
||||||
|
auto wxSender = new_wx_string(account::get_self_wxid());
|
||||||
|
|
||||||
|
func_send_xml(reinterpret_cast<QWORD>(buff.get()), reinterpret_cast<QWORD>(wxSender.get()),
|
||||||
|
reinterpret_cast<QWORD>(wxReceiver.get()), reinterpret_cast<QWORD>(wxXml.get()),
|
||||||
|
reinterpret_cast<QWORD>(wxPath.get()), reinterpret_cast<QWORD>(nullBuf), type, 0x4, sign,
|
||||||
|
reinterpret_cast<QWORD>(buff2.get()));
|
||||||
|
|
||||||
|
func_free_chat_msg(reinterpret_cast<QWORD>(buff.get()));
|
||||||
|
func_free_chat_msg(reinterpret_cast<QWORD>(buff2.get()));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sender::send_emotion(const std::string &wxid, const std::string &path)
|
||||||
|
{
|
||||||
|
WxString *wxWxid = util::CreateWxString(wxid);
|
||||||
|
WxString *wxPath = util::CreateWxString(path);
|
||||||
|
QWORD *buff = util::AllocBuffer<QWORD>(8);
|
||||||
|
if (!wxWxid || !wxPath || !buff) {
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
util::FreeBuffer(buff);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD mgr = func_get_emotion_mgr();
|
||||||
|
func_send_emotion(mgr, wxPath, buff, wxWxid, 2, buff, 0, buff);
|
||||||
|
util::FreeBuffer(buff);
|
||||||
|
util::FreeWxString(wxWxid);
|
||||||
|
util::FreeWxString(wxPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Sender::send_rich_text(const RichText &rt)
|
||||||
|
{
|
||||||
|
QWORD status = -1;
|
||||||
|
|
||||||
|
char *buff = util::AllocBuffer<char>(0x3F0);
|
||||||
|
WxString *pReceiver = util::CreateWxString(rt.receiver);
|
||||||
|
WxString *pTitle = util::CreateWxString(rt.title);
|
||||||
|
WxString *pUrl = util::CreateWxString(rt.url);
|
||||||
|
WxString *pThumburl = util::CreateWxString(rt.thumburl);
|
||||||
|
WxString *pDigest = util::CreateWxString(rt.digest);
|
||||||
|
WxString *pAccount = util::CreateWxString(rt.account);
|
||||||
|
WxString *pName = util::CreateWxString(rt.name);
|
||||||
|
if (!buff || !pReceiver || !pTitle || !pUrl || !pThumburl || !pDigest || !pAccount || !pName) {
|
||||||
|
util::FreeWxString(pReceiver);
|
||||||
|
util::FreeWxString(pTitle);
|
||||||
|
util::FreeWxString(pUrl);
|
||||||
|
util::FreeWxString(pThumburl);
|
||||||
|
util::FreeWxString(pDigest);
|
||||||
|
util::FreeWxString(pAccount);
|
||||||
|
util::FreeWxString(pName);
|
||||||
|
util::FreeBuffer(buff);
|
||||||
|
LOG_ERROR("Out of Memory...");
|
||||||
|
return static_cast<int>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
func_new_mmreader(reinterpret_cast<QWORD>(buff));
|
||||||
|
memcpy(buff + 0x8, pTitle, sizeof(WxString));
|
||||||
|
memcpy(buff + 0x48, pUrl, sizeof(WxString));
|
||||||
|
memcpy(buff + 0xB0, pThumburl, sizeof(WxString));
|
||||||
|
memcpy(buff + 0xF0, pDigest, sizeof(WxString));
|
||||||
|
memcpy(buff + 0x2C0, pAccount, sizeof(WxString));
|
||||||
|
memcpy(buff + 0x2E0, pName, sizeof(WxString));
|
||||||
|
|
||||||
|
status = func_send_rich_text(func_get_app_mgr(), pReceiver, buff);
|
||||||
|
func_free_mmreader(reinterpret_cast<QWORD>(buff));
|
||||||
|
|
||||||
|
// TODO: 验证是否有内存泄露
|
||||||
|
// util::FreeWxString(pReceiver);
|
||||||
|
// util::FreeWxString(pTitle);
|
||||||
|
// util::FreeWxString(pUrl);
|
||||||
|
// util::FreeWxString(pThumburl);
|
||||||
|
// util::FreeWxString(pDigest);
|
||||||
|
// util::FreeWxString(pAccount);
|
||||||
|
// util::FreeWxString(pName);
|
||||||
|
util::FreeBuffer(buff);
|
||||||
|
|
||||||
|
return static_cast<int>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Sender::send_pat(const std::string &roomid, const std::string &wxid)
|
||||||
|
{
|
||||||
|
QWORD status = -1;
|
||||||
|
|
||||||
|
util::WxStringHolder<std::string> holderRoom(roomid);
|
||||||
|
util::WxStringHolder<std::string> holderWxid(wxid);
|
||||||
|
|
||||||
|
status = func_send_pat(&holderRoom.wx, &holderWxid.wx);
|
||||||
|
|
||||||
|
return static_cast<int>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Sender::forward(QWORD msgid, const std::string &receiver)
|
||||||
|
{
|
||||||
|
uint32_t dbIdx = 0;
|
||||||
|
QWORD localId = 0;
|
||||||
|
|
||||||
|
if (db::get_local_id_and_dbidx(msgid, &localId, &dbIdx) != 0) {
|
||||||
|
LOG_ERROR("Failed to get localId, Please check id: {}", msgid);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER l;
|
||||||
|
l.HighPart = dbIdx;
|
||||||
|
l.LowPart = static_cast<DWORD>(localId);
|
||||||
|
|
||||||
|
WxString *pReceiver = util::CreateWxString(receiver);
|
||||||
|
|
||||||
|
return static_cast<int>(func_forward(pReceiver, l.QuadPart, 0x4, 0x0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPC 方法
|
||||||
|
bool Sender::rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_SEND_TXT>(out, len, [&](Response &rsp) {
|
||||||
|
if (text.msg == nullptr || text.receiver == nullptr || strlen(text.msg) == 0 || strlen(text.receiver) == 0) {
|
||||||
|
LOG_ERROR("Empty message or receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
send_text(text.receiver, text.msg, text.aters ? text.aters : "");
|
||||||
|
rsp.msg.status = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
std::string path(file.path);
|
||||||
|
std::string receiver(file.receiver);
|
||||||
|
return fill_response<Functions_FUNC_SEND_IMG>(out, len, [&](Response &rsp) {
|
||||||
|
if (path.empty() || receiver.empty()) {
|
||||||
|
LOG_ERROR("Empty path or receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
send_image(receiver, path);
|
||||||
|
rsp.msg.status = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
std::string path(file.path);
|
||||||
|
std::string receiver(file.receiver);
|
||||||
|
return fill_response<Functions_FUNC_SEND_FILE>(out, len, [&](Response &rsp) {
|
||||||
|
if (path.empty() || receiver.empty()) {
|
||||||
|
LOG_ERROR("Empty path or receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
send_file(receiver, path);
|
||||||
|
rsp.msg.status = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
std::string path(file.path);
|
||||||
|
std::string receiver(file.receiver);
|
||||||
|
return fill_response<Functions_FUNC_SEND_EMOTION>(out, len, [&](Response &rsp) {
|
||||||
|
if (path.empty() || receiver.empty()) {
|
||||||
|
LOG_ERROR("Empty path or receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
send_emotion(receiver, path);
|
||||||
|
rsp.msg.status = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_xml(const XmlMsg &xml, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_SEND_XML>(out, len, [&](Response &rsp) {
|
||||||
|
if (xml.content == nullptr || xml.receiver == nullptr) {
|
||||||
|
LOG_ERROR("Empty content or receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
// send_xml(xml.receiver, xml.content, xml.path, xml.type);
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_SEND_RICH_TXT>(out, len, [&](Response &rsp) {
|
||||||
|
if (rt.receiver == nullptr) {
|
||||||
|
LOG_ERROR("Empty receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
rsp.msg.status = send_rich_text(rt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
std::string wxid(pat.wxid);
|
||||||
|
std::string roomid(pat.roomid);
|
||||||
|
return fill_response<Functions_FUNC_SEND_PAT_MSG>(out, len, [&](Response &rsp) {
|
||||||
|
if (roomid.empty() || wxid.empty()) {
|
||||||
|
LOG_ERROR("Empty roomid or wxid.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
rsp.msg.status = send_pat(roomid, wxid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sender::rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
uint64_t msgid = fm.id;
|
||||||
|
std::string receiver(fm.receiver);
|
||||||
|
return fill_response<Functions_FUNC_FORWARD_MSG>(out, len, [&](Response &rsp) {
|
||||||
|
if (receiver.empty()) {
|
||||||
|
LOG_ERROR("Empty receiver.");
|
||||||
|
rsp.msg.status = -1;
|
||||||
|
} else {
|
||||||
|
rsp.msg.status = forward(msgid, receiver);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
81
WeChatFerry/spy/message_sender.h
Normal file
81
WeChatFerry/spy/message_sender.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "wcf.pb.h"
|
||||||
|
|
||||||
|
#include "spy_types.h"
|
||||||
|
|
||||||
|
namespace message
|
||||||
|
{
|
||||||
|
|
||||||
|
class Sender
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Sender &get_instance();
|
||||||
|
|
||||||
|
void send_text(const std::string &wxid, const std::string &msg, const std::string &at_wxids = "");
|
||||||
|
void send_image(const std::string &wxid, const std::string &path);
|
||||||
|
void send_file(const std::string &wxid, const std::string &path);
|
||||||
|
void send_xml(const std::string &receiver, const std::string &xml, const std::string &path, uint64_t type);
|
||||||
|
void send_emotion(const std::string &wxid, const std::string &path);
|
||||||
|
int send_rich_text(const RichText &rt);
|
||||||
|
int send_pat(const std::string &roomid, const std::string &wxid);
|
||||||
|
int forward(uint64_t msgid, const std::string &receiver);
|
||||||
|
|
||||||
|
// RPC 方法
|
||||||
|
bool rpc_send_text(const TextMsg &text, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_image(const PathMsg &file, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_file(const PathMsg &file, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_emotion(const PathMsg &file, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_xml(const XmlMsg &rt, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_rich_text(const RichText &rt, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_send_pat(const PatMsg &pat, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_forward(const ForwardMsg &fm, uint8_t *out, size_t *len);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Sender();
|
||||||
|
~Sender() = default;
|
||||||
|
|
||||||
|
Sender(const Sender &) = delete;
|
||||||
|
Sender &operator=(const Sender &) = delete;
|
||||||
|
|
||||||
|
using New_t = QWORD (*)(QWORD);
|
||||||
|
using Free_t = QWORD (*)(QWORD);
|
||||||
|
using SendMsgMgr_t = QWORD (*)();
|
||||||
|
using GetAppMgr_t = QWORD (*)();
|
||||||
|
using SendText_t = QWORD (*)(QWORD, WxString *, WxString *, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||||
|
using SendImage_t = QWORD (*)(QWORD, QWORD, WxString *, WxString *, QWORD);
|
||||||
|
using SendFile_t = QWORD (*)(QWORD, char *, WxString *, WxString *, QWORD, QWORD *, QWORD, QWORD *, QWORD, QWORD *,
|
||||||
|
QWORD, QWORD);
|
||||||
|
using SendRichText_t = QWORD (*)(QWORD, WxString *, char *);
|
||||||
|
using SendPat_t = QWORD (*)(WxString *, WxString *);
|
||||||
|
using Forward_t = QWORD (*)(WxString *, QWORD, QWORD, QWORD);
|
||||||
|
using GetEmotionMgr_t = QWORD (*)();
|
||||||
|
using SendEmotion_t = QWORD (*)(QWORD, WxString *, QWORD *, WxString *, QWORD, QWORD *, QWORD, QWORD *);
|
||||||
|
using XmlBufSign_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||||
|
using SendXml_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||||
|
|
||||||
|
New_t func_new_chat_msg;
|
||||||
|
Free_t func_free_chat_msg;
|
||||||
|
SendMsgMgr_t func_send_msg_mgr;
|
||||||
|
GetAppMgr_t func_get_app_mgr;
|
||||||
|
SendText_t func_send_text;
|
||||||
|
SendImage_t func_send_image;
|
||||||
|
SendFile_t func_send_file;
|
||||||
|
New_t func_new_mmreader;
|
||||||
|
Free_t func_free_mmreader;
|
||||||
|
SendRichText_t func_send_rich_text;
|
||||||
|
SendPat_t func_send_pat;
|
||||||
|
Forward_t func_forward;
|
||||||
|
GetEmotionMgr_t func_get_emotion_mgr;
|
||||||
|
SendEmotion_t func_send_emotion;
|
||||||
|
XmlBufSign_t func_xml_buf_sign;
|
||||||
|
SendXml_t func_send_xml;
|
||||||
|
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const char *str);
|
||||||
|
std::unique_ptr<WxString> new_wx_string(const std::string &str);
|
||||||
|
};
|
||||||
|
}
|
421
WeChatFerry/spy/misc_manager.cpp
Normal file
421
WeChatFerry/spy/misc_manager.cpp
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
#pragma warning(disable : 4244)
|
||||||
|
#include "misc_manager.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "framework.h"
|
||||||
|
|
||||||
|
#include "codec.h"
|
||||||
|
#include "database_executor.h"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "message_handler.h"
|
||||||
|
#include "offsets.h"
|
||||||
|
#include "rpc_helper.h"
|
||||||
|
#include "spy.h"
|
||||||
|
#include "spy_types.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace misc
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
namespace OsMisc = Offsets::Misc;
|
||||||
|
namespace OsSns = Offsets::Misc::Sns;
|
||||||
|
|
||||||
|
using get_sns_data_mgr_t = QWORD (*)();
|
||||||
|
using get_sns_timeline_mgr_t = QWORD (*)();
|
||||||
|
using get_sns_first_page_t = QWORD (*)(QWORD, QWORD, QWORD);
|
||||||
|
using get_sns_next_page_scene_t = QWORD (*)(QWORD, QWORD);
|
||||||
|
using get_chat_mgr_t = QWORD (*)();
|
||||||
|
using new_chat_msg_t = QWORD (*)(char *);
|
||||||
|
using free_chat_msg_t = QWORD (*)(QWORD);
|
||||||
|
using get_pre_download_mgr_t = QWORD (*)();
|
||||||
|
using get_mgr_by_prefix_localid_t = QWORD (*)(QWORD, QWORD);
|
||||||
|
using push_attach_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD);
|
||||||
|
using get_ocr_manager_t = QWORD (*)();
|
||||||
|
using do_ocr_task_t = QWORD (*)(QWORD, QWORD, QWORD, QWORD, QWORD, QWORD);
|
||||||
|
using get_qr_code_mgr_t = QWORD (*)();
|
||||||
|
|
||||||
|
struct ImagePattern {
|
||||||
|
uint8_t header1_candidate;
|
||||||
|
uint8_t header2_expected;
|
||||||
|
const char *extension;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr ImagePattern patterns[] = {
|
||||||
|
{ 0x89, 0x50, ".png" },
|
||||||
|
{ 0xFF, 0xD8, ".jpg" },
|
||||||
|
{ 0x47, 0x49, ".gif" },
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::string detect_image_extension(uint8_t header1, uint8_t header2, uint8_t *key)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (const auto &pat : patterns) {
|
||||||
|
*key = pat.header1_candidate ^ header1;
|
||||||
|
if ((pat.header2_expected ^ *key) == header2) {
|
||||||
|
return pat.extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_ERROR("未知类型:{:02x} {:02x}", header1, header2);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decrypt_image(const fs::path &src, const fs::path &dst_dir)
|
||||||
|
{
|
||||||
|
if (!fs::exists(src)) {
|
||||||
|
LOG_ERROR("文件不存在: {}", src.string());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream in(src, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
LOG_ERROR("无法打开文件: {}", src.string());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> buffer(std::istreambuf_iterator<char>(in), {});
|
||||||
|
if (buffer.size() < 2) return "";
|
||||||
|
|
||||||
|
uint8_t key = 0x00;
|
||||||
|
auto ext = detect_image_extension(buffer[0], buffer[1], &key);
|
||||||
|
if (ext.empty()) {
|
||||||
|
LOG_ERROR("无法检测文件类型.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::for_each(buffer.begin(), buffer.end(), [key](char &c) { c ^= key; });
|
||||||
|
|
||||||
|
fs::path dst_path = dst_dir / (src.stem().string() + ext);
|
||||||
|
if (!fs::exists(dst_dir)) fs::create_directories(dst_dir);
|
||||||
|
|
||||||
|
std::ofstream out(dst_path, std::ios::binary);
|
||||||
|
if (!out) {
|
||||||
|
LOG_ERROR("写入文件失败: {}", dst_path.generic_string());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(buffer.data(), buffer.size());
|
||||||
|
return dst_path.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_first_page()
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
|
||||||
|
auto GetSNSDataMgr = Spy::getFunction<get_sns_data_mgr_t>(OsSns::DATA_MGR);
|
||||||
|
auto GetSNSFirstPage = Spy::getFunction<get_sns_first_page_t>(OsSns::FIRST);
|
||||||
|
|
||||||
|
QWORD buff[16] = { 0 };
|
||||||
|
QWORD mgr = GetSNSDataMgr();
|
||||||
|
status = (int)GetSNSFirstPage(mgr, (QWORD)&buff, 1);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_next_page(QWORD id)
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
|
||||||
|
auto GetSnsTimeLineMgr = Spy::getFunction<get_sns_timeline_mgr_t>(OsSns::TIMELINE);
|
||||||
|
auto GetSNSNextPageScene = Spy::getFunction<get_sns_next_page_scene_t>(OsSns::NEXT);
|
||||||
|
|
||||||
|
QWORD mgr = GetSnsTimeLineMgr();
|
||||||
|
status = (int)GetSNSNextPageScene(mgr, id);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int refresh_pyq(uint64_t id)
|
||||||
|
{
|
||||||
|
auto &msgHandler = message::Handler::getInstance();
|
||||||
|
if (!msgHandler.isPyqListening()) {
|
||||||
|
LOG_ERROR("没有启动朋友圈消息接收,参考:enable_receiving_msg");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == 0) {
|
||||||
|
return get_first_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_next_page(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* 都说我不写注释,写一下吧
|
||||||
|
* 其实也没啥好写的,就是下载资源
|
||||||
|
* 主要介绍一下几个参数:
|
||||||
|
* id:好理解,消息 id
|
||||||
|
* thumb:图片或者视频的缩略图路径;如果是视频,后缀为 mp4 后就是存在路径了
|
||||||
|
* extra:图片、文件的路径
|
||||||
|
*******************************************************************************/
|
||||||
|
int download_attachment(uint64_t id, const fs::path &thumb, const fs::path &extra)
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
QWORD localId;
|
||||||
|
uint32_t dbIdx;
|
||||||
|
|
||||||
|
if (fs::exists(extra)) { // 第一道,不重复下载。TODO: 通过文件大小来判断
|
||||||
|
LOG_WARN("文件已存在:{}", extra.generic_string());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) {
|
||||||
|
LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id));
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NewChatMsg = Spy::getFunction<new_chat_msg_t>(OsMisc::INSATNCE);
|
||||||
|
auto FreeChatMsg = Spy::getFunction<free_chat_msg_t>(OsMisc::FREE);
|
||||||
|
auto GetChatMgr = Spy::getFunction<get_chat_mgr_t>(OsMisc::CHAT_MGR);
|
||||||
|
auto GetPreDownLoadMgr = Spy::getFunction<get_pre_download_mgr_t>(OsMisc::PRE_DOWNLOAD_MGR);
|
||||||
|
auto PushAttachTask = Spy::getFunction<push_attach_task_t>(OsMisc::PUSH_ATTACH_TASK);
|
||||||
|
auto GetMgrByPrefixLocalId = Spy::getFunction<get_mgr_by_prefix_localid_t>(OsMisc::PRE_LOCAL_ID_MGR);
|
||||||
|
|
||||||
|
LARGE_INTEGER l;
|
||||||
|
l.HighPart = dbIdx;
|
||||||
|
l.LowPart = (DWORD)localId;
|
||||||
|
|
||||||
|
char *buff = util::AllocBuffer<char>(0x460);
|
||||||
|
if (buff == nullptr) {
|
||||||
|
LOG_ERROR("申请内存失败.");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWORD pChatMsg = NewChatMsg(buff);
|
||||||
|
GetChatMgr();
|
||||||
|
GetMgrByPrefixLocalId(l.QuadPart, pChatMsg);
|
||||||
|
QWORD type = util::get_qword(reinterpret_cast<QWORD>(buff) + 0x38);
|
||||||
|
|
||||||
|
fs::path save_path, thumb_path;
|
||||||
|
switch (type) {
|
||||||
|
case 0x03: { // Image: extra
|
||||||
|
save_path = extra;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x3E:
|
||||||
|
case 0x2B: { // Video: thumb
|
||||||
|
thumb_path = thumb;
|
||||||
|
save_path = fs::path(thumb).replace_extension("mp4");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x31: { // File: extra
|
||||||
|
save_path = extra;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR("不支持的文件类型: {}", type);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs::exists(save_path)) { // 不重复下载。TODO: 通过文件大小来判断
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("保存路径: {}", save_path.generic_string());
|
||||||
|
// 创建父目录,由于路径来源于微信,不做检查
|
||||||
|
fs::create_directory(save_path.parent_path());
|
||||||
|
|
||||||
|
int temp = 1;
|
||||||
|
auto wx_save_path = util::CreateWxString(save_path.make_preferred().string());
|
||||||
|
auto wx_thumb_path = util::CreateWxString(thumb_path.make_preferred().string());
|
||||||
|
|
||||||
|
memcpy(&buff[0x280], wx_thumb_path, sizeof(WxString));
|
||||||
|
memcpy(&buff[0x2A0], wx_save_path, sizeof(WxString));
|
||||||
|
memcpy(&buff[0x40C], &temp, sizeof(temp));
|
||||||
|
|
||||||
|
QWORD mgr = GetPreDownLoadMgr();
|
||||||
|
status = (int)PushAttachTask(mgr, pChatMsg, 0, 1);
|
||||||
|
FreeChatMsg(pChatMsg);
|
||||||
|
util::FreeBuffer(buff);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_audio(uint64_t id, const fs::path &dir)
|
||||||
|
{
|
||||||
|
if (!fs::exists(dir)) fs::create_directories(dir);
|
||||||
|
|
||||||
|
fs::path mp3path = dir / (std::to_string(id) + ".mp3");
|
||||||
|
if (fs::exists(mp3path)) return mp3path.generic_string();
|
||||||
|
|
||||||
|
auto silk = db::get_audio_data(id);
|
||||||
|
if (silk.empty()) {
|
||||||
|
LOG_ERROR("没有获取到语音数据.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Silk2Mp3(silk, mp3path.generic_string(), 24000);
|
||||||
|
return mp3path.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr)
|
||||||
|
{
|
||||||
|
if (!fs::exists(dir)) fs::create_directories(dir);
|
||||||
|
|
||||||
|
fs::path pcmpath = dir / (std::to_string(id) + ".pcm");
|
||||||
|
if (fs::exists(pcmpath)) return pcmpath.generic_string();
|
||||||
|
|
||||||
|
auto silk = db::get_audio_data(id);
|
||||||
|
if (silk.empty()) {
|
||||||
|
LOG_ERROR("没有获取到语音数据.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> pcm;
|
||||||
|
SilkDecode(silk, pcm, sr);
|
||||||
|
|
||||||
|
std::ofstream out(pcmpath, std::ios::binary);
|
||||||
|
if (!out) {
|
||||||
|
LOG_ERROR("创建文件失败: {}", pcmpath.generic_string());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(reinterpret_cast<char *>(pcm.data()), pcm.size());
|
||||||
|
return pcmpath.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
OcrResult_t get_ocr_result(const fs::path &path)
|
||||||
|
{
|
||||||
|
OcrResult_t ret = { -1, "" };
|
||||||
|
#if 0 // 参数没调好,会抛异常,看看有没有好心人来修复
|
||||||
|
if (!fs::exists(path)) {
|
||||||
|
LOG_ERROR("Can not find: {}", path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_ocr_manager_t GetOCRManager = (get_ocr_manager_t)(g_WeChatWinDllAddr + 0x1D6C3C0);
|
||||||
|
do_ocr_task_t DoOCRTask = (do_ocr_task_t)(g_WeChatWinDllAddr + 0x2D10BC0);
|
||||||
|
|
||||||
|
QWORD unk1 = 0, unk2 = 0, unused = 0;
|
||||||
|
QWORD *pUnk1 = &unk1;
|
||||||
|
QWORD *pUnk2 = &unk2;
|
||||||
|
// 路径分隔符有要求,必须为 `\`
|
||||||
|
wstring wsPath = util::s2w(fs::path(path).make_preferred().string());
|
||||||
|
WxString wxPath(wsPath);
|
||||||
|
vector<QWORD> *pv = (vector<QWORD> *)HeapAlloc(GetProcessHeap(), 0, 0x20);
|
||||||
|
RawVector_t *pRv = (RawVector_t *)pv;
|
||||||
|
pRv->finish = pRv->start;
|
||||||
|
char buff[0x98] = { 0 };
|
||||||
|
memcpy(buff, &pRv->start, sizeof(QWORD));
|
||||||
|
|
||||||
|
QWORD mgr = GetOCRManager();
|
||||||
|
ret.status = (int)DoOCRTask(mgr, (QWORD)&wxPath, unused, (QWORD)buff, (QWORD)&pUnk1, (QWORD)&pUnk2);
|
||||||
|
|
||||||
|
QWORD count = util::get_qword(buff + 0x8);
|
||||||
|
if (count > 0) {
|
||||||
|
QWORD header = util::get_qword(buff);
|
||||||
|
for (QWORD i = 0; i < count; i++) {
|
||||||
|
QWORD content = util::get_qword(header);
|
||||||
|
ret.result += util::w2s(get_pp_wstring(content + 0x28));
|
||||||
|
ret.result += "\n";
|
||||||
|
header = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int revoke_message(uint64_t id)
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
#if 0 // 这个挺鸡肋的,因为自己发的消息没法直接获得 msgid,就这样吧
|
||||||
|
QWORD localId;
|
||||||
|
uint32_t dbIdx;
|
||||||
|
if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) {
|
||||||
|
LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id));
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_login_url()
|
||||||
|
{
|
||||||
|
std::string uri;
|
||||||
|
auto get_qr_code_mgr = Spy::getFunction<get_qr_code_mgr_t>(OsMisc::QR_CODE);
|
||||||
|
|
||||||
|
uint64_t addr = get_qr_code_mgr() + 0x68;
|
||||||
|
uint64_t len = *(uint64_t *)(addr + 0x10);
|
||||||
|
if (len == 0) {
|
||||||
|
LOG_ERROR("获取二维码失败.");
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(uint64_t *)(addr + 0x18) == 0xF) {
|
||||||
|
uri = std::string((char *)addr, len);
|
||||||
|
} else {
|
||||||
|
uri = std::string(*(char **)(addr), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "http://weixin.qq.com/x/" + uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid)
|
||||||
|
{
|
||||||
|
LOG_ERROR("技术太菜,实现不了。");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_GET_AUDIO_MSG>(
|
||||||
|
out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_audio(am.id, am.dir).c_str(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_pcm_audio(uint64_t id, const fs::path &dir, int32_t sr, uint8_t *out, size_t *len) { return false; }
|
||||||
|
|
||||||
|
bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_DECRYPT_IMAGE>(
|
||||||
|
out, len, [&](Response &rsp) { rsp.msg.str = (char *)decrypt_image(dec.src, dec.dst).c_str(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_login_url(uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_REFRESH_QRCODE>(
|
||||||
|
out, len, [&](Response &rsp) { rsp.msg.str = (char *)get_login_url().c_str(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_REFRESH_PYQ>(out, len,
|
||||||
|
[&](Response &rsp) { rsp.msg.status = refresh_pyq(id); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
int status = -1;
|
||||||
|
if (att.thumb || att.extra) {
|
||||||
|
std::string thumb = att.thumb ? att.thumb : "";
|
||||||
|
std::string extra = att.extra ? att.extra : "";
|
||||||
|
status = download_attachment(att.id, thumb, extra);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("文件地址不能全为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fill_response<Functions_FUNC_DOWNLOAD_ATTACH>(out, len, [&](Response &rsp) { rsp.msg.status = status; });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_REVOKE_MSG>(out, len,
|
||||||
|
[&](Response &rsp) { rsp.msg.status = revoke_message(id); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_get_ocr_result(const fs::path &path, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
auto ret = get_ocr_result(path);
|
||||||
|
return fill_response<Functions_FUNC_EXEC_OCR>(out, len, [&](Response &rsp) {
|
||||||
|
rsp.msg.ocr.status = ret.status;
|
||||||
|
rsp.msg.ocr.result = (char *)ret.result.c_str();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len)
|
||||||
|
{
|
||||||
|
return fill_response<Functions_FUNC_RECV_TRANSFER>(
|
||||||
|
out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); });
|
||||||
|
}
|
||||||
|
} // namespace misc
|
40
WeChatFerry/spy/misc_manager.h
Normal file
40
WeChatFerry/spy/misc_manager.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "wcf.pb.h"
|
||||||
|
|
||||||
|
#include "pb_types.h"
|
||||||
|
|
||||||
|
namespace misc
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string get_audio(uint64_t id, const std::filesystem::path &dir);
|
||||||
|
std::string get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr);
|
||||||
|
std::string decrypt_image(const std::filesystem::path &src, const std::filesystem::path &dst);
|
||||||
|
std::string get_login_url();
|
||||||
|
|
||||||
|
int refresh_pyq(uint64_t id);
|
||||||
|
int download_attachment(uint64_t id, const std::filesystem::path &thumb, const std::filesystem::path &extra);
|
||||||
|
int revoke_message(uint64_t id);
|
||||||
|
|
||||||
|
OcrResult_t get_ocr_result(const std::filesystem::path &path);
|
||||||
|
int receive_transfer(const std::string &wxid, const std::string &transferid, const std::string &transactionid);
|
||||||
|
|
||||||
|
// RPC
|
||||||
|
// clang-format off
|
||||||
|
bool rpc_get_audio(const AudioMsg &am, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_get_pcm_audio(uint64_t id, const std::filesystem::path &dir, int32_t sr, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_decrypt_image(const DecPath &dec, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_get_login_url(uint8_t *out, size_t *len);
|
||||||
|
bool rpc_refresh_pyq(uint64_t id, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_get_ocr_result(const std::filesystem::path &path, uint8_t *out, size_t *len);
|
||||||
|
bool rpc_receive_transfer(const Transfer &tf, uint8_t *out, size_t *len);
|
||||||
|
// clang-format on
|
||||||
|
} // namespace misc
|
149
WeChatFerry/spy/offsets.h
Normal file
149
WeChatFerry/spy/offsets.h
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Offsets
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace Account
|
||||||
|
{
|
||||||
|
constexpr uint64_t SERVICE = 0x1B58B50; // 账户服务
|
||||||
|
constexpr uint64_t PATH = 0x25E9090; // 数据路径
|
||||||
|
constexpr uint64_t WXID = 0x80; // WXID
|
||||||
|
constexpr uint64_t NAME = 0x1E8; // 昵称
|
||||||
|
constexpr uint64_t MOBILE = 0x128; // 手机号
|
||||||
|
constexpr uint64_t LOGIN = 0x7F8; // 登录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Chatroom
|
||||||
|
{
|
||||||
|
constexpr uint64_t MGR = 0x1B86F60;
|
||||||
|
constexpr uint64_t DEL = 0x2158830;
|
||||||
|
constexpr uint64_t ADD = 0x21581F0;
|
||||||
|
constexpr uint64_t INV = 0x2157BD0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Contact
|
||||||
|
{
|
||||||
|
constexpr uint64_t MGR = 0x1B44B20;
|
||||||
|
constexpr uint64_t LIST = 0x21A1E00;
|
||||||
|
constexpr uint64_t BIN = 0x200;
|
||||||
|
constexpr uint64_t BIN_LEN = 0x208;
|
||||||
|
constexpr uint64_t WXID = 0x10;
|
||||||
|
constexpr uint64_t CODE = 0x30;
|
||||||
|
constexpr uint64_t REMARK = 0x80;
|
||||||
|
constexpr uint64_t NAME = 0xA0;
|
||||||
|
constexpr uint64_t GENDER = 0x0E;
|
||||||
|
constexpr uint64_t STEP = 0x6A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Db
|
||||||
|
{
|
||||||
|
constexpr uint64_t INSTANCE = 0x59226C8; // 数据库实例地址
|
||||||
|
constexpr uint64_t MSG_I = 0x5980420; // MSGi.db & MediaMsgi.db
|
||||||
|
constexpr uint64_t MICROMSG = 0xB8;
|
||||||
|
constexpr uint64_t CHAT_MSG = 0x2C8;
|
||||||
|
constexpr uint64_t MISC = 0x5F0;
|
||||||
|
constexpr uint64_t EMOTION = 0x15F0;
|
||||||
|
constexpr uint64_t MEDIA = 0xF48;
|
||||||
|
constexpr uint64_t BIZCHAT_MSG = 0x1AC0;
|
||||||
|
constexpr uint64_t FUNCTION_MSG = 0x1B98;
|
||||||
|
constexpr uint64_t NAME = 0x28;
|
||||||
|
|
||||||
|
// SQLITE3
|
||||||
|
constexpr uint64_t EXEC = 0x3A76430;
|
||||||
|
// constexpr uint64_t BACKUP_INIT = EXEC - 0x1D113E0;
|
||||||
|
constexpr uint64_t PREPARE = EXEC + 0x7CB0;
|
||||||
|
// constexpr uint64_t OPEN = EXEC - 0x1CA2430;
|
||||||
|
// constexpr uint64_t BACKUP_STEP = EXEC - 0x1D110A0;
|
||||||
|
// constexpr uint64_t BACKUP_REMAINING = EXEC - 0x1D10880;
|
||||||
|
// constexpr uint64_t BACKUP_PAGECOUNT = EXEC - 0x1D10890;
|
||||||
|
// constexpr uint64_t BACKUP_FINISH = EXEC - 0x1D10940;
|
||||||
|
// constexpr uint64_t SLEEP = EXEC - 0x1CA1BB0;
|
||||||
|
// constexpr uint64_t ERRCODE = EXEC - 0x1CA3770;
|
||||||
|
// constexpr uint64_t CLOSE = EXEC - 0x1CA4FD0;
|
||||||
|
constexpr uint64_t STEP = EXEC - 0x3C000;
|
||||||
|
constexpr uint64_t COLUMN_COUNT = EXEC - 0x3B7E0;
|
||||||
|
constexpr uint64_t COLUMN_NAME = EXEC - 0x3ADE0;
|
||||||
|
constexpr uint64_t COLUMN_TYPE = EXEC - 0x3AF90;
|
||||||
|
constexpr uint64_t COLUMN_BLOB = EXEC - 0x3B7B0;
|
||||||
|
constexpr uint64_t COLUMN_BYTES = EXEC - 0x3B6C0;
|
||||||
|
constexpr uint64_t FINALIZE = EXEC - 0x3CF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Message
|
||||||
|
{
|
||||||
|
namespace Log
|
||||||
|
{
|
||||||
|
constexpr uint64_t LEVEL = 0x56E4244; // 日志级别
|
||||||
|
constexpr uint64_t CALL = 0x261B890; // 日志函数
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Receive
|
||||||
|
{
|
||||||
|
constexpr uint64_t CALL = 0x2141E80; // 接收消息 Call
|
||||||
|
constexpr uint64_t ID = 0x30; // 消息 ID
|
||||||
|
constexpr uint64_t TYPE = 0x38; // 消息类型
|
||||||
|
constexpr uint64_t SELF = 0x3C; // 消息是否来自自己
|
||||||
|
constexpr uint64_t TIMESTAMP = 0x44; // 消息时间戳
|
||||||
|
constexpr uint64_t ROOMID = 0x48; // 群聊 ID(或者发送者 wxid)
|
||||||
|
constexpr uint64_t CONTENT = 0x88; // 消息内容
|
||||||
|
constexpr uint64_t WXID = 0x240; // 发送者 wxid
|
||||||
|
constexpr uint64_t SIGN = 0x260; // 消息签名
|
||||||
|
constexpr uint64_t THUMB = 0x280; // 缩略图路径
|
||||||
|
constexpr uint64_t EXTRA = 0x2A0; // 原图路径
|
||||||
|
constexpr uint64_t XML = 0x308; // 消息 XML
|
||||||
|
|
||||||
|
constexpr uint64_t PYQ_CALL = 0x2E56080; // 接收朋友圈 Call
|
||||||
|
constexpr uint64_t PYQ_START = 0x30; // 开始地址
|
||||||
|
constexpr uint64_t PYQ_END = 0x38; // 结束地址
|
||||||
|
constexpr uint64_t PYQ_SENDER = 0x18; // 发布者
|
||||||
|
constexpr uint64_t PYQ_TS = 0x38; // 时间戳
|
||||||
|
constexpr uint64_t PYQ_CONTENT = 0x48; // 文本内容
|
||||||
|
constexpr uint64_t PYQ_XML = 0x9B8; // 其他内容
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Send
|
||||||
|
{
|
||||||
|
constexpr uint64_t MGR = 0x1B57350;
|
||||||
|
constexpr uint64_t INSTANCE = 0x1B614C0;
|
||||||
|
constexpr uint64_t FREE = 0x1B58BD0;
|
||||||
|
constexpr uint64_t TEXT = 0x22C9CA0;
|
||||||
|
constexpr uint64_t IMAGE = 0x22BF430;
|
||||||
|
constexpr uint64_t APP_MGR = 0x1B5C2F0;
|
||||||
|
constexpr uint64_t FILE = 0x20D30E0;
|
||||||
|
constexpr uint64_t XML = 0x0;
|
||||||
|
constexpr uint64_t XML_BUF_SIGN = 0x0;
|
||||||
|
constexpr uint64_t EMOTION_MGR = 0x1BD2310;
|
||||||
|
constexpr uint64_t EMOTION = 0x21B8100;
|
||||||
|
|
||||||
|
constexpr uint64_t NEW_MM_READER = 0x1B60A10;
|
||||||
|
constexpr uint64_t FREE_MM_READER = 0x1B5FDE0;
|
||||||
|
constexpr uint64_t RICH_TEXT = 0x20DD0C0;
|
||||||
|
|
||||||
|
constexpr uint64_t PAT = 0x2CC1E90;
|
||||||
|
|
||||||
|
constexpr uint64_t FORWARD = 0x22C9220;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Misc
|
||||||
|
{
|
||||||
|
constexpr uint64_t QR_CODE = 0x2025A80;
|
||||||
|
|
||||||
|
constexpr uint64_t INSATNCE = Message::Send::INSTANCE;
|
||||||
|
constexpr uint64_t FREE = Message::Send::FREE;
|
||||||
|
constexpr uint64_t CHAT_MGR = 0x1B8AA50;
|
||||||
|
constexpr uint64_t PRE_LOCAL_ID_MGR = 0x2142BF0;
|
||||||
|
constexpr uint64_t PRE_DOWNLOAD_MGR = 0x1C12260;
|
||||||
|
constexpr uint64_t PUSH_ATTACH_TASK = 0x1CE3050;
|
||||||
|
|
||||||
|
namespace Sns
|
||||||
|
{
|
||||||
|
constexpr uint64_t DATA_MGR = 0x21E52F0;
|
||||||
|
constexpr uint64_t TIMELINE = 0x2DC6180;
|
||||||
|
constexpr uint64_t FIRST = 0x2E346C0;
|
||||||
|
constexpr uint64_t NEXT = 0x2E5A270;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
92
WeChatFerry/spy/rpc_helper.h
Normal file
92
WeChatFerry/spy/rpc_helper.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <magic_enum/magic_enum.hpp>
|
||||||
|
|
||||||
|
#include "wcf.pb.h"
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "pb_encode.h"
|
||||||
|
#include "pb_types.h"
|
||||||
|
|
||||||
|
static const std::unordered_map<Functions, int> rpc_tag_map
|
||||||
|
= { { Functions_FUNC_IS_LOGIN, Response_status_tag },
|
||||||
|
{ Functions_FUNC_GET_SELF_WXID, Response_str_tag },
|
||||||
|
{ Functions_FUNC_GET_USER_INFO, Response_ui_tag },
|
||||||
|
{ Functions_FUNC_GET_MSG_TYPES, Response_types_tag },
|
||||||
|
{ Functions_FUNC_GET_CONTACTS, Response_contacts_tag },
|
||||||
|
{ Functions_FUNC_GET_DB_NAMES, Response_dbs_tag },
|
||||||
|
{ Functions_FUNC_GET_DB_TABLES, Response_tables_tag },
|
||||||
|
{ Functions_FUNC_GET_AUDIO_MSG, Response_str_tag },
|
||||||
|
{ Functions_FUNC_SEND_TXT, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_IMG, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_FILE, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_XML, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_RICH_TXT, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_PAT_MSG, Response_status_tag },
|
||||||
|
{ Functions_FUNC_FORWARD_MSG, Response_status_tag },
|
||||||
|
{ Functions_FUNC_SEND_EMOTION, Response_status_tag },
|
||||||
|
{ Functions_FUNC_ENABLE_RECV_TXT, Response_status_tag },
|
||||||
|
{ Functions_FUNC_DISABLE_RECV_TXT, Response_status_tag },
|
||||||
|
{ Functions_FUNC_EXEC_DB_QUERY, Response_rows_tag },
|
||||||
|
{ Functions_FUNC_REFRESH_PYQ, Response_status_tag },
|
||||||
|
{ Functions_FUNC_DOWNLOAD_ATTACH, Response_status_tag },
|
||||||
|
{ Functions_FUNC_GET_CONTACT_INFO, Response_contacts_tag },
|
||||||
|
{ Functions_FUNC_ACCEPT_FRIEND, Response_status_tag },
|
||||||
|
{ Functions_FUNC_RECV_TRANSFER, Response_status_tag },
|
||||||
|
{ Functions_FUNC_REVOKE_MSG, Response_status_tag },
|
||||||
|
{ Functions_FUNC_REFRESH_QRCODE, Response_str_tag },
|
||||||
|
{ Functions_FUNC_DECRYPT_IMAGE, Response_str_tag },
|
||||||
|
{ Functions_FUNC_EXEC_OCR, Response_ocr_tag },
|
||||||
|
{ Functions_FUNC_ADD_ROOM_MEMBERS, Response_status_tag },
|
||||||
|
{ Functions_FUNC_DEL_ROOM_MEMBERS, Response_status_tag },
|
||||||
|
{ Functions_FUNC_INV_ROOM_MEMBERS, Response_status_tag } };
|
||||||
|
|
||||||
|
template <Functions FuncType, typename AssignFunc> bool fill_response(uint8_t *out, size_t *len, AssignFunc assign)
|
||||||
|
{
|
||||||
|
Response rsp = Response_init_default;
|
||||||
|
rsp.func = FuncType;
|
||||||
|
|
||||||
|
auto it = rpc_tag_map.find(FuncType);
|
||||||
|
if (it == rpc_tag_map.end()) {
|
||||||
|
LOG_ERROR("Unknown function type: {}", magic_enum::enum_name(rsp.func));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rsp.which_msg = it->second;
|
||||||
|
|
||||||
|
assign(rsp);
|
||||||
|
|
||||||
|
pb_ostream_t stream = pb_ostream_from_buffer(out, *len);
|
||||||
|
if (!pb_encode(&stream, Response_fields, &rsp)) {
|
||||||
|
LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*len = stream.bytes_written;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Functions FuncType, typename DataType, typename AssignFunc>
|
||||||
|
bool fill_response(uint8_t *out, size_t *len, DataType &&data, AssignFunc &&assign)
|
||||||
|
{
|
||||||
|
Response rsp = Response_init_default;
|
||||||
|
rsp.func = FuncType;
|
||||||
|
|
||||||
|
auto it = rpc_tag_map.find(FuncType);
|
||||||
|
if (it == rpc_tag_map.end()) {
|
||||||
|
LOG_ERROR("Unknown function type: {}", magic_enum::enum_name(rsp.func));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rsp.which_msg = it->second;
|
||||||
|
|
||||||
|
assign(rsp, data);
|
||||||
|
|
||||||
|
pb_ostream_t stream = pb_ostream_from_buffer(out, *len);
|
||||||
|
if (!pb_encode(&stream, Response_fields, &rsp)) {
|
||||||
|
LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*len = stream.bytes_written;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,58 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef SPY_EXPORTS
|
#include <atomic>
|
||||||
#define SPY_API __declspec(dllexport)
|
#include <functional>
|
||||||
#else
|
#include <thread>
|
||||||
#define SPY_API __declspec(dllimport)
|
#include <unordered_map>
|
||||||
#endif
|
|
||||||
|
|
||||||
int RpcStartServer(int port);
|
#include <nng/nng.h>
|
||||||
int RpcStopServer();
|
|
||||||
|
#include "wcf.pb.h"
|
||||||
|
|
||||||
|
#include "message_handler.h"
|
||||||
|
#include "message_sender.h"
|
||||||
|
|
||||||
|
class RpcServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static RpcServer &getInstance();
|
||||||
|
static void destroyInstance();
|
||||||
|
|
||||||
|
int start(int port = RPC_DEFAULT_PORT);
|
||||||
|
int stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
RpcServer(int port = RPC_DEFAULT_PORT);
|
||||||
|
~RpcServer();
|
||||||
|
RpcServer(const RpcServer &) = delete;
|
||||||
|
RpcServer &operator=(const RpcServer &) = delete;
|
||||||
|
|
||||||
|
void run_rpc_server();
|
||||||
|
void on_message_callback();
|
||||||
|
bool start_message_listener(bool pyq, uint8_t *out, size_t *len);
|
||||||
|
bool stop_message_listener(uint8_t *out, size_t *len);
|
||||||
|
bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len);
|
||||||
|
|
||||||
|
static std::string build_url(int port);
|
||||||
|
|
||||||
|
using FunctionHandler = std::function<bool(const Request &, uint8_t *, size_t *)>;
|
||||||
|
|
||||||
|
// 服务器默认端口号和绑定地址
|
||||||
|
static constexpr int RPC_DEFAULT_PORT = 10086;
|
||||||
|
static constexpr const char *RPC_SERVER_ADDRESS = "tcp://0.0.0.0";
|
||||||
|
|
||||||
|
int port_ = RPC_DEFAULT_PORT;
|
||||||
|
std::atomic<bool> isRunning_ { false };
|
||||||
|
std::thread cmdThread_;
|
||||||
|
std::thread msgThread_;
|
||||||
|
|
||||||
|
message::Handler &handler_;
|
||||||
|
message::Sender &sender_;
|
||||||
|
|
||||||
|
struct Deleter {
|
||||||
|
void operator()(RpcServer *server) const { delete server; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<RpcServer, Deleter> instance_;
|
||||||
|
static const std::unordered_map<Functions, FunctionHandler> rpcFunctionMap;
|
||||||
|
};
|
||||||
|
@ -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);
|
|
Binary file not shown.
@ -1,49 +1,48 @@
|
|||||||
#include <filesystem>
|
#include "spy.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "rpc_server.h"
|
#include "rpc_server.h"
|
||||||
#include "spy.h"
|
#include "spy.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
UINT64 g_WeChatWinDllAddr = 0;
|
namespace Spy
|
||||||
|
|
||||||
static bool IsWxVersionMatched(const wchar_t *version)
|
|
||||||
{
|
{
|
||||||
if (wcscmp(version, SUPPORT_VERSION) != 0) {
|
int Init(void *args)
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitSpy(LPVOID args)
|
|
||||||
{
|
{
|
||||||
|
auto *pp = static_cast<util::PortPath *>(args);
|
||||||
wchar_t version[16] = { 0 };
|
|
||||||
PortPath_t *pp = (PortPath_t *)args;
|
|
||||||
|
|
||||||
Log::InitLogger(pp->path);
|
Log::InitLogger(pp->path);
|
||||||
g_WeChatWinDllAddr = (UINT64)GetModuleHandle(L"WeChatWin.dll"); // 获取wechatWin模块地址
|
if (auto dll_addr = GetModuleHandle(L"WeChatWin.dll")) {
|
||||||
if (g_WeChatWinDllAddr == 0) {
|
WeChatDll.store(reinterpret_cast<uint64_t>(dll_addr));
|
||||||
LOG_ERROR("获取 wechatWin.dll 模块地址失败");
|
} else {
|
||||||
return; // TODO: 退出进程,避免后面操作失败
|
LOG_ERROR("获取 WeChatWin.dll 模块地址失败");
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GetWeChatVersion(version)) { // 获取微信版本
|
std::string version = util::get_wechat_version();
|
||||||
LOG_ERROR("获取微信版本失败");
|
std::string msg = fmt::format("WCF 支持版本: {},当前版本: {}", SUPPORT_VERSION, version);
|
||||||
return;
|
if (version != SUPPORT_VERSION) {
|
||||||
}
|
LOG_ERROR(msg);
|
||||||
LOG_INFO("WeChat version: {}", Wstring2String(version).c_str());
|
util::MsgBox(NULL, msg.c_str(), "微信版本错误", MB_ICONERROR);
|
||||||
if (!IsWxVersionMatched(version)) {
|
return -2;
|
||||||
LOG_ERROR("不支持当前版本");
|
|
||||||
MessageBox(NULL, L"不支持当前版本", L"错误", 0);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcStartServer(pp->port);
|
LOG_INFO(msg);
|
||||||
|
RpcServer::getInstance().start(pp->port);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanupSpy()
|
void Cleanup()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("CleanupSpy");
|
LOG_DEBUG("CleanupSpy");
|
||||||
RpcStopServer();
|
RpcServer::destroyInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
__declspec(dllexport) int InitSpy(void *args) { return Spy::Init(args); }
|
||||||
|
__declspec(dllexport) void CleanupSpy() { Spy::Cleanup(); }
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "framework.h"
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#define SUPPORT_VERSION L"3.9.11.25"
|
namespace Spy
|
||||||
|
{
|
||||||
|
constexpr std::string_view SUPPORT_VERSION = "3.9.12.17";
|
||||||
|
inline std::atomic<std::uintptr_t> WeChatDll { 0 };
|
||||||
|
|
||||||
void InitSpy(int port);
|
template <typename T> inline T getFunction(std::uintptr_t offset) { return reinterpret_cast<T>(WeChatDll + offset); }
|
||||||
void CleanupSpy();
|
template <typename T> inline T getFunction(std::uintptr_t base, std::uintptr_t offset)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<T>(base + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Init(void *args);
|
||||||
|
void Cleanup();
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#undef APSTUDIO_READONLY_SYMBOLS
|
#undef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD>壬<EFBFBD>й<EFBFBD>) resources
|
// 中文(简体,中国) resources
|
||||||
|
|
||||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
|
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
|
||||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||||
@ -51,8 +51,8 @@ END
|
|||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 39,3,5,0
|
FILEVERSION 39,4,1,0
|
||||||
PRODUCTVERSION 3,9,11,25
|
PRODUCTVERSION 3,9,12,17
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
@ -69,12 +69,12 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "WeChatFerry"
|
VALUE "CompanyName", "WeChatFerry"
|
||||||
VALUE "FileDescription", "WeChatFerry"
|
VALUE "FileDescription", "WeChatFerry"
|
||||||
VALUE "FileVersion", "39.3.5.0"
|
VALUE "FileVersion", "39.4.1.0"
|
||||||
VALUE "InternalName", "spy.dll"
|
VALUE "InternalName", "spy.dll"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2023"
|
VALUE "LegalCopyright", "Copyright (C) 2023"
|
||||||
VALUE "OriginalFilename", "spy.dll"
|
VALUE "OriginalFilename", "spy.dll"
|
||||||
VALUE "ProductName", "WeChatFerry"
|
VALUE "ProductName", "WeChatFerry"
|
||||||
VALUE "ProductVersion", "3.9.11.25"
|
VALUE "ProductVersion", "3.9.12.17"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
@ -83,7 +83,7 @@ BEGIN
|
|||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
#endif // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD>壬<EFBFBD>й<EFBFBD>) resources
|
#endif // 中文(简体,中国) resources
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,36 +5,51 @@
|
|||||||
|
|
||||||
typedef uint64_t QWORD;
|
typedef uint64_t QWORD;
|
||||||
|
|
||||||
struct WxString {
|
class WxString
|
||||||
|
{
|
||||||
|
public:
|
||||||
const wchar_t *wptr;
|
const wchar_t *wptr;
|
||||||
DWORD size;
|
DWORD size;
|
||||||
DWORD capacity;
|
DWORD length;
|
||||||
const char *ptr;
|
const char *ptr;
|
||||||
DWORD clen;
|
DWORD clen;
|
||||||
WxString()
|
|
||||||
|
WxString() : wptr(nullptr), size(0), length(0), ptr(nullptr), clen(0) { }
|
||||||
|
|
||||||
|
explicit WxString(const std::wstring &ws)
|
||||||
|
: wptr(ws.c_str()), size(static_cast<DWORD>(ws.size())), length(static_cast<DWORD>(ws.length())), ptr(nullptr),
|
||||||
|
clen(0)
|
||||||
{
|
{
|
||||||
wptr = NULL;
|
|
||||||
size = 0;
|
|
||||||
capacity = 0;
|
|
||||||
ptr = NULL;
|
|
||||||
clen = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WxString(std::wstring &ws)
|
WxString(const WxString &) = delete;
|
||||||
|
WxString &operator=(const WxString &) = delete;
|
||||||
|
|
||||||
|
WxString(WxString &&other) noexcept
|
||||||
|
: wptr(other.wptr), size(other.size), length(other.length), ptr(other.ptr), clen(other.clen)
|
||||||
{
|
{
|
||||||
wptr = ws.c_str();
|
other.wptr = nullptr;
|
||||||
size = (DWORD)ws.size();
|
other.size = 0;
|
||||||
capacity = (DWORD)ws.capacity();
|
other.length = 0;
|
||||||
ptr = NULL;
|
other.ptr = nullptr;
|
||||||
clen = 0;
|
other.clen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
WxString &operator=(WxString &&other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
wptr = other.wptr;
|
||||||
|
size = other.size;
|
||||||
|
length = other.length;
|
||||||
|
ptr = other.ptr;
|
||||||
|
clen = other.clen;
|
||||||
|
|
||||||
|
other.wptr = nullptr;
|
||||||
|
other.size = 0;
|
||||||
|
other.length = 0;
|
||||||
|
other.ptr = nullptr;
|
||||||
|
other.clen = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct RawVector {
|
|
||||||
#ifdef _DEBUG
|
|
||||||
QWORD head;
|
|
||||||
#endif
|
|
||||||
QWORD start;
|
|
||||||
QWORD finish;
|
|
||||||
QWORD end;
|
|
||||||
} RawVector_t;
|
|
||||||
|
@ -138,25 +138,6 @@
|
|||||||
#define SQLITE_NULL 5
|
#define SQLITE_NULL 5
|
||||||
#define SQLITE_TEXT 3
|
#define SQLITE_TEXT 3
|
||||||
|
|
||||||
#define SQLITE3_EXEC_OFFSET 0x3A5EDA0
|
|
||||||
#define SQLITE3_BACKUP_INIT_OFFSET 0x3A18EA0
|
|
||||||
#define SQLITE3_PREPARE_OFFSET 0x3A66A20
|
|
||||||
#define SQLITE3_OPEN_OFFSET 0x3A9E210
|
|
||||||
#define SQLITE3_BACKUP_STEP_OFFSET 0x3A193F0
|
|
||||||
#define SQLITE3_BACKUP_REMAINING_OFFSET 0x1B26EB0
|
|
||||||
#define SQLITE3_BACKUP_PAGECOUNT_OFFSET 0x1B26EE0
|
|
||||||
#define SQLITE3_BACKUP_FINISH_OFFSET 0x3A19AF0
|
|
||||||
#define SQLITE3_SLEEP_OFFSET 0x3A9EE70
|
|
||||||
#define SQLITE3_ERRCODE_OFFSET 0x3A9CB10
|
|
||||||
#define SQLITE3_CLOSE_OFFSET 0x3A9AC70
|
|
||||||
#define SQLITE3_STEP_OFFSET 0x3A22DA0
|
|
||||||
#define SQLITE3_COLUMN_COUNT_OFFSET 0x3A235C0
|
|
||||||
#define SQLITE3_COLUMN_NAME_OFFSET 0x3A23FC0
|
|
||||||
#define SQLITE3_COLUMN_TYPE_OFFSET 0x3A23E10
|
|
||||||
#define SQLITE3_COLUMN_BLOB_OFFSET 0x3A235F0
|
|
||||||
#define SQLITE3_COLUMN_BYTES_OFFSET 0x3A236E0
|
|
||||||
#define SQLITE3_FINALIZE_OFFSET 0x3A21E50
|
|
||||||
|
|
||||||
typedef int (*Sqlite3_callback)(void *, int, char **, char **);
|
typedef int (*Sqlite3_callback)(void *, int, char **, char **);
|
||||||
|
|
||||||
typedef int(__cdecl *Sqlite3_exec)(QWORD, /* An open database */
|
typedef int(__cdecl *Sqlite3_exec)(QWORD, /* An open database */
|
||||||
|
@ -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();
|
|
2
clients/python/MANIFEST.in
vendored
2
clients/python/MANIFEST.in
vendored
@ -1,2 +1,2 @@
|
|||||||
include wcferry/*.dll
|
include wcferry/*.dll
|
||||||
include wcferry/*.exe
|
include wcferry/*.md
|
||||||
|
5
clients/python/README.MD
vendored
5
clients/python/README.MD
vendored
@ -44,8 +44,8 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
|
|||||||
|
|
||||||
## 版本更新
|
## 版本更新
|
||||||
|
|
||||||
### v39.3.3.1
|
### v39.4.1.0
|
||||||
* 修复 `send_xml`
|
* 修复乱码问题
|
||||||
|
|
||||||
<details><summary>点击查看更多</summary>
|
<details><summary>点击查看更多</summary>
|
||||||
|
|
||||||
@ -70,7 +70,6 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc
|
|||||||
* 发送图片消息
|
* 发送图片消息
|
||||||
* 发送文件消息
|
* 发送文件消息
|
||||||
* 发送卡片消息
|
* 发送卡片消息
|
||||||
* 发送 XML 消息
|
|
||||||
* 发送 GIF 消息
|
* 发送 GIF 消息
|
||||||
* 拍一拍群友
|
* 拍一拍群友
|
||||||
* 转发消息
|
* 转发消息
|
||||||
|
18
clients/python/wcferry/DISCLAIMER.md
vendored
Normal file
18
clients/python/wcferry/DISCLAIMER.md
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 免责声明
|
||||||
|
|
||||||
|
1. **本工具为开源项目,仅提供基础功能,供用户进行合法的学习、研究和非商业用途**。禁止将本工具用于任何违法或侵权行为。
|
||||||
|
|
||||||
|
2. **二次开发者的责任**:
|
||||||
|
- 任何基于本工具进行的二次开发、修改或衍生产品,其行为及后果由二次开发者独立承担,与本工具贡献者无关。
|
||||||
|
- **禁止使用贡献者的姓名、项目名称或相关信息作为二次开发产品的背书或推广手段**。
|
||||||
|
- 建议二次开发者在其衍生产品中添加自己的免责声明,明确责任归属。
|
||||||
|
|
||||||
|
3. **用户责任**:
|
||||||
|
- 使用本工具或其衍生产品的所有后果由用户自行承担。原贡献者不对因直接或间接使用本工具而导致的任何损失、责任或争议负责。
|
||||||
|
|
||||||
|
4. **法律约束**:
|
||||||
|
- 用户和二次开发者须遵守《中华人民共和国网络安全法》、《中华人民共和国著作权法》等相关法律法规。
|
||||||
|
- 本工具涉及的所有第三方商标或产品名称,其权利归权利人所有,作者与第三方无任何直接或间接关联。
|
||||||
|
|
||||||
|
5. **作者保留权利**:
|
||||||
|
- 本工具作者保留随时修改、更新、删除或终止本工具的权利,无需事先通知或承担任何义务。
|
18
clients/python/wcferry/client.py
vendored
18
clients/python/wcferry/client.py
vendored
@ -1,7 +1,7 @@
|
|||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__version__ = "39.3.3.3"
|
__version__ = "39.4.1.0"
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import base64
|
import base64
|
||||||
@ -10,6 +10,7 @@ import logging
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@ -70,6 +71,7 @@ class Wcf():
|
|||||||
self._dl_path = f"{self._wcf_root}/.dl"
|
self._dl_path = f"{self._wcf_root}/.dl"
|
||||||
os.makedirs(self._dl_path, exist_ok=True)
|
os.makedirs(self._dl_path, exist_ok=True)
|
||||||
self.LOG = logging.getLogger("WCF")
|
self.LOG = logging.getLogger("WCF")
|
||||||
|
self._set_console_utf8()
|
||||||
self.LOG.info(f"wcferry version: {__version__}")
|
self.LOG.info(f"wcferry version: {__version__}")
|
||||||
self.port = port
|
self.port = port
|
||||||
self.host = host
|
self.host = host
|
||||||
@ -115,6 +117,13 @@ class Wcf():
|
|||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
|
def _set_console_utf8(self):
|
||||||
|
try:
|
||||||
|
subprocess.run("chcp 65001", shell=True, check=True,
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self.LOG.error(f"修改控制台代码页失败: {e}")
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
"""关闭连接,回收资源"""
|
"""关闭连接,回收资源"""
|
||||||
if not self._is_running:
|
if not self._is_running:
|
||||||
@ -141,7 +150,9 @@ class Wcf():
|
|||||||
data = req.SerializeToString()
|
data = req.SerializeToString()
|
||||||
self.cmd_socket.send(data)
|
self.cmd_socket.send(data)
|
||||||
rsp = wcf_pb2.Response()
|
rsp = wcf_pb2.Response()
|
||||||
rsp.ParseFromString(self.cmd_socket.recv_msg().bytes)
|
bs = self.cmd_socket.recv_msg().bytes
|
||||||
|
self.LOG.debug(bs.hex())
|
||||||
|
rsp.ParseFromString(bs)
|
||||||
return rsp
|
return rsp
|
||||||
|
|
||||||
def is_receiving_msg(self) -> bool:
|
def is_receiving_msg(self) -> bool:
|
||||||
@ -150,7 +161,6 @@ class Wcf():
|
|||||||
|
|
||||||
def get_qrcode(self) -> str:
|
def get_qrcode(self) -> str:
|
||||||
"""获取登录二维码,已经登录则返回空字符串"""
|
"""获取登录二维码,已经登录则返回空字符串"""
|
||||||
raise Exception("Not implemented, yet")
|
|
||||||
req = wcf_pb2.Request()
|
req = wcf_pb2.Request()
|
||||||
req.func = wcf_pb2.FUNC_REFRESH_QRCODE # FUNC_REFRESH_QRCODE
|
req.func = wcf_pb2.FUNC_REFRESH_QRCODE # FUNC_REFRESH_QRCODE
|
||||||
rsp = self._send_request(req)
|
rsp = self._send_request(req)
|
||||||
@ -398,6 +408,7 @@ class Wcf():
|
|||||||
Returns:
|
Returns:
|
||||||
int: 0 为成功,其他失败
|
int: 0 为成功,其他失败
|
||||||
"""
|
"""
|
||||||
|
raise Exception("Not implemented, yet")
|
||||||
req = wcf_pb2.Request()
|
req = wcf_pb2.Request()
|
||||||
req.func = wcf_pb2.FUNC_SEND_XML # FUNC_SEND_XML
|
req.func = wcf_pb2.FUNC_SEND_XML # FUNC_SEND_XML
|
||||||
req.xml.receiver = receiver
|
req.xml.receiver = receiver
|
||||||
@ -763,6 +774,7 @@ class Wcf():
|
|||||||
Returns:
|
Returns:
|
||||||
int: 1 为成功,其他失败
|
int: 1 为成功,其他失败
|
||||||
"""
|
"""
|
||||||
|
raise Exception("Not implemented, yet")
|
||||||
req = wcf_pb2.Request()
|
req = wcf_pb2.Request()
|
||||||
req.func = wcf_pb2.FUNC_REVOKE_MSG # FUNC_REVOKE_MSG
|
req.func = wcf_pb2.FUNC_REVOKE_MSG # FUNC_REVOKE_MSG
|
||||||
req.ui64 = id
|
req.ui64 = id
|
||||||
|
463
clients/python/wcferry/wcf_pb2.py
vendored
463
clients/python/wcferry/wcf_pb2.py
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user