From 8ab646be960ce8c0e9830a4a3c1ed31c3b5ce4bf Mon Sep 17 00:00:00 2001 From: hugy <504650082@qq.com> Date: Thu, 26 Oct 2023 20:25:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B6=88=E6=81=AF=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/base/src/include/utils.h | 2 + app/base/src/utils.cc | 18 ++- app/wxhelper/src/global_manager.cc | 1 + app/wxhelper/src/http_client.cc | 73 +++++++++++ app/wxhelper/src/http_client.h | 26 ++++ app/wxhelper/src/http_url_handler.cc | 47 ++++++- app/wxhelper/src/http_url_handler.h | 1 + app/wxhelper/src/wechat_function.h | 1 + app/wxhelper/src/wechat_hook.cc | 187 +++++++++++++++++++++++++++ app/wxhelper/src/wechat_hook.h | 34 +++++ app/wxhelper/src/wechat_service.cc | 1 + app/wxhelper/src/wxutils.cc | 89 +++++++++++++ app/wxhelper/src/wxutils.h | 13 +- 13 files changed, 490 insertions(+), 3 deletions(-) create mode 100644 app/wxhelper/src/http_client.cc create mode 100644 app/wxhelper/src/http_client.h create mode 100644 app/wxhelper/src/wechat_hook.cc create mode 100644 app/wxhelper/src/wechat_hook.h diff --git a/app/base/src/include/utils.h b/app/base/src/include/utils.h index 96c9b53..f7c6803 100644 --- a/app/base/src/include/utils.h +++ b/app/base/src/include/utils.h @@ -56,6 +56,8 @@ bool CreateConsole(); void CloseConsole(); void HideModule(HMODULE module); + +bool IsDigit(const std::string &str); } // namespace utils } // namespace base #endif \ No newline at end of file diff --git a/app/base/src/utils.cc b/app/base/src/utils.cc index bfe9536..b663b58 100644 --- a/app/base/src/utils.cc +++ b/app/base/src/utils.cc @@ -1,7 +1,10 @@ #include "include/utils.h" -#include #include + +#include + +#include "utils.h" namespace base { namespace utils { const std::string hex_table = "0123456789abcdef"; @@ -151,5 +154,18 @@ void HideModule(HMODULE module) { } #endif } + +bool IsDigit(const std::string &str) { + if (str.length() == 0) { + return false; + } + for (auto it : str) { + if (it < '0' || it > '9') { + return false; + } + } + return true; +} + } // namespace utils } // namespace base diff --git a/app/wxhelper/src/global_manager.cc b/app/wxhelper/src/global_manager.cc index 3c75064..ad14e32 100644 --- a/app/wxhelper/src/global_manager.cc +++ b/app/wxhelper/src/global_manager.cc @@ -23,6 +23,7 @@ void GlobalManager::initialize(HMODULE module) { http_server = std::unique_ptr( new http::HttpServer(config->GetPort())); http_server->AddHttpApiUrl("/api/sendTextMsg", SendTextMsg); + http_server->AddHttpApiUrl("/api/hookSyncMsg", HookSyncMsg); http_server->Start(); base::ThreadPool::GetInstance().Create(2, 8); diff --git a/app/wxhelper/src/http_client.cc b/app/wxhelper/src/http_client.cc new file mode 100644 index 0000000..7b2a4c6 --- /dev/null +++ b/app/wxhelper/src/http_client.cc @@ -0,0 +1,73 @@ +#include "http_client.h" + +namespace http { + +void HttpClient::SendRequest(const std::string &content) { + struct mg_mgr mgr; + Data data; + data.done = false; + data.post_data = content; + mg_mgr_init(&mgr); + mg_http_connect(&mgr, kUrl.c_str(), OnHttpEvent, &data); + while (!data.done) { + mg_mgr_poll(&mgr, 500); + } + mg_mgr_free(&mgr); + data.done = false; +} + +void HttpClient::SetConfig(std::string url, uint64_t timeout) { + kUrl = url; + kTimeout = timeout; +} + +void HttpClient::OnHttpEvent(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + const char *s_url = kUrl.c_str(); + Data *data = (Data *)fn_data; + if (ev == MG_EV_OPEN) { + // Connection created. Store connect expiration time in c->data + *(uint64_t *)c->data = mg_millis() + kTimeout; + } else if (ev == MG_EV_POLL) { + if (mg_millis() > *(uint64_t *)c->data && + (c->is_connecting || c->is_resolving)) { + mg_error(c, "Connect timeout"); + } + } else if (ev == MG_EV_CONNECT) { + struct mg_str host = mg_url_host(s_url); + if (mg_url_is_ssl(s_url)) { + // no implement + } + // Send request + size_t content_length = data->post_data.size(); + mg_printf(c, + "POST %s HTTP/1.0\r\n" + "Host: %.*s\r\n" + "Content-Type: application/json\r\n" + "Content-Length: %d\r\n" + "\r\n", + mg_url_uri(s_url), (int)host.len, host.ptr, content_length); + mg_send(c, data->post_data.c_str(), content_length); + } else if (ev == MG_EV_HTTP_MSG) { +// Response is received. Print it +#ifdef _DEBUG + struct mg_http_message *hm = (struct mg_http_message *)ev_data; + printf("%.*s", (int)hm->message.len, hm->message.ptr); +#endif + // c->is_closing = 1; // Tell mongoose to close this connection + c->is_draining = 1; + data->done = true; // Tell event loop to stops + } else if (ev == MG_EV_ERROR) { + data->done = true; // Error, tell event loop to stop + } else if (ev == MG_EV_CLOSE) { + if (!data->done) { + data->done = true; + } + } else if (ev == MG_EV_HTTP_CHUNK) { + mg_error(c, "http chunk no implement"); + c->is_closing = 1; + data->done = true; + } +} + +} // namespace http \ No newline at end of file diff --git a/app/wxhelper/src/http_client.h b/app/wxhelper/src/http_client.h new file mode 100644 index 0000000..de65ba8 --- /dev/null +++ b/app/wxhelper/src/http_client.h @@ -0,0 +1,26 @@ +#ifndef WXHELPER_HTTP_CLIENT_H_ +#define WXHELPER_HTTP_CLIENT_H_ +#include + +#include "mongoose.h" +#include "singleton.h" + +namespace http { +static std::string kUrl = "http://127.0.0.1:8000"; +static uint64_t kTimeout = 3000; +struct Data { + bool done; + std::string post_data; +}; + +class HttpClient { + public: + static void SendRequest(const std::string &content); + static void SetConfig(std::string url, uint64_t timeout); + static void OnHttpEvent(struct mg_connection *c, int ev, void *ev_data, + void *fn_data); + +}; + +} // namespace http +#endif \ No newline at end of file diff --git a/app/wxhelper/src/http_url_handler.cc b/app/wxhelper/src/http_url_handler.cc index 2eb0c1b..57d673d 100644 --- a/app/wxhelper/src/http_url_handler.cc +++ b/app/wxhelper/src/http_url_handler.cc @@ -4,12 +4,34 @@ #include "utils.h" #include "wechat_service.h" -#include "windows.h" +#include "wechat_hook.h" + +#define STR2ULL(str) (base::utils::IsDigit(str) ? stoull(str) : 0) +#define STR2LL(str) (base::utils::IsDigit(str) ? stoll(str) : 0) +#define STR2I(str) (base::utils::IsDigit(str) ? stoi(str) : 0) namespace wxhelper { std::wstring GetWStringParam(nlohmann::json data, std::string key) { return base::utils::Utf8ToWstring(data[key].get()); } +int GetIntParam(nlohmann::json data, std::string key) { + int result; + try { + result = data[key].get(); + } catch (nlohmann::json::exception) { + result = STR2I(data[key].get()); + } + return result; +} + +bool GetBoolParam(nlohmann::json data, std::string key) { + return data[key].get(); +} + +std::string GetStringParam(nlohmann::json data, std::string key) { + return data[key].get(); +} + std::string SendTextMsg(mg_http_message* hm) { nlohmann::json j_param = nlohmann::json::parse( hm->body.ptr, hm->body.ptr + hm->body.len, nullptr, false); @@ -21,4 +43,27 @@ std::string SendTextMsg(mg_http_message* hm) { std::string ret = ret_data.dump(); return ret; } + +std::string HookSyncMsg(mg_http_message* hm) { + nlohmann::json j_param = nlohmann::json::parse( + hm->body.ptr, hm->body.ptr + hm->body.len, nullptr, false); + int port = GetIntParam(j_param, "port"); + std::string ip = GetStringParam(j_param, "ip"); + bool enable = GetBoolParam(j_param, "enableHttp"); + std::string url = "http:://127.0.0.1:19088"; + uint64_t timeout = 3000; + if (enable) { + url = GetStringParam(j_param, "url"); + timeout = GetIntParam(j_param, "timeout"); + } + hook::WechatHookParam param = { + ip, url, port, enable, timeout, + }; + hook::WechatHook::GetInstance().Init(param); + INT64 success = hook::WechatHook::GetInstance().HookSyncMsg(); + nlohmann::json ret_data = { + {"code", success}, {"data", {}}, {"msg", "success"}}; + std::string ret = ret_data.dump(); + return ret; +} } // namespace wxhelper \ No newline at end of file diff --git a/app/wxhelper/src/http_url_handler.h b/app/wxhelper/src/http_url_handler.h index 01e79a5..37ae834 100644 --- a/app/wxhelper/src/http_url_handler.h +++ b/app/wxhelper/src/http_url_handler.h @@ -5,6 +5,7 @@ #include "mongoose.h" namespace wxhelper { std::string SendTextMsg(struct mg_http_message *hm); +std::string HookSyncMsg(struct mg_http_message *hm); } #endif \ No newline at end of file diff --git a/app/wxhelper/src/wechat_function.h b/app/wxhelper/src/wechat_function.h index caf6da8..4f0eb26 100644 --- a/app/wxhelper/src/wechat_function.h +++ b/app/wxhelper/src/wechat_function.h @@ -487,6 +487,7 @@ namespace offset { const UINT64 kGetSendMessageMgr = 0x8fe740; const UINT64 kFreeChatMsg = 0x8fffc0; const UINT64 kSendTextMsg = 0x1024370; +const UINT64 kDoAddMsg = 0x106b810; } // namespace offset namespace function { typedef UINT64 (*__GetSendMessageMgr)(); diff --git a/app/wxhelper/src/wechat_hook.cc b/app/wxhelper/src/wechat_hook.cc new file mode 100644 index 0000000..bbcc65c --- /dev/null +++ b/app/wxhelper/src/wechat_hook.cc @@ -0,0 +1,187 @@ +#include +#include "wechat_hook.h" + + +#include +#include + +#include + +#include "base64.h" +#include "http_client.h" +#include "spdlog/spdlog.h" +#include "thread_pool.h" +#include "wxutils.h" +namespace offset = wxhelper::V3_9_7_29::offset; +namespace common = wxhelper::common; + +namespace hook { +static bool kEnableHttp = false; +static bool kLogHookFlag = false; +static char kServerIp[20] = "127.0.0.1"; +static int kServerPort = 19099; + +UINT64(*RealDoAddMsg) +(UINT64, UINT64, UINT64) = (UINT64(*)(UINT64, UINT64, UINT64))( + wxhelper::wxutils::GetWeChatWinBase() + offset::kDoAddMsg); + +VOID SendMsgCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, + PTP_WORK Work) { + common::InnerMessageStruct *msg = (common::InnerMessageStruct *)context; + if (msg == NULL) { + SPDLOG_INFO("add work:msg is null"); + return; + } + std::unique_ptr sms(msg); + nlohmann::json j_msg = nlohmann::json::parse( + msg->buffer, msg->buffer + msg->length, nullptr, false); + if (j_msg.is_discarded() == true) { + return; + } + std::string jstr = j_msg.dump() + "\n"; + + WSADATA was_data = {0}; + int ret = WSAStartup(MAKEWORD(2, 2), &was_data); + if (ret != 0) { + SPDLOG_ERROR("WSAStartup failed:{}", ret); + return; + } + + SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (client_socket < 0) { + SPDLOG_ERROR("socket init fail"); + return; + } + BOOL status = false; + sockaddr_in client_addr; + memset(&client_addr, 0, sizeof(client_addr)); + client_addr.sin_family = AF_INET; + client_addr.sin_port = htons((u_short)kServerPort); + InetPtonA(AF_INET, kServerIp, &client_addr.sin_addr.s_addr); + if (connect(client_socket, reinterpret_cast(&client_addr), + sizeof(sockaddr)) < 0) { + SPDLOG_ERROR("socket connect fail. host:{} , port:{},",kServerIp,kServerPort); + goto clean; + } + char recv_buf[1024] = {0}; + ret = send(client_socket, jstr.c_str(), static_cast(jstr.size()), 0); + if (ret < 0) { + SPDLOG_ERROR("socket send fail ,ret:{}", ret); + goto clean; + } + ret = shutdown(client_socket, SD_SEND); + if (ret == SOCKET_ERROR) { + SPDLOG_ERROR("shutdown failed with erro:{}", ret); + goto clean; + } + ret = recv(client_socket, recv_buf, sizeof(recv_buf), 0); + if (ret < 0) { + SPDLOG_ERROR("socket recv fail ,ret:{}", ret); + goto clean; + } +clean: + closesocket(client_socket); + WSACleanup(); + return; +} + +VOID SendHttpMsgCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, + PTP_WORK Work) { + common::InnerMessageStruct *msg = (common::InnerMessageStruct *)context; + if (msg == NULL) { + SPDLOG_INFO("http msg is null"); + return; + } + + std::unique_ptr sms(msg); + nlohmann::json j_msg = nlohmann::json::parse( + msg->buffer, msg->buffer + msg->length, nullptr, false); + if (j_msg.is_discarded() == true) { + return; + } + std::string jstr = j_msg.dump() + "\n"; + http::HttpClient::SendRequest(jstr); +} + +void HandleSyncMsg(INT64 param1, INT64 param2, INT64 param3) { + nlohmann::json msg; + + msg["pid"] = GetCurrentProcessId(); + msg["fromUser"] = + wxhelper::wxutils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x18)); + msg["toUser"] = + wxhelper::wxutils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x28)); + msg["content"] = + wxhelper::wxutils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x30)); + msg["signature"] = + wxhelper::wxutils::ReadWeChatStr(*(INT64 *)(param2 + 0x48)); + msg["msgId"] = *(INT64 *)(param2 + 0x60); + msg["msgSequence"] = *(DWORD *)(param2 + 0x5C); + msg["createTime"] = *(DWORD *)(param2 + 0x58); + msg["displayFullContent"] = + wxhelper::wxutils::ReadWeChatStr(*(INT64 *)(param2 + 0x50)); + DWORD type = *(DWORD *)(param2 + 0x24); + msg["type"] = type; + if (type == 3) { + int a = 1; + std::string img = + wxhelper::wxutils::ReadSKBuiltinBuffer(*(INT64 *)(param2 + 0x40)); + SPDLOG_INFO("encode size:{}", img.size()); + msg["base64Img"] = base64_encode(img); + a = 2; + } + std::string jstr = msg.dump() + '\n'; + common::InnerMessageStruct *inner_msg = new common::InnerMessageStruct; + inner_msg->buffer = new char[jstr.size() + 1]; + memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); + inner_msg->length = jstr.size(); + if (kEnableHttp) { + bool add = + base::ThreadPool::GetInstance().AddWork(SendHttpMsgCallback, inner_msg); + SPDLOG_INFO("add http msg work:{}", add); + } else { + bool add = + base::ThreadPool::GetInstance().AddWork(SendMsgCallback, inner_msg); + SPDLOG_INFO("add msg work:{}", add); + } + RealDoAddMsg(param1, param2, param3); +} + +void WechatHook::Init(WechatHookParam param) { + if(!init_){ + param_ = param; + kEnableHttp = param.enable_http; + } +} + +int WechatHook::HookSyncMsg() { + if (sync_msg_flag_) { + SPDLOG_INFO("recv msg hook already called"); + return 2; + } + + if (param_.server_ip.size() < 1) { + return -2; + } + UINT64 base = wxhelper::wxutils::GetWeChatWinBase(); + if (!base) { + SPDLOG_INFO("base addr is null"); + return -1; + } + if (param_.enable_http) { + http::HttpClient::SetConfig(param_.http_url,param_.http_time_out); + }else{ + const char* charPtr = param_.server_ip.c_str(); + std::strcpy(kServerIp, charPtr); + kServerPort = param_.server_port; + } + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID &)RealDoAddMsg, &HandleSyncMsg); + LONG ret = DetourTransactionCommit(); + if (ret == NO_ERROR) { + sync_msg_flag_ = true; + } + return ret; +} +} // namespace hook diff --git a/app/wxhelper/src/wechat_hook.h b/app/wxhelper/src/wechat_hook.h new file mode 100644 index 0000000..b86bf2c --- /dev/null +++ b/app/wxhelper/src/wechat_hook.h @@ -0,0 +1,34 @@ +#ifndef WXHELPER_WECHAT_HOOK_H_ +#define WXHELPER_WECHAT_HOOK_H_ +#include "wechat_function.h" +#include "singleton.h" + +namespace hook { +struct WechatHookParam { + std::string server_ip = "127.0.0.1"; + std::string http_url = "http:://127.0.0.1:19088"; + int server_port = 19099; + bool enable_http = false; + uint64_t http_time_out = 3000; +}; + +class WechatHook :public base::Singleton{ + public: + void Init(WechatHookParam param); + + int HookSyncMsg(); + int UnHookSyncMsg(); + + int HookLog(); + + int UnHookLog(); + + private: + WechatHookParam param_; + bool sync_msg_flag_; + bool init_; +}; + +} // namespace hook + +#endif \ No newline at end of file diff --git a/app/wxhelper/src/wechat_service.cc b/app/wxhelper/src/wechat_service.cc index 5998aa0..1ff05ce 100644 --- a/app/wxhelper/src/wechat_service.cc +++ b/app/wxhelper/src/wechat_service.cc @@ -179,4 +179,5 @@ void WechatService::SetBaseAddr(UINT64 addr) { base_addr_ = addr; } void WechatService::SetJsApiAddr(UINT64 addr) { js_api_addr_ = addr; } + } // namespace wxhelper \ No newline at end of file diff --git a/app/wxhelper/src/wxutils.cc b/app/wxhelper/src/wxutils.cc index eade7d2..7c6211d 100644 --- a/app/wxhelper/src/wxutils.cc +++ b/app/wxhelper/src/wxutils.cc @@ -1,7 +1,96 @@ #include "wxutils.h" +#include "utils.h" +#define BUFSIZE 1024 +#define JPEG0 0xFF +#define JPEG1 0xD8 +#define JPEG2 0xFF +#define PNG0 0x89 +#define PNG1 0x50 +#define PNG2 0x4E +#define BMP0 0x42 +#define BMP1 0x4D +#define GIF0 0x47 +#define GIF1 0x49 +#define GIF2 0x46 namespace wxhelper { namespace wxutils { UINT64 GetWeChatWinBase() { return (UINT64)GetModuleHandleA("WeChatWin.dll"); } + +std::string ReadSKBuiltinString(INT64 addr) { + INT64 inner_string = *(INT64 *)(addr + 0x8); + if (inner_string == 0) { + return std::string(); + } + return ReadWeChatStr(inner_string); +} + +std::string ReadSKBuiltinBuffer(INT64 addr) { + INT64 len = *(INT64 *)(addr + 0x10); + if (len == 0) { + return std::string(); + } + INT64 inner_string = *(INT64 *)(addr + 0x8); + if (inner_string == 0) { + return std::string(); + } + return ReadWeChatStr(inner_string); +} + +std::string ReadWeChatStr(INT64 addr) { + INT64 len = *(INT64 *)(addr + 0x10); + if (len == 0) { + return std::string(); + } + INT64 max_len = *(INT64 *)(addr + 0x18); + if ((max_len | 0xF) == 0xF) { + return std::string((char *)addr, len); + } + char *char_from_user = *(char **)(addr); + return std::string(char_from_user, len); +} + +std::string ImageXor(std::string buf) { + const char *origin = buf.c_str(); + short key = 0; + if ((*origin ^ JPEG0) == (*(origin + 1) ^ JPEG1)) { + key = *origin ^ JPEG0; + } else if ((*origin ^ PNG1) == (*(origin + 1) ^ PNG2)) { + key = *origin ^ PNG1; + } else if ((*origin ^ GIF0) == (*(origin + 1) ^ GIF1)) { + key = *origin ^ GIF0; + } else if ((*origin ^ BMP0) == (*(origin + 1) ^ BMP1)) { + key = *origin ^ BMP0; + } else { + key = -1; + } + if (key > 0) { + char *img_buf = new char[buf.size()]; + for (unsigned int i = 0; i < buf.size(); i++) { + img_buf[i] = *(origin + i) ^ key; + } + std::string str(img_buf); + delete[] img_buf; + img_buf = NULL; + return str; + } + return std::string(); +} + +std::wstring ReadWstring(INT64 addr) { + DWORD len = *(DWORD *)(addr + 0x8); + if (len == 0) { + return std::wstring(); + } + wchar_t *str = *(wchar_t **)(addr); + if (str == NULL) { + return std::wstring(); + } + return std::wstring(str, len); +} +std::string ReadWstringThenConvert(INT64 addr) { + std::wstring wstr = ReadWstring(addr); + return base::utils::WstringToUtf8(wstr); +} } // namespace wxutils } // namespace wxhelper diff --git a/app/wxhelper/src/wxutils.h b/app/wxhelper/src/wxutils.h index bb7fffd..189a986 100644 --- a/app/wxhelper/src/wxutils.h +++ b/app/wxhelper/src/wxutils.h @@ -1,10 +1,21 @@ #ifndef WXHELPER_WXUTILS_H_ #define WXHELPER_WXUTILS_H_ #include +#include namespace wxhelper { namespace wxutils { + UINT64 GetWeChatWinBase(); -} + std::string ReadSKBuiltinString(INT64 addr); + std::string ReadSKBuiltinBuffer(INT64 addr); + std::string ReadWeChatStr(INT64 addr); + + std::string ImageXor(std::string buf); + std::wstring ReadWstring(INT64 addr); + std::string ReadWstringThenConvert(INT64 addr); + + INT64 DecodeImage(const wchar_t* file_path, const wchar_t* save_dir); +} // namespace wxutils } // namespace wxhelper #endif \ No newline at end of file