2008-11-08 9 views
7

Мой друг создал небольшой ассемблер доказательств концепции, который работал на x86. Я решил портировать его и для x86_64, но сразу же попал в проблему.Ptyon ctypes и вызовы функций

Я написал небольшую часть программы в C, а затем скомпилировал и передал код. После этого я вставил его в мой питон скрипт, поэтому код 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 -area. Если, конечно, вы не используете защиту от записи в .text

Но почему же все-таки PROT_EXEC везде? Разве это не просто поможет чрезвычайно, чтобы ваш .text-раздел был защищен от записи?

ответ

8

Как vincent упоминалось, это связано с выделенной страницы помечается как не исполняемый файл. Новые процессоры поддерживают этот functionality, а его используют как дополнительный уровень безопасности для ОС, которые его поддерживают. Идея заключается в защите от некоторых атак переполнения буфера. Например. Общей атакой является переполнение переменной стека, переписывание обратного адреса, чтобы указать на введенный вами код. В случае неисполняемого стека теперь создается только 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, но не на других ОС. Он не будет работать на окнах, покупка может делать на других unixes (BSD, OsX?).

+0

Даже лучший ответ. valloc полезен, например, обратите внимание, что после этого бит EXEC не очищается. Но меня, возможно, не интересует ни один аспект. – Cheery 2008-11-09 08:45:43

0

Поддерживает ли python такое использование? Я должен научиться этому ...

Я думаю, что интерпретатор не ожидает, что какой-либо регистр будет изменен. Попробуйте сохранить регистры, которые вы используете внутри функции, если вы планируете использовать выход ассемблера следующим образом.

Btw, соглашение о вызове x86_64 отличается от обычного x86. У вас могут возникнуть проблемы, если вы потеряете выравнивание указателя стека и смешайте внешние объекты, сгенерированные другими инструментами.

+0

ctypes заботится о том, чтобы мои правила вызова были правильными, достаточно, чтобы код был выведен gcc. Что касается изменений в регистрах, я думал, что вызывающие соглашения x86_64 говорят, что подпрограмма может свободно менять большинство регистров. – Cheery 2008-11-09 09:00:44

4

Я думаю, что вы не можете свободно выполнять выделенную память без предварительной установки ее в качестве исполняемого файла. Я никогда не пробовал сам, но вы можете проверить функцию Unix mprotect:

http://linux.about.com/library/cmd/blcmdl2_mprotect.htm

VirtualProtect кажется, сделать то же самое на окнах:

http://msdn.microsoft.com/en-us/library/aa366898(VS.85).aspx

+0

Несмотря на то, что я нашел его в другом месте раньше, это действительно правильно, но с небольшими вариациями. Я объясняю это в своем собственном ответе. – Cheery 2008-11-09 00:22:09

7

сделал некоторые исследования с моим другом и выяснил, что это проблема, связанная с платформой. Мы подозреваем, что на некоторых платформах malloc записывает память без PROT_EXEC и других.

Поэтому после этого необходимо изменить уровень защиты с помощью mprotect.

Lame thing, требуется время, чтобы узнать, что делать.

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)) 
+0

абсолютно лучший пример когда-либо видел на эту тему! – mtasic85 2009-11-05 13:14:07

0

Существует более простой подход, который я только что рассмотрел, но в последнее время это не связано с mprotect. Простое mmap исполняемое пространство для программы напрямую. В эти дни у python есть модуль для выполнения именно этого, хотя я не нашел способ получить адрес кода. Короче говоря, вы выделяли вызов памяти mmap вместо использования строковых буферов и установку флажка выполнения опосредованно. Это проще и безопаснее, вы можете быть уверены, что только ваш код может быть выполнен сейчас.