эта ссылка дает хороший пример выполнения asm с помощью Python на платформе Linux, но я не знаю, как вызвать asm-функцию с возвращаемым значением в Windows. Не могли бы вы рассказать мне, как это сделать, или привести пример?
import ctypes
import mmap
buf = mmap.mmap(-1, mmap.PAGESIZE, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)
ftype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
fpointer = ctypes.c_void_p.from_buffer(buf)
f = ftype(ctypes.addressof(fpointer))
buf.write(
b'\x8b\xc7' # mov eax, edi
b'\x83\xc0\x01' # add eax, 1
b'\xc3' # ret
)
r = f(42)
print(r)
del fpointer
buf.close()
PROT_READ
, PROT_WRITE
и PROT_EXEC
специфичны для Unix. Для систем Windows используйте альтернативный конструктор с аргументом access
.
Хорошо, между ОС Unix и Windows существует множество различий, поэтому такие низкоуровневые вещи обычно привязаны к платформе.
Начнем с листинга сборки.
mov eax, edi
add eax, 1
ret
Этот код специально написан для ОС Unix. Почему? Из-за предположения, что первый аргумент передается с использованием регистра edi
. В системах Windows первый аргумент передается с помощью ecx
, поэтому правильная сборка для систем Windows:
mov eax, ecx
add eax, 1
ret
Итак, пропатченная "скомпилированная" сборка:
asm_function = (
b'\x8b\xc1' # mov eax, ecx
b'\x83\xc0\x01' # add eax, 1
b'\xc3' # ret
)
Относительно mmap
. Я пытался заставить его работать, но любая попытка заканчивалась сообщением о нарушении прав доступа. Я даже пытался установить защиту PAGE_EXECUTE_READWRITE
для выделенной памяти (используя mmap()
) с помощью VirtualProtect()
, но функция WinAPI не удалась с кодом ошибки ERROR_INVALID_PARAMETER
(0x57
).
Поскольку я не могу заставить это работать с nmap
, почему бы не спуститься на один уровень ниже и не выделить память с помощью WinAPI? Это довольно просто, нам достаточно вызвать VirtualAlloc()
для выделения памяти и RtlMoveMemory()
для копирования "прекомпилированной" сборки в эту выделенную память.
Нам нужно исправить argtypes
и restype
по умолчанию для ctypes.windll.kernel32.VirtualAlloc
и ctypes.windll.kernel32.RtlMoveMemory
, чтобы они соответствовали подписям из MSDN. Мы должны сделать это, потому что в системах x64 указатели являются 64-битными, но возвращаемое значение по умолчанию равно 32-битным, поэтому адрес, возвращаемый VirtualAlloc()
, не будет обрабатываться правильно. Отдельное спасибо спаситель жизни, он указал на это в его ответ.
Окончательный код:
import ctypes
asm_function = (
b'\x8b\xc1' # mov eax, ecx
b'\x83\xc0\x01' # add eax, 1
b'\xc3' # ret
)
# https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_COMMIT
MEM_COMMIT = 0x00001000
# https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_RESERVE
MEM_RESERVE = 0x00002000
# https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants#PAGE_EXECUTE_READWRITE
PAGE_EXECUTE_READWRITE = 0x40
# https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
ctypes.windll.kernel32.VirtualAlloc.argtypes = (
ctypes.c_void_p, # LPVOID
ctypes.c_size_t, # SIZE_T
ctypes.c_long, # DWORD
ctypes.c_long, # DWORD
)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p # LPVOID
memory_buffer = ctypes.windll.kernel32.VirtualAlloc(
0, # lpAddress - NULL
len(asm_function), # dwSize
MEM_COMMIT | MEM_RESERVE, # flAllocationType
PAGE_EXECUTE_READWRITE # flProtect
)
if not memory_buffer: # VirtualAlloc returned NULL
print("VirtualAlloc call failed. Error code:", ctypes.GetLastError())
exit(-1)
c_buffer = ctypes.c_char_p(asm_function)
# https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory
ctypes.windll.kernel32.RtlMoveMemory.argtypes = (
ctypes.c_void_p, # VOID*
ctypes.c_void_p, # VOID*
ctypes.c_size_t # SIZE_T
)
ctypes.windll.kernel32.RtlMoveMemory(
memory_buffer, # Destination
c_buffer, # Source
len(asm_function) # Length
)
f = ctypes.cast(
memory_buffer,
ctypes.CFUNCTYPE(
ctypes.c_int, # return type
ctypes.c_int # argument type
)
)
r = f(42)
print(r)
P.S. Буду признателен, если кто-нибудь добавит ответ, как это сделать с помощью mmap
.
Вы можете помочь моей стране, отметьте информация моего профиля.
Странно, что VirtualProtect не сработал. Интересно, что-то пытается применить W ^ X, не позволяя странице одновременно иметь разрешение на запись и выполнение. Windows обычно этого не делает, насколько я знаю, но некоторые операционные системы, такие как OpenBSD, делают это. (В этом случае вам придется скопировать на страницу, а затем изменить ее разрешение на чтение + выполнение.) Или, может быть, оболочки Python содержат ошибки? Возможно, было бы полезно отслеживать системные вызовы с помощью внешнего инструмента, чтобы увидеть, какие системные вызовы действительно произошли. (Я думаю, что есть некоторый Windows-эквивалент Linux strace
.)
@PeterCordes, действительно странно. У меня было ограниченное время для подробного изучения, так что все еще возможно, что я пропустил что-то, связанное с этими странными оболочками Python. Я выделил два буфера, один с использованием mmap()
, а другой с помощью VirtualAlloc()
, и попытался вызвать VirtualProtect()
на обоих. Он нормально работал с буфером, выделенным с помощью VirtualAlloc()
, но всегда терпел неудачу с другим, выделенным с помощью mmap()
. Код ошибки (ERROR_INVALID_PARAMETER
) довольно странный, возможно, проблема была в том, как я получил адрес выделенной памяти. Вернемся к этому чуть позже.
Если это простое любопытство, то все в порядке, но, пожалуйста, так и скажите. Если у вас есть основная проблема, которую вы пытаетесь решить с помощью «встроенной» сборки Python, вместо этого спросите об этой проблеме напрямую. В противном случае ваш вопрос будет просто проблема XY.