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

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

tenac1ous

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

2.3.3 Дамп структур из памяти

Одним из преимуществ WinDbg является возможность использовать символы Microsoft для ключевых модулей (DLL).
Файлы символов содержат структуры. Структуры — это программная концепция, позволяющая создавать пользовательские типы данных, которые могут комбинировать разные элементы данных.

Различное ПО может активно использовать структуры — в зависимости от его сложности и функционала.
Если бы у нас был исходный код, такие структуры легко читались бы напрямую.
Однако проблема возникает на этапе компиляции, когда код преобразуется в двоичные значения.
Это означает, что мы не можем просто «прочитать» структуры напрямую.

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

Команда Display Type (dt) принимает имя структуры, которую нужно отобразить, и (опционально) адрес памяти, начиная с которого нужно вывести содержимое структуры.
Структура должна присутствовать в одном из загруженных файлов символов.

На примере ниже отображается структура Thread Environment Block (TEB) без дополнительных аргументов.
1758556170287.png


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

В случаях, когда поле указывает на вложенную структуру, тип данных этого поля заменяется на соответствующий тип подструктуры.
Тип подструктуры также можно распознать по тому, что он начинается с символа подчёркивания (_), а его имя указано ЗАГЛАВНЫМИ буквами.

В Изображении 11 обратите внимание, что поле NtTib по смещению 0x0 является вложенной структурой типа _NT_TIB.
Если адрес структуры в памяти известен, его можно передать команде dtв качестве дополнительного параметра.

Продолжая предыдущий пример, мы можем использовать dtс адресом TEB, воспользовавшись псевдорегистром $teb.

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

Указав флаг -r для команды dt, WinDbg рекурсивно отобразит вложенные структуры, если они присутствуют.
1758556342577.png

1758556377878.png

Мы также можем отобразить конкретные поля внутри структуры, передав имя поля как дополнительный параметр.
Ниже приведён пример для поля ThreadLocalStoragePointerв структуре TEB:
1758556473107.png


WinDbg также может отобразить размер структуры, извлечённой из файла символов.
Это важно, так как некоторые API Windows принимают структуру в качестве аргумента, и нам нужно знать точный размер этой структуры.
Для получения этой информации мы используем команду WinDbg sizeof следующим образом:
1758556515178.png

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


2.3.3.1 Упражнение

  1. Поэкспериментируйте с командой dtи сделайте дамп некоторых структур, таких как PEB, вместе с их содержимым по конкретному адресу памяти.

2.3.4 Запись в память


После того как мы изучили команды для отображения и дампа информации из памяти, перейдём к изменению данных в памяти процесса.
Главная команда WinDbg для этой задачи — [B]e\*[/B]

Модификаторы размера, используемые для команды display, также применимы к команде edit.
Ниже приведён пример, показывающий, как изменить значение DWORD, на которое указывает регистр ESP:
1758556687426.png



Как и в случае с командой display, мы можем напрямую записывать или изменять ASCII-символы с помощью команды ea,
или Unicode-символы с помощью команды eu.

В следующем примере мы записали ASCII-текст "Hello" по адресу памяти, на который указывает регистр ESP:
1758556751493.png

Запись ASCII- или Unicode-строк прямо в память позволяет быстро получить их шестнадцатеричное представление, которое можно затем использовать в эксплойте.
Например, представим, что у нас есть шеллкод, который записывает файл на диск. Путь к файлу должен быть передан в виде байтов внутри самого шеллкода. С помощью WinDbg мы можем записать этот путь прямо в память. Затем в коде эксплойта мы копируем байты, отображённые командой db, в сам шеллкод.

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

  1. Попробуйте команды записи в память и измените содержимое по адресу ESP как в виде бинарного значения, так и в виде Unicode-строки.
  2. Что произойдёт, если попытаться изменить память по адресу, на который указывает EIP?

2.3.5 Поиск по пространству памяти

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

Сначала воспользуемся недавно приобретённым навыком редактирования памяти и изменим DWORD, на который указывает регистр ESP, на 0x41414141. Затем с помощью команды поиска найдём это значение в памяти приложения.

Для выполнения поиска нам нужна команда sи четыре дополнительных параметра:
  • тип памяти, в которой ищем;
  • адрес начала поиска;
  • длина области для поиска;
  • шаблон для поиска.
