Здравствуйте! В этом уроке мы изучим разработку эксплойтов для Linux. Для этой цели мы используем пюре protostar Linux. Protostar был разработан эксплуатирующей организацией exercises.com. К сожалению, хост-сайт сейчас недоступен. В любом случае вы можете загрузить файл iso из Интернета. Просто погуглите. Поэтому сначала загрузите его и используйте виртуальную коробку или Vmware в качестве программного обеспечения для виртуализации.

В качестве первого шага загрузите protostar и войдите в систему как root. Имя пользователя/пароль по умолчанию — «root:godmode». После входа в систему как пользователь root используйте ifconfig, чтобы получить IP-адрес пюре.

Теперь вы можете использовать SSH в Linux или putty для доступа к нашему затиранию жертв. На этот раз вы должны войти в систему как обычный пользователь. Учетные данные по умолчанию: «пользователь: пользователь».

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

Теперь начинается самое интересное. Все вызовы находятся внутри «/opt/protostar/bin».

Так что используйте cd && ls

cd /opt/protostar/bin && ls

В игре 25 уровней, которые можно разделить на следующие основные категории.

  1. Переполнение буфера на основе стека
  2. Переполнение буфера в куче
  3. Форматировать строку Эксплойты

Наиболее простая для понимания часть — это эксплойты, основанные на стеке. Даже если вы новичок в использовании разработки, вы можете понять, что происходит. Первый уровень, который вы хотите попробовать, это stack0. Это научит вас, как происходят вызовы функций? Как строятся кадры стека и как перегружать данные за пределами выделенного буфера и т. д.

давайте посмотрим, что мы должны сделать.

./stack0

просто введите строку и посмотрите, что произойдет.

Сказано повторить попытку.

Мы также предоставили исходный код, но на самом деле это не очень помогает. Просто постарайтесь понять, что происходит.

#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
int main(int argc,char **argv){
  volatile int modified;
  char buffer[64];
  modified=0;
  gets(buffer);
  if(modified!=0){
    printf("you have changed the 'modified' variable\n");
  }
  else
  {
    printf("Try again?\n");
  }
}

Сначала он объявляет две переменные с именами «модифицированный» и «буфер». Размер буфера 64 байта. После того, как он принимает строку в качестве ввода от пользователя и копирует ее в буферное пространство. Этот код не выполняет никакой проверки привязки перед копированием данных в буферное пространство. Неважно, больше ли предоставленная строка, чем буферное пространство. В таких ситуациях происходит переполнение буфера.

Вы заметили что-то особенное при объявлении «модифицированного» целочисленного значения? почему есть изменчивое ключевое слово? Сначала мы присваиваем нулевое значение нашему «модифицированному» значению. Но в этом коде оно никогда не меняется, и после этого есть if-statement для проверки, равна ли переменная int нулю или нет. Что за шутка слышите.

. Когда компилятор видит это, он не заботится о if-statement и оптимизирует код. Вот почему ключевое слово volatile используется в приведенном выше коде. Он говорит компилятору: «Привет, GCC, не беспокойся о целочисленном значении». Это может измениться во время выполнения :-)’

Теперь пришло время разобрать бинарник и увидеть его внутреннюю работу. Мы используем GDB для этого. Позвольте представить вам наш замечательный инструмент GDB. Это аббревиатура от отладчика GNU. Используя отладчик, мы можем увидеть, как что-то происходит внутри кода затирания. На следующем снимке экрана вы можете видеть, что я использовал синтаксис Intel для сборки.

set disassembly-flavor intel

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

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

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

главный

В левой части есть несколько шестнадцатеричных значений. Они называются адресами памяти. Наши инструкции по сборке хранятся в этих местах. Память компьютера разделена на небольшие части, называемые байтами. Вы знаете, что один байт равен 8 битам. 1 бит может содержать ноль или единицу. Таким образом, в двоичном формате 8 бит могут содержать 256 значений. Их диапазон составляет от 0 до 256 в десятичном формате. Обычно мы работаем с 4-байтовыми словами.

