• XSS.stack #1 – первый литературный журнал от юзеров форума

Techniques Jump-Oriented Programming: вспомнить старое - понять новое

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
ОРИГИНАЛьная статьЯ

Начнём.

Pre-party

Программирование, ориентированное на возврат, - это эффективный метод повторного использования кода, при котором короткие последовательности кода,
заканчивающиеся инструкцией ret, находящиеся в двоичных файлах и выполняются в произвольном порядке, захватывая контроль над стеком. Это позволяет
добиться полного по-Тьюрингу поведения в целевой программе без необходимости внедрения атакующего кода, что значительно снижает эффективность текущих усилий по защите от внедрения
кода (например, W⊕X). С другой стороны, его неотъемлемые характеристики, такие как повторное использование стека и последовательное выполнение гаджетов, ориентированных на возврат, побудили
разработать множество защитных средств для его обнаружения или предотвращения. В этой статье мы представляем новый класс атак / статья 2011 по данным вебархива / на повторное использование кода, называемый прыжково-ориентированным программированием. Эта новая атака устраняет зависимость от стека и инструкций ret (включая ret-подобные инструкции, такие как pop+jmp),
наблюдаемую в программировании, ориентированном на возврат, не жертвуя выразительной силой. Эта атака состоит из цепочки функциональных гаджетов, каждый из которых выполняет определенные
примитивные операции, только эти гаджеты заканчиваются не ret, а косвенным ответвлением. Без удобства использования ret для их объединения, атака полагается на гаджет-диспетчера для диспетчеризации и выполнения функциональных гаджетов. Мы успешно определили наличие этих гаджетов, ориентированных на переход, в библиотеке GNU libc. Наш опыт с примером атаки shellcode демонстрирует практичность и эффективность этой техники

Введение

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

Одной из самых ранних форм программных эксплойтов является атака с внедрением кода, когда вредоносное сообщение содержит машинный код, и используется переполнение буфера или другая техника
перенаправления потока управления на код, предоставленный злоумышленником. Однако с появлением процессоров и операционных систем, поддерживающих W⊕X [3], эта угроза была уменьшена во многих контекстах. В
частности, W⊕X обеспечивает соблюдение свойства, согласно которому "данная страница памяти никогда не будет одновременно и записываемой, и исполняемой". Основная предпосылка этого заключается в том, что если
страница не может быть записана, а затем выведена из нее, инъекция кода становится невозможной. К сожалению, злоумышленники разработали инновационные способы преодоления W⊕X. Например, одним из возможных способов является атака с повторным использованием кода, когда существующий код переиспользуется в злонамеренных целях. Наиболее простой и распространенной формой такой атаки является техника return-into-libc. В этом сценарии злоумышленник использует переполнение
буфера для перезаписи части стека с адресами возврата и параметрами для списка функций в libc (основной библиотеке языка Си, которая динамически подключается ко всем приложениям в UNIX -подобных средах). Это позволяет злоумышленнику выполнить произвольную последовательность функций libc, обычным примером которой является вызов system("/bin/sh") для запуска оболочки. Хотя return-into-libc является мощным средством, он не позволяет проводить сложные вычисления в контексте эксплуатируемого приложения. Для этого злоумышленник может обратиться к программированию,
ориентированному на возврат (ROP) Как ираньше, ROP перезаписывает стек с адресами возврата и аргументами. Однако теперь адреса возврата указывают на произвольные точки в существующей кодовой базе, и единственным требованием является то, что эти фрагменты кода, или гаджеты, заканчиваются инструкцией ret для передачи управления на следующий гаджет.
Было показано, что программирование, ориентированное на возврат, является полным по - Тьюрингу на различных платформах и кодовых базах , а автоматизированные методы сделали разработку таких гаджетов
простым процессом [10, 23, 30]. Опасность этой техники в реальном мире была продемонстрирована, когда Checkoway и др. использовали ее для нарушения целостности широко распространенной электронной машины
для голосования. С момента появления программирования, ориентированного на возврат, было предложено множество средств защиты для обнаружения или предотвращения атак на основе ROP. Например,
DynIMA обнаруживает последовательное выполнение небольших последовательностей команд, каждая из которых заканчивается символом ret, и подозревает их как гаджеты в атаке ROP. DROP замечает, что при выполнении ROP постоянно появляются адреса возврата, которые всегда указываtт на одну и ту же конкретную область памяти, и считает это свойством, присущим ROP, полезным для обнаружения. Подход без возврата выполняется дальше, устраняя все возвратные структуры в программе, тем самым устраняя существование гаджетов, ориентированных на возврат, и исключая
возможность атаки на основе ROP.

В этой статье мы представляем альтернативную парадигму атаки называемую прыжково-ориентированным программированием (JOP). В атаке на основе JOP атакующий полностью отказывается от использования стека для
потока управления и ret для обнаружения и цепочки гаджетов вместо этого используя не что иное, как последовательность косвенных переходов инструкций. Поскольку почти все известные техники защиты
против ROP зависят от его зависимости от стека или ret, ни одна из них не способна обнаружить или защититься от этого нового подхода. Исключением являются системы, обеспечивающие полную целостность потока управления, к сожалению, такие системы не получили широкого распространения, вероятно, из-за опасений по поводу их сложности и негативного влияния на производительность.
Подобно ROP, строительными блоками JOP являются короткие последовательности кода, называемые гаджетами.
Однако вместо того, чтобы заканчиваться ret, каждый такой гаджет завершается косвенной инструкцией jmp1. Некоторые из этих инструкций jmp намеренно выдаются компилятором.
Другие не предназначены, но присутствуют из-за плотности инструкций x86 и возможности невыровненного выполнения. Однако, в отличие от ROP, где гаджет ret может естественным образом возвращает управление, основываясь на содержимом стека, гаджет jmp выполняет однонаправленную передачу управляющего потока управления на свою цель, что затрудняет возврат управления обратно в цепочку выполнения.
следующего ориентированного на прыжок гаджета. Отметим, что атака повторного использования кода, основанная на косвенных jmps была выдвинута в качестве теоретической возможности еще в 2003 году
Однако всегда оставалась открытой проблема как злоумышленник будет сохранять контроль над выполнением программы. При отсутствии общего механизма управления типа ret для объединения их, было неясно, как соединить гаджеты в цепочку вместе с помощью однонаправленных jmps. Наше решение этой проблемы заключается в предложении нового класса гаджетов, гаджета-диспетчера. Такой гаджет предназначен для управления потоком управления между различными ориентированными на переходы гаджетами. Более конкретно, если мы рассматриваем другие гаджеты как функциональные гаджеты, которые выполняют примитивные операции, то этот гаджет-диспетчер специально выбран для определения того, какой функциональный гаджет будет вызван следующим. Естественно, диспетчерский гаджет может поддерживать внутреннюю диспетчерскую таблицу, который явно определяет поток управления функциональными гаджетами. Кроме того, он гарантирует, что завершающая инструкция jmp функционального гаджета всегда будет передавать управление обратно
к гаджету-диспетчеру. Таким образом, прыжко-ориентированная коммутация становится выполнимой. Для того чтобы достичь такой же полной по - Тьюрингу выразительной мощности ROP, мы также стремимся определить различные ориентированные на переходы гаджеты для загрузки/сохранения в памяти, арифметических вычислений, бинарных операций, условных ветвлений и системные вызовы. Для этого мы предлагаем алгоритм для обнаружения и сбора гаджетов, ориентированных на переходы, организации их в различные категории и сохранения в памяти. эгории и сохранять их в центральном каталоге гаджетов.

