• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья (ПЕРЕВОД) Windows User Mode Exploit Development: OSED - Часть 3

tenac1ous

Shellcode
Модератор
Регистрация
22.01.2023
Сообщения
169
Реакции
224

2.5 Дополнительные возможности WinDbg

До сих пор мы обсуждали, как получать доступ к памяти и изменять её, а также как контролировать поток выполнения отлаживаемого приложения.
Далее мы рассмотрим дополнительные возможности WinDbg — вычисления, преобразования и псевдорегистры.

2.5.1 Просмотр загруженных модулей и символов в WinDbg

Часто полезно посмотреть, какие модули загружены в адресное пространство процесса.
Команда lm позволяет вывести список всех загруженных модулей, включая их начальные и конечные адреса в виртуальном адресном пространстве:
1758816267445.png

Четвёртый столбец в предыдущем примере показывает информацию о файлах символов для каждого модуля.
Когда мы выполняем эту команду в только что запущенном экземпляре Notepad, символы по умолчанию не загружены.

Однако мы можем принудительно перезагрузить символы с помощью команды: .reload /f
А затем снова вывести список модулей, чтобы убедиться, что символы были подгружены.
1758816403059.png

1758816425849.png



Когда PDB-файл для модуля недоступен, WinDbg переходит в режим export symbols.
В этом случае отладчик попытается собрать имена символов, экспортированных модулем, используя Export Directory Table.

Команда lm также может фильтровать вывод по имени модуля, принимая шаблон с символом подстановки * и параметр m.
В листинге 44 показан пример фильтрации, при которой отображаются все модули, начинающиеся с kernel:
1758816543615.png


Когда у нас есть список модулей, мы можем получить больше информации об их символах с помощью команды x (examining symbol).
В следующем примере мы выводим информацию о символах, присутствующих в модуле KERNELBASE.
Обратите внимание, что используется символ подстановки (*), чтобы отобразить все символы, начинающиеся с CreateProc:
1758816687646.png

1758816706764.png


Команда x из прошлого примера может быть очень полезна, если мы не знаем или не помним полное имя символа — она быстро выведет все совпадения.

2.5.2 Использование WinDbg в качестве калькулятора

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

Математические вычисления выполняются командой evaluate expression?.
Очень часто нам нужно, например, найти разницу между двумя адресами или получить младший и старший байты значения DWORD.
WinDbg легко справляется с этим, как показано в следущем примере.
1758816810126.png


Ввод для команды ? по умолчанию интерпретируется как шестнадцатеричный формат, если только мы не используем префиксы 0n или 0y, о которых пойдёт речь в следующем разделе.

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

2.5.3 Формат вывода данных

По умолчанию WinDbg отображает данные в шестнадцатеричном формате.
Однако иногда нам нужны данные в другом виде.

К счастью, мы можем преобразовать их в десятичный или двоичный формат с помощью префиксов 0n (decimal) и 0y (binary).
Примеры таких преобразований показаны ниже с использованием команды evaluate expression (?)
1758823430568.png

1758823467535.png


Здесь мы сначала преобразуем шестнадцатеричное число 41414141 в десятичное, затем конвертируем десятичное число 0n41414141 обратно в шестнадцатеричное, и наконец преобразуем двоичное значение 0y1110100110111 в десятичный и шестнадцатеричный формат.

Команда .formats также полезна — она позволяет сразу увидеть представление одного и того же значения в разных форматах, включая ASCII-представление, как показано ниже.
1758823544427.png


2.5.3.1 Упражнение

  1. Потренируйтесь в преобразовании форматов данных, используя как специальные префиксы для типов (например, 0n, 0y), так и команду .formats.

2.5.4 Псевдорегистры

В WinDbg есть набор псевдорегистров.
Это не аппаратные регистры CPU — а заранее определённые отладчиком переменные. Многие псевдорегистры, например$teb, которые мы использовали ранее, имеют заранее заданное семантическое значение.

Кроме встроенных, есть 20 пользовательских псевдорегистров — от $t0 до $t19, которые можно использовать как временные переменные при выполнении вычислений. Также мы можем выполнять вычисления, используя эти псевдорегистры вместе с явными значениями.

При обращении к псевдорегистрaм и обычным регистрам рекомендуется ставить префикс @ — это указывает WinDbg трактовать содержимое как регистр или псевдорегистр. Такой приём ускоряет вычисления, поскольку WinDbg не будет пытаться сначала разрешить имя как символ.

Иногда при реверсе или разработке эксплойта приходится делать достаточно сложные расчёты. В следующем примере показана условно сложная (фиктивная) вычислительная последовательность.
1758823800334.png


