Предисловие

Этот пост предназначен исключительно для образовательных целей. Denuvo считается одним из самых успешных решений для управления цифровыми правами, поэтому оно многим интересно. В этом посте представлен большой объём моих личных заметок и переписки с другими реверс-инженерами (см. раздел «Благодарности»), содержащий информацию о последних версиях Denuvo; многое из этого раньше не публиковалось.

Я не стремлюсь нанести какой-либо ущерб Irdeto, поэтому часть информации была вырезана из поста.

Denuvo

Denuvo — это система для защиты от вмешательства и для управления цифровыми правами (digital rights management system, DRM). В основном она используется для защиты от пиратства и реверс-инжиниринга цифровых медиа наподобие видеоигр. В отличие от традиционных DRM-систем, Denuvo использует широкий спектр уникальных методик и проверок для подтверждения и целостности игры, и лицензированности пользователя.

Общий принцип работы

В идее, лежащей в основе Denuvo, нет ничего нового. По причинам, которые вскоре станут понятны, её можно описать как полуонлайн-DRM. Общая схема выглядит так:

  1. Пользователь запускает program.exe в первый раз.

  2. Перед исполнением кода игры Denuvo собирает информацию для идентификации оборудования текущей системы и подготавливает её для отправки через Интернет.

  3. Затем program.exe отправляет эту информацию об оборудовании на сервер Denuvo. Разумеется, мы не знаем, что происходит на сервере, но, скорее всего, там применятся обратимые математические функции, чтобы скомбинировать «заимствованные константы» (stolen constants) (подробнее о них ниже) с информацией об оборудовании, переданной program.exe. Далее сервер отправляет эту смешанную информацию, которую я в дальнейшем буду называть «файл лицензии», обратно program.exe.

  4. После получения program.exe файла лицензии создаётся его локальная копия, к которой может обращаться program.exe при последующих запусках, благодаря чему дополнительные онлайн-запросы не требуются (отсюда и «полуонлайновость», о которой говорилось выше).

  5. program.exe перенаправляется на исходную точку входа (original entry point, OEP) и начинает исполнять сам код игры. В процессе исполнения program.exe собирает информацию об оборудовании и пытается расшифровать заимствованные константы из файла лицензии. Эти уже расшифрованные константы затем применяются для исполнения «исходных команд игры».

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

Более техническое объяснение

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

Возвращаемся к общему принципу работы

Файл лицензии

Когда Denuvo впервые добавляется к двоичному файлу, в игре выбираются определённые функции, которые становятся «защищёнными». Это означает, что сама функция будет исполняться внутри виртуальной машины, а выбранные части определённых команд будут полностью удалены из двоичного файла. Файл лицензии — это просто все эти удалённые байты, соединённые вместе и скомбинированные с идентификацией оборудования пользователя при помощи обратимых математических функций. Здесь важно, что все применяемые операции обратимы: в противном случае клиент не смог бы расшифровать данные и получить исходную константу.

DWORD лицензии

Поскольку заимствованных команд много, до передачи исполнения точке входа OEP Denuvo записывает избранные части файла лицензии в DWORD, разбросанные по разделу .vm (.vm — это раздел PE, содержащий код VM). Каждое DWORD, которые я буду называть «DWORD лицензии» — это, по сути, одна команда, извлечённая из двоичного файла и скомбинированная с идентификационной информацией об оборудовании пользователя.

Пример зашифрованной константы/удалённой команды

Я покажу на конкретном примере, как команды «удаляются» из двоичного файла. Допустим, у нас есть следующая функция:

add(int, int):
	push  rbp
	mov  rbp, rsp
	mov  DWORD  PTR [rbp-4], edi
	mov  DWORD  PTR [rbp-8], esi
	mov  edx, DWORD  PTR [rbp-4]
	mov  eax, DWORD  PTR [rbp-8]
	add  eax, edx
	pop  rbp
	ret

Легко увидеть, что после компиляции некоторые части команд не меняются. Например:

mov  DWORD  PTR [rbp-4], edi

