Мой друг создал небольшой пробный ассемблер, работающий на x86. Я решил портировать его и на x86_64, но тут же возникла проблема.
Я написал небольшой фрагмент программы на C, затем скомпилировал и скопировал код. После этого я вставил его в свой скрипт python, поэтому код x86_64 правильный:
from ctypes import cast, CFUNCTYPE, c_char_p, c_long
buffer = ''.join(map(chr, [ #0000000000000000 <add>:
0x55, # push %rbp
0x48, 0x89, 0xe5, # mov %rsp,%rbp
0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp)
0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax
0x48, 0x83, 0xc0, 0x0a, # add $0xa,%rax
0xc9, # leaveq
0xc3, # retq
]))
fptr = cast(c_char_p(buffer), CFUNCTYPE(c_long, c_long))
print fptr(1234)
Итак, почему этот скрипт продолжает делать ошибки сегментации всякий раз, когда я его запускаю?
У меня еще есть вопрос о mprotect и об отсутствии флага выполнения. Говорят, что он защищает от большинства основных уязвимостей безопасности, таких как переполнение буфера. Но какова настоящая причина его использования? Вы можете просто продолжать писать, пока не нажмете .text, а затем вставьте свои инструкции в красивую PROT_EXEC-область. Если, конечно, вы не используете защиту от записи в .text
Но тогда почему вообще этот PROT_EXEC везде? Разве вам не поможет то, что ваш раздел .text защищен от записи?






Допускает ли Python такое использование? Тогда я должен выучить это ...
Я думаю, что интерпретатор не ожидает изменения какого-либо регистра. Попробуйте сохранить регистры, которые вы используете внутри функции, если вы планируете использовать вывод ассемблера таким образом.
Кстати, соглашение о вызовах x86_64 отличается от обычного x86. У вас могут возникнуть проблемы, если вы потеряете выравнивание указателя стека и смешаете внешние объекты, созданные с помощью других инструментов.
Я думаю, что вы не можете свободно выполнять любую выделенную память, не установив ее сначала как исполняемый файл. Я никогда не пробовал себя, но вы можете проверить функцию unix mprotect:
http://linux.about.com/library/cmd/blcmdl2_mprotect.htm
VirtualProtect, похоже, делает то же самое в Windows:
http://msdn.microsoft.com/en-us/library/aa366898(VS.85).aspx
Хотя я и раньше находил это где-то в другом месте, это действительно правильно, но с небольшими отклонениями. Объясняю это в собственном ответе.
Провели небольшое исследование с моим другом и выяснили, что это проблема, связанная с платформой. Мы подозреваем, что на некоторых платформах память malloc mmaps без PROT_EXEC, а на других есть.
Следовательно, необходимо впоследствии изменить уровень защиты с помощью mprotect.
Хромая вещь, потребовалось время, чтобы понять, что делать.
from ctypes import (
cast, CFUNCTYPE, c_long, sizeof, addressof, create_string_buffer, pythonapi
)
PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC = 0, 1, 2, 4
mprotect = pythonapi.mprotect
buffer = ''.join(map(chr, [ #0000000000000000 <add>:
0x55, # push %rbp
0x48, 0x89, 0xe5, # mov %rsp,%rbp
0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp)
0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax
0x48, 0x83, 0xc0, 0x0a, # add $0xa,%rax
0xc9, # leaveq
0xc3, # retq
]))
pagesize = pythonapi.getpagesize()
cbuffer = create_string_buffer(buffer)#c_char_p(buffer)
addr = addressof(cbuffer)
size = sizeof(cbuffer)
mask = pagesize - 1
if mprotect(~mask&addr, mask&addr + size, PROT_READ|PROT_WRITE|PROT_EXEC) < 0:
print "mprotect failed?"
else:
fptr = cast(cbuffer, CFUNCTYPE(c_long, c_long))
print repr(fptr(1234))
абсолютно лучший пример, который когда-либо видел по этой теме!
Как упоминалось в Винсент, это связано с тем, что выделенная страница помечена как неисполняемая. Новые процессоры поддерживают этот функциональность, и он используется в качестве дополнительного уровня безопасности ОС, которые его поддерживают. Идея состоит в том, чтобы защитить себя от определенных атак переполнения буфера. Например. Распространенная атака - переполнение переменной стека, переписывание адреса возврата, чтобы он указывал на код, который вы вставили. С неисполняемым стеком это теперь вызывает только segfault, а не контроль над процессом. Подобные атаки существуют и для кучи памяти.
Чтобы обойти это, нужно переделать защиту. Это может быть выполнено только в памяти, выровненной по страницам, поэтому вам, вероятно, придется изменить свой код на что-то вроде следующего:
libc = CDLL('libc.so')
# Some constants
PROT_READ = 1
PROT_WRITE = 2
PROT_EXEC = 4
def executable_code(buffer):
"""Return a pointer to a page-aligned executable buffer filled in with the data of the string provided.
The pointer should be freed with libc.free() when finished"""
buf = c_char_p(buffer)
size = len(buffer)
# Need to align to a page boundary, so use valloc
addr = libc.valloc(size)
addr = c_void_p(addr)
if 0 == addr:
raise Exception("Failed to allocate memory")
memmove(addr, buf, size)
if 0 != libc.mprotect(addr, len(buffer), PROT_READ | PROT_WRITE | PROT_EXEC):
raise Exception("Failed to set protection on buffer")
return addr
code_ptr = executable_code(buffer)
fptr = cast(code_ptr, CFUNCTYPE(c_long, c_long))
print fptr(1234)
libc.free(code_ptr)
Примечание. Перед освобождением страницы рекомендуется снять флаг исполняемого файла. Большинство библиотек C фактически не возвращают память в ОС после завершения, а хранят ее в своем собственном пуле. Это может означать, что они будут повторно использовать страницу в другом месте, не сбрасывая бит EXEC, в обход преимущества безопасности.
Также обратите внимание, что это довольно непереносимо. Я тестировал его на Linux, но не на других ОС. Он не будет работать в Windows, но может подойти для других unix (BSD, OsX?).
Даже лучший ответ. valloc полезен, например, обратите внимание, что бит EXEC не очищается после этого. Но, возможно, меня не интересует ни один из аспектов.
Я только что придумал более простой подход, но в последнее время он не включает mprotect. Просто mmap исполняемое пространство для программы напрямую. В наши дни у python есть модуль для этого, хотя я не нашел способа получить адрес кода. Короче говоря, вы должны выделить память, вызывая mmap, вместо использования строковых буферов и косвенной установки флага выполнения. Это проще и безопаснее, вы можете быть уверены, что только ваш код может быть выполнен сейчас.
ctypes заботится о том, чтобы мои соглашения о вызовах были правильными, достаточно, чтобы код был выведен gcc. Что касается изменения регистров, я думал, что соглашения о вызовах x86_64 говорят, что подпрограмма может свободно изменять большинство регистров.