
В последнее время появилось множество релизов на основе гипервизора, нацеленных на обход DRM Denuvo, поэтому я решил написать статью о том, в чём заключается этот подход, и как он работает с технической точки зрения.
Важные примечания: эта статья предназначена исключительно для образовательных целей. Я НЕ оправдываю и НЕ поддерживаю анализ, обход и взлом игр (будь то с защитой Denuvo или какой-то другой) и НЕ поддерживаю пиратство ни в каком виде. Пожалуйста, поддерживайте разработчиков, упорно трудившихся над этими играми и мерами защиты (мы любим тебя, Blaukovitch).
Цель этой статьи — проанализировать такой способ обхода и способ защиты от него со стороны Denuvo.
Почему был выбран этот способ
Почему вообще выбрали этот способ?
Основная причина этого заключается в том, что это не требующий особых усилий способ запуска защищённой игры без необходимости выполнения полного реверс-инижиниринга против самой защиты. Его можно быстро выпустить, а это важно, ведь ранний релиз может повлиять на показатели первой недели продаж игры.
Но за это приходится расплачиваться низкой производительностью, нестабильностью, проблемами с безопасностью и множеством других трудностей.
Введение
В этой статье мы проанализируем релиз обхода основной DLL из недавно выпущенной Resident Evil: Requiem с cs.rin.ru и подробно расскажем о том, как гипервизоры наподобие HyperDBG можно использовать для спуфинга аппаратных значений, фактически, обеспечив таким образом обход DRM Denuvo.
Требования и ограничения
Чтобы этот способ сработал, обычно необходимо ослабить базовые меры защиты ядра Windows для возможности загрузки неподписанного или нестандартного драйвера ядра.
В публичных релизах чаще всего встречаются следующие требования:
Отключение Driver Signature Enforcement (DSE)
Отключение PatchGuard
Отключение Windows Defender
Модификация ядра Windows
То есть мы практически отключаем Windows, чтобы запустить игру
Чтобы сделать всю свою систему максимально незащищённой, можно использовать EfiGuard [17]!
Анализ DLL гипервизора Resident Evil: Requiem
0) Первоначальная загрузка
В показанном ниже примере amd_ags_x64.dll [4] заменяется на пропатченную прокси-dll, поставляемый вместе с релизом. Затем прокси-dll выполняет запуск:
Основной dll обхода
Оригинальной dll [4] AMD GPU Services SDK, переименованной в
amd_ags_x64.org


1) Выбор бэкенда гипервизора (Intel/AMD)
После того, как основная dll обхода становится доступной, она в зависимости от производителя CPU выбирает бэкенд гипервизора.
Считывается строка CPUID vendor и на основании ID производителя выбирается бэкенд.
В нашем примере:
"AuthenticAMD"→SimpleSvm.sys[1]"GenuineIntel"→hyperkd.sys[2]

2) Создание сервиса и запуск драйвера
После выбора драйвера создаётся сервис, который запускается для этого драйвера.
Так как виртуализация оборудования обычно выполняется исключительным образом, часто в коде есть логика, проверяющая отсутствие других гипервизоров, занимающих VT-x или AMD-V. Если гипервизор есть, он может быть остановлен, чтобы мог инициализироваться выбранный драйвер.
Скриншоты этой логики:


3) Ресолвинг номеров системных вызовов из ntdll
Из ntdll.dll берутся номера системных вызовов, которые затем используются в других функциях.

4) Перехват IAT
Перехват IAT используется для перенаправления выбранных импортов в исполняемый файл игры.
Перехватываются следующие импорты: ntdll.dll, kernel32.dll, kernelbase.dll, user32.dll




После патчинга IAT вызовы, обычно шедшие напрямую к этим модулям, пропускаются через обработчики dll обхода защиты.
5) Работа с файлом лицензии/токенами
Внутри основной dll обхода есть два заранее сгенерированных токена, назначаемых в зависимости от архитектуры CPU хоста и соответствующего гипервизора (как показано ниже). Эти токены при первом запуске записываются в файл .bin, а затем используются гипервизором в качестве токенов Denuvo.

