This commit is contained in:
yincong
2025-11-26 20:34:21 +08:00
parent 1b82e016c0
commit 377542197f
14 changed files with 848 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
/**
* wechat_method_finder.js
* 目标: 批量挂钩微信主程序 (WeChat.app) 中所有与消息/加密相关的 Objective-C 方法。
* 作用: 找到微信内部封装的消息发送、接收、加解密逻辑的“入口”类和方法。
*/
// 定义您认为可能包含消息处理或加解密逻辑的类名关键词
const TARGET_KEYWORDS = [
"Message", "Data", "Encry", "Crypt",
"Send", "Recv", "Net", "Session",
"Pack", "Unpack", "PB" // Protobuf相关的类
];
// -------------------------------------------------------------------
// 核心挂钩逻辑
// -------------------------------------------------------------------
function hookWeChatMethods() {
if (!ObjC.available) {
console.error("[-] Objective-C 运行时不可用,无法进行 ObjC 方法挂钩。");
return;
}
let hooksCount = 0;
const targetModule = Process.findModuleByName("WeChat"); // 仅关注主二进制文件
if (!targetModule) {
console.error("[-] 微信主模块 'WeChat' 未找到。");
return;
}
console.log(`[+] 目标模块: ${targetModule.name} (${targetModule.base})`);
console.log(`[+] 正在筛选包含关键词的 Objective-C 类: ${TARGET_KEYWORDS.join(', ')}...`);
// 遍历所有已加载的 Objective-C 类
Object.keys(ObjC.classes).forEach(className => {
// 确保类位于 'WeChat' 主模块内 (避免挂钩系统库)
const classPtr = ObjC.classes[className].handle;
if (!targetModule.base.le(classPtr) || !targetModule.base.add(targetModule.size).gt(classPtr)) {
return; // 跳过不在 WeChat 模块内的类
}
// 筛选包含关键词的类
const matchesKeyword = TARGET_KEYWORDS.some(keyword => className.includes(keyword));
if (matchesKeyword) {
const targetClass = ObjC.classes[className];
console.log(`\n[*** FOUND CLASS ***] ${className}`);
// 遍历并挂钩该类的所有方法 (包括实例方法和类方法)
[...targetClass.$methods].forEach(methodName => {
try {
const method = targetClass[methodName];
const methodSignature = (methodName.startsWith('+')) ? `[Class] ${methodName}` : `[Instance] ${methodName}`;
Interceptor.attach(method.implementation, {
onEnter: function (args) {
// 使用 this.className 和 this.methodName 存储信息以便 onLeave 使用
this.className = className;
this.methodName = methodName;
console.log(`\n${"~".repeat(80)}`);
console.log(`[CALL] Class: **${this.className}**`);
console.log(`[CALL] Method: **${methodSignature}**`);
// 打印回溯,这正是找到函数入口的关键
console.log("[ENTRY POINT] Call Stack (寻找更上层的业务逻辑入口):");
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n')
);
// 打印参数 (由于参数类型未知,我们只打印前几个指针)
// 注意:args[0] = self (this), args[1] = _cmd (selector)
console.log(`[ARGS] Argument 3 (args[2]): ${args[2]}`);
if (args.length > 3) {
console.log(`[ARGS] Argument 4 (args[3]): ${args[3]}`);
}
},
onLeave: function (retval) {
console.log(`[EXIT POINT] ${methodSignature} Returned: ${retval}`);
console.log("~".repeat(80));
}
});
hooksCount++;
} catch (e) {
// console.error(`Error hooking ${className}.${methodName}: ${e.message}`);
}
});
}
});
console.log(`[+] 总共挂钩了 ${hooksCount} 个方法。开始在微信中发送/接收消息。`);
}
// 启动挂钩
hookWeChatMethods();
+127
View File
@@ -0,0 +1,127 @@
// ======================
// 安全参数打印
// ======================
function safePrintArg(ptr) {
try {
if (!ptr || ptr.isNull()) return "<null>";
// 如果能包装成 ObjC 对象
try {
try {
let o = new ObjC.Object(args[0]);
console.log(o.$className);
desc = o.toString();
if (desc.length > 300) desc = desc.slice(0, 300) + "...<truncated>";
return `[${cls}] ${desc}`;
} catch (_) {
console.log("raw:", args[0]);
}
} catch (_) {
// 不能包装成 OC 对象,则返回 pointer 地址
return ptr.toString();
}
} catch (e) {
return `<print-error: ${e}>`;
}
}
// ======================
// Hook 方法工具
// ======================
function hookMethod(clsName, methodName) {
try {
const cls = ObjC.classes[clsName];
if (!cls) return;
const method = cls[methodName];
if (!method || !method.implementation) return;
console.log(`\n🔥 Hooking ${clsName} ${methodName}`);
Interceptor.attach(method.implementation, {
onEnter(args) {
console.log(`\n🚀 ${clsName} ${methodName} called`);
// 打印参数 x0~x5
for (let i = 0; i < 6; i++) {
try {
console.log(`arg[${i}]: ${safePrintArg(args[i])}`);
} catch (e) {
console.log(`arg[${i}]: <error ${e}>`);
}
}
},
onLeave(retval) {
console.log(`⬅️ return: ${safePrintArg(retval)}`);
}
});
} catch (e) {
console.log(`❌ Hook ${clsName} ${methodName} failed: ${e}`);
}
}
// ======================
// WeChat 消息加密关键入口列表
// ======================
const HOOK_TARGETS = [
["WCMessageWrap", "- protobufEncode"],
["WCMessageWrap", "- serialize"],
// 发送消息必走
["WCProtoBuf", "- encodeMessage:"],
["WCProtoBuf", "- data"],
// 尝试抓取 key/data
["MMEncryptMessage", "- encryptMessage:key:"],
["WCEncryptHelper", "- encrypt:withKey:"],
["WCEncryptHelper", "- encryptData:key:"],
// 发送消息必走路径
["WCMessageMgr", "- SendAppMsg:"],
["WCMessageMgr", "- SendTextMessage:"],
["WCMessageMgr", "- SendImageMessage:"],
// 底层 protobuf builder
["PBGeneratedMessage", "- data"],
["PBGeneratedMessage", "- serialize"],
// 经常出现的 encode 函数
["MMProtoBase", "- serialize"],
["MMProtoBase", "- encode"]
];
// ======================
// 挂钩所有关键点
// ======================
function hookAll() {
console.log("🚀 WeChat 上层加密 Hook 正在启动...\n");
HOOK_TARGETS.forEach(([cls, method]) => {
hookMethod(cls, method);
});
// 额外扫描所有类名包含 Encode 或 Message 的 class
console.log("\n🔍 自动扫描 Encode / Message 类...");
for (const name in ObjC.classes) {
if (!name.includes("Encode") && !name.includes("Message")) continue;
const cls = ObjC.classes[name];
const methods = cls.$ownMethods;
methods.forEach(m => {
if (m.includes("encode") || m.includes("Encrypt") || m.includes("serialize")) {
hookMethod(name, m);
}
});
}
console.log("\n🎉 Hook 完成,开始抓取 WeChat 消息明文 / proto / encrypt 信息...\n");
}
// ======================
if (ObjC.available) {
hookAll();
} else {
setTimeout(hookAll, 1000);
}
+103
View File
@@ -0,0 +1,103 @@
// ====== 配置:你想 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) {
try {
let bt = Thread.backtrace(
context,
Backtracer.FUZZY // 更稳定,遇到系统函数更不容易崩
).map(DebugSymbol.fromAddress)
.join("\n");
console.log("\n--- 调用堆栈 ---");
console.log(bt);
console.log("-----------------\n");
} catch (e) {
console.log("无法获取堆栈:" + e);
}
}
// ====== 主逻辑:Hook 偏移量函数 ======
function hook_wechat_internal_functions() {
const wechatModule = Process.findModuleByName("WeChat");
if (!wechatModule) {
console.error("❌ 找不到 WeChat 模块");
return;
}
const base = wechatModule.base;
console.log("📌 WeChat Base:", base);
WECHAT_OFFSETS.forEach(offset => {
const target = base.add(offset);
// 尝试符号化
let funcName = `WeChat!0x${offset.toString(16)}`;
try {
const sym = DebugSymbol.fromAddress(target);
if (sym && sym.name) funcName = sym.name;
} catch (_) {}
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}`);
}
});
}
// ====== 入口 ======
setImmediate(() => {
hook_wechat_internal_functions();
});
+45
View File
@@ -0,0 +1,45 @@
const mod = Process.getModuleByName("WeChat");
const realAddr = ptr("0x1057ee3a8").sub("0x100000000").add(mod.base);
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
console.log(`\n[+] arg${i} ${args[i]}`);
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
console.log(hexdump(args[i], {
offset: 0,
length: 128
}));
}
} catch (e) {
console.log("Enter Error:", e);
}
}
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
},
onLeave(retval) {
console.log("===== sub_105808800 LEAVE =====");
console.log("Return value:", retval);
try {
console.log("Return hexdump:");
console.log(hexdump(retval, {
offset: 0,
length: 128
}));
} catch (_) {
}
}
});
+89
View File
@@ -0,0 +1,89 @@
const keyword = "7.7";
const addrs = ["6000033DDE48"]
console.log(keyword);
console.log(addrs);
function memoGet(p) {
p = "0x" + p;
const idaAddr = ptr(p);
MemoryAccessMonitor.enable(
{
base: idaAddr,
size: 0x40 // buffer 大小
},
{
onAccess(details) {
console.log("Access by:", details.from);
attach(details.from)
console.log(hexdump(idaAddr, {length: 0x40}));
}
}
);
}
function attach(from) {
const realAddr = ptr(from);
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
console.log(`\n[+] arg${i} ${args[i]}`);
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
const buf = args[i].readByteArray(128)
if (!buf) {
continue;
}
let s = "";
const u8 = new Uint8Array(buf);
for (let b of u8) {
if (b >= 0x20 && b <= 0x7E) {
s += String.fromCharCode(b);
} else {
s += ".";
}
}
if (keyword === "" || s.includes(keyword)) {
console.log(`\n[+] arg${i} ${args[i]} ${s}`);
console.log(hexdump(args[i], {length: 128}));
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
return;
}
}
} catch (e) {
console.log("Enter Error:", e);
}
}
},
onLeave(retval) {
console.log("===== sub_105808800 LEAVE =====");
console.log("Return value:", retval);
try {
if (retval.compare(ptr("0x600000000000")) >= 0 && retval.compare(ptr("0x700000000000")) < 0 && retval.and(0x7).isNull()) {
console.log(hexdump(retval, {
offset: 0,
length: 40
}));
}
} catch (_) {
}
}
});
}
for (let addr of addrs) {
memoGet(addr);
}
+64
View File
@@ -0,0 +1,64 @@
const mod = Process.getModuleByName("WeChat");
const realAddr = ptr("0x105efadc0").sub("0x100000000").add(mod.base);
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
const buf = args[i].readByteArray(128)
if (!buf) {
continue;
}
let s = "";
const u8 = new Uint8Array(buf);
for (let b of u8) {
if (b >= 0x20 && b <= 0x7E) {
s += String.fromCharCode(b);
} else {
s += ".";
}
}
console.log(`\n[+] arg${i} ${args[i]} ${s}`);
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
// if (s.includes(".cc")) {
// console.log(`\n[+] arg${i} ${args[i]} ${s}`);
// console.log(hexdump(args[i], { length: 128 }));
// console.log(
// Thread.backtrace(this.context, Backtracer.ACCURATE)
// .map(DebugSymbol.fromAddress).join('\n'));
// return;
// }
}
} catch (e) {
console.log("Enter Error:", e);
}
}
},
onLeave(retval) {
// console.log("===== sub_105808800 LEAVE =====");
// console.log("Return value:", retval);
// try {
// console.log("Return hexdump:");
// console.log(hexdump(retval, {
// offset: 0,
// length: 128
// }));
// } catch (_) {
// }
}
});
+64
View File
@@ -0,0 +1,64 @@
const mod = Process.getModuleByName("WeChat");
const realAddr = ptr("0x10a1f083c");
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
const buf = args[i].readByteArray(128)
if (!buf) {
continue;
}
let s = "";
const u8 = new Uint8Array(buf);
for (let b of u8) {
if (b >= 0x20 && b <= 0x7E) {
s += String.fromCharCode(b);
} else {
s += ".";
}
}
console.log(`\n[+] arg${i} ${args[i]} ${s}`);
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
// if (s.includes(".cc")) {
// console.log(`\n[+] arg${i} ${args[i]} ${s}`);
// console.log(hexdump(args[i], { length: 128 }));
// console.log(
// Thread.backtrace(this.context, Backtracer.ACCURATE)
// .map(DebugSymbol.fromAddress).join('\n'));
// return;
// }
}
} catch (e) {
console.log("Enter Error:", e);
}
}
},
onLeave(retval) {
// console.log("===== sub_105808800 LEAVE =====");
// console.log("Return value:", retval);
// try {
// console.log("Return hexdump:");
// console.log(hexdump(retval, {
// offset: 0,
// length: 128
// }));
// } catch (_) {
// }
}
});
+36
View File
@@ -0,0 +1,36 @@
[
"sendmsg",
"newsendmsg",
"SendMsg",
"SendMsgFH"
].forEach(sig => {
let arr = DebugSymbol.findFunctionsMatching("*" + sig + "*");
arr.forEach(target => {
console.log("Hooking:", target);
Interceptor.attach(ptr(target), {
onEnter(args) {
console.log("🚀 Called:", target);
// 打印调用堆栈
console.log(
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress)
.join("\n")
);
// 打印第 1 个参数,一般就是 protobuf buffer
try {
console.log("arg0:", args[0]);
console.log(hexdump(args[0], { length: 256 }));
} catch(e){}
// 打印第 2 个参数(通常是长度)
console.log("arg1:", args[1]);
},
onLeave(retval) {
console.log("⬅️ return:", retval);
}
});
});
});
+43
View File
@@ -0,0 +1,43 @@
const libc = Process.getModuleByName("libSystem.B.dylib");
// 2. 拦截 memcpy
const memcpy_ptr = libc.findExportByName("memcpy");
if (memcpy_ptr) {
console.log("[+] Hooking memcpy.");
Interceptor.attach(memcpy_ptr, {
onEnter(args) {
// args[0] = destination (目标缓冲区)
// args[1] = source (源数据)
// args[2] = length
this.src = args[1];
this.len = args[2].toInt32();
},
onLeave(retval) {
// 只检查长度适中的数据块 (防止日志爆炸)
if (this.len > 5 && this.len < 500) {
try {
// 尝试将源数据读取为 C 字符串
const message = this.src.readCString();
if (message && message.length > 1) {
console.log(`\n======================================================`);
console.log(`[+] ⚠️ 明文捕获于 memcpy!`);
console.log(`[+] 长度: ${this.len} 字节`);
console.log(`[+] 消息内容 (ASCII): ${message}`);
// 打印 Hexdump 验证
console.log(`--- Hexdump 验证 ---`);
console.log(hexdump(this.src, { length: this.len > 128 ? 128 : this.len }));
console.log(`======================================================`);
}
} catch (e) {
// 忽略读取错误
}
}
}
});
} else {
console.log("[!] memcpy symbol not found.");
}
+13
View File
@@ -0,0 +1,13 @@
MemoryAccessMonitor.enable(
{
base: ptr("0x6000034c1000"),
size: 0x40 // buffer 大小
},
{
onAccess(details) {
console.log("Access by:", details.from);
console.log("Operation:", details.operation);
console.log(hexdump(idaAddr, { length: 0x40 }));
}
}
);
+17
View File
@@ -0,0 +1,17 @@
const idaAddr = ptr("0x6000025A96DA");
console.log("start monitor");
MemoryAccessMonitor.enable(
{
base: idaAddr,
size: 0x40 // buffer 大小
},
{
onAccess(details) {
console.log("0x6000029C6DF0 Details:", JSON.stringify(details));
// console.log("0x6000029C6DF0 Access by:", DebugSymbol.fromAddress(details.from));
console.log(hexdump(idaAddr, { length: 0x40 }));
}
}
);
+63
View File
@@ -0,0 +1,63 @@
const mod = Process.getModuleByName("WeChat");
const realAddr = ptr("0x1057ee3a8").sub("0x100000000").add(mod.base);
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
// console.log(`\n[+] arg${i} ${args[i]}`);
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
const buf = args[i].readByteArray(128)
if (!buf) {
continue;
}
let s = "";
const u8 = new Uint8Array(buf);
for (let b of u8) {
if (b >= 0x20 && b <= 0x7E) {
s += String.fromCharCode(b);
} else {
s += ".";
}
}
if (s.includes("3.3.3.3")) {
console.log(`\n[+] arg${i} ${args[i]}`);
console.log(hexdump(args[i], { length: 128 }));
args[i].add(0x18).writeUtf16String("5555");
console.log(hexdump(args[i], { length: 128 }));
// console.log(
// Thread.backtrace(this.context, Backtracer.ACCURATE)
// .map(DebugSymbol.fromAddress).join('\n'));
return;
}
}
} catch (e) {
console.log("Enter Error:", e);
}
}
},
onLeave(retval) {
// console.log("===== sub_105808800 LEAVE =====");
// console.log("Return value:", retval);
// try {
// console.log("Return hexdump:");
// console.log(hexdump(retval, {
// offset: 0,
// length: 128
// }));
// } catch (_) {
// }
}
});
+21
View File
@@ -0,0 +1,21 @@
defineHandler({
onEnter(log, args, state) {
log('ccaes_cbc_encrypt_mode() [libcorecrypto.dylib]');
try {
const bt = Thread.backtrace(
this.context,
Backtracer.ACCURATE
)
.map(DebugSymbol.fromAddress)
.join('\n');
log('--- Call Stack ---\n' + bt + '\n-------------------');
} catch (e) {
log('Error printing backtrace: ' + e);
}
},
onLeave(log, retval, state) {
}
});
+62
View File
@@ -0,0 +1,62 @@
const mod = Process.getModuleByName("WeChat");
const realAddr = ptr("0x1057ee3a8").sub("0x100000000").add(mod.base);
console.log("[+] Real Function Address:", realAddr);
Interceptor.attach(realAddr, {
onEnter(args) {
for (let i = 0; i < 10; i++) {
try {
if (args[i].isNull()) {
continue;
}
// console.log(`\n[+] arg${i} ${args[i]}`);
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0 && args[i].and(0x7).isNull()) {
const buf = args[i].readByteArray(128)
if (!buf) {
continue;
}
let s = "";
const u8 = new Uint8Array(buf);
for (let b of u8) {
if (b >= 0x20 && b <= 0x7E) {
s += String.fromCharCode(b);
} else {
s += ".";
}
}
if (s.includes("3.3.3.3")) {
console.log(`\n[+] arg${i} ${args[i]}`);
console.log(hexdump(args[i], { length: 128 }));
args[i].add(0x18).writeUtf16String("5555");
// console.log(
// Thread.backtrace(this.context, Backtracer.ACCURATE)
// .map(DebugSymbol.fromAddress).join('\n'));
return;
}
}
} catch (e) {
console.log("Enter Error:", e);
}
}
},
onLeave(retval) {
// console.log("===== sub_105808800 LEAVE =====");
// console.log("Return value:", retval);
// try {
// console.log("Return hexdump:");
// console.log(hexdump(retval, {
// offset: 0,
// length: 128
// }));
// } catch (_) {
// }
}
});