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

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

tenac1ous

Shellcode
Модератор
Регистрация
22.01.2023
Сообщения
169
Реакции
224
Добро пожаловать на курc "Windows User Mode Exploit Development: (EXP-301)

EXP-301 разработан для специалистов по безопасности, которые уже имеют некоторый опыт в поиске известных уязвимостей и в использовании публичных эксплойтов против них.
Этот курс охватывает разработку эксплойтов от начального до промежуточного уровня для двоичных приложений в операционной системе Windows. Он также включает введение в реверс-инжиниринг двоичных приложений, чтобы помогать в поиске уязвимостей.


1.1 О курсе EXP-301
Прежде чем перейти к материалам курса, давайте разберём некоторые базовые термины.
Понятие разработки эксплойтов применяется во многих областях offensive security. Например, мы можем находить уязвимости в веб-приложениях и создавать эксплойты для их эксплуатации, либо злоупотреблять небезопасными конфигурациями, связанными с правами на сервисы или файлы.
Чтобы сузить область, этот курс фокусируется на приложениях, написанных на языках низкого уровня, таких как C++, и затем скомпилированных в машинный код. Для таких приложений исходный код часто недоступен при поиске уязвимостей или разработке эксплойта.
После компиляции кода, написанного на языках низкого уровня, его нельзя легко декомпилировать. Это отличается от языков высокого уровня, таких как C# или Java, где код компилируется лишь в промежуточный байт-код.
Поскольку мы будем атаковать приложения, написанные на языках низкого уровня, нам придётся работать с бинарным представлением кода. Это означает, что мы будем разбирать низкоуровневые механизмы, включая прямые манипуляции с памятью, ассемблерный код, флаги процессора и регистры.

В области эксплуатации двоичных файлов Windows существует множество направлений для изучения, и чтобы получить твёрдое понимание разработки эксплойтов, важно заложить прочный фундамент. Чтобы ещё сильнее сузить рамки, в этом курсе будут рассмотрены техники эксплуатации и уязвимости в серверных приложениях, которые не содержат встроенных скриптовых движков. Это исключает такие приложения, как веб-браузеры.
Хотя большинство современных Windows-систем — 64-битные, многие приложения всё ещё 32-битные. Это возможно на платформе Windows благодаря реализации Windows on Windows 64 (WOW64). На рабочих станциях это включает такие приложения, как пакет Microsoft Office, а многие корпоративные серверные приложения также всё ещё остаются 32-битными.
EXP-301 будет фокусироваться исключительно на 32-битной архитектуре ввиду большого объёма знаний, необходимых для освоения и становления специалистом по разработке эксплойтов. Также важно отметить, что большинство приёмов для 32-бит можно адаптировать под 64-бит, поэтому глубокое изучение на 32-битной архитектуре имеет большую ценность.

Атакующим нужна база пересекающихся знаний, чтобы и найти уязвимость, и написать эксплойт.
Вы будете развивать эту базу знаний через модули этого курса. EXP-301 начинается с разбора перезаписи указателя инструкции (Instruction Pointer — EIP) и перезаписи структурированной обработки исключений (Structured Exception Handling — SEH), а также с изучения egghunters и того, как создавать собственный шеллкод. Затем мы переходим к изучению приёмов реверс-инжиниринга с использованием IDA Pro, после чего разбираем способы обхода защиты от выполнения данных (Data Execution Prevention — DEP) с помощью техник Return-Oriented Programming (ROP). Финальные модули курса охватывают продвинутые кастомные ROP-цепочки, обход рандомизации адресного пространства (Address Space Layout Randomization — ASLR) и создание/использование примитивов чтения и записи для достижения сложных атак.
2.0 WinDbg и архитектура x86
В этом модуле мы познакомимся с основными понятиями архитектуры x86, включая регистры CPU, память программ и механизм возврата функций. Затем мы изучим базовые навыки, необходимые для отладки приложений с помощью WinDbg.
Практика в WinDbg и уверенное владение его командами необходимы для того, чтобы справиться с более сложными темами эксплуатации, которые будут рассматриваться позже в курсе.

2.1 Введение в архитектуру x86

