PC微信逆向) 读取内存联系人链表

前言

这篇文章来说说逆向的原理和实现。

需要用到的工具
  • 微信(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一样扫描其他进程的内存呢,暂时没搜到什么很好的工具,只能自己实现了。

首先通过GetSystemInfoAPI获取到内存的最大和最小地址,然后通过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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值