本文仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
环境准备
- wechat x64版本
- x64dbg
- ida 或者 ghidra
- debugview++
- Visual Studio 2022
定位调试信息函数
对于大型软件来说,由于其庞大复杂的功能,为了开发人员更好的定位 bug,会输出调试信息辅助调试,对于逆向来说,有调试信息也会有助于更好的定位功能点
调试信息一般是固定格式的字符串,例如:日期 - 功能 - 函数名 - 文件名
定位调试信息的方法:找到固定格式大量出现的字符串,然后都点进去看看是不是同一个函数
对于微信来说,在查看核心模块wechatwin.dll的时候,就会看到很多疑似调试信息的伪代码,例如:(这里我进行了函数重命名)
log_message(
2,
(__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\01_ui\\chatroom\\ChatMsgList.cpp",
35,
(__int64)"ChatMsgList::InsertChatItem",
"ChatMsgList",
"InsertChatItem msg svrid : %d, pre localid : %d",
&v13,
&v12,
v17,
&v16,
&v15,
&v14);
后来调试发现,确实是这个函数在拼接调试信息,但是没有打印出来,这里应该是微信禁用了调试信息的输出
既然如此,那就手动打印调试信息吧,hook 该函数获取调试信息进行手动打印输出
还有一个思路找日志函数(参考资料[0]),搜索代码文件的.cpp
后缀
分析 hook 点
参考资料[0]找的日志信息位于日志函数执行时候的栈信息,要实现从栈中拿数据,只需要写dll注入,hook有数据的那个地方,读取栈指针去读取内容
但是我是个懒人,能自动就不想手动,hook库detours似乎只能hook函数开头的位置(hook其他地方之后,补的指令有问题)
所以就找找内部某个函数的参数或者返回值是否满足我的要求:具有完整的日志信息
完整伪代码:
void log_message(
int a1,
__int64 a2,
int a3,
__int64 a4,
const char *a5,
const char *a6,
__int128 *a7,
__int128 *a8,
__int128 *a9,
__int128 *a10,
__int128 *a11,
__int128 *a12,
...)
{
__int128 *v15; // rax
__int64 v16; // rcx
const char *v17; // rsi
int v18; // ecx
__int64 v19; // r9
__int128 *v20; // r8
char *v21; // rdi
__int64 v22; // rdi
_BYTE *v23; // rdx
DWORD CurrentThreadId; // [rsp+50h] [rbp-B0h]
int v26; // [rsp+80h] [rbp-80h] BYREF
char *v27; // [rsp+88h] [rbp-78h]
__int64 v28; // [rsp+90h] [rbp-70h]
__int64 v29; // [rsp+98h] [rbp-68h]
int v30; // [rsp+A0h] [rbp-60h]
char v31[12]; // [rsp+A4h] [rbp-5Ch] BYREF
__int64 v32; // [rsp+B0h] [rbp-50h]
__int64 v33; // [rsp+B8h] [rbp-48h]
__int64 v34; // [rsp+C0h] [rbp-40h]
struct _SYSTEMTIME SystemTime; // [rsp+D0h] [rbp-30h] BYREF
void *Src; // [rsp+E0h] [rbp-20h] BYREF
int v37; // [rsp+E8h] [rbp-18h]
int v38; // [rsp+ECh] [rbp-14h]
char v39[4104]; // [rsp+F0h] [rbp-10h] BYREF
__int128 v40[6]; // [rsp+10F8h] [rbp+FF8h] BYREF
int v41; // [rsp+1158h] [rbp+1058h]
int v42; // [rsp+115Ch] [rbp+105Ch]
__int128 v43[6]; // [rsp+1160h] [rbp+1060h] BYREF
char Buffer[16]; // [rsp+11C0h] [rbp+10C0h] BYREF
__int128 v45; // [rsp+11D0h] [rbp+10D0h]
__int128 v46; // [rsp+11E0h] [rbp+10E0h]
__int128 v47; // [rsp+11F0h] [rbp+10F0h]
char v48[256]; // [rsp+1200h] [rbp+1100h] BYREF
if ( sub_183CEBBD0(a1) )
{
v15 = v40;
v16 = 6i64;
do
{
*(_BYTE *)v15++ = -1;
--v16;
}
while ( v16 );
v39[0] = 0;
Src = v39;
v38 = 4096;
v17 = 0i64;
v37 = 0;
v41 = 0;
v18 = 0;
v42 = 0;
v43[0] = *a7;
v43[1] = *a8;
v43[2] = *a9;
v43[3] = *a10;
v43[4] = *a11;
v43[5] = *a12;
v19 = 0i64;
v20 = v43;
do
{
if ( *(_BYTE *)v20 == 0xFF )
break;
v40[v18] = *v20;
v18 = ++v42;
++v19;
++v20;
}
while ( v19 < 6 );
sub_18260E7A0(&Src, a6, v20, v19);
sub_18260E3B0((__int64)&Src, 1);
if ( Src )
*((_BYTE *)Src + v37++) = 10;
GetLocalTime(&SystemTime);
v21 = (&off_184F54F80)[a1 % 5];
CurrentThreadId = GetCurrentThreadId();
snprintf(
v48,
(const char *const)0x100,
"(%d-%d-%d:%d:%02d:%02d:%03d %05d)-%s/%s:",
SystemTime.wYear,
SystemTime.wMonth,
SystemTime.wDay,
SystemTime.wHour,
SystemTime.wMinute,
SystemTime.wSecond,
SystemTime.wMilliseconds,
CurrentThreadId,
v21,
a5);
if ( v48[0] )
{
v22 = -1i64;
do
++v22;
while ( v48[v22] );
sub_18260E3B0((__int64)&Src, v22);
v23 = Src;
if ( Src )
{
memmove((char *)Src + (int)v22, Src, v37 + 1);
memmove(Src, v48, (int)v22);
v37 += v22;
v23 = Src;
v17 = (char *)Src + (int)v22;
}
}
else
{
v23 = Src;
}
v23[v37] = 0;
v26 = a1;
*(_OWORD *)Buffer = 0i64;
v45 = 0i64;
v46 = 0i64;
v47 = 0i64;
snprintf(Buffer, (const char *const)0x40, "%s%s", "MMPC_", a5);
v27 = Buffer;
v28 = a2;
v29 = a4;
v30 = a3;
sub_183CF40E0(v31, 0i64);
v32 = sub_183CF41C0();
v33 = sub_183CF4230();
v34 = sub_183CF41B0();
sub_183CEBC10((__int64)&v26, v17);
if ( Src != v39 )
free(Src);
}
}
中间有snprintf的拼接字符串,完整的日志信息在这后面,分析代码可以看到,日志信息保存在了Src变量里,而这个v39也是来自Src变量
一般来说,一个函数的前面都是在为功能做准备,做好准备后执行,最后的函数就很可疑:sub_183CEBC10,但是劫持这里没法从参数中获得完整日志信息,只能获得部分
经过分析,看反汇编:
.text:000000018260F0C4 nop
.text:000000018260F0C5 lea rax, [rbp+1250h+var_1260] ; v39
.text:000000018260F0C9 mov rcx, [rbp+1250h+Src] ; Block
.text:000000018260F0CD cmp rcx, rax
.text:000000018260F0D0 jz short loc_18260F0D7
.text:000000018260F0D2 call free
.text:000000018260F0D7
.text:000000018260F0D7 loc_18260F0D7: ; CODE XREF: log_message+56↑j
.text:000000018260F0D7 ; log_message+320↑j
.text:000000018260F0D7 mov rcx, [rbp+1250h+var_50]
.text:000000018260F0DE xor rcx, rsp ; StackCookie
.text:000000018260F0E1 call __security_check_cookie
.text:000000018260F0E6 add rsp, 1318h
.text:000000018260F0ED pop r15
.text:000000018260F0EF pop r14
.text:000000018260F0F1 pop r13
.text:000000018260F0F3 pop r12
.text:000000018260F0F5 pop rdi
.text:000000018260F0F6 pop rsi
.text:000000018260F0F7 pop rbx
.text:000000018260F0F8 pop rbp
.text:000000018260F0F9 retn
log_message 函数的末尾有一个比较,v39和Src变量的比较,比较完成之后,如果相同就返回
这里log_message函数的类型是void,无返回值,但实际返回的时候,rax的值是Src指针,其实是返回了东西的,这个Src指针指向局部变量,很快就会被覆盖
所以这里的思路是,直接hook log_message函数,执行log_message函数,拿到返回值,复制走返回值的字符串,然后OutputDebugStringA打印调试信息
代码实现(核心)
定义函数原型定成返回char*来接收指针
接收到之后立马复制走里面的数据避免被覆盖,因为该指针指向的栈数组的生命周期已经结束了,数据随时有被覆盖的风险
然后这里的异常处理是为了应对一些特殊情况:
#include "pch.h"
#include "selHook.h"
typedef char*(__fastcall* _log_message)(
int a1,
__int64 a2,
int a3,
__int64 a4,
const char* a5,
const char* a6,
__int128* a7,
__int128* a8,
__int128* a9,
__int128* a10,
__int128* a11,
__int128* a12
);
_log_message flog_message;
char* __fastcall Mylog_message(
int a1,
__int64 a2,
int a3,
__int64 a4,
const char* a5,
const char* a6,
__int128* a7,
__int128* a8,
__int128* a9,
__int128* a10,
__int128* a11,
__int128* a12
) {
char* tmp = flog_message(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
__try {
if (tmp == NULL) return NULL;
int len = strlen(tmp);
char log[0x1000];
strncpy_s(log, tmp, len < 0x1000 ? len : 0x1000);
OutputDebugStringA(log);
return tmp;
}
__except(1){
return NULL;
}
}
void SetHook() {
HMODULE hMod = GetModuleHandleA("WeChatWin.dll");
flog_message = (_log_message)((unsigned long long)hMod + FUNCTION_log);
// Initiate the detours hooks
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// Set our hooks
DetourAttach(&(PVOID&)flog_message, Mylog_message);
// Apply our hooks
LONG error = DetourTransactionCommit();
if (error != NO_ERROR)
{
printf("Failed to commit hooks\n");
exit(1);
}
}
实现效果
使用debugview++接收调试信息,得到完整调试信息:
1 0.000000 20528 WeChat.exe (2024-7-2:9:21:34:058 01924)-i/LoginWnd:click login btn
2 0.003215 20528 WeChat.exe (2024-7-2:9:21:34:058 01924)-i/QRCodeLoginMgr:killTimer
3 0.003371 20528 WeChat.exe (2024-7-2:9:21:34:058 01924)-i/NetScenePushLoginURL:new NetScenePushLoginURL (id:5)
4 0.008051 20528 WeChat.exe (2024-7-2:9:21:34:068 01924)-i/NetScenePushLoginURL:doSceneImpl(id:5)
5 0.008201 20528 WeChat.exe (2024-7-2:9:21:34:068 01924)-i/QRCodeLoginMgr:qrCodeLogin mgr reset()
6 0.008337 20528 WeChat.exe (2024-7-2:9:21:34:068 01924)-i/WCSMgr:Start Get CCData is Need Verify File Sign : 0
7 0.022331 20528 WeChat.exe (2024-7-2:9:21:34:085 01924)-i/WCSMgr:Finish Get CCData cost : 17
8 0.022471 20528 WeChat.exe (2024-7-2:9:21:34:085 01924)-i/WCSMgr:data size : 2826 md5 :b0c72a2b6f30e98bb6d7c9217faa1489
9 0.022644 20528 WeChat.exe (2024-7-2:9:21:34:085 01924)-i/NetSceneBase:in send NetScenePushLoginURL(id:5)
10 0.023630 20528 WeChat.exe (2024-7-2:9:21:34:085 32620)-i/EcdhInfo:getLoginEcdh. use ml cert.
11 0.025080 20528 WeChat.exe (2024-7-2:9:21:34:088 32620)-i/NetSceneBase:EncodeHybirdEncryptPack,1
12 0.025254 20528 WeChat.exe (2024-7-2:9:21:34:088 32620)-i/NetSceneBaseEx:out NetScenePushLoginURL::req2Buf size:3889, id:5
13 0.267871 20528 WeChat.exe (2024-7-2:9:21:34:328 32620)-i/NetSceneBaseEx:decoede with random key
14 0.268586 20528 WeChat.exe (2024-7-2:9:21:34:328 32620)-i/NetSceneBaseEx:out NetScenePushLoginURL::buf2Resp unpackSize: 128, id:5
15 0.268842 20528 WeChat.exe (2024-7-2:9:21:34:328 32620)-i/WinMarsMgr:onGYNetEnd sceneID:5 errType:0 errCode:0
16 0.270117 20528 WeChat.exe (2024-7-2:9:21:34:328 01924)-i/NetScenePushLoginURL:onGYNetEnd(errType:0, errCode:0, sceneID:5)
17 0.271740 20528 WeChat.exe (2024-7-2:9:21:34:328 01924)-i/NetScenePushLoginURL:scene: 0, flag: 0, alertCode:0
18 0.272301 20528 WeChat.exe (2024-7-2:9:21:34:328 01924)-i/NetScenePushLoginURL:pushLoginURL success, UUID = 略 , checkTime = 15锛?m_expiredTime = 290
19 0.273595 20528 WeChat.exe (2024-7-2:9:21:34:328 01924)-i/NetScenePushLoginURL:~NetScenePushLoginURL (id:5)
20 0.275985 20528 WeChat.exe (2024-7-2:9:21:34:338 01924)-i/LoginWnd:ON_NETSCENE_PUSH_LOGIN_URL_SUCCESS
21 0.276295 20528 WeChat.exe (2024-7-2:9:21:34:340 01924)-i/QRCodeLoginMgr:delayCheck
22 0.276557 20528 WeChat.exe (2024-7-2:9:21:34:340 01924)-i/Utils:get UILanguage from Sys. 2052
23 1.708338 20528 WeChat.exe (2024-7-2:9:21:35:772 32620)-i/WinMarsMgr:onNotify seq:default-longlink cmd:231
24 1.708593 20528 WeChat.exe (2024-7-2:9:21:35:772 32620)-i/NotifyMgr:Receive a LOGIN_QRCOCE_NOTIFY
25 1.708720 20528 WeChat.exe (2024-7-2:9:21:35:772 32620)-i/NotifyMgr:Decrypt qrcode notify Pack Success!
26 1.709628 20528 WeChat.exe (2024-7-2:9:21:35:773 01924)-i/LoginWnd:qrCodeScaned
27 1.709775 20528 WeChat.exe (2024-7-2:9:21:35:773 01924)-i/LoginWnd:scan status = 1
28 1.709900 20528 WeChat.exe (2024-7-2:9:21:35:773 01924)-i/AccountService:phone type =
29 1.710002 20528 WeChat.exe (2024-7-2:9:21:35:773 01924)-i/QRCodeLoginMgr:PhoneVersion 0
30 1.710126 20528 WeChat.exe (2024-7-2:9:21:35:773 01924)-i/QRCodeLoginMgr:URL Expired Time = 0
31 15.276283 20528 WeChat.exe (2024-7-2:9:21:49:338 01924)-i/QRCodeLoginMgr:OnCheckQrCodeTimerCallback
32 15.277088 20528 WeChat.exe (2024-7-2:9:21:49:338 01924)-i/NetSceneCheckLoginQRCode:new NetSceneCheckLoginQRCode (id:6)
33 15.277392 20528 WeChat.exe (2024-7-2:9:21:49:338 01924)-i/EcdhInfo:getLoginEcdh. use ml cert.
34 15.277678 20528 WeChat.exe (2024-7-2:9:21:49:338 01924)-i/NetSceneBase:in send NetSceneCheckLoginQRCode(id:6)
35 15.278189 20528 WeChat.exe (2024-7-2:9:21:49:338 32620)-i/EcdhInfo:getLoginEcdh. use ml cert.
36 15.279678 20528 WeChat.exe (2024-7-2:9:21:49:338 32620)-i/NetSceneBase:EncodeHybirdEncryptPack,1
37 15.279953 20528 WeChat.exe (2024-7-2:9:21:49:338 32620)-i/NetSceneBaseEx:out NetSceneCheckLoginQRCode::req2Buf size:337, id:6
38 15.469843 20528 WeChat.exe (2024-7-2:9:21:49:534 32620)-i/NetSceneBaseEx:decoede with random key
39 15.470510 20528 WeChat.exe (2024-7-2:9:21:49:535 32620)-i/NetSceneBaseEx:out NetSceneCheckLoginQRCode::buf2Resp unpackSize: 228, id:6
40 15.470759 20528 WeChat.exe (2024-7-2:9:21:49:535 32620)-i/WinMarsMgr:onGYNetEnd sceneID:6 errType:0 errCode:0
41 15.470942 20528 WeChat.exe (2024-7-2:9:21:49:535 01924)-i/NetSceneCheckLoginQRCode:onGYNetEnd(errType:0, errCode:0, sceneID:6)
42 15.472293 20528 WeChat.exe (2024-7-2:9:21:49:535 01924)-i/NetSceneCheckLoginQRCode:status:1, hdimg:http://wx.q (略)
43 15.472516 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/NetSceneCheckLoginQRCode:~NetSceneCheckLoginQRCode (id:6)
44 15.472720 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/LoginWnd:qrCodeScaned
45 15.472987 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/LoginWnd:scan status = 1
46 15.473246 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/AccountService:phone type =
47 15.473421 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/QRCodeLoginMgr:PhoneVersion 0
48 15.473767 20528 WeChat.exe (2024-7-2:9:21:49:537 01924)-i/QRCodeLoginMgr:URL Expired Time = 0
49 15.473978 20528 WeChat.exe (2024-7-2:9:21:49:538 01924)-i/QRCodeLoginMgr:delayCheck
参考资料