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

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

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

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

0x01. Необходимые знания: память приложения
0x02. Необходимые знания: Стек
0x03. Необходимые знания: вызовы и возврат функций
0x04. Атака: переполнение буфера стека
0x05. Атака: атаки с возвратом к libc (ret2libc)

Нажмите ниже, чтобы перейти к следующей части этой серии:

Бинарная эксплуатация ELI5 - Часть 2

0x01 - Необходимые знания: память приложения

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

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

Итак, давайте разберемся с аналогией и свяжем ее с концепцией виртуальной памяти:

Конвейерная лента для суши: Как я сказал выше, компьютеры должны быть очень осторожны и точны в том, где они помещают данные приложения в память, чтобы ничего не перезаписывалось. Хотя компьютер может просто аккуратно поместить приложения в физическую память, это в конечном итоге вызовет проблемы, поскольку фрагменты приложений быстро заполнят все пространство. В приведенном выше примере отдельные кусочки суши можно рассматривать как фрагменты приложения или блоки памяти, выделенные приложением, в то время как весь набор суши (6 на контейнер) можно рассматривать как само приложение.

Дрейк и Джош: Чтобы обойти проблему заполнения конвейерной ленты отдельными кусочками суши, Дрейк и Джош организовали их в отдельные контейнеры, которые затем могли перемещаться по конвейерной ленте. Как и Дрейк и Джош, ваш компьютер также организует и размещает приложения в контейнерах, называемых ячейками виртуальной памяти. Эти области виртуальной памяти (или виртуальные адресные пространства) позволяют приложению полагать, что оно имеет полный контроль над всем объемом памяти. Однако, когда приложение вызывает местоположение или пытается выделить память в его виртуальном адресном пространстве вместо предоставления доступа к произвольной физической памяти, небольшая, но чрезвычайно важная часть оборудования в ЦП (центральном процессоре) вашего компьютера называется MMU. (Блок управления памятью) сопоставляет вызов приложения с определенной областью физической памяти и облегчает любые манипуляции с памятью. Такое отображение памяти позволяет компьютерам организовывать и обрабатывать несколько приложений с требованиями к динамической памяти с помощью централизованно организованной таблицы поиска.

Также важно отметить, что хотя весь код приложения содержится в его виртуальном адресном пространстве, приложения часто используют динамически подключаемые библиотеки (DLL), такие как libc или kernel32. Эти библиотеки DLL представляют собой просто внешние (не хранящиеся в адресном пространстве приложения) системные приложения или другие пользовательские приложения, из которых программа импортирует код. Возьмите, например, приведенный ниже код:

Как видите, нигде в этой 6-строчной программе я не определяю, что такое printf. Однако эта программа по-прежнему будет работать без проблем и распечатывать «Hello World». Это связано с тем, что функция printf - это системная функция, определенная в libc, которая является стандартной библиотекой C. В процессе компиляции libc внешне связывается с исполняемым файлом. В системе Linux вы можете просмотреть зависимости разделяемых библиотек программы с помощью команды ldd.

Если вы смотрите на приведенный выше снимок экрана и задаетесь вопросом, что такое вообще 0xb7e99000, то это адрес библиотеки libc в памяти. Адреса памяти представлены в шестнадцатеричном формате. Пожалуйста, щелкните здесь, чтобы получить дополнительную информацию о шестнадцатеричной системе счисления.

0x02 - Необходимые знания: Стек

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

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

Теперь вместо Боба и стопки тарелок просто представьте себе компьютер и стопку объектов данных. Каждый раз, когда что-то помещается в стек, оно добавляется в верхнюю часть стека, а всякий раз, когда что-то выталкивается из стека, оно удаляется из верхней части стека. , что делает его л ным I n первым механизмом O ut (LIFO).

Стек используется программами для хранения всевозможных вещей, таких как указатели на функции (расположение функции в памяти) и переменные.

0x03 - Необходимые знания: вызовы функций и возврат

Взгляните на приведенный ниже код:

В этом фрагменте кода мы видим, что функция add принимает 2 аргумента целочисленного типа, называемых A и B. В функции main мы видим, что мы вызвали Add с номером 1 для аргумента A и номером 2 для аргумента Б. Если мы разбиваем этот код на базовый машинный код, мы видим:

Как видите, при вызове функции с параметрами программа сначала помещает оба параметра в стек, а затем выполняет инструкцию call. Этот оператор call перенаправляет указатель инструкции программы (указатель инструкции похож на маленький карандаш, который вы используете для отслеживания того, какое слово вы читаете. Указатель инструкции всегда указывает на инструкцию, которая вот-вот должна быть выполнена (слово, которое будет прочитано)) на адрес вызываемой функции. Однако перед переходом к вызываемой функции оператор call помещает адрес следующей инструкции ниже в стек, чтобы при возврате функции add он знал откуда продолжить обработку. Адрес места, в которое функция должна вернуться, называется функцией указатель возврата.

0x04 - Атака: переполнение буфера стека

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

