Files
2026-07-10 09:28:22 +08:00

1325 lines
54 KiB
JavaScript
Raw Permalink 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.
var targetPath = "/Applications/WeChat.app/Contents/MacOS/WeChat";
var module = Process.enumerateModules().find(function(m) {
return m.path === targetPath;
});
if (!module) {
throw new Error("[-] Cannot find module: " + targetPath);
}
var moduleBase = module.base;
console.log("[+] WeChat module base: " + moduleBase);
// Enumerate readable ranges within 500MB from module base to search for "req2buf"
var searchSize = 1000 * 1024 * 1024;
var searchEnd = moduleBase.add(searchSize);
var _req2bufSearchAddr = null;
var baseAddr = null;
var ranges = Process.enumerateRanges("r--").filter(function(r) {
var rangeEnd = r.base.add(r.size);
return r.base.compare(searchEnd) < 0 && rangeEnd.compare(moduleBase) > 0;
});
console.log("[+] Found " + ranges.length + " readable ranges within 1000MB window");
var pending = ranges.length;
if (pending === 0) {
throw new Error("[-] No readable ranges found within 1000MB from module base");
}
ranges.forEach(function(r) {
Memory.scan(r.base, r.size, "72 65 71 32 62 75 66", {
onMatch: function(address, size) {
if (_req2bufSearchAddr === null) {
var rangeInfo = Process.findRangeByAddress(address);
if (rangeInfo) {
if (rangeInfo.size > 100 * 1024 * 1024) {
_req2bufSearchAddr = address;
console.log("[+] Range size > 100MB, accepted as base address");
}
}
}
},
onError: function(reason) {
// skip unreadable sub-pages
},
onComplete: function() {
pending--;
if (pending === 0) {
if (_req2bufSearchAddr === null) {
throw new Error("[-] Cannot find 'req2buf' keyword in a range > 100MB");
}
var foundRange = Process.findRangeByAddress(_req2bufSearchAddr);
baseAddr = foundRange.base;
console.log("[+] Base address from range: " + baseAddr);
console.log("[+] Range size: " + foundRange.size);
initAddresses();
}
}
});
});
function initAddresses() {
// 文本消息全局变量 (new_text.js approach)
blrX8Addr = baseAddr.add({{.blrX8Addr}});
autoBufferWriteFunc = baseAddr.add({{.autoBufferWriteFunc}});
// 双方公共使用的地址
req2bufEnterAddr = baseAddr.add({{.req2bufEnterAddr}});
req2bufExitAddr = baseAddr.add({{.req2bufExitAddr}});
sendFuncAddr = baseAddr.add({{.sendFuncAddr}});
buf2RespAddr = baseAddr.add({{.buf2RespAddr}});
uploadImageAddr = baseAddr.add({{.uploadImageAddr}});
cndOnCompleteAddr = baseAddr.add({{.cndOnCompleteAddr}});
uploadGetCallbackWrapperAddr = baseAddr.add({{.uploadGetCallbackWrapperAddr}});
uploadGetCallbackWrapperFuncAddr = baseAddr.add({{.uploadGetCallbackWrapperFuncAddr}});
uploadOnCompleteAddr = baseAddr.add({{.uploadOnCompleteAddr}});
uploadOnCompleteFuncAddr = baseAddr.add({{.uploadOnCompleteFuncAddr}});
downloadImagAddr = baseAddr.add({{.downloadImagAddr}});
startDownloadMedia = baseAddr.add({{.startDownloadMedia}});
downloadFileAddr = baseAddr.add({{.downloadFileAddr}});
downloadVideoAddr = baseAddr.add({{.downloadVideoAddr}});
sendMessageCallbackFunc = baseAddr.add(0x0);
imgMessageCallbackFunc = baseAddr.add(0x0);
videoMessageCallbackFunc = baseAddr.add(0x0);
replyMessageCallbackFunc = baseAddr.add(0x0);
voiceMessageCallbackFunc = baseAddr.add(0x0);
setupRetOneStub(); // 必须同步先执行,初始化fakeVtable
setImmediate(setupSendTextMessageDynamic);
setImmediate(setupSendFileMessageDynamic);
setImmediate(setupSendFileUploadMessageDynamic);
setImmediate(setupSendAppAttachMessageDynamic);
setImmediate(attachBlrX8Hook);
setImmediate(AttachSendFunc);
setImmediate(attachReq2buf);
setImmediate(setupSendImgMessageDynamic);
setImmediate(attachUploadMedia);
setImmediate(patchCdnOnComplete);
setImmediate(attachGetCallbackFromWrapper);
setImmediate(setupSendReplyMessageDynamic);
setImmediate(setupDownloadFileDynamic);
setImmediate(setReceiver);
}
// -------------------------基础函数分区-------------------------
function hexToByteArray(hexStr) {
var bytes = [];
for (var i = 0; i < hexStr.length; i += 2) {
bytes.push(parseInt(hexStr.substr(i, 2), 16));
}
return bytes;
}
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;
}
const MAX_FRIDA_MESSAGE_BYTES = 4 * 1024 * 1024;
function isReadablePointer(addr) {
try {
if (!addr || addr.isNull()) {
return false;
}
const range = Process.findRangeByAddress(addr);
return range !== null && range.protection.indexOf('r') !== -1;
} catch (e) {
return false;
}
}
function readPointerIfReadable(addr) {
try {
if (!isReadablePointer(addr)) {
return ptr(0);
}
const value = addr.readPointer();
if (!isReadablePointer(value)) {
return ptr(0);
}
return value;
} catch (e) {
return ptr(0);
}
}
function readUtf8StringIfReadable(addr) {
try {
if (!isReadablePointer(addr)) {
return "";
}
return addr.readUtf8String();
} catch (e) {
return "";
}
}
function readByteArrayIfReadable(addr, len) {
try {
if (len <= 0 || !isReadablePointer(addr)) {
return null;
}
return addr.readByteArray(len);
} catch (e) {
return null;
}
}
function sendDownloadChunks(dataPtr, dataLen, fileId, cdnUrl) {
if (!cdnUrl || dataLen <= 0) {
return;
}
if (dataLen > 0 && dataLen <= 10 * 1024 * 1024) {
var buffer = dataPtr.readByteArray(dataLen);
var uint8Array = new Uint8Array(buffer);
send({
type: "download",
media: Array.from(uint8Array),
file_id: fileId,
cdn_url: cdnUrl,
})
}
}
function fillUploadX1AndStart(idAddr, pathAddr, x1Buffer, receiver, md5, filePath, payloadHex) {
if (uploadGlobalX0.equals(ptr(0))) {
console.error("[!] uploadGlobalX0 尚未初始化,请等待 hook 捕获");
return "fail";
}
const payload = hexToByteArray(payloadHex);
patchString(idAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1");
patchString(md5Addr, md5);
patchString(uploadAesKeyAddr, generateAESKey());
patchString(pathAddr, filePath);
x1Buffer.writeByteArray(payload);
x1Buffer.writePointer(uploadFunc1Addr);
x1Buffer.add(0x08).writePointer(uploadFunc2Addr);
x1Buffer.add(0x48).writePointer(idAddr);
x1Buffer.add(0x68).writeUtf8String(receiver);
x1Buffer.add(0xa8).writePointer(md5Addr);
x1Buffer.add(0xe8).writePointer(pathAddr);
x1Buffer.add(0x118).writePointer(pathAddr);
x1Buffer.add(0x148).writePointer(pathAddr);
x1Buffer.add(0x200).writePointer(uploadAesKeyAddr);
const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']);
return startUploadMedia(uploadGlobalX0, x1Buffer);
}
// -------------------------基础函数分区-------------------------
// -------------------------全局变量分区-------------------------
// 文本消息全局变量 (new_text.js approach)
var blrX8Addr;
var autoBufferWriteFunc;
var textCgiAddr = ptr(0);
var sendTextMessageAddr = ptr(0);
var textMessageAddr = ptr(0);
var sendMessageCallbackFunc;
var retOneStub = ptr(0);
var fakeVtable = ptr(0);
var pendingInsertMsgAddr = ptr(0); // 等待buf2resp后清理的insertMsgAddr
var pendingSendMsgType = ""; // 等待buf2resp回调时使用的消息类型
var pendingBuf2RespTaskId = 0; // 等待buf2resp匹配的taskId
var textProtoDataAddr = ptr(0);
// 双方公共使用的地址
var triggerX1Payload;
var triggerX0;
var req2bufEnterAddr;
var req2bufExitAddr;
var sendFuncAddr;
var insertMsgAddr = ptr(0);
var sendMsgType = "";
var buf2RespAddr;
var uploadImageAddr;
var cndOnCompleteAddr;
var imgMessageCallbackFunc;
var videoMessageCallbackFunc;
var uploadGetCallbackWrapperAddr;
var uploadGetCallbackWrapperFuncAddr;
var uploadOnCompleteAddr;
var uploadOnCompleteFuncAddr;
var downloadImagAddr;
var startDownloadMedia;
var downloadFileAddr;
var downloadVideoAddr;
var downloadGlobalX0;
var downloadFileX1 = ptr(0)
var fileIdAddr = ptr(0)
var downloadAesKeyAddr = ptr(0)
var filePathAddr = ptr(0)
var fileCdnUrlAddr = ptr(0)
var uploadImageX1 = ptr(0);
var imgCgiAddr = ptr(0);
var sendImgMessageAddr = ptr(0);
var imgMessageAddr = 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 uploadCallback = ptr(0)
var videoCgiAddr = ptr(0);
var sendVideoMessageAddr = ptr(0);
var videoMessageAddr = ptr(0);
var uploadVideoX1 = ptr(0);
var videoIdAddr = ptr(0);
var videoPathAddr1 = ptr(0)
// 语音消息全局变量
var voiceMessageCallbackFunc;
var voiceCgiAddr = ptr(0);
var sendVoiceMessageAddr = ptr(0);
var voiceMessageAddr = ptr(0);
var uploadVoiceX1 = ptr(0);
var voiceIdAddr = ptr(0);
var voicePathAddr1 = ptr(0);
var voiceProtoHexGlobal = "";
var voiceDurationGlobal = 0;
var voiceSilkDataLenGlobal = 0;
var voiceAudioDataAddr = ptr(0);
// 发送消息的全局变量
var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复
// 文本消息protobuf全局变量 (从Go直接传入hex编码)
var textProtoHexGlobal = "";
// 图片消息protobuf全局变量 (从Go直接传入hex编码)
var imgProtoHexGlobal = "";
// 视频消息protobuf全局变量 (从Go直接传入hex编码)
var videoProtoHexGlobal = "";
// 回复消息protobuf全局变量 (从Go直接传入hex编码)
var replyProtoHexGlobal = "";
// 文件消息protobuf全局变量 (从Go直接传入hex编码)
var fileProtoHexGlobal = "";
var fileUploadProtoHexGlobal = "";
// uploadappattach protobuf全局变量 (从Go直接传入hex编码)
var appAttachProtoHexGlobal = "";
// 文件消息全局变量
var fileCgiAddr = ptr(0);
var sendFileMessageAddr = ptr(0);
var fileMessageAddr = ptr(0);
var uploadFileIdAddr = ptr(0);
var uploadFileX1 = ptr(0);
// sendfileuploadmsg 全局变量
var fileUploadCgiAddr = ptr(0);
var sendFileUploadMessageAddr = ptr(0);
var fileUploadMessageAddr = ptr(0);
// uploadappattach 全局变量
var appAttachCgiAddr = ptr(0);
var sendAppAttachMessageAddr = ptr(0);
var appAttachMessageAddr = ptr(0);
// 回复消息全局变量
var replyMessageCallbackFunc;
var replyCgiAddr = ptr(0);
var sendReplyMessageAddr = ptr(0);
var replyMessageAddr = ptr(0);
// -------------------------全局变量分区-------------------------
// -------------------------发送文本消息分区-------------------------
// 初始化进行内存的分配
function setupSendTextMessageDynamic() {
// 动态分配内存
textCgiAddr = Memory.alloc(128);
sendTextMessageAddr = Memory.alloc(256);
textMessageAddr = Memory.alloc(256);
textProtoDataAddr = Memory.alloc(64 * 1024); // 支持 50KB 分片(uploadappattach)的 protobuf
// A. 写入字符串内容
patchString(textCgiAddr, "/cgi-bin/micromsg-bin/newsendmsg");
// B. 构建 sendTextMessageAddr 结构体 (X24 基址位置)
sendTextMessageAddr.add(0x00).writeU64(0);
sendTextMessageAddr.add(0x08).writeU64(0);
sendTextMessageAddr.add(0x10).writeU64(0);
sendTextMessageAddr.add(0x18).writeU64(1);
sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendTextMessageAddr.add(0x28).writePointer(textMessageAddr);
// C. 构建 Message 结构体
textMessageAddr.add(0x00).writePointer(fakeVtable);
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"));
console.log("[+] Dynamic Text Message Setup Complete.");
}
// -------------------------发送文件消息分区-------------------------
function setupSendFileMessageDynamic() {
fileCgiAddr = Memory.alloc(128);
sendFileMessageAddr = Memory.alloc(256);
fileMessageAddr = Memory.alloc(256);
uploadFileIdAddr = Memory.alloc(128);
uploadFileX1 = Memory.alloc(1024);
patchString(uploadFileIdAddr, "file_upload_not_init");
patchString(fileCgiAddr, "/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(fakeVtable);
fileMessageAddr.add(0x08).writeU32(taskIdGlobal);
fileMessageAddr.add(0x0c).writeU32(0x6e);
fileMessageAddr.add(0x10).writeU64(0x3);
fileMessageAddr.add(0x18).writePointer(fileCgiAddr);
fileMessageAddr.add(0x20).writeU64(0x20);
fileMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
fileMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
}
function triggerSendFileMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "file");
}
function triggerUploadFile(receiver, md5, filePath, payloadHex) {
return fillUploadX1AndStart(uploadFileIdAddr, ImagePathAddr1, uploadFileX1, receiver, md5, filePath, payloadHex);
}
// -------------------------sendfileuploadmsg分区-------------------------
function setupSendFileUploadMessageDynamic() {
fileUploadCgiAddr = Memory.alloc(128);
sendFileUploadMessageAddr = Memory.alloc(256);
fileUploadMessageAddr = Memory.alloc(256);
patchString(fileUploadCgiAddr, "/cgi-bin/micromsg-bin/sendfileuploadmsg");
sendFileUploadMessageAddr.add(0x00).writeU64(0);
sendFileUploadMessageAddr.add(0x08).writeU64(0);
sendFileUploadMessageAddr.add(0x10).writeU64(0);
sendFileUploadMessageAddr.add(0x18).writeU64(1);
sendFileUploadMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendFileUploadMessageAddr.add(0x28).writePointer(fileUploadMessageAddr);
fileUploadMessageAddr.add(0x00).writePointer(fakeVtable);
fileUploadMessageAddr.add(0x08).writeU32(taskIdGlobal);
fileUploadMessageAddr.add(0x0c).writeU32(0x6e);
fileUploadMessageAddr.add(0x10).writeU64(0x3);
fileUploadMessageAddr.add(0x18).writePointer(fileUploadCgiAddr);
fileUploadMessageAddr.add(0x20).writeU64(0x20);
fileUploadMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
fileUploadMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
}
function triggerSendFileUploadMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "fileupload");
}
// -------------------------uploadappattach分区-------------------------
function setupSendAppAttachMessageDynamic() {
appAttachCgiAddr = Memory.alloc(128);
sendAppAttachMessageAddr = Memory.alloc(256);
appAttachMessageAddr = Memory.alloc(256);
patchString(appAttachCgiAddr, "/cgi-bin/micromsg-bin/uploadappattach");
sendAppAttachMessageAddr.add(0x00).writeU64(0);
sendAppAttachMessageAddr.add(0x08).writeU64(0);
sendAppAttachMessageAddr.add(0x10).writeU64(0);
sendAppAttachMessageAddr.add(0x18).writeU64(1);
sendAppAttachMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendAppAttachMessageAddr.add(0x28).writePointer(appAttachMessageAddr);
appAttachMessageAddr.add(0x00).writePointer(fakeVtable);
appAttachMessageAddr.add(0x08).writeU32(taskIdGlobal);
appAttachMessageAddr.add(0x0c).writeU32(0x6e);
appAttachMessageAddr.add(0x10).writeU64(0x3);
appAttachMessageAddr.add(0x18).writePointer(appAttachCgiAddr);
appAttachMessageAddr.add(0x20).writeU64(0x25);
appAttachMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
appAttachMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
}
function triggerUploadAppAttach(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "appattach");
}
// -------------------------发送文件消息分区-------------------------
// 创建一个只返回1的小函数stub
function setupRetOneStub() {
retOneStub = Memory.alloc(Process.pageSize);
Memory.patchCode(retOneStub, 8, code => {
// MOV W0, #1 = 0x52800020, RET = 0xD65F03C0 (little-endian)
code.writeByteArray([0x20, 0x00, 0x80, 0x52, 0xC0, 0x03, 0x5F, 0xD6]);
});
console.log("[+] Return-1 stub created at: " + retOneStub);
// 构造假vtable:所有槽位指向retOneStub,这样mars对我们伪造结构做虚调用时不会崩溃
fakeVtable = Memory.alloc(512);
for (var i = 0; i < 64; i++) {
fakeVtable.add(i * 8).writePointer(retOneStub);
}
console.log("[+] Fake vtable created at: " + fakeVtable);
}
function attachBlrX8Hook() {
console.log("[+] Hooking BLR X8 at: " + blrX8Addr);
var nativeAutoBufferWrite = new NativeFunction(autoBufferWriteFunc, 'int', ['pointer', 'pointer', 'int']);
Interceptor.attach(blrX8Addr, {
onEnter: function(args) {
var currentTaskId = this.context.x20.toUInt32();
if (currentTaskId !== taskIdGlobal) {
return;
}
console.log("[+] BLR X8 命中! taskId=" + currentTaskId + " sendMsgType=" + sendMsgType);
var autoBuffer = this.context.x1;
var protoHex = "";
if (sendMsgType === "text") {
protoHex = textProtoHexGlobal;
} else if (sendMsgType === "img") {
protoHex = imgProtoHexGlobal;
} else if (sendMsgType === "video") {
protoHex = videoProtoHexGlobal;
} else if (sendMsgType === "reply") {
protoHex = replyProtoHexGlobal;
} else if (sendMsgType === "file") {
protoHex = fileProtoHexGlobal;
} else if (sendMsgType === "fileupload") {
protoHex = fileUploadProtoHexGlobal;
} else if (sendMsgType === "appattach") {
protoHex = appAttachProtoHexGlobal;
} else if (sendMsgType === "voice") {
protoHex = voiceProtoHexGlobal;
}
if (!protoHex || protoHex.length === 0) {
console.error("[!] protoHex 为空, sendMsgType=" + sendMsgType);
return;
}
var finalPayload = hexToByteArray(protoHex);
textProtoDataAddr.writeByteArray(finalPayload);
// 调用 autoBufferWrite(autoBuffer, data, len) 填充 v133
nativeAutoBufferWrite(autoBuffer, textProtoDataAddr, finalPayload.length);
console.log("[+] autoBufferWrite 调用完成, protobuf长度: " + finalPayload.length);
// 将 X8 指向 retOneStub,这样 BLR X8 只会返回1,不执行原始逻辑
this.context.x8 = retOneStub;
}
});
}
function triggerSendTextMessage(taskId, receiver, content, atUser, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, "", receiver, protoHex, payloadHex, "text");
}
function AttachSendFunc() {
Interceptor.attach(sendFuncAddr.add(0x10), {
onEnter: function (args) {
if (triggerX1Payload) {
return
}
triggerX0 = this.context.x0;
triggerX1Payload = this.context.x1;
console.log(`[+] 捕获到 StartTask 调用,X0${triggerX0}, Payload: ${triggerX1Payload}`);
}
})
}
// -------------------------发送文本消息分区-------------------------
// -------------------------Req2Buf公共部分分区-------------------------
function attachReq2buf() {
Interceptor.attach(req2bufEnterAddr, {
onEnter: function (args) {
if (!this.context.x1.equals(taskIdGlobal)) {
return;
}
const x24_base = this.context.x24;
insertMsgAddr = x24_base.add(0x60);
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 if (sendMsgType === "video") {
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);
} else if (sendMsgType === "voice") {
insertMsgAddr.writePointer(sendVoiceMessageAddr);
console.log("[+] 发送语音消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendVoiceMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
} else if (sendMsgType === "file") {
insertMsgAddr.writePointer(sendFileMessageAddr);
console.log("[+] 发送文件消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
} else if (sendMsgType === "fileupload") {
insertMsgAddr.writePointer(sendFileUploadMessageAddr);
console.log("[+] 发送fileUploadMsg成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileUploadMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
} else if (sendMsgType === "appattach") {
insertMsgAddr.writePointer(sendAppAttachMessageAddr);
console.log("[+] 发送uploadAppAttach成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendAppAttachMessageAddr +
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
}
}
});
// 在出口处拦截req2buf,记录insertMsgAddr等buf2resp回调后再清理
Interceptor.attach(req2bufExitAddr, {
onEnter: function (args) {
if (!this.context.x25.equals(taskIdGlobal)) {
return;
}
// 不立即清除insertMsgAddr,让mars能路由buf2resp回调
// 用fakeVtable保护结构体,防止中间被访问时崩溃
pendingInsertMsgAddr = insertMsgAddr;
pendingSendMsgType = sendMsgType;
pendingBuf2RespTaskId = taskIdGlobal;
taskIdGlobal = 0;
}
});
}
// -------------------------Req2Buf公共部分分区-------------------------
// -------------------------发送图片消息分区-------------------------
// 初始化进行内存的分配
function setupSendImgMessageDynamic() {
// 1. 动态分配内存块(按需分配大小)
// 分配原则:字符串给 64-128 字节,结构体按实际大小分配
imgCgiAddr = Memory.alloc(128);
sendImgMessageAddr = Memory.alloc(256);
imgMessageAddr = Memory.alloc(256);
uploadFunc1Addr = Memory.alloc(24);
uploadFunc2Addr = Memory.alloc(24);
uploadCallback = Memory.alloc(128);
imageIdAddr = Memory.alloc(256);
md5Addr = Memory.alloc(256);
uploadAesKeyAddr = Memory.alloc(256);
ImagePathAddr1 = Memory.alloc(256);
uploadImageX1 = Memory.alloc(1024);
// 图片数据写入
patchString(imgCgiAddr, "/cgi-bin/micromsg-bin/uploadmsgimg");
sendImgMessageAddr.add(0x00).writeU64(0);
sendImgMessageAddr.add(0x08).writeU64(0);
sendImgMessageAddr.add(0x10).writeU64(0);
sendImgMessageAddr.add(0x18).writeU64(1);
sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendImgMessageAddr.add(0x28).writePointer(imgMessageAddr);
imgMessageAddr.add(0x00).writePointer(fakeVtable);
imgMessageAddr.add(0x08).writeU32(taskIdGlobal);
imgMessageAddr.add(0x0c).writeU32(0x6e);
imgMessageAddr.add(0x10).writeU64(0x3);
imgMessageAddr.add(0x18).writePointer(imgCgiAddr);
imgMessageAddr.add(0x20).writeU64(0x22);
imgMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
imgMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
// 视频数据写入
videoCgiAddr = Memory.alloc(128);
sendVideoMessageAddr = Memory.alloc(256);
videoMessageAddr = Memory.alloc(256);
videoIdAddr = Memory.alloc(256);
videoPathAddr1 = Memory.alloc(256);
uploadVideoX1 = Memory.alloc(1024);
patchString(videoCgiAddr, "/cgi-bin/micromsg-bin/uploadvideo");
sendVideoMessageAddr.add(0x00).writeU64(0);
sendVideoMessageAddr.add(0x08).writeU64(0);
sendVideoMessageAddr.add(0x10).writeU64(0);
sendVideoMessageAddr.add(0x18).writeU64(1);
sendVideoMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendVideoMessageAddr.add(0x28).writePointer(videoMessageAddr);
videoMessageAddr.add(0x00).writePointer(fakeVtable);
videoMessageAddr.add(0x08).writeU32(taskIdGlobal);
videoMessageAddr.add(0x0c).writeU32(0x6e);
videoMessageAddr.add(0x10).writeU64(0x3);
videoMessageAddr.add(0x18).writePointer(videoCgiAddr);
videoMessageAddr.add(0x20).writeU64(0x21);
videoMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
videoMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
// 语音数据写入
voiceCgiAddr = Memory.alloc(128);
sendVoiceMessageAddr = Memory.alloc(256);
voiceMessageAddr = Memory.alloc(256);
voiceIdAddr = Memory.alloc(256);
voicePathAddr1 = Memory.alloc(256);
uploadVoiceX1 = Memory.alloc(1024);
voiceAudioDataAddr = Memory.alloc(5 * 1024 * 1024); // 预分配5MB
patchString(voiceCgiAddr, "/cgi-bin/micromsg-bin/uploadvoice");
sendVoiceMessageAddr.add(0x00).writeU64(0);
sendVoiceMessageAddr.add(0x08).writeU64(0);
sendVoiceMessageAddr.add(0x10).writeU64(0);
sendVoiceMessageAddr.add(0x18).writeU64(1);
sendVoiceMessageAddr.add(0x20).writeU32(taskIdGlobal);
sendVoiceMessageAddr.add(0x28).writePointer(voiceMessageAddr);
voiceMessageAddr.add(0x00).writePointer(fakeVtable);
voiceMessageAddr.add(0x08).writeU32(taskIdGlobal);
voiceMessageAddr.add(0x0c).writeU32(0x6e);
voiceMessageAddr.add(0x10).writeU64(0x3);
voiceMessageAddr.add(0x18).writePointer(voiceCgiAddr);
voiceMessageAddr.add(0x20).writeU64(0x21);
voiceMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
voiceMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
}
function triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, msgType) {
if (!taskId || !receiver) {
console.error("[!] " + msgType + ": taskId or receiver is empty!");
return "fail";
}
if (!triggerX0 || !triggerX1Payload) {
console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获");
return "fail";
}
var msgAddrInfo = {
"text": { messageAddr: textMessageAddr, sendMessageAddr: sendTextMessageAddr, cgiAddr: textCgiAddr, protoHexSetter: function(h) { textProtoHexGlobal = h; } },
"img": { messageAddr: imgMessageAddr, sendMessageAddr: sendImgMessageAddr, cgiAddr: imgCgiAddr, protoHexSetter: function(h) { imgProtoHexGlobal = h; } },
"video": { messageAddr: videoMessageAddr, sendMessageAddr: sendVideoMessageAddr, cgiAddr: videoCgiAddr, protoHexSetter: function(h) { videoProtoHexGlobal = h; } },
"reply": { messageAddr: replyMessageAddr, sendMessageAddr: sendReplyMessageAddr, cgiAddr: replyCgiAddr, protoHexSetter: function(h) { replyProtoHexGlobal = h; } },
"voice": { messageAddr: voiceMessageAddr, sendMessageAddr: sendVoiceMessageAddr, cgiAddr: voiceCgiAddr, protoHexSetter: function(h) { voiceProtoHexGlobal = h; } },
"file": { messageAddr: fileMessageAddr, sendMessageAddr: sendFileMessageAddr, cgiAddr: fileCgiAddr, protoHexSetter: function(h) { fileProtoHexGlobal = h; } },
"fileupload": { messageAddr: fileUploadMessageAddr, sendMessageAddr: sendFileUploadMessageAddr, cgiAddr: fileUploadCgiAddr, protoHexSetter: function(h) { fileUploadProtoHexGlobal = h; } },
"appattach": { messageAddr: appAttachMessageAddr, sendMessageAddr: sendAppAttachMessageAddr, cgiAddr: appAttachCgiAddr, protoHexSetter: function(h) { appAttachProtoHexGlobal = h; } },
};
var info = msgAddrInfo[msgType];
if (!info) {
console.error("[!] unknown msgType: " + msgType);
return "fail";
}
info.protoHexSetter(protoHex);
taskIdGlobal = taskId;
info.messageAddr.add(0x08).writeU32(taskIdGlobal);
info.sendMessageAddr.add(0x20).writeU32(taskIdGlobal);
const payloadData = hexToByteArray(payloadHex);
triggerX1Payload.writeByteArray(payloadData);
triggerX1Payload.add(0x18).writePointer(info.cgiAddr);
triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0));
triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198));
sendMsgType = msgType;
const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']);
try {
MMStartTask(triggerX0, triggerX1Payload);
return "1";
} catch (e) {
console.error("[!] Error trigger " + msgType + " MMStartTask: " + e);
return "fail";
}
}
function triggerSendImgMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "img");
}
function triggerSendVideoMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "video");
}
function triggerUploadImg(receiver, md5, imagePath, payloadHex) {
return fillUploadX1AndStart(imageIdAddr, ImagePathAddr1, uploadImageX1, receiver, md5, imagePath, payloadHex);
}
function triggerUploadVideo(receiver, md5, videoPath, payloadHex) {
return fillUploadX1AndStart(videoIdAddr, videoPathAddr1, uploadVideoX1, receiver, md5, videoPath, payloadHex);
}
function triggerUploadVoice(receiver, voicePath, payloadHex, audioDataHex, durationMs) {
if (uploadGlobalX0.equals(ptr(0))) {
console.error("[!] uploadGlobalX0 尚未初始化,请等待 hook 捕获");
return "fail";
}
voiceDurationGlobal = durationMs;
const payload = hexToByteArray(payloadHex);
// 解码音频二进制数据,写入预分配的5MB内存
const audioBytes = hexToByteArray(audioDataHex);
const audioLen = audioBytes.length;
voiceSilkDataLenGlobal = audioLen;
voiceAudioDataAddr.writeByteArray(audioBytes);
const voiceIdStr = receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1";
patchString(voiceIdAddr, voiceIdStr);
patchString(voicePathAddr1, voicePath);
uploadVoiceX1.writeByteArray(payload);
uploadVoiceX1.writePointer(uploadFunc1Addr);
uploadVoiceX1.add(0x08).writePointer(uploadFunc2Addr);
uploadVoiceX1.add(0x48).writePointer(voiceIdAddr);
uploadVoiceX1.add(0x50).writeU64(voiceIdStr.length);
uploadVoiceX1.add(0x58).writeU64(uint64("0x8000000000000000").add(voiceIdStr.length + 1));
uploadVoiceX1.add(0x68).writeUtf8String(receiver);
// 音频二进制数据: 0x100=指针, 0x108=长度, 0x110=容量(长度+1)|高位
uploadVoiceX1.add(0x100).writePointer(voiceAudioDataAddr);
uploadVoiceX1.add(0x108).writeU64(audioLen);
uploadVoiceX1.add(0x110).writeU64(uint64("0x8000000000000000").add(audioLen + 1));
const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']);
return startUploadMedia(uploadGlobalX0, uploadVoiceX1);
}
function attachUploadMedia() {
Interceptor.attach(uploadImageAddr.add(0x10), {
onEnter: function (args) {
uploadGlobalX0 = this.context.x0;
}
})
}
function patchCdnOnComplete() {
Interceptor.attach(cndOnCompleteAddr, {
onEnter: function (args) {
try {
const x2 = this.context.x2;
const currentFileId = x2.add(0x20).readPointer().readUtf8String();
const imageFileId = imageIdAddr.readUtf8String();
const videoFileId = videoIdAddr.readUtf8String();
const voiceFileId = voiceIdAddr.readUtf8String();
const fileUploadFileId = uploadFileIdAddr.readUtf8String();
if (currentFileId !== imageFileId && currentFileId !== videoFileId && currentFileId !== voiceFileId && currentFileId !== fileUploadFileId) {
console.log("[-] CndOnComplete x2: " + x2 + " currentFileId: " + currentFileId +
" imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId);
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("cndOnComplete x2: " + x2 + " cdnKey: " + cdnKey + " aesKey: " + aesKey + " md5Key: " + md5Key + " videoId: " + videoId + " targetId: " + targetId);
if (cdnKey !== "" && cdnKey != null && aesKey !== "" && aesKey != null) {
// 判断是语音、视频、文件还是图片
if (currentFileId === voiceFileId) {
// 语音
send({
type: "upload_voice_finish",
target_id: targetId,
cdn_key: cdnKey,
aes_key: aesKey,
voice_duration: voiceDurationGlobal,
silk_data_len: voiceSilkDataLenGlobal
});
} else if (currentFileId === fileUploadFileId) {
// 文件
var attachId = "@cdn_" + cdnKey + "_" + aesKey + "_1";
send({
type: "upload_file_finish",
target_id: targetId,
cdn_key: cdnKey,
aes_key: aesKey,
md5_key: md5Key,
attach_id: attachId,
file_upload_token: "",
overwrite_msg_id: ""
});
} else if (currentFileId === videoFileId) {
// 视频
send({
type: "upload_video_finish",
target_id: targetId,
cdn_key: cdnKey,
aes_key: aesKey,
md5_key: md5Key,
video_id: videoId
});
} else {
// 图片
send({
type: "upload_image_finish",
target_id: targetId,
cdn_key: cdnKey,
aes_key: aesKey,
md5_key: md5Key
});
}
} else {
console.error("cdnKey or aesKey 为空");
}
} catch (e) {
console.error("[-] CdnOnComplete error: " + e);
}
}
});
}
function attachGetCallbackFromWrapper() {
Interceptor.attach(uploadGetCallbackWrapperAddr, {
onEnter: function (args) {
try {
const tmpFileId = this.context.x1.readPointer().readUtf8String();
const imageFileId = imageIdAddr.readUtf8String();
const videoFileId = videoIdAddr.readUtf8String();
const voiceFileId = voiceIdAddr.readUtf8String();
const fileUploadFileId = uploadFileIdAddr.readUtf8String();
if (tmpFileId !== imageFileId && tmpFileId !== videoFileId && tmpFileId !== voiceFileId && tmpFileId !== fileUploadFileId) {
console.log("[+] GetCallbackFromWrapper tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId);
return
}
uploadCallback.add(0x10).writePointer(uploadGetCallbackWrapperFuncAddr);
this.context.x8 = uploadCallback;
} catch (e) {
console.error("[-] GetCallbackFromWrapper error: " + e);
}
}
})
Interceptor.attach(uploadOnCompleteAddr, {
onEnter: function (args) {
try {
const tmpFileId = this.context.x1.readPointer().readUtf8String();
const imageFileId = imageIdAddr.readUtf8String();
const videoFileId = videoIdAddr.readUtf8String();
const voiceFileId = voiceIdAddr.readUtf8String();
const fileUploadFileId = uploadFileIdAddr.readUtf8String();
if (tmpFileId !== imageFileId && tmpFileId !== videoFileId && tmpFileId !== voiceFileId && tmpFileId !== fileUploadFileId) {
console.log("[+] OnComplete tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId);
return
}
uploadCallback.add(0x30).writePointer(uploadOnCompleteFuncAddr);
this.context.x8 = uploadCallback;
} catch (e) {
console.error("[-] OnComplete error: " + e);
}
}
})
}
// -------------------------发送回复消息分区-------------------------
function setupSendReplyMessageDynamic() {
replyCgiAddr = Memory.alloc(128);
sendReplyMessageAddr = Memory.alloc(256);
replyMessageAddr = Memory.alloc(256);
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(fakeVtable);
replyMessageAddr.add(0x08).writeU32(taskIdGlobal);
replyMessageAddr.add(0x0c).writeU32(0x6e);
replyMessageAddr.add(0x10).writeU64(0x3);
replyMessageAddr.add(0x18).writePointer(replyCgiAddr);
replyMessageAddr.add(0x20).writeU64(0x20);
replyMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
replyMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
console.log("[+] Reply message setup complete. CgiAddr: " + replyCgiAddr + " SendAddr: " + sendReplyMessageAddr);
}
function triggerSendReplyMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "reply");
}
// -------------------------发送回复消息分区-------------------------
// -------------------------发送语音消息分区-------------------------
function triggerSendVoiceMessage(taskId, sender, receiver, protoHex, payloadHex) {
return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "voice");
}
// -------------------------发送语音消息分区-------------------------
rpc.exports = {
triggerSendImgMessage: triggerSendImgMessage,
triggerUploadImg: triggerUploadImg,
triggerSendTextMessage: triggerSendTextMessage,
triggerDownload: triggerDownload,
triggerUploadVideo: triggerUploadVideo,
triggerSendVideoMessage: triggerSendVideoMessage,
triggerSendReplyMessage: triggerSendReplyMessage,
triggerUploadVoice: triggerUploadVoice,
triggerSendVoiceMessage: triggerSendVoiceMessage,
triggerSendFileMessage: triggerSendFileMessage,
triggerSendFileUploadMessage: triggerSendFileUploadMessage,
triggerUploadFile: triggerUploadFile,
triggerUploadAppAttach: triggerUploadAppAttach,
};
// -------------------------发送图片消息分区-------------------------
// -------------------------接收消息分区-------------------------
function setupDownloadFileDynamic() {
downloadFileX1 = Memory.alloc(1624)
fileIdAddr = Memory.alloc(128)
downloadAesKeyAddr = Memory.alloc(128)
filePathAddr = Memory.alloc(256)
fileCdnUrlAddr = Memory.alloc(256)
}
function setReceiver() {
Interceptor.attach(buf2RespAddr, {
onEnter: function (args) {
// 通过 SP+0x140 读取当前 buf2resp 对应的 taskId
var respTaskId = this.context.sp.add(0x140).readS32();
const currentPtr = this.context.x20;
const x2 = this.context.x0.toInt32();
if (!isReadablePointer(currentPtr) || x2 < 4 || x2 > MAX_FRIDA_MESSAGE_BYTES) {
console.error("[-] buf2resp: pointer 不可读 或 x2 大小不正确, ptr=" + currentPtr + " x2=" + x2);
return;
}
// 判断是否是我们发送的消息的 ack
if (pendingBuf2RespTaskId !== 0 && respTaskId === pendingBuf2RespTaskId) {
// 清理 insertMsgAddr
if (!pendingInsertMsgAddr.isNull()) {
pendingInsertMsgAddr.writeU64(0x0);
console.log("[+] buf2resp: 已清理 insertMsgAddr, msgType=" + pendingSendMsgType + " taskId=" + respTaskId);
pendingInsertMsgAddr = ptr(0);
}
// 读取响应数据
var respData = x2 >= 4 && x2 <= MAX_FRIDA_MESSAGE_BYTES ? readByteArrayIfReadable(currentPtr, x2) : null;
if (respData) {
var bytes = new Uint8Array(respData);
console.log("[+] buf2resp: 收到响应, msgType=" + pendingSendMsgType + " taskId=" + respTaskId + " len=" + x2);
send({
type: "buf2resp",
msg_type: pendingSendMsgType,
data: Array.from(bytes),
});
}
pendingBuf2RespTaskId = 0;
pendingSendMsgType = "";
return
}
const mem = readByteArrayIfReadable(currentPtr, x2);
if (!mem) {
console.warn("[skip] protobuf_msg memory read failed, length=" + x2);
return;
}
const uint8Array = new Uint8Array(mem);
// 与已验证稳定的旧版本保持一致,只做最宽松的消息候选判断。
// 具体结构交给 Go 解析,宁可产生误判日志,也不要在 JS 层漏掉消息。
if (uint8Array[0] !== 0x08) {
return;
}
send({
type: "protobuf_msg",
data: Array.from(uint8Array),
})
},
});
Interceptor.attach(startDownloadMedia, {
onEnter: function (args) {
downloadGlobalX0 = this.context.x0;
var fileIDAddr = readPointerIfReadable(this.context.x1.add(0x40));
var fileId = readUtf8StringIfReadable(fileIDAddr);
if (!fileId || !isReadablePointer(this.context.x1.add(0xA0))) {
return;
}
const t = this.context.x1.add(0xA0).readU32()
if (t === 3) {
if (fileId.endsWith("_1")) {
this.context.x1.add(0xA0).writeU32(0x02);
}
if (fileId.endsWith("_31")) {
this.context.x1.add(0xA0).writeU32(0x04);
}
}
}
})
Interceptor.attach(downloadFileAddr, {
onEnter: function (args) {
var dataPtr = this.context.x22;
var dataLen = this.context.x2.toInt32();
var fileId = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2E0)));
var cdnUrl = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2F8)));
sendDownloadChunks(dataPtr, dataLen, fileId, cdnUrl);
}
});
Interceptor.attach(downloadImagAddr, {
onEnter: function (args) {
var dataPtr = this.context.x22;
var dataLen = this.context.x2.toInt32();
var fileId = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2E0)));
var cdnUrl = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2F8)));
sendDownloadChunks(dataPtr, dataLen, fileId, cdnUrl);
}
});
Interceptor.attach(downloadVideoAddr, {
onEnter: function (args) {
var dataPtr = readPointerIfReadable(this.context.x20.add(0x178));
var dataLen = this.context.x23.toInt32();
var fileId = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2E0)));
var cdnUrl = readUtf8StringIfReadable(readPointerIfReadable(this.context.x19.add(0x2F8)));
sendDownloadChunks(dataPtr, dataLen, fileId, cdnUrl);
}
});
}
// fileType: HdImage => 1,Image => 2, thumbImage => 3, Video => 4, File => 5,
function triggerDownload(receiver, cdnUrl, aesKey, filePath, fileType) {
if (!downloadGlobalX0) {
console.error("[!] downloadGlobalX0 尚未初始化,请等待 hook 捕获");
return "fail";
}
const downloadMediaPayload = [
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, // 0x10
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xB6, 0x4C, 0xFC, 0x0A, 0x00, 0x00, 0x00, // 0x40
0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x80, 0x10, 0x4B, 0xFA, 0x0A, 0x00, 0x00, 0x00, // 0x58
0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xF0, 0xB3, 0x4C, 0xFC, 0x0A, 0x00, 0x00, 0x00, // 0x70
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x60, 0xC4, 0x2D, 0xFE, 0x0A, 0x00, 0x00, 0x00, // 0x88
0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90
0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x98
0x03, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // 0xa0
0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, // 0xa8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xb0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd0
0x01, 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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x100
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x110
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // 0x128
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x148
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, 0x02, 0x00, 0x00, 0xAA, 0xAA, 0xAA, // 0x170
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // 0x180
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, // 0x1a0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xAA, 0xAA, 0xAA, 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, 0x22, 0x1A, 0xFE, 0x0A, 0x00, 0x00, 0x00, // 0x1d0
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, // 0x1f0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x200
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,
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,
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, // 0x288
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x4F, 0x56, 0xFC, 0x0A, 0x00, 0x00, 0x00, // 0x2c0
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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x300
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // 0x318
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, 0x0A, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x340
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, 0x0A, 0x00, 0x00, 0x00, // 0x378
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x0A, 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, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, // 0x3e0
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,
];
patchString(fileIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1");
patchString(fileCdnUrlAddr, cdnUrl)
patchString(downloadAesKeyAddr, aesKey)
patchString(filePathAddr, filePath);
downloadFileX1.writeByteArray(downloadMediaPayload);
downloadFileX1.add(0x40).writePointer(fileIdAddr);
downloadFileX1.add(0x58).writePointer(fileCdnUrlAddr);
downloadFileX1.add(0x70).writePointer(downloadAesKeyAddr);
downloadFileX1.add(0x88).writePointer(filePathAddr);
downloadFileX1.add(0xa0).writeU32(fileType);
const startDwMedia = new NativeFunction(startDownloadMedia, 'int64', ['pointer', 'pointer']);
return startDwMedia(downloadGlobalX0, downloadFileX1);
}
// -------------------------接收消息分区-------------------------