From 4c13d86d41c6266db90030e08d4259780e3ecf7b Mon Sep 17 00:00:00 2001 From: Changhua Date: Tue, 21 Nov 2023 20:55:54 +0800 Subject: [PATCH] Impl download attachments: image, file, video. #14 close #35 --- WeChatFerry/rpc/proto/wcf.proto | 9 ++ WeChatFerry/spy/exec_sql.cpp | 36 +++++++ WeChatFerry/spy/exec_sql.h | 1 + WeChatFerry/spy/funcs.cpp | 162 ++++++++++++++++++++++++++++++++ WeChatFerry/spy/funcs.h | 1 + WeChatFerry/spy/load_calls.cpp | 4 +- WeChatFerry/spy/rpc_server.cpp | 28 ++++++ WeChatFerry/spy/spy_types.h | 10 ++ 8 files changed, 250 insertions(+), 1 deletion(-) diff --git a/WeChatFerry/rpc/proto/wcf.proto b/WeChatFerry/rpc/proto/wcf.proto index 5cdc36b..29162ab 100644 --- a/WeChatFerry/rpc/proto/wcf.proto +++ b/WeChatFerry/rpc/proto/wcf.proto @@ -23,6 +23,7 @@ enum Functions { FUNC_ACCEPT_FRIEND = 0x51; FUNC_RECV_TRANSFER = 0x52; FUNC_REFRESH_PYQ = 0x53; + FUNC_DOWNLOAD_ATTACH = 0x54; FUNC_DECRYPT_IMAGE = 0x60; FUNC_ADD_ROOM_MEMBERS = 0x70; FUNC_DEL_ROOM_MEMBERS = 0x71; @@ -45,6 +46,7 @@ message Request Transfer tf = 11; uint64 ui64 = 12; // 64 位整数,通用 bool flag = 13; + AttachMsg att = 14; } } @@ -176,3 +178,10 @@ message Transfer string tfid = 2; // 转账id transferid string taid = 3; // Transaction id } + +message AttachMsg +{ + uint64 id = 1; // 消息 id + string thumb = 2; // 消息中的 thumb + string extra = 3; // 消息中的 extra +} diff --git a/WeChatFerry/spy/exec_sql.cpp b/WeChatFerry/spy/exec_sql.cpp index 7ff4f3d..309f37a 100644 --- a/WeChatFerry/spy/exec_sql.cpp +++ b/WeChatFerry/spy/exec_sql.cpp @@ -159,3 +159,39 @@ DbRows_t ExecDbQuery(const string db, const string sql) } return rows; } + +int GetLocalIdandDbidx(uint64_t id, uint64_t *localId, uint32_t *dbIdx) +{ + DWORD msgMgrAddr = GET_DWORD(g_WeChatWinDllAddr + OFFSET_DB_MSG_MGR); + DWORD dbIndex = GET_DWORD(msgMgrAddr + 0x38); + DWORD pStart = GET_DWORD(msgMgrAddr + 0x2C); + + *dbIdx = 0; + for (int i = dbIndex - 1; i >= 0; i--) { // 从后往前遍历 + DWORD dbAddr = GET_DWORD(pStart + i * 0x04); + if (dbAddr) { + string dbname = Wstring2String(GET_WSTRING(dbAddr)); + dbMap[dbname] = GET_DWORD(dbAddr + 0x60); + 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 = strtoul((const char *)(field.content.data()), NULL, 10); + *dbIdx = GET_DWORD(GET_DWORD(dbAddr + 0x18) + 0x144); + + return 0; + } + } + + return -1; +} diff --git a/WeChatFerry/spy/exec_sql.h b/WeChatFerry/spy/exec_sql.h index c3ae625..86ce30e 100644 --- a/WeChatFerry/spy/exec_sql.h +++ b/WeChatFerry/spy/exec_sql.h @@ -5,3 +5,4 @@ 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); diff --git a/WeChatFerry/spy/funcs.cpp b/WeChatFerry/spy/funcs.cpp index aa940cd..b6206dc 100644 --- a/WeChatFerry/spy/funcs.cpp +++ b/WeChatFerry/spy/funcs.cpp @@ -1,8 +1,10 @@ #pragma warning(disable : 4244) #include "framework.h" +#include #include +#include "exec_sql.h" #include "funcs.h" #include "log.h" #include "spy_types.h" @@ -16,6 +18,7 @@ #define HEADER_GIF2 0x49 using namespace std; +namespace fs = std::filesystem; extern bool gIsListeningPyq; extern WxCalls_t g_WxCalls; @@ -91,6 +94,47 @@ bool DecryptImage(string src, string dst) return true; } +static string DecImage(string src) +{ + ifstream in(src.c_str(), ios::binary); + if (!in.is_open()) { + LOG_ERROR("Failed to open file {}", src); + return ""; + } + + filebuf *pfb = in.rdbuf(); + size_t size = pfb->pubseekoff(0, ios::end, ios::in); + pfb->pubseekpos(0, ios::in); + + char *pBuf = new char[size]; + 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 = fs::path(src).replace_extension(ext).string(); + ofstream out(dst.c_str(), ios::binary); + if (!out.is_open()) { + LOG_ERROR("Failed to open file {}", dst); + return ""; + } + + out.write(pBuf, size); + out.close(); + + delete[] pBuf; // memory leak + + return dst; +} + static int GetFirstPage() { int rv = -1; @@ -152,3 +196,121 @@ int RefreshPyq(uint64_t id) return GetNextPage(id); } + +string DownloadAttach(uint64_t id, string thumb, string extra) +{ + int status = -1; + uint64_t localId; + uint32_t dbIdx; + if (GetLocalIdandDbidx(id, &localId, &dbIdx) != 0) { + LOG_ERROR("Failed to get localId, Please check id: {}", to_string(id)); + return ""; + } + + char buff[0x2D8] = { 0 }; + DWORD dlCall1 = g_WeChatWinDllAddr + g_WxCalls.da.call1; + DWORD dlCall2 = g_WeChatWinDllAddr + g_WxCalls.da.call2; + DWORD dlCall3 = g_WeChatWinDllAddr + g_WxCalls.da.call3; + DWORD dlCall4 = g_WeChatWinDllAddr + g_WxCalls.da.call4; + DWORD dlCall5 = g_WeChatWinDllAddr + g_WxCalls.da.call5; + DWORD dlCall6 = g_WeChatWinDllAddr + g_WxCalls.da.call6; + + __asm { + pushad; + pushfd; + lea ecx, buff; + call dlCall1; + call dlCall2; + push dword ptr [dbIdx]; + lea ecx, buff; + push dword ptr [localId]; + call dlCall3; + add esp, 0x8; + popfd; + popad; + } + + DWORD type = GET_DWORD(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; + } + + // 创建父目录,由于路径来源于微信,不做检查 + fs::create_directory(fs::path(save_path).parent_path().string()); + + wstring wsSavePath = String2Wstring(save_path); + wstring wsThumbPath = String2Wstring(thumb_path); + + WxString_t wxSavePath = { 0 }; + WxString_t wxThumbPath = { 0 }; + + wxSavePath.text = (wchar_t *)wsSavePath.c_str(); + wxSavePath.size = wsSavePath.size(); + wxSavePath.capacity = wsSavePath.capacity(); + + wxThumbPath.text = (wchar_t *)wsThumbPath.c_str(); + wxThumbPath.size = wsThumbPath.size(); + wxThumbPath.capacity = wsThumbPath.capacity(); + + int temp = 1; + memcpy(&buff[0x19C], &wxThumbPath, sizeof(wxThumbPath)); + memcpy(&buff[0x1B0], &wxSavePath, sizeof(wxSavePath)); + memcpy(&buff[0x29C], &temp, sizeof(temp)); + + __asm { + pushad; + pushfd; + call dlCall4; + push 0x1; + push 0x0; + lea ecx, buff; + push ecx; + mov ecx, eax; + call dlCall5; + mov status, eax; + lea ecx, buff; + push 0x0; + call dlCall6; + popfd; + popad; + } + + if (status != 0) + { + return ""; + } + + // 保存成功,如果是图片则需要解密。考虑异步? + if (type == 0x03) { + uint32_t cnt = 0; + while (cnt < 10) { + if (fs::exists(save_path)) { + return DecImage(save_path); + } + Sleep(500); + cnt++; + } + return ""; + } + + return save_path; +} diff --git a/WeChatFerry/spy/funcs.h b/WeChatFerry/spy/funcs.h index 7c4f739..0fbe456 100644 --- a/WeChatFerry/spy/funcs.h +++ b/WeChatFerry/spy/funcs.h @@ -5,3 +5,4 @@ bool DecryptImage(std::string src, std::string dst); int RefreshPyq(uint64_t id); +std::string DownloadAttach(uint64_t id, std::string thumb, std::string extra); diff --git a/WeChatFerry/spy/load_calls.cpp b/WeChatFerry/spy/load_calls.cpp index b2551ad..99dc733 100644 --- a/WeChatFerry/spy/load_calls.cpp +++ b/WeChatFerry/spy/load_calls.cpp @@ -27,7 +27,9 @@ WxCalls_t wxCalls = { { 0x7B2E60, 0x15E2C20, 0x79C250 }, // Receive transfer /* Receive PYQ hook, call, call1, call2, call3, start, end, ts, wxid, content, xml, step*/ - { 0x14F9E15, 0x14FA0A0, 0xC39680, 0x14E2140, 0x14E21E0, 0x20, 0x24, 0x2C, 0x18, 0x3C, 0x384, 0xB48 } + { 0x14F9E15, 0x14FA0A0, 0xC39680, 0x14E2140, 0x14E21E0, 0x20, 0x24, 0x2C, 0x18, 0x3C, 0x384, 0xB48 }, + /* call1, call2, call3, call4, call5, call6*/ + { 0x76F010, 0x792700, 0xBC0370, 0x80F110, 0x82BB40, 0x756E30} }; int LoadCalls(const wchar_t *version, WxCalls_t *calls) diff --git a/WeChatFerry/spy/rpc_server.cpp b/WeChatFerry/spy/rpc_server.cpp index 3e0e93b..52c31eb 100644 --- a/WeChatFerry/spy/rpc_server.cpp +++ b/WeChatFerry/spy/rpc_server.cpp @@ -511,6 +511,29 @@ bool func_refresh_pyq(uint64_t id, uint8_t *out, size_t *len) return true; } +bool func_download_attach(AttachMsg att, uint8_t *out, size_t *len) +{ + Response rsp = Response_init_default; + rsp.func = Functions_FUNC_DOWNLOAD_ATTACH; + rsp.which_msg = Response_str_tag; + + uint64_t id = att.id; + string thumb = string(att.thumb ? att.thumb : ""); + string extra = string(att.extra ? att.extra : ""); + + string path = DownloadAttach(id, thumb, extra); + rsp.msg.str = (char *)path.c_str(); + + 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; +} + bool func_decrypt_image(char *src, char *dst, uint8_t *out, size_t *len) { Response rsp = Response_init_default; @@ -683,6 +706,11 @@ static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len ret = func_refresh_pyq(req.msg.ui64, out, out_len); break; } + case Functions_FUNC_DOWNLOAD_ATTACH: { + LOG_DEBUG("[Functions_FUNC_DOWNLOAD_ATTACH]"); + ret = func_download_attach(req.msg.att, out, out_len); + break; + } case Functions_FUNC_DECRYPT_IMAGE: { LOG_DEBUG("[FUNCTIONS_FUNC_DECRYPT_IMAGE]"); ret = func_decrypt_image(req.msg.dec.src, req.msg.dec.dst, out, out_len); diff --git a/WeChatFerry/spy/spy_types.h b/WeChatFerry/spy/spy_types.h index a279714..0315eb1 100644 --- a/WeChatFerry/spy/spy_types.h +++ b/WeChatFerry/spy/spy_types.h @@ -102,6 +102,15 @@ typedef struct Pyq { DWORD step; } Pyq_t; +typedef struct DlAttach{ + DWORD call1; + DWORD call2; + DWORD call3; + DWORD call4; + DWORD call5; + DWORD call6; +} DlAttach_t; + typedef struct WxCalls { DWORD login; // 登录状态 UserInfoCall_t ui; // 用户信息 @@ -118,6 +127,7 @@ typedef struct WxCalls { RoomMember_t drm; // 删除群成员 TF_t tf; // 接收转账 Pyq_t pyq; // 接收朋友圈消息 + DlAttach_t da; // 下载资源(图片、文件、视频) } WxCalls_t; typedef struct WxString {