Небольшие дополнения:
Для загрузки игры и эмуляции сервисов API Steam применялся форк GBE Detanup01 [10].
Для дополнительного сокрытия гипервизора применялся HyperEvade (об этом мы поговорим чуть ниже).

В исследованном мной образце строка бренда CPU имеет значение DenuvOWO CPU @ 1337 GHz. (Я привёл его только для того, чтобы показать, что часть информации необязательно должна быть реальной, а окружения можно изменять, чтобы в гипервизоре использовались специальные значения).

Давайте перейдём к подробностям!

HyperDBG — это очень многофункциональный отладчик, поэтому я не буду анализировать все его части, поэтому в первую очередь сосредоточусь на основной теме спуфинга оборудования и на том, как его можно реализовать в контексте Denuvo.
Все приведённые здесь команды можно для тестирования использовать в скриптовой системе HyperDBG. Позже можно создать специальную реализацию для оптимизации этого процесса под релиз реального обхода.
Примеры:
https://docs.hyperdbg.org/commands/scripting-language/debugger-script https://github.com/HyperDbg/scripts
Спуфинг оборудования
Для обеспечения возможности выхода из CPUID HyperDBG использует Virtual Machine Control Structure (VMCS). Это гарантирует, что при исполнении команды CPUID процессор будет приостанавливать работу и запускать выход из VM, передавая управление гипервизору. По сути, это позволяет выполнять спуфинг оборудования на очень низких уровнях системы.
Как же конкретно это происходит внутри HyperDBG? Так как в HyperDBG нет базы данных всех CPU и их аппаратных значений (да и, честно говоря, она не требуется), нам нужно найти аппаратные значения для конкретного CPU и передать их HyperDBG, чтобы он выполнил свою работу. Для этого лично я пользуюсь coreinfo64.exe из инструментов Windows Sysinternals (их можно скачать отсюда: live.sysinternals.com). Те же значения можно найти и на странице продуктов Intel или на странице валидатора CPU-Z [12][13].
Затем можно использовать команду !cpuid [5] и другие команды, чтобы выполнить непосредственный спуфинг значений CPU!
Подробную информацию об CPUID можно изучить здесь: http://www.flounder.com/cpuid_explorer2.htm
Спуфинг базовых значений CPUID [8]
В этом примере я буду использовать i9-11900K.
Его стандартная сигнатура обычно равна 0x000A0671, поэтому мы можем просто передать HyperDBG команду переписать CPUID так, чтобы он совпадал с нашим CPU.

Здесь мы присваиваем CPUID значение 0x000A0671 и обнуляем бит Hypervisor Present, чтобы HyperDBG не обнаруживался.
Закончив со спуфингом CPUID, перейдём к строке Brand.
Она равна 11th Gen Intel(R) Core(TM) i9-11900K @ 3.50GHz.
Вся строка имеет длину 48 байт, поэтому нужно разбить её на три сегмента по 16 байт. (Так же, как это показано в релизе обхода).
Примечание: думаю, что можно изменить это значение на любое другое, но не совсем в этом уверен.

После этого перейдём к спуфингу значений в отдельных листьях, чтобы они точно соответствовали нашему CPU [14].
Изучите страницу о CPUID в Википедии, где перечислены все значения листьев cpuid: https://en.wikipedia.org/wiki/CPUID
Спуфинг флагов дополнительных фич (лист 0x7) :
Чтобы CPU совпадал идеально, нужно проспуфить гораздо большее количество значений; мы не можем рисковать при проверках, поэтому будем внимательны ко всем значениям, описывающим CPU.
Давайте начнём со спуфинга флагов дополнительных фич [6]. Я снова использую CPU i9-11900K.

Этот лист (0x7 / 0) используется для проверки того, поддерживает ли CPU определённые фичи безопасности или производительности.
Если у вас более старый процессор и эти флаги у него отличаются, то это может вызывать некоторые проблемы.
Спуфинг параметров кэша (лист 0x4) [7]:
i9-11900K имеет 16 МБ кэш-памяти, давайте выполним спуфинг этого значения, чтобы оно соответствовало нашему CPU. Для этого нужно спуфить значения во всех подлистьях листа 0x4 и все они должны совпадать.