В целом, данная статья вносит следующий вклад:

1. Мы расширяем таксономию атак на повторное использование кода на платформе x86, включив новый класс атак: прыжко-ориентированное программирование. По сравнению с существующим программированием, ориентированным на возврат, наша атака имеет преимущество в том, что она не полагается на стек для потока управления. Вместо этого мы вводим понятие гаджета-диспетчера, который берет на себя роль выполнения функциональных гаджетов.

2. Мы представляем алгоритм, основанный на эвристике, для эффективного обнаружения различных гаджетов, ориентированных на переходы, на x86, включая критический гаджет диспетчера. Наши результаты
показывают, что все эти гаджеты в изобилии доступны в GNU libc, которая динамически подключается почти ко всем приложениям UNIX. все приложения UNIX.

3. Мы демонстрируем эффективность этой техники с помощью ориентированной на прыжок атаки шеллкода, основанной на гаджетах. обнаруженных нашим алгоритмом.

Чтобы понять вклад этой статьи, необходимо кратко описать методы, лежащие в основе программирования, ориентированного на возврат. Поскольку наша система разработана на 32-битной архитектуре x86, наше обсуждение в основном сосредоточено на этой платформе. Как показано на рисунке 1, стек x86 управляется двумя специальными регистрами процессора: регистром esp "указатель стека", который указывает на вершину стека, и регистром ebp "указатель базы", который указывает на нижнюю часть текущего кадра стека. Поскольку стек растет вниз, т.е. растет в направлении уменьшения адресов, esp ≤ ebp. Каждый кадр стека хранит параметры каждого вызова функции, адрес возврата, указатель предыдущего кадра стека и автоматические (lo- cal) переменные, если таковые имеются. Содержимым стека или указателями можно манипулировать непосредственно через два регистра стека или неявно - через различные операционные коды процессора, такие как push и pop. Набор инструкций включает в себя опкоды для вызова функций (call) и возврата из них (ret)3. Инструкция call помещает адрес следующей инструкции (адрес возврата) в стек. И наоборот,инструкция ret сбрасывает стек в eip,
возобновляя выполнение непосредственно после вызова.
Снимок экрана от 2023-04-05 11-21-47.png

РИСУНОК 1
Снимок экрана от 2023-04-05 11-26-29.png


РИСУНОК 2 Возвратно-ориентированное программирование (ROP) VS прыжково-ориентированного программирования (JOP)
Злоумышленник может использовать уязвимость переполнения буфера или другой недостаток, чтобы перезаписать часть стека, например, заменить адрес возврата текущего кадра на предоставленное значение. В традиционном подходе return-into-libc это новое значение является указателем на функцию в libc, выбранную злоумышленником. После того как программа-жертва использует новое значение и входит в функцию, ячейки памяти рядом с перезаписанным адресом возврата интерпретируются как параметры функции, что позволяет выполнить произвольную функцию с заданными злоумышленником параметрами. Соединяя эти вредоносные стековые кадры в цепочку, можно выполнить последовательность функций. Хотя это, несомненно, очень мощная способность, она не позволяет атакующему выполнять произвольные вычисления. Для этого необходимо запустить другой процесс (например, через exec()) или изменить права доступа к памяти, чтобы сделать возможной традиционную атаку с повреждением кода (например, через mprotect()). Поскольку эти операции могут привести к обнаружению или перехвату, скрытный злоумышленник может прибегнуть к программированию, ориентированному на возврат, которое позволяет произвольные вычисления в контексте уязвимого приложения. Программирование, ориентированное на возврат, основано на понимании того, что адреса возврата в стеке могут указывать куда угодно, а не только наначало функций, как в классическом return-into-libc аtack. Поэтому оно может направлять поток
управления через серию небольших фрагментов существующего кода, каждый из которых заканчивается на ret. Эти небольшие фрагменты кода и называются гаджетами, и в достаточно большой кодовой базе (например, libc) существует огромный выбор гаджетов. На платформе x86 выбор становится еще больше, поскольку инструкции имеют переменную длину, поэтому процессор будет по-разному интерпретировать необработанные байты в структуре, если декодирование начинается с другого смещения. Исходя из этого, программа, ориентированная на возврат, представляет собой просто последовательность адресов гаджетов и значений данных, размещенных в памяти уязвимой программы. При традиционной атаке происходит переполнение стека, хотя буфер может быть загружен в другое место, если атакующий сможет перенаправить
указатель стека esp на новое место. Адреса гаджетов можно представить как опкоды в новой машине, ориентированной на возврат, а указатель стека esp - как программный счетчик. Согласно этому определению, так же как базовый блок традиционного кода - это блок, который явно не переставляет счетчик программы, базовый блок" кода, ориентированного на возврат, - это блок, который явно не переставляет указатель стека esp. И наоборот, условные ветви и циклы могут быть созданы
путем изменения значения esp на основе логики. Комбинация арифметики, логики и условных ветвлений дает полную по-Тьюрингу возвратно-ориентированную машину.
Набор гаджетов, удовлетворяющий этим требованиям, был впервые обнаружен на x86, а затем распространен на многие другие платформы. Кроме того, такие атаки могут также выполнять произвольные системные вызовы,
поскольку это просто вызов соответствующей библиотечной процедуры или даже прямой доступ к интерфейсу системных вызовов ядра. Поэтому атака, ориентированная на возврат,
по выразительности эквивалентна успешной инъекции кода.

Ряд исследователей пытались решить проблему программирования, ориентированного на возврат. Каждая из предложенных систем защиты определяет конкретную черту, проявляющуюся в атаках, ориентированных на возврат, и разрабатывает меры по ее обнаружению или предотвращению. Некоторые из них применяют вариант стека LIFO некоторые обнаруживают чрезмерное выполнение инструкции ret, а одна пошла настолько далеко, что исключила все экземпляры опкода ret из образа ядра. Общим у этих методов является то, что все они предполагают, что атака должна использовать стек для управления потоком управления. В данной работе представлено прыжково-ориентированное программирование как новая альтернатива, которая не зависит от стека и, следовательно, невосприимчива к таким средствам защиты. В данной работе мы предполагаем, что противник может поместить полезную нагрузку в память и получить контроль над рядом регистров, особенно над указателем инструкций eip, чтобы перенаправить выполнение программы. Это предположение разумно, поскольку существует несколько распространенных уязвимостей, таких как выход за пределы буфера, переполнение кучи и ошибки форматной строки, которые удовлетворяют этому требованию. Мы также предполагаем наличие значительной кодовой базы, в которой можно найти гаджеты. Как и в случае с ROP, мы обнаружили, что это может быть выполнено исключительно с помощью содержимого libc, который динамически подключается ко всем процессам в UNIX- подобных средах. С защитной стороны, уязвимая программа защищена строгим соблюдением целостности кода (например, W⊕X), что побеждает традиционную атаку с внедрением кода.

