From 2591003ce71e5c78478bb8cbb70ff8d2e911f7bf Mon Sep 17 00:00:00 2001 From: yincong Date: Thu, 5 Feb 2026 15:25:48 +0800 Subject: [PATCH] add 4.1.7 image --- frida/check_point_value.js | 81 ----- frida/checkx26.js | 85 ----- frida/enumhook.js | 127 ------- frida/function.js | 71 ---- frida/idapro.js | 45 --- frida/image.js | 556 +++++++++++++++++++------------ frida/log.js | 64 ---- frida/log_memaddress.js | 64 ---- frida/matchFun.js | 36 -- frida/memcpy.js | 72 ---- frida/memo_check.js | 33 -- frida/other_memo_check.js | 17 - idapro/readme.md | 3 +- onebot/script.js | 80 +++-- wechat_version/4_1_6_47_mac.json | 4 +- wechat_version/4_1_7_31_mac.json | 31 +- 16 files changed, 414 insertions(+), 955 deletions(-) delete mode 100644 frida/check_point_value.js delete mode 100644 frida/checkx26.js delete mode 100644 frida/enumhook.js delete mode 100644 frida/function.js delete mode 100644 frida/idapro.js delete mode 100644 frida/log.js delete mode 100644 frida/log_memaddress.js delete mode 100644 frida/matchFun.js delete mode 100644 frida/memcpy.js delete mode 100644 frida/memo_check.js delete mode 100644 frida/other_memo_check.js diff --git a/frida/check_point_value.js b/frida/check_point_value.js deleted file mode 100644 index 56cce56..0000000 --- a/frida/check_point_value.js +++ /dev/null @@ -1,81 +0,0 @@ -const mod = Process.getModuleByName("WeChat"); -console.log("[+] Base Address:", mod.base); -var counterMap = new Map(); - -function checkValid(p) { - if (p.isNull()) { - return false; - } - - if (!p.and(0x7).isNull()) { - return false; - } - return p.compare("0x200000000000") >= 0 && p.compare("0x700000000000000") < 0; -} - -function handlePr(addr, keyword) { - const realAddr = ptr("0x" + addr).sub("0x100000000").add(mod.base); - console.log("[+] real Address:", realAddr) - - Interceptor.attach(realAddr, { - onEnter(args) { - const currentCount = counterMap.get(addr) || 0; - counterMap.set(addr, currentCount + 1); - for (let i = 0; i < 30; i++) { - try { - if (checkValid(args[i])) { - const buf = args[i].readByteArray(128) - if (!buf) { - continue; - } - let s = ""; - const u8 = new Uint8Array(buf); - for (let b of u8) { - if (b >= 0x20 && b <= 0x7E) { - s += String.fromCharCode(b); - } else { - s += "."; - } - } - - if (s.includes(keyword) || keyword === "") { - console.log(`\n[+] ${addr} || arg${i} ${args[i]}`); - console.log(hexdump(args[i], {length: 64})); - console.log(addr + "|| " + s + "\n" + - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - } - } - } catch (e) { - console.log("Enter Error:", e); - } - } - - - }, - - onLeave(retval) { - console.log(`===== ${addr} LEAVE =====`); - console.log(` ${addr} Return value ${retval}`); - } - }); - -} - - -const prs = ["105831328", "1058469dc", "10587c208"] -const k = "o.o.o.o"; -for (let pr of prs) { - handlePr(pr, k); -} - -function ShowCount() { - for (let [addr, count] of counterMap) { - console.log(`${addr}: ${count}`); - } -} - -function clearCount() { - counterMap.clear(); - console.log("Counter cleared."); -} diff --git a/frida/checkx26.js b/frida/checkx26.js deleted file mode 100644 index 37f3cfc..0000000 --- a/frida/checkx26.js +++ /dev/null @@ -1,85 +0,0 @@ -const mod = Process.getModuleByName("WeChat"); -console.log("[+] Base Address:", mod.base); -var counterMap = new Map(); - -function checkValid(p) { - if (p.isNull()) { - return false; - } - - if (!p.and(0x7).isNull()) { - return false; - } - if (p.compare(ptr("0x600000000000")) >= 0 && p.compare(ptr("0x700000000000")) < 0) { - return true; - } - return false; -} - -function handlePr(addr, keyword) { - const realAddr = ptr("0x" + addr).sub("0x100000000").add(mod.base); - console.log("[+] real Address:", realAddr) - - Interceptor.attach(realAddr, { - onEnter(args) { - const currentCount = counterMap.get(addr) || 0; - counterMap.set(addr, currentCount + 1); - - try { - const x26Value = this.context.x26; - console.log(`[!] X26 Register Value: ${x26Value}`); - - - if (checkValid(x26Value)) { - console.log(`\n[+] ${addr} || X26 has a valid pointer: ${x26Value}`); - - const buf = x26Value.readByteArray(128); - if (buf) { - let s = ""; - const u8 = new Uint8Array(buf); - for (let b of u8) { - if (b >= 0x20 && b <= 0x7E) { - s += String.fromCharCode(b); - } else { - s += "."; - } - } - - if (s.includes(keyword) || keyword === "") { - console.log(addr + "|| X26 Read String: " + s + `${u8}`); - // 打印堆栈追踪 - console.log(Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - } - } - } - } catch (e) { - console.log("Enter Error:", e); - } - - - }, - - onLeave(retval) { - } - }); - -} - - -const prs = ["102454D74"] -const k = ""; -for (let pr of prs) { - handlePr(pr, k); -} - -function ShowCount() { - for (let [addr, count] of counterMap) { - console.log(`${addr}: ${count}`); - } -} - -function clearCount() { - counterMap.clear(); - console.log("Counter cleared."); -} diff --git a/frida/enumhook.js b/frida/enumhook.js deleted file mode 100644 index 1389da2..0000000 --- a/frida/enumhook.js +++ /dev/null @@ -1,127 +0,0 @@ -// ====================== -// 安全参数打印 -// ====================== -function safePrintArg(ptr) { - try { - if (!ptr || ptr.isNull()) return ""; - - // 如果能包装成 ObjC 对象 - try { - try { - let o = new ObjC.Object(args[0]); - console.log(o.$className); - - desc = o.toString(); - if (desc.length > 300) desc = desc.slice(0, 300) + "..."; - return `[${cls}] ${desc}`; - } catch (_) { - console.log("raw:", args[0]); - } - } catch (_) { - // 不能包装成 OC 对象,则返回 pointer 地址 - return ptr.toString(); - } - - } catch (e) { - return ``; - } -} - -// ====================== -// Hook 方法工具 -// ====================== -function hookMethod(clsName, methodName) { - try { - const cls = ObjC.classes[clsName]; - if (!cls) return; - - const method = cls[methodName]; - if (!method || !method.implementation) return; - - console.log(`\n🔥 Hooking ${clsName} ${methodName}`); - Interceptor.attach(method.implementation, { - onEnter(args) { - console.log(`\n🚀 ${clsName} ${methodName} called`); - - // 打印参数 x0~x5 - for (let i = 0; i < 6; i++) { - try { - console.log(`arg[${i}]: ${safePrintArg(args[i])}`); - } catch (e) { - console.log(`arg[${i}]: `); - } - } - }, - onLeave(retval) { - console.log(`⬅️ return: ${safePrintArg(retval)}`); - } - }); - } catch (e) { - console.log(`❌ Hook ${clsName} ${methodName} failed: ${e}`); - } -} - -// ====================== -// WeChat 消息加密关键入口列表 -// ====================== -const HOOK_TARGETS = [ - ["WCMessageWrap", "- protobufEncode"], - ["WCMessageWrap", "- serialize"], - - // 发送消息必走 - ["WCProtoBuf", "- encodeMessage:"], - ["WCProtoBuf", "- data"], - - // 尝试抓取 key/data - ["MMEncryptMessage", "- encryptMessage:key:"], - ["WCEncryptHelper", "- encrypt:withKey:"], - ["WCEncryptHelper", "- encryptData:key:"], - - // 发送消息必走路径 - ["WCMessageMgr", "- SendAppMsg:"], - ["WCMessageMgr", "- SendTextMessage:"], - ["WCMessageMgr", "- SendImageMessage:"], - - // 底层 protobuf builder - ["PBGeneratedMessage", "- data"], - ["PBGeneratedMessage", "- serialize"], - - // 经常出现的 encode 函数 - ["MMProtoBase", "- serialize"], - ["MMProtoBase", "- encode"] -]; - -// ====================== -// 挂钩所有关键点 -// ====================== -function hookAll() { - console.log("🚀 WeChat 上层加密 Hook 正在启动...\n"); - - HOOK_TARGETS.forEach(([cls, method]) => { - hookMethod(cls, method); - }); - - // 额外扫描所有类名包含 Encode 或 Message 的 class - console.log("\n🔍 自动扫描 Encode / Message 类..."); - for (const name in ObjC.classes) { - if (!name.includes("Encode") && !name.includes("Message")) continue; - - const cls = ObjC.classes[name]; - const methods = cls.$ownMethods; - - methods.forEach(m => { - if (m.includes("encode") || m.includes("Encrypt") || m.includes("serialize")) { - hookMethod(name, m); - } - }); - } - - console.log("\n🎉 Hook 完成,开始抓取 WeChat 消息明文 / proto / encrypt 信息...\n"); -} - -// ====================== -if (ObjC.available) { - hookAll(); -} else { - setTimeout(hookAll, 1000); -} diff --git a/frida/function.js b/frida/function.js deleted file mode 100644 index f493a72..0000000 --- a/frida/function.js +++ /dev/null @@ -1,71 +0,0 @@ -function dumpMemoryToHex(ptr, size) { - try { - // 1. 读取内存数据 - const buffer = ptr.readByteArray(size); - const data = new Uint8Array(buffer); - - let output = ""; - let line = ""; - - for (let i = 0; i < data.length; i++) { - // 格式化为 0x00 形式 - const hex = "0x" + data[i].toString(16).padStart(2, '0').toUpperCase(); - line += hex; - - // 如果不是最后一个元素,添加逗号和空格 - if (i < data.length - 1) { - line += ", "; - } - - // 每 8 个字节输出一行 - if ((i + 1) % 8 === 0 || i === data.length - 1) { - output += line + "\n"; - line = ""; - } - } - - console.log("==================== MEMORY DUMP ===================="); - console.log(output); - console.log("====================================================="); - - } catch (e) { - console.log("[-] Dump 失败: " + e.message); - } -} - - - -function generateRandom5ByteVarint() { - let res = []; - - // 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机 - for (let i = 0; i < 4; i++) { - let random7Bit = Math.floor(Math.random() * 128); - res.push(random7Bit | 0x80); // 强制设置最高位为 1 - } - - // 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0 - let lastByte = Math.floor(Math.random() * 127) + 1; - res.push(lastByte & 0x7F); // 确保最高位为 0 - - return res; -} - -// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes) -function getVarintTimestampBytes() { - let ts = Math.floor(Date.now() / 1000); - let encodedBytes = []; - let tempTs = ts >>> 0; // 强制转为 32位 无符号整数 - - while (true) { - let byte = tempTs & 0x7F; - tempTs >>>= 7; - if (tempTs !== 0) { - encodedBytes.push(byte | 0x80); - } else { - encodedBytes.push(byte); - break; - } - } - return encodedBytes; -} \ No newline at end of file diff --git a/frida/idapro.js b/frida/idapro.js deleted file mode 100644 index 073aa8e..0000000 --- a/frida/idapro.js +++ /dev/null @@ -1,45 +0,0 @@ -const mod = Process.getModuleByName("WeChat"); -const realAddr = ptr("0x1057ee3a8").sub("0x100000000").add(mod.base); - -console.log("[+] Real Function Address:", realAddr); - -Interceptor.attach(realAddr, { - onEnter(args) { - for (let i = 0; i < 10; i++) { - try { - if (args[i].isNull()) { - continue; - } - console.log(`\n[+] arg${i} ${args[i]}`); - - if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) { - console.log(hexdump(args[i], { - offset: 0, - length: 128 - })); - } - } catch (e) { - console.log("Enter Error:", e); - } - } - - console.log( - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - - }, - - onLeave(retval) { - console.log("===== sub_105808800 LEAVE ====="); - console.log("Return value:", retval); - try { - console.log("Return hexdump:"); - console.log(hexdump(retval, { - offset: 0, - length: 128 - })); - } catch (_) { - } - } -}); - diff --git a/frida/image.js b/frida/image.js index 5a6c9d2..602c95b 100644 --- a/frida/image.js +++ b/frida/image.js @@ -5,48 +5,283 @@ if (!baseAddr) { } console.log("[+] WeChat base address: " + baseAddr); +// -------------------------基础函数分区------------------------- +function toVarint(n) { + let res = []; + while (n >= 128) { + res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 + n = n >> 7; // 右移7位 + } + res.push(n); // 最后一位最高位为0 + return res; +} -// 触发函数地址,不同版本的地址看wechat_version 中的json文件复制过来 -var sendImgMessageCallbackFunc = ptr(0x0); -var imgMessageCallbackFunc1 = baseAddr.add(0x7fa0b18); -var CndOnCompleteAddr = baseAddr.add(0x34154E0); -// 这个必须是绝对位置 -var triggerX1Payload = ptr(0x175ED6600); -var triggerFuncAddr = baseAddr.add(0x448A858); -var req2bufEnterAddr = baseAddr.add(0x34566C0); -var req2bufExitAddr = baseAddr.add(0x34577D8); -var imgProtobufAddr = baseAddr.add(0x2275BFC); -var patchImgProtobufFunc1 = baseAddr.add(0x2275BB8) -var patchImgProtobufFunc2 = baseAddr.add(0x2275BD8); -var imgProtobufDeleteAddr = baseAddr.add(0x2275C14); +function stringToHexArray(str) { + var utf8Str = unescape(encodeURIComponent(str)); + var arr = []; + for (var i = 0; i < utf8Str.length; i++) { + arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值) + } + return arr; +} + + +function generateRandom5ByteVarint() { + let res = []; + + // 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机 + for (let i = 0; i < 4; i++) { + let random7Bit = Math.floor(Math.random() * 128); + res.push(random7Bit | 0x80); // 强制设置最高位为 1 + } + + // 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0 + let lastByte = Math.floor(Math.random() * 127) + 1; + res.push(lastByte & 0x7F); // 确保最高位为 0 + + return res; +} + + +// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes) +function getVarintTimestampBytes() { + let ts = Math.floor(Date.now() / 1000); + let encodedBytes = []; + let tempTs = ts >>> 0; // 强制转为 32位 无符号整数 + + while (true) { + let byte = tempTs & 0x7F; + tempTs >>>= 7; + if (tempTs !== 0) { + encodedBytes.push(byte | 0x80); + } else { + encodedBytes.push(byte); + break; + } + } + return encodedBytes; +} + +function patchString(addr, plainStr) { + const bytes = []; + for (let i = 0; i < plainStr.length; i++) { + bytes.push(plainStr.charCodeAt(i)); + } + + addr.writeByteArray(bytes); + addr.add(bytes.length).writeU8(0); +} + +function generateAESKey() { + const chars = 'abcdef0123456789'; + let key = ''; + for (let i = 0; i < 32; i++) { + key += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return key; +} + +function getCleanString(uint8Array) { + var out = ""; + var i = 0; + var len = uint8Array.length; + + while (i < len) { + var c = uint8Array[i++]; + + // 1. 处理单字节 (ASCII: 0xxxxxxx) + if (c < 0x80) { + // 只保留可见字符 (Space 32 到 ~ 126) + if (c >= 32 && c <= 126) { + out += String.fromCharCode(c); + } + } + // 2. 处理双字节 (110xxxxx 10xxxxxx) + else if ((c & 0xE0) === 0xC0 && i < len) { + var c2 = uint8Array[i++]; + if ((c2 & 0xC0) === 0x80) { + // 这种通常是特殊拉丁字母等,按需保留 + var charCode = ((c & 0x1F) << 6) | (c2 & 0x3F); + out += String.fromCharCode(charCode); + } + } + // 3. 处理三字节 (1110xxxx 10xxxxxx 10xxxxxx) -> 绝大多数汉字在此 + else if ((c & 0xF0) === 0xE0 && i + 1 < len) { + var c2 = uint8Array[i++]; + var c3 = uint8Array[i++]; + if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80) { + var charCode = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); + if ( + (charCode >= 0x4E00 && charCode <= 0x9FA5) || // 基本汉字 + (charCode >= 0x3000 && charCode <= 0x303F) || // 常用中文标点 (。,、) + (charCode >= 0xFF00 && charCode <= 0xFFEF) || // 全角符号/标点 (!:?) + (charCode >= 0x2000 && charCode <= 0x206F) || // 常用标点扩展 (含 \u2005) + (charCode >= 0x3400 && charCode <= 0x4DBF) // 扩展 A 区汉字 + ) { + out += String.fromCharCode(charCode); + } + } + } else if ((c & 0xF8) === 0xF0 && i + 2 < len) { + var c2 = uint8Array[i++]; + var c3 = uint8Array[i++]; + var c4 = uint8Array[i++]; + if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80 && (c4 & 0xC0) === 0x80) { + // 计算 Unicode 码点 + var codePoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F); + + // Emoji 范围通常在 U+1F000 到 U+1F9FF 之间 + if (codePoint >= 0x1F000 && codePoint <= 0x1FADF) { + // 使用 fromCodePoint 处理 4 字节字符 + out += String.fromCodePoint(codePoint); + } + } + } + } + return out; +} + +function generateBytes(n) { + // 生成随机字符串 + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + + for (let i = 0; i < n; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return stringToHexArray(result); +} + +// -------------------------基础函数分区------------------------- + +// -------------------------全局变量分区------------------------- + +// 双方公共使用的地址 +var triggerX1Payload; +var triggerX0; +var req2bufEnterAddr = baseAddr.add(0x35CEDBC); +var req2bufExitAddr = baseAddr.add(0x35CFF94); +var sendFuncAddr = baseAddr.add(0x4A49BC4); +var insertMsgAddr = ptr(0); +var sendMsgType = ""; + +// 图片消息全局变量 +var uploadImageAddr = baseAddr.add(0x47D5D64); + +var imgProtobufAddr = baseAddr.add(0x237DFE0); +var patchImgProtobufFunc1 = baseAddr.add(0x237DF9C) +var patchImgProtobufFunc2 = baseAddr.add(0x237DFBC); +var imgProtobufDeleteAddr = baseAddr.add(0x237DFF8); +var CndOnCompleteAddr = baseAddr.add(0x358D98C); +var imgMessageCallbackFunc1 = baseAddr.add(0x865D680); -// 消息体的一些指针地址 var imgCgiAddr = ptr(0); var sendImgMessageAddr = ptr(0); var imgMessageAddr = ptr(0); -var contentAddr = ptr(0); -var insertMsgAddr = ptr(0); -var protoX1PayloadAddr = ptr(0); - -// 消息的taskId -var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复 -var receiverGlobal = "wxid_7wd1ece99f7i21" -var senderGlobal = "wxid_ldftuhe36izg19" -var lastSendTime = 0; - -var globalImageCdnKey = ""; -var globalAesKey1 = ""; -var globalMd5Key = ""; +var imgProtoX1PayloadAddr = ptr(0); var uploadGlobalX0 = ptr(0) var uploadFunc1Addr = ptr(0) +var uploadFunc1 = baseAddr.add(0x086EEB8C8) var uploadFunc2Addr = ptr(0) +var uploadFunc2 = baseAddr.add(0x086E32AB0) var imageIdAddr = ptr(0) var md5Addr = ptr(0) var uploadAesKeyAddr = ptr(0) var ImagePathAddr1 = ptr(0) var uploadImagePayload = ptr(0); +var globalImageCdnKey = ""; +var globalAesKey1 = ""; +var globalMd5Key = ""; + +// 发送消息的全局变量 +var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复 +var receiverGlobal = "wxid_" +var contentGlobal = ""; +var senderGlobal = "wxid_" +var lastSendTime = 0; +var atUserGlobal = ""; + +const imageCp = generateBytes(16) // m30c4674f5a0b9d + +// -------------------------全局变量分区------------------------- + + + +// -------------------------Req2Buf公共部分分区------------------------- +function attachReq2buf() { + console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr); + + // 2. 开始拦截 + Interceptor.attach(req2bufEnterAddr, { + onEnter: function (args) { + if (!this.context.x1.equals(taskIdGlobal)) { + return; + } + + console.log("[+] 已命中目标Req2Buf地址:0x1033EE8E8 taskId:" + taskIdGlobal + "base:" + baseAddr); + + // 3. 获取 X24 寄存器的值 + const x24_base = this.context.x24; + insertMsgAddr = x24_base.add(0x60); + console.log("[+] 当前 Req2Buf X24 基址: " + x24_base); + + if (sendMsgType === "text") { + insertMsgAddr.writePointer(sendTextMessageAddr); + console.log("[+] 发送文本消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendTextMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "img") { + insertMsgAddr.writePointer(sendImgMessageAddr); + console.log("[+] 发送图片消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendImgMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } + } + }); + + // 在出口处拦截req2buf,把insertMsgAddr设置为0,避免被垃圾回收导致整个程序崩溃 + console.log("[+] Target Req2buf leave Address: " + req2bufExitAddr); + Interceptor.attach(req2bufExitAddr, { + onEnter: function (args) { + if (!this.context.x25.equals(taskIdGlobal)) { + return; + } + insertMsgAddr.writeU64(0x0); + console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer()); + taskIdGlobal = 0; + receiverGlobal = ""; + senderGlobal = ""; + contentGlobal = ""; + atUserGlobal = ""; + send({ + type: "finish", + }) + } + }); +} + +setImmediate(attachReq2buf); + +function AttachSendTextProto() { + Interceptor.attach(sendFuncAddr, { + onEnter: function (args) { + if (triggerX1Payload) { + return + } + + triggerX0 = this.context.x0; + triggerX1Payload = this.context.x1; + console.log(`[+] 捕获到 StartTask 调用,X0地址:${triggerX0}, Payload 地址: ${triggerX1Payload}`); + } + }) +} + +setImmediate(AttachSendTextProto); + + +// -------------------------Req2Buf公共部分分区------------------------- + +// -------------------------发送图片消息分区------------------------- // 初始化进行内存的分配 function setupSendImgMessageDynamic() { @@ -56,27 +291,22 @@ function setupSendImgMessageDynamic() { // 分配原则:字符串给 64-128 字节,结构体按实际大小分配 imgCgiAddr = Memory.alloc(128); sendImgMessageAddr = Memory.alloc(256); - imgMessageAddr = Memory.alloc(512); - contentAddr = Memory.alloc(16); - - uploadFunc1Addr = Memory.alloc(16); - uploadFunc2Addr = Memory.alloc(16); - imageIdAddr = Memory.alloc(128); - md5Addr = Memory.alloc(128); - uploadAesKeyAddr = Memory.alloc(128); - ImagePathAddr1 = Memory.alloc(128); - uploadImagePayload = Memory.alloc(512); - - + imgMessageAddr = Memory.alloc(256); + uploadFunc1Addr = Memory.alloc(24); + uploadFunc2Addr = Memory.alloc(24); + imageIdAddr = Memory.alloc(256); + md5Addr = Memory.alloc(256); + uploadAesKeyAddr = Memory.alloc(256); + ImagePathAddr1 = Memory.alloc(256); + uploadImagePayload = Memory.alloc(1024); // A. 写入字符串内容 patchString(imgCgiAddr, "/cgi-bin/micromsg-bin/uploadmsgimg"); - patchString(contentAddr, " "); // B. 构建 SendMessage 结构体 (X24 基址位置) sendImgMessageAddr.add(0x00).writeU64(0); sendImgMessageAddr.add(0x08).writeU64(0); - sendImgMessageAddr.add(0x10).writePointer(sendImgMessageCallbackFunc); + sendImgMessageAddr.add(0x10).writeU64(0); sendImgMessageAddr.add(0x18).writeU64(1); sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); sendImgMessageAddr.add(0x28).writePointer(imgMessageAddr); @@ -101,21 +331,21 @@ function setupSendImgMessageDynamic() { console.log(" [+] Dynamic Memory Setup Complete. - Message Object: " + imgMessageAddr); - uploadFunc1Addr.writePointer(baseAddr.add(0x802b8b0)); - uploadFunc2Addr.writePointer(baseAddr.add(0x7fd5908)); + uploadFunc1Addr.writePointer(uploadFunc1); + uploadFunc2Addr.writePointer(uploadFunc2); } setImmediate(setupSendImgMessageDynamic); -function patchProtoBuf() { +function patchImgProtoBuf() { Memory.patchCode(patchImgProtobufFunc1, 4, code => { const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc1}); cw.putNop(); cw.flush(); }); - console.log("[+] Patching BL to NOP at " + patchImgProtobufFunc1 + " completed."); + console.log("[+] Patching patchImgProtobufFunc1 " + patchImgProtobufFunc1 + " 成功."); Memory.patchCode(patchImgProtobufFunc2, 4, code => { const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc2}); @@ -123,7 +353,7 @@ function patchProtoBuf() { cw.flush(); }); - console.log("[+] Patching BL to NOP at " + patchImgProtobufFunc2 + " completed."); + console.log("[+] Patching patchImgProtobufFunc2 " + patchImgProtobufFunc2 + " 成功."); Memory.patchCode(imgProtobufDeleteAddr, 4, code => { const cw = new Arm64Writer(code, {pc: imgProtobufDeleteAddr}); @@ -131,15 +361,19 @@ function patchProtoBuf() { cw.flush(); }); - console.log("[+] Patching BL DELETE to NOP at " + imgProtobufDeleteAddr + " completed."); + console.log("[+] Patching imgProtobufDeleteAddr " + imgProtobufDeleteAddr + " 成功."); } -setImmediate(patchProtoBuf); -function manualTrigger(taskId, sender, receiver) { +setTimeout(function () { + console.log("[+] 2秒等待结束,准备执行 Patch..."); + patchImgProtoBuf(); +}, 2000); + +function triggerSendImgMessage(taskId, sender, receiver) { console.log("[+] Manual Trigger Started..."); - if (!taskId || !receiver) { - console.error("[!] taskId or Receiver or Content is empty!"); + if (!taskId || !receiver || !sender) { + console.error("[!] taskId or receiver or sender is empty!"); return "fail"; } @@ -179,7 +413,7 @@ function manualTrigger(taskId, sender, receiver) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xB0 - 0xC0, 0x66, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0xB8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 @@ -206,140 +440,57 @@ function manualTrigger(taskId, sender, receiver) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x178 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x180 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 0x188 - 0x98, 0x67, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198 ]; - - // 从 0x175ED6604 开始写入 Payload triggerX1Payload.writeU32(taskIdGlobal); triggerX1Payload.add(0x04).writeByteArray(payloadData); triggerX1Payload.add(0x18).writePointer(imgCgiAddr); + triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); + triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); + sendMsgType = "img" console.log("finished init payload") - - const MMStartTask = new NativeFunction(triggerFuncAddr, 'int64', ['pointer']); + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); // 5. 调用函数 try { - const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600 - console.log(`[+] Calling MMStartTask at ${triggerFuncAddr} with args: (${arg2})`); - const result = MMStartTask(arg2); - console.log("[+] Execution MMStartTask Success. Return value: " + result); + const result = MMStartTask(triggerX0, triggerX1Payload); + console.log(`[+] Execution StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}) Success. Return value: ` + result); return "ok"; } catch (e) { - console.error("[!] Error trigger function during execution: " + e); + console.error(`[!] Error trigger StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); return "fail"; } } -// ReqBuf 进行拦截,替换入参数的消息指针 -function attachReq2buf() { - - console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr); - - // 2. 开始拦截 - Interceptor.attach(req2bufEnterAddr, { - onEnter: function (args) { - if (!this.context.x1.equals(taskIdGlobal)) { - return; - } - - console.log("[+] 已命中目标Req2Buf地址:0x1033EE8E8 taskId:" + taskIdGlobal + "base:" + baseAddr); - - // 3. 获取 X24 寄存器的值 - const x24_base = this.context.x24; - insertMsgAddr = x24_base.add(0x60); - console.log("[+] 当前 Req2Buf X24 基址: " + x24_base); - - if (typeof sendImgMessageAddr !== 'undefined') { - insertMsgAddr.writePointer(sendImgMessageAddr); - console.log("[+] 成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendImgMessageAddr + - "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); - console.log(hexdump(insertMsgAddr, { - offset: 0, - length: 16, - header: true, - ansi: true - })) - console.log(hexdump(sendImgMessageAddr, { - offset: 0, - length: 48, - header: true, - ansi: true - })) - } else { - console.error("[!] 错误: 变量 sendMessageAddr 未定义,请确保已运行分配逻辑。"); - } - } - }); - - // 在出口处拦截req2buf,把insertMsgAddr设置为0,避免被垃圾回收导致整个程序崩溃 - console.log("[+] Target Req2buf leave Address: " + req2bufExitAddr); - Interceptor.attach(req2bufExitAddr, { - onEnter: function (args) { - if (!this.context.x25.equals(taskIdGlobal)) { - return; - } - insertMsgAddr.writeU64(0x0); - console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer()); - receiverGlobal = ""; - taskIdGlobal = 0; - send({ - type: "finish", - }) - } - }); -} - -setImmediate(attachReq2buf); - - -function stringToHexArray(str) { - var utf8Str = unescape(encodeURIComponent(str)); - var arr = []; - for (var i = 0; i < utf8Str.length; i++) { - arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值) - } - return arr; -} - // 拦截 Protobuf 编码逻辑,注入自定义 Payload function attachProto() { - console.log("[+] proto注入拦截目标地址: " + protoX1PayloadAddr); - protoX1PayloadAddr = Memory.alloc(1024); - console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr); + console.log("[+] proto注入拦截目标地址: " + imgProtoX1PayloadAddr); + imgProtoX1PayloadAddr = Memory.alloc(1024); + console.log("[+] Frida 分配的 Payload 地址: " + imgProtoX1PayloadAddr); Interceptor.attach(imgProtobufAddr, { onEnter: function (args) { console.log("[+] Protobuf 拦截命中"); - // var sp = this.context.sp; - // var firstValue = sp.readU32(); - // if (firstValue !== taskIdGlobal) { - // console.log("[+] Protobuf 拦截未命中,跳过..."); - // return; - // } - const type = [0x0A, 0x40, 0x0A, 0x01, 0x00] - const msgId = [0x10, 0xc6, 0xbc, 0x90, 0xb9, 0x08] // 时间戳 + const msgId = [0x10].concat(generateRandom5ByteVarint()) const cpHeader = [0x1A, 0x10] - // m30c4674f5a0b9d - const cp = [0x6D, 0x33, 0x30, 0x63, 0x34, 0x36, 0x37, 0x34, 0x66, 0x35, 0x61, 0x30, 0x62, 0x39, 0x64, 0x30] const randomId = [0x20, 0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01] const sysHeader = [0x2A, 0x15] // UnifiedPCMac 26 arm64 const sys = [0x55, 0x6E, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x43, 0x4D, 0x61, 0x63, 0x20, 0x32, 0x36, 0x20, 0x61, 0x72, 0x6D, 0x36, 0x34, 0x30] - // 45872025384@chatroom_176787000_60_xwechat_1 只需要改这个时间戳就能重复发送 const receiverMsgId = stringToHexArray(receiverGlobal).concat([0x5F]) .concat(stringToHexArray(Math.floor(Date.now() / 1000).toString())) .concat([0x5F, 0x31, 0x36, 0x30, 0x5F, 0x78, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5F, 0x33]); // 0xb0, 0x02 是长度,需要看一下什么的长度 - const msgIdHeader = [0xb0, 0x02, 0x12, 0x2E, 0x0A, 0x2C] + const msgIdHeader = [0xb0, 0x02, 0x12, receiverMsgId.length + 2, 0x0A, receiverMsgId.length] const senderHeader = [0x1A, senderGlobal.length + 2, 0x0A, senderGlobal.length]; // wxid_xxxx 或者 chatroom @@ -395,29 +546,21 @@ function attachProto() { 0xC8, 0x02, 0x00, 0x00 // 0x3E8 ] - const finalPayload = type.concat(msgId, cpHeader, cp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId, + const finalPayload = type.concat(msgId, cpHeader, imageCp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId, senderHeader, sender, receiverHeader, receiver, randomId1, type1, randomId2, randomId3, randomId4, htmlHeader, html, cdnHeader, cdn, cdn2Header, cdn2, aesKeyHeader, aesKey, randomId5, cdn3Header, cdn3, randomId6, randomId7, randomId8, aesKey1Header, aesKey1, md5Header, me5Key, randomId9, left0) console.log("[+] Payload 准备写入"); - // dumpMemoryToHex(this.context.x1, 1024) - // console.log("[+] 寄存器修改完成: X1=" + hexdump(this.context.x1, { - // offset: 0, - // length: 1024, - // header: true, - // ansi: true - // })); - - protoX1PayloadAddr.writeByteArray(finalPayload); + imgProtoX1PayloadAddr.writeByteArray(finalPayload); console.log("[+] Payload 已写入,长度: " + finalPayload.length); - this.context.x1 = protoX1PayloadAddr; + this.context.x1 = imgProtoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); - console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(protoX1PayloadAddr, { + console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(imgProtoX1PayloadAddr, { offset: 0, - length: 1024, + length: 256, header: true, ansi: true })); @@ -427,20 +570,8 @@ function attachProto() { setImmediate(attachProto); -function toVarint(n) { - let res = []; - while (n >= 128) { - res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 - n = n >> 7; // 右移7位 - } - res.push(n); // 最后一位最高位为0 - return res; -} - - - -function manualUpload(receiver, md5, imagePath) { +function triggerUploadImg(receiver, md5, imagePath) { const payload = [ 0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 函数 10802b8b0 的指针 0x00, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 函数 107fd5908 的指针 @@ -463,7 +594,7 @@ function manualUpload(receiver, md5, imagePath) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0x01, 0x00, 0x00, 0x00, // 0x98 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xa0 - 0xA0, 0xBE, 0x2D, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 某个aesid 7ea41d569f705357780968e9104284cf 0xa8 + 0xA0, 0xBE, 0x2D, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0xa8 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xb0 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0xb8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -501,7 +632,7 @@ function manualUpload(receiver, md5, imagePath) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1c0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1c8 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d0 - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d8 有个指针 + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1e0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1e8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1f0 @@ -542,60 +673,60 @@ function manualUpload(receiver, md5, imagePath) { uploadImagePayload.add(0x140).writePointer(ImagePathAddr1); uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr); + const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); - const targetAddr = baseAddr.add(0x45DC834); - const startC2cUpload = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']); - - console.log("开始手动触发 C2C 上传..."); - const result = startC2cUpload(uploadGlobalX0, uploadImagePayload); + console.log(`开始手动触发 C2C 上传 X0 ${uploadGlobalX0}, X1: ${uploadImagePayload}` + hexdump(uploadImagePayload, { + offset: 0, + length: 256, + header: true, + ansi: true + })); + const result = startUploadMedia(uploadGlobalX0, uploadImagePayload); console.log("调用结果: " + result); } function attachUploadMedia() { - const targetAddr = baseAddr.add(0x45DC85C); - Interceptor.attach(targetAddr, { + Interceptor.attach(uploadImageAddr.add(0x10), { onEnter: function (args) { - console.log("[+] enter UploadMedia"); uploadGlobalX0 = this.context.x0; - console.log("UploadMedia x1: " + uploadGlobalX0); + const x1 = this.context.x1; + const selfId = x1.add(0x68).readUtf8String(); + const imagePath = x1.add(0xe0).readPointer().readUtf8String(); + send({ + type: "upload", + self_id: selfId, + }) + console.log("UploadMedia x0: " + uploadGlobalX0 + " imagePath: " + imagePath + " selfId: " + selfId); } }) } setImmediate(attachUploadMedia); - -function patchString(addr, plainStr) { - const bytes = []; - for (let i = 0; i < plainStr.length; i++) { - bytes.push(plainStr.charCodeAt(i)); - } - - addr.writeByteArray(bytes); - addr.add(bytes.length).writeU8(0); -} - -function generateAESKey() { - const chars = 'abcdef0123456789'; - let key = ''; - for (let i = 0; i < 32; i++) { - key += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return key; -} - - function patchCdnOnComplete() { Interceptor.attach(CndOnCompleteAddr, { onEnter: function (args) { - console.log("[+] enter CndOnCompleteAddr"); + console.log("[+] enter CndOnUploadCompleteAddr"); try { const x2 = this.context.x2; globalImageCdnKey = x2.add(0x60).readPointer().readUtf8String(); globalAesKey1 = x2.add(0x78).readPointer().readUtf8String(); globalMd5Key = x2.add(0x90).readPointer().readUtf8String(); - console.log("[+] globalImageCdnKey: " + globalImageCdnKey + " globalAesKey1: " + globalAesKey1 + " globalAesKey2: " + globalMd5Key); + const targetId = x2.add(0x40).readUtf8String(); + console.log("[+] globalImageCdnKey: " + globalImageCdnKey + " globalAesKey1: " + globalAesKey1 + + " globalMd5Key: " + globalMd5Key); + send({ + type: "finish", + }) + + if (globalImageCdnKey !== "" && globalImageCdnKey != null && globalAesKey1 !== "" && globalAesKey1 != null && + globalMd5Key !== "" && globalMd5Key != null) { + send({ + type: "upload_finish", + target_id: targetId, + }) + } } catch (e) { console.log("[-] Memory access error at onEnter: " + e); } @@ -605,7 +736,10 @@ function patchCdnOnComplete() { setImmediate(patchCdnOnComplete) + rpc.exports = { - manualTrigger: manualTrigger, - manualUpload: manualUpload -}; \ No newline at end of file + triggerSendImgMessage: triggerSendImgMessage, + triggerUploadImg: triggerUploadImg, +}; + +// -------------------------发送图片消息分区------------------------- diff --git a/frida/log.js b/frida/log.js deleted file mode 100644 index 130fe36..0000000 --- a/frida/log.js +++ /dev/null @@ -1,64 +0,0 @@ -const mod = Process.getModuleByName("WeChat"); -const realAddr = ptr("0x105efadc0").sub("0x100000000").add(mod.base); - -console.log("[+] Real Function Address:", realAddr); - -Interceptor.attach(realAddr, { - onEnter(args) { - for (let i = 0; i < 10; i++) { - try { - if (args[i].isNull()) { - continue; - } - - if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) { - const buf = args[i].readByteArray(128) - if (!buf) { - continue; - } - let s = ""; - const u8 = new Uint8Array(buf); - for (let b of u8) { - if (b >= 0x20 && b <= 0x7E) { - s += String.fromCharCode(b); - } else { - s += "."; - } - } - - console.log(`\n[+] arg${i} ${args[i]} ${s}`); - console.log( - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - - // if (s.includes(".cc")) { - // console.log(`\n[+] arg${i} ${args[i]} ${s}`); - // console.log(hexdump(args[i], { length: 128 })); - // console.log( - // Thread.backtrace(this.context, Backtracer.ACCURATE) - // .map(DebugSymbol.fromAddress).join('\n')); - // return; - // } - } - } catch (e) { - console.log("Enter Error:", e); - } - } - - - }, - - onLeave(retval) { - // console.log("===== sub_105808800 LEAVE ====="); - // console.log("Return value:", retval); - // try { - // console.log("Return hexdump:"); - // console.log(hexdump(retval, { - // offset: 0, - // length: 128 - // })); - // } catch (_) { - // } - } -}); - diff --git a/frida/log_memaddress.js b/frida/log_memaddress.js deleted file mode 100644 index 35a3b6c..0000000 --- a/frida/log_memaddress.js +++ /dev/null @@ -1,64 +0,0 @@ -const mod = Process.getModuleByName("WeChat"); -const realAddr = ptr("0x10a1f083c"); - -console.log("[+] Real Function Address:", realAddr); - -Interceptor.attach(realAddr, { - onEnter(args) { - for (let i = 0; i < 10; i++) { - try { - if (args[i].isNull()) { - continue; - } - - if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) { - const buf = args[i].readByteArray(128) - if (!buf) { - continue; - } - let s = ""; - const u8 = new Uint8Array(buf); - for (let b of u8) { - if (b >= 0x20 && b <= 0x7E) { - s += String.fromCharCode(b); - } else { - s += "."; - } - } - - console.log(`\n[+] arg${i} ${args[i]} ${s}`); - console.log( - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - - // if (s.includes(".cc")) { - // console.log(`\n[+] arg${i} ${args[i]} ${s}`); - // console.log(hexdump(args[i], { length: 128 })); - // console.log( - // Thread.backtrace(this.context, Backtracer.ACCURATE) - // .map(DebugSymbol.fromAddress).join('\n')); - // return; - // } - } - } catch (e) { - console.log("Enter Error:", e); - } - } - - - }, - - onLeave(retval) { - // console.log("===== sub_105808800 LEAVE ====="); - // console.log("Return value:", retval); - // try { - // console.log("Return hexdump:"); - // console.log(hexdump(retval, { - // offset: 0, - // length: 128 - // })); - // } catch (_) { - // } - } -}); - diff --git a/frida/matchFun.js b/frida/matchFun.js deleted file mode 100644 index 55a61a9..0000000 --- a/frida/matchFun.js +++ /dev/null @@ -1,36 +0,0 @@ -[ - "sendmsg", - "newsendmsg", - "SendMsg", - "SendMsgFH" -].forEach(sig => { - let arr = DebugSymbol.findFunctionsMatching("*" + sig + "*"); - arr.forEach(target => { - console.log("Hooking:", target); - - Interceptor.attach(ptr(target), { - onEnter(args) { - console.log("🚀 Called:", target); - - // 打印调用堆栈 - console.log( - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress) - .join("\n") - ); - - // 打印第 1 个参数,一般就是 protobuf buffer - try { - console.log("arg0:", args[0]); - console.log(hexdump(args[0], { length: 256 })); - } catch(e){} - - // 打印第 2 个参数(通常是长度) - console.log("arg1:", args[1]); - }, - onLeave(retval) { - console.log("⬅️ return:", retval); - } - }); - }); -}); diff --git a/frida/memcpy.js b/frida/memcpy.js deleted file mode 100644 index 23df56f..0000000 --- a/frida/memcpy.js +++ /dev/null @@ -1,72 +0,0 @@ -const libc = Process.getModuleByName("libSystem.B.dylib"); - -// 2. 拦截 memcpy -const memcpy_ptr = libc.findExportByName("memcpy"); - -if (memcpy_ptr) { - console.log("[+] Hooking memcpy."); - Interceptor.attach(memcpy_ptr, { - onEnter(args) { - console.log("===== Enter ====="); - for (let i = 0; i < 10; i++) { - try { - if (checkValid(args[i])) { - const buf = args[i].readByteArray(128) - if (!buf) { - continue; - } - console.log(`\n[+] arg${i} ${args[i]}`); - let s = ""; - const u8 = new Uint8Array(buf); - for (let b of u8) { - if (b >= 0x20 && b <= 0x7E) { - s += String.fromCharCode(b); - } else { - s += "."; - } - } - - if (s.includes("3.3.3.3")) { - console.log(`\n[+] arg${i} ${args[i]}`); - console.log(hexdump(args[i], { length: 64 })); - console.log( - Thread.backtrace(this.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress).join('\n')); - return; - } - } - } catch (e) { - - } - } - }, - onLeave(retval) { - // console.log("===== LEAVE ====="); - // console.log("Return value:", retval); - // try { - // if (checkValid(retval)) { - // console.log("Return hexdump:"); - // console.log(hexdump(retval, { - // offset: 0, - // length: 128 - // })); - // } - // } catch (_) { - // } - } - }); -} - -function checkValid(p) { - if (p.isNull()) { - return false; - } - - if (!p.and(0x7).isNull()) { - return false; - } - if (p.compare(ptr("0x600000000000")) >= 0 && p.compare(ptr("0x700000000000")) < 0) { - return true; - } - return false; -} \ No newline at end of file diff --git a/frida/memo_check.js b/frida/memo_check.js deleted file mode 100644 index e43f6fc..0000000 --- a/frida/memo_check.js +++ /dev/null @@ -1,33 +0,0 @@ -const targetModule = Process.findModuleByName("WeChat"); // 仅关注主二进制文件 -console.log("Module:", targetModule.base); - -function dectectPr(p) { - const targetPtr = ptr("0x" + p); - - MemoryAccessMonitor.enable( - { - base: targetPtr, - size: 0x10 - }, - { - onAccess(details) { - console.log("from:", details.from); - if (details.from.compare("0x180000000") < 0) { - console.log("Operation:", details.operation, p); // read / write / exec - console.log("From:", details.from.sub(targetModule.base).add(0x100000000).toString(), p); - console.log("Address:", details.address, p); - console.log( - Thread.backtrace(details.context, Backtracer.ACCURATE) - .map(DebugSymbol.fromAddress) - .join("\n") - ); - } - } - }); - -} - -const ps = ["60000132EC32"] -for (let i = 0; i < ps.length; i++) { - dectectPr(ps[i]); -} \ No newline at end of file diff --git a/frida/other_memo_check.js b/frida/other_memo_check.js deleted file mode 100644 index c32be8b..0000000 --- a/frida/other_memo_check.js +++ /dev/null @@ -1,17 +0,0 @@ -const idaAddr = ptr("0x6000025A96DA"); -console.log("start monitor"); - -MemoryAccessMonitor.enable( - { - base: idaAddr, - size: 0x40 // buffer 大小 - }, - { - onAccess(details) { - console.log("memo Details:", JSON.stringify(details)); - console.log("0x6000029C6DF0 Access by:", DebugSymbol.fromAddress(details.from)); - console.log(hexdump(idaAddr, { length: 0x40 })); - } - } -); - diff --git a/idapro/readme.md b/idapro/readme.md index 8eae417..ef5f095 100644 --- a/idapro/readme.md +++ b/idapro/readme.md @@ -1,3 +1,4 @@ 使用 local ARM mac debug sudo /Applications/IDA\ Professional\ 9.1.app/Contents/MacOS/ida -就能够attach \ No newline at end of file +就能够attach +/Applications/IDA\ Professional\ 9.1.app/Contents/MacOS/dbgsrv/mac_server_arm \ No newline at end of file diff --git a/onebot/script.js b/onebot/script.js index c0da663..a82d5a0 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -219,12 +219,13 @@ var textCgiAddr = ptr(0); var sendTextMessageAddr = ptr(0); var textMessageAddr = ptr(0); var textProtoX1PayloadAddr = ptr(0); -var sendMessageCallbackFunc = ptr(0); +var sendMessageCallbackFunc = baseAddr.add({{.sendMessageCallbackFunc}}); var messageCallbackFunc1 = ptr(0); // 双方公共使用的地址 -var triggerX1Payload = ptr({{.triggerX1Payload}}); +var triggerX1Payload; +var triggerX0; var req2bufEnterAddr = baseAddr.add({{.req2bufEnterAddr}}); var req2bufExitAddr = baseAddr.add({{.req2bufExitAddr}}); var sendFuncAddr = baseAddr.add({{.sendFuncAddr}}); @@ -233,7 +234,7 @@ var sendMsgType = ""; var buf2RespAddr = baseAddr.add({{.buf2RespAddr}}); // 图片消息全局变量 -var sendImgMessageCallbackFunc = ptr(0); +var sendImgMessageCallbackFunc = baseAddr.add({{.sendImgMessageCallbackFunc}}); var uploadImageAddr = baseAddr.add({{.uploadImageAddr}}); var imgProtobufAddr = baseAddr.add({{.imgProtobufAddr}}); var patchImgProtobufFunc1 = baseAddr.add({{.patchImgProtobufFunc1}}) @@ -247,7 +248,9 @@ var imgMessageAddr = ptr(0); var imgProtoX1PayloadAddr = ptr(0); var uploadGlobalX0 = ptr(0) var uploadFunc1Addr = ptr(0) +var uploadFunc1 = baseAddr.add({{.uploadFunc1}}); var uploadFunc2Addr = ptr(0) +var uploadFunc2 = baseAddr.add({{.uploadFunc2}}); var imageIdAddr = ptr(0) var md5Addr = ptr(0) var uploadAesKeyAddr = ptr(0) @@ -288,7 +291,7 @@ function setupSendTextMessageDynamic() { // B. 构建 sendTextMessageAddr 结构体 (X24 基址位置) sendTextMessageAddr.add(0x00).writeU64(0); sendTextMessageAddr.add(0x08).writeU64(0); - sendTextMessageAddr.add(0x10).writePointer(sendMessageCallbackFunc); + sendTextMessageAddr.add(0x10).writeU64(0); sendTextMessageAddr.add(0x18).writeU64(1); sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); sendTextMessageAddr.add(0x28).writePointer(textMessageAddr); // 指向动态分配的 Message @@ -301,7 +304,7 @@ function setupSendTextMessageDynamic() { })); // C. 构建 Message 结构体 - textMessageAddr.add(0x00).writePointer(messageCallbackFunc1); + textMessageAddr.add(0x00).writePointer(sendMessageCallbackFunc); textMessageAddr.add(0x08).writeU32(taskIdGlobal); textMessageAddr.add(0x0c).writeU32(0x20a); textMessageAddr.add(0x10).writeU64(0x3); @@ -366,7 +369,7 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { const payloadData = [ 0x0A, 0x02, 0x00, 0x00, // 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 - 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // 0x10 + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x10 0x40, 0xec, 0x0e, 0x12, 0x01, 0x00, 0x00, 0x00, // 0x18 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x28 @@ -387,7 +390,7 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xB0 - 0xC0, 0x66, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0xB8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 @@ -404,6 +407,7 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x128 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x130 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x138 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x138 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x140 0x01, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x148 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150 @@ -414,30 +418,47 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x178 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x180 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 0x188 - 0x98, 0x67, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198 ]; triggerX1Payload.writeU32(taskIdGlobal); triggerX1Payload.add(0x04).writeByteArray(payloadData); triggerX1Payload.add(0x18).writePointer(textCgiAddr); + triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); + triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); sendMsgType = "text" console.log("finished init payload") - const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer']); + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); // 5. 调用函数 try { - // const arg1 = globalMessagePtr; // 第一个指针参数 - const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600 - const result = MMStartTask(arg2); - console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${arg2}) Success. Return value: ` + result); + const result = MMStartTask(triggerX0, triggerX1Payload); + console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}) Success. Return value: ` + result); return "ok"; } catch (e) { - console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} during execution: ` + e); + console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); return "fail"; } } +function AttachSendTextProto() { + Interceptor.attach(sendFuncAddr, { + onEnter: function (args) { + + if (triggerX1Payload) { + return + } + + triggerX0 = this.context.x0; + triggerX1Payload = this.context.x1; + console.log(`[+] 捕获到 StartTask 调用,X0地址:${triggerX0}, Payload 地址: ${triggerX1Payload}`); + } + }) +} + +setImmediate(AttachSendTextProto); + // 拦截 SendTextProto 编码逻辑,注入自定义 Payload function attachSendTextProto() { console.log("[+] proto注入拦截目标地址: " + protobufAddr); @@ -528,20 +549,14 @@ function attachReq2buf() { insertMsgAddr = x24_base.add(0x60); console.log("[+] 当前 Req2Buf X24 基址: " + x24_base); - - // todo 修改为判断是发送图片还是文本 - if (typeof sendTextMessageAddr !== 'undefined') { - if (sendMsgType === "text") { - insertMsgAddr.writePointer(sendTextMessageAddr); - console.log("[+] 发送文本消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendTextMessageAddr + - "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); - } else if (sendMsgType === "img") { - insertMsgAddr.writePointer(sendImgMessageAddr); - console.log("[+] 发送图片消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendImgMessageAddr + - "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); - } - } else { - console.error("[!] 错误: 变量 sendTextMessageAddr 未定义,请确保已运行分配逻辑。"); + if (sendMsgType === "text") { + insertMsgAddr.writePointer(sendTextMessageAddr); + console.log("[+] 发送文本消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendTextMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "img") { + insertMsgAddr.writePointer(sendImgMessageAddr); + console.log("[+] 发送图片消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendImgMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); } } }); @@ -596,7 +611,7 @@ function setupSendImgMessageDynamic() { // B. 构建 SendMessage 结构体 (X24 基址位置) sendImgMessageAddr.add(0x00).writeU64(0); sendImgMessageAddr.add(0x08).writeU64(0); - sendImgMessageAddr.add(0x10).writePointer(sendImgMessageCallbackFunc); + sendImgMessageAddr.add(0x10).writeU64(0); sendImgMessageAddr.add(0x18).writeU64(1); sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); sendImgMessageAddr.add(0x28).writePointer(imgMessageAddr); @@ -609,7 +624,7 @@ function setupSendImgMessageDynamic() { })); // C. 构建 Message 结构体 - imgMessageAddr.add(0x00).writePointer(); + imgMessageAddr.add(0x00).writePointer(sendImgMessageCallbackFunc); imgMessageAddr.add(0x08).writeU32(taskIdGlobal); imgMessageAddr.add(0x0c).writeU32(0x6e); imgMessageAddr.add(0x10).writeU64(0x3); @@ -621,8 +636,8 @@ function setupSendImgMessageDynamic() { console.log(" [+] Dynamic Memory Setup Complete. - Message Object: " + imgMessageAddr); - uploadFunc1Addr.writePointer(baseAddr.add(0x802b8b0)); - uploadFunc2Addr.writePointer(baseAddr.add(0x7fd5908)); + uploadFunc1Addr.writePointer(uploadFunc1); + uploadFunc2Addr.writePointer(uploadFunc2); } setImmediate(setupSendImgMessageDynamic); @@ -738,7 +753,6 @@ function triggerSendImgMessage(taskId, sender, receiver) { triggerX1Payload.add(0x18).writePointer(imgCgiAddr); sendMsgType = "img" - console.log("finished init payload") const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer']); // 5. 调用函数 diff --git a/wechat_version/4_1_6_47_mac.json b/wechat_version/4_1_6_47_mac.json index df0e0e1..03fcb17 100644 --- a/wechat_version/4_1_6_47_mac.json +++ b/wechat_version/4_1_6_47_mac.json @@ -14,5 +14,7 @@ "patchImgProtobufFunc2": "0x2275BD8", "imgProtobufDeleteAddr": "0x2275C14", "CndOnCompleteAddr": "0x34154E0", - "uploadImageAddr": "0x45DC834" + "uploadImageAddr": "0x45DC834", + "uploadFunc1": "0x802b8b0", + "uploadFunc2": "0x7fd5908" } diff --git a/wechat_version/4_1_7_31_mac.json b/wechat_version/4_1_7_31_mac.json index cc6f340..83ae463 100644 --- a/wechat_version/4_1_7_31_mac.json +++ b/wechat_version/4_1_7_31_mac.json @@ -1,17 +1,20 @@ { - "protobufAddr": "0x32FB280", - "patchTextProtobufAddr": "0x32FB25C", - "PatchTextProtobufDeleteAddr": "0x32FB298", - "triggerX1Payload": "0x174E4A600", - "sendFuncAddr": "0x55F7540", - "req2bufEnterAddr": "0x4542DBC", - "req2bufExitAddr": "0x4543F78", - "imgProtobufAddr": "0x3317834", - "patchImgProtobufFunc1": "0x33177F0", - "patchImgProtobufFunc2": "0x3317810 ", - "imgProtobufDeleteAddr": "0x331784C", - "uploadImageAddr": "0x45DC834", - "CndOnCompleteAddr": "0x450195C", + "protobufAddr": "0x2387280", + "patchTextProtobufAddr": "0x238725C", + "PatchTextProtobufDeleteAddr": "0x2387298", + "sendMessageCallbackFunc": "0x865DBB8", + "sendFuncAddr": "0x4A49BC4", + "req2bufEnterAddr": "0x35CEDBC", + "req2bufExitAddr": "0x35CFF94", + "imgProtobufAddr": "0x237DFE0", + "patchImgProtobufFunc1": "0x237DF9C", + "patchImgProtobufFunc2": "0x237DFBC ", + "imgProtobufDeleteAddr": "0x237DFF8", + "uploadImageAddr": "0x47D5D64", + "CndOnCompleteAddr": "0x358D98C", + "sendImgMessageCallbackFunc": "0x865D680", + "buf2RespAddr": "0x35F4B58", - "buf2RespAddr": "0x35F4B58" + "uploadFunc1": "0x802b8b0", + "uploadFunc2": "0x7fd5908" }