#pragma warning(disable : 4244) #include "misc_manager.h" #include #include #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_types.h" #include "util.h" extern QWORD g_WeChatWinDllAddr; namespace misc { using namespace std; namespace fs = std::filesystem; namespace OsSns = Offsets::Misc::Sns; #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 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 (*)(QWORD); 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); 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 buffer(std::istreambuf_iterator(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; get_sns_data_mgr_t GetSNSDataMgr = (get_sns_data_mgr_t)(g_WeChatWinDllAddr + OsSns::DATA_MGR); get_sns_first_page_t GetSNSFirstPage = (get_sns_first_page_t)(g_WeChatWinDllAddr + 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; get_sns_timeline_mgr_t GetSnsTimeLineMgr = (get_sns_timeline_mgr_t)(g_WeChatWinDllAddr + OsSns::TIMELINE); get_sns_next_page_scene_t GetSNSNextPageScene = (get_sns_next_page_scene_t)(g_WeChatWinDllAddr + 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: 通过文件大小来判断 return 0; } if (db::get_local_id_and_dbidx(id, &localId, &dbIdx) != 0) { LOG_ERROR("获取 localId 失败, 请检查消息 id: {} 是否正确", to_string(id)); return status; } new_chat_msg_t NewChatMsg = (new_chat_msg_t)(g_WeChatWinDllAddr + OS_NEW_CHAT_MSG); free_chat_msg_t FreeChatMsg = (free_chat_msg_t)(g_WeChatWinDllAddr + OS_FREE_CHAT_MSG); get_chat_mgr_t GetChatMgr = (get_chat_mgr_t)(g_WeChatWinDllAddr + OS_GET_CHAT_MGR); get_pre_download_mgr_t GetPreDownLoadMgr = (get_pre_download_mgr_t)(g_WeChatWinDllAddr + OS_GET_PRE_DOWNLOAD_MGR); push_attach_task_t PushAttachTask = (push_attach_task_t)(g_WeChatWinDllAddr + OS_PUSH_ATTACH_TASK); get_mgr_by_prefix_localid_t GetMgrByPrefixLocalId = (get_mgr_by_prefix_localid_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("申请内存失败."); return status; } QWORD pChatMsg = NewChatMsg((QWORD)buff); GetChatMgr(); GetMgrByPrefixLocalId(l.QuadPart, pChatMsg); QWORD type = util::get_qword(reinterpret_cast(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").string(); 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.string()); // 创建父目录,由于路径来源于微信,不做检查 fs::create_directory(save_path.parent_path()); int temp = 1; auto wx_save_path = util::new_wx_string(save_path.string()); auto wx_thumb_path = util::new_wx_string(thumb_path.string()); memcpy(&buff[0x280], wx_thumb_path.get(), sizeof(WxString)); memcpy(&buff[0x2A0], wx_save_path.get(), sizeof(WxString)); memcpy(&buff[0x40C], &temp, sizeof(temp)); QWORD mgr = GetPreDownLoadMgr(); status = (int)PushAttachTask(mgr, pChatMsg, 0, 1); FreeChatMsg(pChatMsg); 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 pcm; SilkDecode(silk, pcm, sr); std::ofstream out(pcmpath, std::ios::binary); if (!out) { LOG_ERROR("创建文件失败: {}", pcmpath.generic_string()); return ""; } out.write(reinterpret_cast(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 *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 = 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() { LPVOID targetAddress = reinterpret_cast(g_WeChatWinDllAddr) + OS_LOGIN_QR_CODE; char *dataPtr = *reinterpret_cast(targetAddress); if (!dataPtr) { LOG_ERROR("获取二维码失败."); return ""; } return "http://weixin.qq.com/x/" + std::string(dataPtr, 22); } 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( 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( 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( 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(out, len, [&](Response &rsp) { rsp.msg.status = refresh_pyq(id); }); } bool rpc_download_attachment(const AttachMsg &att, uint8_t *out, size_t *len) { return fill_response( out, len, [&](Response &rsp) { rsp.msg.status = download_attachment(att.id, att.thumb, att.extra); }); } bool rpc_revoke_message(uint64_t id, uint8_t *out, size_t *len) { return fill_response(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) { return fill_response(out, len, [&](Response &rsp) { OcrResult_t ret = { -1, "" }; ret = get_ocr_result(path); 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( out, len, [&](Response &rsp) { rsp.msg.status = receive_transfer(tf.wxid, tf.tfid, tf.taid); }); } } // namespace misc