На рисунке 2 сравниваются программирование, ориентированное на возврат (ROP), и предлагаемое нами программирование, ориентированное на прыжок (JOP). Как и в ROP, программа, ориентированная на прыжок, состоит из набора объявлений гаджетов и значений данных, загруженных в память, причем адреса гаджетов аналогичны опкодам в новой машине, ориентированной на прыжок. В ROP эти данные хранятся в стеке, поэтому указатель стека esp служит "счетчиком программы" в программе, ориентированной на возврат. JOP не ограничивается использованием esp для ссылок на адреса гаджетов, и поток управления не управляется инструкцией ret. Вместо этого JOP использует таблицу диспетчеризации для хранения адресов и данных гаджетов. "Счетчик программы" - это любой регистр, который указывает на таблицу диспетчеризации. Поток управления управляется специальным гаджетом - диспетчером, который выполняет последовательность гаджетов. При каждом вызове диспетчер продвигает виртуальный счетчик программы и запускает соответствующий гаджет.

Снимок экрана от 2023-04-05 11-54-55.png


Рисунок 3 Поток управления в примере программы, ориентированной на переходы, порядок переходов обозначен цифрами 1...6. Здесь edx используется в качестве pc, который диспетчер продвигает, просто добавляя 4,
чтобы добраться до следующее слово в смежной таблице адресов гаджетов (поэтому f (pc) = pc +4). Показанные функциональные гаджеты будут (1) разыменовать eax, (2) добавить значение по адресу ebx
в eax, и (3) сохранить результат по адресу ecx. Регистры esi и edi используются для возврата управления диспетчеру - esi делает это напрямую, тогда как edi проходит через уровень косвенности.
Пример потока управления JOP - программы показан на рисунке 3. В этом примере мы складываем два значения памяти (на которые указывают eax и ebx, соответственно) и сохраняем сумму в другом месте памяти,
на которое указывает ecx, т.е. [ecx] ← [eax] + [ebx]. Основная цель данной работы - продемонстрировать елесообразность программирования, ориентированного на прыжки. Мы показываем, что его
возможности сравнимы с возможностями программирования, ориентированного на возврат. Однако, не полагаясь на стек для потока управления, JOP может потенциально использовать любой диапазон
памяти, включая даже несмежную память, для хранения таблицы диспетчеризации. Ниже мы подробнее рассмотрим гаджет диспетчера, а также функциональные гаджеты, примитивные операции которых составляют фактические вычисления. После этого мы обсудим, как обнаружить эти гаджеты из общедоступной базы кода. Наконец, мы рассмотрим возможные
способы загрузки прыжко-ориентированной программы

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

pc ← f (pc);
goto ∗ pc;

Здесь pc может быть адресом памяти или регистром, который представляет собой указатель в нашей программе, ориентированной на прыжок. Это не указатель инструкций процессора - он ссылается на
указатель в таблице гаджетов, предоставленной злоумышленником. Функция f (pc) - это любая операция, которая изменяет программный счетчик pc предсказуемым и развивающимся способом. В некоторых
случаях она может быть просто выражена с помощью чистой арифметики (например, f (pc) = pc + 4, как показано на рисунке 3). В других случаях это может быть операция разыменования памяти (
например, f (pc) = ∗(pc - 18)) или любая другая операция - выражение, которое может быть заранее поставлено злоумышленником. Каждый раз при вызове гаджета диспетчера pc будет продвигаться
соответствующим образом. Затем диспетчер разыменовывает его и переходит по полученному адресу + 4. Учитывая широкое определение того, что представляет собой диспетчер, нам не составило
труда найти несколько жизнеспособных кандидатов в libc. То, как гаджет диспетчера продвигает pc, влияет на организацию таблицы диспетчеризации. В частности, таблица диспетчеризации может быть простым массивом, если pc многократно продвигается на постоянное значение (например, f (pc) = pc+4), или связанным списком, если память разыменовывается (например, f (pc) =∗(pc -18)). Пример атаки ниже использует
массив для организации таблицы диспетчеризации.
Эта новая модель программирования расширяет базовую атаку повторного использования кода, используемую в ROP. В частности, если рассматривать стек, используемый в программе на основе ROP, как
таблицу диспетчеризации, а esp - как pc, то инструкция ret в конце каждого гаджета, ориентированного на возврат, действует как диспетчер, который продвигает pc на 4 при каждом завершении гаджета, т.е. f (pc) = pc + 4.
Однако все атаки на основе ROP все еще полагаются на стек, который больше не нужен в атаках на основе JOP.

Функциональные гаджеты

Сам гаджет диспетчера не выполняет никакой реальной работы - он существует исключительно для запуска других гаджетов, которые мы называем функциональными гаджетами. Чтобы сохранить контроль над
выполнением, все функциональные гаджеты, выполняемые диспетчером, должны завершаться переходом к нему, чтобы можно было запустить следующий гаджет. Более формально, функциональный гаджет
определяется как количество полезных инструкций, заканчивающихся последовательностью, которая загрузит указатель инструкции результатом известного выражения. Это выражение может быть
регистром (jmp edx), разыменованием регистра (jmp[edx]) или сложным разыменованием (jmp[edx+esi*4-1]). Единственным требованием является то, что к моменту выполнения ветви
она должна оцениваться как объявление диспетчера или другого гаджета, ведущего к диспетчеру. Однако атака не полагается на конкретные операнды для каждой из этих ветвей:
функциональные гаджеты могут изменить состояние процессора, чтобы сделать доступным различный набор гаджетов для следующей операции. Например, один гаджет может завершиться jmp
[edx], а второй может использовать регистр edx для вычислений, прежде чем загрузить esi адресом диспетчера и завершиться jmp esi. Кроме того, функциональный гаджет может оказывать влияние
на pc, что позволяет реализовать условное ветвление в программе, ориентированной на прыжки, включая введение циклов. Наиболее очевидным опкодом для ветвления является непрямой переход (jmp), но интересно отметить, что из-за отсутствия зависимости от стека мы можем также использовать последовательности, заканчивающиеся вызовом, поскольку побочный эффект переноса адреса возврата в стек не имеет значения.
Существует несколько различных видов функциональных гаджетов, необходимых для получения тех же выразительных возможностей ROP.
Загрузка данных В подходе, ориентированном на возврат, есть очевидное место для размещения данных: в самом стеке. Это позволяет использовать вездесущие инструкции pop для загрузки регистров. В
JOP, однако, можно загружать значения данных различными способами - подойдет любой гаджет, который загружает из указателя и продвигает его.

На x86 существует целый ряд последовательностей загрузки строк и циклов, которые делают это. Кроме того, хотя JOP не полагается на стек для потока управления, нет причин, почему стек не может быть
использован в качестве механизма загрузки данных, как в ROP, поскольку существующие методы защиты сосредоточены на защите потока управления на основе стека, а не простого доступа к данным. В нашей
реализации указатель стека esp перенаправляется, и стек используется для этой цели.

Доступ к памяти

Для доступа к памяти необходимы устройства загрузки и хранения. Эти устройства получают адрес памяти и считывают или записывают байт или слово в это место.

Арифметика и логика

После того как операнды (или указатели на операнды) загружены в регистры процессора, операции ALU могут быть применены путем поиска гаджетов с соответствующими опкодами (add, sub, and, or и т.д.).
Ветвление

Безусловное ветвление может быть достигнуто путем изменения регистра или области памяти, используемой для pc. Условное ветвление выполняется путем корректировки pc на основе
результата предыдущего вычисления. Это может быть достигнуто несколькими способами, включая добавление вычисленного значения к pc, использование короткой условной ветви внутри гаджета для изменения pc на основе логики, или даже использование специальной инструкции условного перемещения x86 для обновления pc (cmov).
Системные вызовы

