Impl download attachments: image, file, video. #14 close #35

This commit is contained in:
Changhua 2023-11-21 20:55:54 +08:00
parent 38db02437c
commit 4c13d86d41
8 changed files with 250 additions and 1 deletions

View File

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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
#pragma warning(disable : 4244)
#include "framework.h"
#include <filesystem>
#include <fstream>
#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;
}

View File

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

View File

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

View File

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

View File

@ -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 {