目錄修整
目前的系列目錄(后面會(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)用進(jìn)程內(nèi)的任意函數(shù)
- 利用beaengine反匯編引擎的c接口寫(xiě)一個(gè)pyd庫(kù),用于實(shí)現(xiàn)inline hook
- 利用beaengine反匯編引擎的python接口寫(xiě)一個(gè)py庫(kù),用于實(shí)現(xiàn)inline hook
- 注入python到微信實(shí)現(xiàn)簡(jiǎn)單的收發(fā)消息
- Bug修復(fù)和細(xì)節(jié)優(yōu)化,允許Python加載運(yùn)行py腳本并且支持熱加載
- 讀取微信內(nèi)存中的好友聯(lián)系人列表的信息結(jié)構(gòu)體數(shù)據(jù)
- 做一個(gè)僵尸粉檢測(cè)工具
ctypes的主要功能
ctypes是Python與c寫(xiě)的文件做交互的庫(kù),能和Python直接交互的也就是動(dòng)態(tài)庫(kù)了。所以在Windows上主要是調(diào)用dll,Linux上則是調(diào)用so。
不過(guò),在這個(gè)系列文章里,它的作用稍微有些不同。因?yàn)镻ython已經(jīng)被注入到其他進(jìn)程,可以用ctypes隨意操作其他進(jìn)程的數(shù)據(jù)和調(diào)用其他進(jìn)程里的函數(shù),相對(duì)于用c寫(xiě)的dll注入后,只需要把c的接口改成Python的。這樣就能動(dòng)態(tài)操作,不需要頻繁改動(dòng)dll代碼,注入卸載了
同時(shí)它還能調(diào)用其他進(jìn)程里的任意函數(shù),不過(guò)默認(rèn)只能調(diào)用stdcall
和cdecl
兩種調(diào)用約定的函數(shù)。如果不是這兩種調(diào)用約定,則需要使用內(nèi)聯(lián)匯編來(lái)調(diào)用。當(dāng)然Python無(wú)法直接內(nèi)聯(lián)匯編,但可以通過(guò)匯編引擎將匯編指令翻譯成機(jī)器能識(shí)別的機(jī)器碼寫(xiě)入到內(nèi)存,達(dá)到內(nèi)聯(lián)匯編的效果。也可以不用匯編引擎,直接寫(xiě)機(jī)器碼到內(nèi)存,只要你能記得匯編指令代表的機(jī)器碼(人肉匯編引擎)。
與進(jìn)程交互
對(duì)于調(diào)用dll相關(guān)的功能,我這里就不多贅述了,之前寫(xiě)的一篇文章里有:Python基礎(chǔ)庫(kù)-ctypes
這里我主要說(shuō)下ctypes與進(jìn)程交互方面,比如讀取內(nèi)存結(jié)構(gòu)體,調(diào)用內(nèi)存中的函數(shù)等
寫(xiě)一個(gè)測(cè)試程序
先自己寫(xiě)一個(gè)測(cè)試程序,然后在自己的程序測(cè)試,這樣可以避免很多錯(cuò)誤,也方便調(diào)試。簡(jiǎn)單寫(xiě)了幾個(gè)函數(shù)和結(jié)構(gòu)體測(cè)試,代碼如下:
typedef int(*cdecl_add_pointer)(int, int);
typedef int(__stdcall *stdcall_add_pointer)(int, int);
struct CString
{
wchar_t* s = nullptr;
size_t len = 0;
CString(wchar_t* ss) {
s = ss;
len = wcslen(ss);
}
};
CString ccs((wchar_t*)L"aaaaaa這是個(gè)全局變量結(jié)構(gòu)體");
int cdecl_add(int a, int b) {
std::wcout << L"cdecl調(diào)用約定\n";
return a + b;
}
int __stdcall stdcall_add(int a, int b) {
std::wcout << L"stdcall調(diào)用約定\n";
return a + b;
}
int add_callback(stdcall_add_pointer add, int a, int b) {
std::wcout << L"add_callback \n";
return add(a, b);
}
int console_print(CString* cs) {
std::wcout << L"print CString: ";
std::wcout << cs->s;
std::wcout << L"\n";
return cs->len;
}
調(diào)用進(jìn)程內(nèi)的函數(shù)
這里就用上一篇的pyexe.dll來(lái)將Python注入到目標(biāo)進(jìn)程。
現(xiàn)在開(kāi)始調(diào)用cdecl_add和stdcall_add這兩個(gè)函數(shù),首先需要找到他們的地址偏移,上面的函數(shù)里都有一個(gè)字符串,這也是我為了方便定位刻意寫(xiě)的。
在x32dbg里搜索字符串,就能定位這兩個(gè)函數(shù),比如cdecl_add:
得出cdecl_add函數(shù)的偏移就是00AF4190-00AE0000
, 00AE0000是exe的基址。同理可以知道stdcall_add的基址為0x00AF43B0 - 0x00AE0000
先定義一個(gè)GetModuleHandleW
函數(shù)用于獲取exe的基址
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
GetModuleHandleW = kernel32.GetModuleHandleW
GetModuleHandleW.argtypes = (ctypes.c_wchar_p, )
GetModuleHandleW.restype = ctypes.c_int
base = GetModuleHandleW("CtypesTest.exe")
以下幾行代碼就是調(diào)用cdecl_add
的全部代碼,看注釋一行一行解釋?zhuān)?/p>
# 定義函數(shù)指針類(lèi)型,第一個(gè)參數(shù)是返回值類(lèi)型,后面的都是參數(shù)類(lèi)型
cdecl_add_pfunc = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)
# 函數(shù)的偏移
cdecl_add_offset = 0x00AF4190 - 0x00AE0000
# 通過(guò)基址和偏移得到當(dāng)前函數(shù)所在內(nèi)存地址,然后傳給cdecl_add_pfunc就能得到這個(gè)函數(shù)
cdecl_add = cdecl_add_pfunc(base + cdecl_add_offset)
# 傳入相應(yīng)的參數(shù)就能調(diào)用成功
print("cdecl_add: ", cdecl_add(111, 222))
可以看到結(jié)果成功輸出,也沒(méi)有報(bào)錯(cuò)。沒(méi)有打印cdecl調(diào)用約定
是因?yàn)槲覀冊(cè)谧⑷隤ython是重定向了stdout,如果想要打印目標(biāo)進(jìn)程的輸出則需要使用上一篇文章提到的CPython接口重定向stdout。
而調(diào)用stdcall_add
和它基本一樣,將 ctypes.CFUNCTYPE
改成ctypes.WINFUNCTYPE
即可
構(gòu)建結(jié)構(gòu)體并調(diào)用函數(shù)
接著我們開(kāi)始調(diào)用console_print
,它的參數(shù)類(lèi)型是一個(gè)結(jié)構(gòu)體指針,所以要先在Python構(gòu)建出結(jié)構(gòu)體
ctypes定義結(jié)構(gòu)體代碼如下:
class CString(ctypes.Structure):
_fields_ = [
('s', ctypes.c_wchar_p),
('len', ctypes.c_uint)
]
定義console_print
函數(shù):
console_print_pfunc = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(CString))
console_print_offset = 0x00AF2F10 - 0x00AE0000
console_print = console_print_pfunc(base + console_print_offset)
創(chuàng)建結(jié)構(gòu)體并賦值
cs = CString()
s = "Python結(jié)構(gòu)體字符串"
cs.s = ctypes.c_wchar_p(s)
cs.len = len(s)
為了確保創(chuàng)建的結(jié)構(gòu)體和目標(biāo)進(jìn)程里的一樣,可以先在Python控制臺(tái)創(chuàng)建,然后在x32dbg里查看。
這里我為了避免一直要輸入代碼,使用import sys;sys.path.append(r"T:\Code\PyRobot\part3\py_code")
來(lái)將目錄添加到sys.path,然后導(dǎo)入我寫(xiě)的代碼import testa
。
如果要重新導(dǎo)入:import importlib;importlib.reload(testa)
,查看Python構(gòu)建的結(jié)構(gòu)體內(nèi)存地址有三種方法:
print("ctypes.byref: ", ctypes.byref(cs))
print("ctypes.addressof: ", hex(ctypes.addressof(cs)))
print("ctypes.cast: ", hex(ctypes.cast(ctypes.pointer(cs), ctypes.c_void_p).value))
效果如下:
可以看到cs的內(nèi)存地址是0x1570d40
,然后在x32dbg里查看這個(gè)內(nèi)存地址。
在命令里輸入dump 0x1570d40
或者打開(kāi)幫助->計(jì)算器,輸入這個(gè)地址,然后在內(nèi)存窗口打開(kāi):
這個(gè)地址的內(nèi)容就是Python構(gòu)建出的結(jié)構(gòu)體,如果不清楚結(jié)構(gòu)體在內(nèi)存中長(zhǎng)啥樣,可以把c代碼創(chuàng)建的結(jié)構(gòu)體也打印出來(lái),然后在x32dbg中查看
最后調(diào)用這個(gè)函數(shù),ctypes.byref的作用是傳遞指針的引用,ctypes.pointer也可以,它是構(gòu)造一個(gè)新的指針:
result = console_print(ctypes.byref(cs))
print("console_print result: ", result)
調(diào)用成功,說(shuō)明結(jié)構(gòu)體構(gòu)造的沒(méi)問(wèn)題:
讀取內(nèi)存中的全局結(jié)構(gòu)體
一樣是先計(jì)算偏移
# 全局變量的內(nèi)存地址一般偏移是固定的,如果是函數(shù)內(nèi)的局部變量就不能這么計(jì)算了
ccs_offset = 0x00AFE2D0 - 0x00AE0000
css_addr = base + ccs_offset
然后從地址中讀取出結(jié)構(gòu)體里的字符串和整數(shù)
s = ctypes.c_wchar_p.from_address(css_addr)
l = ctypes.c_uint.from_address(css_addr + 0x4)
print("單獨(dú)讀取內(nèi)存結(jié)構(gòu)體: ", s.value, l)
更簡(jiǎn)單的方法就是直接轉(zhuǎn)為結(jié)構(gòu)體
css = CString.from_address(css_addr)
print("讀取整個(gè)結(jié)構(gòu)體: ", css.s, css.len)
執(zhí)行結(jié)果如下圖:
調(diào)用回調(diào)函數(shù)
先定義一個(gè)Python回調(diào)函數(shù)
def python_stdcall_add(a:int, b:int):
print("python_stdcall_add: ", a, b)
return a-b
定義add_callback函數(shù)
add_callback_pfunc = ctypes.CFUNCTYPE(ctypes.c_int, stdcall_add_pfunc, ctypes.c_int, ctypes.c_int)
add_callback_offset = 0x00AF40D0 - 0x00AE0000
add_callback = add_callback_pfunc(base + add_callback_offset)
因?yàn)榛卣{(diào)函數(shù)的類(lèi)型stdcall_add之前已經(jīng)定義了,這里就直接用了
result = add_callback(stdcall_add_pfunc(python_stdcall_add), 5, 2)
print("add_callback: ", result)
執(zhí)行結(jié)果:
本篇就到此結(jié)束了,其他更復(fù)雜的數(shù)據(jù)類(lèi)型在后面的實(shí)戰(zhàn)中再說(shuō)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-736666.html
本篇文章用到的文件和代碼
https://github.com/kanadeblisst00/PyRobot-part3文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-736666.html
到了這里,關(guān)于【Python微信機(jī)器人】第三篇:使用ctypes調(diào)用進(jìn)程函數(shù)和讀取內(nèi)存結(jié)構(gòu)體的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!