1. 前言

近期分析了一下64位的PC微信,对其中的一些内容做一些分享。本期分享内容:Hook微信自身日志。

本次分析使用版本:3.9.12.51。

2. 逆向分析

众所周知,PC微信的主要业务功能均实现于WechatWin.dll,使用IDA分析。

一般日志都会存在格式化字符串,比如%s、{}等,直接在IDA中搜索。

随便选择一个看着很像(带有一些微信业务属性的,比如消息、图片等)的,追踪一下。

alt text

定位到函数sub_182627590,进去看看。

alt text

函数内发现代码获取了时间和线程ID并格式化,基本可以确定这里就是日志函数了。并发现代码循环处理字符串后在尾部添加结束符\x00。

alt text

将Hook点选择在添加结束符\x00之后,即0x18262780F处。这时候字符串已经组装完毕。

alt text

调试发现在0x18262780F处,rdx指向字符串。

alt text

3. Hook

64位的Hook和32位的有点区别,32位一般在Hook点直接jmp XXXXXXXX(立即数地址)或者call XXXXXXXX(立即数地址)即可,但是64位不支持直接jmp/call立即数地址,所以这里采用mov rax,XXXXXXXXXXXXXXXX(立即数地址),然后jmp rax的方式来Hook。由于这种方式会改写rax的值,所以在这之前还得保存一下rax的值,并在跳转后恢复,防止rax的值丢失。

梳理一下Hook流程:

  1. 保存rax
  2. mov rax并jmp rax,就跳转到我们自己的Hook代码处
  3. 恢复rax
  4. 保存寄存器环境,由于64位没有pushad指令,只能手动保存
  5. 调用我们的功能代码
  6. 恢复寄存器环境,由于64位没有popad指令,只能手动恢复
  7. 执行因为Hook操作而被覆盖的原始代码
  8. 再次保存rax
  9. mov eax并jmp eax,返回到本来的功能代码处
  10. 恢复rax,随后即可正常执行

4. 先手动Hook

4.1. Hook点

alt text

4.2. Hook代码

alt text

5. 编写代码自动Hook

5.1. 定义偏移地址

1
2
3
// 这里wxbase为wechatwin.dll的基地址
uint64_t hookaddr_wxlog = 0x18262780F - 0x180000000 + wxbase;
uint64_t retaddr_wxlog = 0x18262781C - 0x180000000 + wxbase;

5.2. 编写功能函数

这里rdx就是字符串指针,可以根据情况编写功能函数,比如输出到文件等。

1
2
3
void hookdeal_wxlog(uint64_t rdx) {

}

5.3. 编写Hook功能代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 申请内存空间
LPVOID memory = VirtualAlloc(NULL, 1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// pop rax(跳转到Hook代码后,恢复rax)
// pushaq + pushfq(保存寄存器环境)
WriteProcessMemory(GetCurrentProcess(), memory, "\x58\x54\x50\x53\x51\x52\x55\x56\x57\x41\x50\x41\x51\x41\x52\x41\x53\x41\x54\x41\x55\x41\x56\x41\x57\x9C", 26, NULL);
// mov rcx,rdx(参数)
// mov rax(给rax赋值地址)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26), "\x48\x89\xD1\x48\xB8", 5, NULL);
// 地址
void (*ptr)(uint64_t) = hookdeal_wxlog;
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5), &ptr, 8, NULL);
// push rbx(传参但是仍然分配栈)
// call rax(调用hookdeal函数)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8), "\x53\xFF\xD0", 3, NULL);
// pop rbx(恢复栈)
// popfq + popaq(恢复寄存器环境)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8 + 3), "\x5B\x9D\x41\x5F\x41\x5E\x41\x5D\x41\x5C\x41\x5B\x41\x5A\x41\x59\x41\x58\x5F\x5E\x5D\x5A\x59\x5B\x58\x5C", 26, NULL);
// 执行旧的被覆盖的代码
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8 + 3 + 26), "\x44\x89\x75\x80\x0F\x57\xC0\x0F\x11\x85\xC0\x10\x00\x00", 14, NULL);
// push rax(保存rax)
// mov rax(给rax赋值地址)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8 + 3 + 26 + 14), "\x50\x48\xB8", 3, NULL);
// 地址
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8 + 3 + 26 + 14 + 3), &retaddr_wxlog, 8, NULL);
// jmp rax(跳转回原代码)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)((uint64_t)memory + 26 + 5 + 8 + 3 + 26 + 14 + 3 + 8), "\xFF\xE0", 2, NULL);

5.4. Hook原始代码

需要占用14字节的空间。

1
2
3
4
5
6
7
// push rax(保存rax)
// mov rax(给rax赋值地址)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookaddr_wxlog, "\x50\x48\xB8", 3, NULL);
// 地址
WriteProcessMemory(GetCurrentProcess(), (LPVOID)(hookaddr_wxlog + 3), &memory, 8, NULL);
// jmp rax(跳转到Hook代码)pop rax(从Hook代码返回后恢复rax)
WriteProcessMemory(GetCurrentProcess(), (LPVOID)(hookaddr_wxlog + 3 + 8), "\xFF\xE0\x58", 3, NULL);

6. 最终效果

获取到微信日志。

alt text