Хотя вышеперечисленных приспособлений достаточно, чтобы сделать JOP полным по-Тьюрингу (т.е. способным к произвольным вычислениям), для выполнения большинства практических задач необходимы системные вызовы. Есть несколько различных способов сделать системный вызов. Во-первых, можно вызывать легитимные функции, подставляя в стек соответствующие параметры и адрес возврата гаджета, который
восстановит соответствующее состояние процессора и выполнит диспетчер. Однако, поскольку существующие средства защиты от ROP могут обнаружить это, более разумным подходом является непосредственный вызов системы. Методология для этого зависит от процессора и операционной системы. В Linux на базе x86 можно выполнить int 0x80, чтобы поднять прерывание, перейти к подпрограмме ядра под названием kernel_vsyscall
для выполнения инструкции sysenter или даже выполнить инструкцию sysenter напрямую.

Открытие гаджетов

Метод поиска гаджетов в целевом бинарнике - просто разобрать его и поискать косвенные инструкции перехода или вызова. Однако инструкции на платформе x86 имеют переменную длину, поэтому декодирование одной и той же памяти с одним смещением по сравнению с другим может дать совершенно разный набор операций. Это означает, что каждый двоичный файл x86 содержит ряд непредусмотренных кодовых последовательностей,
доступ к которым можно получить, перейдя к смещению, не входящему в исходную границу инструкции. Учитывая это, алгоритм поиска гаджетов, заканчивающихся на ret, был предложен Шахамом в контексте ROP. Мы используем аналогичный подход в нашем процессе обнаружения гаджетов. Алгоритм работает путем сканирования исполняемой области двоичного файла в поисках допустимого стартового байта
(байтов) инструкции косвенного ответвления. На x86 это байт 0xff, за которым следует второй байт с определенным диапазоном значений. Такие последовательности могут быть найдены линейным поиском. Отсюда простое дело - шаг назад по байтам и декодирование каждого возможного гаджета, заканчивающегося косвенным переходом. Этот подход формально определен в Алгоритме 1.

Снимок экрана от 2023-04-05 12-35-21.png


Как показано в алгоритме, процедура F indGadget(C) использует строковый поиск для нахождения косвенных переходов в кодовой базе C, затем идет назад на δmax байт и дизассемблтруется для каждой результатирующей области кода.
Значение δmax - это максимальный размер гаджета в байтах. Его выбор зависит от средней длины инструкций в данной архитектуре и максимального количества инструкций в одном гаджете. Наш опыт показывает, что, как было замечено в ROP, полезные гаджеты не должны быть длиннее 5 инструкций. Существует несколько критериев, по которым потенциальный гаджет может быть исключен на этом этапе; они определяются процедурой IsV
iableGadget(G). Во-первых, поскольку алгоритм идет назад по одному байту за раз, возможно, что последовательность, которая изначально была непрямым переходом, больше не интерпретируется как таковая. Если это так, то гаджет устраняется. Во-вторых, целью непрямого перехода может быть значение регистра (например, esi), адрес, на который указывает регистр ([esi]), или адрес, на который указывает разыменование
памяти ([0x7474505b]). В последнем случае, если указанный адрес, скорее всего, не будет действительным, доступным для записи во время выполнения, то гаджет будет удален. В-третьих, если какая-либо часть гаджета не кодирует законную инструкцию x86, то гаджет удаляется. Наконец, сам гаджет может содержать условное ответвление, отдельное от косвенного ответвления в конце. Если цель этого ответвления лежит за пределами границ гаджета, то гаджет будет удален. Кроме того, если цель ветви не совпадает с инструкциями, определенными в гаджете, она исключается. Это дает набор потенциально полезных гаджетов в кодовой базе, а для такой
большой кодовой базы, как libc, это означает десятки тысяч гаджетов-кандидатов.
Набор сужается далее с помощью Heuristic(G), которая фильтрует гаджеты, основываясь на их пригодности для конкретной цели. Хотя было проделано много работы по полной автоматизации поиска гаджетов в ROP , поиск гаджетов в JOP добавляет дополнительную сложность. Поскольку каждый гаджет должен завершаться переходом обратно к диспетчеру, необходимо позаботиться о том, чтобы регистр, используемый для этой цели, был
правильно установлен до того, как он понадобится. Это вводит два требования при размещении и цепочке гаджетов, ориентированных на переход. Гаджет не должен уничтожать свою собственную цель прыжка. Цель может быть изменена, однако, если это изменение может быть компенсировано для предыдущего гаджета. Например, если гаджет увеличивает edx в качестве побочного эффекта перед завершением в jmp [edx], то значение edx при запуске
гаджета должно быть на единицу меньше, чем предполагаемое значение. Поскольку гаджеты соединены в цепочку, побочные эффекты предыдущего гаджета не должны нарушать цели перехода последующих. Например, если регистр используется для вычисления в гаджете A и используется как цель перехода в гаджете B, то промежуточный гаджет должен установить этот регистр в адрес диспетчера, прежде чем гаджет B сможет быть использован. Из-за этой дополнительной сложности поиск гаджетов
в данной работе требует дополнительных эвристик, представленных в алгоритме как Heuristic(G). Ниже мы опишем наиболее интересные из этих эвристик. Чтобы найти потенциальные гаджеты диспетчера в кодовой базе, мы разработали эвристику диспетчера. Этот алгоритм работает путем фильтрации всех потенциальных гаджетов, найденных алгоритмом поиска, до небольшого набора, из которого разработчик атаки может выбирать.
Для каждого гаджета мы начинаем с получения цели перехода в последней инструкции гаджета, затем проверяем первую инструкцию в последовательности гаджетов на основе трех условий.
Во -первых, инструкция должна иметь цель перехода в качестве операнда назначения.
Если гаджет не изменяет цель перехода, то он не может быть диспетчером.
Во-вторых, мы фильтруем гаджеты по опкоду.
Из-а большого разнообразия опкодов x86, которые могут быть использованы в pc, целесообразнее фильтровать опкоды через черный список, а не белый. Поэтому мы отбрасываем опкоды, которые не могут
переместить цель хотя бы на размер слова.
В-третьих, операции, которые полностью перезаписывают операнд назначения (например, mov), должны быть самореферентными, т.е. операнд назначения также присутствует в операндах источника. Например, опкод "загрузить эффективный адрес" (lea) может выполнять вычисления на основе одного или нескольких регистров. Инструкция lea edx,[eax+ebx] вряд ли будет полезна в диспетчере, так как она перезаписывает edx вычислением eax+ebx - она не
продвигает edx на предсказуемое значение. И наоборот, инструкция lea edx,[edx+esi] продвигает edx на значение, хранящееся в esi, и поэтому является кандидатом в диспетчеры. Требование самореференции не является строго необходимым, так как может существовать многорегистровая схема, которая может действовать как диспетчер, но выполнение этого требования значительно упрощает поиск, устраняя огромное
количество ложных срабатываний. После того как гаджеты отфильтрованы по этим трем условиям, мы рассматриваем каждого кандидата и выбираем того, кто использует наименее часто используемые регистры. Это связано с тем, что регистр или регистры, используемые диспетчером, будут недоступны для вычислений, а значит, функциональные гаджеты, которые полагаются на эти регистры, будут непригодны для использования. Поэтому, чтобы
сделать доступным наибольшее количество функциональных гаджетов, мы выбираем диспетчер, который использует наименьшее количество регистров. Существует ряд эвристик, позволяющих находить различные виды функциональных гаджетов. В случае гаджетов условного ветвления операция условного ветвления может быть разделена на два этапа: (1) обновление регистра общего назначения на основе сравнения, и (2) использование этого результата для
перестановки пк. Поскольку шаг 2 представляет собой простую арифметическую операцию, мы сосредоточимся на поиске гаджетов, реализующих шаг 1. Результат сравнения хранится в регистре флагов сравнения процессора (EFLAGS на x86), и наиболее удобным способом использования этих флагов является инструкция условного перехода. Например, в x86 инструкция je будет
"переходить при равенстве", то есть если установлен "нулевой флаг" ZF. Чтобы найти гаджеты, использующие такие инструкции, эвристике достаточно найти те гаджеты, первая инструкция которых является условным переходом к другой инструкции, расположенной позже в том же гаджете. Такой гаджет будет условно перескакивать через некоторую часть тела гаджета, и потенциально может быть использован для захвата результата сравнения в регистр
общего назначения, где он может быть позже добавлен к pc. Помимо использования условных переходов, некоторые процессоры, например, современные итерации x86, поддерживают инструкции "условное перемещение" (cmov) и "установка байта по условию" (set). Мы можем найти гаджет, который использует эти инструкции для условного изменения регистра. Наконец, существуют инструкции, которые неявно обращаются к флагам компаратора, например, adc ("add with carry"). Эта инструкция работает как обычное сложение, за исключением того, что операнд desti- nation будет увеличен еще на один, если установлен флаг переноса. Поскольку флаг переноса представляет результат сравнения
беззнаковых целых чисел при использовании инструкции cmp, инструкции типа adc ведут себя как инструкции условного перемещения, и поэтому могут использоваться для обновления регистров общего назначения результатом
сравнения. Эвристика для поиска арифметических, логических гаджетов и гаджетов доступа к памяти, по сравнению с этим, намного проще. Нам нужно только ограничить опкод желаемой операцией (add, mov, and и т.д.) и убедиться,
что любые операнды назначения не конфликтуют с целью
перехода.

