mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
600 lines
23 KiB
JavaScript
600 lines
23 KiB
JavaScript
// -------------------------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(0x260C444);
|
||
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;
|
||
var replyMessageCallbackFunc1 = baseAddr.add(0x8C29A08); // 复用图片的callback函数
|
||
|
||
// Reply消息相关地址 - 需要导出为全局变量供file.js使用
|
||
var sendReplyMessageAddr = ptr(0);
|
||
var replyMessageAddr = ptr(0);
|
||
var replyCgiAddr = ptr(0);
|
||
var replyProtobufBufAddr = ptr(0); // 用于存储protobuf数据的缓冲区
|
||
|
||
|
||
// Reply消息内容相关
|
||
var replyContent = ""; // 回复的文本内容
|
||
var replyReferMsgContent = ""; // 被引用消息的内容
|
||
var replyReferMsgType = 1; // 被引用消息的类型
|
||
var replyReferMsgSender = ""; // 被引用消息的发送者
|
||
var replyReferMsgId = ""; // 被引用消息的ID
|
||
var replyReferMsgCreateTime = ""; // 被引用消息的创建时间
|
||
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, referMsgCreateTime) {
|
||
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();
|
||
replyReferMsgCreateTime = referMsgCreateTime || Math.floor(Date.now() / 1000).toString();
|
||
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消息类型
|
||
|
||
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 += "<createtime>" + replyReferMsgCreateTime + "</createtime>";
|
||
xml += "<msgsource>" + escapeXml("<msgsource><alnode><fr>1</fr></alnode></msgsource>") + "</msgsource>";
|
||
xml += "<displayname></displayname>";
|
||
xml += "<svrid>" + escapeXml(replyReferMsgId || generateRandomMsgId()) + "</svrid>";
|
||
xml += "<fromusr>" + escapeXml(replyReferMsgSender) + "</fromusr>";
|
||
xml += "<content>" + escapeXml(replyReferMsgContent) + "</content>";
|
||
xml += "</refermsg>";
|
||
}
|
||
|
||
xml += "</appmsg>";
|
||
|
||
// 添加fromusername
|
||
xml += "<fromusername>" + escapeXml(senderGlobal) + "</fromusername>";
|
||
|
||
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, """)
|
||
.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
|
||
};
|