В ЦП есть 5 основных компонентов для инструкций процессов.

  1. Шина данных
  2. Декодер инструкций
  3. Счетчик команд
  4. Арифметико-логическое устройство (АЛУ)
  5. Регистры

счетчик программ отслеживает, какая инструкция должна быть обработана в этот раз, а какая будет выполнена следующей. На самом деле это произошло с регистром EIP. Регистр EIP всегда содержит адрес памяти инструкции. Теперь CPU знает адрес памяти инструкции. Таким образом, он принимает инструкцию и передает все, что когда-либо было найдено по этому адресу, в декодер инструкций. Инструкции, извлеченные из памяти, называются кодами операций. Они имеют свое собственное значение. Код операции для pop EDI — 5f, а код операции для inc ebp — 45. Обязанность декодера инструкций — выяснить, что делать с эти коды операций. Если он видит код операции 5f, он говорит, что ЦП «выталкивает из стека и сохраняет значение ESP в EDI». На последнем этапе необходимые данные проходят через шину данных и обрабатываются в АЛУ. После этого обработанные данные сохраняются в памяти или регистрах. Хорошо, я надеюсь, вы поняли, что происходит.

На самом деле такие инструкции, как push ebp / mov ebp,esp, исходят не из основной функции. Они включаются компилятором для создания кадра стека для функции. Позвольте мне быстро представить вам термин стек.

Стек — это концепция, которая используется в информатике. В программах мы должны использовать функции, чтобы сделать вещи простыми и понятными. В таких языках, как C и python, вы можете видеть, что мы передаем функции некоторые аргументы, а функции также возвращают некоторые данные. Итак, как это возможно? .Это место, где Стек играет. Мы используем стек для передачи аргументов функции. Стек всегда начинается с верхней памяти и растет до верхней памяти. Мы можем добавить что-то в стек с помощью команды push и удалить с помощью pop команда. Регистр ESP всегда указывает на вершину стека.

На следующем изображении я установил точку останова внутри основной функции. Для этого я использовал break *0x80483f4. Вы можете спросить меня: «Почему вы не использовали break main?». Хорошо, если мы используем break main, отладчик пропускает пролог функции и заботится только о коде основной функции, потому что он знает, что код пролога исходит от компилятора. Поскольку мы хотим посмотреть, как идет сборка стека, мы устанавливаем BP таким образом.

Затем мы используем команду i r, чтобы увидеть, что находится внутри регистров. На самом деле это краткая форма информационных регистров. Вы можете использовать один из них (i r или информационные регистры). Обратите внимание, что EIP указывает на адрес 0x80483f4. Ты помнишь это? Это был адрес первой инструкции приведенного выше дизассемблированного кода. EIP содержит это значение, потому что там находится следующая инструкция, ожидающая выполнения. мы остановили выполнение в начале кода. Ниже приведено графическое представление стека. Вы можете видеть прямо сейчас, что на вершине стека есть что-то под названием ret. Итак, что же это такое? Это адрес возврата, и после завершения процесса нашей функции ЦП должен перейти по этому адресу и выполнить любую инструкцию, найденную там.

Мы можем проверить стек также в GDB. Давайте посмотрим, как это сделать. Ниже приведена команда для проверки памяти в шестнадцатеричном формате.

х/х [адрес памяти]

Если мы хотим видеть содержимое в десятичном формате, мы используем d, а если мы используем t, мы можем видеть его в двоичном формате.

x/d 0xbffff7bc : проверить память в десятичном виде по адресу 0xbffff7bc

x/x 0xbffff7bc : проверить память в шестнадцатеричном формате по адресу 0xbffff7bc

x/t 0xbffff7bc : проверить память в двоичном виде по адресу 0xbffff7bc

Что, если я хочу проверить несколько слов, начинающихся с адреса? Мы можем сделать это таким образом.

x/10wx 0xbffff7bc : проверить 10 слов в шестнадцатеричном формате по адресу 0xbffff7bc.

Еще одно замечание. Используя этот метод, мы можем проверить память непосредственно в регистре.

x/30wx $esp.

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

