This commit is contained in:
yincong
2026-02-04 09:57:16 +08:00
parent 24ef8b8dd3
commit 13e11fa270
12 changed files with 590 additions and 653 deletions
+4 -4
View File
@@ -5,14 +5,14 @@
怎么使用,如果你的mac已经关闭了SIP
```
frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l frida/succ.js
manualTrigger(0x20000095, "wxid_xxxx", "hi")
triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi")
```
没有关闭SIP,查看文件 https://github.com/yincongcyincong/weixin-macos/blob/main/frida-gadget/readme.md
把每一步都执行完成,然后启动微信
```
frida -H 127.0.0.1:27042 -n Gadget -l ./frida/succ.js
manualTrigger(0x20000095, "wxid_xxxx", "hi")
triggerSendTextMessage(0x20000095, "wxid_xxxx", "hi")
```
![image](https://github.com/user-attachments/assets/401de4b8-5d10-48d9-8dcf-eecc8ae8682a)
@@ -35,8 +35,8 @@ mkdir -p "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents
cp /Users/yincong/Desktop/1.png /Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg
manualUpload("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg")
manualTrigger(0x20000199, "wxid_xxx","wxid_xxx")
triggerUploadImg("wxid_7wd1ece99f7i21", "8dd4755e12e052fa5647a883e6bf0783", "/Users/yincong/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_xxx/temp/xxx/2026-01/Img/xxx.jpg")
triggerSendImgMessage(0x20000199, "wxid_xxx","wxid_xxx")
```
+1 -8
View File
@@ -1,10 +1,3 @@
触发 STNManager__MMStartTask
图片hook: startUploadMedia, OnUploadCompleted
Req2Buf 构造消息的buffer入口,其中X4是消息内容,输出的是压缩后的protobuf
Pack是打包函数,消息内容 cgi等信息打包进入
InitClientChannel DoHandShakeLoop 是建立mmtls链接最重要的函数
DoSendEarlyAppData 是消息体函数,可以看到你发的消息体
__RunReadWrite 这条日志打印后的 “task socket send sock:%_, %_ http len:%" 一个BL X8里面有send命令进行发送
老图和新图发送的还不一样
有个映射
startUploadMedia 上传图片,OnUploadCompleted 上传图片完成后的回调
+35
View File
@@ -0,0 +1,35 @@
// 1. 获取微信主模块的基地址
var baseAddr = Process.getModuleByName("WeChat").base;
if (!baseAddr) {
console.error("[!] 找不到 WeChat 模块基址,请检查进程名。");
}
console.log("[+] WeChat base address: " + baseAddr);
var sendFuncAddr = baseAddr.add(0x4683540);
Interceptor.attach(sendFuncAddr, {
onEnter: function (args) {
// 获取 x0 的值
var x0_ptr = args[0];
console.log("[+] Register x0: " + x0_ptr);
// 检查 x0 是否为合法指针,防止访问 0x1 导致崩溃
try {
if (x0_ptr.isNull()) {
console.log("[!] x0 is NULL");
} else if (parseInt(x0_ptr) < 0x1000) {
console.log("[!] x0 looks like an invalid small pointer (potential error code): " + x0_ptr);
} else {
// 打印 x0 指向的内存数据(例如前 32 字节)
console.log("[+] Memory at x0:\n" + hexdump(x0_ptr, {
offset: 0,
length: 256,
header: true,
ansi: true
}));
}
} catch (e) {
console.error("[!] Failed to read memory at x0: " + e.message);
}
}
})
-70
View File
@@ -1,70 +0,0 @@
const mod = Process.getModuleByName("WeChat");
console.log("[+] Base Address:", mod.base);
var counterMap = new Map();
function checkValid(p) {
if (p.isNull()) {
return false;
}
if (!p.and(0x7).isNull()) {
return false;
}
if (p.compare(ptr("0x600000000000")) >= 0 && p.compare(ptr("0x700000000000")) < 0) {
return true;
}
return false;
}
function handlePr(addr, keyword) {
const realAddr = ptr("0x" + addr).sub("0x100000000").add(mod.base);
console.log("[+] real Address:", realAddr)
Interceptor.attach(realAddr, {
onEnter(args) {
const currentCount = counterMap.get(addr) || 0;
counterMap.set(addr, currentCount + 1);
console.log(`${addr} called ${currentCount} times`);
if (args[0].isNull()) {
console.log("args[0] is NULL, skipping hexdump.");
return;
}
try {
console.log("[*] Hexdump of args[0]:");
console.log(hexdump(args[0], {
offset: 0,
length: 256,
header: true,
ansi: true // 在支持彩色输出的终端(如 macOS 终端)中效果很好
}));
} catch (e) {
console.log("[!] 无法读取内存: " + e.message);
}
},
onLeave(retval) {
}
});
}
const prs = ["10438958c", "10434ce6c", "10433c754"]
const k = "";
for (let pr of prs) {
handlePr(pr, k);
}
function ShowCount() {
for (let [addr, count] of counterMap) {
console.log(`${addr}: ${count}`);
}
}
function clearCount() {
counterMap.clear();
console.log("Counter cleared.");
}
+13 -13
View File
@@ -2,16 +2,16 @@ var baseAddr = Process.getModuleByName("WeChat").base;
if (!baseAddr) {
console.error("[!] 找不到 WeChat 模块基址,请检查进程名。");
}
console.log("[+] WeChat base address: " + baseAddr);
var buf2RespAddr = baseAddr.add(0x35F4B58)
// -------------------------接收消息分区-------------------------
function setReceiver() {
var buf2RespAddr = baseAddr.add(0x347BD44);
console.log("[+] buf2RespAddr WeChat Base: " + baseAddr + "[+] Attaching to: " + buf2RespAddr);
// 3. 开始拦截
Interceptor.attach(buf2RespAddr, {
onEnter: function (args) {
console.log("[*] 拦截到消息发送");
const currentPtr = this.context.x1;
let start = 0x1e;
let senderLen = currentPtr.add(start).readU8();
@@ -113,15 +113,6 @@ function setReceiver() {
// 使用 setImmediate 确保在模块加载后执行
setImmediate(setReceiver)
function generateAESKey() {
const chars = 'abcdef0123456789';
let key = '';
for (let i = 0; i < 32; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
}
function getProtobufRawBytes(pBuffer, scanSize) {
const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A];
@@ -235,4 +226,13 @@ function getCleanString(uint8Array) {
}
}
return out;
}
function generateAESKey() {
const chars = 'abcdef0123456789';
let key = '';
for (let i = 0; i < 32; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
}
+412 -410
View File
@@ -5,97 +5,295 @@ if (!baseAddr) {
}
console.log("[+] WeChat base address: " + baseAddr);
// 触发函数地址,不同版本的地址看wechat_version 中的json文件复制过来
var triggerFuncAddr = baseAddr.add(0x444A99C);
var sendMessageCallbackFunc = baseAddr.add(0xEDB4678);
var messageCallbackFunc1 = baseAddr.add(0x7f04f70);
var messageCallbackFunc2 = baseAddr.add(0x7f04fc8);
var messageCallbackFunc3 = baseAddr.add(0x7f96918);
var messageCallbackFunc4 = baseAddr.add(0x7f96a08);
var messageCallbackFunc5 = baseAddr.add(0x7f968a0);
var messageCallbackFunc6 = baseAddr.add(0x7f9dfe0);
// -------------------------基础函数分区-------------------------
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;
}
// 这个必须是绝对位置
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);
function stringToHexArray(str) {
var utf8Str = unescape(encodeURIComponent(str));
var arr = [];
for (var i = 0; i < utf8Str.length; i++) {
arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值)
}
return arr;
}
// 消息体的一些指针地址
var cgiAddr = ptr(0);
var callBackFuncAddr = ptr(0);
var sendMessageAddr = ptr(0);
var messageAddr = ptr(0);
var messageContentAddr = ptr(0);
var messageAddrAddr = ptr(0);
var contentAddr = ptr(0);
function generateRandom5ByteVarint() {
let res = [];
// 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机
for (let i = 0; i < 4; i++) {
let random7Bit = Math.floor(Math.random() * 128);
res.push(random7Bit | 0x80); // 强制设置最高位为 1
}
// 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0
let lastByte = Math.floor(Math.random() * 127) + 1;
res.push(lastByte & 0x7F); // 确保最高位为 0
return res;
}
// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes)
function getVarintTimestampBytes() {
let ts = Math.floor(Date.now() / 1000);
let encodedBytes = [];
let tempTs = ts >>> 0; // 强制转为 32位 无符号整数
while (true) {
let byte = tempTs & 0x7F;
tempTs >>>= 7;
if (tempTs !== 0) {
encodedBytes.push(byte | 0x80);
} else {
encodedBytes.push(byte);
break;
}
}
return encodedBytes;
}
function patchString(addr, plainStr) {
const bytes = [];
for (let i = 0; i < plainStr.length; i++) {
bytes.push(plainStr.charCodeAt(i));
}
addr.writeByteArray(bytes);
addr.add(bytes.length).writeU8(0);
}
function generateAESKey() {
const chars = 'abcdef0123456789';
let key = '';
for (let i = 0; i < 32; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
}
function getProtobufRawBytes(pBuffer, scanSize) {
const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A];
let uint8Array;
try {
const mem = pBuffer.readByteArray(scanSize);
if (!mem) return [];
uint8Array = new Uint8Array(mem);
} catch (e) {
console.error("读取内存失败: " + e);
return [];
}
let finalResults = [];
let i = 0x1a;
tags.forEach(targetTag => {
let found = false;
for (; i < uint8Array.length; i++) {
if (uint8Array[i] === targetTag) {
// 1. 解析 Varint 长度 (支持 1-5 字节长度标识)
let length = 0;
let shift = 0;
let bytesReadForLen = 0;
i = i + 1;
while (i < uint8Array.length) {
let b = uint8Array[i];
length |= (b & 0x7F) << shift;
bytesReadForLen++;
i++;
if (!(b & 0x80)) break;
shift += 7;
}
// 2. 截取原始 Byte 数据
if (i + length <= uint8Array.length) {
let rawData = uint8Array.slice(i, i + length);
finalResults.push(getCleanString(rawData));
i += length;
} else {
finalResults.push(null); // 长度越界
}
found = true;
break; // 找到第一个匹配的 Tag 就跳出
}
}
if (!found) finalResults.push(null); // 未找到该 Tag
});
return finalResults;
}
function getCleanString(uint8Array) {
var out = "";
var i = 0;
var len = uint8Array.length;
while (i < len) {
var c = uint8Array[i++];
// 1. 处理单字节 (ASCII: 0xxxxxxx)
if (c < 0x80) {
// 只保留可见字符 (Space 32 到 ~ 126)
if (c >= 32 && c <= 126) {
out += String.fromCharCode(c);
}
}
// 2. 处理双字节 (110xxxxx 10xxxxxx)
else if ((c & 0xE0) === 0xC0 && i < len) {
var c2 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80) {
// 这种通常是特殊拉丁字母等,按需保留
var charCode = ((c & 0x1F) << 6) | (c2 & 0x3F);
out += String.fromCharCode(charCode);
}
}
// 3. 处理三字节 (1110xxxx 10xxxxxx 10xxxxxx) -> 绝大多数汉字在此
else if ((c & 0xF0) === 0xE0 && i + 1 < len) {
var c2 = uint8Array[i++];
var c3 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80) {
var charCode = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
if (
(charCode >= 0x4E00 && charCode <= 0x9FA5) || // 基本汉字
(charCode >= 0x3000 && charCode <= 0x303F) || // 常用中文标点 (。,、)
(charCode >= 0xFF00 && charCode <= 0xFFEF) || // 全角符号/标点 (!:?)
(charCode >= 0x2000 && charCode <= 0x206F) || // 常用标点扩展 (含 \u2005)
(charCode >= 0x3400 && charCode <= 0x4DBF) // 扩展 A 区汉字
) {
out += String.fromCharCode(charCode);
}
}
} else if ((c & 0xF8) === 0xF0 && i + 2 < len) {
var c2 = uint8Array[i++];
var c3 = uint8Array[i++];
var c4 = uint8Array[i++];
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80 && (c4 & 0xC0) === 0x80) {
// 计算 Unicode 码点
var codePoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
// Emoji 范围通常在 U+1F000 到 U+1F9FF 之间
if (codePoint >= 0x1F000 && codePoint <= 0x1FADF) {
// 使用 fromCodePoint 处理 4 字节字符
out += String.fromCodePoint(codePoint);
}
}
}
}
return out;
}
function generateBytes(n) {
// 生成随机字符串
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < n; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return stringToHexArray(result);
}
// -------------------------基础函数分区-------------------------
// -------------------------全局变量分区-------------------------
// 文本消息全局变量
var protobufAddr = baseAddr.add(0x2387280);
var patchTextProtobufAddr = baseAddr.add(0x238725C);
var PatchTextProtobufDeleteAddr = baseAddr.add(0x2387298);
var textCgiAddr = ptr(0);
var sendTextMessageAddr = ptr(0);
var textMessageAddr = ptr(0);
var textProtoX1PayloadAddr = ptr(0);
var sendMessageCallbackFunc = ptr(0);
var messageCallbackFunc1 = ptr(0);
// 双方公共使用的地址
var triggerX1Payload;
var req2bufEnterAddr = baseAddr.add(0x35CEDC0);
var req2bufExitAddr = baseAddr.add(0x35CFF78);
var sendFuncAddr = baseAddr.add(0x4683540);
var insertMsgAddr = ptr(0);
var receiverAddr = ptr(0);
var htmlContentAddr = ptr(0);
var protoX1PayloadAddr = ptr(0);
var protoX1PayloadLen = 1024;
var sendMsgType = "";
var buf2RespAddr = baseAddr.add(0x4568B58);
// 消息的taskId
// 图片消息全局变量
var sendImgMessageCallbackFunc = ptr(0);
var uploadImageAddr = baseAddr.add(0x45DC834);
var imgProtobufAddr = baseAddr.add(0x3317834);
var patchImgProtobufFunc1 = baseAddr.add(0x0)
var patchImgProtobufFunc2 = baseAddr.add(0x0);
var imgProtobufDeleteAddr = baseAddr.add(0x0);
var CndOnCompleteAddr = baseAddr.add(0x450195C);
var imgCgiAddr = ptr(0);
var sendImgMessageAddr = ptr(0);
var imgMessageAddr = ptr(0);
var imgProtoX1PayloadAddr = ptr(0);
var uploadGlobalX0 = ptr(0)
var uploadFunc1Addr = ptr(0)
var uploadFunc2Addr = ptr(0)
var imageIdAddr = ptr(0)
var md5Addr = ptr(0)
var uploadAesKeyAddr = ptr(0)
var ImagePathAddr1 = ptr(0)
var uploadImagePayload = ptr(0);
var globalImageCdnKey = "";
var globalAesKey1 = "";
var globalMd5Key = "";
// 发送消息的全局变量
var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复
var receiverGlobal = "wxid_"
var contentGlobal = "";
var senderGlobal = "wxid_"
var lastSendTime = 0;
var atUserGlobal = "";
// 打印消息的地址,便于查询问题
function printAddr() {
console.log("[+] Addresses:");
console.log(" - cgiAddr: " + cgiAddr);
console.log(" - callBackFuncAddr: " + callBackFuncAddr);
console.log(" - sendMessageAddr: " + sendMessageAddr);
console.log(" - messageAddr: " + messageAddr);
console.log(" - messageContentAddr: " + messageContentAddr);
console.log(" - messageAddrAddr: " + messageAddrAddr);
console.log(" - contentAddr: " + contentAddr);
console.log(" - globalMessagePtr: " + globalMessagePtr);
console.log(" - triggerX1Payload: " + triggerX1Payload);
}
const imageCp = generateBytes(16) // m30c4674f5a0b9d
// 辅助函数:写入 Hex 字符串
function patchHex(addr, hexStr) {
const bytes = hexStr.split(' ').map(h => parseInt(h, 16));
addr.writeByteArray(bytes);
addr.add(bytes.length).writeU8(0); // 终止符
}
// -------------------------全局变量分区-------------------------
// -------------------------发送文本消息分区-------------------------
// 初始化进行内存的分配
function setupSendMessageDynamic() {
function setupSendTextMessageDynamic() {
console.log("[+] Starting Dynamic Message Patching...");
// 1. 动态分配内存块(按需分配大小)
// 分配原则:字符串给 64-128 字节,结构体按实际大小分配
cgiAddr = Memory.alloc(128);
callBackFuncAddr = Memory.alloc(16);
sendMessageAddr = Memory.alloc(256);
messageAddr = Memory.alloc(512);
messageContentAddr = Memory.alloc(32);
messageAddrAddr = Memory.alloc(64);
contentAddr = Memory.alloc(255);
receiverAddr = Memory.alloc(24);
htmlContentAddr = Memory.alloc(24);
textCgiAddr = Memory.alloc(128);
sendTextMessageAddr = Memory.alloc(256);
textMessageAddr = Memory.alloc(256);
// A. 写入字符串内容
patchHex(cgiAddr, "2F 63 67 69 2D 62 69 6E 2F 6D 69 63 72 6F 6D 73 67 2D 62 69 6E 2F 6E 65 77 73 65 6E 64 6D 73 67");
patchHex(contentAddr, " ");
patchString(textCgiAddr, "/cgi-bin/micromsg-bin/newsendmsg");
// B. 构建 SendMessage 结构体 (X24 基址位置)
sendMessageAddr.add(0x00).writeU64(0);
sendMessageAddr.add(0x08).writeU64(0);
sendMessageAddr.add(0x10).writePointer(sendMessageCallbackFunc); // 虚表地址通常仍需硬编码或从模块基址计算
sendMessageAddr.add(0x18).writeU64(1);
sendMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendMessageAddr.add(0x28).writePointer(messageAddr); // 指向动态分配的 Message
// B. 构建 sendTextMessageAddr 结构体 (X24 基址位置)
sendTextMessageAddr.add(0x00).writeU64(0);
sendTextMessageAddr.add(0x08).writeU64(0);
sendTextMessageAddr.add(0x10).writePointer(sendMessageCallbackFunc);
sendTextMessageAddr.add(0x18).writeU64(1);
sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendTextMessageAddr.add(0x28).writePointer(textMessageAddr); // 指向动态分配的 Message
console.log(" [+] sendMessageAddr Object: ", hexdump(sendMessageAddr, {
console.log(" [+] sendTextMessageAddr Object: ", hexdump(sendTextMessageAddr, {
offset: 0,
length: 48,
header: true,
@@ -103,87 +301,51 @@ function setupSendMessageDynamic() {
}));
// C. 构建 Message 结构体
messageAddr.add(0x00).writePointer(messageCallbackFunc1);
messageAddr.add(0x08).writeU32(taskIdGlobal);
messageAddr.add(0x0c).writeU32(0x20a);
messageAddr.add(0x10).writeU64(0x3);
messageAddr.add(0x18).writePointer(cgiAddr);
textMessageAddr.add(0x00).writePointer(messageCallbackFunc1);
textMessageAddr.add(0x08).writeU32(taskIdGlobal);
textMessageAddr.add(0x0c).writeU32(0x20a);
textMessageAddr.add(0x10).writeU64(0x3);
textMessageAddr.add(0x18).writePointer(textCgiAddr);
textMessageAddr.add(0x20).writeU64(uint64("0x20"));
// 设置一些固定值
messageAddr.add(0x20).writeU64(uint64("0x20"));
messageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
messageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
messageAddr.add(0x58).writeU64(uint64("0x0101010100000001"));
// 处理回调地址
callBackFuncAddr.writePointer(messageCallbackFunc2);
messageAddr.add(0x98).writePointer(callBackFuncAddr);
// 设置内容指针
messageAddr.add(0xb8).writePointer(messageCallbackFunc3);
messageAddr.add(0xc0).writePointer(messageContentAddr);
messageAddr.add(0xc8).writeU64(uint64("0x0000000100000001"));
messageAddr.add(0xd0).writeU64(0x4);
messageAddr.add(0xd8).writeU64(0x1);
messageAddr.add(0xe0).writeU64(0x1);
messageAddr.add(0xe8).writePointer(messageCallbackFunc4);
messageContentAddr.writePointer(messageAddrAddr);
messageAddrAddr.writePointer(messageCallbackFunc5);
receiverAddr.writePointer(messageCallbackFunc6);
receiverAddr.add(0x08).writePointer(contentAddr);
messageAddrAddr.add(0x08).writePointer(receiverAddr);
messageAddrAddr.add(0x10).writePointer(contentAddr);
messageAddrAddr.add(0x18).writeU32(1);
messageAddrAddr.add(0x20).writeU32(Math.floor(Date.now() / 1000));
htmlContentAddr.writePointer(contentAddr);
messageAddrAddr.add(0x28).writePointer(htmlContentAddr);
console.log(" [+] messageAddr Object: ", hexdump(messageAddr, {
console.log(" [+] textMessageAddr Object: ", hexdump(textMessageAddr, {
offset: 0,
length: 64,
header: true,
ansi: true
}));
console.log(" [+] Dynamic Memory Setup Complete. - Message Object: " + messageAddr);
console.log("[+] Dynamic Memory Setup Complete. - Message Object: " + textMessageAddr);
}
setImmediate(setupSendMessageDynamic);
setImmediate(setupSendTextMessageDynamic);
// 设置trigger函数的x0参数
function setTriggerAttach() {
console.log("[+] WeChat Base: " + baseAddr + "[+] Attaching to: " + triggerFuncAddr);
// 3. 开始拦截
Interceptor.attach(triggerFuncAddr, {
onEnter: function (args) {
console.log("[+] Entered Function: 0x10444A99C");
if (!globalMessagePtr.isNull()) {
return;
}
globalMessagePtr = this.context.x0;
console.log("[+] globalMessagePtr 当前 X0 的指针值: " + globalMessagePtr);
},
onLeave: function (retval) {
}
function patchTextProtoBuf() {
Memory.patchCode(patchTextProtobufAddr, 4, code => {
const cw = new Arm64Writer(code, {pc: patchTextProtobufAddr});
cw.putNop();
cw.flush();
});
console.log("[+] Patching patchTextProtobufAddr " + patchTextProtobufAddr + " 成功.");
Memory.patchCode(PatchTextProtobufDeleteAddr, 4, code => {
const cw = new Arm64Writer(code, {pc: PatchTextProtobufDeleteAddr});
cw.putNop();
cw.flush();
});
console.log("[+] Patching PatchTextProtobufDeleteAddr " + PatchTextProtobufDeleteAddr + " 成功.");
}
// 使用 setImmediate 确保在模块加载后执行
setImmediate(setTriggerAttach);
setTimeout(function () {
console.log("[+] 3秒等待结束,准备执行 Patch...");
patchTextProtoBuf();
}, 3000);
function manualTrigger(taskId, receiver, content) {
function triggerSendTextMessage(taskId, receiver, content, atUser) {
console.log("[+] Manual Trigger Started...");
if (globalMessagePtr.isNull()) {
console.error("[!] globalMessagePtr is NULL, cannot trigger!");
return "fail";
}
if (!taskId || !receiver || !content) {
console.error("[!] taskId or Receiver or Content is empty!");
return "fail";
@@ -191,25 +353,20 @@ function manualTrigger(taskId, receiver, content) {
// 获取当前时间戳 (秒)
const timestamp = Math.floor(Date.now() / 1000);
// 全局变量不为空,并且上次发送时间小于1s,不给发送
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;
atUserGlobal = atUser
console.log("taskIdGlobal: " + taskIdGlobal + ", receiverGlobal: " + receiverGlobal + ", contentGlobal: " + contentGlobal + ", atUserGlobal: " + atUserGlobal) ;
messageAddr.add(0x08).writeU32(taskIdGlobal);
sendMessageAddr.add(0x20).writeU32(taskIdGlobal);
messageAddrAddr.add(0x18).writeU32(timestamp);
textMessageAddr.add(0x08).writeU32(taskIdGlobal);
sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal);
const payloadData = [
0x0A, 0x02, 0x00, 0x00, // 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08
0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // 0x10
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x10
0x40, 0xec, 0x0e, 0x12, 0x01, 0x00, 0x00, 0x00, // 0x18
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x28
@@ -230,7 +387,7 @@ function manualTrigger(taskId, receiver, content) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xB0
0xC0, 0x66, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0xB8
0xC0, 0x66, 0x26, 0x73, 0x01, 0x00, 0x00, 0x00, // 0xB8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0
@@ -260,32 +417,114 @@ function manualTrigger(taskId, receiver, content) {
0x98, 0x67, 0xED, 0x75, 0x01, 0x00, 0x00, 0x00, // 0x190
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198
];
// 从 0x175ED6604 开始写入 Payload
triggerX1Payload.writeU32(taskIdGlobal);
triggerX1Payload.add(0x04).writeByteArray(payloadData);
triggerX1Payload.add(0x18).writePointer(cgiAddr);
triggerX1Payload.add(0x18).writePointer(textCgiAddr);
sendMsgType = "text"
const sub_10444A99C = new NativeFunction(triggerFuncAddr, 'uint64', ['pointer', 'pointer']);
console.log("finished init payload")
const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer']);
// 5. 调用函数
try {
const arg1 = globalMessagePtr; // 第一个指针参数
const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600
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);
const result = MMStartTask(triggerX1Payload);
console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${triggerX1Payload}) Success. Return value: ` + result);
return "ok";
} catch (e) {
console.error("[!] Error trigger function during execution: " + e);
console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} with args: (${triggerX1Payload}), during execution: ` + e);
return "fail";
}
}
function AttachSendTextProto() {
Interceptor.attach(sendFuncAddr, {
onEnter: function (args) {
if (triggerX1Payload) {
return
}
// ReqBuf 进行拦截,替换入参数的消息指针
triggerX1Payload = this.context.x0;
console.log(`[+] 捕获到 MMStartTask 调用,Payload 地址: ${triggerX1Payload}`);
}
})
}
setImmediate(AttachSendTextProto);
// 拦截 SendTextProto 编码逻辑,注入自定义 Payload
function attachSendTextProto() {
console.log("[+] proto注入拦截目标地址: " + protobufAddr);
textProtoX1PayloadAddr = Memory.alloc(1024);
console.log("[+] Frida 分配的 Payload 地址: " + textProtoX1PayloadAddr);
Interceptor.attach(protobufAddr, {
onEnter: function (args) {
console.log("[+] Protobuf 拦截命中");
var sp = this.context.sp;
var firstValue = sp.readU32();
if (firstValue !== taskIdGlobal) {
console.log("[+] Protobuf 拦截未命中,跳过...");
return;
}
console.log(`[+] 正在注入 Protobuf Payload content: ${contentGlobal}, receiver: ${receiverGlobal}, atUser: ${atUserGlobal}`);
const type = [0x08, 0x01, 0x12]
const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length];
const receiverProto = stringToHexArray(receiverGlobal);
const contentProto = stringToHexArray(contentGlobal);
const contentHeader = [0x12, ...toVarint(contentProto.length)];
const tsHeader = [0x18, 0x01, 0x20];
const tsBytes = getVarintTimestampBytes();
const msgIdHeader = [0x28]
const msgId = generateRandom5ByteVarint()
const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E]
let atUserHeader = []
if (atUserGlobal) {
atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]).
concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E])
}
const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F,
0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31,
0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61,
0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F,
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72,
0x63, 0x65, 0x3E, 0x00]
const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length]
const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length +
contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length +
htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length)
// 合并数组
const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).
concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart).
concat(atUserHeader).concat(htmlLowerPart);
textProtoX1PayloadAddr.writeByteArray(finalPayload);
this.context.x1 = textProtoX1PayloadAddr;
this.context.x2 = ptr(finalPayload.length);
console.log("[+] 寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(textProtoX1PayloadAddr, {
offset: 0,
length: 128,
header: true,
ansi: true
}));
},
});
}
setImmediate(attachSendTextProto);
// -------------------------发送文本消息分区-------------------------
// -------------------------Req2Buf公共部分分区-------------------------
function attachReq2buf() {
console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr);
// 2. 开始拦截
@@ -300,33 +539,20 @@ function attachReq2buf() {
// 3. 获取 X24 寄存器的值
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, {
offset: 0,
length: 16,
header: true,
ansi: true
}));
if (typeof sendMessageAddr !== 'undefined') {
insertMsgAddr.writePointer(sendMessageAddr);
console.log("[+] 成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
console.log(hexdump(insertMsgAddr, {
offset: 0,
length: 16,
header: true,
ansi: true
}))
console.log(hexdump(sendMessageAddr, {
offset: 0,
length: 48,
header: true,
ansi: true
}))
if (typeof sendTextMessageAddr !== 'undefined') {
if (sendMsgType === "text") {
insertMsgAddr.writePointer(sendTextMessageAddr);
console.log("[+] 发送文本消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendTextMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
} else if (sendMsgType === "img") {
insertMsgAddr.writePointer(sendImgMessageAddr);
console.log("[+] 发送图片消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendImgMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
}
} else {
console.error("[!] 错误: 变量 sendMessageAddr 未定义,请确保已运行分配逻辑。");
console.error("[!] 错误: 变量 sendTextMessageAddr 未定义,请确保已运行分配逻辑。");
}
}
});
@@ -339,10 +565,12 @@ function attachReq2buf() {
return;
}
insertMsgAddr.writeU64(0x0);
console.log("[+] 0x1033EFA00 清空写入后内存预览: " + insertMsgAddr.readPointer());
console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer());
taskIdGlobal = 0;
receiverGlobal = "";
senderGlobal = "";
contentGlobal = "";
atUserGlobal = "";
send({
type: "finish",
})
@@ -352,230 +580,4 @@ function attachReq2buf() {
setImmediate(attachReq2buf);
// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes)
function getVarintTimestampBytes() {
let ts = Math.floor(Date.now() / 1000);
let encodedBytes = [];
let tempTs = ts >>> 0; // 强制转为 32位 无符号整数
while (true) {
let byte = tempTs & 0x7F;
tempTs >>>= 7;
if (tempTs !== 0) {
encodedBytes.push(byte | 0x80);
} else {
encodedBytes.push(byte);
break;
}
}
return encodedBytes;
}
function stringToHexArray(str) {
var utf8Str = unescape(encodeURIComponent(str));
var arr = [];
for (var i = 0; i < utf8Str.length; i++) {
arr.push(utf8Str.charCodeAt(i)); // 获取字符的 ASCII 码 (即十六进制值)
}
return arr;
}
function generateRandom5ByteVarint() {
let res = [];
// 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机
for (let i = 0; i < 4; i++) {
let random7Bit = Math.floor(Math.random() * 128);
res.push(random7Bit | 0x80); // 强制设置最高位为 1
}
// 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0
let lastByte = Math.floor(Math.random() * 127) + 1;
res.push(lastByte & 0x7F); // 确保最高位为 0
return res;
}
// 拦截 Protobuf 编码逻辑,注入自定义 Payload
function attachProto() {
console.log("[+] proto注入拦截目标地址: " + protobufAddr);
protoX1PayloadAddr = Memory.alloc(protoX1PayloadLen);
console.log("[+] Frida 分配的 Payload 地址: " + protoX1PayloadAddr);
Interceptor.attach(protobufAddr, {
onEnter: function (args) {
console.log("[+] Protobuf 拦截命中");
var sp = this.context.sp;
console.log("[+] Protobuf 拦截命中,SP: " + sp, hexdump(sp, {
offset: 0,
length: 16,
header: true,
ansi: true
}));
var firstValue = sp.readU32();
if (firstValue !== taskIdGlobal) {
console.log("[+] Protobuf 拦截未命中,跳过...");
return;
}
console.log("[+] 正在注入 Protobuf Payload...");
const type = [0x08, 0x01, 0x12]
const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length];
const receiverProto = stringToHexArray(receiverGlobal);
const contentProto = stringToHexArray(contentGlobal);
const contentHeader = [0x12, ...toVarint(contentProto.length)];
const tsHeader = [0x18, 0x01, 0x20];
const tsBytes = getVarintTimestampBytes();
const msgIdHeader = [0x28]
const msgId = generateRandom5ByteVarint()
const suffix = [
0x32, 0x32, 0x3C, // 0x28 头部
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x30 msgsour
0x63, 0x65, 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, // 0x38 ce><alno
0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31, // 0x40 de><fr>1
0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, // 0x48 </fr></a
0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F, // 0x50 lnode></
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x58 msgsour
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 = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix);
console.log("[+] Payload 准备写入");
protoX1PayloadAddr.writeByteArray(finalPayload);
console.log("[+] Payload 已写入,长度: " + finalPayload.length);
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: 128,
header: true,
ansi: true
}));
}
});
}
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("[+] setReceiver 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();
var selfId = x1.add(0x48).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";
}
}
var msgType = "private"
var groupId = ""
if (receiver.includes("@chatroom")) {
msgType = "group"
groupId = receiver
}
var parts = strD0.split('\u2005');
var messages = [];
for (let part of parts) {
if (part.startsWith("@")) {
messages.push({type: "at", data: {qq: selfId}});
} else {
messages.push({type: "text", data: {text: part}});
}
}
send({
message_type: msgType,
user_id: sender,
self_id: selfId,
group_id: groupId,
message_id: taskIdGlobal,
type: "send",
raw: {peerUid: receiver},
message: messages
})
},
});
}
// 使用 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
};
// -------------------------Req2Buf公共部分分区-------------------------
+95 -134
View File
@@ -82,61 +82,8 @@ function generateAESKey() {
return key;
}
function readVarint(addr) {
let value = 0;
let shift = 0;
let count = 0;
while (true) {
let byte = addr.add(count).readU8();
// 取低7位进行累加
value |= (byte & 0x7f) << shift;
count++; // 消耗了一个字节
// 如果最高位是0,跳出循环
if ((byte & 0x80) === 0) break;
shift += 7;
if (count > 5) return -1; // 安全校验,防止死循环
}
return {
value: value, // 最终长度数值 (例如 251)
byteLength: count // 长度字段占用的字节数 (例如 2)
};
}
function isPrintableOrChinese(startPtr, maxScanLength) {
let offset = 0;
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;
}
function getProtobufRawBytes(pBuffer, scanSize) {
const tags = [0x12, 0x1A, 0x2A, 0x52];
const tags = [0x12, 0x1A, 0x2A, 0x52, 0x5A];
let uint8Array;
try {
@@ -229,8 +176,7 @@ function getCleanString(uint8Array) {
out += String.fromCharCode(charCode);
}
}
}
else if ((c & 0xF8) === 0xF0 && i + 2 < len) {
} else if ((c & 0xF8) === 0xF0 && i + 2 < len) {
var c2 = uint8Array[i++];
var c3 = uint8Array[i++];
var c4 = uint8Array[i++];
@@ -249,39 +195,51 @@ function getCleanString(uint8Array) {
return out;
}
function generateBytes(n) {
// 生成随机字符串
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < n; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return stringToHexArray(result);
}
// -------------------------基础函数分区-------------------------
// -------------------------全局变量分区-------------------------
// 文本消息全局变量
var protobufAddr = baseAddr.add(0x227EC70);
var patchTextProtobufAddr = baseAddr.add(0x227EC4C);
var PatchTextProtobufDeleteAddr = baseAddr.add(0x227EC88);
var protobufAddr = baseAddr.add(0x66E7280);
var patchTextProtobufAddr = baseAddr.add(0x66E725C);
var PatchTextProtobufDeleteAddr = baseAddr.add(0x66E7298);
var textCgiAddr = ptr(0);
var sendTextMessageAddr = ptr(0);
var textMessageAddr = ptr(0);
var textProtoX1PayloadAddr = ptr(0);
var sendMessageCallbackFunc = ptr(0);
var messageCallbackFunc1 = baseAddr.add(0x7fa1050);
var messageCallbackFunc1 = ptr(0);
// 双方公共使用的地址
var triggerX1Payload = ptr(0x175ED6600);
var req2bufEnterAddr = baseAddr.add(0x34566C0);
var req2bufExitAddr = baseAddr.add(0x34577D8);
var sendFuncAddr = baseAddr.add(0x448A858);
var triggerX1Payload = ptr(0x173a5a600);
var req2bufEnterAddr = baseAddr.add(0x792EDC0);
var req2bufExitAddr = baseAddr.add(0x792FF78);
var sendFuncAddr = baseAddr.add(0x89E3540);
var insertMsgAddr = ptr(0);
var sendMsgType = "";
var buf2RespAddr = baseAddr.add(0x347BD44);
var buf2RespAddr = baseAddr.add(0x4568B58);
// 图片消息全局变量
var sendImgMessageCallbackFunc = ptr(0x0);
var imgMessageCallbackFunc1 = baseAddr.add(0x7fa0b18);
var imgProtobufAddr = baseAddr.add(0x2275BFC);
var patchImgProtobufFunc1 = baseAddr.add(0x2275BB8)
var patchImgProtobufFunc2 = baseAddr.add(0x2275BD8);
var imgProtobufDeleteAddr = baseAddr.add(0x2275C14);
var CndOnCompleteAddr = baseAddr.add(0x34154E0);
var sendImgMessageCallbackFunc = ptr(0);
var uploadImageAddr = baseAddr.add(0x45DC834);
var imgProtobufAddr = baseAddr.add(0x3317834);
var patchImgProtobufFunc1 = baseAddr.add(0x0)
var patchImgProtobufFunc2 = baseAddr.add(0x0);
var imgProtobufDeleteAddr = baseAddr.add(0x0);
var CndOnCompleteAddr = baseAddr.add(0x450195C);
var imgCgiAddr = ptr(0);
var sendImgMessageAddr = ptr(0);
@@ -306,6 +264,9 @@ var receiverGlobal = "wxid_"
var contentGlobal = "";
var senderGlobal = "wxid_"
var lastSendTime = 0;
var atUserGlobal = "";
const imageCp = generateBytes(16) // m30c4674f5a0b9d
// -------------------------全局变量分区-------------------------
@@ -383,7 +344,7 @@ setTimeout(function () {
patchTextProtoBuf();
}, 3000);
function triggerSendTextMessage(taskId, receiver, content) {
function triggerSendTextMessage(taskId, receiver, content, atUser) {
console.log("[+] Manual Trigger Started...");
if (!taskId || !receiver || !content) {
console.error("[!] taskId or Receiver or Content is empty!");
@@ -396,12 +357,12 @@ function triggerSendTextMessage(taskId, receiver, content) {
taskIdGlobal = taskId;
receiverGlobal = receiver;
contentGlobal = content;
atUserGlobal = atUser
console.log("taskIdGlobal: " + taskIdGlobal + ", receiverGlobal: " + receiverGlobal + ", contentGlobal: " + contentGlobal + ", atUserGlobal: " + atUserGlobal) ;
textMessageAddr.add(0x08).writeU32(taskIdGlobal);
sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal);
console.log("start init payload")
const payloadData = [
0x0A, 0x02, 0x00, 0x00, // 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08
@@ -468,12 +429,11 @@ function triggerSendTextMessage(taskId, receiver, content) {
try {
// const arg1 = globalMessagePtr; // 第一个指针参数
const arg2 = triggerX1Payload; // 第二个参数 0x175ED6600
console.log(`[+] Calling MMStartTask at ${sendFuncAddr} with args: (${arg2})`);
const result = MMStartTask(arg2);
console.log("[+] Execution MMStartTask Success. Return value: " + result);
console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${arg2}) Success. Return value: ` + result);
return "ok";
} catch (e) {
console.error("[!] Error trigger function during execution: " + e);
console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} during execution: ` + e);
return "fail";
}
}
@@ -494,7 +454,7 @@ function attachSendTextProto() {
console.log("[+] Protobuf 拦截未命中,跳过...");
return;
}
console.log("[+] 正在注入 Protobuf Payload...");
console.log(`[+] 正在注入 Protobuf Payload content: ${contentGlobal}, receiver: ${receiverGlobal}, atUser: ${atUserGlobal}`);
const type = [0x08, 0x01, 0x12]
const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length];
@@ -506,27 +466,32 @@ function attachSendTextProto() {
const msgIdHeader = [0x28]
const msgId = generateRandom5ByteVarint()
const suffix = [
0x32, 0x32, 0x3C, // 0x28 头部
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x30 msgsour
0x63, 0x65, 0x3E, 0x3C, 0x61, 0x6C, 0x6E, 0x6F, // 0x38 ce><alno
0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31, // 0x40 de><fr>1
0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61, // 0x48 </fr></a
0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F, // 0x50 lnode></
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, // 0x58 msgsour
0x63, 0x65, 0x3E, 0x00 // 0x60 ce>.
];
const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E]
let atUserHeader = []
if (atUserGlobal) {
atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]).
concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E])
}
const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F,
0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31,
0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61,
0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F,
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72,
0x63, 0x65, 0x3E, 0x00]
const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length]
const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length +
contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + suffix.length)
contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length +
htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length)
// 合并数组
const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(suffix);
const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).
concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart).
concat(atUserHeader).concat(htmlLowerPart);
console.log("[+] Payload 准备写入");
textProtoX1PayloadAddr.writeByteArray(finalPayload);
console.log("[+] Payload 已写入,长度: " + finalPayload.length);
this.context.x1 = textProtoX1PayloadAddr;
this.context.x2 = ptr(finalPayload.length);
@@ -594,6 +559,7 @@ function attachReq2buf() {
receiverGlobal = "";
senderGlobal = "";
contentGlobal = "";
atUserGlobal = "";
send({
type: "finish",
})
@@ -643,7 +609,7 @@ function setupSendImgMessageDynamic() {
}));
// C. 构建 Message 结构体
imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc1);
imgMessageAddr.add(0x00).writeU64(0x0);
imgMessageAddr.add(0x08).writeU32(taskIdGlobal);
imgMessageAddr.add(0x0c).writeU32(0x6e);
imgMessageAddr.add(0x10).writeU64(0x3);
@@ -799,25 +765,15 @@ function attachProto() {
onEnter: function (args) {
console.log("[+] Protobuf 拦截命中");
// var sp = this.context.sp;
// var firstValue = sp.readU32();
// if (firstValue !== taskIdGlobal) {
// console.log("[+] Protobuf 拦截未命中,跳过...");
// return;
// }
const type = [0x0A, 0x40, 0x0A, 0x01, 0x00]
const msgId = [0x10, 0xc6, 0xbc, 0x90, 0xb9, 0x08] // 时间戳
const msgId = [0x10].concat(generateRandom5ByteVarint())
const cpHeader = [0x1A, 0x10]
// m30c4674f5a0b9d
const cp = [0x6D, 0x33, 0x30, 0x63, 0x34, 0x36, 0x37, 0x34, 0x66, 0x35, 0x61, 0x30, 0x62, 0x39, 0x64, 0x30]
const randomId = [0x20, 0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01]
const sysHeader = [0x2A, 0x15]
// UnifiedPCMac 26 arm64
const sys = [0x55, 0x6E, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x43, 0x4D, 0x61, 0x63, 0x20, 0x32, 0x36, 0x20, 0x61, 0x72, 0x6D, 0x36, 0x34, 0x30]
// 45872025384@chatroom_176787000_60_xwechat_1 只需要改这个时间戳就能重复发送
const receiverMsgId = stringToHexArray(receiverGlobal).concat([0x5F])
.concat(stringToHexArray(Math.floor(Date.now() / 1000).toString()))
@@ -880,7 +836,7 @@ function attachProto() {
0xC8, 0x02, 0x00, 0x00 // 0x3E8
]
const finalPayload = type.concat(msgId, cpHeader, cp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId,
const finalPayload = type.concat(msgId, cpHeader, imageCp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId,
senderHeader, sender, receiverHeader, receiver, randomId1, type1, randomId2, randomId3, randomId4, htmlHeader, html,
cdnHeader, cdn, cdn2Header, cdn2, aesKeyHeader, aesKey, randomId5, cdn3Header, cdn3, randomId6, randomId7, randomId8,
aesKey1Header, aesKey1, md5Header, me5Key, randomId9, left0)
@@ -1007,9 +963,7 @@ function triggerUploadImg(receiver, md5, imagePath) {
uploadImagePayload.add(0x140).writePointer(ImagePathAddr1);
uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr);
const targetAddr = baseAddr.add(0x45DC834);
const startUploadMedia = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']);
const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']);
console.log("开始手动触发 C2C 上传...");
const result = startUploadMedia(uploadGlobalX0, uploadImagePayload);
@@ -1017,8 +971,7 @@ function triggerUploadImg(receiver, md5, imagePath) {
}
function attachUploadMedia() {
const targetAddr = baseAddr.add(0x45DC85C);
Interceptor.attach(targetAddr, {
Interceptor.attach(uploadImageAddr, {
onEnter: function (args) {
console.log("[+] enter UploadMedia");
uploadGlobalX0 = this.context.x0;
@@ -1103,10 +1056,11 @@ function setReceiver() {
const receiver = fields[1]
const content = fields[2]
const xml = fields[3]
const userContent = fields[4]
if (sender === "" || receiver === "" || content === "" || xml === "") {
console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, {
length: 128,
length: x2,
header: true,
ansi: true,
}))
@@ -1118,36 +1072,27 @@ function setReceiver() {
var groupId = ""
var senderUser = sender
var messages = [];
messages.push({type: "text", data: {text: content}});
var senderNickname = ""
let splitIndex = content.indexOf(':')
let pureContent = content.substring(splitIndex + 1).trim();
if (sender.includes("@chatroom")) {
msgType = "group"
groupId = sender
let splitIndex = -1;
for (let i = 0; i < content.length; i++) {
if (content[i] === ':') {
splitIndex = i;
break;
senderUser = content.substring(0, splitIndex).trim();
const parts = pureContent.split('\u2005');
for (let part of parts) {
part = part.trim();
if (!part.startsWith("@")) {
messages.push({type: "text", data: {text: part}});
}
}
const sendUserStart = content.indexOf('wxid_')
senderUser = content.substring(sendUserStart, splitIndex).trim();
messages = [];
const parts = content.split('\u2005');
for (let part of parts) {
part = part.trim();
if (!part.startsWith("@")) {
if (part.indexOf(":") !== -1) {
messages.push({type: "text", data: {text: part.substring(part.indexOf(':') + 1)}});
} else {
messages.push({type: "text", data: {text: part}});
}
}
}
const atUserMatch = xml.match(/<atuserlist>([\s\S]*?)<\/atuserlist>/);
const atUser = atUserMatch ? atUserMatch[1] : null;
if (atUser) {
@@ -1158,6 +1103,21 @@ function setReceiver() {
}
});
}
// 处理用户的名称
splitIndex = userContent.indexOf(':')
if (splitIndex === -1) {
splitIndex = userContent.indexOf('在群聊中@了你')
senderNickname = userContent.substring(0, splitIndex).trim();
} else {
senderNickname = userContent.substring(0, splitIndex).trim();
}
} else {
// 处理用户的名称
const splitIndex = userContent.indexOf(':')
senderNickname = userContent.substring(0, splitIndex).trim();
messages.push({type: "text", data: {text: pureContent}});
}
const msgId = generateAESKey()
@@ -1169,7 +1129,8 @@ function setReceiver() {
message_id: msgId,
type: "send",
raw: {peerUid: msgId},
message: messages
message: messages,
sender: {user_id: senderUser, nickname: senderNickname},
})
},
});
+3 -2
View File
@@ -1,2 +1,3 @@
pip install qiling
brew install capstone
使用 local ARM mac debug
sudo /Applications/IDA\ Professional\ 9.1.app/Contents/MacOS/ida
就能够attach
+1 -1
View File
@@ -102,7 +102,7 @@ func initFlag() {
flag.IntVar(&config.WechatPid, "wechat_pid", 0, "微信进程 ID: 58183, 仅当 type 为 local 时有效")
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_6_47_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json")
flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_7_31_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json")
flag.Parse()
+7 -10
View File
@@ -220,7 +220,7 @@ var sendTextMessageAddr = ptr(0);
var textMessageAddr = ptr(0);
var textProtoX1PayloadAddr = ptr(0);
var sendMessageCallbackFunc = ptr(0);
var messageCallbackFunc1 = baseAddr.add({{.messageCallbackFunc1}});
var messageCallbackFunc1 = ptr(0);
// 双方公共使用的地址
@@ -233,8 +233,8 @@ var sendMsgType = "";
var buf2RespAddr = baseAddr.add({{.buf2RespAddr}});
// 图片消息全局变量
var sendImgMessageCallbackFunc = ptr(0x0);
var imgMessageCallbackFunc1 = baseAddr.add({{.imgMessageCallbackFunc1}});
var sendImgMessageCallbackFunc = ptr(0);
var uploadImageAddr = baseAddr.add({{.uploadImageAddr}});
var imgProtobufAddr = baseAddr.add({{.imgProtobufAddr}});
var patchImgProtobufFunc1 = baseAddr.add({{.patchImgProtobufFunc1}})
var patchImgProtobufFunc2 = baseAddr.add({{.patchImgProtobufFunc2}});
@@ -433,7 +433,7 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) {
console.log(`[+] Execution MMStartTask ${sendFuncAddr} with args: (${arg2}) Success. Return value: ` + result);
return "ok";
} catch (e) {
console.error("[!] Error trigger function during execution: " + e);
console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} during execution: ` + e);
return "fail";
}
}
@@ -609,7 +609,7 @@ function setupSendImgMessageDynamic() {
}));
// C. 构建 Message 结构体
imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc1);
imgMessageAddr.add(0x00).writePointer();
imgMessageAddr.add(0x08).writeU32(taskIdGlobal);
imgMessageAddr.add(0x0c).writeU32(0x6e);
imgMessageAddr.add(0x10).writeU64(0x3);
@@ -963,9 +963,7 @@ function triggerUploadImg(receiver, md5, imagePath) {
uploadImagePayload.add(0x140).writePointer(ImagePathAddr1);
uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr);
const targetAddr = baseAddr.add(0x45DC834);
const startUploadMedia = new NativeFunction(targetAddr, 'int64', ['pointer', 'pointer']);
const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']);
console.log("开始手动触发 C2C 上传...");
const result = startUploadMedia(uploadGlobalX0, uploadImagePayload);
@@ -973,8 +971,7 @@ function triggerUploadImg(receiver, md5, imagePath) {
}
function attachUploadMedia() {
const targetAddr = baseAddr.add(0x45DC85C);
Interceptor.attach(targetAddr, {
Interceptor.attach(uploadImageAddr, {
onEnter: function (args) {
console.log("[+] enter UploadMedia");
uploadGlobalX0 = this.context.x0;
+2 -1
View File
@@ -13,5 +13,6 @@
"patchImgProtobufFunc1": "0x2275BB8",
"patchImgProtobufFunc2": "0x2275BD8",
"imgProtobufDeleteAddr": "0x2275C14",
"CndOnCompleteAddr": "0x34154E0"
"CndOnCompleteAddr": "0x34154E0",
"uploadImageAddr": "0x45DC834"
}
+17
View File
@@ -0,0 +1,17 @@
{
"protobufAddr": "0x32FB280",
"patchTextProtobufAddr": "0x32FB25C",
"PatchTextProtobufDeleteAddr": "0x32FB298",
"triggerX1Payload": "0x174E4A600",
"sendFuncAddr": "0x55F7540",
"req2bufEnterAddr": "0x4542DBC",
"req2bufExitAddr": "0x4543F78",
"imgProtobufAddr": "0x3317834",
"patchImgProtobufFunc1": "0x33177F0",
"patchImgProtobufFunc2": "0x3317810 ",
"imgProtobufDeleteAddr": "0x331784C",
"uploadImageAddr": "0x45DC834",
"CndOnCompleteAddr": "0x450195C",
"buf2RespAddr": "0x35F4B58"
}