目錄修整
目前的系列目錄(後面會根據實際情況變動):
- 在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,後面程式碼穩定下來再看要不要釋出。因為目前需要頻繁更新,比較麻煩。