【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志
目录修整
目前的系列目录(后面会根据实际情况变动):
- 在windows11上编译python
- 将python注入到其他进程并运行
- 注入Python并使用ctypes主动调用进程内的函数和读取内存结构体
- 调用汇编引擎实战发送文本和图片消息(支持32位和64位微信)
- 允许Python加载运行py脚本且支持热加载
- 利用汇编和反汇编引擎写一个x86任意地址hook,实战Hook微信日志
- 封装Detours为dll,用于Python中x64函数 hook,实战Hook微信日志
- 实战32位和64位接收消息和消息防撤回
- 实战读取内存链表结构体(好友列表)
- 做一个僵尸粉检测工具
- 根据bug反馈和建议进行细节上的优化
- 其他功能看心情加
上上篇文章说的以后只更新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.15
和3.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框架实战打印微信日志的更多相关文章
- Python之路【第七篇】:线程、进程和协程
Python之路[第七篇]:线程.进程和协程 Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 1 ...
- 如何知道我 的python是32位还是64位的?
方法一: 打开IDLE,看第一行提示,例如: 32位系统是这样的 Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.19 ...
- 《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)
1.简介 TestNG为日志记录和报告提供的不同选项.现在,宏哥讲解分享如何开始使用它们.首先,我们将编写一个示例程序,在该程序中我们将使用 ITestListener方法进行日志记录. 2.Test ...
- Python之路【第七篇】python基础 之socket网络编程
本篇文章大部分借鉴 http://www.cnblogs.com/nulige/p/6235531.html python socket 网络编程 一.服务端和客户端 BS架构 (腾讯通软件:ser ...
- Python之路【第七篇续】:I/O多路复用
回顾原生Socket 一.Socket起源: socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作. socket就是该模式的 ...
- Python之路【第七篇】:常用模块
一. 模块介绍 1. 什么是模块 在前面的几个章节中我们基本上是用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了. 为此 Python ...
- Python之路【第七篇续】:进程、线程、协程
Socket Server模块 SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端.即:每个客户端请求连接到服务器时 ...
- 【Python之路】第七篇--Python基础之面向对象及相关
面向对象基础 基础内容介绍详见一下两篇博文: 面向对象初级篇 面向对象进阶篇 其他相关 一.isinstance(obj, cls) 检查obj是否是类 cls 的对象 class Foo(objec ...
- Python学习笔记【第七篇】:文件及文件夹操作
介绍 我们用pytthon.C#.Java等这些编程语言,想要把文件(文字.视频....)永久保存下来就必须将文件写入到硬盘中,这就需要我们应用程序去操作硬件,我们这些编程语言是无法直接操作硬件的. ...
- Python之路【第七篇】:初识Socket
What is Socket 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制, ...
随机推荐
- xlsx和path的运用
从后端获取Excel模板 app.get('/api/download-template', (req, res) => { const templatePath = path.join(__d ...
- 交叉编译 Qt5.12 armv8(aarch64) 带 WebEngine - NVIDIA JETSON TX2
编译平台 Windows10 WSL2 Debian,目标平台 NVIDIA JETSON TX2 (注:Ubuntu <= 16.04 会出现 libclang < 3.8 的问题) 下 ...
- 破局DevOps|8大北极星指标指引研发效能方向
放弃那些动辄就上百个的研发度量指标吧,8大北极星指标指引你的研发效能方向,1个北极星指标公式让你清晰了解公司研发效能现状. 每当研发效能/DevOps业务做规划的时候,有的人就会毫无头绪,不知道如何 ...
- 支持JDK19虚拟线程的web框架,之四:看源码,了解quarkus如何支持虚拟线程
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<支持JDK19虚拟线程的web ...
- modbus转profinet网关连接ABB变频器在博图程序案例
modbus转profinet网关连接ABB变频器在博图程序案例 在博图里PLC无需编程利用兴达易控modbus转Profinet网关将ABB变频器接入到西门子网络中,用到设备为西门子1200PLC, ...
- 实战指南,SpringBoot + Mybatis 如何对接多数据源
本文分享自华为云社区 <实战指南,SpringBoot + Mybatis 如何对接多数据源>,作者:战斧. 在我们开发一些具有综合功能的项目时,往往会碰到一种情况,需要同时连接多个数据库 ...
- docker入门加实战—docker常见命令
docker入门加实战-docker常见命令 在介绍命令之前,先用一副图形象的展示一下docker的命令: 常见命令 docker的常见命令和文档地址如下表: 命令 说明 文档地址 docker pu ...
- 外层div随内层div高度自适应
首先说一下textarea的高度随文字的内容自适应,用div模拟textarea.直接看代码.其中 contenteditable="true"表示div可以编辑..主要是设置 o ...
- vue + tornado 个人博客项目简介
vue + tornado 个人博客项目简介 项目链接:https://www.freepd.top 项目链接:https://admin.freepd.top 项目简介 首页预览 本站接入了百度ap ...
- JAVA类的加载(4) ——类之间能够隔离&类占用的资源能回收
一.类加载体系