Прежде чем мы начнём отлаживать нарушения целостности памяти, нам нужно обсудить организацию памяти программы, понять, как работает программное обеспечение на уровне процессора, и дать несколько базовых определений.
По ходу объяснений мы будем часто ссылаться на ассемблер (Assembly, asm) — крайне низкоуровневый язык программирования, который очень близко соответствует машинным инструкциям, встроенным в CPU.
2.1.1 Память программы
Когда двоичное приложение запускается, оно выделяет память очень специфичным образом внутри границ адресного пространства, используемого современными компьютерами. На рисунке 1 показано, как память процесса распределяется в Windows между наименьшим адресом памяти (0x00000000) и наибольшим адресом (0x7FFFFFFF), используемым приложениями.
1758291403349.png


Хотя на этом рисунке показано несколько областей памяти, в данном модуле мы будем фокусироваться только на стеке.

2.1.1.1 Стек
Когда поток выполняется, он исполняет код либо из образа программы (Program Image), либо из различных динамических библиотек (Dynamic Link Libraries — DLL). Потоку требуется краткосрочная область данных для функций, локальных переменных и служебной информации управления программой — эта область называется стеком. Чтобы обеспечить независимое выполнение нескольких потоков, у каждого потока в запущенном приложении есть собственный стек.
Память стека «просматривается» (используется) процессором по принципу «последним пришёл — первым вышел» (Last-In, First-Out, LIFO). Это по сути означает, что при доступе к стеку элементы, помещённые («запушенные», pushed) на вершину стека, удаляются («выпопанные», popped) первыми. Архитектура x86 реализует специальные инструкции PUSH и POP для добавления и удаления данных из стека соответственно.


2.1.1.2 Конвенции вызова
Конвенции вызова (calling conventions) описывают, как функции получают свои параметры от вызывающего кода и как возвращают результат. Архитектура x86 допускает использование нескольких конвенций вызова. Их различие реализуется по нескольким факторам: как передаются параметры и возвращаемое значение (помещаются ли они в регистры процессора, записываются ли на стек, или используется комбинированный подход), в каком порядке они передаются, как подготавливается и очищается стек до и после вызова, и какие регистры процессора вызываемая функция обязана сохранить для вызывающего.
Как правило, компилятор определяет, какая конвенция вызова используется для всех функций в программе, однако в некоторых случаях программист может указать конкретную конвенцию для отдельной функции.


2.1.1.3 Механика возврата из функции
Когда код внутри потока вызывает функцию, он должен знать, по какому адресу вернуться после завершения функции. Этот «адрес возврата» (вместе с параметрами функции и локальными переменными) сохраняется в стеке. Этот набор данных относится к одному вызову функции и хранится в области стека, известной как кадр стека (stack frame). Пример кадра стека показан на рисунке 2.
1758291507475.png


Когда функция завершается, адрес возврата извлекается из стека и используется для восстановления потока выполнения в вызывающую функцию.
Хотя мы описали процесс на высоком уровне, далее нам нужно понять подробнее, как это достигается на уровне процессора. Для этого необходимо обсудить регистры CPU.


2.1.2 Регистры CPU
Для эффективного выполнения кода процессор поддерживает и использует набор из девяти 32-битных регистров (в 32-битной архитектуре). Регистры — это небольшие, чрезвычайно высокоскоростные ячейки памяти процессора, в которых данные можно эффективно читать или изменять. Эти девять регистров, включая наименования их старших и младших частей, показаны на рисунке 3.
1758291576695.png

Имена регистров были установлены ещё для 16-битных архитектур и затем расширены с появлением 32-битной платформы (x86), отсюда буква «E» в аббревиатурах регистров. Каждый регистр может содержать 32-битное значение (позволяющее значения от 0 до 0xFFFFFFFF) или содержать 16- и 8-битные значения в соответствующих субрегистрах, как показано на регистре EAX на рисунке 4.
1758291610815.png



2.1.2.1 Регистры общего назначения
Несколько регистров — такие как EAX, EBX, ECX, EDX, ESI и EDI — часто используются как регистры общего назначения для хранения временных данных. Обсуждение этого предмета гораздо глубже (см. различные онлайн-ресурсы), но для наших целей основные регистры описаны ниже:
• EAX (accumulator — аккумулятор): арифметические и логические инструкции
• EBX (base — базовый): базовый указатель для адресов памяти
• ECX (counter — счётчик): счётчик для циклов, сдвигов и ротаций
• EDX (data — данные): адресация портов ввода/вывода, умножение и деление
• ESI (source index — индекс источника): указатель на данные-источник при операциях копирования строк
• EDI (destination index — индекс назначения): указатель на данные-приёмник при операциях копирования строк


