add reply

This commit is contained in:
yincong
2026-03-24 11:00:58 +08:00
parent 08ad4507c7
commit 33e58d004c
2 changed files with 661 additions and 1 deletions
+5 -1
View File
@@ -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);
}
}
});
+656
View File
@@ -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 = "<msgsource>" +
"<alnode><fr>1</fr></alnode>" +
"<sec_msg_node>" +
"<uuid>" + generateUuid() + "</uuid>" +
"<risk-file-flag></risk-file-flag>" +
"<risk-file-md5-list></risk-file-md5-list>" +
"</sec_msg_node>" +
"</msgsource>";
// 构建content内容(转义后的msg
const msgContent = "<msg>" +
"<appmsg appid=\"\" sdkver=\"0\">" +
"<title>" + escapeXml(replyContent) + "</title>" +
"<des></des>" +
"<action></action>" +
"<type>57</type>" +
"<showtype>0</showtype>" +
"<soundtype>0</soundtype>" +
"<mediatagname></mediatagname>" +
"<messageext></messageext>" +
"<messageaction></messageaction>" +
"<content></content>" +
"<contentattr>0</contentattr>" +
"<url></url>" +
"<lowurl></lowurl>" +
"<dataurl></dataurl>" +
"<lowdataurl></lowdataurl>" +
"<songalbumurl></songalbumurl>" +
"<songlyric></songlyric>" +
"<template_id></template_id>" +
"<appattach><totallen>0</totallen><attachid></attachid><emoticonmd5></emoticonmd5><fileext></fileext><aeskey></aeskey></appattach>" +
"<extinfo></extinfo>" +
"<sourceusername></sourceusername>" +
"<sourcedisplayname></sourcedisplayname>" +
"<thumburl></thumburl>" +
"<md5></md5>" +
"<statextstr></statextstr>" +
"</appmsg>" +
"</msg>";
let xml = "<appmsg appid=\"\" sdkver=\"0\">";
xml += "<title>" + escapeXml(replyContent) + "</title>";
xml += "<des></des>";
xml += "<action></action>";
xml += "<type>57</type>";
xml += "<showtype>0</showtype>";
xml += "<soundtype>0</soundtype>";
xml += "<mediatagname></mediatagname>";
xml += "<messageext></messageext>";
xml += "<messageaction></messageaction>";
xml += "<content></content>";
xml += "<contentattr>0</contentattr>";
xml += "<url></url>";
xml += "<lowurl></lowurl>";
xml += "<dataurl></dataurl>";
xml += "<lowdataurl></lowdataurl>";
xml += "<songalbumurl></songalbumurl>";
xml += "<songlyric></songlyric>";
xml += "<template_id></template_id>";
xml += "<appattach><totallen>0</totallen><attachid></attachid><emoticonmd5></emoticonmd5><fileext></fileext><aeskey></aeskey></appattach>";
xml += "<extinfo></extinfo>";
xml += "<sourceusername></sourceusername>";
xml += "<sourcedisplayname></sourcedisplayname>";
xml += "<thumburl></thumburl>";
xml += "<md5></md5>";
xml += "<statextstr></statextstr>";
// 添加refermsg (引用消息)
if (replyReferMsgContent || replyReferMsgSender) {
xml += "<refermsg>";
if (replyReferMsgSender) {
xml += "<chatusr>" + escapeXml(replyReferMsgSender) + "</chatusr>";
}
xml += "<type>" + replyReferMsgType + "</type>";
xml += "<svrid>" + escapeXml(replyReferMsgId || generateRandomMsgId()) + "</svrid>";
xml += "<fromusr>" + escapeXml(replyReferMsgSender) + "</fromusr>";
xml += "<content>" + escapeXml(replyReferMsgContent) + "</content>";
xml += "</refermsg>";
}
xml += "</appmsg>";
// 添加msgsource(转义后的XML
xml += "<msgsource>" + escapeXml(msgsourceContent) + "</msgsource>";
// 添加displayname
xml += "<displayname></displayname>";
// 添加svrid
xml += "<svrid>" + generateRandomMsgId() + "</svrid>";
// 添加fromusr
xml += "<fromusr>" + escapeXml(senderGlobal) + "</fromusr>";
// 添加content(转义后的msg
xml += "<content>" + escapeXml(msgContent) + "</content>";
// 添加fromusername
xml += "<fromusername>" + escapeXml(senderGlobal) + "</fromusername>";
// 添加appinfo
xml += "<appinfo>";
xml += "<version>0</version>";
xml += "<appname></appname>";
xml += "<isforceupdate>0</isforceupdate>";
xml += "</appinfo>";
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
// 生成随机消息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
};