mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
Update README.md
This commit is contained in:
@@ -1,224 +1,12 @@
|
||||
# weixin-macos
|
||||
使用frida进行逆向
|
||||
frida -f /Applications/WeChat.app/Contents/MacOS/WeChat -l script.js
|
||||
删除__handler__文件夹格外重要
|
||||
# WeChat 4.0 Message hook
|
||||

|
||||
|
||||
### 第一次尝试,根据关键字失败
|
||||
报错:Failed to attach: unable to access process with pid 43649 from the current user account
|
||||
重启,长按开关进入recovery模式,然后关闭安全模式
|
||||
hook1是触发函数,和用户回车行为一样,触发startTask。
|
||||
|
||||
frida-trace -p 进程号 -i "*Message*" --decorate 比较好用
|
||||
```
|
||||
frida-trace -p 10677 -i '*send*' -x '*objc_msgSend_noarg*' -x '*objc_msgSend_debug*' -x '*objc_msgSend*' -x "*_HIDisableSuddenTerminationForSendEvent*" -x "*_HIEnableSuddenTerminationForSendEvent*" -x "*SendEventToEventTarget*" -x "*s10RTCUtility10XPCMessageV4dictAA16RTCXPCDictionaryVvg*" -x "*MTLMessageContextEnd*" -x "*ictAA16RTCXPCDictionaryVvg*" -x "*_MTLMessageContextBegin_*" -x "*CFMachMessageCheckForAndDestroyUnsentMessag*" -x "*SLEventCopyAuthenticationMessage*" -x "*SendTextInputEvent_WithCompletionHandler*" -x '*mach_msg_send*' -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result*" --decorate --ui-port 60000
|
||||
```
|
||||
hook2是对Req2Buf这个函数进行消息体注入,因为hook1触发的时候我其实没有给消息体,但是我注入的这个消息体,在protobuf过程中一直失败,全是指针,根本看不懂。
|
||||
|
||||
加密函数:
|
||||
```
|
||||
nixiang % frida-trace -p 62349 -i '*encrypt*' -i "*Encrypt*" -x '*objc_msgSend_noarg*' -x '*objc_msgSend_debug*' -x '*objc_msgSend*' -x "*_HIDisableSuddenTerminationForSendEvent*" -x "*_HIEnableSuddenTerminationForSendEvent*" -x "*SendEventToEventTarget*" -x "*s10RTCUtility10XPCMessageV4dictAA16RTCXPCDictionaryVvg*" -x "*MTLMessageContextEnd*" -x "*ictAA16RTCXPCDictionaryVvg*" -x "*_MTLMessageContextBegin_*" -x "*CFMachMessageCheckForAndDestroyUnsentMessag*" -x "*SLEventCopyAuthenticationMessage*" -x "*SendTextInputEvent_WithCompletionHandler*" -x '*mach_msg_send*' -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result*" --decorate --ui-port 60000
|
||||
```
|
||||
所以在hook3处我直接注入protobuf的内容,然后进行发送。
|
||||
|
||||
打印上游调用
|
||||
```
|
||||
defineHandler({
|
||||
onEnter(log, args, state) {
|
||||
const connectionPtr = args[0];
|
||||
const messagePtr = args[1];
|
||||
const targetqPtr = args[2];
|
||||
const handlerPtr = args[3];
|
||||
hook4是在Req2Buf,清除掉消息体的内容,因为后序在OnTaskEnd会回收内存,如果我这边消息体还在整个的指针上就会被清除,但是这个线程不认识这块内存,整个程序就会crash。
|
||||
|
||||
// --- 1. 打印函数调用信息 ---
|
||||
log(`\n======================================================`);
|
||||
log(`[HOOKED] xpc_connection_send_message_with_reply 被调用`);
|
||||
|
||||
// --- 2. 打印调用栈(最重要的一步,用于定位上层应用函数)---
|
||||
const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
|
||||
.map(DebugSymbol.fromAddress).join('\n');
|
||||
log("调用栈 (寻找上游应用函数):");
|
||||
log(backtrace);
|
||||
log("------------------------------------------------------");
|
||||
|
||||
// --- 3. 打印指针参数 ---
|
||||
log(`[Arg 1] Connection (xpc_connection_t): ${connectionPtr}`);
|
||||
log(`[Arg 2] Message (xpc_object_t): ${messagePtr}`);
|
||||
log(`[Arg 3] Target Queue (dispatch_queue_t): ${targetqPtr}`);
|
||||
log(`[Arg 4] Reply Handler: ${handlerPtr}`);
|
||||
log("------------------------------------------------------");
|
||||
|
||||
// --- 4. 打印 XPC 消息的 HexDump 预览 ---
|
||||
if (messagePtr.isNull() === false) {
|
||||
log(`[Arg 2] Message 原始数据预览 (32 Bytes):`);
|
||||
|
||||
// 注意:这里我们使用 hexdump(),然后将结果作为一个字符串打印到 log() 中
|
||||
const hexDumpOutput = hexdump(messagePtr, { length: 32 });
|
||||
log(hexDumpOutput);
|
||||
}
|
||||
|
||||
log(`======================================================`);
|
||||
},
|
||||
|
||||
onLeave(log, retval, state) {
|
||||
// 留空,或在这里打印返回值,例如:
|
||||
// log(`xpc_connection_send_message_with_reply 返回: ${retval}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
会有一个http页面,进去之后,会有一些微信的代码,这些代码中能分析出微信是怎么发送消息的
|
||||
|
||||
### 第二次尝试,根据mac的系统函数
|
||||
注意权限问题,找一个文件夹有权限的
|
||||
|
||||
```
|
||||
cd go/src/github.com/yincongcyincong/nixiang
|
||||
frida-trace -p 17649 -i "*_send*" -i "*_sendto*" -i "*_write*" -x "*xpc_connection_send_message*" -x '*objc_msgSend_noarg*' -x '*objc_msgSend_debug*' -x '*objc_msgSend*' -x "*_HIDisableSuddenTerminationForSendEvent*" -x "*_HIEnableSuddenTerminationForSendEvent*" -x "*SendEventToEventTarget*" -x "*s10RTCUtility10XPCMessageV4dictAA16RTCXPCDictionaryVvg*" -x "*MTLMessageContextEnd*" -x "*ictAA16RTCXPCDictionaryVvg*" -x "*_MTLMessageContextBegin_*" -x "*CFMachMessageCheckForAndDestroyUnsentMessag*" -x "*SLEventCopyAuthenticationMessage*" -x "*SendTextInputEvent_WithCompletionHandler*" -x '*mach_msg_send*' -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result_and_async_reply_4libxpc*" -x "*dispatch_mach_send_with_result*" -x "xpc_*" --decorate --ui-port 60000
|
||||
```
|
||||
```
|
||||
defineHandler({
|
||||
onEnter(log, args, state) {
|
||||
// 1. 打印函数调用和时间
|
||||
log('__write_nocancel() [libsystem_kernel.dylib] 被调用');
|
||||
|
||||
// 2. 关键步骤:打印调用栈
|
||||
log("调用栈 (寻找上游应用函数):");
|
||||
|
||||
// 使用 Thread.backtrace() 捕获当前线程的堆栈
|
||||
// 然后用 DebugSymbol.fromAddress() 将地址转换为符号(函数名+偏移量)
|
||||
const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
|
||||
.map(DebugSymbol.fromAddress).join('\n');
|
||||
|
||||
log(backtrace);
|
||||
log("-----------------------------------------");
|
||||
|
||||
// 3. (可选) 打印写入的数据参数
|
||||
const fd = args[0].toInt32(); // 文件描述符 (File Descriptor)
|
||||
const bufferPtr = args[1]; // 数据缓冲区地址
|
||||
const length = args[2].toUInt32(); // 写入的数据长度
|
||||
|
||||
log(`文件描述符 (FD): ${fd}, 长度: ${length} 字节`);
|
||||
|
||||
if (length > 0) {
|
||||
log("原始写入数据预览 (HexDump):");
|
||||
// 打印前 64 字节
|
||||
log(hexdump(bufferPtr, { length: Math.min(length, 64) }));
|
||||
}
|
||||
},
|
||||
|
||||
onLeave(log, retval, state) {
|
||||
// 可选:打印返回值,即实际写入的字节数
|
||||
// log(`__write_nocancel() 返回: ${retval}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
通过这里成功定位到
|
||||
```
|
||||
__write_nocancel() [libsystem_kernel.dylib] 被调用
|
||||
|
||||
调用栈 (寻找上游应用函数):
|
||||
|
||||
0x18b2e2764 libsystem_c.dylib!__swrite
|
||||
0x18b2c4734 libsystem_c.dylib!_swrite
|
||||
0x18b2c28bc libsystem_c.dylib!__sflush
|
||||
0x18b2d0da0 libsystem_c.dylib!fclose
|
||||
0x1068a307c WeChat!0x462307c (0x10462307c)
|
||||
0x1068a1b90 WeChat!0x4621b90 (0x104621b90)
|
||||
0x106864b3c WeChat!0x45e4b3c (0x1045e4b3c)
|
||||
```
|
||||
|
||||
明显看到代码被混淆,下一步使用 Interceptor.attach() 对内存进行hook
|
||||
```
|
||||
cd go/src/github.com/yincongcyincong/nixiang
|
||||
frida -p 79464 -l ./script.js
|
||||
```
|
||||
|
||||
能打印 send
|
||||
```
|
||||
const wechat = Process.getModuleByName("WeChat");
|
||||
Interceptor.attach(wechat.findExportByName("recv"), {
|
||||
onEnter(args) {
|
||||
this.buf = args[1];
|
||||
this.len = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() > 0) {
|
||||
console.log("=== Recv Data ===");
|
||||
console.log(hexdump(this.buf, { length: retval.toInt32() }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Interceptor.attach(wechat.findExportByName("read"), {
|
||||
onEnter(args) {
|
||||
this.buf = args[1];
|
||||
this.len = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() > 0) {
|
||||
console.log("=== Read Data ===");
|
||||
console.log(hexdump(this.buf, { length: retval.toInt32() }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Interceptor.attach(wechat.findExportByName("send"), {
|
||||
onEnter(args) {
|
||||
console.log("=== Send Data ===");
|
||||
console.log(hexdump(args[1], { length: args[2].toInt32() }));
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() > 0) {
|
||||
console.log("=== Send Data ===");
|
||||
console.log(hexdump(this.buf, { length: retval.toInt32() }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Interceptor.attach(wechat.findExportByName("write"), {
|
||||
onEnter(args) {
|
||||
let fd = args[0].toInt32();
|
||||
this.buf = args[1];
|
||||
this.len = args[2].toInt32();
|
||||
dump("WRITE", this.buf, this.len);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
拦截发送库
|
||||
```
|
||||
const libc = Process.getModuleByName("libSystem.B.dylib");
|
||||
|
||||
Interceptor.attach(libc.findExportByName("read"), {
|
||||
onEnter(args) {
|
||||
this.buf = args[1];
|
||||
this.len = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() > 0) {
|
||||
console.log("=== Read Data ===");
|
||||
console.log(hexdump(this.buf, { length: retval.toInt32() }));
|
||||
console.log(Thread.backtrace(this.context, Backtracer.FUZZY)
|
||||
.map(DebugSymbol.fromAddress).join("\n"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Interceptor.attach(libc.findExportByName("write"), {
|
||||
onEnter(args) {
|
||||
this.buf = args[1];
|
||||
this.len = args[2].toInt32();
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (retval.toInt32() > 0) {
|
||||
console.log("=== Write Data ===");
|
||||
console.log(hexdump(this.buf, { length: retval.toInt32() }));
|
||||
console.log(Thread.backtrace(this.context, Backtracer.FUZZY)
|
||||
.map(DebugSymbol.fromAddress).join("\n"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
查看:./frida/succ.js
|
||||
|
||||
Reference in New Issue
Block a user