Теперь можно двигаться дальше.
Спуфинг топологии (листья 0xB и 0x1F) [9]
Так как Topology Enumeration можно использовать для определения существующих логических процессоров и ядер, эти значения тоже нужно спуфить для соответствия нашему CPU.

На этом мы завершили спуфинг значений CPU.
Спуфинг номеров накопителей и дисков
Завершив со спуфингом значений CPU, перейдём к остальному оборудованию, начав с номеров накопителей и дисков.
Для этого мы воспользуемся командой !syscall, позволяющей спуфить идентификатор накопителя, который видит игра/DRM.
Спуфить мы будем системный вызов NtDeviceIoControlFile, который обычно используется кодом пользовательского режима для передачи драйверам накопителей кодов управления вводом-выводом (IOCTL). Конкретные перехватываемые IOCTL зависят от информации, которую ищет целевой код.
Примечание: IOCTL_STORAGE_GET_DEVICE_NUMBER возвращает STORAGE_DEVICE_NUMBER (тип + номер устройства + раздел), но не содержит строк серийного номера/модели. Информация о серийном номере/модели обычно запрашивается через другие IOCTL устройств хранения (например, через IOCTL_STORAGE_QUERY_PROPERTY / StorageDeviceProperty), поэтому если наша цель заключается в спуфинге серийного номера, то обычно нужно перехватывать именно эти запросы.

Спуфинг номера сборки Windows
Теперь давайте выполним спуфинг номера сборки Windows — как обычно, он должен совпадать со значением, которое использовалось для генерации токена Denuvo.
Здесь я буду использовать команду !epthook для спуфинга чтения NtBuildNumber из KUSER_SHARED_DATA (то есть сделаю так, чтобы запрашивающий значение увидел нужное ему), а не буду надеяться на просто решение с патчингом структуры в памяти.