Начало атаки

Уязвимости, которые могут привести к прыжково-ориентированной атаке, схожи с уязвимостями возвратно-ориентированного программирования. Ключевое различие, однако, заключается в том, что в то время как ROP требует контроля над указателем инструкций eip и указателем стека esp, JOP требует eip плюс любой набор областей памяти или регистров, используемых для запуска гаджета диспетчера. На практике этого можно достичь, сначала направив поток управления через специальный гаджет инициализатора. В частности, гаджет инициализатора заполняет соответствующие регистры либо с помощью арифметики и логики, либо путем загрузки значений из памяти. После этого инициализатор переходит к диспетчеру, и можно начинать выполнение программы, ориентированной на прыжки. Гаджет инициализатора может принимать различные формы, в зависимости от набора регистров, которые необходимо заполнить. Одним из простых случаев является гаджет, выполняющий инструкцию popa, которая загружает каждый регистр общего назначения из стека. Инициализатор не является строго необходимым во всех случаях: если атакующий может взять на себя поток управления в то время, когда регистры будут установлены в полезные значения, диспетчер может быть запущен прямо оттуда. Точные уязвимости, которые могут привести к атаке , ориентированной на возврат, подробно обсуждались ранее. Резюмируя что злоумышленник может предположительно начать атаку, ориентированную на переход, перезаписав стек, указатель функции или буфер setjmp. Поскольку первые два способа уже известны, мы объясним буфер setjmp ниже. Буфер setjmp Стандарт C99 определяет функции setjmp() и longjmp() как средство достижения нелокальных переходов. Эта функциональность часто используется в сложных обработчиках ошибок и в библиотеках потоков пользовательского режима, таких как некоторые версии pthreads. Программист выделяет структуру jmp_buf и вызывает функцию setjmp() с указателем на эту структуру в той точке программы, куда в конечном итоге вернется поток управления. Функция setjmp() будет хранить в памяти структуру, текущее состояние процессора в объекте jmp_buf, включая указатель структуры eip и некоторые (но не все) регистры общего назначения.
В это время функция возвращает 0. Позже программист может вызвать функцию longjmp() с объектом jmp_buf, чтобы вернуть поток управления обратно в точку, когда первоначально была вызвана setjmp(), минуя все стековые семантики. Эта функция восстановит сохраненные регистры и перейдет к сохраненному значению eip. В это время setjmp() как будто возвращается во второй раз, теперь уже с ненулевым значением повторного обращения. Если злоумышленник сможет перезаписать этот буфер и впоследствии будет вызван longjmp(), то поток управления может быть перенаправлен на гаджет инициализатора для запуска программы, ориентированной на прыжок. Из-за простоты этой техники она используется в нашем примере атаки ниже.

РЕАЛИЗАЦИЯ

Чтобы продемонстрировать эффективность техники JOP, мы разработали атаку, ориентированную на прыжки, на современную систему Linux. В частности, атака разработана под Debian Linux 5.0.4 на 32-битной платформе x86, все гаджеты взяты из библиотеки GNU libc. Debian поставляет несколько версий libc для различных CPU и сред виртуализации. Нашей целевой библиотекой была /lib/i686/cmov/libc-.7.so,6 версия для процессоров, поддерживающих инструкцию условного перемещения (cmov). Далее мы сначала рассмотрим общую доступность гаджетов в libc, а затем остановимся на выборе диспетчера и
других функциональных гаджетов. После этого мы представляем пример полной атаки, ориентированной на прыжок.
4.1 Доступность гаджетов
Программирование, ориентированное на прыжки, требует гаджетов, которые заканчиваются непрямыми ветвями вместо инструкции ret. Эти ветви могут быть инструкциями jmp или, поскольку мы не заинтересованы
в использовании стека для потока управления, инструкциями call. Повторим, что переменный размер инструкции в x86 допускает множество интерпретаций одного и того же кода, что приводит к набору
предполагаемых инструкций, генерируемых компилятором, и альтернативному набору непредусмотренных инструкций, найденных при переинтерпретации кода с другого смещения. Для изучения относительной
доступности гаджетов в JOP по-сравнению с ROP, на рисунке 4

Снимок экрана_2023-04-05_10-45-54.png


показано сравнение между количеством инструкций ret и количеством косвенных инструкций jmp и call. Если бы мы были ограничены использованием только предназначенных гаджетов jmp и call, то вряд ли в
одной только libc было бы достаточно гаджетов для поддержания кода атаки, завершенной по-Тьюрингу, поскольку в ней присутствует всего несколько сотен таких инструкций. Однако, если учесть
непреднамеренные последовательности инструкций, выбор гаджетов становится намного больше
Для поиска гаджетов мы применяем алгоритм, приведенный выше. При этом мы должны выбрать значение для δmax, наибольшего размера гаджета, который следует рассматривать, в байтах.
Консервативным значением будет средняя длина гаджета, умноженная на среднюю длину инструкции (3,5), т.е. [5 · 3.5] = 18 Однако единственным побочным эффектом слишком большого значения δmax
является включение в список гаджетов, которые могут быть малополезны из-за своей длины, поэтому мы делаем ошибку в пользу инклюзивности и устанавливаем δmax = 32 байта. Позже список гаджетов
может быть отсортирован по количеству инструкций на гаджет, чтобы сосредоточиться на более коротких и, следовательно, более вероятных вариантах. Когда алгоритм поиска гаджетов применяется к
вырезаемым областям libc, обнаруживается 31 136 потенциальных гаджетов.