Тот же расчёт можно выполнить с использованием псевдорегистра. В примере мы записываем результат первого вычисления в псевдорегистр $t0. Затем читаем содержимое $t0, и WinDbg выводит значение для проверки. Наконец, выполняем сдвиг вправо на 8 бит от $t0, чтобы получить окончательный результат. Этот процесс показан в приведённом ниже примере:
1758823930467.png


Псевдорегистры позволяют сохранять значения или разбивать сложные вычисления на части — мы ещё будем использовать их в следующих модулях.

2.6 Подведение итогов​

На этом завершается вводный модуль по WinDbg и архитектуре x86. Мы рассмотрели ряд команд и приёмов, которые пригодятся на протяжении всего курса. Хотя WinDbg относительно неинтуитивен, он крайне мощный, и уверенное владение им потребуется для последующих разделов.

Внимательно пересмотрите шаги из этого модуля и отработайте каждое упражнение. Привыкайте пользоваться .hh — встроенной справкой, — и заглядывайте в cheat sheet для быстрого повторения.
3. Эксплуатация переполнений стека

В этом модуле мы рассмотрим буферное переполнение (buffer overflow) в приложении Sync Breeze. Мы научимся использовать эту ошибку памяти, чтобы получить контроль над потоком выполнения приложения.
Начнём с простого proof-of-concept, который вызывает падение приложения. Затем постепенно усложним PoC, добьёмся контроля над регистрами процессора и в конечном счёте научимся манипулировать памятью для надёжного выполнения произвольного кода удалённо.
Для разработки эксплойта под этот класс уязвимостей нам нужно понять условия, делающие возможной атаку переполнением стека. Прежде чем приступить к самому приложению, мы разберём основные понятия, связанные с этим классом уязвимостей, на очень простом примере.

3.1 Введение в переполнения стека​

Ниже приведён листинг простейшего исходного кода на C для приложения, уязвимого к переполнению буфера.
1758824252214.png


Даже если вы никогда раньше не работали с кодом на C, логика приведённого листинга должна быть достаточно проста для понимания. Прежде всего стоит отметить, что в C функция main рассматривается как любая другая функция: она может принимать аргументы, возвращать значения вызывающей программе и т. п. Единственная особенность — её «вызывает» сама операционная система при старте процесса.

В нашем примере функция main сначала объявляет символьный массив buffer, рассчитанный на 64 символа. Поскольку эта переменная объявлена внутри функции, компилятор C56 рассматривает её как локальную переменную и резервирует для неё место (64 байта) в стеке. Конкретно — эта область памяти выделяется внутри стека фрейма функции main на время её выполнения. Как следует из названия, локальные переменные имеют локальную область видимости — они доступны только внутри той функции или блока кода, где объявлены. В отличие от них, глобальные переменные размещаются в секции .data программы — отдельной области памяти, доступной всему коду приложения.

Далее программа копирует содержимое первого аргумента командной строки (argv[1]) в массив buffer с помощью strcpy. Обратите внимание: в языке C нет встроенного типа «string» — на низком уровне строка — это последовательность символов, завершающаяся нулевым байтом ('\0'), т. е. по сути одномерный массив символов.

Наконец, программа завершает выполнение и возвращает ноль — стандартный код успешного завершения — операционной системе.

Когда мы запускаем такую программу, мы передаём ей аргументы командной строки. Функция main обрабатывает их через параметры argc и argv: argc — целое число, обозначающее количество переданных аргументов, а argv— массив указателей на сами аргумент-строки.

Если переданный аргумент не длиннее 64 символов, программа отработает как ожидается и нормально завершится. Однако поскольку в коде отсутствуют проверки длины входных данных, при передаче более длинного аргумента — например, 80 байт — лишние 16 символов выйдут за границы массива bufferи перезапишут соседние области стека, «переполнив» массив. Это проиллюстрировано в следующем примере:
1758824715799.png



Эффекты такой порчи памяти зависят от множества факторов, включая размер переполнения и данные, которые в него попали. Как видно на рисунке выше, если переполнение достаточно большое, злоумышленник может перезаписать на стеке адрес возврата уязвимой функции управляемыми данными.

Вспоминая механику возвращения из функции (разобранную в предыдущем модуле), мы знаем: когда функция завершается, адрес возврата берётся со стека и используется для возобновления выполнения в вызывающей функции. В нашем простом примере при этом перезаписанный адрес возврата попадёт в регистр процессора EIP(Extended Instruction Pointer).

Далее процессор попытается прочесть следующую инструкцию по адресу 0x41414141 (0x41 — шестнадцатеричное представление ASCII-символа «A»). Поскольку это невалидный адрес в адресном пространстве процесса, процессор вызовет исключение доступа (access violation), и приложение упадёт.

