目录修整
目前的系列目录(后面会根据实际情况变动):
-
在windows11上编译python -
将python注入到其他进程并运行 -
注入Python并使用ctypes主动调用进程内的函数和读取内存结构体 -
调用汇编引擎实战发送文本和图片消息 -
允许Python加载运行py脚本且支持热加载,发布成pypi库,可pip安装 -
利用beaengine反汇编引擎的c接口写一个pyd库,用于实现inline hook -
利用beaengine反汇编引擎的python接口写一个py库,用于实现inline hook -
使用inline hook实战接收消息 -
读取微信内存中的好友联系人列表的信息结构体数据 -
根据bug反馈和建议进行细节上的优化 -
做一个僵尸粉检测工具 -
其他功能慢慢加
温馨提示
该项目仅做学习使用, 不建议用于实际生产中。因为可能会导致封号,如果想学习的,请使用小号进行测试。
文章篇幅过长,这里分为了两个部分:使用篇和讲解篇。如果不想知道原理,只是想体验一下的,可以看使用篇。
准备工作
需要的环境
当前官网下载的最新x86版本是3.9.7.28
(发文的时候,最新版本已经更新到了3.9.8.12
,代码中已经更新,同时支持两个版本),后面的文章也会使用该版本做为案例。
另外,Python也开始使用官网下载的安装版本(没什么区别,就是大家下载方便)。版本随意,只需要是32位的。anaconda创建的虚拟环境也可以。
原理
跟c语言写的发送消息对比,这种方式用来测试call挺方便的,不用去一直编译dll注入卸载,可以动态修改代码来测试。
实现方法和上一篇文章类似,都是调用进程内的函数,只是x86版本的微信函数大部分都不是stdcall和cdecl调用约定。而用ctypes调用这种函数就需要增加一个步骤:内存构建一个stdcall或cdecl的函数,再去调用这个构建好的函数。
先用汇编写一个简单的函数,在通过汇编引擎将汇编代码转成机器码,在申请一段可执行的内存,将机器码写入到这块内存,接着就可以用Python调用这个内存函数了。这个操作我在之前写的一篇文章里也有提过:用Python发送微信消息给好友(优化篇)。
相比之前的那篇文章,现在可以不用自己构造结构体和一些多余操作,因为Python所在进程已经和微信是同一个了。写的代码也会简短很多,也更容易看懂。
发消息函数汇编
先看看新版本发送文本消息的汇编代码,其实push 0x0
上面那个call也是发消息的函数之一,但因为只需要运行一次就可以,调不调用都可以:
这个函数的调用约定很像fastcall,因为使用ecx和edx还有堆栈传参,但它又不是由被调用函数来平栈的,它是由调用者使用add来平衡堆栈。
开始写代码
汇编指令转机器码
首先需要一个汇编引擎将汇编转为机器码,不然我们还得在Python里手写机器码。这里我选择的是keystone
,安装方法pip install keystone-engine
。
转换的代码如下:
import keystone
def asm2code(asm_code):
'''汇编指令转机器码'''
ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_32)
bytes_code, _ = ks.asm(asm_code, as_bytes=True)
return bytes_code
定义微信使用的结构体字符串
class GeneralStructW(Structure):
_fields_ = [
('content', c_wchar_p),
('content_len1', c_uint32),
('content_len2', c_uint32),
('null_word1', c_int),
('null_word2', c_int)
]
# 缓存大小给大一点,要是给小了微信会崩溃
buffer_len = 0x500
# 缓存用的数据类型无所谓,这里就用DWORD数组
buffer_struct = DWORD * buffer_len
定义内存函数
正常情况下的函数都需要保存ebp环境,汇编指令如下:
push ebp;
mov ebp, esp;
... // 在这里写你的操作
pop ebp;
ret;
当然如果你还想保存edi的话,就再加一个push edi
和pop edi
。如果函数有参数的话,ebp+0x8
为第一个参数,ebp+0xC
为第二个参数,依次加4。
计算函数地址
# 获取WeChatWin.dll基址
wxwin_base = GetModuleHandleW("WeChatWin.dll")
#
text_call1 = c_int(wxwin_base + 0xD49990)
text_call2 = c_int(wxwin_base + 0x79D9A0)
开始写汇编
就是把微信里发送文本的汇编指令稍微改一下,然后再把变量改成参数,这里需要注意:text_call1不能在调用内存函数之前被垃圾回收机制给回收了,不能就会报很奇怪的错误
# 先获取text_call的地址
call1_addr = addressof(text_call1)
call2_addr = addressof(text_call2)
# 函数的汇编指令
asm_code = f'''
push ebp;
mov ebp, esp;
push 0x0;
push 0x0;
push 0x1;
push 0x1;
mov eax, dword ptr [ebp+0x8]; // @的结构体
push eax;
mov eax, dword ptr [ebp+0xC]; // 发送的文本内容
push eax;
mov edx, dword ptr [ebp+0x10]; // wxid
mov ecx, dword ptr [ebp+0x14]; // 缓存
call dword ptr ds:[{call1_addr:#02x}];
add esp, 0x18;
mov ecx, dword ptr [ebp+0x14];
call dword ptr ds:[{call2_addr:#02x}];
pop ebp;
ret;
'''
申请内存将机器码写进去
# 汇编转机器码
shellcode = asm2code(asm_code.encode())
# 申请一块可读可写可执行的内存区域,大写是4096个字节
mem_buf = VirtualAlloc(0, 4096, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
print("构建发送文本函数的内存地址: ", hex(mem_buf))
# 将机器码写到这块内存里
memmove(mem_buf, shellcode, len(shellcode))
用Python封装这个函数调用
args_type = (
buffer_struct, # @的结构体,这里用DWORD数组
POINTER(GeneralStructW), # 发送的文本内容结构体
POINTER(GeneralStructW), # wxid结构体
buffer_struct # 缓存,也用DWORD数组
)
mem_call = CFUNCTYPE(void, *args_type)(mem_buf)
构造结构体参数
# 构建微信id结构体
wxid = "filehelper" # 这是文件传输助手的wxid
wxid_struct = GeneralStructW()
c_wxid = c_wchar_p(wxid)
wxid_struct.content = c_wxid
wxid_struct.content_len1 = len(wxid)
wxid_struct.content_len2 = len(wxid)
# 构建文本结构体
text_struct = GeneralStructW()
text = "测试消息!"
c_text = c_wchar_p(text)
text_struct.content = c_text
text_struct.content_len1 = len(text)
text_struct.content_len2 = len(text)
# 创建DWROD数组,并赋值为0。相对于c语言中的 DWORD buffer1[buffer_len] = {0};
buffer1 = buffer_struct(*[0]*buffer_len)
buffer2 = buffer_struct(*[0]*buffer_len)
你可以使用addressof输出构建的结构体地址,然后在x32dbg里查看是否和发消息的参数一样的
最后发送消息
mem_call(buffer1, byref(text_struct), byref(wxid_struct), buffer2)
发送图片基本上是一样的,图片可以是正常的图片格式,也可以是微信加密的dat格式。这里就不重复放代码了,感兴趣的可以看github的源码
64位程序
这里稍微提一下怎么注入Python到64位程序,并且调用内部函数。因为之后的inline hook我不会64位的,所以只是简单的说一遍,后面的内容就没有64位的参与了。也说不准哪天我就找到可以在Python中hook 64位的方法,找到的话到时候就会更新。
微信的版本就选择我已经安装的3.9.7.29
,发消息的汇编代码如下:
在64位程序里,没有了奇奇怪怪的调用约定,ecx、edx、r8、r9依次是前四个参数,后面的参数通过堆栈传递。所以调用x64的函数更简单,不用写汇编代码。
定义结构体
class GeneralStructW(Structure):
_fields_ = [
('content', c_wchar_p),
('content_len1', c_uint64),
('content_len2', c_uint64),
('null_word1', c_uint64),
('null_word2', c_uint64)
]
# 缓存大小给大一点,要是给小了微信会崩溃
buffer_len = 0x500
# 缓存用的数据类型无所谓,这里就用DWORD数组
buffer_struct = DWORD * buffer_len
定义发消息函数
args_type = (
buffer_struct,
POINTER(GeneralStructW),
POINTER(GeneralStructW),
buffer_struct,
c_int64,
c_int64,
c_int64,
c_int64,
)
wxwin_base = GetModuleHandleW("WeChatWin.dll")
sendmsg_call_addr = wxwin_base + 0x1024370
sendmsg_call = CFUNCTYPE(void, *args_type)(sendmsg_call_addr)
构建参数
wxid = "filehelper" # 这是文件传输助手的wxid
wxid_struct = GeneralStructW()
c_wxid = c_wchar_p(wxid)
wxid_struct.content = c_wxid
wxid_struct.content_len1 = len(wxid)
wxid_struct.content_len2 = len(wxid)
text_struct = GeneralStructW()
text = "测试消息!"
c_text = c_wchar_p(text)
text_struct.content = c_text
text_struct.content_len1 = len(text)
text_struct.content_len2 = len(text)
buffer1 = buffer_struct(*[0]*buffer_len)
buffer2 = buffer_struct(*[0]*buffer_len)
发送消息
sendmsg_call(buffer1, byref(wxid_struct), byref(text_struct), buffer2, 1, 1, 0, 0)
下篇预告
下一篇先实现一下热加载Python脚本,毕竟在控制台下操作不太方便。如果注入Python之后能直接运行我的Python代码,并且可以在我修改代码之后,可以自动重新加载,那样才方便测试。顺便把代码发布到pypi上,支持用pip安装,到时候就可以import使用了
github源码
https://github.com/kanadeblisst00/PyRobot-part4
原文始发于微信公众号(Python成长路):【Python微信机器人】第四篇:实战发送文本和图片消息(讲解篇)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196110.html