2.1.2.2 ESP — указатель стека
Как упоминалось ранее, стек используется для хранения данных, указателей и аргументов. Поскольку стек динамичен и постоянно меняется во время выполнения программы, указатель стека ESP «отслеживает» наиболее недавно использованное место в стеке (вершину стека), сохраняя адрес на него.
Указатель — это ссылка на адрес (или местоположение) в памяти. Когда мы говорим, что регистр «хранит указатель» или «указывает» на адрес, это по сути означает, что регистр содержит целевой адрес.

2.1.2.3 EBP — базовый указатель

Поскольку стек постоянно меняется во время выполнения потока, функции бывает трудно найти свой кадр стека, где хранятся аргументы, локальные переменные и адрес возврата. EBP, базовый указатель, решает эту проблему, сохраняя адрес вершины стека на момент вызова функции. Обращаясь к EBP, функция может легко получить доступ к данным из своего кадра стека (по смещениям) во время выполнения.


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


2.2 Введение в Windows Debugger
Теперь, когда мы понимаем базовые концепции архитектуры x86, пора познакомиться с отладочным инструментом, который мы будем использовать на протяжении всего курса.
В Windows доступно несколько отладочных программ. OllyDbg13 и Immunity Debugger14 хорошо известны в сообществе реверс-инжиниринга и разработки эксплойтов за их удобный интерфейс. Immunity Debugger изначально появился как форк OllyDbg, но с тех пор превзошёл функциональность OllyDbg.
Несмотря на удобство этих программ, в этом курсе мы будем использовать исключительно отладчик Microsoft WinDbg15. Это связано с тем, что WinDbg предоставляет те же возможности скриптинга, что и Immunity Debugger, а также выпускается в версиях для 32- и 64-битных платформ. Хотя существует open-source реализация OllyDbg для 64-бит, она не даёт того же набора функций и поддержки, что WinDbg.
WinDbg также является нашим предпочтительным отладчиком, потому что он умеет отлаживать как в пользовательском (user-mode), так и в ядровом (kernel-mode) режимах, что делает его наилучшим выбором для разработки любых эксплойтов под Windows. WinDbg поставляется в составе Software Development Kit (SDK), Windows Driver Kit (WDK) и набора Debugging Tools for Windows — бесплатно. WinDbg предустановлен в виртуальной машине курса.
Microsoft выпустила версию WinDbg под названием WinDbg Preview.16 Она имеет более интуитивный интерфейс и дополнительные возможности, такие как time-travel debugging17 и JavaScript API18 для поддержки скриптов. Однако WinDbg Preview работает только на Windows 10 Anniversary Edition (1607/RS1) и более новых версиях.
В этом курсе мы будем использовать стандартную версию WinDbg, чтобы освоить возможности, доступные во всех выпусках WinDbg, и избежать проблем совместимости.
С помощью WinDbg мы научимся ставить точки останова (breakpoints) для пошагового прохождения и управления потоком приложения. В качестве примера мы будем использовать Блокнот (Notepad) для создания, чтения и открытия текстового файла. Наконец, мы разберём команды, необходимые для отображения важной информации и манипуляции памятью внутри WinDbg.


2.2.1 Что такое отладчик?

Начнём с краткого освежения в памяти. Отладчик — это программа, которая вставляется между целевым приложением и CPU и, по сути, действует как посредник. Использование отладчика позволяет нам просматривать и взаимодействовать с памятью и потоком выполнения приложений. Пространство памяти в большинстве операционных систем, включая Windows, разделено на две части: режим ядра (kernel-mode, ring 0) и пользовательский режим (user-mode, ring 3). На протяжении всего курса мы будем работать с пользовательским режимом и избегать аспектов, связанных с режимом ядра.

CPU обрабатывает код на бинарном (машинном) уровне, что человеку трудно читать и понимать. Язык ассемблера вводит соответствие «один к одному» между бинарным содержимым и языком программирования.

Хотя язык ассемблера предполагается как человекочитаемый, это по-прежнему низкоуровневый язык, и его освоение требует времени. Опкод (opcode) — это бинарная последовательность, интерпретируемая процессором как конкретная инструкция. В отладчике это отображается в виде шестнадцатеричных значений вместе с их переводом в ассемблерный код.


2.2.2 Интерфейс WinDbg
Теперь, когда мы обсудили основы отладки, давайте посмотрим на WinDbg. После запуска перед нами открывается пустой экран, как показано на рисунке 5.
1758291891349.png