Здесь мы записываем содержимое 32-битного регистра EDI в [RBP-4]. В этом случае Denuvo вырежет из двоичного файла константу -4 и сохранит её на сервере. Теперь единственный способ получить доступ к этой константе, которая будет необходима для успешного выполнения add(int, int) — это запросить файл лицензии у Denuvo, потому что он будет содержать DWORD лицензии, в которых находится зашифрованная константа -4 (напомню, что файл лицензии содержит константы, перемешанные с идентификацией оборудования). Более того: Denuvo преобразует всю функцию add(int, int) в байт-код, который может понимать только её виртуальная машина. В этом байт-коде присутствует код, действующий в качестве обёртки вокруг удалённой функции. Эта обёртка отвечает за следующее:

  1. Сбор соответствующей информации об оборудовании во время исполнения (конкретной информации об оборудовании, которая была смешана с константой).

  2. Чтение соответствующего DWORD лицензии, содержащего зашифрованную константу для этой конкретной функции.

  3. Выполнение последовательности математических операций с использованием DWORD лицензии и идентификации оборудования, собранной во время исполнения, для получения значения -4. Это должны быть операции, обратные тем, что выполнял сервер.

  4. Исполнение исходной команды с уже расшифрованной константой.

В предыдущем разделе я говорил, что если собранная во время исполнения информация об оборудовании не согласуется с той, что использовалась на сервере Denuvo для шифрования константы, то пункт (3), скорее всего, вернёт результат, не равный -4, и это вызовет неопределённое поведение.

Проверки целостности пользователя

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

Проверки до OEP

Прочитав предыдущие разделы, вы могли задаться вопросом: а что произойдёт, если идентификационная информация оборудования каким-то образом изменится (например, обновится Windows, будет установлен новый CPU и так далее)? Denuvo учитывает это при помощи специальных проверок, исполняемых непосредственно перед передачей управления OEP. Они просто выполняют расшифровку каких-то констант, но вместо того, чтобы применить константу для исполнения команды, они проверяют, равна ли она нужному значению (это единственные проверки, которые выполняют такие действия, все остальные предполагают, что расшифрованная константа верна, и действуют соответствующим образом). Если результат не совпал с ожидаемым, Denuvo удаляет локально сохранённый файл лицензии и запрашивает с сервера Denuvo новый; по сути, повторяется процесс, описанный в разделе «Общий принцип работы».

KUSER_SHARED_DATA

KUSER_SHARED_DATA — это одна страница памяти только для чтения (4096 байт), отражаемая в каждый процесс, работающий на машине с Windows. Она содержит информацию, которая может понадобиться процессам, например Windows Version, Windows Build Number, SystemTime и так далее. Большую часть содержащейся в ней информации можно использовать для идентификации машины, а потому Denuvo активно пользуется ею в своих целях.

Denuvo использует следующие поля:

  • 0x026C : ULONG NtMajorVersion

  • 0x02E8 : ULONG NumberOfPhysicalPages

  • 0x02D0 : ULONG SuiteMask

  • 0x0260 : ULONG NtBuildNumber

  • 0x0264 : NT_PRODUCT_TYPE NtProductType

  • 0x0268 : BOOLEAN ProductTypeIsValid

  • 0x0270 : ULONG NtMinorVersion

  • 0x0274 : BOOLEAN ProcessorFeatures [0x40]

  • 0x026A : USHORT NativeProcessorArchitecture

  • 0x03C0 : ULONG volatile ActiveProcessorCount


Примечание: смещения указаны для 64-битных машин.

CPUID

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

Denuvo использует следующие параметры:

  • EAX=0x1 : Processor Info and Feature Bits

  • EAX=0x80000001 : Extended Processor Info and Feature Bits

  • EAX=0x80000002, 0x80000003, 0x80000004 : Processor Brand String

SYSCALL

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

Denuvo использует всего один параметр:

  • 0x36 : NtQuerySystemInformation

Проверки NTDLL

ntdll.dll — это «интерфейс ядра Windows для пользовательского режима». По сути, он предоставляет многофункциональный API, который приложения пользовательского режима могут использовать, чтобы просить ядро выполнять действия от их имени. Windows Loader загружает ntdll.dll практически во все процессы Windows; обычно эта библиотека меняется с обновлением Windows, поэтому идеально подходит для Denuvo.

Проверки функций NTDLL

Этот аспект я изучал не так глубоко, как он того стоил. Похоже, Denuvo идентифицирует пользователя на основании расположения в ntdll.dll байт определённых функций и их относительного виртуального адреса (RVA).

Папка данных образа NTDLL

Как говорилось выше, ntdll.dll обычно немного меняется с каждым обновлением/версией Windows, поэтому вполне логично, что Denuvo изучает её Image Data Directory. Если конкретнее, то она получает доступ к следующим полям:

  • Export Directory RVA

  • Export Directory Size

  • Import Directory RVA

  • Import Directory Size

  • Resource Directory RVA

  • Resource Directory Size

  • Exception Directory RVA

  • Exception Directory Size

  • Relocation Directory RVA

  • Relocation Directory Size

