Часть 61

Мы рассмотрим другой случай в уязвимом драйвере. Теперь поговорим о INTEGER OVERFLOW.

Многие спрашивают меня, почему бы не проанализировать его непосредственно в C или C++. Проблема состоит в том, что некоторые из них уже сделаны на C и C++. Методы те же самые. Поэтому перенос их на PYTHON не только приносит что-то новое, но и заставляет нас практиковаться в PYTHON и библиотеке CTYPES, что является для нас важным.

Для тех, кто хочет увидеть методы, исходный код доступен здесь:

https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/tree/master/Exploit

И здесь он уже откомпилирован. Если вы хотите попробовать эксплоит, вы можете отлаживать его и сравнивать полученный результат в PYTHON с оригинальным результатом, что очень помогает.

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

Здесь у нас есть блок, который перемещает нас в IOCTL, где вызывается INTEGER OVERFLOW.

Давайте посмотрим, что IOCTL действительно прибывает сюда, в самое начало.

Регистр EAX у нас равен 0x0022201F.

И чтобы идти по правильному пути, я изменяю регистр EDX, содержащий наш IOCTL код. Он должен быть больше, чем регистр EAX.

Затем программа передает наше значение в регистр EAX и вычитает значение 0x00222023, и если оно не равен нулю, программа вычитает еще 4, что остаётся в регистре ECX. Затем идут инструкции PUSH 4 - POP ECX. Если результат равен нулю, программа перейдёт к правильному блоку.

IOCTL – 0x222023 – 0x4 = 0
IOCTL = 0x222023 + 0x4
Python>hex(0x222023 + 4)
0x222027

Этот IOCTL будет тем, который перенесет нас в блок, где срабатывает INTEGER OVERFLOW. Давайте проанализируем его.

Мы видим, что, как и в предыдущем случае, программа передает в функцию два аргумента: первый это адрес структуры IRP, а второй - _IO_STACK_LOCATION.

Поскольку мы уже импортировали структуру _IO_STACK_LOCATION, здесь программа перемещает её начальный адрес и начинается работа с ее смещениями. Поле 0x10. Давайте посмотрим, что это. Я нажимаю T и выбирает соответствующую структуру.

Мы видим, что

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

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

Здесь мы видим эти два аргумента. Программа также устанавливает переменную STATUS в нуль. А в стеке есть буфер с именем KERNELBUFFER. Давайте посмотрим его длину.

Длина равна 512 * 4, так как каждый компонент является DWORD (DD), поэтому общая длина будет равна.

Python>hex(512 * 4)
0x800

Программа инициализируйте в нуль этот буфер, сначала записав здесь первые 4 байта регистра EDI, который равен нулю. А затем вызывает функцию MEMSET из оставшихся 0x7FC байтов, добавив 4к месту назначения в инструкции LEA, чтобы программа записывала данные начиная на 4 байтов больше.

Здесь также есть структура. Мы увидим, для чего она нужна. IDA обнаружил её.

Посмотрим, что она делает. Здесь сказано следующее.

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

Затем печатается 4 значения: размер, который мы передали из пользовательского буфера, указатель на пользовательский буфер, адрес KERNELBUFFER и его размер.

Мы видим, что IDA говорит нам, что есть конструкция TRY - EXCEPT или другими словами, в этом блоке есть исключение. Программа переходит на нижний блок. Поэтому из верхнего блока есть три стрелки. Две нормальные для сравнения и другая для TRY – EXCEPT.

Мы видим, что блок принимает размер, который передается в регистре EBX и сравнивает его с константой 0x800, которая находится в регистре ESI.

Но прежде, в мой размер добавляет значение 4. И если он будет ниже, все будет нормально.

Мы уже видим проблему. Eсли мы передадим в качестве размера, например число 0XFFFFFFFF, при добавлении 4 произойдет INTEGER OVERFLOW, и результат будет таким.

Python>hex((0xffffffff + 4))
0x100000003L

Если мы сократим его до 32 бит как делает процессор, то получим

Python>hex((0xffffffff + 4) & 0xffffffff)
0x3L

Это дает нам в результате 3. И это меньше, чем 0x800, даже при сравнении без знака.

