Введение в реверсинг с нуля, используя IDA PRO. Часть 61.

Дата публикации 17 дек 2018 | Редактировалось 21 дек 2018
Мы рассмотрим другой случай в уязвимом драйвере. Теперь поговорим о INTEGER OVERFLOW.

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

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

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

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

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

1.png

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

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

2.png

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

3.png

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

4.png

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

IOCTL – 0x222023 – 0x4 = 0

IOCTL = 0x222023 + 0x4

Python>hex(0x222023 + 4)
0x222027

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

5.png

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

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

6.png

Мы видим, что

7.png

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

8.png

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

9.png

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

10.png

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

Python>hex(512 * 4)
0x800

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

11.png

12.png

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

13.png

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

14.png

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

15.png

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

16.png

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

17.png

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

42.png

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

18.png

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

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

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

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

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

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

19.png

20.png

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

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

21.png

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

22.png

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

23.png

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

24.png

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

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

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

25.png

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

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

26.png

27.png

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

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

28.png

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

29.png

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

30.png

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

31.png

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

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

32.png

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

33.png

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

34.png

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

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

35.png

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

36.png

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

37.png

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

38.png

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

39.png

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

Python>hex(2088)
0x828

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

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

40.png

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

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

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

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

41.png


=======================================================
Автор текста: Рикардо Нарваха - Ricardo Narvaja (@ricnar456)
Перевод на русский с испанского: Яша_Добрый_Хакер(Ростовский фанат Нарвахи).
Перевод специально для форума системного и низкоуровневого программирования — WASM.IN
21.12.2018
Версия 1.0

4 4.392
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90

Комментарии


      1. sp1der_cat 19 дек 2018
        это кликбейт или я особенный и не вижу контента?)
        sn0w и yashechka нравится это.
      2. yashechka 19 дек 2018
        Это заглушка пока я делаю.
      3. sn0w 26 дек 2018
        простите пожалйста, буду малословен: payload_offset = id(object)
        payload_offset += class_payload_offset
        *
        payload_offset = "lalala"
      4. yashechka 26 дек 2018
        Да Вы эксперт я вижу.