4.2 Гаджет диспетчера
Используя эвристику, полный набор потенциальных гаджетов был сокращен до 35 кандидатов. Поскольку вариантов очень много, мы можем исключить последовательности длиной более двух инструкций (минимальная
длина любого полезного гаджета), и у нас останется 14 кандидатов на выбор. Проведя ручной анализ, мы обнаружили, что 12 из них являются жизнеспособными. Эти варианты используют либо арифметику, либо
разыменование для продвижения пк и полагаются на различные регистры для работы. Поскольку регистры, используемые диспетчером, недоступны для использования функциональными гаджетами, выбор
диспетчера, использующего наименее распространенные регистры, сделает доступным наиболее широкий спектр функциональных гаджетов. Исходя из этого, мы выбрали следующий гаджет диспетчера в нашем примере шеллкода:
Код:
add ebp, edi
jmp [ebp-0x39]

Этот гаджет использует указатель базы стека ebp в качестве цели перехода pc, добавляя к нему значение, хранящееся в edi. Мы видим, что в отношении функциональных гаджетов ни один из этих регистров не играет
заметной роли в коде, сгенерированном компилятором. Кроме того, постоянное смещение -0x39, применяемое к инструкции jmp, не имеет особого значения, поскольку оно может быть статически скомпенсировано при установке ebp для начала.
Поскольку он прост, предсказуем и использует только два малополезных регистра, мы выбрали этот гаджет диспетчера для примера шеллкода,

4.3 Другие гаджеты
Как только диспетчер установлен, одним из первых необходимых функций гаджетов является средство для загрузки операндов. В ROP это достигается путем размещения данных в стеке, перемешиваясь с адресами возврата, которые указывают на гаджеты. Таким образом, гаджеты могут использовать инструкции pop для доступа к данным. Нет причин, почему этот подход не может быть применен в JOP, так как методы защиты от ROP
сосредоточены на злоупотреблениях стеком как средством для управления потоком выполнения, а не данных. В нашей реализации часть атаки включает перемещение указателя стека esp в часть вредоносного буфера. Затем данные могут быть загружены непосредственно из
буфера с помощью инструкции pop. Это составляет основу для нашего гаджета загрузки данных. Для поиска таких гаджетов можно применить эвристику; единственными требованиями являются следующие: (а) первая
инструкция кандидата должна быть pop в регистре общего назначения, отличный от тех, которые используются нашим диспетчером выбора (ebp и edi), и (б) косвенный переход в конце не должен использовать этот
регистр для назначения. Эта эвристика дает 60 возможностей в libc, поэтому мы фильтруем результат, чтобы включить только гаджеты с тремя инструкциями или меньше; это дает
22 возможности. Ручной анализ этого списка дает 14 гаджетов данных загрузки, которые могут быть использованы для загрузки любого из регистров общего назначения, не задействованных в диспетчере.
Дальнейшая фильтрация не требуется -поскольку эти гаджеты имеют различные побочные эффекты и косвенные цели перехода, каждый из них может быть полезен в разное время, в зависимости
от того, какие регистры используются для вычислений в программе, ориентированной на переход. Если необходимо загрузить все регистры сразу, можно выполнить гаджет, использующий инструкцию popa.
Эта инструкция загружает из стека сразу все регистры общего назначения. Это составляет основу гаджета инициализатора, который используется для подготовки состояния процессора к началу атаки.
Аналогично поиску гаджетов для работы с данными о загрузке, основные арифметические и логические гаджеты могут быть найдены с помощью простой эвристики. Из-за недостатка места достаточно
сказать, что существует большой выбор гаджетов, реализующих эти операции. Ограничивая длину гаджета тремя инструкциями, мы находим 221 вариант для гаджета add, 129 вариантов для sub, 112 для or,
1191 для xor и т.д.
Достижение произвольного доступа к памяти достигается аналогичными средствами. Наиболее простые гаджеты памяти используют инструкцию mov для копирования данных между регистрами и памятью.
Эвристика для поиска гаджетов записи в память требует просто найти инструкции вида mov [dest], src, а гаджет чтения из памяти имеет вид mov dest, [src]. Как и в большинстве инструкций x86, адрес
памяти в mov может быть смещен на константу, но это можно компенсировать при разработке атаки. Основываясь на вышеизложенных наблюдениях, поиск в libc обнаружил 150 возможных гаджетов загрузки и 33
возможных гаджета записи, основанных на mov. Сюда не входит большое количество инструкций x86, которые выполняют операции загрузки и хранения неявно, например, инструкции работы со строками
lod и sto.
Самым распространенным способом перемещения результата сравнения в регистр общего назначения являются инструкции adc и sbb, которые работают как add и sub, только увеличивают/уменьшают результат на один раз, если установлен "флаг переноса" процессора. Поскольку этот флаг представляет собой результат сравнения беззнаковых целых чисел, гаджеты, реализующие эти инструкции, можно использовать для
выполнения условных ветвлений. В libc существует 1664 таких гаджетов, 333 из которых состоят всего из двух инструкций. Эти гаджеты могут обновлять любой из регистров общего назначения. Чтобы завершить
условный переход, нам достаточно применить обычные арифметические гаджеты, найденные ранее, чтобы добавить некоторое кратное обновленного регистра к pc. Для выполнения системных вызовов существует
несколько различных подходов, которые может использовать злоумышленник. Конечно, злоумышленник может вызвать обычную библиотечную процедуру, например system().
Однако, поскольку это предполагает создание искусственного стекового кадра, такой подход рискует быть обнаруженным существующими средствами защиты от ROP. Вместо этого злоумышленник может напрямую запросить системный вызов через обычный интерфейс ядра. В Linux на базе x86 это можно сделать, выполнив инструкцию sysenter для доступа к функции "быстрого системного вызова". Чтобы использовать этот механизм, вызывающая сторона (1) установит eax в значение номер системного вызова, (2) установит регистры ebx, ecx и edx в параметры вызова, и (3) выполнит инструкцию sysenter. Как правило, вызывающая сторона также выталкивает ecx, edx, ebp и адрес следующей инструкции в стек, но этот учет необязателен для ориентированных на прыжки at такера. Вместо этого мы можем воспользоваться тем фактом, что адрес возврата указан в стеке, указывая его обратно к диспетчеру. Это означает, что диспетчерский гаджет не должен заканчиваться непрямым переходом. Обратите внимание, что этот адрес возврата адрес не совпадает с адресом возврата обычной функции - интерфейс ядра позволяет пользователю задавать это значение. Это происходит потому, что все системные вызовы имеют одну и ту же точку выхода в пространстве пользователя: небольшой фрагмент кода, предоставляемого ядром, который переходит обратно к сохраненному адресу. Учитывая это, единственной проблемой при выполнении системного вызова является заполнение нужных регистров. Это становится все более сложнее по мере увеличения количества параметров. Для вызовов с тремя параметрами, таких как execve(), необходимо одновременно установить eax, ebx, ecx и edx. Это несколько сложно, поскольку не существует гаджета popa, который переходит на основе регистра, отличного от того, который необходим для системного вызова, а выбор гаджетов становится ограниченным, поскольку регистры общего назначения становятся заняты конкретными значениями. Тем не менее, можно делать произвольные системные вызовы, используя только материал из libc путем объединения нескольких гаджетов в цепочку. Для примера следующая последовательность гаджетов загрузит eax, ebx, ecx и edx из предоставленной злоумышленником памяти, а затем выполнит системный вызов. Эта последовательность гаджетов была использована при конструировании шеллкода для примера атаки, представленной ниже

