From ef3ccfb3d272ea68d0b077619ad8f0e52c88dc75 Mon Sep 17 00:00:00 2001 From: yincong Date: Sat, 9 May 2026 14:07:15 +0800 Subject: [PATCH 1/4] update 4.1.9.52 --- frida-wechat.md | 6 +++--- frida/auto.js | 15 ++++----------- frida/receiver.js | 20 ++++++++++---------- frida/text.js | 17 ++++++++++------- onebot/script.js | 16 ++++++++-------- wechat_version/4_1_9_31_mac.json | 22 ---------------------- wechat_version/4_1_9_52_mac.json | 22 ++++++++++++++++++++++ 7 files changed, 57 insertions(+), 61 deletions(-) delete mode 100644 wechat_version/4_1_9_31_mac.json create mode 100644 wechat_version/4_1_9_52_mac.json diff --git a/frida-wechat.md b/frida-wechat.md index 811e321..94c6bab 100644 --- a/frida-wechat.md +++ b/frida-wechat.md @@ -2,9 +2,9 @@ 图片hook: startUploadMedia (ida搜), OnUploadCompleted (ce查) startUploadMedia 上传图片,OnUploadCompleted 上传图片完成后的回调 加密后的图片在 _OnRecvFileData -download系列都是_OnRecvFileData,在不同的文件下面 -var downloadImagAddr // image_download -var downloadFileAddr // c2c_download + +var downloadFileAddr // c2c_download 文件和缩略图 +var downloadImagAddr // image_download 高清图 var downloadVideoAddr // hdvideo_streaming OnRecvedData WeChatExt中会有导致idapro出问题的代码 diff --git a/frida/auto.js b/frida/auto.js index afdd21f..eff0fc6 100644 --- a/frida/auto.js +++ b/frida/auto.js @@ -23,10 +23,7 @@ function scanPatterns() { console.log(`"${name}": "${offset}", 绝对地址: ${address}`); break; case "downloadFileAddr": - console.log(`"${name}": "${offset.sub(0x3c)}", 绝对地址: ${address.sub(0x3c)}`); - break; - case "downloadImageAddr": - console.log(`"${name}": "${offset.sub(0x18)}", 绝对地址: ${address.sub(0x18)}`); + console.log(`"${name}": "${offset}", 绝对地址: ${address}`); break; case "uploadImageAddr": console.log(`"${name}": "${offset.sub(0x30)}", 绝对地址: ${address.sub(0x30)}`); @@ -41,7 +38,7 @@ function scanPatterns() { console.log(`"${name}": "${offset.sub(0x28)}", 绝对地址: ${address.sub(0x28)}`); break; case "downloadVideoAddr": - console.log(`"${name}": "${offset.sub(0x2c)}", 绝对地址: ${address.sub(0x2c)}`); + console.log(`"${name}": "${offset.sub(0xc)}", 绝对地址: ${address.sub(0xc)}`); break; } }, @@ -69,11 +66,7 @@ const myPatterns = [ }, { name: "downloadFileAddr", - pattern: "F5 03 00 AA 68 4A 41 F9 08 41 46 39 60 22 01 91 69 52 41 F9" - }, - { - name: "downloadImageAddr", - pattern: "68 52 41 F9 19 8D 41 F9 1A 00 14 8B 5F 03 19 EB" + pattern: "08 01 40 F9 A8 83 1B F8 08 50 41 F9 08 29 41 F9" }, { name: "uploadImageAddr", @@ -93,7 +86,7 @@ const myPatterns = [ }, { name: "downloadVideoAddr", - pattern: "E0 03 07 91 01 00 80 52 45 C1 80 52 06 00 80 52 07 00 80 D2" + pattern: "F5 03 00 AA 76 43 09 91 E0 03 16 AA" } ]; diff --git a/frida/receiver.js b/frida/receiver.js index 41ae1db..4d8bab7 100644 --- a/frida/receiver.js +++ b/frida/receiver.js @@ -9,10 +9,10 @@ if (!baseAddr) { console.log("[+] WeChat base address: " + baseAddr); var buf2RespAddr = baseAddr.add(0x39FAC00) -var downloadImagAddr = baseAddr.add(0x4CFFF3C) // image_download -var downloadFileAddr = baseAddr.add(0x4C9EF48) // c2c_download -var downloadVideoAddr = baseAddr.add(0x4CB8070) // hdvideo_streaming -var startDownloadMedia = baseAddr.add(0x4bdd618) +var downloadImagAddr = baseAddr.add(0x4E6F32C) // image_download +var downloadFileAddr = baseAddr.add(0x4E0E264) // c2c_download +var downloadVideoAddr = baseAddr.add(0x4E28044) // hdvideo_streaming +var startDownloadMedia = baseAddr.add(0x4d4c838) var downloadGlobalX0; var downloadFileX1 = ptr(0) @@ -153,8 +153,8 @@ function setReceiver() { Interceptor.attach(downloadFileAddr, { onEnter: function (args) { var dataPtr = this.context.x22; - var dataLen = this.context.x0.toInt32(); - var fileId = this.context.sp.add(0x30).readPointer().readUtf8String(); + var dataLen = this.context.x2.toInt32(); + var fileId = this.context.x19.add(0x2E0).readPointer().readUtf8String(); var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String(); if (dataLen > 0) { @@ -184,10 +184,10 @@ function setReceiver() { Interceptor.attach(downloadVideoAddr, { onEnter: function (args) { - var dataPtr = this.context.x1; - var dataLen = this.context.x24.toInt32(); - var fileId = this.context.x22.add(0x40).readPointer().readUtf8String(); - var cdnUrl = this.context.x22.add(0x58).readPointer().readUtf8String(); + var dataPtr = this.context.x20.add(0x178).readPointer(); + var dataLen = this.context.x23.toInt32(); + var fileId = this.context.x19.add(0x2E0).readPointer().readUtf8String(); + var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String(); if (dataLen > 0) { var buffer = dataPtr.readByteArray(dataLen); diff --git a/frida/text.js b/frida/text.js index c7bafba..f6ffa18 100644 --- a/frida/text.js +++ b/frida/text.js @@ -1,5 +1,8 @@ -var moduleName = "wechat.dylib"; -var baseAddr = Process.findModuleByName(moduleName).base; +var targetPath = "/Applications/WeChat.app/Contents/Resources/wechat.dylib"; +var module = Process.enumerateModules().find(function(m) { + return m.path === targetPath; +}); +const baseAddr = module.base; if (!baseAddr) { console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); } @@ -78,7 +81,7 @@ function patchString(addr, plainStr) { // -------------------------全局变量分区------------------------- // 文本消息全局变量 -var textCallbackFuncAddr = baseAddr.add(0x2587018); +var textCallbackFuncAddr = baseAddr.add(0x26779D4); var protobufAddr = textCallbackFuncAddr.add(0x40); var patchTextProtobufAddr = textCallbackFuncAddr.add(0x20); var patchTextProtobufByte @@ -90,15 +93,15 @@ var sendTextMessageAddr = ptr(0); var textMessageAddr = ptr(0); var textProtoX1PayloadAddr = ptr(0); -var sendMessageCallbackFunc = baseAddr.add(0x8A047B0); +var sendMessageCallbackFunc = baseAddr.add(0x8C2C880); // 双方公共使用的地址 var triggerX1Payload; var triggerX0; -var req2bufEnterAddr = baseAddr.add(0x388b400); -var req2bufExitAddr = baseAddr.add(0x388C514); -var sendFuncAddr = baseAddr.add(0x4a5e108); +var req2bufEnterAddr = baseAddr.add(0x39d51d8); +var req2bufExitAddr = baseAddr.add(0x39D62EC); +var sendFuncAddr = baseAddr.add(0x4bd0e1c); var insertMsgAddr = ptr(0); var sendMsgType = ""; diff --git a/onebot/script.js b/onebot/script.js index 43e5e80..b53d4a7 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -930,10 +930,10 @@ function setReceiver() { Interceptor.attach(downloadFileAddr, { onEnter: function (args) { - var dataPtr = this.context.x22; - var dataLen = this.context.x20.toInt32(); - var fileId = this.context.sp.add(0x30).readPointer().readUtf8String(); - var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String(); + var dataPtr = this.context.x22; + var dataLen = this.context.x2.toInt32(); + var fileId = this.context.x19.add(0x2E0).readPointer().readUtf8String(); + var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String(); if (dataLen > 0) { var buffer = dataPtr.readByteArray(dataLen); @@ -972,10 +972,10 @@ function setReceiver() { Interceptor.attach(downloadVideoAddr, { onEnter: function (args) { - var dataPtr = this.context.x1; - var dataLen = this.context.x24.toInt32(); - var fileId = this.context.x22.add(0x40).readPointer().readUtf8String(); - var cdnUrl = this.context.x22.add(0x58).readPointer().readUtf8String(); + var dataPtr = this.context.x20.add(0x178).readPointer(); + var dataLen = this.context.x23.toInt32(); + var fileId = this.context.x19.add(0x2E0).readPointer().readUtf8String(); + var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String(); if (dataLen > 0) { var buffer = dataPtr.readByteArray(dataLen); diff --git a/wechat_version/4_1_9_31_mac.json b/wechat_version/4_1_9_31_mac.json deleted file mode 100644 index 6df0f90..0000000 --- a/wechat_version/4_1_9_31_mac.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "textCallbackFuncAddr": "0x26779D4", - "sendMessageCallbackFunc": "0x8C2C880", - "sendFuncAddr": "0x4A61D3C", - "req2bufEnterAddr": "0x38cba28", - "req2bufExitAddr": "0x38CC97C", - "imageCallbackFuncAddr": "0x266E3A0", - "imgMessageCallbackFunc": "0x8C2C348", - "uploadImageAddr": "0x4bdc9b0", - "cndOnCompleteAddr": "0x388883c", - "buf2RespAddr": "0x38f1520", - "uploadGetCallbackWrapperAddr": "0x4b9b754", - "uploadGetCallbackWrapperFuncAddr": "", - "uploadOnCompleteAddr": "", - "uploadOnCompleteFuncAddr": "", - "downloadImagAddr": "0x4CFFF3C", - "startDownloadMedia": "0x4bdd618", - "downloadFileAddr": "0x4C9EF50", - "downloadVideoAddr": "0x4CB8070", - "videoCallbackFuncAddr": "0x26DD8C8", - "videoMessageCallbackFunc": "0x8C2F0F8" -} diff --git a/wechat_version/4_1_9_52_mac.json b/wechat_version/4_1_9_52_mac.json new file mode 100644 index 0000000..bec5655 --- /dev/null +++ b/wechat_version/4_1_9_52_mac.json @@ -0,0 +1,22 @@ +{ + "textCallbackFuncAddr": "0x26779D4", + "sendMessageCallbackFunc": "0x8C2C880", + "sendFuncAddr": "0x4bd0e1c", + "req2bufEnterAddr": "0x39d51d8", + "req2bufExitAddr": "0x39D62EC", + "imageCallbackFuncAddr": "0x266E3A0", + "imgMessageCallbackFunc": "0x8C2C348", + "uploadImageAddr": "0x4bdc9b0", + "cndOnCompleteAddr": "0x388883c", + "buf2RespAddr": "0x38f1520", + "uploadGetCallbackWrapperAddr": "0x4d0a928", + "uploadGetCallbackWrapperFuncAddr": "0x8CC0A90", + "uploadOnCompleteAddr": "0x4D0B0E0", + "uploadOnCompleteFuncAddr": "0x8CC0AB0", + "startDownloadMedia": "0x4d4c838", + "downloadImagAddr": "0x4E6F32C", + "downloadFileAddr": "0x4E0E264", + "downloadVideoAddr": "0x4E28044", + "videoCallbackFuncAddr": "0x26DD8C8", + "videoMessageCallbackFunc": "0x8C2F0F8" +} From 62a77d34c11dc4c1995bc7ac89b49988fab482b5 Mon Sep 17 00:00:00 2001 From: yincong Date: Sat, 9 May 2026 17:33:20 +0800 Subject: [PATCH 2/4] finish 4.1.9.52 --- frida/file.js | 413 ++++++++++++++++--------------- onebot/main.go | 2 +- onebot/script.js | 20 +- onebot/upload_builder.go | 97 ++++---- wechat_version/4_1_9_52_mac.json | 10 +- 5 files changed, 288 insertions(+), 254 deletions(-) diff --git a/frida/file.js b/frida/file.js index 163ff16..7c42af6 100644 --- a/frida/file.js +++ b/frida/file.js @@ -1,6 +1,9 @@ // 1. 获取微信主模块的基地址 -var moduleName = "wechat.dylib"; -var baseAddr = Process.findModuleByName(moduleName).base; +var targetPath = "/Applications/WeChat.app/Contents/Resources/wechat.dylib"; +var module = Process.enumerateModules().find(function(m) { + return m.path === targetPath; +}); +const baseAddr = module.base; if (!baseAddr) { console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); } @@ -103,14 +106,14 @@ function generateBytes(n) { // 双方公共使用的地址 var triggerX1Payload; var triggerX0; -var req2bufEnterAddr = baseAddr.add(0x388b400); -var req2bufExitAddr = baseAddr.add(0x388C514); -var sendFuncAddr = baseAddr.add(0x4a5e108); +var req2bufEnterAddr = baseAddr.add(0x39d51d8); +var req2bufExitAddr = baseAddr.add(0x39D62EC); +var sendFuncAddr = baseAddr.add(0x4bd0e1c); var insertMsgAddr = ptr(0); var sendMsgType = ""; // 图片消息全局变量 -var imageCallbackFuncAddr = baseAddr.add(0x25A3944); +var imageCallbackFuncAddr = baseAddr.add(0x266E3A0); var imgProtobufAddr = imageCallbackFuncAddr.add(0x50); var patchImgProtobufFunc1 = imageCallbackFuncAddr.add(0x10); var patchImgProtobufFunc1Byte; @@ -120,7 +123,7 @@ var imgProtobufDeleteAddr = imageCallbackFuncAddr.add(0x6c); var imgProtobufDeleteAddrByte; // 视频消息全局变量 -var videoCallbackFuncAddr = baseAddr.add(0x25EC2D4); +var videoCallbackFuncAddr = baseAddr.add(0x26DD8C8); var videoProtobufAddr = videoCallbackFuncAddr.add(0x50); var patchVideoProtobufFunc1 = videoCallbackFuncAddr.add(0x10); var patchVideoProtobufFunc1Byte; @@ -129,22 +132,22 @@ var patchVideoProtobufFunc2Byte; var videoProtobufDeleteAddr = videoCallbackFuncAddr.add(0x6c); var videoProtobufDeleteAddrByte; -var uploadImageAddr = baseAddr.add(0x4ba56cc); -var cndOnCompleteAddr = baseAddr.add(0x38487b8); -var imgMessageCallbackFunc1 = baseAddr.add(0x8A05298); -var videoMessageCallbackFunc1 = baseAddr.add(0x8A07018); +var uploadImageAddr = baseAddr.add(0x4d4bbd0); +var cndOnCompleteAddr = baseAddr.add(0x399180c); +var imgMessageCallbackFunc1 = baseAddr.add(0x8C2C348); +var videoMessageCallbackFunc1 = baseAddr.add(0x8C2F0F8); -var uploadGetCallbackWrapperAddr = baseAddr.add(0x4b73190); -var uploadGetCallbackWrapperFuncAddr = baseAddr.add(0x3848004); -var uploadOnCompleteAddr = baseAddr.add(0x4B7378C); -var uploadOnCompleteFuncAddr = baseAddr.add(0x38491E8); +var uploadGetCallbackWrapperAddr = baseAddr.add(0x4d0a928); +var uploadGetCallbackWrapperFuncAddr = baseAddr.add(0x3991058); +var uploadOnCompleteAddr = baseAddr.add(0x4D0B0E0); +var uploadOnCompleteFuncAddr = baseAddr.add(0x399223C); var uploadImageX1 = ptr(0); var imgCgiAddr = ptr(0); var sendImgMessageAddr = ptr(0); var imgMessageAddr = ptr(0); var imgProtoX1PayloadAddr = ptr(0); -var uploadGlobalX0 = ptr(0) +var uploadGlobalX0; var uploadFunc1Addr = ptr(0) var uploadFunc2Addr = ptr(0) var imageIdAddr = ptr(0) @@ -913,100 +916,101 @@ setImmediate(attachVideoProto()); 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 - 0x26, 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, // 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 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x290 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x298 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2c0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2c8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2e0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x2e8 + 0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x00 函数指针1 (writePointer覆盖) + 0x00, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x08 函数指针2 (writePointer覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 + 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x48 videoId (writePointer覆盖) + 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 发送人wxid (writeUtf8String覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, // 0x78 [0x7F]=长度0x13 + 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 + 0x01, 0xAA, 0xAA, 0xAA, 0x01, 0x00, 0x00, 0x00, // 0x98 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 md5 (writePointer覆盖) + 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, // 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, 0x55, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0xE8 视频路径 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 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 + 0x40, 0x54, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x118 视频路径2 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x120 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 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 + 0x40, 0x5D, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x148 视频路径3 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150 + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x158 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x168 + 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, // 0x170 [0x174]=0x0B + 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0xAA, 0xAA, 0xAA, 0x01, 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, 0x00, 0x00, 0x00, 0x00, // 0x1B0 + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x1B8 [0x1BC]=0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1C8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1D0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1D8 + 0x00, 0x01, 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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1F8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x200 aes key (writePointer覆盖) + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x208 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x210 + 0x80, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x258 + 0x00, 0x01, 0x00, 0x00, 0x01, 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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x290 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x298 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C8 + 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x2F0 ] patchString(imageIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); @@ -1020,10 +1024,10 @@ function triggerUploadImg(receiver, md5, imagePath) { uploadImageX1.add(0x48).writePointer(imageIdAddr); uploadImageX1.add(0x68).writeUtf8String(receiver); uploadImageX1.add(0xa8).writePointer(md5Addr); - uploadImageX1.add(0xe0).writePointer(ImagePathAddr1); - uploadImageX1.add(0x110).writePointer(ImagePathAddr1); - uploadImageX1.add(0x140).writePointer(ImagePathAddr1); - uploadImageX1.add(0x1f8).writePointer(uploadAesKeyAddr); + uploadImageX1.add(0xe8).writePointer(ImagePathAddr1); + uploadImageX1.add(0x118).writePointer(ImagePathAddr1); + uploadImageX1.add(0x148).writePointer(ImagePathAddr1); + uploadImageX1.add(0x200).writePointer(uploadAesKeyAddr); const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); @@ -1039,100 +1043,101 @@ function triggerUploadImg(receiver, md5, imagePath) { function triggerUploadVideo(receiver, md5, videoPath) { 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, 0x0B, 0x00, 0x00, 0x00, // 0x40 - 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 图片id // 0x48 + 0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x00 函数指针1 (writePointer覆盖) + 0x00, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x08 函数指针2 (writePointer覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 + 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x48 videoId (writePointer覆盖) 0x26, 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, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 发送人wxid (writeUtf8String覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, // 0x78 [0x7F]=长度0x13 + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90 0x01, 0xAA, 0xAA, 0xAA, 0x04, 0x00, 0x00, 0x00, // 0x98 - 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xa0 - 0xA0, 0xBE, 0x2D, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 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, // 0xA0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 md5 (writePointer覆盖) + 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, // 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 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 + 0x00, 0x55, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0xE8 视频路径 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 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, // 0x110 + 0x40, 0x54, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x118 视频路径2 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x120 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 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 + 0x40, 0x5D, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x148 视频路径3 (writePointer覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150 + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x158 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160 - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xE0, 0x03, // 0x168 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x170 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x168 + 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, // 0x170 [0x174]=0x0B 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, // 0x188 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190 + 0x00, 0xAA, 0xAA, 0xAA, 0x01, 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, 0x00, 0x00, 0x00, 0x00, // 0x1B0 + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x1B8 [0x1BC]=0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1C8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1D0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1D8 + 0x00, 0x01, 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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1F8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x200 aes key (writePointer覆盖) + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x208 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x210 + 0x80, 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, // 0x250 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x258 + 0x00, 0x01, 0x00, 0x00, 0x01, 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, // 0x278 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x280 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x288 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x290 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x298 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2c0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2c8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d8 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2e0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x2e8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C8 + 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x2F0 ] patchString(videoIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); @@ -1146,10 +1151,17 @@ function triggerUploadVideo(receiver, md5, videoPath) { uploadVideoX1.add(0x48).writePointer(videoIdAddr); uploadVideoX1.add(0x68).writeUtf8String(receiver); uploadVideoX1.add(0xa8).writePointer(md5Addr); - uploadVideoX1.add(0xe0).writePointer(videoPathAddr1); - uploadVideoX1.add(0x110).writePointer(videoPathAddr1); - uploadVideoX1.add(0x140).writePointer(videoPathAddr1); - uploadVideoX1.add(0x1f8).writePointer(uploadAesKeyAddr); + uploadVideoX1.add(0xe8).writePointer(videoPathAddr1); + uploadVideoX1.add(0x118).writePointer(videoPathAddr1); + uploadVideoX1.add(0x148).writePointer(videoPathAddr1); + uploadVideoX1.add(0x200).writePointer(uploadAesKeyAddr); + + console.log(hexdump(uploadVideoX1, { + offset: 0, + length: 0x2e8, + header: true, + ansi: true + })) const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); @@ -1160,9 +1172,20 @@ function triggerUploadVideo(receiver, md5, videoPath) { function attachUploadMedia() { Interceptor.attach(uploadImageAddr.add(0x10), { onEnter: function (args) { + if (uploadGlobalX0) { + return + } + + console.log(hexdump(this.context.x1, { + offset: 0, + length: 0x2e8, + header: true, + ansi: true + })) + uploadGlobalX0 = this.context.x0; const selfId = this.context.x1.add(0x68).readUtf8String(); - const filePath = this.context.x1.add(0xe0).readPointer().readUtf8String(); + const filePath = this.context.x1.add(0xe8).readPointer().readUtf8String(); send({ type: "upload", self_id: selfId, diff --git a/onebot/main.go b/onebot/main.go index e652988..5187d31 100644 --- a/onebot/main.go +++ b/onebot/main.go @@ -60,7 +60,7 @@ func initFlag() { flag.StringVar(&config.FridaGadgetAddr, "gadget_addr", "127.0.0.1:27042", "Gadget 地址: 127.0.0.1:27042 仅当 type 为 gadget 时有效") flag.StringVar(&config.OnebotToken, "token", "MuseBot", "OneBot Token: MuseBot") flag.StringVar(&config.ImagePath, "image_path", "", "图片路径: /Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/xxx/temp/xxx/2026-01/Img/") - flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_8_107_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") + flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_9_52_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") flag.StringVar(&config.ConnType, "conn_type", "http", "连接类型: http | websocket") flag.IntVar(&config.SendInterval, "send_interval", 1000, "发送间隔: ms") flag.IntVar(&config.WechatPid, "wechat_pid", 0, "微信进程 PID,不设置则自动查找") diff --git a/onebot/script.js b/onebot/script.js index b53d4a7..2e641ea 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -42,7 +42,7 @@ function generateAESKey() { // 文本消息全局变量 var textCallbackFuncAddr = baseAddr.add({{.textCallbackFuncAddr}}); -var protobufAddr = textCallbackFuncAddr.add(0x44); +var protobufAddr = textCallbackFuncAddr.add(0x40); var patchTextProtobufAddr = textCallbackFuncAddr.add(0x20); var patchTextProtobufByte var patchTextProtobufDeleteAddr = textCallbackFuncAddr.add(0x5C); @@ -688,10 +688,10 @@ function triggerUploadImg(receiver, md5, imagePath, payloadHex) { uploadImageX1.add(0x48).writePointer(imageIdAddr); uploadImageX1.add(0x68).writeUtf8String(receiver); uploadImageX1.add(0xa8).writePointer(md5Addr); - uploadImageX1.add(0xe0).writePointer(ImagePathAddr1); - uploadImageX1.add(0x110).writePointer(ImagePathAddr1); - uploadImageX1.add(0x140).writePointer(ImagePathAddr1); - uploadImageX1.add(0x1f8).writePointer(uploadAesKeyAddr); + uploadImageX1.add(0xe8).writePointer(ImagePathAddr1); + uploadImageX1.add(0x118).writePointer(ImagePathAddr1); + uploadImageX1.add(0x148).writePointer(ImagePathAddr1); + uploadImageX1.add(0x200).writePointer(uploadAesKeyAddr); const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); @@ -718,10 +718,10 @@ function triggerUploadVideo(receiver, md5, videoPath, payloadHex) { uploadVideoX1.add(0x48).writePointer(videoIdAddr); uploadVideoX1.add(0x68).writeUtf8String(receiver); uploadVideoX1.add(0xa8).writePointer(md5Addr); - uploadVideoX1.add(0xe0).writePointer(videoPathAddr1); - uploadVideoX1.add(0x110).writePointer(videoPathAddr1); - uploadVideoX1.add(0x140).writePointer(videoPathAddr1); - uploadVideoX1.add(0x1f8).writePointer(uploadAesKeyAddr); + uploadVideoX1.add(0xe8).writePointer(videoPathAddr1); + uploadVideoX1.add(0x118).writePointer(videoPathAddr1); + uploadVideoX1.add(0x148).writePointer(videoPathAddr1); + uploadVideoX1.add(0x200).writePointer(uploadAesKeyAddr); const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); @@ -734,7 +734,7 @@ function attachUploadMedia() { try { uploadGlobalX0 = this.context.x0; const selfId = this.context.x1.add(0x68).readUtf8String(); - const filePath = this.context.x1.add(0xe0).readPointer().readUtf8String(); + const filePath = this.context.x1.add(0xe8).readPointer().readUtf8String(); send({ type: "upload", self_id: selfId, diff --git a/onebot/upload_builder.go b/onebot/upload_builder.go index fa59ab8..53c447f 100644 --- a/onebot/upload_builder.go +++ b/onebot/upload_builder.go @@ -4,11 +4,10 @@ import "encoding/hex" // BuildUploadPayload 构建上传图片/视频的payload模板,返回hex编码字符串 // uploadType: "img", "video" -// JS侧会在固定偏移写入运行时指针: 0x00, 0x08, 0x48, 0x68(receiver), 0xa8, 0xe0, 0x110, 0x140, 0x1f8 +// JS侧会在固定偏移写入运行时指针: 0x00, 0x08, 0x48, 0x68(receiver), 0xa8, 0xe8, 0x118, 0x148, 0x200 func BuildUploadPayload(uploadType string) string { - // 公共部分 (656字节 = 0x290),以img为基准 - // 差异字节: [0x44] img=0x01/video=0x0B, [0x9C] img=0x01/video=0x04 - // [0x16C-0x16F] img=01000000/video=0400E003 + // 公共部分 (760字节 = 0x2F8),以img为基准 + // 差异字节: [0x9C] img=0x01/video=0x04, [0x1BC-0x1BF] img=AAAAAAAA/video=01000000 payload := []byte{ 0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x00 (被uploadFunc1Addr覆盖) 0x00, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x08 (被uploadFunc2Addr覆盖) @@ -18,66 +17,66 @@ func BuildUploadPayload(uploadType string) string { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 ([0x44]由uploadType决定) + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x48 (被imageIdAddr/videoIdAddr覆盖) 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x58 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 - 0x77, 0x78, 0x69, 0x64, 0x5F, 0x37, 0x77, 0x64, // 0x68 (被receiver覆盖) - 0x31, 0x65, 0x63, 0x65, 0x39, 0x39, 0x66, 0x37, // 0x70 - 0x69, 0x32, 0x31, 0x00, 0x00, 0x00, 0x00, 0x13, // 0x78 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 (被receiver覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, // 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 0x01, 0xAA, 0xAA, 0xAA, 0x01, 0x00, 0x00, 0x00, // 0x98 ([0x9C]由uploadType决定) - 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xA0 - 0xA0, 0xBE, 0x2D, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0xA8 (被md5Addr覆盖) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 (被md5Addr覆盖) 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, // 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, 0x55, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0xE0 (被ImagePath覆盖) - 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 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 文件大小(-1=未知) + 0x00, 0x55, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0xE8 (被ImagePath/videoPath覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 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 (被ImagePath覆盖) - 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, // 0x128 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x110 + 0x40, 0x54, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x118 (被ImagePath/videoPath覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x120 + 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x128 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x130 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x138 - 0x40, 0x5D, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x140 (被ImagePath覆盖) - 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, // 0x140 + 0x40, 0x5D, 0xDB, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x148 (被ImagePath/videoPath覆盖) + 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150 + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x158 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160 - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x168 ([0x16C-0x16F]由uploadType决定) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x170 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x168 + 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, // 0x170 [0x174]=0x0B 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, // 0x190 + 0x00, 0xAA, 0xAA, 0xAA, 0x01, 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, // 0x1B0 + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x1B8 ([0x1BC-0x1BF]由uploadType决定) 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, // 0x1D0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1D8 + 0x00, 0x01, 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 (被uploadAesKeyAddr覆盖) - 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, // 0x1F8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x200 (被uploadAesKeyAddr覆盖) + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x208 + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x210 + 0x80, 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 @@ -85,23 +84,35 @@ func BuildUploadPayload(uploadType string) string { 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, // 0x258 + 0x00, 0x01, 0x00, 0x00, 0x01, 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 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x290 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x298 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2A8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2B8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2C8 + 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2D8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2E8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2F0 } switch uploadType { case "video": - payload[0x44] = 0x0B payload[0x9C] = 0x04 - payload[0x16C] = 0x04 - payload[0x16D] = 0x00 - payload[0x16E] = 0xE0 - payload[0x16F] = 0x03 + payload[0x1BC] = 0x01 + payload[0x1BD] = 0x00 + payload[0x1BE] = 0x00 + payload[0x1BF] = 0x00 } return hex.EncodeToString(payload) diff --git a/wechat_version/4_1_9_52_mac.json b/wechat_version/4_1_9_52_mac.json index bec5655..4ec1b93 100644 --- a/wechat_version/4_1_9_52_mac.json +++ b/wechat_version/4_1_9_52_mac.json @@ -6,13 +6,13 @@ "req2bufExitAddr": "0x39D62EC", "imageCallbackFuncAddr": "0x266E3A0", "imgMessageCallbackFunc": "0x8C2C348", - "uploadImageAddr": "0x4bdc9b0", - "cndOnCompleteAddr": "0x388883c", - "buf2RespAddr": "0x38f1520", + "uploadImageAddr": "0x4d4bbd0", + "cndOnCompleteAddr": "0x399180c", + "buf2RespAddr": "0x39FAC00", "uploadGetCallbackWrapperAddr": "0x4d0a928", - "uploadGetCallbackWrapperFuncAddr": "0x8CC0A90", + "uploadGetCallbackWrapperFuncAddr": "0x3991058", "uploadOnCompleteAddr": "0x4D0B0E0", - "uploadOnCompleteFuncAddr": "0x8CC0AB0", + "uploadOnCompleteFuncAddr": "0x399223C", "startDownloadMedia": "0x4d4c838", "downloadImagAddr": "0x4E6F32C", "downloadFileAddr": "0x4E0E264", From 62767e335809495ab53073e7bd7fab1bfe9b96a4 Mon Sep 17 00:00:00 2001 From: yincong Date: Mon, 11 May 2026 12:12:08 +0800 Subject: [PATCH 3/4] finish 4.1.9.52 --- frida/auto.js | 19 +- frida/reply.js | 5 +- onebot/http.go | 59 ++++ onebot/param.go | 21 +- onebot/proto/wxproto/reply_msg.pb.go | 404 +++++++++++++++++++++++++++ onebot/proto/wxproto/reply_msg.proto | 42 +++ onebot/reply_builder.go | 177 ++++++++++++ onebot/script.js | 164 +++++++++++ onebot/text_builder.go | 5 + onebot/worker.go | 33 ++- wechat_version/4_1_9_52_mac.json | 4 +- 11 files changed, 916 insertions(+), 17 deletions(-) create mode 100644 onebot/proto/wxproto/reply_msg.pb.go create mode 100644 onebot/proto/wxproto/reply_msg.proto create mode 100644 onebot/reply_builder.go diff --git a/frida/auto.js b/frida/auto.js index eff0fc6..73e4cf9 100644 --- a/frida/auto.js +++ b/frida/auto.js @@ -23,7 +23,7 @@ function scanPatterns() { console.log(`"${name}": "${offset}", 绝对地址: ${address}`); break; case "downloadFileAddr": - console.log(`"${name}": "${offset}", 绝对地址: ${address}`); + console.log(`"${name}": "${offset.sub(0x08)}", 绝对地址: ${address.sub(0x08)}`); break; case "uploadImageAddr": console.log(`"${name}": "${offset.sub(0x30)}", 绝对地址: ${address.sub(0x30)}`); @@ -32,13 +32,16 @@ function scanPatterns() { console.log(`"${name}": "${offset.sub(0x44)}", 绝对地址: ${address.sub(0x44)}`); break; case "buf2RespAddr": - console.log(`"${name}": "${offset.sub(0x10)}", 绝对地址: ${address.sub(0x10)}`); + console.log(`"${name}": "${offset.sub(0x14)}", 绝对地址: ${address.sub(0x14)}`); break; case "startDownloadMedia": console.log(`"${name}": "${offset.sub(0x28)}", 绝对地址: ${address.sub(0x28)}`); break; case "downloadVideoAddr": - console.log(`"${name}": "${offset.sub(0xc)}", 绝对地址: ${address.sub(0xc)}`); + console.log(`"${name}": "${offset.sub(0x2c)}", 绝对地址: ${address.sub(0x2c)}`); + break; + case "downloadImageAddr": + console.log(`"${name}": "${offset.sub(0x18)}", 绝对地址: ${address.sub(0x18)}`); break; } }, @@ -64,9 +67,13 @@ const myPatterns = [ name: "req2bufEnterAddr", pattern: "09 0F 46 F8 C9 01 00 B4 E8 03 18 AA 2A 21 40 B9" }, + { + name: "downloadImageAddr", + pattern: "68 52 41 F9 19 75 41 F9 1A 00 14 8B 5F 03 19 EB" + }, { name: "downloadFileAddr", - pattern: "08 01 40 F9 A8 83 1B F8 08 50 41 F9 08 29 41 F9" + pattern: "FF 07 00 B9 68 52 41 F9 15 71 41 F9 E0 03 14 AA" }, { name: "uploadImageAddr", @@ -78,7 +85,7 @@ const myPatterns = [ }, { name: "buf2RespAddr", - pattern: "3C 00 80 52 E0 C3 00 91" + pattern: "E8 DF C1 39 28 E6 FF 36 E0 33 40 F9 E8 3B 40 F9" }, { name: "startDownloadMedia", @@ -86,7 +93,7 @@ const myPatterns = [ }, { name: "downloadVideoAddr", - pattern: "F5 03 00 AA 76 43 09 91 E0 03 16 AA" + pattern: "E0 03 01 91 01 00 80 52 A5 5B 80 52 06 00 80 52" } ]; diff --git a/frida/reply.js b/frida/reply.js index 418cf49..e3d1db3 100644 --- a/frida/reply.js +++ b/frida/reply.js @@ -18,7 +18,7 @@ var triggerX1Payload; var insertMsgAddr; // 回复消息回调函数地址 (基于用户提供: 0x24BDE40) -var replyCallbackFuncAddr = baseAddr.add(0x24BDE40); +var replyCallbackFuncAddr = baseAddr.add(0x260C444); var replyProtobufAddr = replyCallbackFuncAddr.add(0x50); var patchReplyProtobufFunc1 = replyCallbackFuncAddr.add(0x10); var patchReplyProtobufFunc1Byte; @@ -26,13 +26,14 @@ 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数据的缓冲区 -var replyMessageCallbackFunc1 = baseAddr.add(0x89170D0); // 复用图片的callback函数 + // Reply消息内容相关 var replyContent = ""; // 回复的文本内容 diff --git a/onebot/http.go b/onebot/http.go index e805a0f..f4af0ac 100644 --- a/onebot/http.go +++ b/onebot/http.go @@ -55,6 +55,52 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { Content: v.Data.File, Type: v.Type, } + } else if v.Type == "reply" { + if v.Data.ReplyMessage == nil { + Error("reply_message为空") + continue + } + rm := v.Data.ReplyMessage + + // 提取被回复消息的内容 + referContent := "" + referMsgType := 1 // 默认text + if len(rm.Message) > 0 { + switch rm.Message[0].Type { + case "text": + referContent = rm.Message[0].Data.Text + referMsgType = 1 + case "image": + referMsgType = 3 + case "video": + referMsgType = 43 + case "file": + referMsgType = 49 + } + } + + // 提取发送者昵称 + displayName := "" + if rm.Sender != nil { + displayName = rm.Sender.Nickname + } + + // msgsource需要JSON unescape(双重编码: \\u003c → \u003c → <) + msgsource := jsonUnescapeString(rm.MsgResource) + + msgChan <- &SendMsg{ + UserId: req.UserID, + GroupID: req.GroupID, + Content: v.Data.Text, + Type: "reply", + ReferMsgId: rm.MessageId, + ReferMsgSender: rm.UserID, + ReferMsgType: referMsgType, + ReferCreateTime: rm.Time, + ReferMsgsource: msgsource, + ReferDisplayName: displayName, + ReferContent: referContent, + } } } @@ -123,3 +169,16 @@ func SendHttpReq(jsonData []byte) { Info("返回内容", "status", resp.StatusCode, "body", string(body)) } + +// jsonUnescapeString 对双重JSON编码的字符串做unescape +// 例如: \\u003c → \u003c (第一次json.Unmarshal) → < (本函数) +func jsonUnescapeString(s string) string { + if s == "" { + return s + } + var result string + if err := json.Unmarshal([]byte(`"`+s+`"`), &result); err != nil { + return s + } + return result +} diff --git a/onebot/param.go b/onebot/param.go index f231644..bcf422a 100644 --- a/onebot/param.go +++ b/onebot/param.go @@ -63,6 +63,14 @@ type SendMsg struct { VideoId string Duration int32 VideoSize int32 + + ReferMsgId string + ReferMsgSender string + ReferMsgType int + ReferCreateTime int64 + ReferMsgsource string + ReferDisplayName string + ReferContent string } type VideoInfo struct { @@ -83,12 +91,13 @@ type Message struct { } type SendRequestData struct { - Id string `json:"id,omitempty"` - Text string `json:"text,omitempty"` - File string `json:"file,omitempty"` - URL string `json:"url,omitempty"` - QQ string `json:"qq,omitempty"` - Media []byte `json:"media,omitempty"` + Id string `json:"id,omitempty"` + Text string `json:"text,omitempty"` + File string `json:"file,omitempty"` + URL string `json:"url,omitempty"` + QQ string `json:"qq,omitempty"` + Media []byte `json:"media,omitempty"` + ReplyMessage *WechatMessage `json:"reply_message,omitempty"` } type Config struct { diff --git a/onebot/proto/wxproto/reply_msg.pb.go b/onebot/proto/wxproto/reply_msg.pb.go new file mode 100644 index 0000000..c19afdb --- /dev/null +++ b/onebot/proto/wxproto/reply_msg.pb.go @@ -0,0 +1,404 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.5.1 +// source: proto/wxproto/reply_msg.proto + +package wxproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 发送回复(引用)消息的protobuf结构 +// CG路径: /cgi-bin/micromsg-bin/sendappmsg +// 消息类型: appmsg type=57 +type WxSendReplyMsg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Header *ReplyMsgHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` + Body *ReplyMsgBody `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WxSendReplyMsg) Reset() { + *x = WxSendReplyMsg{} + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WxSendReplyMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WxSendReplyMsg) ProtoMessage() {} + +func (x *WxSendReplyMsg) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WxSendReplyMsg.ProtoReflect.Descriptor instead. +func (*WxSendReplyMsg) Descriptor() ([]byte, []int) { + return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{0} +} + +func (x *WxSendReplyMsg) GetHeader() *ReplyMsgHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *WxSendReplyMsg) GetBody() *ReplyMsgBody { + if x != nil { + return x.Body + } + return nil +} + +// Header: 固定64字节 +type ReplyMsgHeader struct { + state protoimpl.MessageState `protogen:"open.v1"` + Flag []byte `protobuf:"bytes,1,opt,name=flag" json:"flag,omitempty"` // 固定 [0x00] + Timestamp *uint32 `protobuf:"varint,2,opt,name=timestamp" json:"timestamp,omitempty"` // 请求时间戳(秒) + ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof" json:"client_proof,omitempty"` // 16字节随机数据/md5 + DeviceId *uint64 `protobuf:"varint,4,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` // 设备标识(大整数) + Platform *string `protobuf:"bytes,5,opt,name=platform" json:"platform,omitempty"` // 平台信息 "UnifiedPCMac 26 arm64" + Version *uint32 `protobuf:"varint,6,opt,name=version" json:"version,omitempty"` // 版本/标记 = 163 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReplyMsgHeader) Reset() { + *x = ReplyMsgHeader{} + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReplyMsgHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReplyMsgHeader) ProtoMessage() {} + +func (x *ReplyMsgHeader) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReplyMsgHeader.ProtoReflect.Descriptor instead. +func (*ReplyMsgHeader) Descriptor() ([]byte, []int) { + return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{1} +} + +func (x *ReplyMsgHeader) GetFlag() []byte { + if x != nil { + return x.Flag + } + return nil +} + +func (x *ReplyMsgHeader) GetTimestamp() uint32 { + if x != nil && x.Timestamp != nil { + return *x.Timestamp + } + return 0 +} + +func (x *ReplyMsgHeader) GetClientProof() []byte { + if x != nil { + return x.ClientProof + } + return nil +} + +func (x *ReplyMsgHeader) GetDeviceId() uint64 { + if x != nil && x.DeviceId != nil { + return *x.DeviceId + } + return 0 +} + +func (x *ReplyMsgHeader) GetPlatform() string { + if x != nil && x.Platform != nil { + return *x.Platform + } + return "" +} + +func (x *ReplyMsgHeader) GetVersion() uint32 { + if x != nil && x.Version != nil { + return *x.Version + } + return 0 +} + +// Body: 消息体 +type ReplyMsgBody struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sender *string `protobuf:"bytes,1,opt,name=sender" json:"sender,omitempty"` // 接收方wxid (field1是target) + Unknown2 []byte `protobuf:"bytes,2,opt,name=unknown2" json:"unknown2,omitempty"` // 空 (必须显式存在) + Unknown3 *int32 `protobuf:"varint,3,opt,name=unknown3" json:"unknown3,omitempty"` // 0 (必须显式存在) + Receiver *string `protobuf:"bytes,4,opt,name=receiver" json:"receiver,omitempty"` // 发送方wxid + MsgType *int32 `protobuf:"varint,5,opt,name=msg_type,json=msgType" json:"msg_type,omitempty"` // 消息类型 = 57 + Content []byte `protobuf:"bytes,6,opt,name=content" json:"content,omitempty"` // XML内容 (appmsg XML) + SendTimestamp *int64 `protobuf:"varint,7,opt,name=send_timestamp,json=sendTimestamp" json:"send_timestamp,omitempty"` // 发送时间戳 + ClientMsgId *string `protobuf:"bytes,8,opt,name=client_msg_id,json=clientMsgId" json:"client_msg_id,omitempty"` // 消息ID + Unknown9 *int32 `protobuf:"varint,9,opt,name=unknown9" json:"unknown9,omitempty"` // 0 + Flag *int32 `protobuf:"varint,10,opt,name=flag" json:"flag,omitempty"` // 1 + Unknown11 *int32 `protobuf:"varint,11,opt,name=unknown11" json:"unknown11,omitempty"` // 0 + Msgsource []byte `protobuf:"bytes,12,opt,name=msgsource" json:"msgsource,omitempty"` // msgsource XML + Unknown13 []byte `protobuf:"bytes,13,opt,name=unknown13" json:"unknown13,omitempty"` // 空 (必须显式存在) + Unknown14 []byte `protobuf:"bytes,14,opt,name=unknown14" json:"unknown14,omitempty"` // 空 (必须显式存在) + Unknown15 []byte `protobuf:"bytes,15,opt,name=unknown15" json:"unknown15,omitempty"` // 空 (必须显式存在) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReplyMsgBody) Reset() { + *x = ReplyMsgBody{} + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReplyMsgBody) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReplyMsgBody) ProtoMessage() {} + +func (x *ReplyMsgBody) ProtoReflect() protoreflect.Message { + mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReplyMsgBody.ProtoReflect.Descriptor instead. +func (*ReplyMsgBody) Descriptor() ([]byte, []int) { + return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{2} +} + +func (x *ReplyMsgBody) GetSender() string { + if x != nil && x.Sender != nil { + return *x.Sender + } + return "" +} + +func (x *ReplyMsgBody) GetUnknown2() []byte { + if x != nil { + return x.Unknown2 + } + return nil +} + +func (x *ReplyMsgBody) GetUnknown3() int32 { + if x != nil && x.Unknown3 != nil { + return *x.Unknown3 + } + return 0 +} + +func (x *ReplyMsgBody) GetReceiver() string { + if x != nil && x.Receiver != nil { + return *x.Receiver + } + return "" +} + +func (x *ReplyMsgBody) GetMsgType() int32 { + if x != nil && x.MsgType != nil { + return *x.MsgType + } + return 0 +} + +func (x *ReplyMsgBody) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *ReplyMsgBody) GetSendTimestamp() int64 { + if x != nil && x.SendTimestamp != nil { + return *x.SendTimestamp + } + return 0 +} + +func (x *ReplyMsgBody) GetClientMsgId() string { + if x != nil && x.ClientMsgId != nil { + return *x.ClientMsgId + } + return "" +} + +func (x *ReplyMsgBody) GetUnknown9() int32 { + if x != nil && x.Unknown9 != nil { + return *x.Unknown9 + } + return 0 +} + +func (x *ReplyMsgBody) GetFlag() int32 { + if x != nil && x.Flag != nil { + return *x.Flag + } + return 0 +} + +func (x *ReplyMsgBody) GetUnknown11() int32 { + if x != nil && x.Unknown11 != nil { + return *x.Unknown11 + } + return 0 +} + +func (x *ReplyMsgBody) GetMsgsource() []byte { + if x != nil { + return x.Msgsource + } + return nil +} + +func (x *ReplyMsgBody) GetUnknown13() []byte { + if x != nil { + return x.Unknown13 + } + return nil +} + +func (x *ReplyMsgBody) GetUnknown14() []byte { + if x != nil { + return x.Unknown14 + } + return nil +} + +func (x *ReplyMsgBody) GetUnknown15() []byte { + if x != nil { + return x.Unknown15 + } + return nil +} + +var File_proto_wxproto_reply_msg_proto protoreflect.FileDescriptor + +const file_proto_wxproto_reply_msg_proto_rawDesc = "" + + "\n" + + "\x1dproto/wxproto/reply_msg.proto\x12\awxproto\"l\n" + + "\x0eWxSendReplyMsg\x12/\n" + + "\x06header\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\x06header\x12)\n" + + "\x04body\x18\x02 \x01(\v2\x15.wxproto.ReplyMsgBodyR\x04body\"\xb8\x01\n" + + "\x0eReplyMsgHeader\x12\x12\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1c\n" + + "\ttimestamp\x18\x02 \x01(\rR\ttimestamp\x12!\n" + + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" + + "\tdevice_id\x18\x04 \x01(\x04R\bdeviceId\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\rR\aversion\"\xc0\x03\n" + + "\fReplyMsgBody\x12\x16\n" + + "\x06sender\x18\x01 \x01(\tR\x06sender\x12\x1a\n" + + "\bunknown2\x18\x02 \x01(\fR\bunknown2\x12\x1a\n" + + "\bunknown3\x18\x03 \x01(\x05R\bunknown3\x12\x1a\n" + + "\breceiver\x18\x04 \x01(\tR\breceiver\x12\x19\n" + + "\bmsg_type\x18\x05 \x01(\x05R\amsgType\x12\x18\n" + + "\acontent\x18\x06 \x01(\fR\acontent\x12%\n" + + "\x0esend_timestamp\x18\a \x01(\x03R\rsendTimestamp\x12\"\n" + + "\rclient_msg_id\x18\b \x01(\tR\vclientMsgId\x12\x1a\n" + + "\bunknown9\x18\t \x01(\x05R\bunknown9\x12\x12\n" + + "\x04flag\x18\n" + + " \x01(\x05R\x04flag\x12\x1c\n" + + "\tunknown11\x18\v \x01(\x05R\tunknown11\x12\x1c\n" + + "\tmsgsource\x18\f \x01(\fR\tmsgsource\x12\x1c\n" + + "\tunknown13\x18\r \x01(\fR\tunknown13\x12\x1c\n" + + "\tunknown14\x18\x0e \x01(\fR\tunknown14\x12\x1c\n" + + "\tunknown15\x18\x0f \x01(\fR\tunknown15B>Z wxproto.ReplyMsgHeader + 2, // 1: wxproto.WxSendReplyMsg.body:type_name -> wxproto.ReplyMsgBody + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_proto_wxproto_reply_msg_proto_init() } +func file_proto_wxproto_reply_msg_proto_init() { + if File_proto_wxproto_reply_msg_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_reply_msg_proto_rawDesc), len(file_proto_wxproto_reply_msg_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_wxproto_reply_msg_proto_goTypes, + DependencyIndexes: file_proto_wxproto_reply_msg_proto_depIdxs, + MessageInfos: file_proto_wxproto_reply_msg_proto_msgTypes, + }.Build() + File_proto_wxproto_reply_msg_proto = out.File + file_proto_wxproto_reply_msg_proto_goTypes = nil + file_proto_wxproto_reply_msg_proto_depIdxs = nil +} diff --git a/onebot/proto/wxproto/reply_msg.proto b/onebot/proto/wxproto/reply_msg.proto new file mode 100644 index 0000000..957c1d1 --- /dev/null +++ b/onebot/proto/wxproto/reply_msg.proto @@ -0,0 +1,42 @@ +syntax = "proto2"; + +package wxproto; + +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; + +// 发送回复(引用)消息的protobuf结构 +// CG路径: /cgi-bin/micromsg-bin/sendappmsg +// 消息类型: appmsg type=57 +message WxSendReplyMsg { + optional ReplyMsgHeader header = 1; + optional ReplyMsgBody body = 2; +} + +// Header: 固定64字节 +message ReplyMsgHeader { + optional bytes flag = 1; // 固定 [0x00] + optional uint32 timestamp = 2; // 请求时间戳(秒) + optional bytes client_proof = 3; // 16字节随机数据/md5 + optional uint64 device_id = 4; // 设备标识(大整数) + optional string platform = 5; // 平台信息 "UnifiedPCMac 26 arm64" + optional uint32 version = 6; // 版本/标记 = 163 +} + +// Body: 消息体 +message ReplyMsgBody { + optional string sender = 1; // 接收方wxid (field1是target) + optional bytes unknown2 = 2; // 空 (必须显式存在) + optional int32 unknown3 = 3; // 0 (必须显式存在) + optional string receiver = 4; // 发送方wxid + optional int32 msg_type = 5; // 消息类型 = 57 + optional bytes content = 6; // XML内容 (appmsg XML) + optional int64 send_timestamp = 7; // 发送时间戳 + optional string client_msg_id = 8; // 消息ID + optional int32 unknown9 = 9; // 0 + optional int32 flag = 10; // 1 + optional int32 unknown11 = 11; // 0 + optional bytes msgsource = 12; // msgsource XML + optional bytes unknown13 = 13; // 空 (必须显式存在) + optional bytes unknown14 = 14; // 空 (必须显式存在) + optional bytes unknown15 = 15; // 空 (必须显式存在) +} diff --git a/onebot/reply_builder.go b/onebot/reply_builder.go new file mode 100644 index 0000000..9f935b2 --- /dev/null +++ b/onebot/reply_builder.go @@ -0,0 +1,177 @@ +package main + +import ( + "encoding/hex" + "fmt" + "math/rand" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" +) + +// BuildReplyMsgProto 构建发送回复消息的protobuf并返回hex编码的字符串 +func BuildReplyMsgProto(sender, receiver string, replyInfo *ReplyInfo) (string, error) { + now := time.Now().Unix() + + // 构建appmsg XML + appmsgXml := buildReplyAppmsgXml(replyInfo) + + // 构建客户端消息ID + clientMsgId := fmt.Sprintf("%s_%d_%d_xwechat_1", sender, now, rand.Intn(100)) + + // msgsource + msgsource := "1" + + // proto2 需要使用指针 + var ( + unknown2 = []byte{} + unknown3 = int32(0) + msgType = int32(57) + unknown9 = int32(0) + flag = int32(1) + unknown11 = int32(0) + unknown13 = []byte{} + unknown14 = []byte{} + unknown15 = []byte{} + timestamp = uint32(now) + deviceId = generateDeviceId() + version = uint32(163) + ) + + msg := &wxproto.WxSendReplyMsg{ + Header: &wxproto.ReplyMsgHeader{ + Flag: []byte{0x00}, + Timestamp: ×tamp, + ClientProof: generateRandomBytes(16), + DeviceId: &deviceId, + Platform: proto.String("UnifiedPCMac 26 arm64"), + Version: &version, + }, + Body: &wxproto.ReplyMsgBody{ + Sender: &receiver, + Unknown2: unknown2, + Unknown3: &unknown3, + Receiver: &sender, + MsgType: &msgType, + Content: []byte(appmsgXml), + SendTimestamp: proto.Int64(now), + ClientMsgId: &clientMsgId, + Unknown9: &unknown9, + Flag: &flag, + Unknown11: &unknown11, + Msgsource: []byte(msgsource), + Unknown13: unknown13, + Unknown14: unknown14, + Unknown15: unknown15, + }, + } + + data, err := proto.Marshal(msg) + if err != nil { + return "", fmt.Errorf("marshal reply proto failed: %w", err) + } + + fmt.Println(fmt.Printf("0x% x\n", data)) + + return hex.EncodeToString(data), nil +} + +// ReplyInfo 回复消息的全部信息 +type ReplyInfo struct { + Content string // 回复的文本内容 + MsgId string // 被回复消息的svrid + MsgSender string // 被回复消息的发送者wxid + MsgType int // 被回复消息的类型 (1=text, 3=image, 43=video, 49=appmsg) + CreateTime int64 // 被回复消息的时间戳(毫秒) + Msgsource string // 被回复消息的msgsource + DisplayName string // 被回复消息发送者的昵称 + MsgContent string // 被回复消息的内容 +} + +// buildReplyAppmsgXml 构建回复消息的appmsg XML,字段顺序匹配微信真实protobuf +func buildReplyAppmsgXml(info *ReplyInfo) string { + // 时间戳:毫秒转秒 + createTime := info.CreateTime / 1000 + + xml := `` + xml += `` + escapeXmlStr(info.Content) + `` + 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 - 字段顺序与微信一致: chatusr → type → createtime → msgsource → displayname → svrid → fromusr → content + xml += `` + xml += `` + escapeXmlStr(info.MsgSender) + `` + xml += `` + fmt.Sprintf("%d", info.MsgType) + `` + xml += `` + fmt.Sprintf("%d", createTime) + `` + xml += `` + escapeXmlStr(info.Msgsource) + `` + xml += `` + escapeXmlStr(info.DisplayName) + `` + xml += `` + escapeXmlStr(info.MsgId) + `` + xml += `` + escapeXmlStr(info.MsgSender) + `` + xml += `` + escapeXmlStr(info.MsgContent) + `` + xml += `` + xml += `` + + xml += `` + escapeXmlStr(info.MsgSender) + `` + + return xml +} + +// escapeXmlStr 简单的XML转义 +func escapeXmlStr(s string) string { + result := "" + for _, c := range s { + switch c { + case '&': + result += "&" + case '<': + result += "<" + case '>': + result += ">" + case '"': + result += """ + case '\'': + result += "'" + default: + result += string(c) + } + } + return result +} + +// generateRandomBytes 生成随机字节 +func generateRandomBytes(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(rand.Intn(256)) + } + return b +} + +// generateDeviceId 动态生成设备ID (大整数,高位置1模拟真实设备) +func generateDeviceId() uint64 { + return rand.Uint64() | (0xFFFFFFFF << 32) +} diff --git a/onebot/script.js b/onebot/script.js index 2e641ea..e2e0d4d 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -137,6 +137,23 @@ var textProtoHexGlobal = ""; var imgProtoHexGlobal = ""; // 视频消息protobuf全局变量 (从Go直接传入hex编码) var videoProtoHexGlobal = ""; +// 回复消息protobuf全局变量 (从Go直接传入hex编码) +var replyProtoHexGlobal = ""; + +// 回复消息全局变量 +var replyCallbackFuncAddr = baseAddr.add({{.replyCallbackFuncAddr}}); +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 replyMessageCallbackFunc = baseAddr.add({{.replyMessageCallbackFunc}}); +var replyCgiAddr = ptr(0); +var sendReplyMessageAddr = ptr(0); +var replyMessageAddr = ptr(0); +var replyProtoX1PayloadAddr = ptr(0); // -------------------------全局变量分区------------------------- @@ -344,6 +361,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); } } }); @@ -853,6 +874,148 @@ function attachGetCallbackFromWrapper() { setImmediate(attachGetCallbackFromWrapper); +// -------------------------发送回复消息分区------------------------- +function setupSendReplyMessageDynamic() { + replyCgiAddr = Memory.alloc(128); + sendReplyMessageAddr = Memory.alloc(256); + replyMessageAddr = Memory.alloc(256); + replyProtoX1PayloadAddr = Memory.alloc(4096); + + patchString(replyCgiAddr, "/cgi-bin/micromsg-bin/sendappmsg"); + + 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.add(0x00).writePointer(replyMessageCallbackFunc); + replyMessageAddr.add(0x08).writeU32(taskIdGlobal); + replyMessageAddr.add(0x0c).writeU32(0x6e); + replyMessageAddr.add(0x10).writeU64(0x3); + replyMessageAddr.add(0x18).writePointer(replyCgiAddr); + 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); + + console.log("[+] Reply message setup complete. CgiAddr: " + replyCgiAddr + " SendAddr: " + sendReplyMessageAddr); +} + +setImmediate(setupSendReplyMessageDynamic); + +function patchReplyProtoBuf() { + Interceptor.attach(replyCallbackFuncAddr, { + onEnter: function (args) { + var firstValue = this.context.sp.add(0x10).readU32(); + if (firstValue === taskIdGlobal) { + 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 { + 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 attachReplyProto() { + Interceptor.attach(replyProtobufAddr, { + onEnter: function (args) { + var currTaskId = this.context.sp.add(0x30).readU32(); + if (currTaskId !== taskIdGlobal) { + return; + } + + if (!replyProtoHexGlobal || replyProtoHexGlobal.length === 0) { + console.error("[!] replyProtoHexGlobal 为空"); + return; + } + + const finalPayload = hexToByteArray(replyProtoHexGlobal); + replyProtoX1PayloadAddr.writeByteArray(finalPayload); + this.context.x1 = replyProtoX1PayloadAddr; + this.context.x2 = ptr(finalPayload.length); + console.log("[+] Reply protobuf注入完成, length=" + finalPayload.length); + }, + }); +} + +setImmediate(attachReplyProto); + +function triggerSendReplyMessage(taskId, sender, receiver, protoHex, payloadHex) { + if (!taskId || !receiver || !sender) { + console.error("[!] reply: taskId or receiver or sender is empty!"); + return "fail"; + } + + if (!triggerX0 || !triggerX1Payload) { + console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获"); + return "fail"; + } + + replyProtoHexGlobal = protoHex; + taskIdGlobal = taskId; + + replyMessageAddr.add(0x08).writeU32(taskIdGlobal); + sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal); + + const payloadData = hexToByteArray(payloadHex); + triggerX1Payload.writeByteArray(payloadData); + triggerX1Payload.add(0x18).writePointer(replyCgiAddr); + triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); + triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); + sendMsgType = "reply" + + const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); + + try { + MMStartTask(triggerX0, triggerX1Payload); + return "1"; + } catch (e) { + console.error("[!] Error trigger reply MMStartTask: " + e); + return "fail"; + } +} + +// -------------------------发送回复消息分区------------------------- + rpc.exports = { triggerSendImgMessage: triggerSendImgMessage, triggerUploadImg: triggerUploadImg, @@ -860,6 +1023,7 @@ rpc.exports = { triggerDownload: triggerDownload, triggerUploadVideo: triggerUploadVideo, triggerSendVideoMessage: triggerSendVideoMessage, + triggerSendReplyMessage: triggerSendReplyMessage, }; // -------------------------发送图片消息分区------------------------- diff --git a/onebot/text_builder.go b/onebot/text_builder.go index c1e2946..c82d5f8 100644 --- a/onebot/text_builder.go +++ b/onebot/text_builder.go @@ -124,6 +124,11 @@ func BuildSendPayload(taskId int64, msgType string) string { payloadData[16] = 0x10 payloadData[28] = 0x21 payloadData[92] = 0x6E + case "reply": + payloadData[0] = 0x6E + payloadData[16] = 0x10 + payloadData[28] = 0x22 + payloadData[92] = 0x6E } // 构建完整buffer: taskId(4字节) + payloadData(412字节) = 0x1A0字节 diff --git a/onebot/worker.go b/onebot/worker.go index b182914..c4792be 100644 --- a/onebot/worker.go +++ b/onebot/worker.go @@ -142,6 +142,29 @@ func SendWechatMsg(m *SendMsg) { case "download": result := fridaScript.ExportsCall("triggerDownload", targetId, m.FIleCdnUrl, m.AesKey, m.FilePath, m.FileType) Info("📩 下载任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) + case "reply": + replyInfo := &ReplyInfo{ + Content: m.Content, + MsgId: m.ReferMsgId, + MsgSender: m.ReferMsgSender, + MsgType: m.ReferMsgType, + CreateTime: m.ReferCreateTime, + Msgsource: m.ReferMsgsource, + DisplayName: m.ReferDisplayName, + MsgContent: m.ReferContent, + } + protoHex, err := BuildReplyMsgProto(myWechatId, targetId, replyInfo) + if err != nil { + Error("构建回复protobuf失败", "err", err) + return + } + payloadHex := BuildSendPayload(currTaskId, "reply") + result := fridaScript.ExportsCall("triggerSendReplyMessage", currTaskId, myWechatId, targetId, protoHex, payloadHex) + Info("📩 发送回复任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) + if result != "1" { + Error("发送回复失败", "task_id", currTaskId, "target_id", targetId, "result", result) + return + } } select { @@ -225,8 +248,14 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("XML解析失败", "err", err) return nil, err } - - data, err := DownloadFile(fileMsg.Emoji.ThumbUrl) + + // 优先thumburl,为空则用externurl + emojiUrl := fileMsg.Emoji.ThumbUrl + if emojiUrl == "" { + emojiUrl = fileMsg.Emoji.ExternUrl + } + + data, err := DownloadFile(emojiUrl) if err != nil { Error("下载表情失败", "err", err) return nil, err diff --git a/wechat_version/4_1_9_52_mac.json b/wechat_version/4_1_9_52_mac.json index 4ec1b93..36c455b 100644 --- a/wechat_version/4_1_9_52_mac.json +++ b/wechat_version/4_1_9_52_mac.json @@ -18,5 +18,7 @@ "downloadFileAddr": "0x4E0E264", "downloadVideoAddr": "0x4E28044", "videoCallbackFuncAddr": "0x26DD8C8", - "videoMessageCallbackFunc": "0x8C2F0F8" + "videoMessageCallbackFunc": "0x8C2F0F8", + "replyCallbackFuncAddr": "0x260C444", + "replyMessageCallbackFunc": "0x8C29A08" } From c69f8cbd2c9142c84239e8566db0763f9e5f4c89 Mon Sep 17 00:00:00 2001 From: jackyin <648588267@qq.com> Date: Mon, 11 May 2026 14:22:19 +0800 Subject: [PATCH 4/4] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 69b038a..ab2c5c7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # WeChat 4.0 Message hook +4.1.9之后的不更新了,感谢各位大佬的支持 + 本代码库中的所有代码、示例、文档及相关内容(以下简称“本项目”)仅供学习、研究和技术交流之目的使用。使用本项目所产生的任何风险(包括但不限于数据丢失、系统崩溃、安全问题、法律风险等)均由使用者自行承担。 我hook的是微信三端最底层的发消息能力,这块代码是开源的,感兴趣google tencent/mars