-
-
使用Python的ctypes调用目标进程函数和读取进程内存结构体
-
发表于:
2023-11-1 10:41
9907
-
使用Python的ctypes调用目标进程函数和读取进程内存结构体
该系列文章为Python基础教程,与逆向没有多大关系。文章提到的目标call都只会给出偏移,不会说明逆向过程。
目前的系列目录(后面会根据实际情况变动):
在windows11上编译python
将python注入到其他进程并运行
注入Python并使用ctypes主动调用进程内的函数和读取内存结构体
使用汇编引擎调用进程内的任意函数
利用beaengine反汇编引擎的c接口写一个pyd库,用于实现inline hook
利用beaengine反汇编引擎的python接口写一个py库,用于实现inline hook
注入python到微信实现简单的收发消息
Bug修复和细节优化,允许Python加载运行py脚本并且支持热加载
读取微信内存中的好友联系人列表的信息结构体数据
做一个僵尸粉检测工具
ctypes是Python与c写的文件做交互的库,能和Python直接交互的也就是动态库了。所以在Windows上主要是调用dll,Linux上则是调用so。
不过,在这个系列文章里,它的作用稍微有些不同。因为Python已经被注入到其他进程,可以用ctypes随意操作其他进程的数据和调用其他进程里的函数,相对于用c写的dll注入后,只需要把c的接口改成Python的。这样就能动态操作,不需要频繁改动dll代码,注入卸载了
同时它还能调用其他进程里的任意函数,不过默认只能调用`stdcall`和`cdecl`两种调用约定的函数。如果不是这两种调用约定,则需要使用内联汇编来调用。当然Python无法直接内联汇编,但可以通过汇编引擎将汇编指令翻译成机器能识别的机器码写入到内存,达到内联汇编的效果。也可以不用汇编引擎,直接写机器码到内存,只要你能记得汇编指令代表的机器码(人肉汇编引擎)。
对于调用dll相关的功能,我这里就不多赘述了,之前写的一篇文章里有:[Python基础库-ctypes](https://blog.csdn.net/Qwertyuiop2016/article/details/125841653)
这里我主要说下ctypes与进程交互方面,比如读取内存结构体,调用内存中的函数等
先自己写一个测试程序,然后在自己的程序测试,这样可以避免很多错误,也方便调试。简单写了几个函数和结构体测试,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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这是个全局变量结构体" );
int cdecl_add( int a, int b) {
std::wcout << L "cdecl调用约定\n" ;
return a + b;
}
int __stdcall stdcall_add( int a, int b) {
std::wcout << L "stdcall调用约定\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;
}
|
这里就用上一篇的pyexe.dll来将Python注入到目标进程。
现在开始调用cdecl_add和stdcall_add这两个函数,首先需要找到他们的地址偏移,上面的函数里都有一个字符串,这也是我为了方便定位刻意写的。
在x32dbg里搜索字符串,就能定位这两个函数,比如cdecl_add:

得出cdecl_add函数的偏移就是`00AF4190-00AE0000`, 00AE0000是exe的基址。同理可以知道stdcall_add的基址为`0x00AF43B0 - 0x00AE0000`
先定义一个`GetModuleHandleW`函数用于获取exe的基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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" )
```
以下几行代码就是调用`cdecl_add`的全部代码,看注释一行一行解释:
```python
cdecl_add_pfunc = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)
cdecl_add_offset = 0x00AF4190 - 0x00AE0000
cdecl_add = cdecl_add_pfunc(base + cdecl_add_offset)
print ( "cdecl_add: " , cdecl_add( 111 , 222 ))
|

可以看到结果成功输出,也没有报错。没有打印`cdecl调用约定`是因为我们在注入Python是重定向了stdout,如果想要打印目标进程的输出则需要使用上一篇文章提到的CPython接口重定向stdout。
而调用`stdcall_add`和它基本一样,将 `ctypes.CFUNCTYPE`改成`ctypes.WINFUNCTYPE`即可

接着我们开始调用`console_print`,它的参数类型是一个结构体指针,所以要先在Python构建出结构体
ctypes定义结构体代码如下:
1 2 3 4 5 | class CString(ctypes.Structure):
_fields_ = [
( 's' , ctypes.c_wchar_p),
( 'len' , ctypes.c_uint)
]
|
定义`console_print`函数:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-11-1 10:59
被Python成长路编辑
,原因: 修改格式