微信逆向:hook 强制输出调试信息

selph
selph
发布于 2024-07-02 / 191 阅读
0
0

微信逆向:hook 强制输出调试信息

#re

环境准备

  • 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*来接收指针

接收到之后立马复制走里面的数据避免被覆盖,因为该指针指向的栈数组的生命周期已经结束了,数据随时有被覆盖的风险

然后这里的异常处理是为了应对一些特殊情况:

  • 返回空,例如:NULL
  • 返回非指针数据,例如:1
#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 = lue, 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.qlogo.cn/mmhead/ver_1/略/0, flag: 0
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

参考资料


评论