Вопрос

Мой друг создал небольшой ассемблер, который работал на 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    <*>xa,%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 защищен от записи?

Это было полезно?

Решение

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

Другие советы

Провел некоторое исследование с моим другом и выяснил, что это проблема для конкретной платформы. Мы подозреваем, что на некоторых платформах 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    <*>xa,%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))

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

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

VirtualProtect , похоже, делает то же самое в Windows:

http://msdn.microsoft.com/ ан-нас / библиотека / aa366898 (VS.85) .aspx

Допускает ли Python такое использование? Я должен изучить это тогда ...

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top