From 2fd0611e78f480e92c9a75ac0744c22d894a8695 Mon Sep 17 00:00:00 2001 From: yincong Date: Tue, 20 Jan 2026 10:36:49 +0800 Subject: [PATCH] update onebot doc --- README.md | 3 + frida/succ.js | 1329 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 967 insertions(+), 365 deletions(-) diff --git a/README.md b/README.md index 9a7f1a9..ae9101c 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,6 @@ manualUpload("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/ manualTrigger(0x20000199, "wxid_xxx","wxid_xxx") ``` + +## 支持onebot协议 (http接口) +https://github.com/yincongcyincong/weixin-macos/blob/main/onebot/readme.md diff --git a/frida/succ.js b/frida/succ.js index 39a4a30..6ad634c 100644 --- a/frida/succ.js +++ b/frida/succ.js @@ -5,97 +5,334 @@ 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; +} -// 这个必须是绝对位置 + +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 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]; + 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; +} + +// -------------------------基础函数分区------------------------- + +// -------------------------全局变量分区------------------------- + +// 文本消息全局变量 +var protobufAddr = baseAddr.add(0x227EC70); +var patchTextProtobufAddr = baseAddr.add(0x227EC4C); +var PatchTextProtobufDeleteAddr = baseAddr.add(0x227EC88); +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 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); - -// 消息体的一些指针地址 -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); +var req2bufEnterAddr = baseAddr.add(0x34566C0); +var req2bufExitAddr = baseAddr.add(0x34577D8); +var sendFuncAddr = baseAddr.add(0x448A858); 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(0x347BD44); -// 消息的taskId +// 图片消息全局变量 +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 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; -// 打印消息的地址,便于查询问题 -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); -} +// -------------------------全局变量分区------------------------- -// 辅助函数:写入 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 +340,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) { 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,20 +392,15 @@ 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; - 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); + + console.log("start init payload") const payloadData = [ 0x0A, 0x02, 0x00, 0x00, // 0x00 @@ -260,21 +456,21 @@ 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 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); + console.log(`[+] Calling MMStartTask at ${sendFuncAddr} with args: (${arg2})`); + const result = MMStartTask(arg2); + console.log("[+] Execution MMStartTask Success. Return value: " + result); return "ok"; } catch (e) { console.error("[!] Error trigger function during execution: " + e); @@ -282,139 +478,17 @@ function manualTrigger(taskId, receiver, content) { } } - -// 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); - 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 - })) - } 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("[+] 0x1033EFA00 清空写入后内存预览: " + insertMsgAddr.readPointer()); - taskIdGlobal = 0; - receiverGlobal = ""; - contentGlobal = ""; - send({ - type: "finish", - }) - } - }); -} - -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() { +// 拦截 SendTextProto 编码逻辑,注入自定义 Payload +function attachSendTextProto() { console.log("[+] proto注入拦截目标地址: " + protobufAddr); - protoX1PayloadAddr = Memory.alloc(protoX1PayloadLen); - console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr); + textProtoX1PayloadAddr = Memory.alloc(1024); + console.log("[+] Frida 分配的 Payload 地址: " + textProtoX1PayloadAddr); 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 拦截未命中,跳过..."); @@ -450,89 +524,651 @@ function attachProto() { 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); + textProtoX1PayloadAddr.writeByteArray(finalPayload); console.log("[+] Payload 已写入,长度: " + finalPayload.length); - this.context.x1 = protoX1PayloadAddr; + this.context.x1 = textProtoX1PayloadAddr; 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(textProtoX1PayloadAddr, { offset: 0, length: 128, header: true, ansi: true })); + }, + }); +} + +setImmediate(attachSendTextProto); + +// -------------------------发送文本消息分区------------------------- + + +// -------------------------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); + + + // 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 未定义,请确保已运行分配逻辑。"); + } + } + }); + + // 在出口处拦截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 = ""; + send({ + type: "finish", + }) } }); } -function toVarint(n) { - let res = []; - while (n >= 128) { - res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 - n = n >> 7; // 右移7位 +setImmediate(attachReq2buf); + +// -------------------------Req2Buf公共部分分区------------------------- + +// -------------------------发送图片消息分区------------------------- + +// 初始化进行内存的分配 +function setupSendImgMessageDynamic() { + console.log("[+] Starting setupSendImgMessageDynamic Dynamic Message Patching..."); + + // 1. 动态分配内存块(按需分配大小) + // 分配原则:字符串给 64-128 字节,结构体按实际大小分配 + imgCgiAddr = Memory.alloc(128); + sendImgMessageAddr = Memory.alloc(256); + 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"); + + // B. 构建 SendMessage 结构体 (X24 基址位置) + sendImgMessageAddr.add(0x00).writeU64(0); + sendImgMessageAddr.add(0x08).writeU64(0); + sendImgMessageAddr.add(0x10).writePointer(sendImgMessageCallbackFunc); + sendImgMessageAddr.add(0x18).writeU64(1); + sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendImgMessageAddr.add(0x28).writePointer(imgMessageAddr); + + console.log(" [+] sendImgMessageAddr Object: ", hexdump(sendImgMessageAddr, { + offset: 0, + length: 48, + header: true, + ansi: true + })); + + // C. 构建 Message 结构体 + imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc1); + imgMessageAddr.add(0x08).writeU32(taskIdGlobal); + imgMessageAddr.add(0x0c).writeU32(0x6e); + imgMessageAddr.add(0x10).writeU64(0x3); + imgMessageAddr.add(0x18).writePointer(imgCgiAddr); + imgMessageAddr.add(0x20).writeU64(0x22); + imgMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); + imgMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); + + console.log(" [+] Dynamic Memory Setup Complete. - Message Object: " + imgMessageAddr); + + + uploadFunc1Addr.writePointer(baseAddr.add(0x802b8b0)); + uploadFunc2Addr.writePointer(baseAddr.add(0x7fd5908)); +} + +setImmediate(setupSendImgMessageDynamic); + + +function patchImgProtoBuf() { + Memory.patchCode(patchImgProtobufFunc1, 4, code => { + const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc1}); + cw.putNop(); + cw.flush(); + }); + + console.log("[+] Patching patchImgProtobufFunc1 " + patchImgProtobufFunc1 + " 成功."); + + Memory.patchCode(patchImgProtobufFunc2, 4, code => { + const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc2}); + cw.putNop(); + cw.flush(); + }); + + console.log("[+] Patching patchImgProtobufFunc2 " + patchImgProtobufFunc2 + " 成功."); + + Memory.patchCode(imgProtobufDeleteAddr, 4, code => { + const cw = new Arm64Writer(code, {pc: imgProtobufDeleteAddr}); + cw.putNop(); + cw.flush(); + }); + + console.log("[+] Patching imgProtobufDeleteAddr " + imgProtobufDeleteAddr + " 成功."); +} + + +setTimeout(function () { + console.log("[+] 2秒等待结束,准备执行 Patch..."); + patchImgProtoBuf(); +}, 2000); + +function triggerSendImgMessage(taskId, sender, receiver) { + console.log("[+] Manual Trigger Started..."); + if (!taskId || !receiver || !sender) { + console.error("[!] taskId or receiver or sender is empty!"); + return "fail"; } - res.push(n); // 最后一位最高位为0 - return res; + + // 获取当前时间戳 (秒) + const timestamp = Math.floor(Date.now() / 1000); + lastSendTime = timestamp + taskIdGlobal = taskId; + receiverGlobal = receiver; + senderGlobal = sender; + + imgMessageAddr.add(0x08).writeU32(taskIdGlobal); + sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); + + console.log("start init payload") + + const payloadData = [ + 0x6e, 0x00, 0x00, 0x00, // 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 + 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // 0x10 + 0x40, 0xec, 0x0e, 0x12, 0x01, 0x00, 0x00, 0x00, // 0x18 + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 cgi的长度 + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x28 + 0x00, 0x01, 0x01, 0x01, 0x00, 0xAA, 0xAA, 0xAA, // 0x30 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // 0x38 + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, // 0x48 + 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, // 0x50 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58 + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x2D, // 0x68 default- + 0x6C, 0x6F, 0x6E, 0x67, 0x6C, 0x69, 0x6E, 0x6B, // 0x70 longlink + 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x10, // 0x78 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x98 + 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, // 0xC0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x100 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x108 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x110 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x118 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x120 + 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, // 0x140 + 0x01, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x148 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x158 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x168 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x170 + 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, // 0x198 + ]; + triggerX1Payload.writeU32(taskIdGlobal); + triggerX1Payload.add(0x04).writeByteArray(payloadData); + triggerX1Payload.add(0x18).writePointer(imgCgiAddr); + sendMsgType = "img" + + console.log("finished init payload") + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer']); + + // 5. 调用函数 + try { + 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); + return "ok"; + } catch (e) { + console.error("[!] Error trigger function during execution: " + e); + return "fail"; + } +} + + +// 拦截 Protobuf 编码逻辑,注入自定义 Payload +function attachProto() { + 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 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, receiverMsgId.length + 2, 0x0A, receiverMsgId.length] + + const senderHeader = [0x1A, senderGlobal.length + 2, 0x0A, senderGlobal.length]; + // wxid_xxxx 或者 chatroom + const sender = stringToHexArray(senderGlobal); + const receiverHeader = [0x22, receiverGlobal.length + 2, 0x0A, receiverGlobal.length] + // wxid_xxxx + const receiver = stringToHexArray(receiverGlobal) + const randomId1 = [0x28, 0xF4, 0x0B] + const type1 = [0x30, 0x00] + const randomId2 = [0x38, 0xF4, 0x0B] + const randomId3 = [0x42, 0x04, 0x08, 0x00, 0x12, 0x00] + const randomId4 = [0x48, 0x03] + const htmlHeader = [0x52, 0x32]; + + const html = [0x3C, + 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 cdnHeader = [0x58, 0x01, 0x60, 0x02, 0x68, 0x00, 0x7A, 0xB2, 0x01] + // 3057 开头的cdn key + const cdn = stringToHexArray(globalImageCdnKey); + + const cdn2Header = [0x82, 0x01, 0xB2, 0x01] + const cdn2 = stringToHexArray(globalImageCdnKey) + + const aesKeyHeader = [0x8A, 0x01, 0x20] + const aesKey = stringToHexArray(globalAesKey1) + + const randomId5 = [0x90, 0x01, 0x01, 0x98, 0x01, 0xFF, // 0x2C8 + 0x13, 0xA0, 0x01, 0xFF, 0x13] + + const cdn3Header = [0xAA, 0x01, 0xB2, 0x01] + const cdn3 = stringToHexArray(globalImageCdnKey) + + const randomId6 = [0xB0, 0x01, 0xF4, 0x0B] + const randomId7 = [0xB8, 0x01, 0x68] + const randomId8 = [0xC0, 0x01, 0x3A] + const aesKey1Header = [0xCA, 0x01, 0x20] + const aesKey1 = stringToHexArray(globalAesKey1) + const md5Header = [0xDA, 0x01, 0x20] + const me5Key = stringToHexArray(globalMd5Key) + + const randomId9 = [0xE0, 0x01, 0xd9, 0xe7, 0xc7, 0xF3, 0x02] + + var left0 = [ + 0xF0, 0x01, 0x00, 0xA0, 0x02, 0x00, // 0x3E0 + 0xC8, 0x02, 0x00, 0x00 // 0x3E8 + ] + + const finalPayload = type.concat(msgId, cpHeader, cp, 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 准备写入"); + imgProtoX1PayloadAddr.writeByteArray(finalPayload); + console.log("[+] Payload 已写入,长度: " + finalPayload.length); + + this.context.x1 = imgProtoX1PayloadAddr; + this.context.x2 = ptr(finalPayload.length); + + console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(imgProtoX1PayloadAddr, { + offset: 0, + length: 256, + header: true, + ansi: true + })); + }, + }); } setImmediate(attachProto); + +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 的指针 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 + 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 图片id // 0x48 + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x78, 0x69, 0x64, 0x5F, 0x37, 0x77, 0x64, // 发送人 0x68 + 0x31, 0x65, 0x63, 0x65, 0x39, 0x39, 0x66, 0x37, + 0x69, 0x32, 0x31, 0x00, 0x00, 0x00, 0x00, 0x13, // 发送人id长度 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88 + 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 + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x55, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0xe0 图片地址 高清 /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_ldftuhe36izg19_5e7d/temp/04ebaab7e3ea6050e26ff31d89cc121e/2026-01/Img/166_1768214492_hd.jpg + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xe8 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0xf0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x100 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x108 + 0x40, 0x54, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x110 图片地址 普清 /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_ldftuhe36izg19_5e7d/temp/04ebaab7e3ea6050e26ff31d89cc121e/2026-01/Img/166_1768214492.jpg + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x118 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x120 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x5D, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x140 图片地址 缩略图 /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_ldftuhe36izg19_5e7d/temp/04ebaab7e3ea6050e26ff31d89cc121e/2026-01/Img/166_1768214492_thumb.jpg + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x148 + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x150 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x158 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160 + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x168 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x170 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x178 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x180 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// 0x188 + 0x00, 0xAA, 0xAA, 0xAA, 0x01, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// 0x1a0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1a8 + 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0A, 0x0A, // 0x1b0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1b8 + 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, 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 + 0xD0, 0x78, 0x46, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x1f8 某个key ecd57e9cf85f2e2087aee8c0fd1e445e + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x200 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x208 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x210 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x218 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x220 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x228 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x230 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x238 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x240 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x248 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// 0x250 + 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x258 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x260 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x268 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x270 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// 0x278 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// 0x280 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x288 + ] + + patchString(imageIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); + patchString(md5Addr, md5) + patchString(uploadAesKeyAddr, generateAESKey()) + patchString(ImagePathAddr1, imagePath); + + uploadImagePayload.writeByteArray(payload); + uploadImagePayload.writePointer(uploadFunc1Addr); + uploadImagePayload.add(0x08).writePointer(uploadFunc2Addr); + uploadImagePayload.add(0x48).writePointer(imageIdAddr); + uploadImagePayload.add(0x68).writeUtf8String(receiver); + uploadImagePayload.add(0xa8).writePointer(md5Addr); + uploadImagePayload.add(0xe0).writePointer(ImagePathAddr1); + uploadImagePayload.add(0x110).writePointer(ImagePathAddr1); + uploadImagePayload.add(0x140).writePointer(ImagePathAddr1); + uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr); + + + const targetAddr = baseAddr.add(0x45DC834); + const startUploadMedia = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']); + + console.log("开始手动触发 C2C 上传..."); + const result = startUploadMedia(uploadGlobalX0, uploadImagePayload); + console.log("调用结果: " + result); +} + +function attachUploadMedia() { + const targetAddr = baseAddr.add(0x45DC85C); + Interceptor.attach(targetAddr, { + onEnter: function (args) { + console.log("[+] enter UploadMedia"); + uploadGlobalX0 = this.context.x0; + 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 x1: " + uploadGlobalX0 + " imagePath: " + imagePath + " selfId: " + selfId); + } + }) +} + +setImmediate(attachUploadMedia); + +function patchCdnOnComplete() { + Interceptor.attach(CndOnCompleteAddr, { + onEnter: function (args) { + console.log("[+] enter CndOnCompleteAddr"); + + try { + const x2 = this.context.x2; + globalImageCdnKey = x2.add(0x60).readPointer().readUtf8String(); + globalAesKey1 = x2.add(0x78).readPointer().readUtf8String(); + globalMd5Key = x2.add(0x90).readPointer().readUtf8String(); + 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); + } + } + }) +} + +setImmediate(patchCdnOnComplete) + + +rpc.exports = { + triggerSendImgMessage: triggerSendImgMessage, + triggerUploadImg: triggerUploadImg, + triggerSendTextMessage: triggerSendTextMessage +}; + +// -------------------------发送图片消息分区------------------------- + +// -------------------------接收消息分区------------------------- function setReceiver() { - console.log("[+] setReceiver WeChat Base: " + baseAddr + "[+] Attaching to: " + receiveAddr); // 3. 开始拦截 - Interceptor.attach(receiveAddr, { + Interceptor.attach(buf2RespAddr, { 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"; + const currentPtr = this.context.x1; + let start = 0x1e; + let senderLen = currentPtr.add(start).readU8(); + if (senderLen !== 0x14 && senderLen !== 0x13) { + start = 0x1d; + let senderLen = currentPtr.add(start).readU8(); + if (senderLen !== 0x14 && senderLen !== 0x13) { + return } } + const x2 = this.context.x2.toInt32(); + const fields = getProtobufRawBytes(currentPtr, x2) + + const sender = fields[0] + const receiver = fields[1] + const content = fields[2] + const xml = fields[3] + + if (sender === "" || receiver === "" || content === "" || xml === "") { + console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, { + length: 128, + header: true, + ansi: true, + })) + return; + } + + var selfId = receiver var msgType = "private" var groupId = "" - if (receiver.includes("@chatroom")) { - msgType = "group" - groupId = receiver - } - - var parts = strD0.split('\u2005'); + var senderUser = sender 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}}); + messages.push({type: "text", data: {text: content}}); + + 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; + } + } + + 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) { + atUser.split(',').forEach(atUser => { + atUser = atUser.trim(); + if (atUser) { + messages.push({type: "at", data: {qq: atUser}}); + } + }); } } + const msgId = generateAESKey() send({ message_type: msgType, - user_id: sender, - self_id: selfId, - group_id: groupId, - message_id: taskIdGlobal, + user_id: senderUser, // 发送人的 ID + self_id: selfId, // 接收人的 ID + group_id: groupId, // 群 ID + message_id: msgId, type: "send", - raw: {peerUid: receiver}, + raw: {peerUid: msgId}, message: messages }) }, @@ -541,41 +1177,4 @@ function setReceiver() { // 使用 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 +// -------------------------接收消息分区------------------------- \ No newline at end of file