Análise da Tecnologia Denuvo
Aviso: Este artigo é para fins educacionais. Denuvo é considerado um dos sistemas de gerenciamento de direitos digitais (DRM) mais bem-sucedidos e, portanto, de grande interesse. Este artigo apresenta uma grande quantidade de minhas notas pessoais e correspondências com outros engenheiros reversos (ver seção 'Agradecimentos'), contendo informações sobre as versões mais recentes do Denuvo; muito disso não foi publicado antes.
Não tenho intenção de causar nenhum dano à Irdeto, portanto, algumas informações foram removidas do artigo.
Denuvo
Denuvo é um sistema de proteção anti-tamper e gerenciamento de direitos digitais (DRM). É usado principalmente para proteger contra pirataria e engenharia reversa de mídia digital, como videogames. Ao contrário dos sistemas DRM tradicionais, o Denuvo emprega uma ampla gama de técnicas e verificações exclusivas para validar a integridade do jogo e o licenciamento do usuário.
Princípio Geral de Operação
A ideia por trás do Denuvo não é nova. Pelas razões que logo ficarão claras, pode ser descrito como um DRM semi-online. O esquema geral é o seguinte:
- O usuário executa
program.exepela primeira vez. - Antes de executar o código do jogo, o Denuvo coleta informações para identificar o hardware do sistema atual e as prepara para envio pela Internet.
- Então,
program.exeenvia essas informações de hardware para o servidor Denuvo. Obviamente, não sabemos o que acontece no servidor, mas provavelmente funções matemáticas reversíveis são aplicadas para combinar "constantes roubadas" (stolen constants) (mais sobre isso abaixo) com as informações de hardware transmitidas porprogram.exe. Em seguida, o servidor envia essas informações combinadas, que chamarei de "arquivo de licença", de volta paraprogram.exe. - Após receber o arquivo de licença,
program.execria uma cópia local, que pode ser acessada em execuções subsequentes, eliminando a necessidade de solicitações online adicionais (daí o "semi-online" mencionado acima). program.exeé redirecionado para o ponto de entrada original (OEP) e começa a executar o código do jogo. Durante a execução,program.execoleta informações sobre o hardware e tenta descriptografar as constantes roubadas do arquivo de licença. Essas constantes já descriptografadas são então usadas para executar os "comandos originais do jogo".
Em essência, o jogo realiza verificações de integridade do usuário. Se as informações de hardware coletadas durante a execução não forem iguais às usadas para criar o arquivo de licença no servidor Denuvo, uma constante roubada incorreta será descriptografada, afetando o jogo (geralmente levando a uma falha).
Explicação Mais Técnica
Nesta seção, explicaremos cada mecanismo de proteção e verificação de integridade do usuário em mais detalhes. Lembre-se de que o Denuvo envolve muito mais do que o descrito aqui.
Retornando ao Princípio Geral de Operação
Arquivo de Licença
Quando o Denuvo é adicionado a um arquivo binário pela primeira vez, funções específicas são selecionadas no jogo para serem "protegidas". Isso significa que a função em si será executada dentro de uma máquina virtual (VM), e partes selecionadas de comandos específicos serão completamente removidas do arquivo binário. O arquivo de licença é simplesmente todos esses bytes removidos, unidos e combinados com a identificação do hardware do usuário por meio de funções matemáticas reversíveis. É importante ressaltar que todas as operações aplicadas são reversíveis; caso contrário, o cliente não conseguiria descriptografar os dados e obter a constante original.
DWORD de Licença
Como há muitos comandos roubados, antes de transferir a execução para o ponto de entrada OEP, o Denuvo grava partes selecionadas do arquivo de licença em DWORDs espalhados pela seção .vm (.vm é uma seção PE que contém o código da VM). Cada DWORD, que chamarei de "DWORD de licença", é essencialmente um comando extraído do arquivo binário e combinado com informações de identificação do hardware do usuário.
Exemplo de Constante Criptografada/Comando Removido
Mostrarei com um exemplo concreto como os comandos são "removidos" do arquivo binário. Suponha que temos a seguinte função:
cadd(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
É fácil ver que, após a compilação, algumas partes dos comandos não mudam. Por exemplo:
assemblymov DWORD PTR [rbp-4], edi
Aqui, estamos gravando o conteúdo do registrador de 32 bits EDI em [RBP-4]. Nesse caso, o Denuvo cortará a constante -4 do arquivo binário e a armazenará no servidor. Agora, a única maneira de acessar essa constante, que será necessária para a execução bem-sucedida de add(int, int), é solicitar o arquivo de licença do Denuvo, pois ele conterá o DWORD de licença que contém a constante criptografada -4 (lembrando que o arquivo de licença contém constantes misturadas com a identificação do hardware). Além disso, o Denuvo converte toda a função add(int, int) em bytecode que só pode ser entendido por sua máquina virtual. Este bytecode inclui código que atua como um wrapper em torno da função removida. Este wrapper é responsável pelo seguinte:
- Coletar informações de hardware relevantes durante a execução (informações de hardware específicas que foram misturadas com a constante).
- Ler o DWORD de licença relevante que contém a constante criptografada para esta função específica.
- Executar uma sequência de operações matemáticas usando o DWORD de licença e a identificação do hardware coletada durante a execução para obter o valor
-4. Estas devem ser as operações inversas das executadas pelo servidor. - Executar o comando original com a constante já descriptografada.
Na seção anterior, mencionei que, se as informações de hardware coletadas durante a execução não corresponderem às usadas no servidor Denuvo para criptografar a constante, o ponto (3) provavelmente retornará um resultado diferente de -4, causando um comportamento indefinido.
Verificações de Integridade do Usuário
Abaixo, listarei todos os vetores que o Denuvo usa para determinar a integridade do sistema que executa o arquivo binário protegido. Pela própria natureza dessa proteção, pelo menos um exemplo de cada verificação deve ser enviado ao servidor ao solicitar o arquivo de licença.
Verificações Pré-OEP
Depois de ler as seções anteriores, você pode se perguntar: o que acontece se as informações de identificação do hardware forem alteradas de alguma forma (por exemplo, o Windows é atualizado, uma nova CPU é instalada, etc.)? O Denuvo leva isso em consideração com verificações especiais executadas imediatamente antes de transferir o controle para o OEP. Eles simplesmente descriptografam algumas constantes, mas em vez de aplicar a constante para executar um comando, eles verificam se ela é igual ao valor correto (estas são as únicas verificações que executam tais ações; todas as outras assumem que a constante descriptografada está correta e agem de acordo). Se o resultado não corresponder ao esperado, o Denuvo exclui o arquivo de licença armazenado localmente e solicita um novo do servidor Denuvo; essencialmente, o processo descrito na seção "Princípio Geral de Operação" é repetido.
KUSER_SHARED_DATA
KUSER_SHARED_DATA é uma página de memória somente leitura (4096 bytes) refletida em cada processo em execução em uma máquina Windows. Ele contém informações que os processos podem precisar, como a versão do Windows, o número de build do Windows, o SystemTime e assim por diante. A maior parte das informações contidas nele pode ser usada para identificar a máquina e, portanto, o Denuvo a usa ativamente para seus propósitos.
O Denuvo usa os seguintes campos:
0x026C:ULONG NtMajorVersion0x02E8:ULONG NumberOfPhysicalPages0x02D0:ULONG SuiteMask0x0260:ULONG NtBuildNumber0x0264:NT_PRODUCT_TYPE NtProductType0x0268:BOOLEAN ProductTypeIsValid0x0270:ULONG NtMinorVersion0x0274:BOOLEAN ProcessorFeatures [0x40]0x026A:USHORT NativeProcessorArchitecture0x03C0:ULONG volatile ActiveProcessorCount
Nota: os deslocamentos são especificados para máquinas de 64 bits.
CPUID
O comando CPUID é usado para extrair detalhes sobre o processador. Provavelmente, é a maneira mais popular de coletar informações de hardware usada pelo Denuvo. E, como mostraremos abaixo, os desenvolvedores fizeram um grande esforço para proteger contra a interferência em sua execução.
O Denuvo usa os seguintes parâmetros:
EAX=0x1: Informações do Processador e Bits de RecursoEAX=0x80000001: Informações Estendidas do Processador e Bits de RecursoEAX=0x80000002,0x80000003,0x80000004: String da Marca do Processador
SYSCALL
O comando SYSCALL chama o manipulador de chamadas do sistema operacional com nível de privilégio 0. Pode ser considerado como uma forma de os programas do modo de usuário se comunicarem e solicitarem serviços do kernel.
O Denuvo usa apenas um parâmetro:
0x36:NtQuerySystemInformation
Verificações NTDLL
ntdll.dll é a "interface do kernel do Windows para o modo de usuário". Essencialmente, ele fornece uma API rica em recursos que os aplicativos do modo de usuário podem usar para solicitar que o kernel execute ações em seu nome. O Windows Loader carrega ntdll.dll em praticamente todos os processos do Windows; esta biblioteca geralmente muda com as atualizações do Windows, tornando-a ideal para o Denuvo.
Verificações de Funções NTDLL
Eu não estudei este aspecto tão profundamente quanto merecia. Parece que o Denuvo identifica o usuário com base na localização em ntdll.dll dos bytes de funções específicas e seu endereço virtual relativo (RVA).
Diretório de Dados da Imagem NTDLL
Como mencionado acima, ntdll.dll geralmente muda um pouco a cada atualização/versão do Windows, então faz sentido que o Denuvo examine seu Image Data Directory. Mais especificamente, ele acessa os seguintes campos:
- 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
Bloco de Ambiente do Processo (Process Environment Block, PEB)
O Process Environment Block (PEB) é semelhante ao KUSER_SHARED_DATA no sentido de que ambos contêm informações. No entanto, o PEB contém menos informações "globais" e mais informações "locais". Além disso, cada processo no sistema tem seu próprio PEB exclusivo. Outra diferença importante é que um aplicativo pode sobrescrever livremente os valores no PEB, então não é tão ideal para verificar informações de hardware, mas o Denuvo ainda o usa.
O Denuvo usa os seguintes campos:
0x0118:ULONG OSMajorVersion0x011C:ULONG OSMinorVersion0x012C:ULONG ImageSubsystemMajorVersion0x0130:ULONG ImageSubsystemMinorVersion
Notas: os deslocamentos são especificados para máquinas de 64 bits.
XGETBV
XGETBV lê o extended-control-register (XCR). Não conheço detalhes específicos sobre ele; é um comando muito pequeno e exclusivo em termos de execução que pode ser usado para determinar características sutis da CPU.
GetWindowsDirectoryW
GetWindowsDirectoryW obtém o caminho para a pasta do Windows.
GetVolumeInformationW
GetVolumeInformationW obtém informações sobre o sistema de arquivos e o volume associado a um diretório raiz específico.
GetComputerNameW
GetComputerNameW obtém o nome NetBIOS do computador local.
GetUsernameW
GetUsernameW obtém o nome de usuário associado ao thread atual. Em nosso caso, este será o nome do usuário que está tentando executar o arquivo binário protegido pelo Denuvo.
Verificações de Integridade do Código
Cyclic Redundancy Check (CRC)
CRC VM
Como seria de esperar, o Denuvo executa varreduras de manipuladores importantes (por exemplo, CPUID, SYSCALL e assim por diante) e, provavelmente, de outro código para garantir que não haja interceptação/interferência. Infelizmente, isso é tudo o que posso dizer sobre essas verificações.
Como se fosse uma verificação aleatória .VM
Frequentemente, o Denuvo coleta uma constante lendo um número aparentemente aleatório de bytes da seção .VM. Essa constante é então usada para executar cálculos que falharão se a constante for alterada. Por exemplo, considere o seguinte manipulador:
assemblymov edx, dword ptr ds:[rax+0x03] ; ler o índice do próximo manipulador movsx r13, word ptr ds:[0x00000001467FEE8D] ; aqui o Denuvo lê uma palavra "aleatória" do código .VM add r13, 0xFFFFFFFFFFFFDBAB ; descriptografar a palavra add rax,r13 ; atualizar vip mov qword ptr ds:[rcx+418],rax ; salvar vip lea rax,qword ptr ds:[0x14E2FD140] ; copiar o endereço da tabela de manipuladores para rax ; calcular o próximo manipulador e ir para ele mov r12,qword ptr ds:[rax+rdx*8] xchg qword ptr ss:[rsp],r12 ret
Se um usuário definir um breakpoint/interceptador ou tentar alterar a palavra armazenada no endereço 0x00000001467FEE8D (se me lembro corretamente, é CPUID), a VM provavelmente executará um manipulador aleatório como resultado, fazendo com que o valor resultante em R13 seja diferente, causando comportamento indefinido.
Diversos
Máquina Virtual (VM)
Não sei muito sobre a máquina virtual. Parece que existem diferentes tipos. Às vezes, parece simples (por exemplo, tabela de manipuladores, ausência de chave dinâmica e assim por diante). Talvez valha a pena considerá-la em um post futuro?
Vetor de Bits
Provavelmente, o que mais gosto no Denuvo é que, ao contrário das VMs tradicionais (por exemplo, VMP e Themida), o Denuvo não armazena valores em memória contígua. Seus desenvolvedores decidiram armazenar, por exemplo, valores de registradores, espalhando seus bytes/bits por toda parte. Isso torna a análise extremamente difícil, especialmente quando operações são executadas nesses valores. Provavelmente, este é o melhor exemplo que posso dar de como o Denuvo grava um valor bit a bit:
assembly; extrair o bit 0x7 EDI mov eax, edi shr rax, 0x7 and eax, 0x1 mov qword ptr ss:[rsp+0x48], rax ; extrair o bit 0x8 EDI mov eax, edi shr rax, 0x8 and eax, 0x1 mov qword ptr ss:[rsp+0xB0], rax ; Extrair o bit 0x9 EDI mov eax, edi shr rax, 0x9 and eax, 0x1 mov qword ptr ss:[rsp+0x40], rax ; extrair o bit 0xC EDI mov eax, edi shr rax, 0xC and eax, 0x1 mov qword ptr ss:[rsp+0xB8], rax ...
Aleatoriedade
Aleatoriedade é a pedra angular da proteção. Sem ela, as verificações de patching seriam extremamente triviais. Ao contrário de outros esquemas de proteção, o Denuvo não usa nenhuma API e o comando RDRAND x86. Em vez disso, o Denuvo aplica valores de registradores nativos. Esta é uma solução engenhosa, pois os dados de entrada são essencialmente garantidos para mudar, seja devido à realocação da base da imagem ou porque o personagem do jogador perdeu saúde.
Uma das maneiras usadas pelo Denuvo e, provavelmente, a única, é gerar aleatoriedade com base no valor do registrador nativo do jogo usando aritmética modular. Aqui está um exemplo real de um arquivo executável protegido pelo Denuvo:
Nota: não posso fornecer o código assembly porque é extremamente ofuscado e ilegível, mas esta demonstração em C deve ser suficiente.
cif (VCTX[0] % 9 == 0) // VCTX -> VM Context { CPUID_A(); // manipulador cpuid } else { CPUID_B(); // manipulador cpuid }
Neste exemplo, CPUID_A e CPUID_B são semanticamente idênticos. Não há diferença em qual você decide executar.
Mixed-Boolean-Arithmetic (MBA)
Mixed-Boolean Arithmetic (MBA) é uma forma de traduzir expressões em uma representação difícil de entender e analisar, mantendo a semântica da expressão original. Ele substitui a expressão por operações aritméticas e booleanas (ou seja, ^, |, +, -, ~, &).
Exemplos:
x + y = (x & y) + (x | y)x | y = x + y + 1 + (~x | ~y)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)))
Nota: a equivalência dessas expressões pode ser provada por uma ferramenta de prova automática de teoremas, como Z3.
Se você olhar de perto, pode perceber que para obter (3), simplesmente substituímos repetidamente as identidades x | y e x + y em x - y. Esta é uma maneira comum e simples de gerar expressões MBA. Outras maneiras, possivelmente mais "avançadas", de gerar MBA estão além do escopo deste artigo; por exemplo, eles usam álgebra linear e abstrata. Mas se você estiver curioso, explore as seguintes fontes:
- zhou2007
- SiMBA
- Justus Polzin Blog
- MBA-Blast
Nota: meu artigo fornecerá apenas explicações de alto nível de conceitos e ideias, mas para leitores que desejam deduções matemáticas rigorosas, são fornecidos links para os teoremas mencionados acima.
No Denuvo, eles são ativamente usados para MBA. Em particular, eles usam os resultados de zhou2007:
(zhou2007, Teorema 2)
Seja e uma expressão bit a bit, então e tem uma expressão MBA linear não trivial.
(zhou2007, Prova 1)
Qualquer operação na álgebra BA (você pode considerá-las operadores booleanos e aritméticos, por exemplo, ^, |, +, -, ~, >, <, &, …) pode ser representada como uma expressão MBA polinomial de alto grau.
Nota: novamente, todas as formulações rigorosas são omitidas aqui. Veja os artigos acima para mais detalhes.
Esses resultados essencialmente implicam que a maioria dos comandos x86 podem ser reescritos como expressões MBA. Por exemplo, pegue o seguinte comando x86:
assemblymov rax, rbx
Vamos reescrevê-lo:
assembly; y = ((~x)&(x))|y push rax not rax and qword ptr [rsp], rax pop rax or rbx
De acordo com zhou2007 (Teorema 2), podemos aplicar transformações MBA adicionais aos comandos de álgebra BA reescritos, tornando a expressão ainda mais complexa. Este exemplo foi intencionalmente simplificado; aqui está o código VM Denuvo bruto:
assemblymov 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
Aqui, nem tudo é tão simples. Entre outras coisas, as formas de aplicar MBA incluem Software Watermarking e Constant Hiding, que podem ser encontradas em zhou2007 (Section 4, Protection Methods). Mas não sei se eles foram usados no Denuvo.
CPUID Descriptografável + Recriptografável em Tempo Real
Às vezes, em vez de executar o manipulador CPUID padrão na VM, o Denuvo descriptografa o CPUID na seção VM, o executa e, em seguida, o criptografa rapidamente novamente. Acho que isso é feito para impedir que os hackers correspondam aos padrões de cada comando CPUID, embora isso não fosse particularmente útil para eles. Usar criptografia e descriptografia em tempo real tem consequências curiosas:
- A VM tem manipuladores compartilhados com diferentes threads de execução. E se dois threads tentarem executar o mesmo CPUID criptografado ao mesmo tempo? Para evitar que os threads causem comportamento indefinido, é necessário um spinlock. No entanto, os spinlocks devem ser rápidos, porque, caso contrário, estaremos executando código já ofuscado e fazendo isso já em um loop. Para resolver este problema, os desenvolvedores do Denuvo removeram completamente a lógica principal do spinlock da ofuscação. Consequentemente, os hackers podem executar varreduras de padrões em busca de spinlocks, o que, por sua vez, informará onde está o CPUID criptografado (mais ou menos). Como o Denuvo resolveu este problema? Criptografando o spinlock, o que requer outro spinlock.
Não sei se ele criptografou o spinlock que rastreia o spinlock criptografado que rastreia o comando CPUID criptografado, mas é muito provável que sim.
Padrão de spinlock do Denuvo:
assemblypush r0 push r1 mov r1, 0x1 xor r0, r0 spinlock_entry: lock cmpxchg dword ptr ds:[SPINLOCK_BOOL], r1 ; SPINLOCK_BOOL é um byte de alternância je spinlock_exit pause jmp spinlock_entry spinlock_exit: pop r1 pop r0 ... ; mais cedo ou mais tarde executará jmp para o código descriptografado
Proteção Contra Interceptação Baseada em Exceção
Os ataques às primeiras versões do Denuvo foram realizados principalmente corrigindo todas as verificações de informações de hardware, fazendo com que as informações corretas de hardware necessárias para calcular a constante correta fossem retornadas todas as vezes. Uma forma comum era interceptar os comandos CPUID e SYSCALL por meio de um hook baseado em exceção. No entanto, com a API do Windows, é fácil registrar um vector exception handler. A principal abordagem era substituir cada comando CPUID e SYSCALL pelo comando UD2 para iniciar um INVALID_OPCODE_EXCEPTION com a interceptação de KiUserExceptionDispatcher para carregar as informações de hardware corretas nos registradores necessários.
Esta abordagem funcionou bem porque CPUID e SYSCALL têm dois bytes de comprimento, o que significa que é suficiente corrigir um byte para interceptá-los. No entanto, o Denuvo implementou um patch engenhoso. Antes de executar o manipulador CPUID, o Denuvo grava valores importantes na parte superior do espaço da pilha "não utilizado". Posteriormente, ele extrai este valor para executar cálculos importantes, o que, caso contrário, causaria comportamento indefinido. Isso permitiu a proteção contra todas as formas de interceptação baseadas em exceção, porque, mais frequentemente, quando uma exceção é acionada, o Windows grava um EXCEPTION_RECORD na parte superior do espaço da pilha não utilizado. Provavelmente, você já entendeu para onde tudo isso está indo. Agora, no caso de interceptar um CPUID com uma exceção, este valor importante é sobrescrito pelo EXCEPTION_RECORD, causando posteriormente um comportamento indefinido. Provavelmente, esta proteção pode ser contornada conectando depuradores ao processo e definindo flags específicas ao lidar com exceções, mas devido à aleatoriedade, esta forma de corrigir todas as verificações de hardware ainda é bastante inconveniente.
Hacking
Patching de Verificações de ID de Hardware
Ao tentar contornar esta proteção, a primeira coisa que você deve tentar é corrigir manualmente cada verificação de identificação de hardware, fazendo com que as informações corretas de hardware sejam retornadas todas as vezes (por "correto" aqui, quero dizer que o hardware descriptografará a constante correta). No entanto, como afirmado nas seções anteriores, isso será extremamente difícil de fazer. O hacker terá que lidar não apenas com CRCs complexos, mas também com aleatoriedade, o que tornará praticamente impossível para uma pessoa descobrir todas as verificações, muito menos corrigi-las.
Patching de Descriptografia de Constantes
Semelhante ao patching de todas as verificações de informações de hardware, você pode direcionar as sub-rotinas de descriptografia de constantes e retornar a constante correta em vez da descriptografada incorretamente devido a informações de hardware incorretas. Além disso, esta solução é muito mais razoável do que corrigir todas as verificações de informações de hardware, pois não há CRC e aleatoriedade nessas sub-rotinas ainda. No entanto, encontrar uma única descriptografia de constante em um rastreamento de cerca de dez milhões de comandos x86 não é tão fácil.
Reconstrução Completa de binary.exe
Só pelo nome desta solução, você pode perceber o quão difícil será fazê-la. Isso exigirá a correção/desvirtualização de, provavelmente, milhares de comandos. No entanto, conheço um exemplo de reconstrução completa de um arquivo binário protegido pelo Denuvo (provavelmente, este é o melhor hack que conheço).
Hipervisor
Uma solução um pouco mais avançada é usar um hipervisor para falsificar todas as informações de hardware necessárias. Obviamente, isso é mais fácil dizer do que fazer. No entanto, tanto a AMD quanto a Intel oferecem suporte à capacidade de interceptar comandos como CPUID e XGETBV, e interceptar SYSCALL do nível do hipervisor também não é muito difícil de implementar. Acho que a única parte difícil seria corrigir as verificações NTDLL e KUSER para não quebrar todos os outros aplicativos no computador. Na verdade, estou surpreso que ainda não exista uma solução baseada em hipervisor peer2peer (p2p).
Em Conclusão
Pode-se dizer com segurança que o Denuvo faz um trabalho incrivelmente bom. Ele demonstrou repetidamente sua capacidade de proteger jogos por meses e, às vezes, anos. Não está claro se a razão é a preguiça ou a incompetência dos hackers, mas o Denuvo definitivamente sai vitorioso. Parece-me que esta tecnologia estará conosco por muito tempo.
Agradecimentos
Obrigado pela ajuda de todas essas pessoas maravilhosas:
Sp********
Ma****
Mk***
Az****





