Files
wechat_chatter/frida/tmp_file.js
T
2026-03-25 09:53:00 +08:00

828 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 下载文件上传函数 - 基于视频上传,0x98位置从0x04改为0x05
// 获取微信主模块的基地址
var moduleName = "wechat.dylib";
var baseAddr = Process.findModuleByName(moduleName).base;
if (!baseAddr) {
console.error("[!] 找不到 WeChat 模块基址,请检查进程名。");
}
console.log("[+] WeChat base address: " + baseAddr);
// 全局变量
var uploadFileX1 = ptr(0);
var fileIdAddr = ptr(0);
var md5Addr = ptr(0)
var uploadAesKeyAddr = ptr(0);
var filePathAddr1 = ptr(0);
var uploadGlobalX0 = ptr(0);
var uploadFunc1Addr = ptr(0);
var uploadFunc2Addr = ptr(0);
var uploadCallback = ptr(0);
var uploadImageAddr = baseAddr.add(0x4ad95c0);
var uploadGetCallbackWrapperAddr = baseAddr.add(0x4aa7084);
var uploadGetCallbackWrapperFuncAddr = baseAddr.add(0x37C874C);
var uploadOnCompleteAddr = baseAddr.add(0x4AA7680);
var uploadOnCompleteFuncAddr = baseAddr.add(0x37C9930);
var cndOnCompleteAddr = baseAddr.add(0x37c8f00);
var fileCallbackFuncAddr = baseAddr.add(0x2544584);
var fileProtobufAddr = fileCallbackFuncAddr.add(0x50);
var patchFileProtobufFunc1 = fileCallbackFuncAddr.add(0x10);
var patchFileProtobufFunc1Byte;
var patchFileProtobufFunc2 = fileCallbackFuncAddr.add(0x30);
var patchFileProtobufFunc2Byte;
var fileProtobufDeleteAddr = fileCallbackFuncAddr.add(0x6c);
var fileProtobufDeleteAddrByte;
var fileMessageCallbackFunc1 = baseAddr.add(0x892DEF8)
var sendFuncAddr = baseAddr.add(0x4992040);
var req2bufEnterAddr = baseAddr.add(0x380b950);
var req2bufExitAddr = baseAddr.add(0x380CA64);
var fileCgiAddr2 = ptr(0);
var sendFileMessageAddr = ptr(0);
var fileMessageAddr = ptr(0);
var fileProtoX1PayloadAddr = ptr(0);
var triggerX1Payload;
var triggerX0;
var insertMsgAddr = ptr(0);
var sendMsgType = "";
var taskIdGlobal = 0x0
var receiverGlobal = "wxid_"
var contentGlobal = "";
var senderGlobal = "wxid_"
var lastSendTime = 0;
const fileCp = generateBytes(16)
// 文件上传完成队列
var fileUploadQueue = [];
function getFileUploadInfo() {
if (fileUploadQueue.length > 0) {
return fileUploadQueue.shift();
}
return null;
}
function pushFileUploadInfo(info) {
fileUploadQueue.push(info);
console.log("[+] 文件上传信息已入队,当前队列长度:", fileUploadQueue.length);
}
function setupUploadFileDynamic() {
fileIdAddr = Memory.alloc(256);
filePathAddr1 = Memory.alloc(256);
uploadFileX1 = Memory.alloc(1024);
uploadFunc1Addr = Memory.alloc(24);
uploadFunc2Addr = Memory.alloc(24);
uploadCallback = Memory.alloc(128);
md5Addr = Memory.alloc(128);
uploadAesKeyAddr = Memory.alloc(128);
// 发送文件消息相关内存分配
fileCgiAddr2 = Memory.alloc(128);
sendFileMessageAddr = Memory.alloc(256);
fileMessageAddr = Memory.alloc(256);
fileProtoX1PayloadAddr = Memory.alloc(4096);
triggerX1Payload = Memory.alloc(1024);
patchString(fileCgiAddr2, "/cgi-bin/micromsg-bin/sendappmsg");
sendFileMessageAddr.add(0x00).writeU64(0);
sendFileMessageAddr.add(0x08).writeU64(0);
sendFileMessageAddr.add(0x10).writeU64(0);
sendFileMessageAddr.add(0x18).writeU64(1);
sendFileMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendFileMessageAddr.add(0x28).writePointer(fileMessageAddr);
fileMessageAddr.add(0x00).writePointer(fileMessageCallbackFunc1);
fileMessageAddr.add(0x08).writeU32(taskIdGlobal);
fileMessageAddr.add(0x0c).writeU32(0x20a);
fileMessageAddr.add(0x10).writeU64(0x3);
fileMessageAddr.add(0x18).writePointer(fileCgiAddr2);
fileMessageAddr.add(0x20).writeU64(uint64("0x20"));
patchFileProtobufFunc1Byte = patchFileProtobufFunc1.readByteArray(4);
patchFileProtobufFunc2Byte = patchFileProtobufFunc2.readByteArray(4);
fileProtobufDeleteAddrByte = fileProtobufDeleteAddr.readByteArray(4);
console.log("[+] setupUploadFileDynamic Complete.");
}
setImmediate(setupUploadFileDynamic);
function triggerUploadFile(receiver, md5, filePath) {
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
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, 0x05, 0x00, 0x00, 0x00, // 0x98 ← 这里从0x04改成0x05
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 文件地址
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 文件地址
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 文件地址
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, 0x04, 0x00, 0xE0, 0x03, // 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
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, 0x01, 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
]
patchString(fileIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1");
patchString(md5Addr, md5)
patchString(uploadAesKeyAddr, generateAESKey())
patchString(filePathAddr1, filePath);
uploadFileX1.writeByteArray(payload);
uploadFileX1.writePointer(uploadFunc1Addr);
uploadFileX1.add(0x08).writePointer(uploadFunc2Addr);
uploadFileX1.add(0x48).writePointer(fileIdAddr);
uploadFileX1.add(0x68).writeUtf8String(receiver);
uploadFileX1.add(0xa8).writePointer(md5Addr);
uploadFileX1.add(0xe0).writePointer(filePathAddr1);
uploadFileX1.add(0x110).writePointer(filePathAddr1);
uploadFileX1.add(0x140).writePointer(filePathAddr1);
uploadFileX1.add(0x1f8).writePointer(uploadAesKeyAddr);
const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']);
console.log("开始手动触发文件上传 X0 " + uploadGlobalX0 + ", X1: " + uploadFileX1 + hexdump(uploadFileX1, {
offset: 0,
length: 256,
header: true,
ansi: true
}));
const result = startUploadMedia(uploadGlobalX0, uploadFileX1);
console.log("文件上传调用结果: " + result);
}
// -------------------------上传相关Attach逻辑-------------------------
function attachUploadFileMedia() {
Interceptor.attach(uploadImageAddr.add(0x10), {
onEnter: function (args) {
uploadGlobalX0 = this.context.x0;
const selfId = this.context.x1.add(0x68).readUtf8String();
const filePath = this.context.x1.add(0xe0).readPointer().readUtf8String();
send({
type: "upload",
self_id: selfId,
})
console.log("UploadFileMedia x0: " + uploadGlobalX0 + " filePath: " + filePath + " selfId: " + selfId);
}
})
}
setImmediate(attachUploadFileMedia);
function patchFileCdnOnComplete() {
Interceptor.attach(cndOnCompleteAddr, {
onEnter: function (args) {
try {
const x2 = this.context.x2;
const currentFileId = x2.add(0x20).readPointer().readUtf8String();
const fileId = fileIdAddr.readUtf8String();
if (currentFileId !== fileId) {
console.log("[-] FileCdnOnComplete x2: " + x2 + " currentFileId: " + currentFileId + " fileId: " + fileId);
return;
}
const cdnKey = x2.add(0x60).readPointer().readUtf8String();
const aesKey = x2.add(0x78).readPointer().readUtf8String();
const md5Key = x2.add(0x90).readPointer().readUtf8String();
const videoId = x2.add(0xf0).readPointer().readUtf8String();
const targetId = x2.add(0x40).readUtf8String();
console.log("File X2: " + x2 + "[+] cdnKey: " + cdnKey + " aesKey: " + aesKey +
" md5Key: " + md5Key + " videoId:" + videoId);
send({
type: "finish",
});
if (cdnKey !== "" && cdnKey != null && aesKey !== "" && aesKey != null &&
md5Key !== "" && md5Key != null) {
pushFileUploadInfo({
cdnKey: cdnKey,
aesKey: aesKey,
md5Key: md5Key,
targetId: targetId
});
send({
type: "upload_file_finish",
target_id: targetId,
cdn_key: cdnKey,
aes_key: aesKey,
md5_key: md5Key
});
} else {
console.error("cdnKey or aesKey or md5key 为空")
}
} catch (e) {
console.log("[-] File Memory access error at onEnter: " + e);
}
}
})
}
setImmediate(patchFileCdnOnComplete);
function attachFileGetCallbackFromWrapper() {
Interceptor.attach(uploadGetCallbackWrapperAddr, {
onEnter: function (args) {
const tmpFileId = this.context.x1.readPointer().readUtf8String();
const fileId = fileIdAddr.readUtf8String();
if (tmpFileId !== fileId) {
console.log("[+] File GetCallbackFromWrapper tmpFileId: " + tmpFileId + " fileId: " + fileId);
return;
}
uploadCallback.add(0x10).writePointer(uploadGetCallbackWrapperFuncAddr);
this.context.x8 = uploadCallback;
console.log("[+] File GetCallbackFromWrapper x8: " + this.context.x8);
}
})
Interceptor.attach(uploadOnCompleteAddr, {
onEnter: function (args) {
const tmpFileId = this.context.x1.readPointer().readUtf8String();
const fileId = fileIdAddr.readUtf8String();
if (tmpFileId !== fileId) {
console.log("[+] File OnComplete tmpFileId: " + tmpFileId + " fileId: " + fileId);
return;
}
uploadCallback.add(0x30).writePointer(uploadOnCompleteFuncAddr);
this.context.x8 = uploadCallback;
console.log("[+] File OnComplete x8: " + this.context.x8);
}
})
}
setImmediate(attachFileGetCallbackFromWrapper);
// -------------------------上传相关Attach逻辑结束-------------------------
// -------------------------Req2Buf公共部分分区-------------------------
function attachReq2buf() {
console.log("[+] Target Req2buf enter Address: " + req2bufEnterAddr);
Interceptor.attach(req2bufEnterAddr, {
onEnter: function (args) {
if (!this.context.x1.equals(taskIdGlobal)) {
return;
}
console.log("[+] 已命中目标Req2Buf taskId:" + taskIdGlobal + " base:" + baseAddr);
const x24_base = this.context.x24;
insertMsgAddr = x24_base.add(0x60);
console.log("[+] 当前 Req2Buf X24 基址: " + x24_base);
if (sendMsgType === "file") {
insertMsgAddr.writePointer(sendFileMessageAddr);
console.log("[+] 发送文件消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileMessageAddr +
" Req2Buf 写入后内存预览: " + insertMsgAddr);
}
}
});
console.log("[+] Target Req2buf leave Address: " + req2bufExitAddr);
Interceptor.attach(req2bufExitAddr, {
onEnter: function (args) {
if (!this.context.x25.equals(taskIdGlobal)) {
return;
}
insertMsgAddr.writeU64(0x0);
console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer());
taskIdGlobal = 0;
receiverGlobal = "";
senderGlobal = "";
contentGlobal = "";
send({
type: "finish",
})
}
});
}
setImmediate(attachReq2buf);
function AttachSendProto() {
Interceptor.attach(sendFuncAddr, {
onEnter: function (args) {
if (triggerX1Payload) {
return
}
triggerX0 = this.context.x0;
triggerX1Payload = this.context.x1;
console.log(`[+] 捕获到 StartTask 调用,X0地址:${triggerX0}, Payload 地址: ${triggerX1Payload}`);
}
})
}
setImmediate(AttachSendProto);
// -------------------------Req2Buf公共部分分区结束-------------------------
// -------------------------PatchFileProtoBuf分区-------------------------
function patchFileProtoBuf() {
Interceptor.attach(fileCallbackFuncAddr, {
onEnter: function (args) {
var firstValue = this.context.sp.add(0x10).readU32();
console.log("[+] 捕获到 FileCallbackFunc 调用,firstValue", firstValue, "taskIdGlobal", taskIdGlobal);
if (firstValue === taskIdGlobal) {
if (patchFileProtobufFunc1.readU32() !== 3573751839) {
Memory.patchCode(patchFileProtobufFunc1, 4, code => {
const cw = new Arm64Writer(code, {pc: patchFileProtobufFunc1});
cw.putNop();
cw.flush();
});
Memory.patchCode(patchFileProtobufFunc2, 4, code => {
const cw = new Arm64Writer(code, {pc: patchFileProtobufFunc2});
cw.putNop();
cw.flush();
});
Memory.patchCode(fileProtobufDeleteAddr, 4, code => {
const cw = new Arm64Writer(code, {pc: fileProtobufDeleteAddr});
cw.putNop();
cw.flush();
});
}
} else {
if (patchFileProtobufFunc1.readU32() === 3573751839) {
Memory.patchCode(patchFileProtobufFunc1, 4, code => {
const cw = new Arm64Writer(code, {pc: patchFileProtobufFunc1});
cw.putBytes(new Uint8Array(patchFileProtobufFunc1Byte));
cw.flush();
});
Memory.patchCode(patchFileProtobufFunc2, 4, code => {
const cw = new Arm64Writer(code, {pc: patchFileProtobufFunc2});
cw.putBytes(new Uint8Array(patchFileProtobufFunc2Byte));
cw.flush();
});
Memory.patchCode(fileProtobufDeleteAddr, 4, code => {
const cw = new Arm64Writer(code, {pc: fileProtobufDeleteAddr});
cw.putBytes(new Uint8Array(fileProtobufDeleteAddrByte));
cw.flush();
});
}
}
}
})
}
setImmediate(patchFileProtoBuf);
// -------------------------PatchFileProtoBuf分区结束-------------------------
// -------------------------发送文件消息分区-------------------------
function triggerSendFileMessage(taskId, sender, receiver) {
console.log("[+] File Manual Trigger Started...");
if (!taskId || !receiver || !sender) {
console.error("[!] taskId or receiver or sender is empty!");
return "fail";
}
const timestamp = Math.floor(Date.now() / 1000);
lastSendTime = timestamp
taskIdGlobal = taskId;
receiverGlobal = receiver;
senderGlobal = sender;
fileMessageAddr.add(0x08).writeU32(taskIdGlobal);
sendFileMessageAddr.add(0x20).writeU32(taskIdGlobal);
const payloadData = [
0x0A, 0x02, 0x00, 0x00, // 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08
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
0x00, 0x01, 0x01, 0x01, 0x00, 0xAA, 0xAA, 0xAA, // 0x30
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // 0x38
0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0xAA, 0xAA, // 0x48
0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, // 0x50
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58
0x0A, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60
0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x2D, // 0x68 default-
0x6C, 0x6F, 0x6E, 0x67, 0x6C, 0x69, 0x6E, 0x6B, // 0x70 longlink
0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x10, // 0x78
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x98
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0xB0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x100
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x108
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x110
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x118
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x120
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x128
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x130
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x138
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x140
0x01, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x148
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x150
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x158
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x160
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x168
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x170
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x178
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x180
0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 0x188
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x190
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x198
];
triggerX1Payload.writeU32(taskIdGlobal);
triggerX1Payload.add(0x04).writeByteArray(payloadData);
triggerX1Payload.add(0x18).writePointer(fileCgiAddr2);
triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0));
triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198));
sendMsgType = "file"
console.log("finished init file payload")
const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']);
try {
const result = MMStartTask(triggerX0, triggerX1Payload);
console.log(`[+] Execution StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}) Success. Return value: ` + result);
return "ok";
} catch (e) {
console.error(`[!] Error trigger StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e);
return "fail";
}
}
function attachFileProto() {
Interceptor.attach(fileProtobufAddr, {
onEnter: function (args) {
var currTaskId = this.context.sp.add(0x30).readU32();
if (currTaskId !== taskIdGlobal) {
console.log(`[+] 文件拦截到非目标 currTaskId: ${currTaskId} taskIdGlobal: ${taskIdGlobal}`);
return;
}
// 从文件队列获取上传信息
const fileUploadInfo = getFileUploadInfo();
let cdnKey = "";
let aesKey = "";
let md5Key = "";
let targetId = "";
let fileName = "";
let fileSize = "";
let appId = "";
let fileExt = "";
let fileUploadToken = "";
if (fileUploadInfo) {
cdnKey = fileUploadInfo.cdnKey;
aesKey = fileUploadInfo.aesKey;
md5Key = fileUploadInfo.md5Key;
targetId = fileUploadInfo.targetId;
fileName = fileUploadInfo.fileName || "";
fileSize = fileUploadInfo.fileSize || "0";
appId = fileUploadInfo.appId || "wx6618f1cfc6c132f8";
fileExt = fileUploadInfo.fileExt || "";
fileUploadToken = fileUploadInfo.fileUploadToken || "";
} else {
console.error("[!] 无法获取文件上传信息");
return;
}
// --- 构建 protobuf ---
// Field 1: Header (0x0A, len=0x40)
const type = [0x0A, 0x40, 0x0A, 0x01, 0x00]
const msgId = [0x10].concat(generateRandom5ByteVarint())
const cpHeader = [0x1A, 0x10]
const randomId = [0x20, 0x9D, 0xB0, 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]
const sysEnd = [0x30, 0xF8, 0x01]
// Field 2: Message body
// sender (field 1)
const senderBytes = stringToHexArray(senderGlobal);
const senderHeader = [0x0A, senderBytes.length];
// appid (field 2)
const appIdBytes = stringToHexArray(appId);
const appIdHeader = [0x12, appIdBytes.length];
// field 3 = 0
const field3 = [0x18, 0x00];
// receiver (field 4)
const receiverBytes = stringToHexArray(targetId);
const receiverHeader = [0x22, receiverBytes.length];
// type = 6 (file) (field 5)
const msgType = [0x28, 0x06];
// 构建 appmsg XML (field 6)
const cdnAttachUrl = cdnKey.replace(/_[^_]*$/, '');
const appmsgXml = '<appmsg appid="' + appId + '" sdkver="0">' +
'<title>' + fileName + '</title>' +
'<des></des>' +
'<action></action>' +
'<type>6</type>' +
'<showtype>0</showtype>' +
'<soundtype>0</soundtype>' +
'<mediatagname></mediatagname>' +
'<messageext></messageext>' +
'<messageaction></messageaction>' +
'<content></content>' +
'<contentattr>0</contentattr>' +
'<url></url>' +
'<lowurl></lowurl>' +
'<dataurl></dataurl>' +
'<lowdataurl></lowdataurl>' +
'<songalbumurl></songalbumurl>' +
'<songlyric></songlyric>' +
'<template_id></template_id>' +
'<appattach>' +
'<totallen>' + fileSize + '</totallen>' +
'<attachid>' + cdnKey + '</attachid>' +
'<emoticonmd5></emoticonmd5>' +
'<fileext>' + fileExt + '</fileext>' +
'<cdnattachurl>' + cdnAttachUrl + '</cdnattachurl>' +
'<aeskey>' + aesKey + '</aeskey>' +
'<encryver>0</encryver>' +
'<overwrite_newmsgid>' + Math.floor(Date.now() * 1000000 + Math.random() * 1000000).toString() + '</overwrite_newmsgid>' +
(fileUploadToken ? '<fileuploadtoken>' + fileUploadToken + '</fileuploadtoken>' : '') +
'</appattach>' +
'<extinfo></extinfo>' +
'<sourceusername></sourceusername>' +
'<sourcedisplayname></sourcedisplayname>' +
'<thumburl></thumburl>' +
'<md5>' + md5Key + '</md5>' +
'<statextstr></statextstr>' +
'</appmsg>';
const appmsgBytes = stringToHexArray(appmsgXml);
// fromusername 紧跟在 appmsg XML 后面
const fromUsernameXml = '<fromusername>' + senderGlobal + '</fromusername>';
const fromUsernameBytes = stringToHexArray(fromUsernameXml);
// 合并 appmsg content 和 fromusername
const contentBytes = appmsgBytes.concat(fromUsernameBytes);
const contentHeader = [0x32].concat(toVarint(contentBytes.length));
// timestamp (field 7)
const tsHeader = [0x38];
const tsBytes = getVarintTimestampBytes();
// msgid string (field 8): wxid_xxx_timestamp_3_xwechat_9
const receiverMsgId = stringToHexArray(targetId).concat([0x5F])
.concat(stringToHexArray(Math.floor(Date.now() / 1000).toString()))
.concat([0x5F, 0x33, 0x5F, 0x78, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5F, 0x39]); // _3_xwechat_9
const msgIdHeader2 = [0x42, receiverMsgId.length];
// field 10: varint = 1
const field10 = [0x50, 0x01];
// field 12: msgsource XML
const msgsourceXml = '<msgsource><alnode><fr>1</fr><cf>2</cf></alnode></msgsource>';
const msgsourceBytes = stringToHexArray(msgsourceXml);
const msgsourceHeader = [0x62, msgsourceBytes.length];
// empty fields
const field13 = [0x6A, 0x00];
const field14 = [0x72, 0x00];
const field15 = [0x7A, 0x00];
// 构建 field 2 内部数据
const field2Inner = senderHeader.concat(senderBytes, appIdHeader, appIdBytes, field3,
receiverHeader, receiverBytes, msgType, contentHeader, contentBytes,
tsHeader, tsBytes, msgIdHeader2, receiverMsgId, field10,
msgsourceHeader, msgsourceBytes, field13, field14, field15);
// 构建完整 field 2
const field2HeaderBytes = [0x12].concat(toVarint(field2Inner.length));
// md5 trailing field: 0x2A 0x20 + 32 bytes md5
const md5Trailing = [0x2A, 0x20].concat(stringToHexArray(md5Key));
// field 9: varint = 1
const field9 = [0x48, 0x01];
// field 10 (second): file size varint
const fileSizeVarint = toVarint(parseInt(fileSize));
const field10b = [0x50].concat(fileSizeVarint);
// field 11: varint = 2
const field11 = [0x58, 0x02];
// 构建最终 payload
const finalPayload = type.concat(msgId, cpHeader, fileCp, randomId, sysHeader, sys, sysEnd,
field2HeaderBytes, field2Inner,
md5Trailing, field9, field10b, field11);
fileProtoX1PayloadAddr.writeByteArray(finalPayload);
console.log("[+] 文件Payload 已写入,长度: " + finalPayload.length);
this.context.x1 = fileProtoX1PayloadAddr;
this.context.x2 = ptr(finalPayload.length);
console.log("[+] 文件寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(fileProtoX1PayloadAddr, {
offset: 0,
length: Math.min(finalPayload.length, 512),
header: true,
ansi: true
}));
},
});
}
setImmediate(attachFileProto);
// -------------------------发送文件消息分区结束-------------------------
rpc.exports = {
triggerUploadFile: triggerUploadFile,
triggerSendFileMessage: triggerSendFileMessage,
};
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 toVarint(n) {
let res = [];
while (n >= 128) {
res.push((n & 0x7F) | 0x80);
n = n >> 7;
}
res.push(n);
return res;
}
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 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);
}