Важно снова подчеркнуть: регистр EIP используется процессором для управления исполнением кода на уровне ассемблера. Поэтому надёжный контроль над EIP даёт возможность выполнить любой машинный код по вашему выбору, а в конечной стадии — запустить shellcode и получить обратную оболочку (reverse shell) в контексте уязвимого приложения. Мы пройдём весь этот путь до практической реализации далее в модуле на примере буферного переполнения в Sync Breeze.


3.2 Установка приложения Sync Breeze​

Теперь, когда мы разобрались с концепцией буферных переполнений, давайте подготовим тестовый стенд.
Сначала нужно установить приложение syncbreezeent_setup_v10.0.28, которое лежит в папке C:\Installers\stack_overflow\; при установке примем все параметры по умолчанию. После завершения установки поставим галочку Run Sync Breeze Enterprise 10.0.28, как показано далее.
1758825320713.png


Далее включим веб-сервер — для этого нажмите кнопку Options, как показано ниже:
1758825390533.png


Далее в левом меню выбираем Server и ставим галочку Enable Web Server on Port:, оставив порт по умолчанию — 80.
1758825610176.png


Это приложение будет многократно крашится в процессе разработки эксплойта, поэтому нам нужно иметь возможность быстро его перезапускать. Это удобно делать из оснастки Services.msс— перезапуская службу Sync Breeze Enterprise от имени администратора.

3.2.1.1 Упражнение​

  1. Установите приложение Sync Breeze на вашу учебную виртуальную машину с Windows 10.

3.3 Crash приложения Sync Breeze

В 2017 году в механизме входа (login) Sync Breeze версии 10.0.28 была обнаружена уязвимость типа буферного переполнения. Конкретно — поле username в HTTP POST-запросе при попытке входа можно было использовать, чтобы вызвать краш приложения. Поскольку для триггера уязвимости не требуются рабочие учётные данные, это считается pre-authentication buffer overflow — переполнение до аутентификации.

В общем случае есть три основных подхода к поиску багов в приложениях. Самый простой — ревью исходного кода, если он доступен. Если исходников нет, можно применять методы реверс-инжиниринга или фаззинг для обнаружения уязвимостей. В дальнейшем по ходу курса мы сосредоточимся на реверс-инжиниринге, но поскольку эта уязвимость публична и для неё уже доступен первоначальный proof-of-concept (PoC) в базе Exploit Database, сейчас мы сосредоточимся непосредственно на эксплуатации целевого приложения.

Давайте начнём с копирования Python proof-of-concept на нашу машину Kali:
1758825868438.png


Код выше - формирует HTTP POST-запрос для логина, в котором в поле username передаётся 800 символов «A». Затем скрипт устанавливает соединение с удалённым сервером на порту 80 (адрес сервера передаётся как аргумент) и отсылает этот запрос.
Чтобы убедиться, что код действительно работает как задумано, мы сначала подключим отладчик к целевому процессу. Это позволит нам инспектировать память процесса и содержимое регистров процессора в случае падения приложения.

Поскольку syncbrs.exe работает как служба под учётной записью Local System, нужно запускать WinDbg с правами администратора, чтобы иметь возможность присоединиться к процессу.
1758826011904.png


После присоединения отладчика выполним команду g, чтобы позволить приложению продолжить работу. Затем запустим наш скрипт с машины Kali.
Когда мы запускаем скрипт, приложение падает ожидаемым образом с ошибкой доступа (access violation). В WinDbg мы можем посмотреть регистр EIP и заметить, что он перезаписан четырьмя байтами 0x41 — частью нашей строки имени пользователя, буфера из 800 символов «A».

Это первый шаг: мы подтвердили, что отправка имени пользователя длиной 800 байт вызывает падение приложения. В следующих разделах мы постепенно увеличим воздействие — от простого краша до выполнения кода на удалённом хосте.

3.3.1.1 Упражнение​

  1. Напишите автономный скрипт, который воспроизводит падение приложения (реплицирует crash).

3.4 Эксплуатация переполнения буфера в Win32​

Разработка полноценного рабочего эксплойта — от падения приложения до получения удалённой оболочки — приносит сильное удовлетворение. Как уже упоминалось, первичная цель — получить контроль над регистром EIP, поскольку это позволит нам управлять потоком выполнения целевого приложения. Дальше нам нужно будет найти способ внедрить вредоносный код (например, обратную оболочку) в адресное пространство процесса и перенаправить выполнение на этот код.

3.4.1 Немного о DEP, ASLR и CFG​


