开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 369|回复: 6
打印 上一主题 下一主题
收起左侧

[其它源码] 【技术解析】使用Python与Pymem实现PCVX内存操作与远程函...

[复制链接]
结帖率:95% (316/334)
跳转到指定楼层
楼主
发表于 前天 17:28 | 只看该作者 |只看大图 回帖奖励 |正序浏览 |阅读模式   广东省深圳市
分享源码
界面截图:
是否带模块: 调用了模块
备注说明: -

【技术解析】使用Python与Pymem实现PCVX内存操作与远程函数调用

⚠️ 法律声明与免责声明: 本文旨在探讨Windows操作系统下的进程内存管理、动态链接库(DLL)加载机制以及远程线程注入技术。所提供的代码和方法仅供技术研究和学习之用。严禁将本文内容用于非法获取用户信息、破坏计算机系统或任何违反法律法规的活动。读者应自行承担因使用本文技术所产生的一切法律后果,作者对此不承担任何责任。

一、引言

在Windows应用开发和逆向工程领域,直接操作目标进程的内存空间是一项关键技术。它允许我们读取运行时数据或在目标进程的上下文中执行代码。本文将深入分析如何使用Python结合pymem库,对PC版VX(WeChat for Windows)进行内存读取(获取用户信息)和远程函数调用(发送消息)。

我们将重点关注以下核心技术点:

  1. 进程附加与模块jz定位。
  2. 基于静态偏移的内存数据读取。
  3. x64调用约定的Shellcode构建。
  4. 使用CreateRemoteThread实现远程代码执行。

二、核心技术原理

在深入代码实现之前,需要理解几个Windows内存管理和进程交互的基础概念。

1. 内存读写与Pymem库

操作系统通过虚拟内存机制为每个进程分配独立的地址空间。要访问另一个进程的内存,需要使用特定的Windows API,如OpenProcess, ReadProcessMemory (RPM) 和 WriteProcessMemory (WPM)。

Pymem是一个Python库,它封装了这些底层的Windows API调用,提供了一个面向对象的接口,简化了跨进程内存操作的复杂性。

2. 模块jz(Base Address)与偏移量(Offset)

现代操作系统普遍采用地址空间布局随机化(ASLR)技术。这意味着,程序的主执行文件(.exe)和它所依赖的动态链接库(DLL)每次加载到内存时的起始地址(jz)都是变化的。

然而,DLL文件内部的结构是固定的。一个特定的变量或函数相对于该DLL加载jz的距离(偏移量)是恒定的(在同一软件版本内)。

因此,访问特定数据的标准流程是:

  1. 定位目标DLL(如WeChatWin.dll)在当前进程中的加载jz。
  2. 结合已知的静态偏移量,计算出目标数据的实际内存地址。

实际地址 = 模块jz + 静态偏移量

3. 远程线程注入(Remote Thread Injection)

为了在目标进程中执行一个函数(例如发送消息的函数),我们不能在外部进程中直接调用它。我们需要让目标进程自己去执行。

远程线程注入是通过Windows API CreateRemoteThread实现的。其原理是在目标进程空间内申请一段内存,写入要执行的机器码(Shellcode),然后在目标进程中创建一个新的线程,并将该线程的执行起点指向这段Shellcode。

三、代码实现深度解析

我们将分析WeChatMemoryReader类的关键实现。

1. 初始化与jz定位

import psutil
from pymem import Pymem
# ... (其他导入)

class WeChatMemoryReader:
    def __init__(self, pid: int):
        self.pid = pid
        # 警告:以下偏移量与WeChat版本强相关,版本更新后极大概率失效。
        self.offsets = {
            'phone': 0x5A2CB68,
            'wxid': 0x5A2D160,
            'send_message': 0x22D4A90,
        }
        # ...
        try:
            # 附加到目标进程
            self.pm = Pymem(pid)
            self.init_base_address()
        except Exception as e:
            # ... 错误处理 ...

    def init_base_address(self):
        """初始化WeChatWin.dlljz"""
        # 遍历进程加载的所有模块
        for module in self.pm.list_modules():
            if module.name == "WeChatWin.dll":
                self.base_address = module.lpBaseOfDll
                print(f"[PID {self.pid}] WeChatWin.dll Base Address: {hex(self.base_address)}")
                return
  • Pymem(pid):通过进程ID附加到目标进程,获取操作句柄。
  • init_base_address():利用pymem.list_modules()枚举VX进程加载的所有DLL,找到核心逻辑模块WeChatWin.dll,并记录其lpBaseOfDll(jz),为后续操作提供基准。

2. 内存数据读取与指针解引用

