• 【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志


    目录修整

    目前的系列目录(后面会根据实际情况变动):

    1. 在windows11上编译python
    2. 将python注入到其他进程并运行
    3. 注入Python并使用ctypes主动调用进程内的函数和读取内存结构体
    4. 调用汇编引擎实战发送文本和图片消息(支持32位和64位微信)
    5. 允许Python加载运行py脚本且支持热加载
    6. 利用汇编和反汇编引擎写一个x86任意地址hook,实战Hook微信日志
    7. 封装Detours为dll,用于Python中x64函数 hook,实战Hook微信日志
    8. 实战32位和64位接收消息和消息防撤回
    9. 实战读取内存链表结构体(好友列表)
    10. 做一个僵尸粉检测工具
    11. 根据bug反馈和建议进行细节上的优化
    12. 其他功能看心情加

    上上篇文章说的以后只更新32位版本这句话收回,以后会同时更新32位和64位的最新版本,已经可以在Python中使用Detours来hook 64位版本。

    为了加快进度,第六篇和第七篇同一天发布,这篇文章为使用总结,想知道hook原理的可以看同时间发布的其他几篇文章。

    温馨提示:本次发布的这几篇文章都是偏技术,想获取成品直接使用的可以等下一篇文章(实战32位和64位接收消息和消息防撤回)

    另外,这篇文章开始建群,请关注github或者公众号菜单栏

    封装好的Hook库

    32位程序的Hook

    hook的参数有两个:内存地址和回调函数。回调函数的参数是一个包含x86所有寄存器的结构体指针,没有返回值。结构体的定义如下:

    class RegisterContext(Structure):
        _fields_ = [
            ('EFLAGS', DWORD),
            ('EDI', DWORD),
            ('ESI', DWORD),
            ('EBP', DWORD),
            ('ESP', DWORD),
            ('EBX', DWORD),
            ('EDX', DWORD),
            ('ECX', DWORD),
            ('EAX', DWORD),
        ]
    

    一个简单的Hook 示例:

    def default_hook_log_callback(pcontext):
        # 获取指针内容,获取的context就是RegisterContext类型了
        context:RegisterContext = pcontext.contents
        # 取eax寄存器的值
        eax = context.EAX
        print("当前eax寄存器的值: ", eax)
    
    addr = 0x100000
    hooker = Hook()
    hooker.hook(addr, hook_log_callback_enter)
    

    context这个结构体获取的就是当执行到这个地址时的寄存器的值,这个和你用x32dbg看到的寄存器的值是一样的。值的类型都定义成DWORD,如果寄存器是类型是其他类型,比如字符串或结构体,你需要在Python里做相应的转换,可以参考下面Hook日志的代码

    你同样可以在回调函数里修改这个指针中寄存器的值,它会反映到实际的寄存器,案例的话会在消息防撤回那一篇文章演示。

    64位的Hook

    因为64位hook是封装的Detour,比32位需要多定义一个函数指针,而且只能hook函数。所以hook之前需要知道被Hook的函数参数有几个,类型如果不知道的话,可以像上面一样都定义成c_uint64

    回调函数的参数跟被Hook函数的参数必须一样,如果参数很多,你也可以用*arg来表示,示例代码如下:

    def hook_log_callback(*args):
        print(args)
        print(kwargs)
            
    hooker = Hook()
    log_addr = 0x100000
    c_log_addr = c_uint64(log_addr)
    lp_log_func = CFUNCTYPE(c_uint64, c_uint64, c_uint64, c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64)
    hooker.hook(c_log_addr, lp_log_func, hook_log_callback)
    

    另外,回调函数的返回值类型也需要和被Hook函数一样,一般都是先调用原函数获取返回值然后返回。如果返回错误类型的返回值,进程会崩溃。

    案例

    为什么要选择Hook日志做案例?日志是多线程打印的,如果Hook日志没有问题的话,其他任何位置的Hook基本都不会有问题。

    效果

    hook后的效果如下:

    32位代码

    from py_process_hooker import Hook
    from py_process_hooker.winapi import *
    
    base = GetModuleHandleW("WeChatWin.dll")
    

    先定义回调函数,因为我需要同时获取参数和返回值,所以要hook两个地方(函数头和函数尾)。

    用x32dbg在日志函数头位置下个断点,看起来有两个有用的信息:EDX的代码路径和esp的函数返回地址。

    定义回调函数:

    def hook_log_callback_enter(pcontext):
        context = pcontext.contents
        esp = context.ESP
        # 计算调用日志函数的地址偏移
        esp_call_offset = c_ulong.from_address(esp).value - base
        # 获取日志中的代码文件路径
        edx = context.EDX
        # 类型是char数组,ctypes定义是(c_char * n), 这个*是Python中的乘号,
        # 如果是char*指针 ctypes则定义为c_char_p
        c_code_file = (c_char * MAX_PATH).from_address(edx)
        code_file = c_code_file.value.decode()
        print(f"调用地址: WeChatWin.dll+{hex(esp_call_offset)}, 代码路径: {code_file}, ", end=" ")
    
    

    然后看返回值,返回值获取的是EAX的值

    def hook_log_callback_leave(pcontext):
        context = pcontext.contents  
        eax = context.EAX
        c_log_info = (c_char * 1000).from_address(eax)
        log_info = c_log_info.value.decode()
        print("日志信息: ", log_info)
    

    在new一个Hook类hook这两个位置:

    hooker = Hook()
    enter_addr = base + 0x102C250
    hook.hook(enter_addr, hook_log_callback_enter)
    
    enter_addr = base + 0x102C584
    hook.hook(enter_addr, hook_log_callback_leave)
    

    因为需要支持热加载,所以在hook之前先调用一下unhook,这样你修改代码就会生效新的hook。

    使用

    你想hook日志的话,先将github的代码拉下来,然后安装依赖,再运行main.py注入Python之后,修改robot.py, 添加如下代码控制台就会打印日志了:

    from module import HookLog
    
    h = HookLog()
    h.hook() 
    

    github的代码更新了3.9.8.153.9.8.12两个版本,如果有更新的版本,请提issue。

    64位代码

    from py_process_hooker import Hook
    from py_process_hooker.winapi import *
    

    x64dbg打上断点,可以看到RDX是代码路径,而RDX是函数的第二个参数。因为获取不到寄存器,所以返回地址就拿不到了。

    返回值如下, 也是char数组:

    定义回调函数,日志函数有12个参数,我就用args来代替了:

    def hook_log_callback(*args):
        # 读取第二个参数的代码路径
        c_code_file = (c_char * MAX_PATH).from_address(args[1])
        code_file = c_code_file.value.decode()
        # 调用被hook函数,至于为什么要这么调请看编译和讲解Detour那一篇
        ret = lp_log_func(c_log_addr.value)(*args)
        # 读取返回值中的日志信息
        c_log_info = (c_char * 1000).from_address(ret)
        log_info = c_log_info.value.decode()
        print(f"文件路径: {code_file}, 日志信息: {log_info}")
        return ret
    

    开始hook

    log_addr = GetModuleHandleW("WeChatWin.dll") + 0x13D6380
    # 定义一个保存日志函数地址的指针
    c_log_addr = c_uint64(log_addr)
    # 定义函数类型
    lp_log_func = CFUNCTYPE(c_uint64, c_uint64, c_uint64, c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64,c_uint64)
    
    hooker = Hook()
    # 注意c_log_addr的生命周期,不能被垃圾回收机制回收
    hook.hook(c_log_addr, lp_log_func, hook_log_callback)
    
    

    代码更新

    以后微信相关的代码统一到下面的仓库更新:

    • github:https://github.com/kanadeblisst00/WeChat-PyRobot
    • 国内仓库: http://www.pygrower.cn:21180/kanadeblisst/WeChat-PyRobot

    32位和64位hook的代码封装成库并发布到pypi,可以通过pip install py_process_hooker安装或者pip install --upgrade py_process_hooker更新,具体操作请看仓库说明。

    • github: https://github.com/kanadeblisst00/py_hooker
    • 国内仓库: http://www.pygrower.cn:21180/kanadeblisst/py_hooker

    其实微信相关的代码也可以发布到pypi,后面代码稳定下来再看要不要发布。因为目前需要频繁更新,比较麻烦。

  • 相关阅读:
    全球汽车安全气囊芯片总体规模分析
    全方位详解Java:技术体系与实战项目
    如何成为小型咖啡店的优秀老板?需具备这6个特质
    清华深圳2024届推免拟录取名单
    【极简python】第六章 for循环与while循环
    C++ MiniZip实现目录压缩与解压
    java毕业设计软件源代码SSM家庭理财|个人理财管理系统|记账系统
    软件测试项目实战经验附视频以及源码【商城项目,app项目,电商项目,银行项目,医药项目,金融项目】(web+app+h5+小程序)
    【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
    【源码解析】Spring源码解读-beanFactory和Bean的后置处理器流程
  • 原文地址:https://www.cnblogs.com/kanadeblisst/p/17928132.html