Код:
popa ; Load all registers
cmc ; No practical effect
jmp far dword [ecx] ; Back to dispatcher via ecx
xchg ecx, eax ; Exchange ecx and eax
fdiv st, st(3) ; No practical effect
jmp [esi-0xf] ; Back to dispatcher via esi
mov eax, [esi+0xc] ; Set eax
mov [esp], eax ; No practical effect
call [esi+0x4] ; Back to dispatcher via esi
sysenter ; Perform system call

Пример атаки

В силу своей простоты мы используем уязвимую тестовую программу, аналогичную той, что привели Checkoway и Shacham. Исходный код этой программы приведен на рисунке 5

Снимок экрана_2023-04-05_11-49-13.png


По сути, эта программа копирует первый аргумент командной строки argv[1] в буфер размером 256 байт на куче. Поскольку программа не ограничивает объем копируемых данных, эта программа уязвима к эксплойту setjmp, описанному ввыше. Злоумышленник может переполнить буфер и, когда когда функция longjmp будет вызвана в строке 17, получить контроль над регистрами ebx, esi, edi, ebp, esp и указателем инструкций eip. указатель инструкций eip. Это конкретное приложение является лишь примером: любой эксплойт, передающий контроль над указателем инструкций и другими регистрами, потенциально может быть использован для начала прыжково- ориентированной атаки.

Мы используем эту программу в качестве платформы для запуска shellcode, ориентированную на прыжки, которая в конечном итоге использует системный вызов execve для запуска интерактивной оболочки. В частности, наш пример атаки был создан в NASM, который, несмотря на то, что является ассемблером, использовался только для задания полей необработанных данных. Макросы и арифметические функции NASM позволяют выразить код эксплойта простым способом. При сборке NASM этот сценарий создает двоичный файл эксплойта, который затем предоставляется уязвимой программе в качестве аргумента командной строки:
Код:
$ ./vulnerable "`cat exploit.bin`".

Это запускает программу, ориентированную на прыжок, и в конечном итоге выдает интерактивную подсказку shell без единой инструкции ret.

5. Пообсуждаем

В этом разделе мы рассмотрим возможные ограничения и обсудим дальнейшие усовершенствования техники программирования, ориентированного на прыжки.

Во-первых, хотя мы обнаружили, что JOP способен произвольные вычисления, построение кода атаки вручную является сложной задачей, даже более сложной, чем в ROP. Основная причина заключается в дополнительном уровне взаимозависимости в JOP-гаджетах. В частности, из-за зависимости от определенных регистров, служащих "состоянием" для системы, ориентированной на переходы (например, указатель на таблицу диспетчеризации и обратный вызов диспетчера после каждого выполнения гаджета), существуют сложные ограничения на последовательность гаджетов, которые могут быть собраны. Часто разработчику атак приходится вводить гаджеты, единственная цель которых - заставить работать следующий гаджет (например, путем установки регистра цели перехода). Это, естественно, усложняет разработку автоматизированных методов для облегчения программирования, ориентированного на прыжки.

Во-вторых, хотя идея программирования, ориентированного на переходы, теоретически применима к архитектурам с инструкциями фиксированной длины (SPARC, ARM и т.д.), может оказаться так, что для реализации полной по-Тьюрингу работы потребуется гораздо большая кодовая база. Это связано с тем, что две особенности x86 делают гаджеты, основанные на jmp и call, особенно многочисленными: (1) инструкции переменной длины допускают множество интерпретаций потока кода, и (2) инструкции косвенного ветвления начинаются с особенно распространенного байта 0xff. Тщательный анализ возможности и эффективности применения JOP на альтернативных платформах (например, MIPS), включая переносимость гаджетов диспетчера и соответствующей таблицы диспетчеризации (раздел 3), является важным вопросом, который мы оставляем для будущей работы.

В-третьих, если мы рассмотрим природу двух различных моделей программирования, т.е. ROP и JOP, то основой уязвимости являются не возвраты или косвенные переходы, а скорее беспорядочное поведение, позволяющее входить по любому адресу в исполняемой программе или библиотеке.

Чтобы защититься от них, необходимо обеспечить целостность потока управления. С другой точки зрения, может быть заманчиво предположить, что эту атаку можно тривиально победить, выявив аномалии, такие как поведение, подобное диспетчеру, или высокочастотные косвенные переходы. К сожалению, это не так. Такую защиту можно легко обойти, если организовать выполнение долго выполняющихся функций и периодически менять диспетчеров. Далее мы рассмотрим смежные работы и обсудим ряд ортогональных средств защиты, которые могут быть использованы для препятствования или предотвратить программирование, ориентированное на возврат или переход.

СВЯЗАННАЯ РАБОТА

Анти-ROP защита Недавно был предложен ряд систем защиты для обнаружения или предотвращения атак возвратно-ориентированного программирования. Например, основанная на

отдельного теневого стека (аналогично [17, 22]), ROPdefender предлагает подход двоичного перезаписывания для обеспечения валидности каждого возврата, блокируя таким образом выполнение

гаджетов, ориентированных на возврат [19]. DROP [16] и DynIMA обнаруживают атаки на основе ROP, отслеживая выполнение коротких последовательностей команд, каждая из которых заканчивается символом ret. Подход

Безвозвратный подход [31] признает необходимость ret для построения и цепочки гаджетов и разрабатывает подход на основе компилятора для удаления присутствия опкода ret.

В отличие от этого, программирование, ориентированное на прыжки, защищено от этих защитных средств, поскольку не полагается на ret или стек возврата для запуска атаки на основе JOP. В этом отношении JOP отражает тенденцию продолжающейся гонки вооружений в области безопасности.

Совсем недавно Онарлиоглу и др. представили G-Free, компилятор, предназначенный для создания двоичных файлов без гаджетов [34]. Это достигается путем удаления всех непреднамеренных передач потока управления, а затем защиты намеченных с помощью шифрования указателей

и куки стека. Поскольку инструкции косвенного перехода и вызова защищены в этой схеме, это предотвратит их неправильное использование и, по сути, обобщит существующие атаки ROP до традиционных атак return-into-libc.

Независимо от нашей работы, в параллельном подходе Checkoway и др. предлагается заменить ret в ROP на pop+jmp на x86, что, возможно, является шагом дальше от

оригинальной модели ROP. Однако последовательности pop+jmp встречаются редко, что приводит к необходимости парадигмы "принеси свой собственный pop+jmp", где последовательность должна быть найдена в особенно большой базе кода. На самом деле, наш анализ текстового раздела стандартной libc в

Debian Linux 5.0.4 на 32-битной платформе x86 не было найдено ни одной последовательности pop+jmp. Кроме того, использование такой последовательности все еще накладывает необходимость полагаться на стек для управления потоком управления между гаджетами. управления потоком управления между гаджетами. Для сравнения, наша модель JOP не имеет

не имеет таких ограничений, и поэтому угрожает гораздо более широкому набору приложений и сред. Реализация на ARM в этой работе была также представлена реализация, которая полагается на гаджет "update-load-branch" для поддержания потока управления. Это сходство подтверждает теорию о том, что прыжко-ориентированные атаки могут быть кросс-платформенной угрозой, не ограничиваясь x86. С другой точки зрения, другие ортогональные схемы защиты