读取用户信息是相对直接的操作,通过jz + 偏移量定位数据地址。

    def get_user_info(self):
        # ...
        # 读取VX号
        wxid_address = self.base_address + self.offsets['wxid']
        # 使用自定义函数处理可能的指针结构
        wxid = self.read_comment_string(wxid_address, 20)
        # ...

read_comment_string:处理复杂数据结构

在C/C++编译的程序中(如VX),字符串通常不会直接存储在静态偏移位置,而是存储一个结构体(如std::string或自定义结构),该结构体内部包含一个指向堆内存中实际字符串的指针。

    def read_comment_string(self, address, max_length=500):
        try:
            # 1. 尝试将该地址内容视为一个64位指针 (longlong)
            pointer = self.pm.read_longlong(address)

            if pointer:
                try:
                    # 2. 如果是指针,则读取指针指向的地址内容
                    result = self.pm.read_string(pointer, max_length)
                    if result:
                        return result
                except:
                    pass

            # 3. 备用方案:如果不是指针(例如短字符串优化SSO),尝试直接从原地址读取
            result = self.pm.read_string(address, max_length)
            return result
        # ...

read_comment_string体现了处理这种内存结构的策略:首先尝试进行指针解引用(Dereference),如果失败,则尝试直接读取地址内容。

3. 远程函数调用:发送消息

这是最复杂的部分,涉及内存写入、Shellcode构建和线程注入。目标是调用WeChatWin.dll内部的发送消息函数。

步骤 1: 参数准备与内存写入

VX的内部函数通常使用复杂的结构体作为参数,而非简单的基本类型。我们需要在VX的内存空间中模拟构建这些结构体。

    def send_message(self, wxid, message):
        # ...
        # 1. 准备WXID结构体 (模拟类似 std::wstring 的结构)
        # VX内部通常使用UTF-16LE编码
        wxid_unicode = wxid.encode('utf-16le') + b'\x00\x00'

        # 在目标进程中分配内存存储字符串内容
        wxid_ptr = self.pm.allocate(len(wxid_unicode))
        self.pm.write_bytes(wxid_ptr, wxid_unicode, len(wxid_unicode))

        # 在目标进程中分配内存存储结构体 (假设结构体大小为32字节)
        wxid_struct = self.pm.allocate(32)

        # 写入结构体成员:指针、长度等
        self.pm.write_longlong(wxid_struct, wxid_ptr)      # Offset 0: 指向字符串的指针
        self.pm.write_longlong(wxid_struct + 8, len(wxid)) # Offset 8: 字符串长度
        # ... (其他成员初始化为0)

        # 2. 准备消息内容结构体 (同理)
        # ...

步骤 2: 构建Shellcode(x64调用约定)

为了调用函数,我们需要一段机器码(Shellcode)来按照Windows x64应用程序二进制接口(ABI)的规定设置参数。

在Windows x64 ABI中,前四个整数或指针参数通常通过寄存器RCX, RDX, R8, R9传递,其余参数通过堆栈传递。

    def _create_call_shellcode(self, func_addr, rcx, rdx, r8, r9):
        shellcode = bytearray()

        # --- 函数序言 (保存寄存器状态) ---
        # ... (PUSH RAX, RCX, RDX, R8, R9等)

        # --- 设置参数 (根据x64调用约定) ---
        # mov rcx, rcx_value
        shellcode.extend([0x48, 0xB9])
        shellcode.extend(struct.pack('<Q', rcx)) # RCX = 参数1

        # mov rdx, rdx_value
        shellcode.extend([0x48, 0xBA])
        shellcode.extend(struct.pack('<Q', rdx)) # RDX = 参数2

        # ... (设置R8, R9) ...

        # --- 准备堆栈空间 (Shadow Space) ---
        shellcode.extend([0x48, 0x83, 0xEC, 0x20])  # sub rsp, 0x20

        # --- 调用函数 ---
        # mov rax, func_addr
        shellcode.extend([0x48, 0xB8])
        shellcode.extend(struct.pack('<Q', func_addr))
        # call rax
        shellcode.extend([0xFF, 0xD0])

        # --- 函数尾声 (清理堆栈,恢复寄存器) ---
        # ... (ADD RSP, POP等)
        shellcode.extend([0xC3])  # ret (线程返回)

        return bytes(shellcode)

这段Shellcode精确地模拟了编译器在调用函数时生成的汇编指令。

步骤 3: 执行远程线程

