From e2d898666eaf45897a9dce08c9c719910ca3b2da Mon Sep 17 00:00:00 2001 From: yincong Date: Wed, 31 Dec 2025 10:48:06 +0800 Subject: [PATCH] long text --- frida/frida.md | 5 +- frida/succ.js | 208 +++++++++++++++++++++++++++++++++++------------ onebot/script.js | 42 ++++++---- 3 files changed, 189 insertions(+), 66 deletions(-) diff --git a/frida/frida.md b/frida/frida.md index 64cec18..b80f12a 100644 --- a/frida/frida.md +++ b/frida/frida.md @@ -1,4 +1,7 @@ frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l script.js frida-trace -p 57961 -i '*encrypt*' -i "*Encrypt*" -x '*objc_msgSend_noarg*' -x '*objc_msgSend_debug*' -x '*objc_msgSend*' -x "*_HIDisableSuddenTerminationForSendEvent*" -x "*_HIEnableSuddenTerminationForSendEvent*" -x "*SendEventToEventTarget*" -x "*s10RTCUtility10XPCMessageV4dictAA16RTCXPCDictionaryVvg*" -x "*MTLMessageContextEnd*" -x "*ictAA16RTCXPCDictionaryVvg*" -x "*_MTLMessageContextBegin_*" -x "*CFMachMessageCheckForAndDestroyUnsentMessag*" -x "*SLEventCopyAuthenticationMessage*" -x "*SendTextInputEvent_WithCompletionHandler*" -x '*mach_msg_send*' -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result*" --decorate --ui-port 60000 frida-trace -p 进程号 -i "*Message*" --decorate 比较好用 -frida -p 79464 -l ./script.js \ No newline at end of file +frida -p 79464 -l ./script.js + +# 怎么使用succ.js +manualTrigger(0x20000095, "wxid_", "哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈") \ No newline at end of file diff --git a/frida/succ.js b/frida/succ.js index 9274f07..9e66534 100644 --- a/frida/succ.js +++ b/frida/succ.js @@ -1,9 +1,9 @@ // 1. 获取微信主模块的基地址 var baseAddr = Process.getModuleByName("WeChat").base; if (!baseAddr) { - console.log("[!] 找不到 WeChat 模块基址,请检查进程名。"); + console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); } -console.log("[*] WeChat base address: " + baseAddr); +console.log("[+] WeChat base address: " + baseAddr); // 触发函数地址,不同版本的地址看wechat_version 中的json文件复制过来 var triggerFuncAddr = baseAddr.add(0x444A99C); @@ -18,6 +18,7 @@ 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); @@ -31,15 +32,19 @@ var messageContentAddr = ptr(0); var messageAddrAddr = ptr(0); var contentAddr = ptr(0); var insertMsgAddr = ptr(0); +var protoX1PayloadAddr = ptr(0); +var protoX1PayloadLen = 1024; + // 消息的taskId var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复 var receiverGlobal = "wxid_" var contentGlobal = ""; var lastSendTime = 0; +var messageId = 0; // 打印消息的地址,便于查询问题 function printAddr() { - console.log("[*] Addresses:"); + console.log("[+] Addresses:"); console.log(" - cgiAddr: " + cgiAddr); console.log(" - callBackFuncAddr: " + callBackFuncAddr); console.log(" - sendMessageAddr: " + sendMessageAddr); @@ -60,7 +65,7 @@ function patchHex(addr, hexStr) { // 初始化进行内存的分配 function setupSendMessageDynamic() { - console.log("[*] Starting Dynamic Message Patching..."); + console.log("[+] Starting Dynamic Message Patching..."); // 1. 动态分配内存块(按需分配大小) // 分配原则:字符串给 64-128 字节,结构体按实际大小分配 @@ -85,7 +90,7 @@ function setupSendMessageDynamic() { sendMessageAddr.add(0x20).writeU32(taskIdGlobal); sendMessageAddr.add(0x28).writePointer(messageAddr); // 指向动态分配的 Message - console.log(" [+] sendMessageAddr Object: ", hexdump(sendMessageAddr, { + console.log(" [+] sendMessageAddr Object: ", hexdump(sendMessageAddr, { offset: 0, length: 48, header: true, @@ -123,9 +128,9 @@ function setupSendMessageDynamic() { messageAddrAddr.writePointer(messageCallbackFunc5); messageAddrAddr.add(0x08).writePointer(contentAddr); - console.log(" [+] messageAddr Object: ", hexdump(messageAddr, { + console.log(" [+] messageAddr Object: ", hexdump(messageAddr, { offset: 0, - length: 128, + length: 64, header: true, ansi: true })); @@ -137,12 +142,12 @@ setImmediate(setupSendMessageDynamic); // 设置trigger函数的x0参数 function setTriggerAttach() { - console.log("[*] WeChat Base: " + baseAddr + "[*] Attaching to: " + triggerFuncAddr); + console.log("[+] WeChat Base: " + baseAddr + "[+] Attaching to: " + triggerFuncAddr); // 3. 开始拦截 Interceptor.attach(triggerFuncAddr, { onEnter: function (args) { - console.log("[*] Entered Function: 0x10444A99C"); + console.log("[+] Entered Function: 0x10444A99C"); if (!globalMessagePtr.isNull()) { return; @@ -161,25 +166,26 @@ setImmediate(setTriggerAttach); function manualTrigger(taskId, receiver, content) { - console.log("[*] Manual Trigger Started..."); + console.log("[+] Manual Trigger Started..."); if (globalMessagePtr.isNull()) { - console.log("[!] globalMessagePtr is NULL, cannot trigger!"); - return; + console.error("[!] globalMessagePtr is NULL, cannot trigger!"); + return "fail"; } - if (taskId.empty() || receiver.empty() || content.empty()) { - console.log("[!] taskId or Receiver or Content is empty!"); - return; + if (!taskId || !receiver || !content) { + console.error("[!] taskId or Receiver or Content is empty!"); + return "fail"; } // 获取当前时间戳 (秒) const timestamp = Math.floor(Date.now() / 1000); // 全局变量不为空,并且上次发送时间小于1s,不给发送 - if ((taskIdGlobal !== 0 || receiverGlobal !== "" || contentGlobal !== "") && lastSendTime + 1 < timestamp) { - console.log("[!] taskId or receiver or content is not empty!"); - return; + 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; @@ -254,14 +260,13 @@ function manualTrigger(taskId, receiver, content) { try { const arg1 = globalMessagePtr; // 第一个指针参数 const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600 - - console.log(`[*] Calling trigger function at ${triggerFuncAddr} with args: (${arg1}, ${arg2})`); - + 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); + return "ok"; } catch (e) { - console.log("[!] Error trigger function during execution: " + e); + console.error("[!] Error trigger function during execution: " + e); + return "fail"; } } @@ -269,11 +274,11 @@ function manualTrigger(taskId, receiver, content) { // ReqBuf 进行拦截,替换入参数的消息指针 function attachReq2buf() { - console.log("[*] Target Req2buf enter Address: " + req2bufEnterAddr); + console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr); // 2. 开始拦截 Interceptor.attach(req2bufEnterAddr, { - onEnter: function(args) { + onEnter: function (args) { if (!this.context.x1.equals(taskIdGlobal)) { return; } @@ -284,8 +289,8 @@ function attachReq2buf() { 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, { + console.log("[+] 当前 Req2Buf X24 基址: " + x24_base); + console.log("[+] 准备修改位置 Req2Buf (X24 + 0x60): " + insertMsgAddr, hexdump(insertMsgAddr, { offset: 0, length: 16, header: true, @@ -294,7 +299,7 @@ function attachReq2buf() { if (typeof sendMessageAddr !== 'undefined') { insertMsgAddr.writePointer(sendMessageAddr); - console.log("[!] 成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendMessageAddr + + console.log("[+] 成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendMessageAddr + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr, hexdump(insertMsgAddr, { offset: 0, length: 16, @@ -302,26 +307,27 @@ function attachReq2buf() { ansi: true })); } else { - console.log("[?] 错误: 变量 sendMessageAddr 未定义,请确保已运行分配逻辑。"); + console.error("[!] 错误: 变量 sendMessageAddr 未定义,请确保已运行分配逻辑。"); } } }); // 在出口处拦截req2buf,把insertMsgAddr设置为0,避免被垃圾回收导致整个程序崩溃 - console.log("[*] Target Req2buf leave Address: " + req2bufExitAddr); + console.log("[+] Target Req2buf leave Address: " + req2bufExitAddr); Interceptor.attach(req2bufExitAddr, { - onEnter: function(args) { + onEnter: function (args) { if (!this.context.x25.equals(taskIdGlobal)) { return; } insertMsgAddr.writeU64(0x0); console.log("[+] 0x1033EFA00 清空写入后内存预览: " + insertMsgAddr.readPointer()); - TaskIdGlobal = 0; + taskIdGlobal = 0; receiverGlobal = ""; contentGlobal = ""; } }); } + setImmediate(attachReq2buf); // 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes) @@ -344,9 +350,10 @@ function getVarintTimestampBytes() { } function stringToHexArray(str) { + var utf8Str = unescape(encodeURIComponent(str)); var arr = []; - for (var i = 0; i < str.length; i++) { - arr.push(str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值) + for (var i = 0; i < utf8Str.length; i++) { + arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值) } return arr; } @@ -369,21 +376,22 @@ function generateRandom5ByteVarint() { // 拦截 Protobuf 编码逻辑,注入自定义 Payload function attachProto() { - - console.log("[*] proto注入拦截目标地址: " + protobufAddr); - - const x1_custom_addr = Memory.alloc(256); - console.log("[*] Frida 分配的 Payload 地址: " + x1_custom_addr); + console.log("[+] proto注入拦截目标地址: " + protobufAddr); + protoX1PayloadAddr = Memory.alloc(protoX1PayloadLen); + console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr); Interceptor.attach(protobufAddr, { - onEnter: function(args) { - const prefix = [ - 0x08, 0x01, 0x12, 0x5E, 0x0A, 0x15, 0x0A, 0x13, // 0x00 - ]; + onEnter: function (args) { + if (lastSendTime === 0) { + console.error("[+] 首次发送时间未记录,跳过注入"); + return; + } + const type = [0x08, 0x01, 0x12] + const receiverHeader = [0x0A, 0x15, 0x0A, 0x13]; const receiverProto = stringToHexArray(receiverGlobal); - const contentHeader = [0x12, content.length]; const contentProto = stringToHexArray(contentGlobal); + const contentHeader = [0x12, ...toVarint(contentProto.length)]; const tsHeader = [0x18, 0x01, 0x20]; const tsBytes = getVarintTimestampBytes(); const msgIdHeader = [0x28] @@ -400,19 +408,23 @@ function attachProto() { 0x63, 0x65, 0x3E, 0x00 // 0x60 ce>. ]; + const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + + contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + suffix.length) + // 合并数组 - const finalPayload = prefix.concat(receiverProto).concat(contentHeader). + const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader). concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix); - x1_custom_addr.writeByteArray(finalPayload); - console.log("[*] Payload 已写入,长度: " + finalPayload.length); - this.context.x1 = x1_custom_addr; - this.context.x2 = ptr(0x62); + protoX1PayloadAddr.writeByteArray(finalPayload); + console.log("[+] Payload 已写入,长度: " + finalPayload.length); - console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(x1_custom_addr, { + this.context.x1 = protoX1PayloadAddr; + this.context.x2 = ptr(finalPayload.length); + + console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(protoX1PayloadAddr, { offset: 0, - length: 160, + length: 128, header: true, ansi: true })); @@ -420,4 +432,98 @@ function attachProto() { }); } +function toVarint(n) { + let res = []; + while (n >= 128) { + res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 + n = n >> 7; // 右移7位 + } + res.push(n); // 最后一位最高位为0 + return res; +} + setImmediate(attachProto); + +function setReceiver() { + console.log("[+] WeChat Base: " + baseAddr + "[+] Attaching to: " + receiveAddr); + + // 3. 开始拦截 + Interceptor.attach(receiveAddr, { + onEnter: function (args) { + console.log("[+] Entered Receive Function: 0x1023B5348"); + const x1 = this.context.x1; + var sender = x1.add(0x18).readUtf8String(); + var receiver = x1.add(0x30).readUtf8String(); + + // 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"; + } + } + + messageId++ + send({ + message_type: "private", + user_id: sender, + self_id: receiver, + message_id: messageId, + type: "send", + raw: {peerUid: receiver}, + message: [{type: "text", data: {text: strD0}}] + }) + }, + }); +} + +// 使用 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 diff --git a/onebot/script.js b/onebot/script.js index aecd811..da6ebf5 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -32,6 +32,9 @@ var messageContentAddr = ptr(0); var messageAddrAddr = ptr(0); var contentAddr = ptr(0); var insertMsgAddr = ptr(0); +var protoX1PayloadAddr = ptr(0); +var protoX1PayloadLen = 1024; + // 消息的taskId var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复 var receiverGlobal = "wxid_" @@ -373,11 +376,9 @@ function generateRandom5ByteVarint() { // 拦截 Protobuf 编码逻辑,注入自定义 Payload function attachProto() { - console.log("[+] proto注入拦截目标地址: " + protobufAddr); - - const x1_custom_addr = Memory.alloc(256); - console.log("[+] Frida 分配的 Payload 地址: " + x1_custom_addr); + protoX1PayloadAddr = Memory.alloc(protoX1PayloadLen); + console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr); Interceptor.attach(protobufAddr, { onEnter: function (args) { @@ -386,13 +387,11 @@ function attachProto() { return; } - const prefix = [ - 0x08, 0x01, 0x12, 0x5E, 0x0A, 0x15, 0x0A, 0x13, // 0x00 - ]; - + const type = [0x08, 0x01, 0x12] + const receiverHeader = [0x0A, 0x15, 0x0A, 0x13]; const receiverProto = stringToHexArray(receiverGlobal); const contentProto = stringToHexArray(contentGlobal); - const contentHeader = [0x12, contentProto.length]; + const contentHeader = [0x12, ...toVarint(contentProto.length)]; const tsHeader = [0x18, 0x01, 0x20]; const tsBytes = getVarintTimestampBytes(); const msgIdHeader = [0x28] @@ -409,16 +408,21 @@ function attachProto() { 0x63, 0x65, 0x3E, 0x00 // 0x60 ce>. ]; - // 合并数组 - const finalPayload = prefix.concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix); + const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length + + contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + suffix.length) - x1_custom_addr.writeByteArray(finalPayload); + // 合并数组 + const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader). + concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix); + + + protoX1PayloadAddr.writeByteArray(finalPayload); console.log("[+] Payload 已写入,长度: " + finalPayload.length); - this.context.x1 = x1_custom_addr; + this.context.x1 = protoX1PayloadAddr; this.context.x2 = ptr(finalPayload.length); - console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(x1_custom_addr, { + console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(protoX1PayloadAddr, { offset: 0, length: 128, header: true, @@ -428,6 +432,16 @@ function attachProto() { }); } +function toVarint(n) { + let res = []; + while (n >= 128) { + res.push((n & 0x7F) | 0x80); // 取后7位,最高位置1 + n = n >> 7; // 右移7位 + } + res.push(n); // 最后一位最高位为0 + return res; +} + setImmediate(attachProto); function setReceiver() {