目录修整

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

  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,后面代码稳定下来再看要不要发布。因为目前需要频繁更新,比较麻烦。

【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志的更多相关文章

  1. Python之路【第七篇】:线程、进程和协程

    Python之路[第七篇]:线程.进程和协程   Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 1 ...

  2. 如何知道我 的python是32位还是64位的?

    方法一: 打开IDLE,看第一行提示,例如: 32位系统是这样的 Python 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:38:48) [MSC v.19 ...

  3. 《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)

    1.简介 TestNG为日志记录和报告提供的不同选项.现在,宏哥讲解分享如何开始使用它们.首先,我们将编写一个示例程序,在该程序中我们将使用 ITestListener方法进行日志记录. 2.Test ...

  4. Python之路【第七篇】python基础 之socket网络编程

    本篇文章大部分借鉴 http://www.cnblogs.com/nulige/p/6235531.html python socket  网络编程 一.服务端和客户端 BS架构 (腾讯通软件:ser ...

  5. Python之路【第七篇续】:I/O多路复用

    回顾原生Socket 一.Socket起源: socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作. socket就是该模式的 ...

  6. Python之路【第七篇】:常用模块

    一. 模块介绍 1. 什么是模块 在前面的几个章节中我们基本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了. 为此 Python ...

  7. Python之路【第七篇续】:进程、线程、协程

    Socket Server模块 SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端.即:每个客户端请求连接到服务器时 ...

  8. 【Python之路】第七篇--Python基础之面向对象及相关

    面向对象基础 基础内容介绍详见一下两篇博文: 面向对象初级篇 面向对象进阶篇 其他相关 一.isinstance(obj, cls) 检查obj是否是类 cls 的对象 class Foo(objec ...

  9. Python学习笔记【第七篇】:文件及文件夹操作

     介绍 我们用pytthon.C#.Java等这些编程语言,想要把文件(文字.视频....)永久保存下来就必须将文件写入到硬盘中,这就需要我们应用程序去操作硬件,我们这些编程语言是无法直接操作硬件的. ...

  10. Python之路【第七篇】:初识Socket

    What is Socket 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制, ...

随机推荐

  1. 《Hadoop3.X大数据开发实战(视频教学版)》新书来啦!

  2. oracle-应用连接数激增测试

    在生产环境遇见过表的连接数过高,导致系统宕机的问题,操作上是由于在大表上建立索引,造成全表锁.故手动在表加表级锁,表上的应用不停,查看Oracle数据库连接数是否激增. 1 应用正常运行,查看当前数据 ...

  3. Django-rest-framework框架——Web应用模式、API接口、接口测试工具(Postman)、RESTfulAPI规范、序列化、drf、环境安装与配置、CBV源码分析、 APIView

    @ 目录 一 Web应用模式 1.1 前后端不分离 1.2 前后端分离 二 API接口 三 接口测试工具:Postman 四 RESTful API规范(背诵牢记) 4.1 数据的安全保障 4.2 接 ...

  4. .NET 数据库大数据 方案(插入、更新、删除、查询 、插入或更新)

    1.功能介绍 (需要版本5.0.45) 海量数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数 ...

  5. Jellyfin Documentation

    Skip to main content     Introduction On this page Welcome to the Jellyfin Documentation Jellyfin is ...

  6. LVS+keepalived配置高可用架构和负载均衡机制(1)

    一.基础知识 1. 四层负载均衡(基于IP+端口的负载均衡) 所谓四层负载均衡,也就是主要通过报文中的目标ip地址和端口,再加上负载均衡设备设置的服务器选择方式(分发策略,轮询),决定最终选择的内部服 ...

  7. 若依(ruoyi)开源系统保姆级实践-完成第一个页面

    一.案例描述 若依官网文档地址:http://doc.ruoyi.vip/ruoyi/document/hjbs.html 本教程主要内容,自定义数据库表,使用若依开源系统生成代码并配置权限. 若依环 ...

  8. Isito 入门(九):安全认证

    本教程已加入 Istio 系列:https://istio.whuanle.cn 目录 7,认证 Peer Authentication PeerAuthentication 的定义 实验 Reque ...

  9. HTML5的重要内容-1

    HTML学习笔记-1 (一):first-child和:first-of-type :first-child第一个元素 :first-of-type第一个某种类型元素 (二):only-child和: ...

  10. Go 方法介绍,理解“方法”的本质

    Go 方法介绍,理解"方法"的本质 目录 Go 方法介绍,理解"方法"的本质 一.认识 Go 方法 1.1 基本介绍 1.2 声明 1.2.1 引入 1.2.2 ...