Хорошо. Мы будем пытаться эксплуатировать переполнение стека из скрипта PYTHON. Обычно эксплуатация ядра происходит локально. Другими словами, скрипт уже эксплуатирует некоторую программу, которая не имеет привилегий системы и хочет повысить привилегии и быть с правами SYSTEM, что устраняет ограничения, которые имеет эксплуатируемый процесс, который имеет только ограниченные привилегии обычного пользователя на машине.
Поэтому, за исключением очень редких исключений, эксплойты ядра - это эксплоиты для повышения привилегий или PRIVILEDGE ESCALATION.
Поэтому, много раз мы будм видеть их код в скомпилированном исполняемом файле или их исходный код, потому что предполагается, что мы должны загрузить файл и запустить его с правами обычного пользователя. Этот исполняемый файл будет атаковать в этом случае наш драйвер и проэксплуатириует его достигнув эскалации.
В любом случае, как код в C, так и код на PYTHON основаны на вызовах одних и тех же WINDOWS API, таких, как CREATEFILE, DEVICEIOCONTROL и т.д. Поэтому, то, что делается на одном языке, легко переносится на другой.
Давайте напомним нашу модель PYTHON предыдущего примера.
![]()
Первым шагом будет изменение имени для драйвера поэтому, когда вы делаете CREATEFILE, нам возвратится его правильный дескриптор.
Напомним, что имя генерируется здесь
![]()
![]()
Здесь драйвер возвращает SYMBOLICNAME, который приходит из строки, которая находится в DEVICENAME.
![]()
Без особых проблем, конечно, если я заменяю имя старого драйвера драйвером новым.
![]()
Мы можем скопировать его на целевую машину и посмотреть, не даст ли он мне ошибку или даст действительный дескриптор, но я буду продолжать смотреть.
Этот драйвер запущен, и я запускаю скрипт.
![]()
Поскольку SYMBOLICLINK идет отсюда, имеет смысл использовать это имя.
![]()
Давайте попробуем так.
![]()
Мы видим, что всё работает.
![]()
Мне вернулось положительное значение, которое является дескриптором драйвера. Остальное меня не интересует, поэтому я закрываю его.
![]()
На данный момент, мы удаляем WHILE и все остальное, что нас не интересует. Следующая вещь заключается в том, чтобы увидеть, какой именно IOCTL переносит нас в блок переполнение стека, чтобы отправить его.
Вопрос состоит в том, как добраться туда.
![]()
Мы видим, что драйвер идёт отсюда.
![]()
Регистр EDX имеет код IOCTL, который получается отсюда.
![]()
Программа сделает вычитание из регистра значения 0x222003, и если результатом будет нуль, мы перейдем к той части, которая нам нужна.
Давайте посмотрим, достигнет ли программа блока, где мы хотим присоединить IDA и устанавливаем BP.
![]()
Я запускаю драйвер на целевой машине и попадаю в BP.
![]()
Мы видим, что в регистр EDX драйвер считывает IOCTL код, который он передает.
![]()
![]()
Программа помещает результат в регистр EAX, вычитает из него значение 0x222003 и поскольку результатом будет нуль, драйвер переходит в уязвимый блок.
![]()
![]()
Программа читает длину, которая является нулем, так как я не передал ей аргументы за исключением IOCTL.
![]()
Поскольку аргумент равен нулю, программа пропускает функцию, в которой происходит переполнение стека.
![]()
Поскольку API функция, вызывается из WIN32FILE в PYTHON, она имеет меньше аргументов, чем исходная функция, которая имеет больше.
![]()
![]()
Здесь находится определение WIN32FILE PYTHON.
![]()
Мы увидим, что если мы достигли этого места, нам нужно сделать буфер, чтобы передать его на вход, так как поскольку нам нужен указатель на него, мы можем сделать это с помощью функции ALLOCATEREADBUFFER, которая выделяет нам в куче количество памяти, которое нам нужно передать входному буферу. (Другая опция WIN32FILE не дает нам).
![]()
Поскольку библиотека WIN32FILE не имеет доступа ко всем API, она не позволяет копировать непосредственно в буфер как функция MEMCPY или что-то вроде этого, поэтому мы можем копировать его только с помощью функции READFILE, поэтому мы делаем файл PEPE.BIN, заполняя его 0x1000 буквами A. Мы передаем файл в функцию CREATEFILE, чтобы она открывала его и вернула нам дескриптор, и мы передали его в функцию READFILE с аргументом BUF буфера, который мы выделяем и копировать содержимое файла туда.
Очевидно, что если мы сделаем это на C, C++ или языке, в котором мы можем использовать функцию VIRTUALALLOC и легко копировать с помощью функции MEMCPY или той которую мы хотим. Но здесь у нас есть определенные ограничения, и мы должны приспосабливаться к этому.
Нам нужно дополнительно знать, какую длину мы должны отправить. Давайте посмотрим на буфер назначения в IDA.
![]()
Мы замечаем, что с начала буфера до адреса возврата получается 520 десятичных байт * 4 размер элемента.
Поэтому
hex(520*4) = '0x820'
Поэтому мы должны отправить 0x820 + RET.
Сейчас, поскольку мы знаем в PYTHON адрес буфера, который мы создаем, чтобы передавать его, мы будем использовать грязный трюк - REPR.
![]()
Мы видим, что функция возвращает адрес, в который я могу писать. В одной строке. Поэтому я ищу сначала 0x, затем ищу запятую в этой строке, и я могу разбить адрес.
![]()
![]()
Здесь cуществует адрес, который мы можем передать с помощью STRUCT.PACK после 0x820 A. Досадно, что мы должны записать их в файл, который читается.
Следовательно, как только у меня есть адрес буфер.
![]()
Я пишу в файл ФРУКТЫ, которые хочу отправить.
![]()
Перед функцией READFILE скопируем их в буфер.
Давайте посмотрим, заработает ли это. В этом случае нет необходимости иметь файл, потому что он будет создан и заполнен, поэтому мы удалили предыдущий.
![]()
Для этого мы изменили аргумент на CREATE ALWAYS.
![]()
Хорошо. Предположительно здесь - наш буфер с буквами A. IDA остановлена. Давайте посмотрим, что я передаю.
![]()
Мы видим, что входной буфер, который передается в регистр EDX, показывает нам то же самое значение. Давайте посмотрим, есть ли там A.
=======================================================================
![]()
=======================================================================
![]()
=======================================================================
Он не заполнился буквами A. Давайте пропустим эксплуатацию и давайте исправим скрипт.
![]()
Я меняю EIP здесь с помощью правой кнопки и SET IP и нажимаю RUN.
Я думаю, что проблема в том, что размер буфера должен соответствовать размеру файла.
![]()
Давайте сделаем так.
Ах, уже упал.
![]()
Скрипту не хватало установки указателя файла в начале файла. Он читал файл с конца, где я оставил WRITEFILE, поэтому я не читал A. Теперь всё работает.
![]()
Я трассирую до MEMCPY.
![]()
Я добираюсь до RET.
![]()
Я продолжаю с помощью F7.
![]()
Я вижу, что я попал в буфер. Проблема состоит в том, что, поскольку я не выделил его с помощью VIRTUALALLOC, а выделяю его с помощью API ALLOCATEREADBUFFER, WIN32FILE у него нет прав на выполнение. Поэтому мне придется найти способ выделить исполняемый код другим способом.
![]()
Я добавил библиотеку CTYPES, и у неё есть функция VIRTUALPROTECT, поэтому я дал ей права на выполнение, и теперь у нас нет проблем.
![]()
Программа переходит и выполняет без проблем. Я действительно вижу, что CTYPES более продвинутая, чем WIN32API, поэтому вы можете делать все, вызывает прямо функцию VIRTUALALLOC непосредственно из CTYPES.
Версия c CTYPES – это так
![]()
Библиотека использует напрямую функции CREATEFILE, VIRTUALALLOC, RTLMOVENEMORY и DEVICEIOCONTROL. Вам просто нужно быть осторожным с одним из типов, но она работает хорошо.
![]()
Здесь всё работает. Вопрос сейчас состоит в том, что мы выполняем код. Нам осталось бы делать шеллкод, потому что так у нас будет только красивый синий экран.
Мы проанализируем шеллкод и создадим его в следующей части.
=======================================================
Автор текста: Рикардо Нарваха - Ricardo Narvaja (@ricnar456)
Перевод на русский с испанского: Яша_Добрый_Хакер(Ростовский фанат Нарвахи).
Перевод специально для форума системного и низкоуровневого программирования — WASM.IN
03.11.2018
Версия 1.0
Введение в реверсинг с нуля, используя IDA PRO. Часть 57
Дата публикации 3 ноя 2018
| Редактировалось 3 ноя 2018