Блок окружения процесса (Process Environment Block, PEB)

Process Environment Block (PEB) похож на KUSER_SHARED_DATA в том смысле, что в них обоих содержится информация. Однако в PEB содержится меньше «глобальной» и больше «локальной» информации. Кроме того, у каждого процесса в системе есть свой уникальный PEB. Ещё одно важное различие заключается в том, что приложение может свободно переписывать значения в PEB, поэтому он не столь идеально подходит для проверки информации об оборудовании, но Denuvo всё равно им пользуется.

Denuvo использует следующие поля:

  • 0x0118 : ULONG OSMajorVersion

  • 0x011C : ULONG OSMinorVersion

  • 0x012C : ULONG ImageSubsystemMajorVersion

  • 0x0130 : ULONG ImageSubsystemMinorVersion

Примечания: указаны смещения для 64-битных машин.

XGETBV

XGETBV считывает extended-control-register (XCR). Я не знаю особых подробностей о ней, это очень маленькая и уникальная с точки зрения исполнения команда, которую можно использовать для определения тонких особенностей CPU.

GetWindowsDirectoryW

GetWindowsDirectoryW получает путь к папке Windows.

GetVolumeInformationW

GetVolumeInformationW получает информацию о файловой системе и о томе, связанном с конкретным корневым каталогом.

GetComputerNameW

GetComputerNameW получает имя NetBIOS локального компьютера.

GetUsernameW

GetUsernameW получает имя пользователя, связанного с текущим потоком. В нашем случае это будет имя пользователя, пытающегося запустить защищённый Denuvo двоичный файл.

Проверки целостности кода

Cyclic Redundancy Check (CRC)

CRC VM

Как и можно ожидать, Denuvo выполняет сканирование важных обработчиков (например, CPUID, SYSCALL и так далее) и, вероятно, другого кода, чтобы удостовериться в отсутствии перехвата/вмешательства. К сожалению, это всё, что я могу сказать об этих проверках.

Как будто случайная проверка .VM

Часто Denuvo собирает константу, считывая кажущееся случайным количество байт из раздела .VM. Затем эта константа используется для выполнения вычислений, которые поломаются в случае изменения константы. Например, рассмотрим следующий обработчик:

mov edx, dword ptr ds:[rax+0x03] ; считывание индекса следующего обработчика

movsx r13, word ptr ds:[0x00000001467FEE8D] ; здесь Denuvo считывает "случайное" слово из кода .VM

add r13, 0xFFFFFFFFFFFFDBAB ; расшифровка слова

add rax,r13 ; обновление vip

mov qword ptr ds:[rcx+418],rax ; сохранение vip

lea rax,qword ptr ds:[0x14E2FD140] ; копирование адреса таблицы обработчика в rax

; вычисление следующего обработчика и переход к нему
mov r12,qword ptr ds:[rax+rdx*8]
xchg qword ptr ss:[rsp],r12
ret

Если пользователь установит точку останова/перехватчик или попробует изменить слово, хранящееся по адресу 0x00000001467FEE8D (если я правильно помню, это CPUID), то VM, скорее всего, в результате исполнит случайный обработчик, из-за чего получившееся значение в R13 будет отличаться, вызывая неопределённое поведение.

Разное

Виртуальная машина (VM)

О виртуальной машине я знаю не особо много. Кажется, существуют разные их виды. Иногда она кажется простой (например, таблица обработчиков, отсутствие динамического ключа и так далее). Возможно, стоит рассмотреть её в будущем посте?

Битовый вектор

Наверно, больше всего мне нравится в Denuvo то, что, в отличие от традиционных VM (например, VMP и Themida), Denuvo не хранит значения в сплошной памяти. Её разработчики решили хранить, например, значения регистров, разбросав их байты/биты повсюду. Это крайне усложняет анализ, особенно когда с этими значениями выполняются операции. Вероятно, вот лучший пример, который я могу привести о побитовой записи Denuvo значения:

; извлечение бита 0x7 EDI
mov eax, edi
shr rax, 0x7
and eax, 0x1
mov qword ptr ss:[rsp+0x48], rax

; извлечение бита 0x8 EDI
mov eax, edi
shr rax, 0x8
and eax, 0x1
mov qword ptr ss:[rsp+0xB0], rax

; Извлечение бита 0x9 EDI
mov eax, edi
shr rax, 0x9
and eax, 0x1
mov qword ptr ss:[rsp+0x40], rax

; извлечение бита 0xC EDI
mov eax, edi
shr rax, 0xC
and eax, 0x1
mov qword ptr ss:[rsp+0xB8], rax
...