При поиске значения типа DWORD мы используем ключ -d. Далее задаём адрес начала поиска — в примере он равен 0. Затем указываем длину области для поиска. Чтобы просканировать всё пространство памяти процесса, вводят Lи значение ?80000000, что обозначает весь диапазон памяти процесса (о ключевом слове ? будет сказано подробнее далее). Наконец, указываем искомый шаблон — в нашем случае 41414141.

Вот наглядный пример поиска 41414141 в памяти:
1758556979809.png

Имея хорошее представление о функции поиска, давайте выполним поиск по всему пространству пользовательской памяти процесса для хорошо известной ASCII-строки.

1758557019378.png


Строка ASCII, которую мы использовали в команде поиска в предыдущем примере, присутствует в каждом исполняемом файле Windows как часть заголовка PE. Это означает, что она встречается в каждом модуле, загруженном в адресное пространство процесса notepad.exe. Помимо поиска самой ASCII-строки, WinDbg также показывает адреса памяти, по которым эти строки расположены в каждом модуле.

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

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

  1. С помощью команды editсоздайте в памяти QWORD и найдите его командой s.
  2. С помощью команды editсоздайте Unicode-строку и найдите её командой s.

2.3.6 Просмотр и редактирование регистров CPU в WinDbg


Понимание того, как просматривать значения регистров CPU, так же важно, как и умение получать доступ к данным в памяти. Мы можем обращаться к регистраторам с помощью команды r.
Эта команда очень мощная, потому что позволяет не только отображать значения регистров, но и изменять их.

В следущем примере показано, как вывести все регистры целиком, а также отдельный регистр, используя команду r:
1758557248260.png

1758557269210.png


Мы также можем изменить регистр ECX командой r ecx= с последующим новым значением регистра.
1758557328381.png

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

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

  1. Используйте команду r, чтобы вывести содержимое всех регистров, затем поэкспериментируйте с отдельными регистрами. Практикуйтесь в их модификациях.

2.4 Управление выполнением программы в WinDbg​


WinDbg умеет устанавливать breakpoints, чтобы приостанавливать поток выполнения в нужных местах кода. Существуют два типа: программные и аппаратные (процессорные).
breakpoints, которые контролирует сам отладчик, называются программными (software breakpoints). А те, которые контролирует процессор и которые устанавливаются через отладчик, называются аппаратными (hardware/processor breakpoints).

В следующем разделе мы поэкспериментируем с установкой разных программных и аппаратных breakpoints при подключении к процессу notepad.exe. Мы научимся ставить программные breakpoints на конкретные Windows-API, некоторые из которых ещё не загружены в адресное пространство нашего приложения. Также мы воспользуемся аппаратными breakpoints, чтобы точно определить момент доступа к нашим данным.

2.4.1 Software Breakpoints


При установке software breakpoint WinDbg временно заменяет первый опкод инструкции, на которой мы хотим остановить выполнение, на ассемблерную инструкцию INT 3.
Главное преимущество software breakpoints — мы можем установить их сколько угодно.

Давайте попробуем. Подключив WinDbg к процессу Notepad, мы установим breakpoint, который остановит выполнение приложения в момент, когда происходят изменения при сохранении файла. Для этого мы установим breakpoint на Windows API WriteFile, который обычно используется для записи данных в указанный файл или устройство ввода/вывода (I/O).

Начнём с использования команды bp вместе с адресом, где мы хотим остановить выполнение приложения — в нашем случае это функция kernel32!WriteFile.

После установки breakpoint мы можем использовать команду bl, чтобы вывести список всех breakpoints и убедиться, что всё готово для теста.
1758557658827.png

Помните, что при первом подключении к WinDbg выполнение приложения уже остановлено.
После того как мы установили нужные breakpoints, необходимо продолжить выполнение, введя команду g.

С установленным breakpoint давайте напишем какой-нибудь текст в Notepad, а затем сохраним файл.
Это должно сработать как триггер и остановить выполнение на нашем breakpoint.
1758557711980.png


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

Во время сессии отладки также бывает полезно временно отключать и включать breakpoints с помощью команд bd(disable) и be(enable) соответственно.
Эти команды принимают номера breakpoints, которые можно посмотреть в списке, выводимом командой bl.
1758557764863.png

В предыдущем примере мы отключили и снова включили breakpoint 0 на API WriteFile.

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