Нужно понимать защитные механизмы, которые усложняют получение или эксплуатацию контроля над EIP. Хотя Sync Breeze был собран без этих механизмов, в последующих модулях мы столкнёмся с ними.
Microsoft реализует следующие технологии:
  • DEP (Data Execution Prevention) — набор аппаратных и программных мер, добавляющих проверки памяти и помогающих предотвратить выполнение вредоносного кода. DEP предотвращает исполнение из страниц данных, возбуждая исключение при попытке выполнить код в таких областях.
  • ASLR (Address Space Layout Randomization) — рандомизация базовых адресов загружаемых приложений и DLL при каждой загрузке ОС. На старых системах (например, Windows XP), где ASLR отсутствует, DLL всегда загружаются по одним и тем же адресам, что облегчает эксплуатацию. В сочетании с DEP, ASLR даёт сильную защиту.
  • CFG (Control Flow Guard) — реализация контроля целостности потоков управления от Microsoft. Механизм валидирует косвенные переходы (например, вызовы через регистр вместо прямого адреса, как CALL EAX) и призван мешать перезаписи указателей на функции в эксплойтах.

Как уже сказано, Sync Breeze был скомпилирован без этих защит, что делает эксплуатацию значительно проще и даёт нам удобную площадку для изучения техники без дополнительных сложностей со стороны mitigations.


3.4.2 Получение контроля над EIP​


Получение контроля над регистром EIP — ключевой шаг при эксплуатации ошибок памяти. С помощью EIP мы можем направлять выполнение приложения. Пока что мы лишь видели, что часть нашего буфера из символов A перезаписывает EIP.

Прежде чем загрузить в EIP корректный адрес назначения и управлять выполнением, нам нужно понять, какая именно часть нашего буфера попадает в EIP.
Есть два распространённых способа определить, какие именно байты нашего буфера попадают в EIP. Первый — бинарный (binary search): посылаем вместо 800 A'ов сначала 400 A и 400 B. Если EIP перезаписывается символами B, то значит «четвёрка байт» попадает во вторую половину буфера. Дальше можно разделить эту вторую половину на B/C и повторять, пока не отследим точные четыре байта.m Второй, более быстрый метод — использовать уникальную неповторяющуюся последовательность, составленную из 4-байтных фрагментов. Когда EIP перезапишется одним из этих 4-байтных фрагментов, по этой уникальной последовательности можно однозначно определить сдвиг (offset) в буфере. Попробуем этот метод.

Чтобы сгенерировать неповторяющуюся последовательность, воспользуемся скриптом из Metasploit — pattern_create.rb. Metasploit (поддерживаемый Rapid7) — это продвинутая платформа для разработки, тестирования и использования эксплойтов. Проект эволюционировал из игры в мощный фреймворк для тестирования на проникновение и исследования уязвимостей; в нём есть утилиты для генерации уникальных шаблонов и вычисления смещений.

Скрипт pattern_create.rb находится по пути:
Bash:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb
Но его удобнее запускать через алиас msf-pattern_create из любой директории в Kali.
1758828773532.png


Чтобы задать длину создаваемой строки, мы передаём параметр -l и нужную длину (в нашем случае — 800)
1758828824144.png


Это сгенерирует нашу строку; далее обновим наш Python-скрипт, заменив существующий буфер на только что созданную строку:
1758828977622.png


При перезапуске Sync Breeze и повторном запуске нашего эксплойта мы увидим, что в регистре EIP теперь находится новая последовательность — показано ниже.
1758829289397.png

Регист EIP был перезаписан значением 42306142 (шестнадцатеричное представление строки "B0aB"). Зная это, мы можем воспользоватьсяpattern_offset.rb , чтобы определить смещение этих четырёх байт в нашей строке. В Kali этот скрипт также можно запускать из любой директории через msf-pattern_offset.
Чтобы найти смещение, где происходит перезапись EIP, используем опцию -l для указания длины исходной строки и -q для передачи байт, обнаруженных в EIP (42306142):
1758829341999.png


Скрипт msf-pattern_offset сообщает, что эти четыре байта находятся на смещении 780 в 800-байтном паттерне. Обновим наш proof-of-concept с учётом этой информации: отправим 780 символов A, затем 4 символа B и после них 16 символов C. Наша цель — чтобы четыре B (0x42424242) точно попали в регистр EIP.
1758829402549.png


Когда веб-сервер снова падает, полученный буфер оказывается правильно структурированным. Теперь в EIP содержатся наши четыре B (0x42424242), как показано здесь.
1758829444425.png


Мы получили полный контроль над регистром EIP и, следовательно, над потоком выполнения Sync Breeze! Однако чтобы двигаться дальше, нам нужно заменить заполнитель 0x42424242 на корректный адрес и перенаправить исполнение приложения на код, который мы хотим выполнить.

3.4.2.1 Упражнения​

  1. Определите смещение внутри входного буфера, при котором удаётся успешно контролировать EIP.
  2. Обновите свой автономный скрипт, поместив в EIP уникальное значение, чтобы убедиться, что смещение рассчитано верно.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх