From 13e11fa2705a0cd3fe9b90bce3636fbef6a11a2d Mon Sep 17 00:00:00 2001 From: yincong Date: Wed, 4 Feb 2026 09:57:16 +0800 Subject: [PATCH] 4.1.7 --- README.md | 8 +- frida-wechat.md | 9 +- frida/mmstarttask.js | 35 ++ frida/receive.js | 70 --- frida/receiver.js | 26 +- frida/script.js | 822 ++++++++++++++++--------------- frida/succ.js | 229 ++++----- idapro/readme.md | 5 +- onebot/main.go | 2 +- onebot/script.js | 17 +- wechat_version/4_1_6_47_mac.json | 3 +- wechat_version/4_1_7_31_mac.json | 17 + 12 files changed, 590 insertions(+), 653 deletions(-) create mode 100644 frida/mmstarttask.js delete mode 100644 frida/receive.js create mode 100644 wechat_version/4_1_7_31_mac.json diff --git a/README.md b/README.md index 8b2d514..1ccfa02 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ 怎么使用,如果你的mac已经关闭了SIP ``` frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l frida/succ.js -manualTrigger(0x20000095, "wxid_xxxx", "hi") +triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi") ``` 没有关闭SIP,查看文件 https://github.com/yincongcyincong/weixin-macos/blob/main/frida-gadget/readme.md 把每一步都执行完成,然后启动微信 ``` frida -H 127.0.0.1:27042 -n Gadget -l ./frida/succ.js -manualTrigger(0x20000095, "wxid_xxxx", "hi") +triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi") ``` ![image](https://github.com/user-attachments/assets/401de4b8-5d10-48d9-8dcf-eecc8ae8682a) @@ -35,8 +35,8 @@ mkdir -p "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents cp /Users/yincong/Desktop/1.png /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg -manualUpload("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg") -manualTrigger(0x20000199, "wxid_xxx","wxid_xxx") +triggerUploadImg("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg") +triggerSendImgMessage(0x20000199, "wxid_xxx","wxid_xxx") ``` diff --git a/frida-wechat.md b/frida-wechat.md index 8e573bb..f24c38b 100644 --- a/frida-wechat.md +++ b/frida-wechat.md @@ -1,10 +1,3 @@ 触发 STNManager__MMStartTask 图片hook: startUploadMedia, OnUploadCompleted -Req2Buf 构造消息的buffer入口,其中X4是消息内容,输出的是压缩后的protobuf -Pack是打包函数,消息内容 cgi等信息打包进入 -InitClientChannel, DoHandShakeLoop 是建立mmtls链接最重要的函数 -DoSendEarlyAppData 是消息体函数,可以看到你发的消息体 -__RunReadWrite 这条日志打印后的 “task socket send sock:%_, %_ http len:%" 一个BL X8里面有send命令进行发送 - -老图和新图发送的还不一样 -有个映射 \ No newline at end of file +startUploadMedia 上传图片,OnUploadCompleted 上传图片完成后的回调 \ No newline at end of file diff --git a/frida/mmstarttask.js b/frida/mmstarttask.js new file mode 100644 index 0000000..ac1f8c4 --- /dev/null +++ b/frida/mmstarttask.js @@ -0,0 +1,35 @@ +// 1. 获取微信主模块的基地址 +var baseAddr = Process.getModuleByName("WeChat").base; +if (!baseAddr) { + console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); +} +console.log("[+] WeChat base address: " + baseAddr); + +var sendFuncAddr = baseAddr.add(0x4683540); + +Interceptor.attach(sendFuncAddr, { + onEnter: function (args) { + // 获取 x0 的值 + var x0_ptr = args[0]; + console.log("[+] Register x0: " + x0_ptr); + + // 检查 x0 是否为合法指针,防止访问 0x1 导致崩溃 + try { + if (x0_ptr.isNull()) { + console.log("[!] x0 is NULL"); + } else if (parseInt(x0_ptr) < 0x1000) { + console.log("[!] x0 looks like an invalid small pointer (potential error code): " + x0_ptr); + } else { + // 打印 x0 指向的内存数据(例如前 32 字节) + console.log("[+] Memory at x0:\n" + hexdump(x0_ptr, { + offset: 0, + length: 256, + header: true, + ansi: true + })); + } + } catch (e) { + console.error("[!] Failed to read memory at x0: " + e.message); + } + } +}) \ No newline at end of file diff --git a/frida/receive.js b/frida/receive.js deleted file mode 100644 index da00f6f..0000000 --- a/frida/receive.js +++ /dev/null @@ -1,70 +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); - console.log(`${addr} called ${currentCount} times`); - - if (args[0].isNull()) { - console.log("args[0] is NULL, skipping hexdump."); - return; - } - - try { - console.log("[*] Hexdump of args[0]:"); - console.log(hexdump(args[0], { - offset: 0, - length: 256, - header: true, - ansi: true // 在支持彩色输出的终端(如 macOS 终端)中效果很好 - })); - } catch (e) { - console.log("[!] 无法读取内存: " + e.message); - } - }, - - onLeave(retval) { - } - }); - -} - -const prs = ["10438958c", "10434ce6c", "10433c754"] -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/receiver.js b/frida/receiver.js index 8e98c4e..6261e71 100644 --- a/frida/receiver.js +++ b/frida/receiver.js @@ -2,16 +2,16 @@ var baseAddr = Process.getModuleByName("WeChat").base; if (!baseAddr) { console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); } -console.log("[+] WeChat base address: " + baseAddr); +var buf2RespAddr = baseAddr.add(0x35F4B58) + +// -------------------------接收消息分区------------------------- function setReceiver() { - var buf2RespAddr = baseAddr.add(0x347BD44); - console.log("[+] buf2RespAddr WeChat Base: " + baseAddr + "[+] Attaching to: " + buf2RespAddr); // 3. 开始拦截 Interceptor.attach(buf2RespAddr, { onEnter: function (args) { - + console.log("[*] 拦截到消息发送"); const currentPtr = this.context.x1; let start = 0x1e; let senderLen = currentPtr.add(start).readU8(); @@ -113,15 +113,6 @@ function setReceiver() { // 使用 setImmediate 确保在模块加载后执行 setImmediate(setReceiver) -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 getProtobufRawBytes(pBuffer, scanSize) { const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A]; @@ -235,4 +226,13 @@ function getCleanString(uint8Array) { } } return out; +} + +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; } \ No newline at end of file diff --git a/frida/script.js b/frida/script.js index 39a4a30..bb7ffc8 100644 --- a/frida/script.js +++ b/frida/script.js @@ -5,97 +5,295 @@ if (!baseAddr) { } console.log("[+] WeChat base address: " + baseAddr); -// 触发函数地址,不同版本的地址看wechat_version 中的json文件复制过来 -var triggerFuncAddr = baseAddr.add(0x444A99C); -var sendMessageCallbackFunc = baseAddr.add(0xEDB4678); -var messageCallbackFunc1 = baseAddr.add(0x7f04f70); -var messageCallbackFunc2 = baseAddr.add(0x7f04fc8); -var messageCallbackFunc3 = baseAddr.add(0x7f96918); -var messageCallbackFunc4 = baseAddr.add(0x7f96a08); -var messageCallbackFunc5 = baseAddr.add(0x7f968a0); -var messageCallbackFunc6 = baseAddr.add(0x7f9dfe0); +// -------------------------基础函数分区------------------------- +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; +} -// 这个必须是绝对位置 -var triggerX1Payload = ptr(0x175ED6600); -var req2bufEnterAddr = baseAddr.add(0x33EE8E8); -var req2bufExitAddr = baseAddr.add(0x33EFA00); -var protobufAddr = baseAddr.add(0x223EF58); -var receiveAddr = baseAddr.add(0x23B5348); -// 触发函数X0参数地址 -var globalMessagePtr = ptr(0); +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; +} -// 消息体的一些指针地址 -var cgiAddr = ptr(0); -var callBackFuncAddr = ptr(0); -var sendMessageAddr = ptr(0); -var messageAddr = ptr(0); -var messageContentAddr = ptr(0); -var messageAddrAddr = ptr(0); -var contentAddr = ptr(0); + +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 getProtobufRawBytes(pBuffer, scanSize) { + const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A]; + let uint8Array; + + try { + const mem = pBuffer.readByteArray(scanSize); + if (!mem) return []; + uint8Array = new Uint8Array(mem); + } catch (e) { + console.error("读取内存失败: " + e); + return []; + } + + let finalResults = []; + + let i = 0x1a; + tags.forEach(targetTag => { + let found = false; + for (; i < uint8Array.length; i++) { + if (uint8Array[i] === targetTag) { + // 1. 解析 Varint 长度 (支持 1-5 字节长度标识) + let length = 0; + let shift = 0; + let bytesReadForLen = 0; + i = i + 1; + + while (i < uint8Array.length) { + let b = uint8Array[i]; + length |= (b & 0x7F) << shift; + bytesReadForLen++; + i++; + if (!(b & 0x80)) break; + shift += 7; + } + + // 2. 截取原始 Byte 数据 + if (i + length <= uint8Array.length) { + let rawData = uint8Array.slice(i, i + length); + finalResults.push(getCleanString(rawData)); + i += length; + } else { + finalResults.push(null); // 长度越界 + } + + found = true; + break; // 找到第一个匹配的 Tag 就跳出 + } + } + if (!found) finalResults.push(null); // 未找到该 Tag + }); + + return finalResults; +} + +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 protobufAddr = baseAddr.add(0x2387280); +var patchTextProtobufAddr = baseAddr.add(0x238725C); +var PatchTextProtobufDeleteAddr = baseAddr.add(0x2387298); +var textCgiAddr = ptr(0); +var sendTextMessageAddr = ptr(0); +var textMessageAddr = ptr(0); +var textProtoX1PayloadAddr = ptr(0); +var sendMessageCallbackFunc = ptr(0); +var messageCallbackFunc1 = ptr(0); + + +// 双方公共使用的地址 +var triggerX1Payload; +var req2bufEnterAddr = baseAddr.add(0x35CEDC0); +var req2bufExitAddr = baseAddr.add(0x35CFF78); +var sendFuncAddr = baseAddr.add(0x4683540); var insertMsgAddr = ptr(0); -var receiverAddr = ptr(0); -var htmlContentAddr = ptr(0); -var protoX1PayloadAddr = ptr(0); -var protoX1PayloadLen = 1024; +var sendMsgType = ""; +var buf2RespAddr = baseAddr.add(0x4568B58); -// 消息的taskId +// 图片消息全局变量 +var sendImgMessageCallbackFunc = ptr(0); +var uploadImageAddr = baseAddr.add(0x45DC834); +var imgProtobufAddr = baseAddr.add(0x3317834); +var patchImgProtobufFunc1 = baseAddr.add(0x0) +var patchImgProtobufFunc2 = baseAddr.add(0x0); +var imgProtobufDeleteAddr = baseAddr.add(0x0); +var CndOnCompleteAddr = baseAddr.add(0x450195C); + +var imgCgiAddr = ptr(0); +var sendImgMessageAddr = ptr(0); +var imgMessageAddr = ptr(0); +var imgProtoX1PayloadAddr = ptr(0); +var uploadGlobalX0 = ptr(0) +var uploadFunc1Addr = ptr(0) +var uploadFunc2Addr = ptr(0) +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 = ""; -// 打印消息的地址,便于查询问题 -function printAddr() { - console.log("[+] Addresses:"); - console.log(" - cgiAddr: " + cgiAddr); - console.log(" - callBackFuncAddr: " + callBackFuncAddr); - console.log(" - sendMessageAddr: " + sendMessageAddr); - console.log(" - messageAddr: " + messageAddr); - console.log(" - messageContentAddr: " + messageContentAddr); - console.log(" - messageAddrAddr: " + messageAddrAddr); - console.log(" - contentAddr: " + contentAddr); - console.log(" - globalMessagePtr: " + globalMessagePtr); - console.log(" - triggerX1Payload: " + triggerX1Payload); -} +const imageCp = generateBytes(16) // m30c4674f5a0b9d -// 辅助函数:写入 Hex 字符串 -function patchHex(addr, hexStr) { - const bytes = hexStr.split(' ').map(h => parseInt(h, 16)); - addr.writeByteArray(bytes); - addr.add(bytes.length).writeU8(0); // 终止符 -} +// -------------------------全局变量分区------------------------- + +// -------------------------发送文本消息分区------------------------- // 初始化进行内存的分配 -function setupSendMessageDynamic() { +function setupSendTextMessageDynamic() { console.log("[+] Starting Dynamic Message Patching..."); // 1. 动态分配内存块(按需分配大小) // 分配原则:字符串给 64-128 字节,结构体按实际大小分配 - cgiAddr = Memory.alloc(128); - callBackFuncAddr = Memory.alloc(16); - sendMessageAddr = Memory.alloc(256); - messageAddr = Memory.alloc(512); - messageContentAddr = Memory.alloc(32); - messageAddrAddr = Memory.alloc(64); - contentAddr = Memory.alloc(255); - receiverAddr = Memory.alloc(24); - htmlContentAddr = Memory.alloc(24); - + textCgiAddr = Memory.alloc(128); + sendTextMessageAddr = Memory.alloc(256); + textMessageAddr = Memory.alloc(256); // A. 写入字符串内容 - patchHex(cgiAddr, "2F 63 67 69 2D 62 69 6E 2F 6D 69 63 72 6F 6D 73 67 2D 62 69 6E 2F 6E 65 77 73 65 6E 64 6D 73 67"); - patchHex(contentAddr, " "); + patchString(textCgiAddr, "/cgi-bin/micromsg-bin/newsendmsg"); - // B. 构建 SendMessage 结构体 (X24 基址位置) - sendMessageAddr.add(0x00).writeU64(0); - sendMessageAddr.add(0x08).writeU64(0); - sendMessageAddr.add(0x10).writePointer(sendMessageCallbackFunc); // 虚表地址通常仍需硬编码或从模块基址计算 - sendMessageAddr.add(0x18).writeU64(1); - sendMessageAddr.add(0x20).writeU32(taskIdGlobal); - sendMessageAddr.add(0x28).writePointer(messageAddr); // 指向动态分配的 Message + // B. 构建 sendTextMessageAddr 结构体 (X24 基址位置) + sendTextMessageAddr.add(0x00).writeU64(0); + sendTextMessageAddr.add(0x08).writeU64(0); + sendTextMessageAddr.add(0x10).writePointer(sendMessageCallbackFunc); + sendTextMessageAddr.add(0x18).writeU64(1); + sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendTextMessageAddr.add(0x28).writePointer(textMessageAddr); // 指向动态分配的 Message - console.log(" [+] sendMessageAddr Object: ", hexdump(sendMessageAddr, { + console.log(" [+] sendTextMessageAddr Object: ", hexdump(sendTextMessageAddr, { offset: 0, length: 48, header: true, @@ -103,87 +301,51 @@ function setupSendMessageDynamic() { })); // C. 构建 Message 结构体 - messageAddr.add(0x00).writePointer(messageCallbackFunc1); - messageAddr.add(0x08).writeU32(taskIdGlobal); - messageAddr.add(0x0c).writeU32(0x20a); - messageAddr.add(0x10).writeU64(0x3); - messageAddr.add(0x18).writePointer(cgiAddr); + textMessageAddr.add(0x00).writePointer(messageCallbackFunc1); + textMessageAddr.add(0x08).writeU32(taskIdGlobal); + textMessageAddr.add(0x0c).writeU32(0x20a); + textMessageAddr.add(0x10).writeU64(0x3); + textMessageAddr.add(0x18).writePointer(textCgiAddr); + textMessageAddr.add(0x20).writeU64(uint64("0x20")); - // 设置一些固定值 - messageAddr.add(0x20).writeU64(uint64("0x20")); - messageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); - messageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); - messageAddr.add(0x58).writeU64(uint64("0x0101010100000001")); - - // 处理回调地址 - callBackFuncAddr.writePointer(messageCallbackFunc2); - messageAddr.add(0x98).writePointer(callBackFuncAddr); - - // 设置内容指针 - messageAddr.add(0xb8).writePointer(messageCallbackFunc3); - messageAddr.add(0xc0).writePointer(messageContentAddr); - messageAddr.add(0xc8).writeU64(uint64("0x0000000100000001")); - messageAddr.add(0xd0).writeU64(0x4); - messageAddr.add(0xd8).writeU64(0x1); - messageAddr.add(0xe0).writeU64(0x1); - messageAddr.add(0xe8).writePointer(messageCallbackFunc4); - - - messageContentAddr.writePointer(messageAddrAddr); - messageAddrAddr.writePointer(messageCallbackFunc5); - receiverAddr.writePointer(messageCallbackFunc6); - receiverAddr.add(0x08).writePointer(contentAddr); - messageAddrAddr.add(0x08).writePointer(receiverAddr); - messageAddrAddr.add(0x10).writePointer(contentAddr); - messageAddrAddr.add(0x18).writeU32(1); - messageAddrAddr.add(0x20).writeU32(Math.floor(Date.now() / 1000)); - htmlContentAddr.writePointer(contentAddr); - messageAddrAddr.add(0x28).writePointer(htmlContentAddr); - - console.log(" [+] messageAddr Object: ", hexdump(messageAddr, { + console.log(" [+] textMessageAddr Object: ", hexdump(textMessageAddr, { offset: 0, length: 64, header: true, ansi: true })); - console.log(" [+] Dynamic Memory Setup Complete. - Message Object: " + messageAddr); + console.log("[+] Dynamic Memory Setup Complete. - Message Object: " + textMessageAddr); } -setImmediate(setupSendMessageDynamic); +setImmediate(setupSendTextMessageDynamic); -// 设置trigger函数的x0参数 -function setTriggerAttach() { - console.log("[+] WeChat Base: " + baseAddr + "[+] Attaching to: " + triggerFuncAddr); - // 3. 开始拦截 - Interceptor.attach(triggerFuncAddr, { - onEnter: function (args) { - console.log("[+] Entered Function: 0x10444A99C"); - - if (!globalMessagePtr.isNull()) { - return; - } - - globalMessagePtr = this.context.x0; - console.log("[+] globalMessagePtr 当前 X0 的指针值: " + globalMessagePtr); - }, - onLeave: function (retval) { - } +function patchTextProtoBuf() { + Memory.patchCode(patchTextProtobufAddr, 4, code => { + const cw = new Arm64Writer(code, {pc: patchTextProtobufAddr}); + cw.putNop(); + cw.flush(); }); + + console.log("[+] Patching patchTextProtobufAddr " + patchTextProtobufAddr + " 成功."); + + Memory.patchCode(PatchTextProtobufDeleteAddr, 4, code => { + const cw = new Arm64Writer(code, {pc: PatchTextProtobufDeleteAddr}); + cw.putNop(); + cw.flush(); + }); + + console.log("[+] Patching PatchTextProtobufDeleteAddr " + PatchTextProtobufDeleteAddr + " 成功."); } -// 使用 setImmediate 确保在模块加载后执行 -setImmediate(setTriggerAttach); +setTimeout(function () { + console.log("[+] 3秒等待结束,准备执行 Patch..."); + patchTextProtoBuf(); +}, 3000); - -function manualTrigger(taskId, receiver, content) { +function triggerSendTextMessage(taskId, receiver, content, atUser) { console.log("[+] Manual Trigger Started..."); - if (globalMessagePtr.isNull()) { - console.error("[!] globalMessagePtr is NULL, cannot trigger!"); - return "fail"; - } - if (!taskId || !receiver || !content) { console.error("[!] taskId or Receiver or Content is empty!"); return "fail"; @@ -191,25 +353,20 @@ function manualTrigger(taskId, receiver, content) { // 获取当前时间戳 (秒) const timestamp = Math.floor(Date.now() / 1000); - // 全局变量不为空,并且上次发送时间小于1s,不给发送 - if ((taskIdGlobal !== 0 || receiverGlobal !== "" || contentGlobal !== "") && lastSendTime + 1 > timestamp) { - console.error("[!] taskId or receiver or content is not empty!"); - return "fail"; - } - lastSendTime = timestamp taskIdGlobal = taskId; receiverGlobal = receiver; contentGlobal = content; + atUserGlobal = atUser + console.log("taskIdGlobal: " + taskIdGlobal + ", receiverGlobal: " + receiverGlobal + ", contentGlobal: " + contentGlobal + ", atUserGlobal: " + atUserGlobal) ; - messageAddr.add(0x08).writeU32(taskIdGlobal); - sendMessageAddr.add(0x20).writeU32(taskIdGlobal); - messageAddrAddr.add(0x18).writeU32(timestamp); + textMessageAddr.add(0x08).writeU32(taskIdGlobal); + sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); 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 @@ -230,7 +387,7 @@ function manualTrigger(taskId, receiver, content) { 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 + 0xC0, 0x66, 0x26, 0x73, 0x01, 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 @@ -260,32 +417,114 @@ function manualTrigger(taskId, receiver, content) { 0x98, 0x67, 0xED, 0x75, 0x01, 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(cgiAddr); + triggerX1Payload.add(0x18).writePointer(textCgiAddr); + sendMsgType = "text" - const sub_10444A99C = new NativeFunction(triggerFuncAddr, 'uint64', ['pointer', 'pointer']); + console.log("finished init payload") + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer']); // 5. 调用函数 try { - const arg1 = globalMessagePtr; // 第一个指针参数 - const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600 - console.log(`[+] Calling trigger function at ${triggerFuncAddr} with args: (${arg1}, ${arg2})`); - const result = sub_10444A99C(arg1, arg2); - console.log("[+] Execution trigger function Success. Return value: " + result); + const result = MMStartTask(triggerX1Payload); + console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${triggerX1Payload}) Success. Return value: ` + result); return "ok"; } catch (e) { - console.error("[!] Error trigger function during execution: " + e); + console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} with args: (${triggerX1Payload}), during execution: ` + e); return "fail"; } } +function AttachSendTextProto() { + Interceptor.attach(sendFuncAddr, { + onEnter: function (args) { + if (triggerX1Payload) { + return + } -// ReqBuf 进行拦截,替换入参数的消息指针 + triggerX1Payload = this.context.x0; + console.log(`[+] 捕获到 MMStartTask 调用,Payload 地址: ${triggerX1Payload}`); + } + }) +} + +setImmediate(AttachSendTextProto); + +// 拦截 SendTextProto 编码逻辑,注入自定义 Payload +function attachSendTextProto() { + console.log("[+] proto注入拦截目标地址: " + protobufAddr); + textProtoX1PayloadAddr = Memory.alloc(1024); + console.log("[+] Frida 分配的 Payload 地址: " + textProtoX1PayloadAddr); + + Interceptor.attach(protobufAddr, { + onEnter: function (args) { + console.log("[+] Protobuf 拦截命中"); + + var sp = this.context.sp; + var firstValue = sp.readU32(); + if (firstValue !== taskIdGlobal) { + console.log("[+] Protobuf 拦截未命中,跳过..."); + return; + } + console.log(`[+] 正在注入 Protobuf Payload content: ${contentGlobal}, receiver: ${receiverGlobal}, atUser: ${atUserGlobal}`); + + const type = [0x08, 0x01, 0x12] + const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length]; + const receiverProto = stringToHexArray(receiverGlobal); + const contentProto = stringToHexArray(contentGlobal); + const contentHeader = [0x12, ...toVarint(contentProto.length)]; + const tsHeader = [0x18, 0x01, 0x20]; + const tsBytes = getVarintTimestampBytes(); + const msgIdHeader = [0x28] + const msgId = generateRandom5ByteVarint() + + const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E] + let atUserHeader = [] + if (atUserGlobal) { + atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]). + concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E]) + } + const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F, + 0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31, + 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, + 0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F, + 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, + 0x63, 0x65, 0x3E, 0x00] + + const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length] + + + const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + + contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length + + htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length) + + // 合并数组 + const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader). + concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart). + concat(atUserHeader).concat(htmlLowerPart); + + textProtoX1PayloadAddr.writeByteArray(finalPayload); + this.context.x1 = textProtoX1PayloadAddr; + this.context.x2 = ptr(finalPayload.length); + + console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(textProtoX1PayloadAddr, { + offset: 0, + length: 128, + header: true, + ansi: true + })); + }, + }); +} + +setImmediate(attachSendTextProto); + +// -------------------------发送文本消息分区------------------------- + + +// -------------------------Req2Buf公共部分分区------------------------- function attachReq2buf() { - console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr); // 2. 开始拦截 @@ -300,33 +539,20 @@ function attachReq2buf() { // 3. 获取 X24 寄存器的值 const x24_base = this.context.x24; insertMsgAddr = x24_base.add(0x60); - console.log("[+] 当前 Req2Buf X24 基址: " + x24_base); - console.log("[+] 准备修改位置 Req2Buf (X24 + 0x60): " + insertMsgAddr, hexdump(insertMsgAddr, { - offset: 0, - length: 16, - header: true, - ansi: true - })); - if (typeof sendMessageAddr !== 'undefined') { - insertMsgAddr.writePointer(sendMessageAddr); - console.log("[+] 成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendMessageAddr + - "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); - console.log(hexdump(insertMsgAddr, { - offset: 0, - length: 16, - header: true, - ansi: true - })) - console.log(hexdump(sendMessageAddr, { - offset: 0, - length: 48, - header: true, - ansi: true - })) + 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("[!] 错误: 变量 sendMessageAddr 未定义,请确保已运行分配逻辑。"); + console.error("[!] 错误: 变量 sendTextMessageAddr 未定义,请确保已运行分配逻辑。"); } } }); @@ -339,10 +565,12 @@ function attachReq2buf() { return; } insertMsgAddr.writeU64(0x0); - console.log("[+] 0x1033EFA00 清空写入后内存预览: " + insertMsgAddr.readPointer()); + console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer()); taskIdGlobal = 0; receiverGlobal = ""; + senderGlobal = ""; contentGlobal = ""; + atUserGlobal = ""; send({ type: "finish", }) @@ -352,230 +580,4 @@ function attachReq2buf() { setImmediate(attachReq2buf); -// 辅助函数: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 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 编码逻辑,注入自定义 Payload -function attachProto() { - console.log("[+] proto注入拦截目标地址: " + protobufAddr); - protoX1PayloadAddr = Memory.alloc(protoX1PayloadLen); - console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr); - - Interceptor.attach(protobufAddr, { - onEnter: function (args) { - console.log("[+] Protobuf 拦截命中"); - - var sp = this.context.sp; - console.log("[+] Protobuf 拦截命中,SP: " + sp, hexdump(sp, { - offset: 0, - length: 16, - header: true, - ansi: true - })); - - - var firstValue = sp.readU32(); - if (firstValue !== taskIdGlobal) { - console.log("[+] Protobuf 拦截未命中,跳过..."); - return; - } - console.log("[+] 正在注入 Protobuf Payload..."); - - const type = [0x08, 0x01, 0x12] - const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length]; - const receiverProto = stringToHexArray(receiverGlobal); - const contentProto = stringToHexArray(contentGlobal); - const contentHeader = [0x12, ...toVarint(contentProto.length)]; - const tsHeader = [0x18, 0x01, 0x20]; - const tsBytes = getVarintTimestampBytes(); - const msgIdHeader = [0x28] - const msgId = generateRandom5ByteVarint() - - const suffix = [ - 0x32, 0x32, 0x3C, // 0x28 头部 - 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x30 msgsour - 0x63, 0x65, 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, // 0x38 ce>1 - 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, // 0x48 . - ]; - - const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + - contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + suffix.length) - - // 合并数组 - const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix); - - console.log("[+] Payload 准备写入"); - protoX1PayloadAddr.writeByteArray(finalPayload); - console.log("[+] Payload 已写入,长度: " + finalPayload.length); - - this.context.x1 = protoX1PayloadAddr; - this.context.x2 = ptr(finalPayload.length); - - console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(protoX1PayloadAddr, { - offset: 0, - length: 128, - header: true, - ansi: true - })); - } - }); -} - -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; -} - -setImmediate(attachProto); - -function setReceiver() { - console.log("[+] setReceiver WeChat Base: " + baseAddr + "[+] Attaching to: " + receiveAddr); - - // 3. 开始拦截 - Interceptor.attach(receiveAddr, { - onEnter: function (args) { - console.log("[+] Entered Receive Function: 0x1023B5348"); - const x1 = this.context.x1; - var sender = x1.add(0x18).readUtf8String(); - var receiver = x1.add(0x30).readUtf8String(); - var selfId = x1.add(0x48).readUtf8String(); - - // 3. 从 0xd0 开始处理 - var d0Pos = x1.add(0xd0); - var strD0 = ""; - - if (isPrintableOrChinese(d0Pos)) { - strD0 = d0Pos.readUtf8String(); - console.log("[+] 0xd0 处不是指针,直接读取完毕"); - } else { - // 情况 B:不符合特征(如包含乱码位或指针特征),视为指针 - var ptrD0 = d0Pos.readPointer(); - if (!ptrD0.isNull() && Process.findRangeByAddress(ptrD0)) { - strD0 = ptrD0.readUtf8String(); - console.log("[+] 0xd0 识别为:指针跳转读取"); - } else { - strD0 = "Invalid Data/Pointer"; - } - } - - var msgType = "private" - var groupId = "" - if (receiver.includes("@chatroom")) { - msgType = "group" - groupId = receiver - } - - var parts = strD0.split('\u2005'); - var messages = []; - for (let part of parts) { - if (part.startsWith("@")) { - messages.push({type: "at", data: {qq: selfId}}); - } else { - messages.push({type: "text", data: {text: part}}); - } - } - - send({ - message_type: msgType, - user_id: sender, - self_id: selfId, - group_id: groupId, - message_id: taskIdGlobal, - type: "send", - raw: {peerUid: receiver}, - message: messages - }) - }, - }); -} - -// 使用 setImmediate 确保在模块加载后执行 -setImmediate(setReceiver) - -/** - * 扫描内存直到 \0,判断中间内容是否全部为可见字符或汉字 - */ -function isPrintableOrChinese(startPtr) { - let offset = 0; - const maxScanLength = 8; - - while (offset < maxScanLength) { - let b = startPtr.add(offset).readU8(); - - if (b === 0) { - // 扫描到 \0,且之前没有发现异常字节 - return offset > 0; // 如果第一个就是 \0,视为非字符串(可能是空指针) - } - - // 判定逻辑: - // 1. 可见 ASCII (32-126) 或 换行/制表符 (9, 10, 13) - let isAscii = (b >= 32 && b <= 126) || (b === 9 || b === 10 || b === 13); - - // 2. 汉字 UTF-8 特征:第一个字节通常 >= 0x80 (128) - // 严谨点:UTF-8 汉字首字节通常在 0xE4-0xE9 之间,后续字节在 0x80-0xBF 之间 - // 这里简化处理:如果是高位字符,我们暂时放行,由 readUtf8String 最终处理 - let isHighBit = (b >= 0x80); - - if (!isAscii && !isHighBit) { - // 发现既不是 ASCII 也不是高位字节(如 0x01-0x1F 的控制字符),判定为指针 - return false; - } - offset++; - } - return true; -} - - -rpc.exports = { - manualTrigger: manualTrigger -}; \ No newline at end of file +// -------------------------Req2Buf公共部分分区------------------------- \ No newline at end of file diff --git a/frida/succ.js b/frida/succ.js index 6ad634c..23d358c 100644 --- a/frida/succ.js +++ b/frida/succ.js @@ -82,61 +82,8 @@ function generateAESKey() { return key; } - -function readVarint(addr) { - let value = 0; - let shift = 0; - let count = 0; - - while (true) { - let byte = addr.add(count).readU8(); - // 取低7位进行累加 - value |= (byte & 0x7f) << shift; - count++; // 消耗了一个字节 - - // 如果最高位是0,跳出循环 - if ((byte & 0x80) === 0) break; - - shift += 7; - if (count > 5) return -1; // 安全校验,防止死循环 - } - - return { - value: value, // 最终长度数值 (例如 251) - byteLength: count // 长度字段占用的字节数 (例如 2) - }; -} - -function isPrintableOrChinese(startPtr, maxScanLength) { - let offset = 0; - while (offset < maxScanLength) { - let b = startPtr.add(offset).readU8(); - - if (b === 0) { - // 扫描到 \0,且之前没有发现异常字节 - return offset > 0; // 如果第一个就是 \0,视为非字符串(可能是空指针) - } - - // 判定逻辑: - // 1. 可见 ASCII (32-126) 或 换行/制表符 (9, 10, 13) - let isAscii = (b >= 32 && b <= 126) || (b === 9 || b === 10 || b === 13); - - // 2. 汉字 UTF-8 特征:第一个字节通常 >= 0x80 (128) - // 严谨点:UTF-8 汉字首字节通常在 0xE4-0xE9 之间,后续字节在 0x80-0xBF 之间 - // 这里简化处理:如果是高位字符,我们暂时放行,由 readUtf8String 最终处理 - let isHighBit = (b >= 0x80); - - if (!isAscii && !isHighBit) { - // 发现既不是 ASCII 也不是高位字节(如 0x01-0x1F 的控制字符),判定为指针 - return false; - } - offset++; - } - return true; -} - function getProtobufRawBytes(pBuffer, scanSize) { - const tags = [0x12, 0x1A, 0x2A, 0x52]; + const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A]; let uint8Array; try { @@ -229,8 +176,7 @@ function getCleanString(uint8Array) { out += String.fromCharCode(charCode); } } - } - else if ((c & 0xF8) === 0xF0 && i + 2 < len) { + } else if ((c & 0xF8) === 0xF0 && i + 2 < len) { var c2 = uint8Array[i++]; var c3 = uint8Array[i++]; var c4 = uint8Array[i++]; @@ -249,39 +195,51 @@ function getCleanString(uint8Array) { 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 protobufAddr = baseAddr.add(0x227EC70); -var patchTextProtobufAddr = baseAddr.add(0x227EC4C); -var PatchTextProtobufDeleteAddr = baseAddr.add(0x227EC88); +var protobufAddr = baseAddr.add(0x66E7280); +var patchTextProtobufAddr = baseAddr.add(0x66E725C); +var PatchTextProtobufDeleteAddr = baseAddr.add(0x66E7298); var textCgiAddr = ptr(0); var sendTextMessageAddr = ptr(0); var textMessageAddr = ptr(0); var textProtoX1PayloadAddr = ptr(0); var sendMessageCallbackFunc = ptr(0); -var messageCallbackFunc1 = baseAddr.add(0x7fa1050); +var messageCallbackFunc1 = ptr(0); // 双方公共使用的地址 -var triggerX1Payload = ptr(0x175ED6600); -var req2bufEnterAddr = baseAddr.add(0x34566C0); -var req2bufExitAddr = baseAddr.add(0x34577D8); -var sendFuncAddr = baseAddr.add(0x448A858); +var triggerX1Payload = ptr(0x173a5a600); +var req2bufEnterAddr = baseAddr.add(0x792EDC0); +var req2bufExitAddr = baseAddr.add(0x792FF78); +var sendFuncAddr = baseAddr.add(0x89E3540); var insertMsgAddr = ptr(0); var sendMsgType = ""; -var buf2RespAddr = baseAddr.add(0x347BD44); +var buf2RespAddr = baseAddr.add(0x4568B58); // 图片消息全局变量 -var sendImgMessageCallbackFunc = ptr(0x0); -var imgMessageCallbackFunc1 = baseAddr.add(0x7fa0b18); -var imgProtobufAddr = baseAddr.add(0x2275BFC); -var patchImgProtobufFunc1 = baseAddr.add(0x2275BB8) -var patchImgProtobufFunc2 = baseAddr.add(0x2275BD8); -var imgProtobufDeleteAddr = baseAddr.add(0x2275C14); -var CndOnCompleteAddr = baseAddr.add(0x34154E0); +var sendImgMessageCallbackFunc = ptr(0); +var uploadImageAddr = baseAddr.add(0x45DC834); +var imgProtobufAddr = baseAddr.add(0x3317834); +var patchImgProtobufFunc1 = baseAddr.add(0x0) +var patchImgProtobufFunc2 = baseAddr.add(0x0); +var imgProtobufDeleteAddr = baseAddr.add(0x0); +var CndOnCompleteAddr = baseAddr.add(0x450195C); var imgCgiAddr = ptr(0); var sendImgMessageAddr = ptr(0); @@ -306,6 +264,9 @@ var receiverGlobal = "wxid_" var contentGlobal = ""; var senderGlobal = "wxid_" var lastSendTime = 0; +var atUserGlobal = ""; + +const imageCp = generateBytes(16) // m30c4674f5a0b9d // -------------------------全局变量分区------------------------- @@ -383,7 +344,7 @@ setTimeout(function () { patchTextProtoBuf(); }, 3000); -function triggerSendTextMessage(taskId, receiver, content) { +function triggerSendTextMessage(taskId, receiver, content, atUser) { console.log("[+] Manual Trigger Started..."); if (!taskId || !receiver || !content) { console.error("[!] taskId or Receiver or Content is empty!"); @@ -396,12 +357,12 @@ function triggerSendTextMessage(taskId, receiver, content) { taskIdGlobal = taskId; receiverGlobal = receiver; contentGlobal = content; + atUserGlobal = atUser + console.log("taskIdGlobal: " + taskIdGlobal + ", receiverGlobal: " + receiverGlobal + ", contentGlobal: " + contentGlobal + ", atUserGlobal: " + atUserGlobal) ; textMessageAddr.add(0x08).writeU32(taskIdGlobal); sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); - console.log("start init payload") - const payloadData = [ 0x0A, 0x02, 0x00, 0x00, // 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 @@ -468,12 +429,11 @@ function triggerSendTextMessage(taskId, receiver, content) { try { // const arg1 = globalMessagePtr; // 第一个指针参数 const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600 - console.log(`[+] Calling MMStartTask at ${sendFuncAddr} with args: (${arg2})`); const result = MMStartTask(arg2); - console.log("[+] Execution MMStartTask Success. Return value: " + result); + console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${arg2}) Success. Return value: ` + result); return "ok"; } catch (e) { - console.error("[!] Error trigger function during execution: " + e); + console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} during execution: ` + e); return "fail"; } } @@ -494,7 +454,7 @@ function attachSendTextProto() { console.log("[+] Protobuf 拦截未命中,跳过..."); return; } - console.log("[+] 正在注入 Protobuf Payload..."); + console.log(`[+] 正在注入 Protobuf Payload content: ${contentGlobal}, receiver: ${receiverGlobal}, atUser: ${atUserGlobal}`); const type = [0x08, 0x01, 0x12] const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length]; @@ -506,27 +466,32 @@ function attachSendTextProto() { const msgIdHeader = [0x28] const msgId = generateRandom5ByteVarint() - const suffix = [ - 0x32, 0x32, 0x3C, // 0x28 头部 - 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x30 msgsour - 0x63, 0x65, 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, // 0x38 ce>1 - 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, // 0x48 . - ]; + const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E] + let atUserHeader = [] + if (atUserGlobal) { + atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]). + concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E]) + } + const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F, + 0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31, + 0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, + 0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F, + 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, + 0x63, 0x65, 0x3E, 0x00] + + const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length] + const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + - contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + suffix.length) + contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length + + htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length) // 合并数组 - const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix); + const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader). + concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart). + concat(atUserHeader).concat(htmlLowerPart); - console.log("[+] Payload 准备写入"); textProtoX1PayloadAddr.writeByteArray(finalPayload); - console.log("[+] Payload 已写入,长度: " + finalPayload.length); - this.context.x1 = textProtoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); @@ -594,6 +559,7 @@ function attachReq2buf() { receiverGlobal = ""; senderGlobal = ""; contentGlobal = ""; + atUserGlobal = ""; send({ type: "finish", }) @@ -643,7 +609,7 @@ function setupSendImgMessageDynamic() { })); // C. 构建 Message 结构体 - imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc1); + imgMessageAddr.add(0x00).writeU64(0x0); imgMessageAddr.add(0x08).writeU32(taskIdGlobal); imgMessageAddr.add(0x0c).writeU32(0x6e); imgMessageAddr.add(0x10).writeU64(0x3); @@ -799,25 +765,15 @@ function attachProto() { 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())) @@ -880,7 +836,7 @@ 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) @@ -1007,9 +963,7 @@ function triggerUploadImg(receiver, md5, imagePath) { uploadImagePayload.add(0x140).writePointer(ImagePathAddr1); uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr); - - const targetAddr = baseAddr.add(0x45DC834); - const startUploadMedia = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']); + const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); console.log("开始手动触发 C2C 上传..."); const result = startUploadMedia(uploadGlobalX0, uploadImagePayload); @@ -1017,8 +971,7 @@ function triggerUploadImg(receiver, md5, imagePath) { } function attachUploadMedia() { - const targetAddr = baseAddr.add(0x45DC85C); - Interceptor.attach(targetAddr, { + Interceptor.attach(uploadImageAddr, { onEnter: function (args) { console.log("[+] enter UploadMedia"); uploadGlobalX0 = this.context.x0; @@ -1103,10 +1056,11 @@ function setReceiver() { const receiver = fields[1] const content = fields[2] const xml = fields[3] + const userContent = fields[4] if (sender === "" || receiver === "" || content === "" || xml === "") { console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, { - length: 128, + length: x2, header: true, ansi: true, })) @@ -1118,36 +1072,27 @@ function setReceiver() { var groupId = "" var senderUser = sender var messages = []; - messages.push({type: "text", data: {text: content}}); + var senderNickname = "" + + let splitIndex = content.indexOf(':') + let pureContent = content.substring(splitIndex + 1).trim(); if (sender.includes("@chatroom")) { msgType = "group" groupId = sender - let splitIndex = -1; - for (let i = 0; i < content.length; i++) { - if (content[i] === ':') { - splitIndex = i; - break; + + senderUser = content.substring(0, splitIndex).trim(); + const parts = pureContent.split('\u2005'); + for (let part of parts) { + part = part.trim(); + if (!part.startsWith("@")) { + messages.push({type: "text", data: {text: part}}); } } const sendUserStart = content.indexOf('wxid_') senderUser = content.substring(sendUserStart, splitIndex).trim(); - messages = []; - const parts = content.split('\u2005'); - for (let part of parts) { - part = part.trim(); - if (!part.startsWith("@")) { - if (part.indexOf(":") !== -1) { - messages.push({type: "text", data: {text: part.substring(part.indexOf(':') + 1)}}); - } else { - messages.push({type: "text", data: {text: part}}); - } - - } - } - const atUserMatch = xml.match(/([\s\S]*?)<\/atuserlist>/); const atUser = atUserMatch ? atUserMatch[1] : null; if (atUser) { @@ -1158,6 +1103,21 @@ function setReceiver() { } }); } + + // 处理用户的名称 + splitIndex = userContent.indexOf(':') + if (splitIndex === -1) { + splitIndex = userContent.indexOf('在群聊中@了你') + senderNickname = userContent.substring(0, splitIndex).trim(); + } else { + senderNickname = userContent.substring(0, splitIndex).trim(); + } + + } else { + // 处理用户的名称 + const splitIndex = userContent.indexOf(':') + senderNickname = userContent.substring(0, splitIndex).trim(); + messages.push({type: "text", data: {text: pureContent}}); } const msgId = generateAESKey() @@ -1169,7 +1129,8 @@ function setReceiver() { message_id: msgId, type: "send", raw: {peerUid: msgId}, - message: messages + message: messages, + sender: {user_id: senderUser, nickname: senderNickname}, }) }, }); diff --git a/idapro/readme.md b/idapro/readme.md index ddeadc1..8eae417 100644 --- a/idapro/readme.md +++ b/idapro/readme.md @@ -1,2 +1,3 @@ -pip install qiling -brew install capstone \ No newline at end of file +使用 local ARM mac debug +sudo /Applications/IDA\ Professional\ 9.1.app/Contents/MacOS/ida +就能够attach \ No newline at end of file diff --git a/onebot/main.go b/onebot/main.go index 94f39ac..e08abdd 100644 --- a/onebot/main.go +++ b/onebot/main.go @@ -102,7 +102,7 @@ func initFlag() { flag.IntVar(&config.WechatPid, "wechat_pid", 0, "微信进程 ID: 58183, 仅当 type 为 local 时有效") flag.StringVar(&config.OnebotToken, "token", "MuseBot", "OneBot Token: MuseBot") flag.StringVar(&config.ImagePath, "image_path", "", "图片路径: /Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/xxx/temp/xxx/2026-01/Img/") - flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_6_47_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") + flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_7_31_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") flag.Parse() diff --git a/onebot/script.js b/onebot/script.js index 849ea7c..c0da663 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -220,7 +220,7 @@ var sendTextMessageAddr = ptr(0); var textMessageAddr = ptr(0); var textProtoX1PayloadAddr = ptr(0); var sendMessageCallbackFunc = ptr(0); -var messageCallbackFunc1 = baseAddr.add({{.messageCallbackFunc1}}); +var messageCallbackFunc1 = ptr(0); // 双方公共使用的地址 @@ -233,8 +233,8 @@ var sendMsgType = ""; var buf2RespAddr = baseAddr.add({{.buf2RespAddr}}); // 图片消息全局变量 -var sendImgMessageCallbackFunc = ptr(0x0); -var imgMessageCallbackFunc1 = baseAddr.add({{.imgMessageCallbackFunc1}}); +var sendImgMessageCallbackFunc = ptr(0); +var uploadImageAddr = baseAddr.add({{.uploadImageAddr}}); var imgProtobufAddr = baseAddr.add({{.imgProtobufAddr}}); var patchImgProtobufFunc1 = baseAddr.add({{.patchImgProtobufFunc1}}) var patchImgProtobufFunc2 = baseAddr.add({{.patchImgProtobufFunc2}}); @@ -433,7 +433,7 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) { console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${arg2}) Success. Return value: ` + result); return "ok"; } catch (e) { - console.error("[!] Error trigger function during execution: " + e); + console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} during execution: ` + e); return "fail"; } } @@ -609,7 +609,7 @@ function setupSendImgMessageDynamic() { })); // C. 构建 Message 结构体 - imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc1); + imgMessageAddr.add(0x00).writePointer(); imgMessageAddr.add(0x08).writeU32(taskIdGlobal); imgMessageAddr.add(0x0c).writeU32(0x6e); imgMessageAddr.add(0x10).writeU64(0x3); @@ -963,9 +963,7 @@ function triggerUploadImg(receiver, md5, imagePath) { uploadImagePayload.add(0x140).writePointer(ImagePathAddr1); uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr); - - const targetAddr = baseAddr.add(0x45DC834); - const startUploadMedia = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']); + const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); console.log("开始手动触发 C2C 上传..."); const result = startUploadMedia(uploadGlobalX0, uploadImagePayload); @@ -973,8 +971,7 @@ function triggerUploadImg(receiver, md5, imagePath) { } function attachUploadMedia() { - const targetAddr = baseAddr.add(0x45DC85C); - Interceptor.attach(targetAddr, { + Interceptor.attach(uploadImageAddr, { onEnter: function (args) { console.log("[+] enter UploadMedia"); uploadGlobalX0 = this.context.x0; diff --git a/wechat_version/4_1_6_47_mac.json b/wechat_version/4_1_6_47_mac.json index 48fb1ae..df0e0e1 100644 --- a/wechat_version/4_1_6_47_mac.json +++ b/wechat_version/4_1_6_47_mac.json @@ -13,5 +13,6 @@ "patchImgProtobufFunc1": "0x2275BB8", "patchImgProtobufFunc2": "0x2275BD8", "imgProtobufDeleteAddr": "0x2275C14", - "CndOnCompleteAddr": "0x34154E0" + "CndOnCompleteAddr": "0x34154E0", + "uploadImageAddr": "0x45DC834" } diff --git a/wechat_version/4_1_7_31_mac.json b/wechat_version/4_1_7_31_mac.json new file mode 100644 index 0000000..cc6f340 --- /dev/null +++ b/wechat_version/4_1_7_31_mac.json @@ -0,0 +1,17 @@ +{ + "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", + + "buf2RespAddr": "0x35F4B58" +}