mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
splite file
This commit is contained in:
+58
-90
@@ -1,103 +1,71 @@
|
||||
// ====== 配置:你想 Hook 的 WeChat 函数偏移 ======
|
||||
const WECHAT_OFFSETS = [
|
||||
0x4565b2c,
|
||||
0x4566f1c,
|
||||
0x4564860,
|
||||
0x4591ff8,
|
||||
0x45d09cc,
|
||||
0x45cebbc,
|
||||
0x4591fa0,
|
||||
0x4581834,
|
||||
0x4581760,
|
||||
0x4387d08,
|
||||
0x4334e88,
|
||||
0x4328ebc,
|
||||
0x4384764,
|
||||
0x43811e8
|
||||
];
|
||||
|
||||
// ====== 统一安全打印函数 ======
|
||||
function safePrintRegister(args) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
try {
|
||||
console.log(`x${i}: ${args[i]}`);
|
||||
} catch (e) {
|
||||
console.log(`x${i}: <无法读取> (${e})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safePrintBacktrace(context) {
|
||||
function dumpMemoryToHex(ptr, size) {
|
||||
try {
|
||||
let bt = Thread.backtrace(
|
||||
context,
|
||||
Backtracer.FUZZY // 更稳定,遇到系统函数更不容易崩
|
||||
).map(DebugSymbol.fromAddress)
|
||||
.join("\n");
|
||||
// 1. 读取内存数据
|
||||
const buffer = ptr.readByteArray(size);
|
||||
const data = new Uint8Array(buffer);
|
||||
|
||||
console.log("\n--- 调用堆栈 ---");
|
||||
console.log(bt);
|
||||
console.log("-----------------\n");
|
||||
let output = "";
|
||||
let line = "";
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// 格式化为 0x00 形式
|
||||
const hex = "0x" + data[i].toString(16).padStart(2, '0').toUpperCase();
|
||||
line += hex;
|
||||
|
||||
// 如果不是最后一个元素,添加逗号和空格
|
||||
if (i < data.length - 1) {
|
||||
line += ", ";
|
||||
}
|
||||
|
||||
// 每 8 个字节输出一行
|
||||
if ((i + 1) % 8 === 0 || i === data.length - 1) {
|
||||
output += line + "\n";
|
||||
line = "";
|
||||
}
|
||||
}
|
||||
|
||||
console.log("==================== MEMORY DUMP ====================");
|
||||
console.log(output);
|
||||
console.log("=====================================================");
|
||||
|
||||
} catch (e) {
|
||||
console.log("无法获取堆栈:" + e);
|
||||
console.log("[-] Dump 失败: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 主逻辑:Hook 偏移量函数 ======
|
||||
function hook_wechat_internal_functions() {
|
||||
const wechatModule = Process.findModuleByName("WeChat");
|
||||
if (!wechatModule) {
|
||||
console.error("❌ 找不到 WeChat 模块");
|
||||
return;
|
||||
|
||||
|
||||
function generateRandom5ByteVarint() {
|
||||
let res = [];
|
||||
|
||||
// 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let random7Bit = Math.floor(Math.random() * 128);
|
||||
res.push(random7Bit | 0x80); // 强制设置最高位为 1
|
||||
}
|
||||
|
||||
const base = wechatModule.base;
|
||||
console.log("📌 WeChat Base:", base);
|
||||
// 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0
|
||||
let lastByte = Math.floor(Math.random() * 127) + 1;
|
||||
res.push(lastByte & 0x7F); // 确保最高位为 0
|
||||
|
||||
WECHAT_OFFSETS.forEach(offset => {
|
||||
const target = base.add(offset);
|
||||
return res;
|
||||
}
|
||||
|
||||
// 尝试符号化
|
||||
let funcName = `WeChat!0x${offset.toString(16)}`;
|
||||
try {
|
||||
const sym = DebugSymbol.fromAddress(target);
|
||||
if (sym && sym.name) funcName = sym.name;
|
||||
} catch (_) {}
|
||||
// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes)
|
||||
function getVarintTimestampBytes() {
|
||||
let ts = Math.floor(Date.now() / 1000);
|
||||
let encodedBytes = [];
|
||||
let tempTs = ts >>> 0; // 强制转为 32位 无符号整数
|
||||
|
||||
console.log(`\n🔧 准备 Hook: ${funcName} @ 0x${target}`);
|
||||
|
||||
try {
|
||||
Interceptor.attach(target, {
|
||||
onEnter(args) {
|
||||
console.log("\n==============================================");
|
||||
console.log(`🚀 进入函数: ${funcName}`);
|
||||
console.log(`📍 地址: 0x${target}`);
|
||||
|
||||
console.log("\n--- 🧩 寄存器参数 x0-x7 ---");
|
||||
safePrintRegister(args);
|
||||
|
||||
console.log("\n--- 🧵 调用堆栈 ---");
|
||||
safePrintBacktrace(this.context);
|
||||
|
||||
console.log("==============================================\n");
|
||||
},
|
||||
|
||||
onLeave(retval) {
|
||||
// 如果需要打印返回值,可打开:
|
||||
// console.log("返回值:", retval);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ 已 Hook: ${funcName}`);
|
||||
|
||||
} catch (e) {
|
||||
console.error(`❌ Hook 失败 @ 0x${target} :${e}`);
|
||||
while (true) {
|
||||
let byte = tempTs & 0x7F;
|
||||
tempTs >>>= 7;
|
||||
if (tempTs !== 0) {
|
||||
encodedBytes.push(byte | 0x80);
|
||||
} else {
|
||||
encodedBytes.push(byte);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ====== 入口 ======
|
||||
setImmediate(() => {
|
||||
hook_wechat_internal_functions();
|
||||
});
|
||||
}
|
||||
return encodedBytes;
|
||||
}
|
||||
+47
-257
@@ -16,7 +16,6 @@ var triggerFuncAddr = baseAddr.add(0x448A858);
|
||||
var req2bufEnterAddr = baseAddr.add(0x34566C0);
|
||||
var req2bufExitAddr = baseAddr.add(0x34577D8);
|
||||
var protobufAddr = baseAddr.add(0x2275BFC);
|
||||
var buf2RespAddr = baseAddr.add(0x347BD44);
|
||||
var patchProtobufFunc1 = baseAddr.add(0x2275BB8)
|
||||
var patchProtobufFunc2 = baseAddr.add(0x2275BD8);
|
||||
var protobufDeleteAddr = baseAddr.add(0x2275C14);
|
||||
@@ -49,7 +48,7 @@ var senderGlobal = "wxid_ldftuhe36izg19"
|
||||
var lastSendTime = 0;
|
||||
var globalImageCdnKey = "";
|
||||
var globalAesKey1 = "";
|
||||
var globalAesKey2 = "";
|
||||
var globalMd5Key = "";
|
||||
|
||||
// 打印消息的地址,便于查询问题
|
||||
function printAddr() {
|
||||
@@ -62,13 +61,6 @@ function printAddr() {
|
||||
console.log(" - triggerX1Payload: " + triggerX1Payload);
|
||||
}
|
||||
|
||||
// 辅助函数:写入 Hex 字符串
|
||||
function patchHex(addr, hexStr) {
|
||||
const bytes = hexStr.split(' ').map(h => parseInt(h, 16));
|
||||
addr.writeByteArray(bytes);
|
||||
addr.add(bytes.length).writeU8(0); // 终止符
|
||||
}
|
||||
|
||||
// 初始化进行内存的分配
|
||||
function setupSendMessageDynamic() {
|
||||
console.log("[+] Starting Dynamic Message Patching...");
|
||||
@@ -87,8 +79,8 @@ function setupSendMessageDynamic() {
|
||||
|
||||
|
||||
// A. 写入字符串内容
|
||||
patchHex(cgiAddr, "2f 63 67 69 2d 62 69 6e 2f 6d 69 63 72 6f 6d 73 67 2d 62 69 6e 2f 75 70 6c 6f 61 64 6d 73 67 69 6d 67");
|
||||
patchHex(contentAddr, " ");
|
||||
patchString(cgiAddr, "/cgi-bin/micromsg-bin/uploadmsgimg");
|
||||
patchString(contentAddr, " ");
|
||||
|
||||
// B. 构建 SendMessage 结构体 (X24 基址位置)
|
||||
sendMessageAddr.add(0x00).writeU64(0);
|
||||
@@ -127,13 +119,6 @@ function setupSendMessageDynamic() {
|
||||
|
||||
setImmediate(setupSendMessageDynamic);
|
||||
|
||||
// 辅助函数:写入 Hex 字符串
|
||||
function patchHex(addr, hexStr) {
|
||||
const bytes = hexStr.split(' ').map(h => parseInt(h, 16));
|
||||
addr.writeByteArray(bytes);
|
||||
addr.add(bytes.length).writeU8(0); // 终止符
|
||||
}
|
||||
|
||||
|
||||
function patchProtoBuf() {
|
||||
Memory.patchCode(patchProtobufFunc1, 4, code => {
|
||||
@@ -174,8 +159,8 @@ function manualTrigger(taskId, sender, receiver) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
lastSendTime = timestamp
|
||||
taskIdGlobal = taskId;
|
||||
// receiverGlobal = receiver;
|
||||
// senderGlobal = sender;
|
||||
receiverGlobal = receiver;
|
||||
senderGlobal = sender;
|
||||
|
||||
messageAddr.add(0x08).writeU32(taskIdGlobal);
|
||||
sendMessageAddr.add(0x20).writeU32(taskIdGlobal);
|
||||
@@ -311,7 +296,8 @@ function attachReq2buf() {
|
||||
}
|
||||
insertMsgAddr.writeU64(0x0);
|
||||
console.log("[+] 清空写入后内存预览: " + insertMsgAddr.readPointer());
|
||||
// receiverGlobal = "";
|
||||
receiverGlobal = "";
|
||||
taskIdGlobal = 0;
|
||||
send({
|
||||
type: "finish",
|
||||
})
|
||||
@@ -321,24 +307,6 @@ function attachReq2buf() {
|
||||
|
||||
setImmediate(attachReq2buf);
|
||||
|
||||
// 辅助函数:Protobuf Varint 编码 (对应 get_varint_timestamp_bytes)
|
||||
function getVarintTimestampBytes() {
|
||||
let ts = Math.floor(Date.now() / 1000);
|
||||
let encodedBytes = [];
|
||||
let tempTs = ts >>> 0; // 强制转为 32位 无符号整数
|
||||
|
||||
while (true) {
|
||||
let byte = tempTs & 0x7F;
|
||||
tempTs >>>= 7;
|
||||
if (tempTs !== 0) {
|
||||
encodedBytes.push(byte | 0x80);
|
||||
} else {
|
||||
encodedBytes.push(byte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return encodedBytes;
|
||||
}
|
||||
|
||||
function stringToHexArray(str) {
|
||||
var utf8Str = unescape(encodeURIComponent(str));
|
||||
@@ -349,22 +317,6 @@ function stringToHexArray(str) {
|
||||
return arr;
|
||||
}
|
||||
|
||||
function generateRandom5ByteVarint() {
|
||||
let res = [];
|
||||
|
||||
// 前 4 个字节:最高位(bit 7)必须是 1,低 7 位随机
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let random7Bit = Math.floor(Math.random() * 128);
|
||||
res.push(random7Bit | 0x80); // 强制设置最高位为 1
|
||||
}
|
||||
|
||||
// 第 5 个字节:最高位必须是 0,为了确保不变成 4 字节,低 7 位不能全为 0
|
||||
let lastByte = Math.floor(Math.random() * 127) + 1;
|
||||
res.push(lastByte & 0x7F); // 确保最高位为 0
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// 拦截 Protobuf 编码逻辑,注入自定义 Payload
|
||||
function attachProto() {
|
||||
console.log("[+] proto注入拦截目标地址: " + protobufAddr);
|
||||
@@ -446,12 +398,11 @@ function attachProto() {
|
||||
const randomId8 = [0xC0, 0x01, 0x3A]
|
||||
const aesKey1Header = [0xCA, 0x01, 0x20]
|
||||
const aesKey1 = stringToHexArray(globalAesKey1)
|
||||
const aesKey2Header = [0xDA, 0x01, 0x20]
|
||||
const aesKey2 = stringToHexArray(globalAesKey2)
|
||||
const md5Header = [0xDA, 0x01, 0x20]
|
||||
const me5Key = stringToHexArray(globalMd5Key)
|
||||
|
||||
const randomId9 = [0xE0, 0x01, 0xd9, 0xe7, 0xc7, 0xF3, 0x02]
|
||||
|
||||
|
||||
var left0 = [
|
||||
0xF0, 0x01, 0x00, 0xA0, 0x02, 0x00, // 0x3E0
|
||||
0xC8, 0x02, 0x00, 0x00 // 0x3E8
|
||||
@@ -460,7 +411,7 @@ function attachProto() {
|
||||
const finalPayload = type.concat(msgId, cpHeader, cp, randomId, sysHeader, sys, msgIdHeader, receiverMsgId,
|
||||
senderHeader, sender, receiverHeader, receiver, randomId1, type1, randomId2, randomId3, randomId4, htmlHeader, html,
|
||||
cdnHeader, cdn, cdn2Header, cdn2, aesKeyHeader, aesKey, randomId5, cdn3Header, cdn3, randomId6, randomId7, randomId8,
|
||||
aesKey1Header, aesKey1, aesKey2Header, aesKey2, randomId9, left0)
|
||||
aesKey1Header, aesKey1, md5Header, me5Key, randomId9, left0)
|
||||
|
||||
console.log("[+] Payload 准备写入");
|
||||
// dumpMemoryToHex(this.context.x1, 1024)
|
||||
@@ -499,183 +450,6 @@ function toVarint(n) {
|
||||
return res;
|
||||
}
|
||||
|
||||
function setReceiver() {
|
||||
console.log("[+] buf2RespAddr WeChat Base: " + baseAddr + "[+] Attaching to: " + buf2RespAddr);
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
const currentPtr = this.context.x1;
|
||||
let start = 0x1e;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
start = 0x1d;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let senderPtr = currentPtr.add(start + 1);
|
||||
let sender = senderPtr.readUtf8String(senderLen);
|
||||
|
||||
let receiverLenPtr = senderPtr.add(senderLen).add(3);
|
||||
let receiverLen = receiverLenPtr.readU8();
|
||||
let receiverStrPtr = receiverLenPtr.add(1);
|
||||
let receiver = receiverStrPtr.readUtf8String(receiverLen);
|
||||
|
||||
let contentLenPtr = receiverStrPtr.add(receiverLen).add(6);
|
||||
if (isPrintableOrChinese(contentLenPtr, 1)) {
|
||||
contentLenPtr = contentLenPtr.add(-1);
|
||||
}
|
||||
const contentLenValue = readVarint(contentLenPtr)
|
||||
let contentPtr = contentLenPtr.add(contentLenValue.byteLength);
|
||||
var content = contentPtr.readUtf8String(contentLenValue.value);
|
||||
|
||||
var selfId = receiver
|
||||
var msgType = "private"
|
||||
var groupId = ""
|
||||
var senderUser = sender
|
||||
var messages = [];
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
let splitIndex = -1;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] === ':') {
|
||||
splitIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
senderUser = content.substring(0, splitIndex).trim();
|
||||
content = content.substring(splitIndex + 2).trim();
|
||||
|
||||
messages = [];
|
||||
const parts = content.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (!part.startsWith("@")) {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
|
||||
const xmlPtr = contentPtr.add(contentLenValue.value).add(15);
|
||||
const xmlLenValue = readVarint(xmlPtr)
|
||||
const xml = xmlPtr.add(xmlLenValue.byteLength).readUtf8String(xmlLenValue.value);
|
||||
const atUserMatch = xml.match(/<atuserlist>([\s\S]*?)<\/atuserlist>/);
|
||||
const atUser = atUserMatch ? atUserMatch[1] : null;
|
||||
if (atUser) {
|
||||
messages.push({type: "at", data: {qq: atUser}});
|
||||
}
|
||||
}
|
||||
|
||||
send({
|
||||
message_type: msgType,
|
||||
user_id: senderUser, // 发送人的 ID
|
||||
self_id: selfId, // 接收人的 ID
|
||||
group_id: groupId, // 群 ID
|
||||
message_id: taskIdGlobal,
|
||||
type: "send",
|
||||
raw: {peerUid: taskIdGlobal},
|
||||
message: messages
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 setImmediate 确保在模块加载后执行
|
||||
setImmediate(setReceiver)
|
||||
|
||||
function readVarint(addr) {
|
||||
let value = 0;
|
||||
let shift = 0;
|
||||
let count = 0;
|
||||
|
||||
while (true) {
|
||||
let byte = addr.add(count).readU8();
|
||||
// 取低7位进行累加
|
||||
value |= (byte & 0x7f) << shift;
|
||||
count++; // 消耗了一个字节
|
||||
|
||||
// 如果最高位是0,跳出循环
|
||||
if ((byte & 0x80) === 0) break;
|
||||
|
||||
shift += 7;
|
||||
if (count > 5) return -1; // 安全校验,防止死循环
|
||||
}
|
||||
|
||||
return {
|
||||
value: value, // 最终长度数值 (例如 251)
|
||||
byteLength: count // 长度字段占用的字节数 (例如 2)
|
||||
};
|
||||
}
|
||||
|
||||
function dumpMemoryToHex(ptr, size) {
|
||||
try {
|
||||
// 1. 读取内存数据
|
||||
const buffer = ptr.readByteArray(size);
|
||||
const data = new Uint8Array(buffer);
|
||||
|
||||
let output = "";
|
||||
let line = "";
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// 格式化为 0x00 形式
|
||||
const hex = "0x" + data[i].toString(16).padStart(2, '0').toUpperCase();
|
||||
line += hex;
|
||||
|
||||
// 如果不是最后一个元素,添加逗号和空格
|
||||
if (i < data.length - 1) {
|
||||
line += ", ";
|
||||
}
|
||||
|
||||
// 每 8 个字节输出一行
|
||||
if ((i + 1) % 8 === 0 || i === data.length - 1) {
|
||||
output += line + "\n";
|
||||
line = "";
|
||||
}
|
||||
}
|
||||
|
||||
console.log("==================== MEMORY DUMP ====================");
|
||||
console.log(output);
|
||||
console.log("=====================================================");
|
||||
|
||||
} catch (e) {
|
||||
console.log("[-] Dump 失败: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function isPrintableOrChinese(startPtr, maxScanLength) {
|
||||
let offset = 0;
|
||||
while (offset < maxScanLength) {
|
||||
let b = startPtr.add(offset).readU8();
|
||||
|
||||
if (b === 0) {
|
||||
// 扫描到 \0,且之前没有发现异常字节
|
||||
return offset > 0; // 如果第一个就是 \0,视为非字符串(可能是空指针)
|
||||
}
|
||||
|
||||
// 判定逻辑:
|
||||
// 1. 可见 ASCII (32-126) 或 换行/制表符 (9, 10, 13)
|
||||
let isAscii = (b >= 32 && b <= 126) || (b === 9 || b === 10 || b === 13);
|
||||
|
||||
// 2. 汉字 UTF-8 特征:第一个字节通常 >= 0x80 (128)
|
||||
// 严谨点:UTF-8 汉字首字节通常在 0xE4-0xE9 之间,后续字节在 0x80-0xBF 之间
|
||||
// 这里简化处理:如果是高位字符,我们暂时放行,由 readUtf8String 最终处理
|
||||
let isHighBit = (b >= 0x80);
|
||||
|
||||
if (!isAscii && !isHighBit) {
|
||||
// 发现既不是 ASCII 也不是高位字节(如 0x01-0x1F 的控制字符),判定为指针
|
||||
return false;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function patchCdnOnComplete() {
|
||||
Interceptor.attach(CndOnCompleteAddr, {
|
||||
onEnter: function (args) {
|
||||
@@ -685,8 +459,8 @@ function patchCdnOnComplete() {
|
||||
const x2 = this.context.x2;
|
||||
globalImageCdnKey = x2.add(0x60).readPointer().readUtf8String();
|
||||
globalAesKey1 = x2.add(0x78).readPointer().readUtf8String();
|
||||
globalAesKey2 = x2.add(0x90).readPointer().readUtf8String();
|
||||
console.log("[+] globalImageCdnKey: " + globalImageCdnKey + " globalAesKey1: " + globalAesKey1 + " globalAesKey2: " + globalAesKey2);
|
||||
globalMd5Key = x2.add(0x90).readPointer().readUtf8String();
|
||||
console.log("[+] globalImageCdnKey: " + globalImageCdnKey + " globalAesKey1: " + globalAesKey1 + " globalAesKey2: " + globalMd5Key);
|
||||
} catch (e) {
|
||||
console.log("[-] Memory access error at onEnter: " + e);
|
||||
}
|
||||
@@ -700,39 +474,29 @@ var uploadGlobalX0 = ptr(0)
|
||||
var uploadFunc1Addr = ptr(0)
|
||||
var uploadFunc2Addr = ptr(0)
|
||||
var imageIdAddr = ptr(0)
|
||||
var uploadAesKeyAddr1 = ptr(0)
|
||||
var uploadAesKeyAddr2 = ptr(0)
|
||||
var md5Addr = ptr(0)
|
||||
var uploadAesKeyAddr = ptr(0)
|
||||
var ImagePathAddr1 = ptr(0)
|
||||
var ImagePathAddr2 = ptr(0)
|
||||
var ImagePathAddr3 = ptr(0)
|
||||
var uploadImagePayload = ptr(0);
|
||||
|
||||
function initMemo() {
|
||||
uploadFunc1Addr = Memory.alloc(24);
|
||||
uploadFunc2Addr = Memory.alloc(24);
|
||||
imageIdAddr = Memory.alloc(256);
|
||||
uploadAesKeyAddr1 = Memory.alloc(256);
|
||||
uploadAesKeyAddr2 = Memory.alloc(256);
|
||||
md5Addr = Memory.alloc(256);
|
||||
uploadAesKeyAddr = Memory.alloc(256);
|
||||
ImagePathAddr1 = Memory.alloc(256);
|
||||
ImagePathAddr2 = Memory.alloc(256);
|
||||
ImagePathAddr3 = Memory.alloc(256);
|
||||
uploadImagePayload = Memory.alloc(512);
|
||||
|
||||
|
||||
uploadFunc1Addr.writePointer(baseAddr.add(0x802b8b0));
|
||||
uploadFunc2Addr.writePointer(baseAddr.add(0x7fd5908));
|
||||
patchHex(imageIdAddr, "77 78 69 64 5F 37 77 64 31 65 63 65 39 39 66 37 69 32 31 5F 31 37 36 38 32 37 34 36 36 37 5F 32 33 32 5F 31");
|
||||
patchHex(uploadAesKeyAddr1, "37 65 61 34 31 64 35 36 39 66 37 30 35 33 35 37 37 38 30 39 36 38 65 39 31 30 34 32 38 34 63 66")
|
||||
patchHex(uploadAesKeyAddr2, "65 63 64 35 37 65 39 63 66 38 35 66 32 65 32 30 38 37 61 65 65 38 63 30 66 64 31 65 34 34 35 65")
|
||||
patchHex(ImagePathAddr1, "2F 55 73 65 72 73 2F 79 69 6E 63 6F 6E 67 2F 4C 69 62 72 61 72 79 2F 43 6F 6E 74 61 69 6E 65 72 73 2F 63 6F 6D 2E 74 65 6E 63 65 6E 74 2E 78 69 6E 57 65 43 68 61 74 2F 44 61 74 61 2F 44 6F 63 75 6D 65 6E 74 73 2F 78 77 65 63 68 61 74 5F 66 69 6C 65 73 2F 77 78 69 64 5F 6C 64 66 74 75 68 65 33 36 69 7A 67 31 39 5F 35 65 37 64 2F 74 65 6D 70 2F 30 34 65 62 61 61 62 37 65 33 65 61 36 30 35 30 65 32 36 66 66 33 31 64 38 39 63 63 31 32 31 65 2F 32 30 32 36 2D 30 31 2F 49 6D 67 2F 32 33 32 5F 31 37 36 38 32 37 33 36 36 37 2E 6A 70 67");
|
||||
patchHex(ImagePathAddr2, "2F 55 73 65 72 73 2F 79 69 6E 63 6F 6E 67 2F 4C 69 62 72 61 72 79 2F 43 6F 6E 74 61 69 6E 65 72 73 2F 63 6F 6D 2E 74 65 6E 63 65 6E 74 2E 78 69 6E 57 65 43 68 61 74 2F 44 61 74 61 2F 44 6F 63 75 6D 65 6E 74 73 2F 78 77 65 63 68 61 74 5F 66 69 6C 65 73 2F 77 78 69 64 5F 6C 64 66 74 75 68 65 33 36 69 7A 67 31 39 5F 35 65 37 64 2F 74 65 6D 70 2F 30 34 65 62 61 61 62 37 65 33 65 61 36 30 35 30 65 32 36 66 66 33 31 64 38 39 63 63 31 32 31 65 2F 32 30 32 36 2D 30 31 2F 49 6D 67 2F 32 33 32 5F 31 37 36 38 32 37 33 36 36 37 2E 6A 70 67");
|
||||
patchHex(ImagePathAddr3, "2F 55 73 65 72 73 2F 79 69 6E 63 6F 6E 67 2F 4C 69 62 72 61 72 79 2F 43 6F 6E 74 61 69 6E 65 72 73 2F 63 6F 6D 2E 74 65 6E 63 65 6E 74 2E 78 69 6E 57 65 43 68 61 74 2F 44 61 74 61 2F 44 6F 63 75 6D 65 6E 74 73 2F 78 77 65 63 68 61 74 5F 66 69 6C 65 73 2F 77 78 69 64 5F 6C 64 66 74 75 68 65 33 36 69 7A 67 31 39 5F 35 65 37 64 2F 74 65 6D 70 2F 30 34 65 62 61 61 62 37 65 33 65 61 36 30 35 30 65 32 36 66 66 33 31 64 38 39 63 63 31 32 31 65 2F 32 30 32 36 2D 30 31 2F 49 6D 67 2F 32 33 32 5F 31 37 36 38 32 37 33 36 36 37 2E 6A 70 67");
|
||||
}
|
||||
|
||||
setImmediate(initMemo)
|
||||
|
||||
|
||||
function manualUpload() {
|
||||
function manualUpload(receiver, md5, imagePath) {
|
||||
|
||||
const payload = [
|
||||
0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 函数 10802b8b0 的指针
|
||||
@@ -819,15 +583,21 @@ function manualUpload() {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 0x288
|
||||
]
|
||||
|
||||
patchString(imageIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1");
|
||||
patchString(md5Addr, md5)
|
||||
patchString(uploadAesKeyAddr, generateAESKey())
|
||||
patchString(ImagePathAddr1, imagePath);
|
||||
|
||||
uploadImagePayload.writeByteArray(payload);
|
||||
uploadImagePayload.writePointer(uploadFunc1Addr);
|
||||
uploadImagePayload.add(0x08).writePointer(uploadFunc2Addr);
|
||||
uploadImagePayload.add(0x48).writePointer(imageIdAddr);
|
||||
uploadImagePayload.add(0xa8).writePointer(uploadAesKeyAddr1);
|
||||
uploadImagePayload.add(0x68).writeUtf8String(receiver);
|
||||
uploadImagePayload.add(0xa8).writePointer(md5Addr);
|
||||
uploadImagePayload.add(0xe0).writePointer(ImagePathAddr1);
|
||||
uploadImagePayload.add(0x110).writePointer(ImagePathAddr2);
|
||||
uploadImagePayload.add(0x140).writePointer(ImagePathAddr3);
|
||||
uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr2);
|
||||
uploadImagePayload.add(0x110).writePointer(ImagePathAddr1);
|
||||
uploadImagePayload.add(0x140).writePointer(ImagePathAddr1);
|
||||
uploadImagePayload.add(0x1f8).writePointer(uploadAesKeyAddr);
|
||||
|
||||
|
||||
const targetAddr = baseAddr.add(0x45DC834);
|
||||
@@ -852,6 +622,26 @@ function attachUploadMedia() {
|
||||
setImmediate(attachUploadMedia);
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
rpc.exports = {
|
||||
manualTrigger: manualTrigger,
|
||||
manualUpload: manualUpload
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
function setReceiver() {
|
||||
var buf2RespAddr = baseAddr.add(0x347BD44);
|
||||
console.log("[+] buf2RespAddr WeChat Base: " + baseAddr + "[+] Attaching to: " + buf2RespAddr);
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
|
||||
const currentPtr = this.context.x1;
|
||||
let start = 0x1e;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
start = 0x1d;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[+] Entered Receive Function: 0x1023B5348");
|
||||
|
||||
let senderPtr = currentPtr.add(start + 1);
|
||||
let sender = senderPtr.readUtf8String(senderLen);
|
||||
|
||||
let receiverLenPtr = senderPtr.add(senderLen).add(3);
|
||||
let receiverLen = receiverLenPtr.readU8();
|
||||
let receiverStrPtr = receiverLenPtr.add(1);
|
||||
let receiver = receiverStrPtr.readUtf8String(receiverLen);
|
||||
|
||||
let contentLenPtr = receiverStrPtr.add(receiverLen).add(6);
|
||||
// 判断是否等于 0x77 ('w'), 如果是,则是短字段
|
||||
if (isPrintableOrChinese(contentLenPtr, 1)) {
|
||||
contentLenPtr = contentLenPtr.add(-1);
|
||||
}
|
||||
const contentLenValue = readVarint(contentLenPtr)
|
||||
let contentPtr = contentLenPtr.add(contentLenValue.byteLength);
|
||||
var content = contentPtr.readUtf8String(contentLenValue.value);
|
||||
|
||||
var selfId = receiver
|
||||
var msgType = "private"
|
||||
var groupId = ""
|
||||
var senderUser = sender
|
||||
var messages = [];
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
let splitIndex = -1;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] === ':') {
|
||||
splitIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
senderUser = content.substring(0, splitIndex).trim();
|
||||
content = content.substring(splitIndex + 2).trim();
|
||||
|
||||
messages = [];
|
||||
const parts = content.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (!part.startsWith("@")) {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
|
||||
const xmlPtr = contentPtr.add(contentLenValue.value).add(15);
|
||||
const xmlLenValue = readVarint(xmlPtr)
|
||||
const xml = xmlPtr.add(xmlLenValue.byteLength).readUtf8String(xmlLenValue.value);
|
||||
const atUserMatch = xml.match(/<atuserlist>([\s\S]*?)<\/atuserlist>/);
|
||||
const atUser = atUserMatch ? atUserMatch[1] : null;
|
||||
if (atUser) {
|
||||
messages.push({type: "at", data: {qq: atUser}});
|
||||
}
|
||||
}
|
||||
|
||||
send({
|
||||
message_type: msgType,
|
||||
user_id: senderUser, // 发送人的 ID
|
||||
self_id: selfId, // 接收人的 ID
|
||||
group_id: groupId, // 群 ID
|
||||
message_id: generateAESKey(),
|
||||
type: "send",
|
||||
raw: {peerUid: generateAESKey()},
|
||||
message: messages
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 setImmediate 确保在模块加载后执行
|
||||
setImmediate(setReceiver)
|
||||
|
||||
function readVarint(addr) {
|
||||
let value = 0;
|
||||
let shift = 0;
|
||||
let count = 0;
|
||||
|
||||
while (true) {
|
||||
let byte = addr.add(count).readU8();
|
||||
// 取低7位进行累加
|
||||
value |= (byte & 0x7f) << shift;
|
||||
count++; // 消耗了一个字节
|
||||
|
||||
// 如果最高位是0,跳出循环
|
||||
if ((byte & 0x80) === 0) break;
|
||||
|
||||
shift += 7;
|
||||
if (count > 5) return -1; // 安全校验,防止死循环
|
||||
}
|
||||
|
||||
return {
|
||||
value: value, // 最终长度数值 (例如 251)
|
||||
byteLength: count // 长度字段占用的字节数 (例如 2)
|
||||
};
|
||||
}
|
||||
|
||||
function isPrintableOrChinese(startPtr, maxScanLength) {
|
||||
let offset = 0;
|
||||
while (offset < maxScanLength) {
|
||||
let b = startPtr.add(offset).readU8();
|
||||
|
||||
if (b === 0) {
|
||||
// 扫描到 \0,且之前没有发现异常字节
|
||||
return offset > 0; // 如果第一个就是 \0,视为非字符串(可能是空指针)
|
||||
}
|
||||
|
||||
// 判定逻辑:
|
||||
// 1. 可见 ASCII (32-126) 或 换行/制表符 (9, 10, 13)
|
||||
let isAscii = (b >= 32 && b <= 126) || (b === 9 || b === 10 || b === 13);
|
||||
|
||||
// 2. 汉字 UTF-8 特征:第一个字节通常 >= 0x80 (128)
|
||||
// 严谨点:UTF-8 汉字首字节通常在 0xE4-0xE9 之间,后续字节在 0x80-0xBF 之间
|
||||
// 这里简化处理:如果是高位字符,我们暂时放行,由 readUtf8String 最终处理
|
||||
let isHighBit = (b >= 0x80);
|
||||
|
||||
if (!isAscii && !isHighBit) {
|
||||
// 发现既不是 ASCII 也不是高位字节(如 0x01-0x1F 的控制字符),判定为指针
|
||||
return false;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function generateAESKey() {
|
||||
const chars = 'abcdef0123456789';
|
||||
let key = '';
|
||||
for (let i = 0; i < 32; i++) {
|
||||
key += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
rpc.exports = {
|
||||
manualTrigger: manualTrigger
|
||||
};
|
||||
@@ -15,7 +15,6 @@ var triggerFuncAddr = baseAddr.add(0x448A858);
|
||||
var req2bufEnterAddr = baseAddr.add(0x34566C0);
|
||||
var req2bufExitAddr = baseAddr.add(0x34577D8);
|
||||
var protobufAddr = baseAddr.add(0x227EC70);
|
||||
var buf2RespAddr = baseAddr.add(0x347BD44);
|
||||
var patchProtobufAddr = baseAddr.add(0x227EC4C);
|
||||
var protobufDeleteAddr = baseAddr.add(0x227EC88);
|
||||
|
||||
@@ -30,7 +29,6 @@ var messageAddr = ptr(0);
|
||||
|
||||
var contentAddr = ptr(0);
|
||||
var insertMsgAddr = ptr(0);
|
||||
var receiverAddr = ptr(0)
|
||||
var protoX1PayloadAddr = ptr(0);
|
||||
var protoX1PayloadLen = 1024;
|
||||
|
||||
@@ -68,7 +66,6 @@ function setupSendMessageDynamic() {
|
||||
sendMessageAddr = Memory.alloc(256);
|
||||
messageAddr = Memory.alloc(256);
|
||||
contentAddr = Memory.alloc(255);
|
||||
receiverAddr = Memory.alloc(24);
|
||||
|
||||
|
||||
// A. 写入字符串内容
|
||||
@@ -414,154 +411,3 @@ function toVarint(n) {
|
||||
}
|
||||
|
||||
setImmediate(attachProto);
|
||||
|
||||
function setReceiver() {
|
||||
console.log("[+] buf2RespAddr WeChat Base: " + baseAddr + "[+] Attaching to: " + buf2RespAddr);
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
|
||||
const currentPtr = this.context.x1;
|
||||
let start = 0x1e;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
start = 0x1d;
|
||||
let senderLen = currentPtr.add(start).readU8();
|
||||
if (senderLen !== 0x14 && senderLen !== 0x13) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[+] Entered Receive Function: 0x1023B5348");
|
||||
|
||||
let senderPtr = currentPtr.add(start + 1);
|
||||
let sender = senderPtr.readUtf8String(senderLen);
|
||||
|
||||
let receiverLenPtr = senderPtr.add(senderLen).add(3);
|
||||
let receiverLen = receiverLenPtr.readU8();
|
||||
let receiverStrPtr = receiverLenPtr.add(1);
|
||||
let receiver = receiverStrPtr.readUtf8String(receiverLen);
|
||||
|
||||
let contentLenPtr = receiverStrPtr.add(receiverLen).add(6);
|
||||
// 判断是否等于 0x77 ('w'), 如果是,则是短字段
|
||||
if (isPrintableOrChinese(contentLenPtr, 1)) {
|
||||
contentLenPtr = contentLenPtr.add(-1);
|
||||
}
|
||||
const contentLenValue = readVarint(contentLenPtr)
|
||||
let contentPtr = contentLenPtr.add(contentLenValue.byteLength);
|
||||
var content = contentPtr.readUtf8String(contentLenValue.value);
|
||||
|
||||
var selfId = receiver
|
||||
var msgType = "private"
|
||||
var groupId = ""
|
||||
var senderUser = sender
|
||||
var messages = [];
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
let splitIndex = -1;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] === ':') {
|
||||
splitIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
senderUser = content.substring(0, splitIndex).trim();
|
||||
content = content.substring(splitIndex + 2).trim();
|
||||
|
||||
messages = [];
|
||||
const parts = content.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (!part.startsWith("@")) {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
|
||||
const xmlPtr = contentPtr.add(contentLenValue.value).add(15);
|
||||
const xmlLenValue = readVarint(xmlPtr)
|
||||
const xml = xmlPtr.add(xmlLenValue.byteLength).readUtf8String(xmlLenValue.value);
|
||||
const atUserMatch = xml.match(/<atuserlist>([\s\S]*?)<\/atuserlist>/);
|
||||
const atUser = atUserMatch ? atUserMatch[1] : null;
|
||||
if (atUser) {
|
||||
messages.push({type: "at", data: {qq: atUser}});
|
||||
}
|
||||
}
|
||||
|
||||
send({
|
||||
message_type: msgType,
|
||||
user_id: senderUser, // 发送人的 ID
|
||||
self_id: selfId, // 接收人的 ID
|
||||
group_id: groupId, // 群 ID
|
||||
message_id: taskIdGlobal,
|
||||
type: "send",
|
||||
raw: {peerUid: taskIdGlobal},
|
||||
message: messages
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 setImmediate 确保在模块加载后执行
|
||||
setImmediate(setReceiver)
|
||||
|
||||
function readVarint(addr) {
|
||||
let value = 0;
|
||||
let shift = 0;
|
||||
let count = 0;
|
||||
|
||||
while (true) {
|
||||
let byte = addr.add(count).readU8();
|
||||
// 取低7位进行累加
|
||||
value |= (byte & 0x7f) << shift;
|
||||
count++; // 消耗了一个字节
|
||||
|
||||
// 如果最高位是0,跳出循环
|
||||
if ((byte & 0x80) === 0) break;
|
||||
|
||||
shift += 7;
|
||||
if (count > 5) return -1; // 安全校验,防止死循环
|
||||
}
|
||||
|
||||
return {
|
||||
value: value, // 最终长度数值 (例如 251)
|
||||
byteLength: count // 长度字段占用的字节数 (例如 2)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
rpc.exports = {
|
||||
manualTrigger: manualTrigger
|
||||
};
|
||||
|
||||
function isPrintableOrChinese(startPtr, maxScanLength) {
|
||||
let offset = 0;
|
||||
while (offset < maxScanLength) {
|
||||
let b = startPtr.add(offset).readU8();
|
||||
|
||||
if (b === 0) {
|
||||
// 扫描到 \0,且之前没有发现异常字节
|
||||
return offset > 0; // 如果第一个就是 \0,视为非字符串(可能是空指针)
|
||||
}
|
||||
|
||||
// 判定逻辑:
|
||||
// 1. 可见 ASCII (32-126) 或 换行/制表符 (9, 10, 13)
|
||||
let isAscii = (b >= 32 && b <= 126) || (b === 9 || b === 10 || b === 13);
|
||||
|
||||
// 2. 汉字 UTF-8 特征:第一个字节通常 >= 0x80 (128)
|
||||
// 严谨点:UTF-8 汉字首字节通常在 0xE4-0xE9 之间,后续字节在 0x80-0xBF 之间
|
||||
// 这里简化处理:如果是高位字符,我们暂时放行,由 readUtf8String 最终处理
|
||||
let isHighBit = (b >= 0x80);
|
||||
|
||||
if (!isAscii && !isHighBit) {
|
||||
// 发现既不是 ASCII 也不是高位字节(如 0x01-0x1F 的控制字符),判定为指针
|
||||
return false;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user