diff --git a/WeChatFerry/spy/chatroom_mgmt.cpp b/WeChatFerry/spy/chatroom_mgmt.cpp new file mode 100644 index 0000000..ce36eac --- /dev/null +++ b/WeChatFerry/spy/chatroom_mgmt.cpp @@ -0,0 +1,114 @@ +#include "framework.h" +#include +#include + +#include "chatroom_mgmt.h" +#include "log.h" +#include "util.h" + +using namespace std; +extern QWORD g_WeChatWinDllAddr; + +#define OS_GET_CHATROOM_MGR 0x1B894E0 +#define OS_ADD_MEMBERS 0x215A820 +#define OS_DELETE_MEMBERS 0x215AE60 +#define OS_INVITE_MEMBERS 0x215A200 + +typedef QWORD (*GetChatRoomMgr_t)(); +typedef QWORD (*AddMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD); +typedef QWORD (*DelMemberFromChatRoom_t)(QWORD, QWORD, QWORD); +typedef QWORD (*InviteMemberToChatRoom_t)(QWORD, QWORD, QWORD, QWORD); + +int AddChatroomMember(string roomid, string wxids) +{ + int status = -1; + + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return status; + } + + GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); + AddMemberToChatRoom_t AddMembers = (AddMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_ADD_MEMBERS); + + vector vMembers; + vector vWxMembers; + wstringstream wss(String2Wstring(wxids)); + while (wss.good()) { + wstring wstr; + getline(wss, wstr, L','); + vMembers.push_back(wstr); + WxString wxMember(vMembers.back()); + vWxMembers.push_back(wxMember); + } + + QWORD temp[2] = { 0 }; + WxString *pWxRoomid = NewWxStringFromStr(roomid); + QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; + + QWORD mgr = GetChatRoomMgr(); + status = (int)AddMembers(mgr, pMembers, (QWORD)pWxRoomid, (QWORD)temp); + return status; +} + +int DelChatroomMember(string roomid, string wxids) +{ + int status = -1; + + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return status; + } + + GetChatRoomMgr_t GetChatRoomMgr = (GetChatRoomMgr_t)(g_WeChatWinDllAddr + OS_GET_CHATROOM_MGR); + DelMemberFromChatRoom_t DelMembers = (DelMemberFromChatRoom_t)(g_WeChatWinDllAddr + OS_DELETE_MEMBERS); + + vector vMembers; + vector vWxMembers; + wstringstream wss(String2Wstring(wxids)); + while (wss.good()) { + wstring wstr; + getline(wss, wstr, L','); + vMembers.push_back(wstr); + WxString wxMember(vMembers.back()); + vWxMembers.push_back(wxMember); + } + + WxString *pWxRoomid = NewWxStringFromStr(roomid); + QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; + + QWORD mgr = GetChatRoomMgr(); + status = (int)DelMembers(mgr, pMembers, (QWORD)pWxRoomid); + return status; +} + +int InviteChatroomMember(string roomid, string wxids) +{ + int status = -1; + + if (roomid.empty() || wxids.empty()) { + LOG_ERROR("Empty roomid or wxids."); + return status; + } + + InviteMemberToChatRoom_t InviteMembers = (InviteMemberToChatRoom_t)(g_WeChatWinDllAddr + OS_INVITE_MEMBERS); + + vector vMembers; + vector vWxMembers; + wstringstream wss(String2Wstring(wxids)); + while (wss.good()) { + wstring wstr; + getline(wss, wstr, L','); + vMembers.push_back(wstr); + WxString wxMember(vMembers.back()); + vWxMembers.push_back(wxMember); + } + QWORD temp[2] = { 0 }; + wstring wsRoomid = String2Wstring(roomid); + WxString *pWxRoomid = NewWxStringFromWstr(wsRoomid); + QWORD pMembers = (QWORD) & ((RawVector_t *)&vWxMembers)->start; + + status = (int)InviteMembers((QWORD)wsRoomid.c_str(), pMembers, (QWORD)pWxRoomid, (QWORD)temp); + return status; +} + diff --git a/WeChatFerry/spy/contact_mgmt.cpp b/WeChatFerry/spy/contact_mgmt.cpp new file mode 100644 index 0000000..b00152d --- /dev/null +++ b/WeChatFerry/spy/contact_mgmt.cpp @@ -0,0 +1,193 @@ +#pragma execution_character_set("utf-8") + +#include "contact_mgmt.h" +#include "log.h" +#include "util.h" + +using namespace std; +extern QWORD g_WeChatWinDllAddr; + +#define OS_GET_CONTACT_MGR 0x1B470B0 +#define OS_GET_CONTACT_LIST 0x21A49D0 +#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 GetContacts() +{ + vector 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 + diff --git a/WeChatFerry/spy/exec_sql.cpp b/WeChatFerry/spy/exec_sql.cpp new file mode 100644 index 0000000..5e8b857 --- /dev/null +++ b/WeChatFerry/spy/exec_sql.cpp @@ -0,0 +1,233 @@ +#include + +#include "exec_sql.h" +#include "log.h" +#include "sqlite3.h" +#include "util.h" + +#define OFFSET_DB_INSTANCE 0x59C5B48 +#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 0x5A23888 + +extern UINT64 g_WeChatWinDllAddr; + +typedef map 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 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 rv(field.content.begin() + 1, field.content.end()); + + return rv; + } + + return vector(); +} + diff --git a/WeChatFerry/spy/funcs.cpp b/WeChatFerry/spy/funcs.cpp new file mode 100644 index 0000000..7eefed7 --- /dev/null +++ b/WeChatFerry/spy/funcs.cpp @@ -0,0 +1,445 @@ +#pragma warning(disable : 4244) + +#include "framework.h" +#include +#include +#include +#include + +#include "codec.h" +#include "exec_sql.h" +#include "funcs.h" +#include "log.h" +#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 0x5A20978 +#define OS_GET_SNS_DATA_MGR 0x21E7EC0 +#define OS_GET_SNS_FIRST_PAGE 0x2E37960 +#define OS_GET_SNS_TIMELINE_MGR 0x2DC9470 +#define OS_GET_SNS_NEXT_PAGE 0x2EDF4D0 +#define OS_NEW_CHAT_MSG 0x1B63A50 +#define OS_FREE_CHAT_MSG 0x1B5B160 +#define OS_GET_CHAT_MGR 0x1B8CFD0 +#define OS_GET_MGR_BY_PREFIX_LOCAL_ID 0x2145220 +#define OS_GET_PRE_DOWNLOAD_MGR 0x1C14930 +#define OS_PUSH_ATTACH_TASK 0x1CE57B0 +#define OS_LOGIN_QR_CODE 0x5A26440 + +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 ""; // 错误 +} + +// 创建多级目录 +bool CreateDir(const char* dir) +{ + int m = 0, n; + string str1, str2; + str1 = dir; + str2 = str1.substr(0, 2); + str1 = str1.substr(3, str1.size()); + while (m >= 0) + { + m = str1.find('/'); + + str2 += '/' + str1.substr(0, m); + //判断该目录是否存在 + n = _access(str2.c_str(), 0); + if (n == -1) + { + //创建目录文件 + int flag = _mkdir(str2.c_str()); + if (flag != 0) { //创建失败 + LOG_ERROR("Failed to CreateDir:{}", dir); + return false; + } + } + + str1 = str1.substr(m + 1, str1.size()); + } + LOG_DEBUG("CreateDir {} success.", dir); + return true; +} + +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 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 + "/"); + replace(dst.begin(), dst.end(), '\\', '/'); + + // 判断dir文件夹是否存在,若不存在则创建(否则将无法创建出文件) + if (_access(dst.c_str(), 0) == -1) {//判断该文件夹是否存在 + bool success = CreateDir(dst.c_str()); //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 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 pcm; + vector 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 *pv = (vector *)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(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE; + + char *dataPtr = *reinterpret_cast(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; +} + diff --git a/WeChatFerry/spy/receive_msg.cpp b/WeChatFerry/spy/receive_msg.cpp new file mode 100644 index 0000000..5a7867a --- /dev/null +++ b/WeChatFerry/spy/receive_msg.cpp @@ -0,0 +1,379 @@ +#pragma execution_character_set("utf-8") + +#include "MinHook.h" +#include "framework.h" +#include +#include +#include + +#include "log.h" +#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 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 0x21444B0 +#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 0x2E59320 +#define OS_WXLOG 0x261E760 + +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 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 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(&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(&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(&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; + } +} + diff --git a/WeChatFerry/spy/send_msg.cpp b/WeChatFerry/spy/send_msg.cpp new file mode 100644 index 0000000..75cf6d6 --- /dev/null +++ b/WeChatFerry/spy/send_msg.cpp @@ -0,0 +1,274 @@ + +#include "framework.h" +#include +#include + +#include "exec_sql.h" +#include "log.h" +#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 0x1B63A50 +#define OS_FREE 0x1B5B160 +#define OS_SEND_MSG_MGR 0x1B598E0 +#define OS_SEND_TEXT 0x22CC660 +#define OS_SEND_IMAGE 0x22C1E70 +#define OS_GET_APP_MSG_MGR 0x1B5E880 +#define OS_SEND_FILE 0x20D5FF0 +#define OS_RTM_NEW 0x1B62FA0 +#define OS_RTM_FREE 0x1B62370 +#define OS_SEND_RICH_TEXT 0x20DFFD0 +#define OS_SEND_PAT_MSG 0x2CC4F10 +#define OS_FORWARD_MSG 0x22CBBE0 +#define OS_GET_EMOTION_MGR 0x1BD49A0 +#define OS_SEND_EMOTION 0x21BACD0 +#define OS_XML_BUFSIGN 0x24FB330 +#define OS_SEND_XML 0x20D5120 + +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 vAtWxids; + vector 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); +} + diff --git a/WeChatFerry/spy/spy.cpp b/WeChatFerry/spy/spy.cpp index ae5ee39..be4feab 100644 --- a/WeChatFerry/spy/spy.cpp +++ b/WeChatFerry/spy/spy.cpp @@ -2,7 +2,7 @@ #include -#include "log.hpp" +#include "log.h" #include "rpc_server.h" #include "spy.h" #include "util.h" diff --git a/WeChatFerry/spy/user_info.cpp b/WeChatFerry/spy/user_info.cpp new file mode 100644 index 0000000..a5c132e --- /dev/null +++ b/WeChatFerry/spy/user_info.cpp @@ -0,0 +1,59 @@ +#include "user_info.h" +#include "log.h" +#include "util.h" + +extern UINT64 g_WeChatWinDllAddr; + +#define OS_USER_HOME 0x59F6330 +#define OS_USER_WXID 0x5A20200 +#define OS_USER_NAME 0x5A20368 +#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; +} +