Случайность

Случайность — краеугольный камень защиты. Без неё проверки патчинга были бы крайне тривиальны. В отличие от других схем защиты, Denuvo не использует никакие API и команду RDRAND x86. Вместо этого Denuvo применяет значения из нативных регистров. Это гениальное решение, ведь входные данные, по сути, гарантированно будут меняться, будь то из-за релокации базы образа или из-за того, что персонаж игрока потерял здоровье.

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

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

if (VCTX[0] % 9 == 0) // VCTX -> VM Context
{
	CPUID_A(); // обработчик cpuid
}
else
{
	CPUID_B(); // обработчик cpuid
}

В этом примере CPUID_A и CPUID_B семантически идентичны. Нет никакой разницы, какой из них вы решите исполнить.

Mixed-Boolean-Arithmetic (MBA)

Mixed-Boolean Arithmetic (MBA) — это способ трансляции выражений в сложное для понимания и анализа представление с сохранением семантики исходного выражения. Он заменяет выражение арифметическими и булевыми операциями (то есть ^, |, +, -, ~, &).

Примеры:

  1. x + y = (x & y) + (x | y)

  2. x | y = x + y + 1 + (~x | ~y)

  3. x - y = (x ^ -y) + 2*(x & -y) = ((x ^ -y) & 2*(x & -y)) + ((x ^ -y) | 2*(x & -y)) = ((x ^ -y) & 2*(x & -y)) + ((x ^ -y) + 2*(x & -y) + 1 + (~(x ^ -y) | ~2*(x & -y)))

Примечание: эквивалентность этих выражений можно доказать инструментом автоматического доказательства теорем, например, Z3.

Если приглядеться, то можно понять, что для получения (3) мы просто многократно подставляем тождества x | y и x + y в x - y. Это распространённый и простой способ генерации MBA-выражений. Другие, возможно, более «совершенные» способы генерации MBA выходят за рамки нашей статьи; например, в них используются линейная и абстрактная алгебра. Но если вам любопытно, изучите следующие источники:

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

В Denuvo они активно применяются для MBA. В частности, в них используются результаты zhou2007:

(zhou2007, теорема 2) Пусть e — побитовое выражение, тогда e имеет нетривиальное линейное выражение MBA.

(zhou2007, доказательство 1) Любую операцию в BA-алгебре (можно считать их булевыми и арифметическими операторами, например, ^, |, +, -, ~, >, <, &, …) можно представить как полиномиальное MBA-выражение высокой степени.

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

Из этих результатов, по сути, следует, что можно переписать большинство команд x86 в виде MBA-выражений. Например, возьмём следующую команду x86:

mov rax, rbx

Перепишем её:

; y = ((~x)&(x))|y
push rax
not rax
and qword ptr [rsp], rax
pop rax
or rbx

Согласно zhou2007 (теорема 2), мы можем применить к представленным в переписанном виде командам BA-алгебры дальнейшие MBA-преобразования, ещё больше усложнив выражение. Этот пример был намеренно упрощён; вот сырой код VM Denuvo:

mov r8b,byte ptr ds:[rcx+2BA]
and r11d,r8d
mov al,byte ptr ds:[rcx+65]
shld r11d,r8d,18
lea rbx,qword ptr ds:[rcx+2BD]
ror r8d,8
or r8d,r11d
lea rbx,qword ptr ds:[rbx+564C320C]
shl eax,18
mov dl,byte ptr ds:[rbx-564C320C]
ror eax,18
and eax,FF
rcr r8d,18
mov r9b,byte ptr ds:[rcx+14A]
ror edx,8
and r8d,FF
sar edx,18
sub ebx,ebx
mov r10d,FF
or ebx,r9d
shr r9d,8
and edx,FF
and ebx,r10d
rcl ebx,18
sub r10d,r10d
sub r11d,r11d
xor r9d,ebx
mov r10b,byte ptr ds:[rcx+AD]
lea rbx,qword ptr ds:[rcx-5DF0648A]
shr r9d,18
mov r11b,byte ptr ds:[rcx+39D]
push rsi
not rsi
or rsi,FFFFFFFFFFFFFF00
and qword ptr ss:[rsp],rsi
pop rsi
or sil,byte ptr ds:[rcx+C7]
push rdi
not rdi
and byte ptr ss:[rsp],dil
pop rdi
rol esi,18
or dil,byte ptr ds:[rbx+5DF0669F]
mov dil,dil
mov rbx,FF
shl edi,18
shr edi,18
shr esi,18
and rdi,rbx
pushfq 
push r15
mov r15,FFFFFFFFFFFF0000
shl r15,20
add r15,0
mov rbx,r15
pop r15
popfq 
push rax