最后一步是将Shellcode注入目标进程并执行。

    def _remote_function_call(self, func_addr, rcx, rdx, r8, r9):
        # 1. 构建Shellcode
        shellcode = self._create_call_shellcode(func_addr, rcx, rdx, r8, r9)

        # 2. 将Shellcode写入目标进程内存
        shellcode_addr = self.pm.allocate(len(shellcode))
        self.pm.write_bytes(shellcode_addr, shellcode, len(shellcode))

        # 3. 使用CreateRemoteThread
        kernel32 = ctypes.windll.kernel32

        # 【关键】为64位环境正确定义API原型
        # 必须明确指定参数类型,防止ctypes在处理64位指针时发生溢出错误 (OverflowError)
        kernel32.CreateRemoteThread.argtypes = [
            wintypes.HANDLE,  # hProcess
            ctypes.c_void_p,  # lpThreadAttributes
            ctypes.c_size_t,  # dwStackSize
            ctypes.c_void_p,  # lpStartAddress (Shellcode地址)
            ctypes.c_void_p,  # lpParameter
            wintypes.DWORD,   # dwCreationFlags
            ctypes.c_void_p   # lpThreadId
        ]
        kernel32.CreateRemoteThread.restype = wintypes.HANDLE

        # 执行注入
        thread_handle = kernel32.CreateRemoteThread(
            self.pm.process_handle,
            None, 0,
            shellcode_addr, # 线程从这里开始执行
            None, 0, None
        )

        # 4. 等待执行并清理资源
        if thread_handle:
            kernel32.WaitForSingleObject(thread_handle, 5000)
            # ... (获取退出码,关闭句柄,释放内存) ...

CreateRemoteThread API在VX进程中启动了一个新线程,该线程执行我们注入的Shellcode,从而完成了对send_message函数的调用。

四、操作注意事项与稳定性分析

1. 依赖环境

  • Python环境(建议使用64位Python以匹配64位VX进程)。
  • 依赖库:pip install pymem psutil pywin32 (pywin32通常是pymem的依赖)。
  • 权限:执行脚本通常需要足够的权限(有时需要管理员权限)才能附加到其他进程。

2. 静态偏移量的脆弱性(Critical Limitation)

本文提供的代码实现存在一个重大的工程局限性:对硬编码静态偏移量的依赖。

        self.offsets = {
            'phone': 0x5A2CB68,
            'wxid': 0x5A2D160,
            # ...
        }

由于软件开发迭代的特性,VX客户Duan每次更新(即使是小版本更新),WeChatWin.dll都会被重新编译。这将导致内部函数和变量的偏移量发生变化。一旦偏移量失效:

  • 读取数据: 将读取到错误的数据。
  • 调用函数: 将调用错误的地址,几乎必然导致目标进程(VX)崩溃。

3. 维护与进阶

要维持此类工具的有效性,需要掌握逆向工程技能:

  • 动态分析: 使用调试器(如x64dbg, WinDbg)分析程序执行流程,定位关键函数。
  • 静态分析: 使用反汇编工具(如IDA Pro, Ghidra)分析DLL文件结构,定位数据引用。
  • 特征码扫描(Signature Scanning): 为了提高兼容性,更健壮的实现应避免使用硬编码偏移量,转而使用特征码(一段独特的字节序列)在内存中动态搜索目标地址。

五、总结

本文详细解析了使用Python和pymem库操作PCVX内存的技术实现。我们探讨了如何定位DLLjz、读取内存数据结构,以及如何通过构建符合x64调用约定的Shellcode并利用CreateRemoteThread实现远程函数调用。

虽然基于静态偏移的实现非常脆弱,但该过程完整展示了Windows环境下进程间通信和内存操作的核心原理,为深入学习逆向工程和系统底层机制提供了实践基础。

main.zip

3.72 KB, 下载次数: 3, 下载积分: 精币 -2 枚

售价: 3 枚 精币  [记录]  [购买]

大哥大姐不好意思,小弟缺精币了


签到天数: 12 天

7
发表于 5 小时前 | 只看该作者   浙江省宁波市
感谢分享,支持开源!!!
回复 支持 反对

使用道具 举报

结帖率:0% (0/2)

签到天数: 11 天

6
发表于 昨天 08:38 | 只看该作者   广西壮族自治区玉林市
支持开源~!感谢分享
回复 支持 反对

使用道具 举报

签到天数: 2 天

地下
发表于 前天 23:38 | 只看该作者   辽宁省朝阳市
非常有用 感谢分享
回复 支持 反对

使用道具 举报

签到天数: 12 天

地板
发表于 前天 22:28 | 只看该作者   贵州省毕节市
学习一下
回复 支持 反对

使用道具 举报

结帖率:100% (3/3)

签到天数: 3 天

板凳
发表于 前天 18:17 | 只看该作者   广东省深圳市
学习一下
回复 支持 反对

使用道具 举报

结帖率:71% (10/14)

签到天数: 11 天

沙发
发表于 前天 17:40 | 只看该作者   安徽省蚌埠市
非常帅气的,但是我选择论坛大哥的模块直接干
回复 支持 1 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表