前言
这篇文章来说说逆向的原理和实现。
需要用到的工具
- 微信(
3.9.12.51
) - x64dbg
- Cheat Engine
- Python
逆向
首先从一些网络上搜索到的信息可以得出联系人列表是保存在内存里的,并且是全局变量不会被释放,所以我们可以通过搜索某个联系人的信息来定位
1、先定位到单个联系人
首先搜索某个好友的微信号,勾选UTF-16 (4.0不需要勾选),找一个结果比较少的,就比如下面的结果19个
然后一个一个去搜索8字节指针
将搜索的结果放到x64dbg里查看(dump 地址
或者打开计算器在内存窗口转到
),直到看到下面的内存结构(有wxid和v3)
这个很大概率就是联系人的内存结构了,然后搜索结构体的开始地址1D107941030
,这个怎么确认哪个是开始地址呢?只能凭经验或者靠猜然后一个一个去验证,因为之前的版本开始地址就是带有wechatwin的地址
这个就是联系人链表的某个节点的数据了,前三个是指向其他节点的指针,然后下面有个wxid,最后一个就是联系人信息的头结点。
这个和之前的版本有点不一样,之前的版本没有这个wxid,直接将联系人信息放到其他节点的下面,估计是为了和4.0保持一致吧,因为4.0就是这种结构。
只要一直遍历前三个指针,就能找到下面没有wxid的一个地址,这个就是所有节点的初始节点,如果是之前的版本,可以通过在CE中右键对这个地址进行指针扫描
来定位这个初始节点的基址。但是这个版本无法定位到偏移了,可能就是一个随机分配的地址。当然肯定是有规律可以查找到这个地址,不然微信内部也无法定位(没仔细研究过)。
会扫描出很多个结果,需要关闭微信重新打开,然后重新找头指针的地址,然后点指针扫描器->重新扫描内存
会继续排除掉一部分,直到没几个结果。
无法定位到基址
既然无法定位到基址,那怎么自动找到这个头结点的地址呢?其实可以通过内存搜索的方法自动查找到。因为我发现每个联系人的头部都是固定的地址
000001D107941030 00007FFB177E0A98
000001D107941038 00007FFB17744A90
在IDA中可以看到这两个就是联系人相关的结构(4.0抹除了这些符号,看不到这种名称了)
所以可以通过搜索这两个地址,来定位到单个联系人信息,也可以将所有的结果都搜出来,然后进行去重就是所有联系人了。或者继续搜索这个联系人信息的头,定位到上层的结构,然后开始遍历所有的节点也能拿到所有的联系人
扫描内存
Python怎么像CE一样扫描其他进程的内存呢,暂时没搜到什么很好的工具,只能自己实现了。
首先通过GetSystemInfo
API获取到内存的最大和最小地址,然后通过VirtualQueryEx
从最小地址遍历到最大地址,获取所有指定进程的内存块。x64dbg的内存布局就是该进程的所有内存块
随便找一个联系人信息的头放到计算器里,然后选择在内存布局里查看
可以查看到内存块有什么特征:
- 不是保留地址
- 地址类型是私有内存(PRV)
- 权限是RW
- 分配的初始权限也是RW
那么我在扫描内存的时候就根据上面的条件过滤掉一些内存块,扫描内存的代码如下:
import ctypes
from ctypes import wintypes
from .winapi import *
from .process import Process
sys_info = SYSTEM_INFO()
GetSystemInfo(ctypes.byref(sys_info))
def scan_and_match_value(prcs:Process, value, max_result_len=1000, match_func=None):
result = []
min_addr = ctypes.cast(sys_info.lpMinimumApplicationAddress, ctypes.c_void_p).value
max_addr = ctypes.cast(sys_info.lpMaximumApplicationAddress, ctypes.c_void_p).value
current_addr = min_addr
while current_addr < max_addr:
mem_info = MEMORY_BASIC_INFORMATION()
VirtualQueryEx(prcs._handle, ctypes.cast(current_addr, wintypes.LPVOID),
ctypes.byref(mem_info), ctypes.sizeof(MEMORY_BASIC_INFORMATION))
region_size = mem_info.RegionSize
if region_size <= 0:
break
if mem_info.Type & MEM_PRIVATE and \
mem_info.State & MEM_COMMIT and \
mem_info.Protect == PAGE_READWRITE and \
mem_info.AllocationProtect == PAGE_READWRITE:
for i in range(0, region_size, 8):
_addr = current_addr + i
_value = prcs.read8ByteNumber(_addr)
if _value == value:
if match_func and match_func(_addr): # 匹配函数
return _addr
print(f"找到匹配值, 地址:0x{_addr:x}")
result.append(_addr)
if len(result) >= max_result_len:
break
current_addr += region_size
CloseHandle(prcs._handle)
return result
然后调用的代码:
from scan_memory import scan_and_match_value
from process import Process
from read_contact_3 import Contacts
pid = 41316
value_to_search = 0x7FFB177E0A98 # 要查找的值
prcs = Process(pid)
def match_func1(addr):
_wxid = prcs.readWstring(addr + 0x18)
if "wxid_" in _wxid:
return True
contact_head = scan_and_match_value(prcs, value_to_search, match_func=match_func1)
print(hex(contact_head))
wxid = prcs.readWstring(contact_head + 0x18)
def match_func2(addr):
_wxid = prcs.readWstring(addr - 0x20)
if _wxid == wxid:
return True
node_head = scan_and_match_value(prcs, contact_head, match_func=match_func2)
node_head = node_head - 0x40
print(hex(node_head))
for i in Contacts().run_head(prcs, node_head):
print(i)
等待个一两分钟就输出所有联系人的信息了,完整代码放到github了:
https://github.com/kanadeblisst00/read_wechat_contacts
优化
为什么这样搜索需要一两分钟,而CE不到一秒就将结果搜索出来了呢?这个不清楚,可能是Python太慢了,也可以是需要频繁调用Windows AP读取其他进程的内存太慢了。
不过有个C++的库可以快速搜索到结果(不到一秒): https://github.com/DarthTon/Blackbone
,使用方法的话自己问gpt和搜索引擎。
引用链接
[0]
:https://blog.csdn.net/Qwertyuiop2016/article/details/146444330