Здесь уже не всё так просто. Среди прочего, к способам применения MBA относятся Software Watermarking и Constant Hiding, которые можно найти в zhou2007 (Section 4, Protection Methods). Но я не знаю, использовались ли они в Denuvo.

Дешифруемый + повторно шифруемый на лету CPUID

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

У VM есть общие обработчики с разными потоками исполнения. Что, если два потока попытаются одновременно исполнить один и тот же зашифрованный CPUID? Чтобы потоки не вызвали неопределённое поведение, требуется спин-блокировка. Однако спин-блокировки должны быть быстрыми, потому что в противном случае мы будем исполнять уже обфусцированный код, и делать это уже в цикле. Для решения этой проблемы разработчики Denuvo полностью убрали основную логику спин-блокировки из обфускации. Следовательно, взломщики могут выполнять сканирование паттернов в поисках спин-блокировок, что, в свою очередь, сообщит им, где находится зашифрованный CPUID (более или менее). Как Denuvo решила эту проблему? Зашифровав спин-блокировку, для чего требуется ещё одна спин-блокировка.

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

Паттерн спин-блокировок Denuvo:

push r0
push r1
mov r1, 0x1
xor r0, r0
spinlock_entry:
lock cmpxchg dword ptr ds:[SPINLOCK_BOOL], r1 ; SPINLOCK_BOOL - это байт переключения
je spinlock_exit
pause
jmp spinlock_entry
spinlock_exit:
pop r1
pop r0
... ; рано или поздно выполнит jmp к дешированному коду

Защита от перехвата на основе исключений

Атаки на ранние версии Denuvo в основном выполнялись патчингом всех проверок информации об оборудовании, благодаря чему возвращалась правильная информация, требуемая для дальнейшего вычисления правильной константы. Часто использовался способ перехвата команд CPUID и SYSCALL через хук на основе исключения. Однако при помощи Windows API можно легко зарегистрировать vector exception handler. Основной подход заключался в том, чтобы заменять каждую команду CPUID и SYSCALL на команду UD2 для запуска и INVALID_OPCODE_EXCEPTION с перехватом KiUserExceptionDispatcher для загрузки в нужные регистры нужной информации об оборудовании.

Этот подход хорошо работал, потому что CPUID и SYSCALL имеют двухбайтную длину, а значит, для их перехвата достаточно пропатчить один байт. Однако Denuvo реализовала гениальный патч. Перед исполнением обработчика CPUID Denuvo записывает важные значения в верхнюю часть «неиспользуемого» пространства стека. В дальнейшем она извлекает это значение, чтобы выполнять важные вычисления, что в противном случае вызовет неопределённое поведение. Это позволило защититься от всех способов перехвата на основе исключений, потому что чаще всего при срабатывании исключения Windows записывает EXCEPTION_RECORD в верхнюю часть неиспользуемого пространства стека. Наверно, вы уже поняли, к чему всё идёт. Теперь в случае перехвата CPUID при помощи исключения это важное значение переписывается EXCEPTION_RECORD, в дальнейшем вызывая неопределённое поведение. Наверно, эту защиту можно обойти, если подключить к процессу отладчики и задать определённые флаги при обработке исключений, но из-за случайности этот способ патчинга всех проверок оборудования всё равно довольно неудобен.

Взлом

Патчинг проверок ID оборудования

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

Патчинг дешифрования констант

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

Полное восстановление binary.exe

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

Гипервизор

Чуть более продвинутое решение заключается в использовании гипервизора для спуфинга всей необходимой информации об оборудовании. Разумеется, это проще сказать, чем сделать. Однако и AMD, и Intel поддерживают возможность перехвата команд наподобие CPUID и XGETBV, а перехват SYSCALL с уровня гипервизора реализовать тоже не очень сложно. Думаю, единственным сложным моментом будет патчинг проверок NTDLL и KUSER так, чтобы не поломать все остальные приложения на компьютере. На самом деле, я удивлён, что до сих пор не существует решения на основе гипервизора peer2peer (p2p).

В заключение

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

Благодарности

Благодарю за помощь всех этих замечательных людей:

  • Sp********

  • Ma****

  • Mk***

  • Az****

Комментарии (1)


  1. AlexSandrov
    16.04.2026 08:18

    Denuvo основана на VMProtect, о чём можно почитать интересную историю тут: https://rsdn.org/forum/shareware/6733631.flat.1