读 《Python灰帽子-黑客与逆向工程师的Python编程之道》笔记
其实这本书就是基于 ctypes 库的, 使用这个库, 可以调用动态链接库中函数、同时创建各种复杂的 C 数据类型和底层操作函数.
1. 调用动态链接库
学 C 的时候就学过函数的调用约定, 先来回顾一下
fastcall调用约定 | Cdecl调用约定 | StdCall调用约定 |
---|---|---|
mov eax, xxx mov ecx, xxx mov ebx, xxx CALL Message | PUSH parameter3 PUSH parameter2 PUSH parameter1 CALL Message ADD ESP,0CH | PUSH parameter3 PUSH parameter2 PUSH parameter1 CALL Message |
通过寄存器来传送参数,被调用方清理堆栈 | 参数从右到左传递,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大. VC编译器默认使用该方式 | 参数从右到左传递,被调函数自身在返回前清空堆栈. 标准调用方式,一般WIN32的函数都是 |
ctypes 库也提供了三种不同的动态链接库加载方式: cdll()、windll() 和 oledll().
- cdll() 对应的是 cdecl 约定
- windll() 对应 stdcall 约定
- oledll() 和 windll() 一样, 但是它假定所有函数会统一返回一个 windows hresult 错误编码.
我们来尝试下调用 msvcrt.dll 中的 printf 函数.
from ctypes import *
#带有可变参数的函数必须且只能使用_cdecl方式, 如 printf
msvcrt = cdll.msvcrt
message = 'Hello'
msvcrt.printf('%s\n', message)
2. 基本数据类型
ctype 提供了一些与 C 互换的基本数据类型, 这就可以通过 ctype 来方便的创建 C 数据类型.
ctype 也支持引用传参, 对于需要传引用的参数像这样传就可以了: ctypes.byref(object)
3. 定义结构体
使用 ctypes 中的 Structure 来实现 C 结构体.
class RULE_LIST_INFOMATION(Structure):
_fields_ = [
("psz_time", c_char * 100),
("complete", c_int),
]
这段代码实现了下面的 C 结构体.
struct RULE_LIST_INFOMATION{
char psz_time[100];
int complete;
};
C 结构体有内存对齐这个概念, 这点在 cytpe 的 Structure 中也有体现, Structure 默认的内存对齐方式和 C 编译器的方式相同(4字节), 通过在子类中定义一个_pack_ 属性可以覆盖这个行为, 如 C 中的 #pragma pack(1) 对应 _pack_ = 1.