«Человеку свойственно ошибаться… для того, чтобы действительно ошибиться, требуется пароль root».

0x00-Предисловие

Эта статья является частью 3 из серии статей Использование двоичных файлов ELI5. Для частей 1 и 2 перейдите по ссылкам ниже:

Использование двоичного кода ELI5 - Часть 1
Использование двоичного кода ELI5 - Часть 2

В этой статье мы рассмотрим:

0x01 - Необходимые знания: регистры
0x02 - Необходимые знания: распределение кучи и памяти
0x03 - Атака: возвратно-ориентированное программирование (ROP)
0x04 - Защита: рандомизация разметки адресного пространства
0x05 - Атака: Кучное опрыскивание

Давайте начнем!

0x01 - Необходимые знания: регистры

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

Как упоминалось в предыдущих статьях, стек - это область памяти в адресном пространстве каждой программы, которая используется для хранения данных (т. Е. Переменных, ячеек памяти и т. Д.). Хотя стек можно рассматривать как рюкзак программы, каждая программа также имеет свой собственный набор карманов, известных как регистры. Регистры - это небольшие области памяти, которые, в зависимости от регистра, хранят структурированную или произвольную информацию. Под структурированным или произвольным я подразумеваю, что некоторые регистры хранят определенные типы информации, такие как регистр ESP, известный как указатель стека и исключительно хранит адрес памяти вершины стека в любой момент времени, в то время как другие, такие как регистр EBX, могут хранить произвольную информацию. При сохранении данных в регистрах вы должны переместить данные в регистр с помощью команды сборки MOV:

Регистры могут использоваться не только для хранения данных, но и для выполнения общих операций. Давайте посмотрим на пример:

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

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

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

0x02 - Необходимые знания: распределение кучи и памяти

Итак, мы обсудили два метода хранения данных: стек и регистры. Обе эти области памяти идеально подходят для хранения обычных переменных, адресов памяти и т. Д. Но что, если вам нужно сохранить что-то действительно большое? Или что, если вашей программе требуется намного больше памяти, чем было выделено для стека? Итак, здесь на помощь приходит Heap. Heap - это просто большой кусок памяти, который выделяется для программы ядром. Если стопка - это наш рюкзак, а регистры - наши карманы, кучу можно рассматривать как комнату, в которую мы кладем наши вещи на ночь, вам может понадобиться только небольшой угол комнаты, чтобы положить ваши вещи, но на всякий случай вам понадобится more вы всегда можете выделить больше места (больше памяти). Чтобы выделить память в куче, мы используем функцию malloc. Malloc - это функция C, которая выделяет желаемый объем памяти. Например, если вы вызвали malloc (8), программа выделит 8 байтов в куче. Наиболее распространенной реализацией malloc является реализация Дуга Ли, которая также сохраняет размер блока каждого блока распределения в байтах, предшествующих блоку.

Давайте посмотрим на распределение памяти на простом примере:

Боб работает на фабрике. Боба попросили отложить 3 винта для Части A и 4 винта для Части B. Боб пишет «3-A» и «4-B» на листе бумаги, затем кладет на бумагу 7 винтов. Теперь Боб назначил правильное количество винтов для обеих частей в одной и той же области.

Видеть? Довольно просто! Если я хочу выделить 8 байтов памяти в куче, я использую malloc (8), который помещает 0x08 в кучу в пространстве, непосредственно предшествующем ячейке памяти выделенной области. Таким образом, если позже нам потребуется выделить больше памяти, программа просто берет базовый адрес кучи и добавляет к нему 0x08, чтобы учесть ранее выделенное пространство! Очень просто!

0x03 - Атака: возвратно-ориентированное программирование (ROP)

Ранее в этой серии статей мы говорили об атаке Return-to-libc (Ret2libc), при которой злоумышленник перезаписывает возвращаемую переменную и заставляет программу выполнить системную команду через библиотеку libc. В этом разделе мы поговорим о Return-Oriented Programming, которое можно рассматривать как более продвинутую версию ret2libc (хотя обычно она вообще не включает libc). В атаках ROP злоумышленник манипулирует обратными вызовами для возврата к различным сегментам в коде сборки (называемым гаджетами), которые позволяют злоумышленнику фактически получить полный контроль над выполнением программы. Например, злоумышленник может непрерывно переходить к различным гаджетам, чтобы создать бэкдор в программе, не манипулируя каким-либо кодом или областями памяти (без прямого выполнения в стеке или куче)!

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

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

Давайте посмотрим на пример:

Как видите, если злоумышленник может манипулировать данными в стеке, он / она может просто вставить переменные и перезаписать адреса возврата, чтобы использовать программу так, как он / она хочет! В приведенном выше примере все, что мы сделали, это заставили программу добавить 3 и 4, чтобы регистр eax был равен 7, но что, если бы вместо этого простого добавления вы использовали целую цепочку гаджетов для помещения различных фрагментов данных в стек и в конечном итоге попадают на гаджеты с сетевыми вызовами, которые могут извлекать данные с компьютера и отправлять их на сервер, удерживаемый злоумышленником.

0x04 - Защита: рандомизация разметки адресного пространства (ASLR)

Ранее в этой серии статей мы говорили о DEP / NX как о жизнеспособной защите от выполнения кода в областях памяти, таких как стек и куча. Однако с помощью таких атак, как ret2libc и Return Oriented Programming, мы быстро смогли обойти этот механизм защиты. Итак, теперь, если мы не можем защититься от кода, выполняемого из-за ROP-атак, и мы не можем гарантировать защитные возможности stack canaries из-за эксплойтов строки формата, как мы можем защититься от атак с использованием бинарных файлов? Хорошо, а что, если мы просто рандомизируем, где находятся сегменты памяти, каждый раз, когда мы открываем программу? Именно это и есть рандомизация разметки адресного пространства. Каждый раз, когда программа выполняется, стек, куча, секция .text (где хранится ассемблерный код) и другие секции памяти помещаются в память в случайных смещениях, так что, хотя сама программа может работать безупречно, злоумышленник может: t просто получить адрес памяти важной части данных в стеке или найти одни и те же гаджеты в одном и том же месте каждый раз, когда они пытаются использовать исполняемый файл. Кроме того, с дальнейшей реализацией рандомизированного связывания адресов ядра злоумышленники не смогут даже уверенно находить связанные библиотеки, такие как libc, поскольку каждый раз при инициализации ссылки библиотека будет загружаться в память по адресу случайное смещение.

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

0x05 - Атака: Кучное опрыскивание

Итак, прежде чем мы перейдем к тому, что такое Heap Spray, мне нужно в начале сказать, что я действительно пропускаю атаку в этой серии статей - Переполнение кучи. Это связано с тем, что они ОЧЕНЬ похожи на переполнение буфера стека, при переполнении кучи злоумышленник получает возможность записывать произвольные данные в кучу, и, пока DEP / NX не включен, злоумышленник может записать вредоносный код в куча. Однако, как упоминалось в приведенном выше разделе ASLR, даже если злоумышленник может записать вредоносный код в кучу, он / она не сможет выполнить его, поскольку расположение кода в памяти будет варьироваться в зависимости от того, где программа поместила вредоносный код. код в куче (который будет рандомизирован посредством реализации ASLR). Итак, как нам найти и выполнить этот код?

Что ж, что, если вместо того, чтобы просто один раз написать вредоносный код в определенную область в куче, мы просто скопируем его по всей куче, а затем попытаемся выполнить где-нибудь в большом разделе кучи? Что ж, именно это и представляет собой кучное опрыскивание. Это в значительной степени закон средних чисел - кидайте в стену достаточно дерьма, и в конце концов что-то должно прилипнуть!

Давайте разберем это на более простой пример:

Боб - слепой игрок в дартс. Очевидно, что если Боб просто бросит один дротик, у него почти не будет шансов попасть в яблочко (так как он даже не видит доску!). Однако, если Боб бросит 1000 дротиков, по крайней мере один из них обязательно попадет в цель. центр мишени.

Если мы поместим наш вредоносный код в кучу один раз, очень маловероятно, что мы найдем его при указании на отдельные адреса памяти, однако, если мы полностью заполним кучу нашим вредоносным кодом, мы обязательно попадем в него в конечном итоге!

Обратите внимание, что в 64-битных системах распыление кучи гораздо менее эффективно, чем в 32-битных системах, поскольку в 32-битных системах вы можете полностью заполнить кучу вредоносным кодом, в то время как в 64-битных системах куча настолько велика, что даже если вы пытались распылить кучу, у вас все еще могут возникнуть проблемы с поиском вредоносного кода.

0x06 - Заключение

В этой статье мы рассмотрели:

0x01. Регистры
0x02. Куча
0x03. Маллок
0x04. Рандомизация разметки адресного пространства
0x05. Рандомизированное связывание адреса ядра
0x06. Возвратно-ориентированное программирование
0x07. Кучное опрыскивание

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

push «Спасибо»
ret
push «For»
ret
pop eax
pop ebx
mov ecx, «чтение»
ret