PatientZero 16 minutos atrás Análise da Quebra do Denuvo com Virtualização Médio 14 min 1.3K Segurança da Informação * Engenharia Reversa * Depuração * Tradução Autor original: Rose Recentemente, surgiram muitos lançamentos baseados em hipervisor com o objetivo de contornar o DRM Denuvo, então decidi escrever um artigo sobre o que essa abordagem envolve e como ela funciona tecnicamente. Observações importantes : este artigo é destinado exclusivamente a fins educacionais. Eu NÃO justifico e NÃO apoio a análise, contorno e quebra de jogos (seja com proteção Denuvo ou alguma outra) e NÃO apoio a pirataria de forma alguma. Por favor, apoiem os desenvolvedores que trabalharam duro nesses jogos e medidas de proteção (nós te amamos, Blaukovitch). O objetivo deste artigo é analisar esse método de contorno e como se proteger dele do lado da Denuvo. Por que esse método foi escolhido Por que esse método foi escolhido? A principal razão para isso é que é uma maneira fácil de executar um jogo protegido sem a necessidade de engenharia reversa completa contra a própria proteção. Ele pode ser lançado rapidamente, e isso é importante, porque um lançamento antecipado pode afetar as vendas na primeira semana do jogo. Mas isso tem um preço em termos de baixo desempenho, instabilidade, problemas de segurança e muitas outras dificuldades. Introdução Neste artigo, analisaremos o lançamento do contorno da DLL principal do recentemente lançado Resident Evil: Requiem de cs.rin.ru e detalharemos como hipervisores como o HyperDBG podem ser usados para falsificar valores de hardware, efetivamente contornando o DRM Denuvo. Requisitos e limitações Para que esse método funcione, geralmente é necessário enfraquecer as medidas básicas de proteção do kernel do Windows para poder carregar um driver de kernel não assinado ou personalizado. Os seguintes requisitos são mais frequentemente encontrados em lançamentos públicos: Desativação da Driver Signature Enforcement (DSE) Desativação do PatchGuard Desativação do Windows Defender Modificação do kernel do Windows Ou seja, basicamente desativamos o Windows para executar o jogo Para tornar todo o seu sistema o mais desprotegido possível, você pode usar o EfiGuard [17]! Análise da DLL do hipervisor Resident Evil: Requiem 0) Carregamento inicial No exemplo abaixo, amd_ags_x64.dll [4] é substituído por uma proxy-dll corrigida, fornecida com o lançamento. Então a proxy-dll executa o lançamento: A DLL principal de contorno A DLL original [4] AMD GPU Services SDK, renomeada para amd_ags_x64.org
- Seleção do backend do hipervisor (Intel/AMD) Depois que a DLL principal de contorno fica disponível, ela seleciona o backend do hipervisor dependendo do fabricante da CPU. A string do fornecedor da CPUID é lida e o backend é selecionado com base no ID do fabricante. Em nosso exemplo: "AuthenticAMD" → SimpleSvm.sys [1] "GenuineIntel" → hyperkd.sys [2]
- Criação do serviço e execução do driver Após a seleção do driver, um serviço é criado e executado para esse driver. Como a virtualização de hardware geralmente é executada de forma exclusiva, muitas vezes há lógica no código que verifica a ausência de outros hipervisores ocupando VT-x ou AMD-V. Se houver um hipervisor, ele pode ser interrompido para que o driver selecionado possa ser inicializado. Capturas de tela dessa lógica:
- Resolução dos números de chamadas do sistema de ntdll De ntdll.dll os números das chamadas do sistema são retirados, que são então usados em outras funções.
- Interceptação IAT A interceptação IAT é usada para redirecionar as importações selecionadas para o arquivo executável do jogo. As seguintes importações são interceptadas: ntdll.dll , kernel32.dll , kernelbase.dll , user32.dll Após o patch do IAT, as chamadas que normalmente iriam diretamente para esses módulos são passadas pelos manipuladores da DLL de contorno de proteção.
- Trabalhando com o arquivo de licença/tokens Dentro da DLL principal de contorno, existem dois tokens pré-gerados que são atribuídos dependendo da arquitetura da CPU do host e do hipervisor correspondente (como mostrado abaixo). Esses tokens são gravados no arquivo .bin na primeira vez que são executados e, em seguida, são usados pelo hipervisor como tokens Denuvo. Pequenas adições: Para carregar o jogo e emular os serviços da API Steam, foi usado um fork do GBE Detanup01 [10]. Para ocultar ainda mais o hipervisor, o HyperEvade foi usado (falaremos sobre isso um pouco mais tarde). Na amostra que estudei, a string da marca da CPU tem o valor DenuvOWO CPU @ 1337 GHz. (Eu o trouxe apenas para mostrar que algumas informações não precisam ser reais, e o ambiente pode ser alterado para que valores especiais sejam usados no hipervisor.) Vamos aos detalhes! HyperDBG é um depurador muito versátil, então não vou analisar todas as suas partes, então, em primeiro lugar, vou me concentrar no tema principal da falsificação de hardware e como ele pode ser implementado no contexto do Denuvo. Todos os comandos aqui podem ser usados para teste no sistema de script do HyperDBG. Mais tarde, você pode criar uma implementação especial para otimizar esse processo para o lançamento de um contorno real. Exemplos: https://docs.hyperdbg.org/commands/scripting-language/debugger-script https://github.com/HyperDbg/scripts Falsificação de hardware Para permitir a saída do CPUID, o HyperDBG usa a Virtual Machine Control Structure (VMCS). Isso garante que, quando o comando CPUID é executado, o processador suspenda o trabalho e inicie a saída da VM, transferindo o controle para o hipervisor. Essencialmente, isso permite que você execute a falsificação de hardware em níveis muito baixos do sistema. Como isso acontece dentro do HyperDBG? Como o HyperDBG não tem um banco de dados de todas as CPUs e seus valores de hardware (e, francamente, não é necessário), precisamos encontrar os valores de hardware para uma CPU específica e passá-los para o HyperDBG para que ele faça seu trabalho. Para isso, pessoalmente, uso coreinfo64.exe das ferramentas do Windows Sysinternals (que podem ser baixadas de: live.sysinternals.com ). Os mesmos valores podem ser encontrados na página de produtos da Intel ou na página do validador da CPU-Z [12][13]. Então você pode usar o comando !cpuid [5] e outros comandos para falsificar diretamente os valores da CPU! Informações detalhadas sobre o CPUID podem ser encontradas aqui: http://www.flounder.com/cpuid_explorer2.htm Falsificação de valores básicos do CPUID [8] Neste exemplo, usarei um i9-11900K. Sua assinatura padrão geralmente é 0x000A0671 , então podemos simplesmente passar o comando HyperDBG para reescrever o CPUID para que ele corresponda à nossa CPU. Aqui, atribuímos ao CPUID o valor 0x000A0671 e zeramos o bit Hypervisor Present para que o HyperDBG não seja detectado. Depois de terminar com a falsificação do CPUID, vamos para a string Brand. É igual a 11th Gen Intel(R) Core(TM) i9-11900K @ 3.50GHz . A string inteira tem 48 bytes de comprimento, então você precisa dividi-la em três segmentos de 16 bytes. (Assim como mostrado no lançamento do contorno.) Observação : Acho que você pode alterar esse valor para qualquer outro, mas não tenho certeza disso. Depois disso, vamos falsificar os valores em folhas individuais para que eles correspondam exatamente à nossa CPU [14]. Estude a página sobre CPUID na Wikipedia, que lista todos os valores das folhas cpuid: https://en.wikipedia.org/wiki/CPUID Falsificação de sinalizadores de recursos adicionais (folha 0x7) : Para que a CPU corresponda perfeitamente, você precisa falsificar muito mais valores; não podemos correr riscos durante as verificações, então prestaremos atenção a todos os valores que descrevem a CPU. Vamos começar com a falsificação de sinalizadores de recursos adicionais [6]. Estou usando a CPU i9-11900K novamente. Esta folha (0x7 / 0) é usada para verificar se a CPU suporta determinados recursos de segurança ou desempenho. Se você tiver um processador mais antigo e esses sinalizadores forem diferentes, isso pode causar alguns problemas. Falsificação de parâmetros de cache (folha 0x4) [7]: O i9-11900K tem 16 MB de cache, vamos falsificar esse valor para que ele corresponda à nossa CPU. Para fazer isso, você precisa falsificar os valores em todas as subfolhas da folha 0x4 e todas elas devem corresponder. Agora você pode seguir em frente. Falsificação de topologia (folhas 0xB e 0x1F) [9] Como a Enumeração de Topologia pode ser usada para determinar os processadores lógicos e núcleos existentes, esses valores também precisam ser falsificados para corresponder à nossa CPU. Com isso, concluímos a falsificação dos valores da CPU. Falsificação de números de unidades e discos Depois de terminar com a falsificação dos valores da CPU, vamos para o restante do hardware, começando com os números das unidades e discos. Para fazer isso, usaremos o comando !syscall , que permite falsificar o identificador da unidade que o jogo/DRM vê. Falsificaremos a chamada do sistema NtDeviceIoControlFile , que é normalmente usado pelo código do modo de usuário para passar códigos de controle de entrada/saída (IOCTL) para os drivers da unidade. Os IOCTLs específicos interceptados dependem das informações que o código de destino está procurando. Observação : IOCTL_STORAGE_GET_DEVICE_NUMBER retorna STORAGE_DEVICE_NUMBER (tipo + número do dispositivo + partição), mas não contém strings de número de série/modelo. As informações sobre o número de série/modelo geralmente são solicitadas por meio de outros IOCTLs de dispositivos de armazenamento (por exemplo, por meio de IOCTL_STORAGE_QUERY_PROPERTY / StorageDeviceProperty ), portanto, se nosso objetivo é falsificar o número de série, geralmente precisamos interceptar essas solicitações. Falsificação do número de compilação do Windows Agora, vamos falsificar o número de compilação do Windows - como de costume, ele deve corresponder ao valor que foi usado para gerar o token Denuvo. Aqui, usarei o comando !epthook para falsificar a leitura NtBuildNumber de KUSER_SHARED_DATA (ou seja, farei com que quem solicita o valor veja o que precisa), em vez de contar com uma simples solução de patch da estrutura na memória. Observação : dependendo da versão do Windows (10/11)/métodos de proteção (e coisas como VBS/VTL1), partes de KUSER_SHARED_DATA podem ser protegidas contra gravação de qualquer maneira, portanto, geralmente é mais seguro falsificar o que o código de destino lê aqui. Além disso, KUSER_SHARED_DATA contém muitos outros valores verificáveis, e se eu for listá-los todos, o artigo se transformará em um guia de 20 páginas sobre os detalhes de KUSER_SHARED_DATA . Vou lançar outro artigo onde Marius e eu analisaremos todas as verificações novas e antigas do Denuvo e como elas, possivelmente , podem ser contornadas. Estude nosso artigo, onde falamos sobre as diferenças KUSER_SHARED_DATA no Windows 10 e 11: https://connormcgarr.github.io/kuser-shared-data-changes-win-11/ Você também pode dar uma olhada no nosso plugin HyperHide, que também aborda a falsificação de KUSER_SHARED_DATA Windows: https://github.com/Air14/HyperHide?tab=readme-ov-file#5-kusershareddata Falsificação do ID do fabricante da GPU Ao falsificar a GPU, usarei o comando !epthook para alterar vários valores. Neste exemplo, falsificarei a GPU, fazendo-a passar por uma AMD Radeon RX 6800 XT. (Você pode aprender mais sobre o PCIe Configuration Space na documentação da AMD: https://docs.amd.com/r/en-US/pg343-pcie-versal/Configuration-Space .) Isso requer a falsificação de cinco valores: Vendor ID , Device ID , Revision ID , Subsystem Vendor ID e Subsystem ID . Mascaramento aprimorado O HyperDBG tem uma opção Transparent Mode (operações RDTSC relacionadas e outros truques...), que pode ser ativada usando o comando !hide . Isso pode ajudar a reduzir o sinal de algumas verificações baseadas em tempo, tentando ocultar a sobrecarga resultante da saída da VM. Observação : este não é um interruptor mágico de ocultação absoluta, e seu comportamento pode variar, então você não deve presumir que cada verificação de tempo será automaticamente contornada apenas por ter o modo de transparência ativado. Você também pode usar o HyperEvade [11], mas não o consideraremos aqui. Observação importante O Denuvo verifica muitos outros valores em KUSER_SHARED_DATA e WinAPI , e também executa muitas outras verificações antes e depois do OEP (Original Entry Point, ponto de entrada original). Se você quiser encontrá-los, prepare-se para analisar a proteção como uma pessoa normal, em vez de tentar procurar "vulnerabilidades" para contorná-las. No entanto, gostaria de dizer que, dessa forma, você pode contornar todas as verificações DRM Denuvo (em fevereiro de 2026). SimpleSVM O SimpleSVM é usado como um hipervisor para CPUs AMD. Eu me perguntei se deveria escrever sobre isso aqui, mas decidi mencionar brevemente. Em comparação com o HyperDBG, o SimpleSVM ainda é bastante experimental. Portanto, seu código-fonte precisa ser modificado para implementar algumas das falsificações mencionadas acima. Além disso, quase não há documentação sobre ele, então você terá que se envolver na boa e velha análise do código-fonte para entender a maior parte do que o hipervisor faz. (Felizmente, o desenvolvedor do SimpleSVM, tandasat, adicionou comentários à parte principal do código explicando como ele funciona). Não entrarei em detalhes sobre a implementação de cada falsificação, mas gostaria de mencionar que existem muitas diferenças entre Intel e AMD, o que pode tornar os hipervisores AMD mais reconhecíveis. Além disso, há MUITOS hipervisores disponíveis publicamente no Github; você pode estudar nossos hipervisores com Marius como base: https://github.com/Nitr0-G/SVM-Hypervisor https://github.com/Nitr0-G/VMX-Hypervisor Nós os usamos para muitos truques legais, e eles podem ser uma base muito boa se você quiser estudar hipervisores e, talvez, criar o seu próprio. Conclusão Depois de terminar com tudo isso, você pode prosseguir para gerar um token Denuvo correspondente à sua CPU (se você tiver uma CPU), simplesmente gerar um novo token Denuvo para esses valores específicos na VM ou usar algum tipo de hipervisor (descubra por si mesmo, existem um milhão de soluções diferentes aqui). No entanto, eu recomendaria gerar o token dentro do ambiente com esses valores e, em seguida, usar os mesmos valores do ambiente para evitar problemas de contorno de proteção. Detecção de tentativas de contorno usando um hipervisor Agora, vamos para a parte do artigo que o Denuvo mais vai gostar: as várias técnicas para detectar tentativas de contorno usando um hipervisor e como corrigir essas técnicas. Francamente, não há muito que você possa fazer aqui no nível do modo de usuário (pelo menos, do que posso falar publicamente), então, para adicionar a maioria dessas verificações, você precisa implementar um driver especializado. Nesta seção, falarei sobre maneiras comuns de detectar hipervisores e, um pouco mais abaixo, explicarei por que alguns deles não funcionarão no caso do Denuvo. Detecção de interceptação EPT Várias técnicas de detecção de interceptação EPT podem ser usadas para reconhecer ganchos no nível EPT [15]. Analisaremos a falsificação de valores de GPU no HyperDBG, porque a interceptação EPT é usada neste caso. As técnicas descritas aqui são revisadas e documentadas por Momo5502: https://momo5502.com/posts/2022-05-02-detecting-hypervisor-assisted-hooking/ Eles incluem verificação de tempo, verificação de thread e verificação de gravação, para os quais Momo forneceu o código-fonte [16]. Não vou repetir o que está escrito em seu artigo, então leia-o! [15] Observação : a verificação de tempo do artigo de Momo pode ser contornada usando o comando !hide on , que falsifica RDTSC por padrão (mas às vezes essa solução pode ser instável). DSE forçado Como em muitos outros DRMs (por exemplo, Byfron), o DSE pode ser aplicado à força para impedir que o jogo seja executado no Modo de Teste do Windows. Como os drivers não assinados não podem ser executados, o driver principal hyperkd.sys e SimpleSVM.sys não funcionará. No caso do Byfron, ativar o modo de teste ou desativar o DSE fará com que o jogo quebre e não possa ser executado até que o modo de teste seja desativado. Verificações de tempo As verificações de tempo podem parecer uma boa ideia, especialmente considerando que o hipervisor tem que sair da VM em muitas funções, essa solução não é confiável e pode ser muito barulhenta, o que pode levar a falsos positivos. Com tudo isso em mente, não recomendo verificações de tempo como solução, porque elas podem causar problemas e podem ser falsificadas usando uma implementação de falsificação suficientemente protegida. A situação atual com o Denuvo Existem muitas verificações para detectar hipervisores, mas o problema é que a maioria delas pode ser falsificada de uma forma ou de outra. As verificações aplicadas devem ser muito confiáveis e muito protegidas contra falsificação. A verificação mais óbvia já está implementada na própria DLL de contorno. Aqui, a DLL verifica a assinatura da interface Microsoft Hyper-V usando cpuid e, teoricamente, o Denuvo pode simplesmente começar a verificar cada valor cpuid e usá-lo para gerar tokens, mas isso pode simplesmente se transformar em outro jogo de "gato e rato": o Denuvo adicionará novas verificações, e novos falsificadores aparecerão nos lançamentos do hipervisor, e esta é uma solução de longo prazo completamente não confiável para hipervisores. As verificações de tempo não são confiáveis e, portanto, também não podem ser usadas. A solução que o Denuvo usará deve: Ser confiável. Não ser muito fácil de contornar. Não afetar o desempenho. Não depender de um driver de kernel. Não estragar a jogabilidade para as pessoas que compraram o jogo. Soluções redundantes Uma solução redundante para reconhecer esses hipervisores pode ser uma das duas: Executar todo o jogo inicialmente em um hipervisor, que funcionará como uma VM em cima da virtualização existente no arquivo binário principal; essa abordagem em casos como o Denuvo será completamente ineficiente devido a problemas de desempenho e causará uma forte reação negativa dos usuários. Além disso, nunca será implementado devido a problemas de suporte de hardware e desempenho. (Além disso, este será outro caso do SecuROM.). A implementação do reconhecimento no nível do driver do kernel para todos os tipos possíveis de fins de prevenção de análise, o que contribuirá para a detecção de hipervisores e o aumento da eficiência geral do DRM. (A Denuvo tem um produto anti-trapaça que funciona no nível do kernel, ou seja, ela já teve experiência em desenvolvimento para o kernel, mas isso não significa automaticamente que os componentes do kernel que não exigem vários compromissos serão lançados para o produto DRM.) Além disso, a situação está piorando rapidamente, considerando o suporte ao Proton/Wine e as expectativas gerais dos usuários em relação ao nível de desempenho. Riscos "Mas quais podem ser os riscos aqui?" Você está falando sério? Executar código no nível do kernel em seu computador, escrito por alguém desconhecido da Internet, parece uma boa ideia para você? Se as pessoas que lançam esses "cracks" quiserem muito, elas ofuscarão o driver, justificando isso com "ocultar seus mecanismos do Denuvo" e lançando qualquer coisa em sua composição. Eles já estão oferecendo para desativar todas as configurações de segurança, então não será difícil para eles executar qualquer código no computador do usuário. Então você pode continuar a confiar nesses idiotas, mas não se surpreenda se um dia a ganância os cegar e eles decidirem lançar um "crack" com código adicional para roubar informações. "Mas o mesmo pode ser dito sobre qualquer software da Internet!" A maioria dos softwares da Internet não precisa ser executada com permissões mais altas do que a conta de administrador, e este é o caso aqui. Infelizmente, a situação hoje é diferente do que era no palco do passado, quando os cracks eram destruídos por causa de pequenos problemas. Essas pessoas não se importam com sua reputação, porque qualquer pessoa pode lançar esses cracks, há apenas uma competição em termos de tempo, quem os lançará primeiro. No geral, é muito difícil convencer os participantes da cena de que usar lançamentos de hipervisores é ruim, porque, para ser honesto, eles não se importam. Eles não se importam que seus dados e informações pessoais acabem em algum fórum chinês, se puderem jogar seus jogos e postar sobre como eles "finalmente derrotaram o Denuvo". Vamos resumir Ao iniciar o jogo, uma DLL de contorno especial é carregada para adicionar uma camada de hipervisor, que pode ser usada para falsificar vários valores Denuvo verificados; essa implementação permite que o usuário execute o jogo com um token já gerado, configurado para o ambiente do hipervisor. Em conclusão Do ponto de vista puramente técnico, esta é uma abordagem bastante boa, mas pessoalmente não acho que os lançamentos de cracks/contornos de quaisquer jogos devam conter código que exija acesso no nível do kernel e se prenda a camadas muito baixas, especialmente às custas do desempenho e da segurança questionável. Em vez de perder tempo com isso, estude a proteção e tente analisá-la por conta própria do ponto de vista de um pesquisador. Links e fontes Estude os projetos e tópicos que mencionei no processo de análise. Para entender alguns dos tópicos levantados neste artigo, é necessário conhecer a arquitetura da CPU Intel. Estude os manuais da CPU Intel (em particular, "Intel (R) 64 and IA-32 Architectures Software Developerʼs Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4") (Na verdade, tive que ler metade deste manual para escrever este artigo.): https://cdrdv2.intel.com/v1/dl/getContent/671200 Leia este artigo para entender melhor o HyperDBG, suas várias aplicações e funções: https://scispace.com/pdf/hyperdbg-reinventing-hardware-assisted-debugging-extended-2qr0u9qm.pdf Estude a documentação da AMD sobre o PCIe Configuration Space: https://docs.amd.com/r/en-US/pg343-pcie-versal/Configuration-Space Informações sobre CPUID: https://en.wikipedia.org/wiki/CPUID Estude o fork LLVM BackEngineering, que pode ser usado para desenvolver um driver de kernel: 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 Documentação para !cpuid HyperDBG: https://docs.hyperdbg.org/commands/extension-commands/cpuid A lista de recursos adicionais do CPUID na Wikipedia: https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features A lista de threads/núcleos CPUID e topologia de cache Intel na Wikipedia: https://en.wikipedia.org/wiki/CPUID#EAX=4_and_EAX=Bh:_Intel_Thread/Core_and_Cache_Topology A lista de valores básicos do CPUID na Wikipedia: https://en.wikipedia.org/wiki/CPUID#EAX=0:_Highest_Function_Parameter_and_Manufacturer_ID Intel 64 Architecture Processor Topology Enumeration (Página 14) : https://www.intel.com/content/www/us/en/content-details/775917/intel-64-architecture-processor-topology -enumeration-technical-paper.html Fork GBE Detanup01: https://github.com/Detanup01/gbe_fork Informações sobre HyperEvade : https://fosdem.org/2026/events/attachments/CDPRDX-invisible_hypervisors_debugging_with_hyperdbg/slides/266821/hyperevad_lyhtmgy .pdf Especificações da 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 Especificações da CPU i9-11900K do validador da CPU-Z: https://valid.x86.fr/8n2zgs Descrição detalhada da identificação por CPUID: https://www.felixcloutier.com/x86/cpuid Técnicas de detecção de interceptação EPT por Momo5502: https://github.com/momo5502/ept-hook-detection Repositório Github Momo5502 sobre detecção de interceptação EPT: https://github.com/momo5502/ept-hook-detection https://github.com/Mattiwatti/EfiGuard Agradecimentos Este artigo foi totalmente disponibilizado graças a Rose/Natasha (0x80000003) Muito obrigado: Marius por analisar o Denuvo e ajudar a escrever este artigo Eintim23 por revisar o artigo e sugerir ideias ********* por publicar o método de contorno da proteção Resident Evil: Requiem Momo5502 por analisar o reconhecimento de ganchos EPT SinaKarvandi por pesquisar hipervisores e desenvolver o HyperDBG ( SinaKarvandi - Visão geral ) Tags: denuvo hipervisor hyper-v quebra de jogos Hubs: Segurança da Informação Engenharia Reversa Depuração +7 3 0 128K+ Alcance em 30 dias 2024 Carma @PatientZero Tradutor freelancer Assinar Fluxo Segurança da Informação disponível 24 horas por dia, 7 dias por semana, graças ao apoio dos amigos do Habr Habr Cursos para todos PUBLICIDADE Prática, Hexlet, SkyPro, cursos do autor - reunimos todos e pedimos descontos. Resta escolher! Ir Ir para o fluxo Segurança da Informação Comentar Melhor do dia Semelhante Mostrar o melhor de todos os tempos





