From 33e58d004c0b7cf7c1750404423bf5ce42950844 Mon Sep 17 00:00:00 2001 From: yincong Date: Tue, 24 Mar 2026 11:00:58 +0800 Subject: [PATCH] add reply --- frida/file.js | 6 +- frida/reply.js | 656 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 661 insertions(+), 1 deletion(-) create mode 100644 frida/reply.js diff --git a/frida/file.js b/frida/file.js index 7efafa4..aa19e5d 100644 --- a/frida/file.js +++ b/frida/file.js @@ -103,7 +103,7 @@ function generateBytes(n) { // 双方公共使用的地址 var triggerX1Payload; var triggerX0; -var req2bufEnterAddr = baseAddr.add(`0x3806b30`); +var req2bufEnterAddr = baseAddr.add(0x3806b30); var req2bufExitAddr = baseAddr.add(0x3807C44); var sendFuncAddr = baseAddr.add(0x498D2E0); var insertMsgAddr = ptr(0); @@ -237,6 +237,10 @@ function attachReq2buf() { insertMsgAddr.writePointer(sendVideoMessageAddr); console.log("[+] 发送视频消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendVideoMessageAddr + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "reply") { + insertMsgAddr.writePointer(sendReplyMessageAddr); + console.log("[+] 发送回复消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendReplyMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); } } }); diff --git a/frida/reply.js b/frida/reply.js new file mode 100644 index 0000000..99eb7de --- /dev/null +++ b/frida/reply.js @@ -0,0 +1,656 @@ +// -------------------------Reply消息全局变量分区------------------------- + +// 模块信息 - 需要与file.js保持一致 +var moduleName = "wechat.dylib"; +var baseAddr = Process.findModuleByName(moduleName).base; + +// 全局变量 +var sendFuncAddr = baseAddr.add(0x4992040); +var req2bufEnterAddr = baseAddr.add(0x380b950); +var req2bufExitAddr = baseAddr.add(0x380CA64); +var taskIdGlobal = 0x0; +var lastSendTime = 0; +var receiverGlobal = "wxid_"; +var senderGlobal = "wxid_"; +var sendMsgType = ""; +var triggerX0; +var triggerX1Payload; +var insertMsgAddr; + +// 回复消息回调函数地址 (基于用户提供: 0x24BDE40) +var replyCallbackFuncAddr = baseAddr.add(0x24BDE40); +var replyProtobufAddr = replyCallbackFuncAddr.add(0x50); +var patchReplyProtobufFunc1 = replyCallbackFuncAddr.add(0x10); +var patchReplyProtobufFunc1Byte; +var patchReplyProtobufFunc2 = replyCallbackFuncAddr.add(0x30); +var patchReplyProtobufFunc2Byte; +var replyProtobufDeleteAddr = replyCallbackFuncAddr.add(0x6c); +var replyProtobufDeleteAddrByte; + +// Reply消息相关地址 - 需要导出为全局变量供file.js使用 +var sendReplyMessageAddr = ptr(0); +var replyMessageAddr = ptr(0); +var replyCgiAddr = ptr(0); +var replyProtobufBufAddr = ptr(0); // 用于存储protobuf数据的缓冲区 +var replyMessageCallbackFunc1 = baseAddr.add(0x89170D0); // 复用图片的callback函数 + +// Reply消息内容相关 +var replyContent = ""; // 回复的文本内容 +var replyReferMsgContent = ""; // 被引用消息的内容 +var replyReferMsgType = 1; // 被引用消息的类型 +var replyReferMsgSender = ""; // 被引用消息的发送者 +var replyReferMsgId = ""; // 被引用消息的ID +var replyChatRoom = ""; // 是否是群聊 +var replyFromUser = ""; // 回复者wxid + +// -------------------------Reply消息全局变量分区------------------------- + + +// -------------------------发送Reply消息动态初始化------------------------- +function setupSendReplyMessageDynamic() { + console.log("[+] Starting setupSendReplyMessageDynamic Dynamic Message Patching..."); + + // 1. 动态分配内存块 + sendReplyMessageAddr = Memory.alloc(256); + replyMessageAddr = Memory.alloc(256); + replyCgiAddr = Memory.alloc(128); + replyProtobufBufAddr = Memory.alloc(3069); + + // 写入reply的cgi路径 + patchString(replyCgiAddr, "/cgi-bin/micromsg-bin/sendappmsg"); + + // 初始化sendReplyMessageAddr结构 + sendReplyMessageAddr.add(0x00).writeU64(0); + sendReplyMessageAddr.add(0x08).writeU64(0); + sendReplyMessageAddr.add(0x10).writeU64(0); + sendReplyMessageAddr.add(0x18).writeU64(1); + sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendReplyMessageAddr.add(0x28).writePointer(replyMessageAddr); + + // 初始化replyMessageAddr结构 + replyMessageAddr.add(0x00).writePointer(replyMessageCallbackFunc1); + replyMessageAddr.add(0x08).writeU32(taskIdGlobal); + replyMessageAddr.add(0x0c).writeU32(0x6e); + replyMessageAddr.add(0x10).writeU64(0x3); + replyMessageAddr.add(0x18).writePointer(replyCgiAddr); // 添加cgi地址 + replyMessageAddr.add(0x20).writeU64(0x22); + replyMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); + replyMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); + + // 保存原始字节用于恢复 + patchReplyProtobufFunc1Byte = patchReplyProtobufFunc1.readByteArray(4); + patchReplyProtobufFunc2Byte = patchReplyProtobufFunc2.readByteArray(4); + replyProtobufDeleteAddrByte = replyProtobufDeleteAddr.readByteArray(4); +} + +setImmediate(setupSendReplyMessageDynamic); + + +// -------------------------Patch Reply protobuf------------------------- +function patchReplyProtoBuf() { + // attach到replyProtobufAddr (replyCallbackFuncAddr + 0x50) 来修改protobuf + Interceptor.attach(replyCallbackFuncAddr, { + onEnter: function (args) { + var firstValue = this.context.sp.add(0x10).readU32(); + console.log("[+] 捕获到 ReplyProtobufAddr 调用,firstValue:", firstValue, "taskIdGlobal:", taskIdGlobal); + + if (firstValue === taskIdGlobal) { + // 不匹配时,patch掉这些函数(NOP替换) + console.log("[+] Reply taskId匹配,开始patch..."); + if (patchReplyProtobufFunc1.readU32() !== 3573751839) { + Memory.patchCode(patchReplyProtobufFunc1, 4, code => { + const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1}); + cw.putNop(); + cw.flush(); + }); + Memory.patchCode(patchReplyProtobufFunc2, 4, code => { + const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2}); + cw.putNop(); + cw.flush(); + }); + Memory.patchCode(replyProtobufDeleteAddr, 4, code => { + const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr}); + cw.putNop(); + cw.flush(); + }); + } + } else { + // 匹配时,恢复原来的字节 + console.log("[+] Reply taskId不匹配,开始修改protobuf..."); + if (patchReplyProtobufFunc1.readU32() === 3573751839) { + Memory.patchCode(patchReplyProtobufFunc1, 4, code => { + const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1}); + cw.putBytes(new Uint8Array(patchReplyProtobufFunc1Byte)); + cw.flush(); + }); + Memory.patchCode(patchReplyProtobufFunc2, 4, code => { + const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2}); + cw.putBytes(new Uint8Array(patchReplyProtobufFunc2Byte)); + cw.flush(); + }); + Memory.patchCode(replyProtobufDeleteAddr, 4, code => { + const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr}); + cw.putBytes(new Uint8Array(replyProtobufDeleteAddrByte)); + cw.flush(); + }); + } + } + } + }); +} + +setImmediate(patchReplyProtoBuf); + +function attachProto() { + + Interceptor.attach(replyProtobufAddr, { + onEnter: function (args) { + + var currTaskId = this.context.sp.add(0x30).readU32(); + if (currTaskId !== taskIdGlobal) { + console.log(`[+] 拦截到非目标 currTaskId: ${currTaskId} taskIdGlobal: ${taskIdGlobal}` + hexdump(this.context.x1, { + offset: 0, + length: 3069, + header: true, + ansi: true + })); + return; + } + + const replyProtobuf = buildReplyProtobuf(); + + // 写入protobuf数据到缓冲区 + replyProtobufBufAddr.writeByteArray(replyProtobuf); + console.log("[+] Reply protobuf写入到缓冲区: ", replyProtobufBufAddr); + + // 设置x1指向缓冲区地址,x2为长度 + this.context.x1 = replyProtobufBufAddr; + this.context.x2 = ptr(replyProtobuf.length); + + console.log("[+] Reply寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(replyProtobufBufAddr, { + offset: 0, + length: 3069, + header: true, + ansi: true + })); + } + }); +} + +setImmediate(attachProto); + + +// -------------------------触发发送Reply消息------------------------- +function triggerSendReplyMessage(taskId, sender, receiver, content, referMsgContent, referMsgType, referMsgSender, referMsgId, chatRoom) { + console.log("[+] Manual Trigger Reply Message Started..."); + + if (!taskId || !receiver || !sender) { + console.error("[!] taskId or receiver or sender is empty!"); + return "fail"; + } + + // 更新全局变量 + const timestamp = Math.floor(Date.now() / 1000); + lastSendTime = timestamp; + taskIdGlobal = taskId; + receiverGlobal = receiver; + senderGlobal = sender; + replyContent = content || ""; + replyReferMsgContent = referMsgContent || ""; + replyReferMsgType = referMsgType || 1; + replyReferMsgSender = referMsgSender || sender; + replyReferMsgId = referMsgId || generateRandomMsgId(); + replyChatRoom = chatRoom || ""; + replyFromUser = sender; + + // 更新消息地址的taskId + replyMessageAddr.add(0x08).writeU32(taskIdGlobal); + sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal); + + console.log("start init reply payload"); + + // 使用与triggerSendImgMessage类似的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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 + 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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198 + ]; + triggerX1Payload.writeU32(taskIdGlobal); + triggerX1Payload.add(0x04).writeByteArray(payloadData); + triggerX1Payload.add(0x18).writePointer(replyCgiAddr); // 使用reply的cgi + triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); + triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); + + sendMsgType = "reply"; + + console.log("finished init reply payload"); + + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); + + try { + const result = MMStartTask(triggerX0, triggerX1Payload); + console.log(`[+] Execution StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}) Success. Return value: ` + result); + return "ok"; + } catch (e) { + console.error(`[!] Error trigger StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); + return "fail"; + } +} + + +// -------------------------构建Reply消息Protobuf------------------------- +function buildReplyProtobuf() { + // 构建appmsg的XML内容 + const appmsgXml = buildReplyAppmsgXml(); + + // 根据用户提供的protobuf格式构建 + const payload = []; + + // 先构建内部数据 + const innerData = []; + + // 字段1: 0x0A 0x01 0x00 (固定) + innerData.push(0x0A, 0x01, 0x00); + + // 字段2: 0x10 + varint (时间戳) + innerData.push(0x10); + const timestampVarint = encodeVarint(Math.floor(Date.now() / 1000)); + innerData.push(...timestampVarint); + + // 字段3: 0x1A 0x10 + md5 (16字节) + innerData.push(0x1A, 0x10); + const md5Bytes = stringToUtf8Bytes(generateRandomMd5()); + innerData.push(...md5Bytes); + + // 字段4: 0x20 + varint (大整数 0x1FF... ) + innerData.push(0x20, 0x9D, 0xB0, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01); + + // 字段5: 0x2A 0x15 + "UnifiedPCMac 26 arm64" + innerData.push(0x2A, 0x15); + innerData.push(...stringToUtf8Bytes("UnifiedPCMac 26 arm64")); + + const fromUsrBytes = stringToUtf8Bytes(senderGlobal); + innerData.push(0x30, 0xa8, 0x01, 0x12, 0xb9); + innerData.push(...encodeVarint(fromUsrBytes.length + 2)); + innerData.push(0x0a) + innerData.push(...encodeVarint(fromUsrBytes.length)); + innerData.push(...fromUsrBytes); + + innerData.push(0x12, 0x00, 0x18, 0x00); + const receiverBytes = stringToUtf8Bytes(receiverGlobal) + innerData.push(0x22); + innerData.push(...encodeVarint(receiverBytes.length)); + innerData.push(...receiverBytes); + + + // 结尾 varint + innerData.push(0x28, 0x39, 0x32); + const appmsgBytes = stringToUtf8Bytes(appmsgXml); + innerData.push(...encodeVarint(appmsgBytes.length)) + innerData.push(...appmsgBytes) + + innerData.push(0x38, 0xb2, 0xe4, 0x87, 0xce, 0x06, 0x42) + + const receiverMsgId = receiverBytes.concat([0x5F]) + .concat(stringToUtf8Bytes(Math.floor(Date.now() / 1000).toString())) + .concat([0x5F, 0x31, 0x36, 0x30, 0x5F, 0x78, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5F, 0x33]); + innerData.push(...encodeVarint(receiverMsgId.length)) + innerData.push(...receiverMsgId) + + innerData.push(0x50, 0x01, 0x62, 0x32, 0x3c, 0x6d, 0x73, 0x67, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x3e, 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); + + // 结尾字段 (根据成功版本) + // 字段12: 0x6A (field 6) + 0x00 + innerData.push(0x6A, 0x00); + // 字段13: 0x72 (field 7) + 0x00 + innerData.push(0x72, 0x00); + // 字段14: 0x7A (field 8) + 0x00 + innerData.push(0x7A, 0x00); + // 字段15: 0x2A (field 9) + 0x00 + innerData.push(0x2A, 0x00); + // 字段16: 0x48 (field 10) + 0x00 + innerData.push(0x48, 0x00); + // 字段17: 0x50 (field 11) + 0x00 + innerData.push(0x50, 0x00); + // 字段18: 0x58 (field 12) + 0x02 + 10个0 + innerData.push(0x58); + innerData.push(0x02); + + payload.push(0x0A, 0x40); + payload.push(...innerData); + + return payload; +} + +// 生成随机MD5格式字符串 (16字节) +function generateRandomMd5() { + let result = "md4"; + for (let i = 0; i < 13; i++) { + result += Math.floor(Math.random() * 10).toString(); + } + return result; +} + +// 生成接收者消息ID +function generateReceiverMsgId() { + const timestamp = Math.floor(Date.now() / 1000); + const receiver = replyChatRoom || receiverGlobal; + return receiver + "_" + timestamp + "_" + Math.floor(Math.random() * 100) + "_xwechat_3"; +} + +// Varint编码 +function encodeVarint(value) { + const result = []; + if (value === 0) { + result.push(0); + return result; + } + while (value > 0) { + let byte = value & 0x7F; + value >>= 7; + if (value > 0) { + byte |= 0x80; + } + result.push(byte); + } + return result; +} + + +// -------------------------构建Reply Appmsg XML------------------------- +function buildReplyAppmsgXml() { + // 构建appmsg XML内容 + // type=57 是reply消息类型 + + // 构建msgsource内容(转义后的XML) + const msgsourceContent = "" + + "1" + + "" + + "" + generateUuid() + "" + + "" + + "" + + "" + + ""; + + // 构建content内容(转义后的msg) + const msgContent = "" + + "" + + "" + escapeXml(replyContent) + "" + + "" + + "" + + "57" + + "0" + + "0" + + "" + + "" + + "" + + "" + + "0" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "0" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + let xml = ""; + xml += "" + escapeXml(replyContent) + ""; + xml += ""; + xml += ""; + xml += "57"; + xml += "0"; + xml += "0"; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += "0"; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += "0"; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + xml += ""; + + // 添加refermsg (引用消息) + if (replyReferMsgContent || replyReferMsgSender) { + xml += ""; + if (replyReferMsgSender) { + xml += "" + escapeXml(replyReferMsgSender) + ""; + } + xml += "" + replyReferMsgType + ""; + xml += "" + escapeXml(replyReferMsgId || generateRandomMsgId()) + ""; + xml += "" + escapeXml(replyReferMsgSender) + ""; + xml += "" + escapeXml(replyReferMsgContent) + ""; + xml += ""; + } + + xml += ""; + + // 添加msgsource(转义后的XML) + xml += "" + escapeXml(msgsourceContent) + ""; + + // 添加displayname + xml += ""; + + // 添加svrid + xml += "" + generateRandomMsgId() + ""; + + // 添加fromusr + xml += "" + escapeXml(senderGlobal) + ""; + + // 添加content(转义后的msg) + xml += "" + escapeXml(msgContent) + ""; + + // 添加fromusername + xml += "" + escapeXml(senderGlobal) + ""; + + // 添加appinfo + xml += ""; + xml += "0"; + xml += ""; + xml += "0"; + xml += ""; + + return xml; +} + +// 生成UUID +function generateUuid() { + return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/x/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }).replace(/-/g, ''); +} + +function AttachSendTextProto() { + Interceptor.attach(sendFuncAddr.add(0x08), { + onEnter: function (args) { + if (triggerX1Payload) { + return + } + + triggerX0 = this.context.x0; + triggerX1Payload = this.context.x1; + console.log(`[+] 捕获到 StartTask 调用,X0地址:${triggerX0}, Payload 地址: ${triggerX1Payload}`); + } + }) +} + +setImmediate(AttachSendTextProto); + + +// -------------------------Reply消息Req2Buf拦截------------------------- +function attachReplyReq2buf() { + console.log("[+] Target Reply Req2buf enter Address: " + req2bufEnterAddr); + + // 拦截入口 + Interceptor.attach(req2bufEnterAddr, { + onEnter: function (args) { + if (!this.context.x1.equals(taskIdGlobal)) { + return; + } + + console.log("[+] 已命中Reply目标Req2Buf地址 taskId:" + taskIdGlobal); + + // 获取 X24 寄存器的值 + const x24_base = this.context.x24; + insertMsgAddr = x24_base.add(0x60); + console.log("[+] 当前 Reply Req2Buf X24 基址: " + x24_base); + + if (sendMsgType === "reply") { + insertMsgAddr.writePointer(sendReplyMessageAddr); + console.log("[+] 发送回复消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendReplyMessageAddr); + } + } + }); + + // 拦截出口 + console.log("[+] Target Reply Req2buf leave Address: " + req2bufExitAddr); + Interceptor.attach(req2bufExitAddr, { + onEnter: function (args) { + if (!this.context.x25.equals(taskIdGlobal)) { + return; + } + insertMsgAddr.writeU64(0x0); + console.log("[+] Reply Req2Buf 清空完成"); + taskIdGlobal = 0; + receiverGlobal = "wxid_"; + senderGlobal = "wxid_"; + } + }); +} + +setImmediate(attachReplyReq2buf); + + +// -------------------------辅助函数------------------------- + +// 字符串转UTF8字节数组 +function stringToUtf8Bytes(str) { + const utf8 = []; + for (let i = 0; i < str.length; i++) { + let charcode = str.charCodeAt(i); + if (charcode < 0x80) utf8.push(charcode); + else if (charcode < 0x800) { + utf8.push(0xc0 | (charcode >> 6), + 0x80 | (charcode & 0x3f)); + } else if (charcode < 0x10000) { + utf8.push(0xe0 | (charcode >> 12), + 0x80 | ((charcode >> 6) & 0x3f), + 0x80 | (charcode & 0x3f)); + } else { + utf8.push(0xf0 | (charcode >> 18), + 0x80 | ((charcode >> 12) & 0x3f), + 0x80 | ((charcode >> 6) & 0x3f), + 0x80 | (charcode & 0x3f)); + } + } + return utf8; +} + +// XML转义 +function escapeXml(str) { + if (!str) return ""; + return str.replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +// 生成随机消息ID +function generateRandomMsgId() { + let result = ''; + for (let i = 0; i < 16; i++) { + result += Math.floor(Math.random() * 10).toString(); + } + return result; +} + +// 辅助函数: 将字符串写入内存 +function patchString(addr, str) { + for (let i = 0; i < str.length; i++) { + addr.add(i).writeU8(str.charCodeAt(i)); + } + addr.add(str.length).writeU8(0); +} + +// -------------------------RPC导出函数------------------------- + +// 发送Reply消息的RPC接口 +// 参数: taskId, sender, receiver, content, referMsgContent, referMsgType, referMsgSender, referMsgId, chatRoom +rpc.exports = { + triggerSendReplyMessage: triggerSendReplyMessage +};