Table of contents
usefulness
ctypes is mainly used to call functions in dll and so
Document translation
windows loads dll
from ctypes import windll, cdll
# 加载Windows的kernel32.dll
print(windll.kernel32)
# 加载Windows的msvcrt.dll,和libc一样,里面是Windows的标准库函数
print(cdll.msvcrt)
linux load so
from ctypes import cdll, CDLL
# 方法1
libc1 = cdll.LoadLibrary("libc.so.6")
# 方法2
libc2 = CDLL("libc.so.6")
Declare function
If the parameters and return values of the function are basic types (strings and numeric values), you can call the function directly without declaration
from ctypes import windll
handle = windll.kernel32.GetModuleHandleA(None)
print(hex(handle ))
t = cdll.msvcrt.time(None)
print(t)
printf = cdll.msvcrt.printf
printf(b"Hello, %s\n", b"World!")
If the function parameters or return value contain some complex data types, you need to declare the function parameters (argtypes) and return value (restype) first
from ctypes import *
from ctypes.wintypes import *
GetModuleHandleA = windll.kernel32.GetModuleHandleA
GetModuleHandleA.argtypes = (LPCSTR,)
GetModuleHandleA.restype = HMODULE
handle = GetModuleHandleA(None)
print(hex(handle))
The return value is different between calling GetModuleHandleA directly and calling it after declaration. This is because the returned pointer type is not interpreted correctly. Therefore, it is best to declare the parameters and types of the function before calling the dll function.
For example, to call GetModuleHandleA of kernel32.dll, you can first search for the function prototype in Microsoft's official documentation.
calling convention
ctypes only supports two calling conventions, cdll supports the cdecl calling convention, and windll supports the stdcall calling convention. There is also oledll which also has stdcall calling convention. I don’t see any difference from windll.
type of data
Using ctypes types
from ctypes import *
i = c_int(10)
print(i)
print(c_wchar_p("Hello, World"))
print(c_ushort(-3))
i.value = 100
print(i)
Pass pointer
You can use the byref function to pass the pointer. Of course, you can also use the pointer function, which has the same effect, but byref is more efficient because pointer needs to construct a real pointer.
from ctypes import *
i = c_int(100)
f = c_float(3.14)
s = create_string_buffer(b"address: ")
cdll.msvcrt.printf(b"%s %p %x", s, byref(f), pointer(i))
Structure
Inherit Structure and then declare the _fields_ field to define a structure type
from ctypes import *
class POINT(Structure):
_fields_ = [("x", c_int),
("y", c_int)]
point = POINT(10, 20)
print(point.x, point.y)
point = POINT(y=5)
print(point.x, point.y)
Structure nesting
class RECT(Structure):
_fields_ = [("upperleft", POINT),
("lowerright", POINT)]
rc = RECT(POINT(1, 2), POINT(3, 4))
# rc = RECT((1, 2), (3, 4))
print(rc.upperleft.x, rc.upperleft.y)
print(rc.lowerright.x, rc.lowerright.y)
Structure field alignment and endianness
The default alignment is the same as C. It can be defined using the _pack_ attribute. The value can be set to a positive integer, indicating the maximum alignment of the field. The #pragma pack(n)
effect is the same.
Structures in ctypes use local byte order. To use non-native byte order, you can use BigEndianStructure, LittleEndianStructure, BigEndianUnion, LittleEndianUnion as base classes. These classes cannot contain pointer fields
array
# 定义
a = c_char * 4
# 赋值
s = a(b'a', b'b', b'c', b'\x00')
Equivalent to
char a[4] = "abc";
Type cast
For example, casting a float type pointer to an int type pointer
from ctypes import *
a = pointer(c_float(3.14))
print(cast(a, POINTER(c_int)).contents)
Of course the output result is definitely not 3
Callback
Define a function in Python that can be called in the dll
qsort is a sorting function. The first parameter is the sorted array, the second is the length of the array, the third is the size of the array elements, and the fourth is a callback function. If the return value is less than 0, a will be placed in front of b. , if greater than 0, a will be placed after b
from ctypes import *
def py_cmp_func(a, b):
print("py_cmp_func", a[0], b[0])
return a[0]-b[0]
IntArray5 = c_int * 5
ia = IntArray5(5, 1, 7, 33, 99)
qsort = cdll.msvcrt.qsort
qsort.restype = None
CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
cmp_func = CMPFUNC(py_cmp_func)
qsort(ia, len(ia), sizeof(c_int), cmp_func)
print(list(ia))
CFUNCTYPE defines the cdecl calling function, and WINFUNCTYPE defines the stdcall calling function. The first parameter is the return value type, and the following is the parameter type.
It can also be defined in the form of a decorator
@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
def py_cmp_func(a, b):
print("py_cmp_func", a[0], b[0])
return a[0] - b[0]
dll exported value
Dynamic link libraries can not only export functions, but also export variables. The pythonapi here is actually the loaded python.dll
from ctypes import *
opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
print(opt_flag)
example
Enumerate all module information of the process
C language approximate code
hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, dwPID );
if( hModuleSnap == INVALID_HANDLE_VALUE ) {
return( r_mi );
}
me32.dwSize = sizeof( MODULEENTRY32 );
if( !Module32First( hModuleSnap, &me32 ) ) {
CloseHandle( hModuleSnap );
return( r_mi );
}
do {
} while( Module32Next( hModuleSnap, &me32 ) );
Python translation
Step 1: Define the structure MODULEENTRY32
from ctypes import *
from ctypes.wintypes import *
class MODULEENTRY32(Structure):
_fields_ = [
("dwSize", DWORD), # 结构的大小,以字节为单位,必须先初始化
("th32ModuleID", DWORD), # 该成员不再使用,并且始终设置为 1
("th32ProcessID", DWORD), # 进程pid
("GlblcntUsage", DWORD), # 无意义, 一般等于0xFFFF
("ProccntUsage", DWORD), # 无意义, 一般等于0xFFFF
("modBaseAddr", POINTER(BYTE)), # 拥有进程上下文中模块的基地址
("modBaseSize", DWORD), # 模块的大小,以字节为单位
("hModule", HMODULE), # 拥有进程上下文中的模块句柄
("szModule", c_char*256), # 模块名称
("szExePath", c_char*260), # 模块路径
]
Step 2: Define the function
kernel32 = WinDLL('kernel32', use_last_error=True)
def func_def(name, restype, *argtypes, dll=kernel32):
def errcheck(result, func, args):
if not result:
raise WinError(get_last_error())
return result
cfunc = getattr(dll, name)
cfunc.argtypes = argtypes
cfunc.restype = restype
#cfunc.errcheck = errcheck
return cfunc
CreateToolhelp32Snapshot = func_def("CreateToolhelp32Snapshot", HANDLE, *(DWORD, DWORD))
Module32First = func_def("Module32First", BOOL, *(HANDLE, POINTER(MODULEENTRY32)))
Module32Next = func_def("Module32Next", BOOL, *(HANDLE, POINTER(MODULEENTRY32)))
CloseHandle = func_def("CloseHandle", BOOL, *(HANDLE,))
third step:
TH32CS_SNAPMODULE = 0x00000008
TH32CS_SNAPMODULE32 = 0x00000010
def getModuleInfo(moduleName, pid):
'''获取模块信息,返回模块信息的字典'''
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32, pid)
me32 = MODULEENTRY32()
me32.dwSize = sizeof(MODULEENTRY32)
bRet = Module32First(hModuleSnap, pointer(me32))
while bRet:
szModule = me32.szModule.decode()
if szModule.upper() == moduleName.upper():
addr = cast(me32.modBaseAddr, c_void_p).value # hex(addressof(modBaseAddr.contents))
CloseHandle(hModuleSnap)
try:
me32.szExePath.decode("gbk")
except UnicodeDecodeError:
print(me32.szExePath)
module = {
'modBaseSize': me32.modBaseSize, # 模块字节大小
'th32ProcessID': me32.th32ProcessID, # 进程pid
'modBaseAddr': addr, # 模块基址
"hModule": me32.hModule, # 模块句柄
'szModule': me32.szModule.decode("ansi"), # 模块名称
'szExePath': me32.szExePath.decode("ansi") # 模块路径
}
return module
bRet = Module32Next(hModuleSnap, pointer(me32) )
CloseHandle(hModuleSnap)
import os
import sys
py_version = str(sys.version_info[0]) + str(sys.version_info[1])
print(getModuleInfo(f"python{
py_version}.dll", os.getpid()))