Сделать это можно с помощью команды bcи указания номера breakpoint.
Чтобы удалить все breakpoints, можно использовать символ * вместо номера.
1758557837214.png

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

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

  1. Подключите WinDbg к новому процессу Notepad и установите breakpoint на API WriteFile, как показано.
  2. Тригерните breakpoint, сохранив документ в Notepad.
  3. Попрактикуйтесь с командами для breakpoints: просмотреть (bl), отключить (bd), включить (be) и удалить (bc).
  4. Сможете ли вы определить, где нужно поставить breakpoint, который будет срабатывать при чтении текстового файла?

2.4.2 Unresolved Function Breakpoint

Мы можем использовать команду buдля установки breakpoint на неразрешённую (unresolved) функцию. Это функция, которая находится в модуле, ещё не загруженном в адресное пространство процесса. В этом случае breakpoint будет активирован (enabled) в момент, когда модуль загрузится и целевая функция будет разрешена.

Обращайтесь к MSDN, чтобы узнать подробнее о разнице между resolved и unresolved breakpoints.

Ниже приведён пример применения команды buк Notepad. Модуль OLE32.dll первоначально не загружен в процессе notepad.exe, но загружается после сохранения файла. После подключения WinDbg к notepad.exe мы установим неразрешённый breakpoint на произвольную функцию из OLE32 — OLE32!WriteStringStream.
1758558084859.png

1758558098450.png


После возобновления выполнения Notepad и сохранения файла мы замечаем, что модуль OLE32.dll был загружен:
1758558147812.png

Наблюдая загрузку модуля в предыдущем скриншоте, мы можем подтвердить, что наш breakpoint теперь разрешён, прервав выполнение приложения.
Сделаем это, нажав кнопку Break (остановка выполнения) в интерфейсе, как показано в следующем примере:
1758558207267.png


Наконец, мы можем проверить наш breakpoint с помощью команды bl:
1758558251953.png


Важно помнить, что хотя наш breakpoint был разрешён после загрузки ole32.dll, он не сработал.
Это произошло потому, что наши действия в Notepad не вызвали функцию ole32!WriteStringStream.

Далее мы узнаем, как использовать возможности WinDbg и breakpoints, чтобы провоцировать выполнение конкретных действий.

2.4.2.1 Упражнения

  1. Подключите WinDbg к новому процессу Notepad и установите unresolved breakpoint на любую API-функцию в ole32.dll.
  2. Вызовите загрузку ole32.dll, сохранив файл в Notepad, и посмотрите, как ваш breakpoint становится resolved.

2.4.3 Breakpoint-Based Actions

Мы также можем автоматизировать выполнение команд внутри отладчика, когда срабатывает breakpoint.
Это позволяет, например, выводить значения регистров, разыменовывать адреса памяти и выполнять другие полезные действия в момент срабатывания breakpoint.

Рассмотрим пример где WinDbg подключен к Notepad, и мы хотим отображать количество байт, записанных в файл, прямо в окне отладчика.
Для этого мы можем выполнять команду.printf каждый раз, когда срабатывает breakpoint, установленный на API kernel32!WriteFile, используя следующий синтаксис:
1758558404882.png

Подобно версии в C/C++, команда .printf поддерживает использование строк формата (format strings), таких как %p, которая отображает переданное значение как указатель.

В нашем примере команда.echo выводит результат работы .printf в окно команд WinDbg.
Символ-разделитель ; используется для того, чтобы объединить несколько команд, назначенных одному breakpoint, и выполнить их по порядку.

В нашем случае мы выбрали вывод значения, на которое указывает регистр ESP со смещением 0x0C (12 байт), что соответствует третьему аргументу функцииkernel32!WriteFile — количеству байт, которые нужно записать в целевой файл.
Это определяется прототипом функции WriteFile:
1758558492157.png


Это связано с тем, что Windows x86 API использует соглашение о вызовах __stdcall, при котором аргументы функции помещаются в стек в обратном порядке (справа налево).
В данном случае каждый аргумент занимает четыре байта памяти в стеке.

После того как мы установили наш breakpoint, мы продолжаем выполнение приложения с помощью команды g, затем вводим строку в Notepad и сохраняем её в файл.
После этого WinDbg должен вывести длину сохранённой строки, как показано ниже:
1758558568428.png

