mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
1325 lines
54 KiB
JavaScript
1325 lines
54 KiB
JavaScript
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);
|
||
}
|
||
|
||
// -------------------------接收消息分区-------------------------
|