Алиса и Боб встречались раньше, но Алиса рассталась с Бобом. Время шло, Алиса уходила, но Боб так и не смог справиться с горем. Теперь Алиса выходит замуж за Роберта Хакермана, заклятого врага Боба. Боб, будучи жутким чудаком, шпионил за всеми свадебными планами Алисы через свой секретный доступ к электронной почте Алисы. Боб увидел, что Алиса наняла известного дизайнера свадебных тортов, который хотел бы, чтобы Алиса отредактировала части его рецепта с учетом ее вкусовых предпочтений. Дизайнер дал Алисе рекомендуемый список ингредиентов, которые нужно добавить, но сказал, что сделает все, что она захочет, в точности. Боб открыл документ, прикрепленный к электронному письму дизайнера, и увидел, что строки рецепта выглядят так:

… Затем мы добавим глазури аромат ______. После этого мы добавим немного шоколада…

Боб заметил, что если вы введете «Банан» в строку, текст будет выглядеть так:

… Затем мы придадим глазури аромат, добавив банан. После этого добавим шоколада…

Но если бы Боб ввел в строку «Strawberry», текст выглядел бы так:

… Затем мы добавим в глазурь ароматизатора, добавив клубничный тертер, мы добавим немного шоколада…

Боб понял, что это будет идеальный способ испортить свадьбу Алисы, все, что ему нужно было сделать, это перезаписать остальную часть рецепта своей собственной, отвратительной версией! В день свадьбы Алисы дизайнер наконец показал торт, который он приготовил - он был весь в жучках и сделан из замороженного майонеза!

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

Итак, теперь давайте посмотрим на это в реальном мире. Взгляните на следующий фрагмент кода с сайта exploit-exercises.com:

В приведенной выше функции мы видим, что массив символьных типов с именем buffer создается с размером 64. Затем мы видим, что для переменной modified установлено значение 0, а для переменной Функция gets вызывается с переменной buffer в качестве аргумента. Наконец, мы видим оператор IF, который проверяет, не является ли измененное значение 0. Очевидно, что в этом приложении для переменной modified не установлено значение, отличное от 0, так как же мы собираемся ее изменить?

Что ж, давайте сначала взглянем на документацию по функции gets:

Как видите, функция gets просто принимает вводимые пользователем данные. Однако функция не проверяет, вписывается ли ввод пользователя в структуру данных, в которой мы его храним (в данном случае, buffer), и, таким образом, мы можем переполнить структуру данных и влияют на другие переменные / данные в стеке. Кроме того, поскольку мы знаем, что все переменные хранятся в стеке, и мы знаем, что такое измененная переменная (0), все, что нам нужно сделать, это ввести достаточно ввода, чтобы перезаписать измененную переменную. Давайте посмотрим на диаграмму:

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

0x05 - Атака: ret2libc

Прежде чем мы поговорим об атаках Return-to-libc (ret2libc), давайте поговорим о libc немного глубже.

Как мы знаем (из раздела 0x01), libc - это стандартная библиотека C. Это означает, что он содержит все общие системные функции, включенные в язык программирования C. А что, если бы злоумышленник смог получить контроль над программой для выполнения некоторых из этих функций?

Что ж, это в значительной степени и есть ret2libc. Идеальной аналогией для последствий ret2libc может быть серия Matrix. Вернитесь к классической сцене Пушки, много оружия. Оператор Танк смог полностью обойти и перепрограммировать матрицу, чтобы ТОННА орудий появлялась из ниоткуда.

Вы можете думать о возврате в libc так: мы можем взять под контроль матрицу (стандартная библиотека C) и заставить ее делать все, что мы хотим.

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

Одной из наиболее распространенных функций, используемых для атак ret2libc, является функция system. Взглянем на документацию:

Как видите, системная команда просто выполняет команды оболочки (оболочка - это командная строка Linux). Более того, если мы прочитаем описание, мы увидим, что система просто выполняет / bin / sh -c ‹command› (/ bin / sh - это фактическая команда оболочки), и команда передается в функцию через аргумент.

Итак, все, что нам нужно сделать, чтобы получить доступ из командной строки к машине, на которой запущено уязвимое приложение, - это поместить «/ bin / sh» в стек в качестве Затем замените указатель возврата или вызова адресом памяти системной функции, чтобы функция вызывалась с / bin / sh в качестве аргумента, запуская оболочку и предоставляя нам полный доступ к системе.

Эксплойты, множество эксплойтов.

0x06 - Часть 1 Заключение

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

0x01. Виртуальная память и как приложения обрабатываются в памяти
0x02. Динамически связанные библиотеки и libc
0x03. Стек
0x04. Как вызываются функции и как работает возврат из функции
0x05. Переполнение буфера стека
0x06. Атаки с возвратом к libc (ret2libc)

Надеюсь, эта статья была полезной. Нажмите ниже, чтобы перейти ко второй части этой серии:

Бинарная эксплуатация ELI5 - Часть 2

Кроме того, если вас интересует реверс-инжиниринг, ознакомьтесь с моей серией статей BOLO: Reverse Engineering:

BOLO: Обратный инжиниринг - Часть 1 (Основные концепции программирования)
BOLO: Обратный инжиниринг - Часть 2 (Расширенные концепции программирования)

И, если вы ищете больше контента ELI5, ознакомьтесь с моей статьей Объясните Spectre и Meltdown, как будто мне 5 лет.

нажмите «Спасибо»,
нажмите «за»,
вызовите чтение