You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)返回的地址调用也会崩溃。现提出两个问题:

  1. 是否因内存执行权限违规导致崩溃?如何让分配的内存可执行?是否必须加载到动态库才能执行?
  2. 若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
      
  • 结论:不需要加载到动态库,直接分配带执行权限的内存就能运行机器码。

问题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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:26:19