Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/stong/cve-2020-15368
Повышение привилегийАнализ уязвимостейЭксплуатацияШелл-кодОбучение и ОбразованиеРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHubstong/cve-2020-15368

CVE-2020-15368

CVE-2020-15368,又名“如何利用易受攻击的驱动程序”

Репозиторий
5154914 лет назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Как эксплуатировать уязвимый драйвер Windows

Эксплойт и Proof of Concept (PoC) для CVE-2020-15368. Asrock перепаковал драйвер rweverything для своего инструмента настройки RGB-контроллера и подписал его. Они "защищают" его, шифруя свои ioctl'ы... lol. Мы случайно нашли этот CVE прошлым летом, и, насколько я знаю, драйвер до сих пор не пропатчен. Последствия, конечно, — выполнение произвольного кода в ядре и т.д. Так что наслаждайтесь этим "0day" lol.

Если вы хотите поспорить со мной, является ли это НАСТОЯЩИМ БОНА ФИДЕ CVE, пожалуйста, свяжитесь со мной в Twitter, мы можем устроить грандиозную ссору в публичных соцсетях, и это будет очень интересно для всех участников! Я даже куплю домен для этой ошибки, если вы так захотите. Всё дело в маркетинге!!!!

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Данная публикация предоставлена только в образовательных целях. Ответственность за соблюдение всех применимых местных, государственных и федеральных законов лежит на читателе. Автор(ы) данной публикации не несут никакой ответственности и не несут ответственности за любое неправильное использование или ущерб, причиненный программным обеспечением, содержащимся в данной публикации.

Предыстория

Застряв на карантине, мы с моими соседями (Pear0, Codetector) баловались на новой материнской плате Pear0 от Asrock. Ярко-красные светодиоды были крайне раздражающими, и настроить их в Linux было невозможно. Поэтому наш план состоял в том, чтобы реверсировать драйвер Windows, управляющий ими, и воспроизвести операции ввода-вывода в Linux.

Короче говоря, не прошло много времени, как мы поняли, что драйвер буквально является просто универсальным драйвером, предоставляющим произвольный доступ на чтение/запись ко всему. Это включает управляющие регистры, такие как CR3, CR4, физическую память и т.д. Такие драйверы предназначены для использования в качестве отладочного инструмента, и на сайте вендора это четко указано.

docs/lol.png

Нам это показалось крайне забавным. Очень волнительно впервые вызвать тройную ошибку (triple fault) и принудительную перезагрузку компьютера из пользовательского режима. (Возможно, в 20-й раз это менее волнительно.) В любом случае, мы сообщили об ошибке и забыли о ней на год.

Настройка

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

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

docs/processhacker.png

Мы можем просмотреть наш объект Device в WinObjEx64.

docs/processhacker.png

Мы даже можем поиграть с устройством в FileTest.

docs/filetest.png

docs/filetest2.png

Все эти три инструмента потрясающие, особенно PH и FileTest. Они как швейцарский нож и обязательно должны быть в инструментарии каждого реверс-инженера Windows. Например, насколько я понимаю, Jonas L нашел бесчисленные уязвимости локального повышения привилегий в Windows, просто балуясь в FileTest. Так что в Windows действительно есть отличные инструменты для баловства. Хотел бы я такое же дерьмо в Linux.

Обход "безопасности"

Rweverything имеет ioctl, который принимает ioctl в качестве параметра, управляющего выполняемой операцией (чтение памяти, запись памяти, чтение MSR и т.д.), и объединение некоторых параметров, специфичных для операции, таких как адрес источника, адрес назначения и т.д. Когда мы сравниваем код двух драйверов:

docs/comparison.png

🤔🤔🤔🤔🤔🤔🤔🤔🤔

Тем не менее, драйвер делает жалкую попытку безопасности через сокрытие, требуя, чтобы все вызовы ioctl были соответствующим образом зашифрованы жестко закодированным ключом AES. Код (после некоторой очистки) выглядит так:

root@kitploit:~
if ( IoControlCode == 0x22EC00 )
{

  char enc_key[32];
  memset(enc_key, 0, sizeof(enc_key));
  memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
  memcpy(enc_key + 13, ioctl_args->key, 16);
  
  size_t cb_decrypted = 0;
  my_decrypted_cmd* decryptedCmd = NULL;
  DWORD iv_size = ioctl_args->iv_size;
  DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);

  // на самом деле просто вызывает BCrypt API для получения реализации AES
  if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
  {
    // Дешифрование не удалось
    if ( decryptedCmd )
      ExFreePoolWithTag(decryptedCmd, 0);
    irp->IoStatus.Status = 0xC000000D;
    goto Fail_Out;
  }
  IoControlCode = decryptedCmd->opcode;
  Rweverything_Args = &decryptedCmd->args;
}
else // не 0x22EC00
{
  if ( IoControlCode != 0x22E858 &&
       IoControlCode != 0x22E860 &&
       IoControlCode != 0x22E800 &&
       IoControlCode != 0x22E804 )// белый список кодов управления
    IoControlCode = 0; // блокировать всё остальное
}

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

Также интересно, что он позволяет пользователю указать часть ключа (???), зачем — понятия не имею. Код просто очень плохо написан.

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

Общение с драйвером

Мы открываем хендл к драйверу и используем DeviceIoControl для вызова ioctl, все стандартно.

root@kitploit:~
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

// ... настройка зашифрованных данных ioctl

BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);

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

Самый простой способ добиться этого — перезаписать CR3 мусором. Я знаю, что некоторые из вас, читающих это, новички, и это нормально, поэтому я объясню подробно. Я тоже глуп, так что, возможно, это поможет вам научиться. Если вы знаете, что делаете, можете пропустить это.

В x86, когда включена страничная организация памяти (практически всегда в любой современной ОС), CR3 указывает на физический базовый адрес каталога таблиц страниц верхнего уровня. Если вы не знаете, что это значит, прочтите статью в Википедии о виртуальной памяти.

Когда мы перезаписываем CR3 мусором, скажем, 0x0000000000000000, TLB сбрасывается, и при попытке выполнить следующую инструкцию процессор (точнее, MMU) попытается преобразовать указатель инструкции в физический адрес. Преобразование адреса можно рассматривать как последовательность обходов таблиц страниц, начиная с CR3. CR3 теперь указывает на физическую память по адресу 0, которая существует и доступна; однако крайне маловероятно, что это допустимая таблица страниц. (Записи таблицы страниц, или PTE, должны следовать определенной структуре.)

Когда это происходит, мы получаем страничную ошибку при преобразовании адреса. Обычно CPU переходит к адресу обработчика страничных ошибок. Но откуда он знает, где находится функция обработчика страничных ошибок? Это хранится в структуре данных памяти, известной как таблица дескрипторов прерываний (IDT). У процессора есть регистр (чтение/запись с помощью инструкций sidt и lidt), который хранит виртуальный адрес IDT. Видите проблему? Чтобы обработать страничную ошибку, нам сначала нужно выполнить еще один доступ к виртуальной памяти, а значит, еще одно преобразование адреса.

Конечно, наше второе преобразование адреса тоже вызовет ошибку. Теперь у нас двойная ошибка (Double Fault): ошибка, возникающая при обработке первой страничной ошибки. Это довольно серьезно, но все еще возможно восстановление — процессор даст нам последний шанс восстановиться. Конечно, эта попытка также безжалостно прерывается третьей и последней страничной ошибкой — тройной ошибкой (Triple Fault). На этом этапе CPU просто сдается и делает аппаратную перезагрузку машины. Если бы вы выполнили эту процедуру на физической машине, вы бы, вероятно, сейчас увидели заставку BIOS.

Теперь, если у вас есть вопросы, я дам тот же ответ, который всегда давали мне. А именно: прочитайте Intel Manual Volume 3A (она же Библия).

Эксплуатация драйвера

ОК, как же на самом деле эксплуатировать драйвер? Оглядываясь, мы видим свободный примитив чтения/записи произвольной физической памяти. Он в основном отображает любой нужный физический адрес с помощью MmMapIoSpace, копирует ваш буфер туда (или наоборот) и отключает отображение адреса.