Примечание: в зависимости от версии Windows (10/11)/способов защиты (и вещей наподобие VBS/VTL1), части KUSER_SHARED_DATA в любом случае могут быть защищены от записи, поэтому в общем случае здесь безопаснее спуфить то, что считывает целевой код. Кроме того, в KUSER_SHARED_DATA есть множество других проверяемых значений, и если я буду перечислять их все, то статья превратится в 20-страничное руководство о подробностях KUSER_SHARED_DATA.
Я собираюсь выпустить ещё одну статью, где мы с Marius проанализируем все новые и старые проверки Denuvo и то, как их, может быть, можно обойти.
Изучите нашу статью, в которой мы говорим о различиях KUSER_SHARED_DATA в Windows 10 и 11: https://connormcgarr.github.io/kuser-shared-data-changes-win-11/
Также можете посмотреть на наш плагин HyperHide, в котором также затрагивается спуфинг KUSER_SHARED_DATA Windows: https://github.com/Air14/HyperHide?tab=readme-ov-file#5-kusershareddata
Спуфинг ID производителя GPU
При спуфинге GPU я буду использовать команду !epthook для изменения различных значений.
В этом примере я спуфлю GPU, выдавая его за AMD Radeon RX 6800 XT. (Подробнее о PCIe Configuration Space можно узнать из документации AMD: https://docs.amd.com/r/en-US/pg343-pcie-versal/Configuration-Space.)
Для этого требуется спуфинг пяти значений: Vendor ID, Device ID, Revision ID, Subsystem Vendor ID и Subsystem ID.


Улучшенная маскировка
В HyperDBG есть опция Transparent Mode (связанные с RDTSC операции и прочие трюки…), которую можно включить при помощи команды !hide. Это может помочь в снижении сигнала от некоторых основанных на таймингах проверок попытками скрыть оверхед, возникающий из-за выхода из VM.
Примечание: это не какой-то волшебный переключатель абсолютного сокрытия, и его поведение может варьироваться, поэтому не стоит предполагать, что каждая тайминг-проверка автоматически будет обходиться только благодаря включенному режиму прозрачности.
Также можно использовать HyperEvade [11], но здесь мы его рассматривать не будем.
Важное примечание
Denuvo проверяет множество других значений в KUSER_SHARED_DATA и WinAPI, а также выполняет множество других проверок перед и после OEP (Original Entry Point, исходной точки входа). Если хотите найти их, то готовьтесь анализировать защиту, как обычный человек, а не пытаться искать «уязвимости» для их обхода. Однако хотелось бы сказать, что таким способом можно обойти все проверки DRM Denuvo (на февраль 2026 года).
SimpleSVM
SimpleSVM используется в качестве гипервизора для CPU AMD. Я сомневался, стоит ли писать здесь о нём, но решил вкратце упомянуть.
По сравнению с HyperDBG, SimpleSVM всё ещё остаётся довольно экспериментальным. Поэтому его исходный код нужно модифицировать, чтобы реализовать некоторые из спуфов, о которых говорилось выше. Кроме того, документации по нему практически нет, так что придётся заниматься старым добрым анализом исходного кода, чтобы понимать основную часть того, что делает гипервизор. (К счастью, разработчик SimpleSVM tandasat добавил в основную часть кода комментарии с объяснениями его работы).
Я не буду вдаваться в подробности реализации каждого спуфинга, но хотел бы упомянуть, что между Intel и AMD есть множество различий, из-за чего гипервизоры AMD могут быть более распознаваемыми.
Кроме того, на Github есть КУЧА публично доступных гипервизоров; в качестве основы можете изучить наши с Marius гипервизоры:
https://github.com/Nitr0-G/SVM-Hypervisor
https://github.com/Nitr0-G/VMX-Hypervisor
Мы использовали их для множества крутых трюков, и они могут быть очень хорошим фундаментом, если вы хотите изучать гипервизоры и, может быть, создать собственный.
Завершение
Закончив со всем этим, можно перейти к генерации токена Denuvo, соответствующего вашему CPU (если у вас есть CPU), просто сгенерировать новый токен Denuvo для этих конкретных значений в VM или использовать какой-то тип гипервизора (разбирайтесь сами, здесь есть миллион разных решений). Впрочем, я бы порекомендовал генерировать токен внутри окружения с этими значениями, а затем использовать те же значения из окружения, чтобы избежать проблем с обходом защиты.
Обнаружение попыток обхода при помощи гипервизора
А теперь давайте перейдём к той части статьи, которая понравится Denuvo больше всех: к различным методикам обнаружения попыток обхода при помощи гипервизора и к патчингу таких методик.
Откровенно говоря, на уровне пользовательского режима здесь особо ничего не поделаешь (во всяком случае, из того, о чём я могу говорить публично), поэтому для добавления большинства этих проверок необходимо реализовать специализированный драйвер.
В этом разделе я расскажу про общие способы обнаружения гипервизоров, а чуть ниже объясню, почему некоторые из них не сработают в случае Denuvo.
Обнаружение перехвата EPT
Для распознавания хуков на уровне EPT можно использовать различные методики обнаружения перехвата EPT [15]. Мы разберём спуфинг значений GPU в HyperDBG, потому что в этом случае используется перехват EPT.
Описанные здесь методики рассмотрены и задокументированы Momo5502: https://momo5502.com/posts/2022-05-02-detecting-hypervisor-assisted-hooking/
В них входят проверка таймингов, проверка потоков и проверка записи, для которых Momo предоставил исходный код [16].
Я не буду повторять то, что написано в его статье, так что прочитайте её! [15]
Примечание: проверку таймингов из статьи Momo можно обойти при помощи использования команды !hide on, которая по умолчанию спуфит RDTSC (но иногда это решение может быть нестабильным).
Принудительный DSE
Как и во многих других DRM (например, Byfron) можно принудительно применять DSE, чтобы игра не запускалась в Windows Test Mode. Так как неподписанные драйверы нельзя запускать, основной драйвер hyperkd.sys и SimpleSVM.sys работать не будут.
В случае Byfron, включение тестового режима или отключение DSE приведёт к поломке игры и невозможности её запуска до отключения тестового режима.
Проверки таймингов
Проверки таймингов могут показаться хорошей идеей, особенно учитывая то, что гипервизору приходится выполнять выход из VM на многих функциях, такое решение ненадёжно и может быть очень шумным, что способно привести к ложноположительным срабатываниям.
С учётом всего этого, я не рекомендую проверки таймингов в качестве решения, потому что они могут вызывать проблемы и их можно спуфить при помощи достаточно защищённой реализации спуфинга.
Текущая ситуация с Denuvo
Существует множество проверок для обнаружения гипервизоров, но проблема в том, что большинство из них можно тем или иным образом спуфить. Применяемые проверки должны быть очень надёжными и очень защищёнными от спуфинга.
Самая очевидная проверка уже реализована в самой dll обхода.

Здесь dll проверяет сигнатуру интерфейса Microsoft Hyper-V, использующую cpuid, и, теоретически, Denuvo способна просто начать проверять каждое значение cpuid и использовать его для генерации токенов, но это может просто превратиться в ещё одну игру «кошки-мышки»: Denuvo будет добавлять новые проверки, а в релизах гипервизора будут появляться всё новые спуфы, а это совершенно ненадёжное долговременное решение для гипервизоров.
Проверки таймингов ненадёжны, а потому тоже не могут использоваться.
Решение, которое будет применять Denuvo, должно:
Быть надёжным.
Быть не очень простым в обходе.
Не влиять на производительность.
Не надеяться на драйвер ядра.
Не портить игровой процесс людям, купившим игру.
Избыточные решения
Избыточное решение для распознавания таких гипервизоров может быть одним из двух:
Изначальный запуск всей игры в гипервизоре, который будет работать в качестве VM поверх уже существующей виртуализации в основном двоичном файле; такой подход в случаях наподобие Denuvo будет совершенно неэффективным из-за проблем с производительностью и вызовет сильную негативную реакцию пользователей. К тому же он никогда не будет реализован из-за проблем с поддержкой оборудования и производительностью. (Кроме того, это будет ещё один случай SecuROM).
Реализация распознавания на уровне драйвера ядра для всех возможных видов целей предотвращения анализа, что будет способствовать и обнаружению гипервизоров, и повышению общей эффективности DRM. (У компании Denuvo есть продукт защиты от читерства, работающий на уровне ядра, то есть она уже имела опыт разработки для ядра, но это не означает автоматически, что для DRM-продукт будут выпускаться компоненты ядра, не требующие различных компромиссов). Кроме того, ситуация быстро ухудшается, если учесть поддержку Proton/Wine и общие ожидания пользователей относительно уровня производительности.
Риски
«Но какие здесь могут быть риски?»
Вы серьёзно? Исполнение на своём компьютере кода уровня ядра, написанного каким-то неизвестным человеком из Интернета, кажется вам хорошей идеей?
Если люди, выпускающие такие «крэки» сильно захотят, то обфусцируют драйвер, оправдывая это «сокрытием его механизмов от Denuvo», и выпустив в его составе всё, что угодно. Они и так уже предлагают отключить все параметры безопасности, так что им будет несложно запустить на компьютере пользователя любой код.
Так что можете продолжать доверять этим придуркам, но не удивляйтесь, если однажды жадность ослепит их и они решат выпустить «крэк» с добавленным кодом для кражи информации.
«Но то же самое можно сказать о любом ПО из Интернета!»
Большинство ПО из Интернета не нужно запускать с более высокими разрешениями, чем учётная запись администратора, а в данном случае это именно так. К сожалению, сегодня ситуация отличается от того, что было на сцене прошлого, когда из-за мельчайших проблем крэки разносили в пух и прах. Этих людей совершенно не заботит их репутация, потому что подобные крэки может выпускать кто угодно, идёт лишь соревнование по времени, кто выпустит их первым.
В целом, очень сложно убедить участников сцены в том, что пользоваться релизами гипервизоров плохо, потому что, честно говоря, им на это пофиг. Их не волнует, что их данные и личная информация окажутся на каком-нибудь китайском форуме, если они смогут играть в свои игры и писать посты о том, что они «наконец победили Denuvo».
Подведём итог
При запуске игры загружается специальная dll обхода для добавления слоя гипервизора, который можно использовать для спуфинга различных проверяемых Denuvo значений; такая реализация позволяет пользователю запускать игру с уже сгенерированным токеном, сконфигурированным под окружение гипервизора.
В заключение
С чисто технической точки зрения это довольно неплохой подход, но лично я не думаю, что релизы крэков/обходов каких-либо игр должны содержать код, требующий доступа уровня ядра и прикрепляющий себя к очень низким слоям, особенно ценой производительности и сомнительной безопасности. Вместо того, чтобы тратить на это время, изучайте защиту и пытайтесь анализировать её самостоятельно с точки зрения исследователя.
Ссылки и источники
Изучите проекты и темы, о которых я говорил в процессе анализа.
Для понимания некоторых из тем, поднятых в этой статье, требуется знание архитектуры CPU Intel. Изучите руководства по CPU Intel (в частности, «Intel (R) 64 and IA-32 Architectures Software Developerʼs Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4») (По сути, для написания этой статьи мне самому пришлось прочитать половину этого руководства.): https://cdrdv2.intel.com/v1/dl/getContent/671200
Прочитайте эту статью, чтобы лучше разобраться в HyperDBG, различных способах его применения и функциях: https://scispace.com/pdf/hyperdbg-reinventing-hardware-assisted-debugging-extended-2qr0u9qm.pdf
Изучите документацию AMD о PCIe Configuration Space: https://docs.amd.com/r/en-US/pg343-pcie-versal/Configuration-Space
Информация о CPUID: https://en.wikipedia.org/wiki/CPUID
Изучите форк LLVM BackEngineering, который можно использовать для разработки драйвера ядра: https://www.llvm-msvc.com/
SimpleSVM : https://github.com/tandasat/SimpleSvm
HyperDBG : https://doxygen.hyperdbg.org/index.html
Hyperkd HyperDBG: https://doxygen.hyperdbg.org/dir_fd401680aa8c7ceffc479e97f6bdc4df.html
amd_ags_x64.dll : https://www.dllme.com/dll/files/amd_ags_x64
Документация по !cpuid HyperDBG: https://docs.hyperdbg.org/commands/extension-commands/cpuid
Лист дополнительных фич CPUID в Википедии: https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features
Лист потоков/ядер CPUID и топологии кэша Intel в Википедии: https://en.wikipedia.org/wiki/CPUID#EAX=4_and_EAX=Bh:_Intel_Thread/Core_and_Cache_Topology
Лист базовых значений CPUID в Википедии: https://en.wikipedia.org/wiki/CPUID#EAX=0:_Highest_Function_Parameter_and_Manufacturer_ID
Intel 64 Architecture Processor Topology Enumeration (Page 14) : https://www.intel.com/content/www/us/en/content-details/775917/intel-64-architecture-processor-topology -enumeration-technical-paper.html
Форк GBE Detanup01: https://github.com/Detanup01/gbe_fork
Информация о HyperEvade : https://fosdem.org/2026/events/attachments/CDPRDX-invisible_hypervisors_debugging_with_hyperdbg/slides/266821/hyperevad_lyhtmgy .pdf
Спецификации CPU i9-11900K: https://www.intel.com/content/www/us/en/products/sku/212325/intel-core-i911900k-processor-16m-cache-up-to-5-30-ghz/specifications.html
Спецификации CPU i9-11900K пользователя валидатора CPU-Z: https://valid.x86.fr/8n2zgs
Подробное описание идентификации по CPUID: https://www.felixcloutier.com/x86/cpuid
Методики обнаружения перехвата EPT авторства Momo5502: https://github.com/momo5502/ept-hook-detection
Репозиторий Github Momo5502 по обнаружению перехвата EPT: https://github.com/momo5502/ept-hook-detection
Благодарности
Эта статья стала полностью доступной благодаря Rose/Natasha (0x80000003)
Огромная благодарность:
Marius за анализ Denuvo и помощь в написании этой статьи
Eintim23 за вычитку статьи и предложенные идеи
********* за публикацию способа обхода защиты Resident Evil: Requiem
Momo5502 за анализ распознавания хуков EPT
SinaKarvandi за исследования гипервизоров и разработку HyperDBG (SinaKarvandi - Overview)