Давайте потренируемся с WinDbg (x86). Начнём с запуска Блокнота (Notepad) и WinDbg на нашей лабораторной виртуальной машине. Чтобы присоединить отладчик к notepad.exe, переключимся в окно WinDbg, откроем меню «Файл» и выберем «Attach to a Process…» (рисунок 6) или нажмём клавишу F6.
1758291963944.png

Откроется окно со списком всех процессов, к которым можно присоединиться, как показано на рисунке 7.
1758292043725.png

Список можно сортировать по-разному (если окно ещё не открыто). По умолчанию используется «System order» — процессы отсортированы от новых к старым. Также можно сортировать по идентификатору процесса (PID) или по имени исполняемого файла, что облегчает поиск нужного запущенного процесса.

Давайте найдём наш только что созданный процесс Notepad — по умолчанию он будет внизу списка. Выбрав процесс Notepad, нажмём OK — WinDbg присоединится к процессу. При присоединении WinDbg приостанавливает поток выполнения, давая нам возможность взаимодействовать с отладчиком.

Отладчик внедряет программную точку останова, перезаписывая текущую инструкцию в памяти инструкцией ассемблера INT 3. Хотя к теме breakpoints мы ещё вернёмся подробнее, важно заметить: если в приглашении командной строки не ввести g (Go), приложение останется suspended.


2.2.3 Понимание Workspace

Без какой-либо кастомизации рабочая область по умолчанию выглядит довольно лаконично. Когда приложение присоединено, WinDbg отображает одно плавающее окно команд (Command window), как показано на рисунке 8.
1758292213489.png

Если мы запускаем WinDbg с панели задач, заметим, что он загружается с преднастроенным макетом workspace. Этот макет был разработан так, чтобы показать наиболее важные окна для курса: Disassembly и Command. Не стесняйтесь изменять workspace по своему вкусу, но именно эти окна будут использоваться чаще всего в ходе курса.

В нашем кастомном workspace окно Command прикреплено (docked), и добавлен вид Disassembly, который показывает следующие инструкции, которые будет выполнять CPU.
1758292294991.png


При создании workspace важно знать, что через меню «View» (Вид) доступны несколько дополнительных окон (рисунок 10).
1758292437225.png


Мы можем расположить разные представления и закрепить их вместе, получив доступ к нескольким видам одновременно. Какие представления держать на экране — чаще вопрос личных предпочтений.
Графический интерфейс предоставляет множество возможностей. Но с практикой окно Command позволит нам взаимодействовать с WinDbg гораздо быстрее и использовать более продвинутые функции, такие как встроенный язык скриптов.

После настройки представлений мы можем сохранить рабочую область через меню «File». Это позволяет использовать один и тот же рабочий набор окон в разных сессиях отладки.

2.2.3.1 Упражнения
  1. Откройте WinDbg и присоедините его к процессу Notepad.
  2. Исследуйте разные окна WinDbg и познакомьтесь с расположением элементов интерфейса.

2.2.4 Debugging Symbols
Файлы символов позволяют WinDbg ссылаться на внутренние функции, структуры и глобальные переменные по именам, а не по адресам. Настройка пути к символам даёт WinDbg возможность загружать файлы символов для нативных исполняемых файлов и библиотек Windows из официального Microsoft Symbol Storre.

Файлы символов (с расширением .PDB) создаются при компиляции нативных файлов Windows компанией Microsoft. Microsoft не предоставляет файлы символов для всех библиотек, и сторонние приложения могут иметь собственные файлы символов.
Виртуальная машина студента уже настроена, но важно понимать, как настраивается среда отладки.
Параметры символов доступны через меню File > Symbol File Path… как показано на рисунке 11. Часто используемый путь для символов — C:\symbols.
1758292685093.png


После настройки пути к символам и при наличии подключения к Интернету мы можем принудительно загрузить доступные файлы символов для всех загруженных модулей перед началом отладки, выполнив команду .reload:
Код:
0:003> .reload /f
Reloading current modules
..*** ERROR: Symbol file could not be found. Defaulted to export symbols for 
C:\Windows\WinSxS\x86_microsoft.windows.common-
controls_6595b64144ccf1df_6.0.16299.64_none_14403bb93691f395\COMCTL32.dll - 
..
Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take
too long.
Run !sym noisy before .reload to track down problems loading symbols.
...............................................
************* Symbol Loading Error Summary **************
Module name                  Error
COMCTL32                      - This error is unknown: 0x800c2eff. Please contact the 
debugger team to report this error. (Mail: windbgfb@microsoft.com)