Маленькая заметка: когда вы пытаетесь вызвать MmMapIoSpace с глупыми аргументами, как мы, при подключенном отладчике ядра, вы получите багчек. Вы можете обойти это, записав магический байт в WinDbg. Поищите комментарий со ссылкой на MiShowBadMapper в exploit.cpp, чтобы узнать больше. Я на самом деле не знаю, что это за хрень, и мне не интересно выяснять

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

Одна идея — прочитать CR3, прочитать таблицы страниц и выполнить преобразование виртуальных адресов самостоятельно. Это отличная идея. Она не работает. Потому что Windows больше не позволяет отображать таблицы страниц с помощью MmMapIoSpace. Так что нам нужно быть умнее.

Я использовал технику xeroxz из VDM. Она довольно проста, но техника весьма умная. Хотя мы не знаем расположение физической памяти, мы все равно можем сканировать всю физическую память, пока не найдем то, что ищем. Мы можем использовать то, что содержимое страниц всегда одинаково как физически, так и виртуально: любые смещения относительно границ страниц всегда сохраняются. Например, если у меня есть страница 0x7fff000000000XXX, отображенная на физический кадр 0x0000000123456XXX, то XXX всех адресов одинаково как в физическом, так и в виртуальном адресе. Вся внутристраничная структура сохраняется; таким образом, мы можем сканировать в поисках какой-нибудь интересующей нас страницы, которую хотели бы перезаписать.

Самое простое, что мы можем перезаписать, — это, вероятно, какой-нибудь легко достигаемый обработчик системного вызова или ioctl. В Windows есть стандартная функция Beep(), которая заставляет компьютер пищать. Хотите верьте, хотите нет, но это реализовано в драйвере Beep.sys, который предоставляет устройство Beep. (Фактически, вы можете увидеть его на скриншоте WinObjEx64 ранее.) Любой может использовать устройство Beep, и оно редко вызывается. Итак, давайте перезапишем обработчик ioctl Beep.

Мы можем загрузить Beep.sys в IDA и проверить обработчик DeviceIoControl.

docs/beep.png

По смещению страницы 0x270 у нас есть этот код с байтами 40 53 48 .... Ни один из этих байтов не перемещен, поэтому сканирование этой функции очень просто. Если бы были перемещенные байты, нам нужно было бы замаскировать их с помощью подстановочных знаков. Это та же идея, что и сканирование сигнатур при написании читов для игр.

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

На этом этапе мы можем довольно легко повысить привилегии, заменив токен безопасности нашего процесса токеном системного процесса, чтобы получить права nt authority\system. К сожалению, драйвер asrock все равно требует прав администратора для открытия, так что это не очень интересно.

Для нас мы пишем простой шеллкод, который выделяет память и копирует нагрузку второго этапа, а затем создает новый поток ядра. Мы не можем сделать все в нашем перезаписанном обработчике Beep, потому что 1) мы ограничены одной страницей и 2) мы вызовем сбой системы при попытке закрыть хендл к устройству Beep, так как мы также испортили остальной код в устройстве Beep. Что касается получения указателей ядра, это на самом деле легко, потому что NtQuerySystemInformation даст их нам бесплатно, если мы вежливо попросим.

root@kitploit:~
__int64 __declspec(dllexport) __fastcall MyIRPHandler(struct _DEVICE_OBJECT* a1, IRP* irp)
{
    MyIrpStruct* user_data = (MyIrpStruct*)irp->AssociatedIrp.SystemBuffer;

    void* my_rwx = user_data->nt_ExAllocatePoolWithTag(NonPagedPoolExecute, user_data->payload_size, 'lmao');

    user_data->nt_memcpy(my_rwx, user_data->payload, user_data->payload_size);

    HANDLE hThread;
    user_data->nt_PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE)my_rwx, NULL);

    user_data->nt_IofCompleteRequest(irp, 0);

    return 0;
}

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

Заключение

Я плохой исследователь безопасности и нахожу только бесполезные ошибки, случайно. Спасибо всем за чтение. Пожалуйста, подпишитесь на мой OnlyFans

Скачать инструмент