【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志
目錄修整
目前的系列目錄(后面會(huì)根據(jù)實(shí)際情況變動(dòng)):
- 在windows11上編譯python
- 將python注入到其他進(jìn)程并運(yùn)行
- 注入Python并使用ctypes主動(dòng)調(diào)用進(jìn)程內(nèi)的函數(shù)和讀取內(nèi)存結(jié)構(gòu)體
- 調(diào)用匯編引擎實(shí)戰(zhàn)發(fā)送文本和圖片消息(支持32位和64位微信)
- 允許Python加載運(yùn)行py腳本且支持熱加載
- 利用匯編和反匯編引擎寫一個(gè)x86任意地址hook,實(shí)戰(zhàn)Hook微信日志
- 封裝Detours為dll,用于Python中x64函數(shù) hook,實(shí)戰(zhàn)Hook微信日志
- 實(shí)戰(zhàn)32位和64位接收消息和消息防撤回
- 實(shí)戰(zhàn)讀取內(nèi)存鏈表結(jié)構(gòu)體(好友列表)
- 做一個(gè)僵尸粉檢測(cè)工具
- 根據(jù)bug反饋和建議進(jìn)行細(xì)節(jié)上的優(yōu)化
- 其他功能看心情加
上上篇文章說的以后只更新32位版本這句話收回,以后會(huì)同時(shí)更新32位和64位的最新版本,已經(jīng)可以在Python中使用Detours來hook 64位版本。
為了加快進(jìn)度,第六篇和第七篇同一天發(fā)布,這篇文章為使用總結(jié),想知道hook原理的可以看同時(shí)間發(fā)布的其他幾篇文章。
溫馨提示:本次發(fā)布的這幾篇文章都是偏技術(shù),想獲取成品直接使用的可以等下一篇文章(實(shí)戰(zhàn)32位和64位接收消息和消息防撤回)
另外,這篇文章開始建群,請(qǐng)關(guān)注github或者公眾號(hào)菜單欄
封裝好的Hook庫(kù)
32位程序的Hook
hook的參數(shù)有兩個(gè):內(nèi)存地址和回調(diào)函數(shù)?;卣{(diào)函數(shù)的參數(shù)是一個(gè)包含x86所有寄存器的結(jié)構(gòu)體指針,沒有返回值。結(jié)構(gòu)體的定義如下:
class RegisterContext(Structure):
_fields_ = [
('EFLAGS', DWORD),
('EDI', DWORD),
('ESI', DWORD),
('EBP', DWORD),
('ESP', DWORD),
('EBX', DWORD),
('EDX', DWORD),
('ECX', DWORD),
('EAX', DWORD),
]
一個(gè)簡(jiǎn)單的Hook 示例:
def default_hook_log_callback(pcontext):
# 獲取指針內(nèi)容,獲取的context就是RegisterContext類型了
context:RegisterContext = pcontext.contents
# 取eax寄存器的值
eax = context.EAX
print("當(dāng)前eax寄存器的值: ", eax)
addr = 0x100000
hooker = Hook()
hooker.hook(addr, hook_log_callback_enter)
context這個(gè)結(jié)構(gòu)體獲取的就是當(dāng)執(zhí)行到這個(gè)地址時(shí)的寄存器的值,這個(gè)和你用x32dbg看到的寄存器的值是一樣的。值的類型都定義成DWORD,如果寄存器是類型是其他類型,比如字符串或結(jié)構(gòu)體,你需要在Python里做相應(yīng)的轉(zhuǎn)換,可以參考下面Hook日志的代碼
你同樣可以在回調(diào)函數(shù)里修改這個(gè)指針中寄存器的值,它會(huì)反映到實(shí)際的寄存器,案例的話會(huì)在消息防撤回那一篇文章演示。
64位的Hook
因?yàn)?4位hook是封裝的Detour,比32位需要多定義一個(gè)函數(shù)指針,而且只能hook函數(shù)。所以hook之前需要知道被Hook的函數(shù)參數(shù)有幾個(gè),類型如果不知道的話,可以像上面一樣都定義成c_uint64。
回調(diào)函數(shù)的參數(shù)跟被Hook函數(shù)的參數(shù)必須一樣,如果參數(shù)很多,你也可以用*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)
另外,回調(diào)函數(shù)的返回值類型也需要和被Hook函數(shù)一樣,一般都是先調(diào)用原函數(shù)獲取返回值然后返回。如果返回錯(cuò)誤類型的返回值,進(jìn)程會(huì)崩潰。
案例
為什么要選擇Hook日志做案例?日志是多線程打印的,如果Hook日志沒有問題的話,其他任何位置的Hook基本都不會(huì)有問題。
效果
hook后的效果如下:
32位代碼
from py_process_hooker import Hook
from py_process_hooker.winapi import *
base = GetModuleHandleW("WeChatWin.dll")
先定義回調(diào)函數(shù),因?yàn)槲倚枰瑫r(shí)獲取參數(shù)和返回值,所以要hook兩個(gè)地方(函數(shù)頭和函數(shù)尾)。
用x32dbg在日志函數(shù)頭位置下個(gè)斷點(diǎn),看起來有兩個(gè)有用的信息:EDX的代碼路徑和esp的函數(shù)返回地址。
定義回調(diào)函數(shù):
def hook_log_callback_enter(pcontext):
context = pcontext.contents
esp = context.ESP
# 計(jì)算調(diào)用日志函數(shù)的地址偏移
esp_call_offset = c_ulong.from_address(esp).value - base
# 獲取日志中的代碼文件路徑
edx = context.EDX
# 類型是char數(shù)組,ctypes定義是(c_char * n), 這個(gè)*是Python中的乘號(hào),
# 如果是char*指針 ctypes則定義為c_char_p
c_code_file = (c_char * MAX_PATH).from_address(edx)
code_file = c_code_file.value.decode()
print(f"調(diào)用地址: 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一個(gè)Hook類hook這兩個(gè)位置:
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)
因?yàn)樾枰С譄峒虞d,所以在hook之前先調(diào)用一下unhook,這樣你修改代碼就會(huì)生效新的hook。
使用
你想hook日志的話,先將github的代碼拉下來,然后安裝依賴,再運(yùn)行main.py注入Python之后,修改robot.py, 添加如下代碼控制臺(tái)就會(huì)打印日志了:
from module import HookLog
h = HookLog()
h.hook()
github的代碼更新了3.9.8.15和3.9.8.12兩個(gè)版本,如果有更新的版本,請(qǐng)?zhí)醝ssue。
64位代碼
from py_process_hooker import Hook
from py_process_hooker.winapi import *
x64dbg打上斷點(diǎn),可以看到RDX是代碼路徑,而RDX是函數(shù)的第二個(gè)參數(shù)。因?yàn)楂@取不到寄存器,所以返回地址就拿不到了。
返回值如下, 也是char數(shù)組:
定義回調(diào)函數(shù),日志函數(shù)有12個(gè)參數(shù),我就用args來代替了:
def hook_log_callback(*args):
# 讀取第二個(gè)參數(shù)的代碼路徑
c_code_file = (c_char * MAX_PATH).from_address(args[1])
code_file = c_code_file.value.decode()
# 調(diào)用被hook函數(shù),至于為什么要這么調(diào)請(qǐng)看編譯和講解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
# 定義一個(gè)保存日志函數(shù)地址的指針
c_log_addr = c_uint64(log_addr)
# 定義函數(shù)類型
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的生命周期,不能被垃圾回收機(jī)制回收
hook.hook(c_log_addr, lp_log_func, hook_log_callback)
代碼更新
以后微信相關(guān)的代碼統(tǒng)一到下面的倉(cāng)庫(kù)更新:
- github:
https://github.com/kanadeblisst00/WeChat-PyRobot - 國(guó)內(nèi)倉(cāng)庫(kù):
http://www.pygrower.cn:21180/kanadeblisst/WeChat-PyRobot
32位和64位hook的代碼封裝成庫(kù)并發(fā)布到pypi,可以通過pip install py_process_hooker安裝或者pip install --upgrade py_process_hooker更新,具體操作請(qǐng)看倉(cāng)庫(kù)說明。
- github:
https://github.com/kanadeblisst00/py_hooker - 國(guó)內(nèi)倉(cāng)庫(kù):
http://www.pygrower.cn:21180/kanadeblisst/py_hooker
其實(shí)微信相關(guān)的代碼也可以發(fā)布到pypi,后面代碼穩(wěn)定下來再看要不要發(fā)布。因?yàn)槟壳靶枰l繁更新,比較麻煩。
總結(jié)
以上是生活随笔為你收集整理的【Python微信机器人】第六七篇: 封装32位和64位Python hook框架实战打印微信日志的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cad光标闪烁没办法绘图如何解决
- 下一篇: 淘宝拼团要自己找人吗(淘宝海外全球站首页