You can troubleshoot most symbol related issues by turning on symbol loading 
diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.

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


2.3 Доступ и манипуляции с памятью из WinDbg
Просмотр и изменение содержимого памяти процесса — критический шаг при разработке эксплойтов. WinDbg предоставляет набор мощных команд для этих целей. В этом разделе мы сосредоточимся на доступе к памяти и её изменении.


2.3.1 Дизассемблирование из памяти
Мы можем отобразить ассемблерный перевод указанного кода программы в памяти с помощью команды WinDbg u. Это полезно, поскольку позволяет нам просматривать ассемблерный код определённых Windows API, а также любую часть кода запущенной программы.

Команда uпринимает в качестве аргумента либо один адрес памяти, либо диапазон адресов, откуда начинать дизассемблирование. Если мы не укажем этот аргумент, дизассемблирование начнётся с адреса, хранящегося в регистре EIP.
С нашим отладчиком, присоединённым к процессу notepad.exe, давайте попробуем просмотреть ассемблерный код Windows API kernel32!GetCurrentThread. Выберем в меню Debug пункт Break, чтобы приостановить выполнение, а затем используем команду u для дизассемблирования целевой функции следующим образом:
1758293199561.png



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

2.3.1.1 Упражнения
  1. Используйте команду u, чтобы дизассемблировать Windows API kernel32!GetCurrentThread.
  2. Можете ли вы объяснить ассемблерный код? Каков результат этой функции и как он возвращается вызывающему коду?

2.3.2 Чтение из памяти
Мы можем читать содержимое памяти процесса с помощью команды отображения, за которой следует индикатор размера. Ниже приведены примеры различных доступных форматов вывода. Для полного списка опций команд отображения обратитесь к онлайн-руководству WinDbg от Microsoft.

Сначала мы можем вывести байты с помощью команды db, как показано тут.
1758293413840.png


В приведённом выше примере используется регистр ESP вместо явного адреса в памяти. Команда отображения также принимает явные адреса (db 00faf974) или имена символов (db kernel32!WriteFile).
Чтобы вывести данные в более крупном размере, можно использовать dw:
1758293488732.png


Как видно выше, dw печатает WORD (2 байта), а не одиночные байты.
Также можно вывести DWORD (4 байта) с помощью dd:
1758293581130.png

Мы можем вывести QWORD (восемь байт) с помощью dq, как показано в этом примере:

Обратите внимание, что в этом примере мы заменили регистр ESP на его шестнадцатеричное значение. Кроме того, удобно отображать ASCII-символы в памяти вместе с WORD или DWORD с помощью dWи dcсоответственно. Не путайте dWс dw— последний служит только для вывода значений типа WORD.

В Листинге 7 справа показаны ASCII-символы вывода, а слева — их эквивалентные шестнадцатеричные значения.
1758295242094.png


По умолчанию длина при отображении данных составляет 0x80байт. Мы можем изменить это значение, используя параметр L в командах отображения, как показано ниже:
1758295292352.png

1758295309007.png


Обратите внимание, как значение, переданное параметру [B]L[/B], меняет объём отображаемых данных. Это зависит от выбранного формата вывода. Команда dW L2 выведет два WORD, тогда как db L2 выведет два байта.

WinDbg позволяет отображать содержимое памяти по указанному адресу в ASCII с помощью команды daили в Unicode с помощью команды du. Мы увидим примеры применения этих команд далее в модуле.

Также можно показать данные через команду «pointer to data» — poi, которая отображает данные, на которые ссылается указатель по адресу в памяти. В приведённом ниже листинге команда [B]dd[/B](display DWORD) используется дважды, чтобы эмулировать разыменование памяти.
1758295631136.png


Мы можем получить тот же результат, используя ddи poiвместе в одной строке:
1758295709317.png


Эти команды отображения очень полезны. В следующем разделе мы покажем ещё более мощные способы отображения данных.


2.3.2.1 Упражнение
  1. Используйте разные варианты [B]dd [/B]для дампа данных из памяти и попробуйте комбинировать команды отображения с poi.

Мы продолжим в следущей статье з дампингом структур из памяти, оставайтесь на связи :)
 


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