Следующая инструкция — push ebp. Так что теоретически значение регистра EBP должно быть скопировано на вершину стека после этой инструкции. Давайте посмотрим, правда это или нет?

Вы можете видеть, что в синем поле есть значение, скопированное в стек, и это 0xbfff838. Это не что иное, как значение EBP.

. Случилось другое. Esp изменен с 0xbffff7bc на 0xbffff7bc. Подсчитайте их разницу с помощью своего калькулятора в уме. Будет 4. Да, размер регистра 4 байта. Таким образом, ESP уменьшился на 4 байта. Подождите, почему ESP уменьшился, пока мы помещаем данные в стек? Это связано с тем, что стек растет до нехватки памяти. Если что-то помещается в стек, ESP уменьшается. Если мы выскочим из стека, ESP станет высоким. Во всяком случае, сейчас стек выглядит так.

Следующая инструкция для выполнения — mov ebp, esp. Таким образом, значение ESP должно быть скопировано в EBP. Теперь оба регистра ESP и ESP указывают на вершину стека вот так.

Давайте посмотрим на эту ситуацию в GDB.

Я использовал другую команду GDB под названием ni Hear. Это похоже на «следующую инструкцию». Название говорит все. Он просто выполняет следующую инструкцию. Также на снимке экрана выше вы можете видеть, что ESP никогда не менялся. Таким образом, ESP остается на своем текущем местоположении.

Далее идет код as и esp, 0xffffffff0. Эта команда используется для выравнивания стека, и мы не хотим особо заботиться об этом. Как всегда ESP меняется вот так. (Идет по низкому адресу)

В качестве следующей инструкции есть sub esp, 0x60 Таким образом, ESP уменьшается на 96 байт. Откуда 96? . 60 в шестнадцатеричном формате похоже на 96 в десятичном формате. Вот как распределяется место для локальных переменных в стеке.

Мы можем видеть это и в GDB.

0xbffff7b0–0xbffff750 = 0x60 ==> 96 байт в десятичном формате .

В ПОРЯДКЕ. Посмотрим, что дальше?.

mov DWRD PTR [esp + 0x5c], 0x0

Этот код получает адрес, на который указывает esp + 0x5c, и копирует в него нулевое значение. Поскольку 0x5c равно 92 в десятичном виде, ноль копируется на 4 байта впереди сохраненного EBP. Вы можете себе представить, что на самом деле делает эта строка кода? В нашем исходном коде C было значение int, равное нулю. Это то значение.

Следующая инструкция есть.

lea eax, [esp + 0x1c]

lea означает «Загрузить эффективный адрес». Это загрузит адрес, указанный esp + 0x1c = esp + 28.

После этого все, что есть в EAX, помещается в стек. Что обе вышеуказанные инструкции сделали вместе? Они загружают адрес в стек. Но почему? .Это аргумент для следующей функции. Следующее, что нужно сделать, это вызвать функцию GETS. Аргумент этой функции был помещен в стек. После вызова функции GETS она записывает данные по этому адресу памяти.

Давайте посмотрим, что происходит, когда функция GETS записывает входные данные в буфер на стеке. Теперь я ввожу несколько A в качестве строки для работы.

Вы можете ясно видеть, что наш ввод копируется в стек.

Что, если я введу большее количество As? Оно переполнится нашим предыдущим значением (модифицированным целым числом). Сколько данных необходимо для переполнения в целочисленное значение? Поскольку наш буфер составляет 64 байта, если я введу 65, то он будет изменен.

Теперь все ясно и ОК. Пришло время добычи. Для этого мы можем использовать прекрасный ❤ Python.

Если я введу python -c «print ‘\x41’ * 65» в оболочке, я могу получить 65 As print. Таким образом, я могу направить вывод этой команды в качестве ввода программы stack0 следующим образом.

Потрясающий. мы сделали это. Мы успешно изменили значение. Это была не одна команда. мы изучили все теории.

Теперь есть еще одна вещь. Что, если я введу более крупный ввод? .

У нас ошибка сегментации. Настоящее счастье начинается слышать. Мы собираемся узнать больше по этой теме в будущих уроках.

До скорой встречи. Спасибо за чтение