Ещё одной мощной возможностью WinDbg является установка conditional breakpoints.
Как следует из названия, такие breakpoints останавливают выполнение программы только если выполняется определённое условие.

В следующем примере мы снова используем Windows API kernel32!WriteFile, но теперь создадим conditional breakpoint.
Мы хотим, чтобы выполнение останавливалось только в том случае, если Notepad записывает ровно 4 байта данных в файл.

Мы можем реализовать это с помощью команд .if и .else, используя следующий синтаксис:
1758558651312.png

Когда наш breakpoint на WriteFile срабатывает, мы используем команду gc(go from conditional breakpoint), чтобы продолжить выполнение — если третий аргумент функции (nNumberOfBytesToWrite, расположенный на стеке) не равен 4.

Если же значение действительно равно 4, то в окно команд WinDbg выводится строка:
1758558892279.png


Мы также можем комбинировать conditional breakpoints с командой .printf.
Это может быть полезно, например, если отлаживаемый код выполняет цикл, и мы хотим отслеживать содержимое определённой области памяти или значение регистра CPU на каждой итерации без остановки выполнения программы.

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

Далее мы рассмотрим hardware breakpoints и узнаем, какие преимущества они имеют по сравнению с software breakpoints.

2.4.3.1 Упражнения

  1. Поэкспериментируйте с командой .printf, чтобы выводить содержимое памяти при срабатывании breakpoint.
  2. Попробуйте команды .if и .else, чтобы останавливать выполнение только при определённом количестве записываемых байт.
  3. При остановке на функцииkernel32!WriteFile попробуйте определить, хранится ли количество записанных байт также в каком-либо регистре.
    Если да — измените conditional breakpoint, чтобы проверять этот регистр, а не аргумент на стеке.

2.4.5 Stepping Through the Code

После остановки выполнения приложения мы можем использовать команды p и t для пошагового выполнения инструкций — с переходом через или внутрь вызова функции соответственно.
  • Команда p выполняет по одной инструкции за раз и перепрыгивает через вызовы функций (step over).
  • Команда t делает то же самое, но проваливается внутрь вызываемой функции (step into).

Давайте перезапустим Notepad и снова подключим к нему WinDbg. После подключения установим software breakpoint на API kernelbase!CreateFileW и продолжим выполнение (g).

С установленным breakpoint напишем произвольную строку в приложении и попытаемся сохранить файл — это должно сработать как триггер.
Чтобы продемонстрировать упомянутые команды, установим ещё один breakpoint на kernelbase!CreateFileW+0x53 и снова продолжим выполнение.

Листинг 39 демонстрирует использование команд p и tдля пошагового выполнения инструкций.
Когда выполнение доходит до инструкции call, команда step into (t) «заходит» в вызываемую функцию и продолжает отладку с её первой инструкции.
1758559609767.png

1758559627300.png


Мы можем использовать команды step into и step over взаимозаменяемо, за исключением случаев, когда встречается инструкция call.
В Листинге 40 выполняется тот же участок кода, но на этот раз мы перешагиваем через вызов (step over), а не заходим внутрь.
1758559668426.png

Здесь мы видим, что с командой p выполнение продолжается по коду приложения, не заходя во вложенные функции.

Ещё одна удобная команда — pt (step to next return), которая позволяет «перемотать» выполнение вперёд до конца текущей функции, то есть до инструкции ret.
1758559736266.png


Пример выше показывает, что выполнение продолжается до первой инструкции ret, которая, как правило, находится в конце текущей функции.

Подобно команде pt, команда ph выполняет код до тех пор, пока не встретится инструкция ветвления.
К ним относятся как условные, так и безусловные переходы, вызовы функций и инструкции возврата (ret).

Мы рассмотрели основные инструменты, которые помогают выполнять пошаговую отладку кода приложения.
В следущей секции мы углубимся в дополнительные возможности WinDbg.

2.4.5.1 Упражнения

  1. Запустите WinDbg и подключите его к новому процессу Notepad.
  2. Установите breakpoint наkernel32!WriteFile и вызовите его, сохранив файл.
  3. Пройдите по инструкциям с помощью команд p, t, ph и pt.
    Убедитесь, что понимаете разницу между ними.
 

Вложения

  • 1758557262558.png
    1758557262558.png
    16.5 КБ · Просмотры: 5


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