(например, рандомизация) были предложены для защиты от атак внедрения кода. В частности, рандомизация компоновки адресного пространства (ASLR) рандомизирует компоновку памяти.

рандомизирует структуру памяти запущенной программы, затрудняя определение адресов в libc и другом легитимном коде, на которые полагаются атаки типа return-into-libc или ROP/JOP. Однако существуют атаки де-рандомизации, позволяющие обойти ASLR или ограничить ее эффективность. Рандомизация набора инструкций (ISR) вместо этого рандомизирует набор инструкций для каждого

чтобы инструкции в инжектированном коде атаки не выполнялись правильно, даже если атаки успешно перехватили поток управления. Однако он не эффективен для атак на основе return-into-libc и ROP/JOP. Безопасность памяти В прошлом было предложено множество защитных механизмов для более эффективного обеспечения повышенной безопасности памяти. или безопасности памяти. Например, CFI [4] и пастырство программ

разработаны для защиты свойства целостности потока управления выполняемой программы. DFI и другие основываются на свойстве целостности потока управления и далее расширяют его

для других типов безопасности памяти (например, целостности потока данных). Обратите внимание, что если целостность потока управления строго соблюдается, то и ROP, и JOP будут заблокированы. ROP и JOP будут заблокированы от перехвата потока управления в первую очередь. Однако точное соблюдение CFI

требует сложного анализа кода, который может быть трудно получить, особенно для программ с большой кодовой базой, включая libc или современные ядра ОС. Кроме того, CFI не получил широкого распространения, вероятно, из-за проблем с производительностью, особенно в случае применения в реальном времени. Другие случаи повторного использования кода Совсем недавно исследователи нашли интересное применение повторному использованию определенных фрагментов кода из вредоносных программ для их лучшего понимания. Например, Кабальеро и др. предложили BCR - инструмент, цель которого - извлечь функцию из двоичного файла (вредоносного ПО), чтобы впоследствии использовать ее повторно. Колбич и др. разработали Inspector для повторного использования существующего кода в двоичном файле и преобразования его в

автономный гаджет, который впоследствии может быть использован для (повторного) выполнения определенных функций вредоносного ПО. Для сравнения, ROP и JOP повторно используют легитимный код уязвимой программы для построения произвольных вычислений без внедрения кода.



Примечание авторов:

"Разрешение на создание цифровых или бумажных копий всей или части этой работы для личного или аудиторного использования предоставляется без взимания платы при условии, что копии не делаются и не распространяются для получения прибыли или коммерческой выгоды и что копии имеют данное уведомление и полное цитирование на первой странице. Для иного копирования, переиздания, размещения на серверах или распространения по спискам требуется предварительное специальное разрешение и/или плата.

ASIACCS '11, 22-24 марта 2011 г., Гонконг, Китай.
Copyright 2011 ACM 978-1-4503-0564-8/11/03 ...$5.00"

Если есть у кого 5 баксов, переведите, а то я совсем на ноле)


Как хорошо что asm_EVM гораздо проще чем всё что тут я понапереводил ;)
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
ROP\JOP\COP - code-reuse атаки интересная тема. Если хочешь попробовать свои силы в подобном то можешь попробовать вот эти таски https://ropemporium.com/ , если где то застрял гугли врайтапы, но лучше конечно самому находить решение, так как удовольствия от этого будет больше. И да как я говорил ранее, в этом разделе так же приветствуется решение ctf-задачек категории pwn. так что не стесняйтесь публикуйте ваши врайтапы и рассказывайте чему вы научились.

p.s. говоря о code-reuse атаках не стоит забывать еще о Control-flow Enforcement Technology (Intel® CET) уже по дефолту многие компиляторы вставляют endbrach32\64 инструкции.

Intel Control-Flow Enforcement Technology (CET)

две основные особенности
1) непрямое отслеживание ветвей endbr
2) shadow stack


инструкция endbr64 и endbr32 это заглушка для control-flow-hijacking атак. Если цп старый инструкция считается как nop.


endbr64 это означает «Завершить 64-битную ветвь» или, точнее, "Завершить непрямую ветвь в 64-битной версии".

Код:
IF EndbranchEnabled(CPL) & EFER.LMA = 1 & CS.L = 1
  IF CPL = 3
  THEN
    IA32_U_CET.TRACKER = IDLE
    IA32_U_CET.SUPPRESS = 0
  ELSE
    IA32_S_CET.TRACKER = IDLE
    IA32_S_CET.SUPPRESS = 0
  FI
FI;


Эта CET функция используется, чтобы убедиться, что ваши непрямые ветки действительно ведут в допустимое место.

ENDBRANCH — это новая инструкция, которая используется для маркировки допустимых целевых адресов непрямых вызовов и переходов в программе. Этот код операции инструкции выбран как NOP на устаревших машинах, так что программы, скомпилированные с новой инструкцией ENDBRANCH, продолжают работать на старых машинах без применения CET. На процессорах, поддерживающих CET, ENDBRANCH по-прежнему является NOP и в основном используется конвейером процессора в качестве инструкции маркера для обнаружения нарушений потока управления. ЦП реализует конечный автомат, который отслеживает косвенные инструкции jmp и call. При обнаружении одной из этих инструкций конечный автомат переходит из состояния IDLE в состояние WAIT_FOR_ENDBRANCH. В состоянии WAIT_FOR_ENDBRANCH следующая инструкция в программном потоке должна быть ENDBRANCH.


Ключ gcc

-fcf-protection=[full|branch|return|none|check]

1. Значение branch сообщает компилятору о необходимости проверки правильности передачи потока управления в точках инструкций косвенного перехода, т.е. инструкций call/jmp.

2. Значение return реализует проверку правильности в момент возврата из функции.
3. Значение full является псевдонимом для указания и , branch и return.
4. Значение none отключает инструментирование.

5. Значение check используется для конечной ссылки с оптимизацией времени связи (LTO).
Выдается ошибка, если объектные файлы LTO скомпилированы с -fcf-protection разными значениями.
Значение check игнорируется во время компиляции.




Обратите внимание, что это не гарантирует, что будет вызвана правильная функция - если злоумышленник изменит поток управления, чтобы перейти к другой функции, которая также начинается с endbr64, конечный автомат не будет жаловаться и продолжит выполнение программы. Тем не менее, это по-прежнему значительно уменьшает поверхность атаки, поскольку большинство атак JOP/COP нацелены на инструкции в середине функции (или даже переходят прямо «в» инструкции).
 
ROP\JOP\COP - code-reuse атаки интересная тема. Если хочешь попробовать свои силы в подобном то можешь попробовать вот эти таски https://ropemporium.com/ , если где то застрял гугли врайтапы, но лучше конечно самому находить решение, так как удовольствия от этого будет больше. И да как я говорил ранее, в этом разделе так же приветствуется решение ctf-задачек категории pwn. так что не стесняйтесь публикуйте ваши врайтапы и рассказывайте чему вы научились.

p.s. говоря о code-reuse атаках не стоит забывать еще о Control-flow Enforcement Technology (Intel® CET) уже по дефолту многие компиляторы вставляют endbrach32\64 инструкции.
спасибо, как смарты освою так сюда и пойду, очень интересная тема эта низкоуровневая
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх