如何在Python中通过ctypes调用字节形式的x86_64汇编函数?
问题背景
我有一段函数的反汇编字节码,对应Python的bytes对象为bytes.fromhex('89 4c 24 08 83 7c 24 08 01 7c 0c 8b 44 24 08 ff c8 89 44 24 08 eb ed c3 ')。尝试用以下Python ctypes代码调用这段机器码时程序崩溃:
import ctypes # 原始反汇编字节 buf = bytes.fromhex('89 4c 24 08 83 7c 24 08 01 7c 0c 8b 44 24 08 ff c8 89 44 24 08 eb ed c3 ') # 函数类型定义 nothFn = ctypes.CFUNCTYPE(None, ctypes.c_int) # ctypes 缓冲区 codebuf = ctypes.create_string_buffer(buf) # 将原始缓冲区地址作为函数地址 cfunc = nothFn(ctypes.addressof(codebuf)) # 调用时程序崩溃 cfunc(ctypes.c_int(3))
同时尝试用str(codebuf)返回的地址调用也会崩溃。现提出两个问题:
- 是否因内存执行权限违规导致崩溃?如何让分配的内存可执行?是否必须加载到动态库才能执行?
- 若CPU为x86_64架构,相同代码能否在Windows和Linux下运行?假设函数仅操作输入参数或栈内存。
问题解答
问题1:内存执行权限与可执行内存分配
- 崩溃原因确实是内存执行权限违规:
ctypes.create_string_buffer分配的内存属于进程的普通数据段,默认只有读/写权限,没有执行权限。现代操作系统的DEP(数据执行保护)机制会禁止执行数据段的代码,触发权限检查时直接导致崩溃。 - 让内存可执行不需要加载到动态库,直接通过系统调用修改内存权限或分配带执行权限的内存即可,不同系统的实现方式如下:
- Linux/macOS:可以用
mmap直接分配带执行权限的内存,或者用mprotect修改已有内存的权限。示例代码(Linux):import ctypes from ctypes import cdll buf = bytes.fromhex('89 4c 24 08 83 7c 24 08 01 7c 0c 8b 44 24 08 ff c8 89 44 24 08 eb ed c3 ') PAGE_EXECUTE_READWRITE = 0x40 libc = cdll.LoadLibrary('libc.so.6') # 分配可执行内存 code_ptr = libc.mmap(0, len(buf), 0x1 | 0x2, 0x2 | 0x1000, -1, 0) ctypes.memmove(code_ptr, buf, len(buf)) nothFn = ctypes.CFUNCTYPE(None, ctypes.c_int) cfunc = nothFn(code_ptr) cfunc(3) # 释放内存 libc.munmap(code_ptr, len(buf)) - Windows:用
VirtualAlloc分配可执行内存,或者VirtualProtect修改已有内存权限。示例代码:import ctypes from ctypes import wintypes buf = bytes.fromhex('89 4c 24 08 83 7c 24 08 01 7c 0c 8b 44 24 08 ff c8 89 44 24 08 eb ed c3 ') PAGE_EXECUTE_READWRITE = 0x40 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # 分配可执行内存 code_ptr = kernel32.VirtualAlloc( wintypes.LPVOID(0), wintypes.SIZE_T(len(buf)), wintypes.DWORD(0x1000 | 0x2000), # MEM_COMMIT | MEM_RESERVE wintypes.DWORD(PAGE_EXECUTE_READWRITE) ) ctypes.memmove(code_ptr, buf, len(buf)) nothFn = ctypes.CFUNCTYPE(None, ctypes.c_int) cfunc = nothFn(code_ptr) cfunc(3) # 释放内存 kernel32.VirtualFree(code_ptr, 0, 0x8000) # MEM_RELEASE
- Linux/macOS:可以用
- 结论:不需要加载到动态库,直接分配带执行权限的内存就能运行机器码。
问题2:x86_64架构下跨Windows/Linux兼容性
不能直接跨平台运行,核心原因有两个:
- 调用约定差异:x86_64下,Windows使用
fastcall调用约定(前4个整数参数用RCX、RDX、R8、R9寄存器传递),而Linux/macOS采用System V AMD64调用约定(前6个整数参数用RDI、RSI、RDX、RCX、R8、R9传递)。你提供的机器码是基于32位x86的栈传递参数逻辑,放到64位环境下本身就不兼容,更别说跨Windows和Linux了。 - 栈与寄存器布局差异:Windows和Linux的栈帧结构、栈对齐要求不同,32位机器码在64位环境下执行时,会因为寄存器宽度、栈操作逻辑不匹配直接崩溃。
即使函数仅操作输入参数和栈内存,32位机器码也无法在x86_64架构的不同系统下正常运行,需要分别编写对应平台的64位机器码。
内容的提问来源于stack exchange,提问作者wanyancan
相关产品推荐
相关产品推荐