Затем программа берет исходный размер и делает с ним SHR или другими словами делит его на 4 с учетом знака. Это делается потому, что копируется DWORDS, а индекс идет один за другим. Поэтому размер - это общее число, разделенное на 4.

Если бы наш размер был равен 0XFFFFFFFF, то разделив его на 4, это дало бы нам значение 0x3FFFFFFF.

Мы видим, что это цикл, где регистр EDI является счетчиком.

Условие выхода заключается в том, чтобы регистр EDI не был меньше, другими словами, чтоб он был больше или равен значению выхода. Если цикл начинается с нуля и увеличивается на один каждый раз, это будет давать много возвратов цикла, пока значение не достигнет 0x3FFFFFFF.

Мы видим, что программа имеет еще одно очень удобное условие для выхода.

Если программа прочитает буфер пользователя, в тот, что мы отправляем значение 0x0BAD0B0B0, она выйдет из цикла.

Наконец, она скопируйте в буфер ядра. Программа развернет выполнение с помощью регистра EDI, который является счетчиком умноженным на 4, то есть она скопирует 4 * 4 байтов.

Затем она складывает значение 4 с адресом буфера пользовательского режима, увеличивает регистр EDI, сохраняет его в переменную COUNT и готово. Это всё, что нужно. Поэтому мы можем спровоцировать переполнение стека, контролируя его большим размером. Мы даже можем выйти из него до того, как программа разрушит весь стек, так как это дает нам возможность выйти из цикла, обработанного нами.

С этими данными мы можем перезаписывать адрес возврата без проблем.

Перед тем, как сделать это в PYTHON, я запускаю исполняемый файл эксплойта, чтобы проверить, что он reversee, и при анализе мы используем IOCTL код равный 0x222027.

Затем программа достигает блока.

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

Здесь мы видим его содержимое.

Если я создам сегмент, я уже могу сгруппировать буквы A вместе, нажав A.

И я вижу наш DWORD где-то здесь.

Я добираюсь до функции, которая вызывает INTEGER OVERFLOW.

Мы видим, что размер который мы передаём равен 0xFFFFFFFF.

Регистр EAX = 3, что меньше, чем 0x800.

После инструкции SHR.

Программа читает содержимое буфера пользовательского режима и оно равно 0x41414141.

Поскольку это не выходная константа равная 0x0BAD0B0B0, программа копирует ее в буфер ядра стека.

Я устанавливаю BP здесь для того, чтобы программа остановилась, когда закончиться копирование.

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

Как только я создаю сегмент, я создаю код клавишей C и создаю функцию с помощью CREATE FUNCTION, и теперь это смотрится лучше.

Здесь мы видим калькулятор с правами SYSTEM.

Теперь идея сделать то же самое, но только в PYTHON.

Я вижу, что буфер состоит из 2088 десятичных байт, когда элементом является байтом. Если это DWORD, то он должен быть умножен на 4

Python>hex(2088)
0x828

Другими словами, мой буфер будет равен 0x828 + адрес для перезаписи адреса возврата.

Я вижу, что адаптация скрипта переполнения стека работает.

Я меняю IOCTL; Я установил размер пользовательского буфера равным -1. Я передаю указатель на пользовательский буфер чтобы перезаписать адрес возврата. В эксплойте на C я создал два буфера: один для перезаписи адреса возврата и другой с шеллкодом, в который я поместил всё в одном.

Data = shellcode + ((0x828 -len(shellcode)) * "A") + struct.pack(">L", int(buf)) + struct.pack(">L", 0x0BAD0B0B0)

Это шеллкод. Затем он заполняется 0x828 байтами минус длина шелл-кода умноженного на "A". Затем идет указатель на тот же буфер, который используется для перезаписи по адресу возврата, и выходной DWORD равный 0x0BAD0B0B0.

Мы видим, что в этом случае не было больших трудностей, так как метод аналогичен методу переполнения стека.Принимая во внимание, что если бы у нас не было DWORD для выхода, то всё бы усложнилось, так что спасибо программисту.

Автор оригинального текста — Рикардо Нарваха.

Перевод и адаптация на русский язык — Яша Яшечкин.

Перевод специально для форума системного и низкоуровневого программирования - WASM.IN

21.12.2018

Источник:

http://ricardonarvaja.info/WEB/INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO/61-INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO%20PARTE%2061.7z