Chave Perdida: Recuperando Código de Registro com Ataque de Texto Conhecido
Este artigo explora a recuperação de um código de registro de um aplicativo Delphi antigo. Analisaremos o algoritmo de criptografia, o gerador de números pseudoaleatórios e aplicaremos um ataque de texto conhecido para extrair o código sem força bruta ou patching.
MundiX News·19 de junho de 2026·3 min de leitura·👁 1 views
Ao investigar um aplicativo antigo desenvolvido em Delphi, nosso objetivo é desvendar o algoritmo de criptografia, analisar o funcionamento do gerador de números pseudoaleatórios e, finalmente, aplicar um ataque baseado em texto conhecido para extrair o código de registro correto. Essa abordagem nos permitirá obter o código sem a necessidade de tentativas de força bruta ou modificações diretas no aplicativo (patching).
Essencialmente, este artigo aborda um cenário incomum de implementação de proteção. Em vez de depender de uma simples comparação de número de série, a proteção se baseia na descriptografia de dados utilizando um código de registro. Um hacker experiente sabe que uma proteção de aplicativo robusta não deve ser quebrada com um simples patch de um byte. Independentemente do quão ofuscado ou virtualizado o código possa ser, se a proteção de um programa se resume à verificação de uma ou algumas condições específicas, encontrá-las e corrigi-las é apenas uma questão de tempo, habilidade do hacker e das ferramentas disponíveis.
A melhor estratégia para dificultar a vida de um engenheiro reverso é ofuscar o código ao máximo, adicionando verificações de integridade e outras funcionalidades que tornem a busca e o contorno dessas proteções mais trabalhosos e demorados do que o lucro potencial obtido com a quebra da proteção. Essa é a clássica guerra de escudo e espada, mas é uma espada de dois gumes. Contrariando o ditado popular "quebrar não é construir", um hacker, teoricamente, investe um esforço consideravelmente maior na engenharia reversa do que o desenvolvedor na proteção do aplicativo.
No entanto, existem casos em que a proteção é intrinsecamente impossível de ser quebrada. Como você já deve ter adivinhado, isso ocorre quando o hacker não possui uma informação crucial, vital para o funcionamento completo do programa. Exemplos incluem a falta de um módulo específico, um pedaço crítico de código, dados carregados de um servidor remoto ou, em último caso, um módulo que é descriptografado usando uma chave que o hacker não possui. Naturalmente, a impossibilidade de quebra de tal esquema é o cenário ideal, onde tudo é meticulosamente planejado: qualquer informação sobre o fragmento de quebra-cabeça ausente está indisponível para o hacker, o servidor remoto é bem protegido, o algoritmo de criptografia é assimétrico, e assim por diante. Mas nosso mundo não é perfeito, e os humanos cometem erros, portanto, toda proteção tem seus pontos fracos. Hoje, examinaremos exatamente um desses casos.
Assim, temos um aplicativo vintage que exibe dados gráficos durante sua operação, cujo conjunto é severamente limitado sem registro. O registro é realizado pela entrada de um código de registro, e este código não possui restrições: seu comprimento é desconhecido, o conjunto de caracteres permitidos é desconhecido, nenhuma validação é realizada, e pode-se digitar qualquer coisa. A correção do código inserido só pode ser verificada reiniciando o programa. Se o registro falhar, um aviso é exibido e, é claro, a maioria dos dados gráficos permanece inacessível. O único ponto otimista é que o código é o mesmo para todos os usuários e não está vinculado ao computador.
Primeiro, determinamos a natureza do nosso aplicativo, alimentando-o no DetectItEasy. O aplicativo é escrito em Delphi antigo, sem qualquer proteção:
Como ferramenta de análise do programa, escolhemos, como de costume, o Interactive Delphi Reconstructor (IDR). Ao descompilar o módulo EXE, encontramos imediatamente duas verificações de validade de registro, bem como uma mensagem sobre a necessidade de registro ao iniciar o aplicativo.
E também um aviso sobre a versão não registrada ao clicar com o mouse em uma imagem inacessível. Poderia parecer que este é o caso do "patch de um byte", mas não é. Ao aplicar um patch nas transições condicionais destacadas (ou mesmo nas variáveis verificadas antes delas), apenas as mensagens de aviso são desativadas; as funções necessárias e os dados gráficos ausentes continuam inacessíveis. Portanto, continuamos a investigar.
Analisando o código das capturas de tela anteriores, descobrimos que o indicador de ausência de registro é o valor padrão da chave de registro "0123456789", que é atribuído quando essa chave não está presente no registro. Paralelamente, encontramos uma validação simples da chave inserida para seu registro no registro ao fechar o formulário de registro. Como você pode ver, ela consiste em uma verificação primitiva do comprimento da chave, que deve ser de pelo menos 3 caracteres. Você provavelmente já adivinhou que o programa não registra com nenhuma chave com mais de 3 caracteres. Além disso, tal chave, embora vá para o registro, por algum motivo não é carregada do registro para o programa ao executar o método de carregamento Form1 da primeira captura de tela - no final, o código de registro padrão 0123456789 ainda será o resultado.
Tentamos entender por que isso acontece, analisando mais atentamente o código deste método. Logo após o carregamento do código do registro, com seus três primeiros caracteres, uma operação estranha é realizada. Agora está claro por que, antes de salvar o código no registro, ele era verificado quanto ao comprimento de três caracteres. Em pseudocódigo humano, isso se parece com:
Procuramos onde a variável dword_45291C é usada posteriormente, e aqui começa a parte mais interessante da nossa missão. Acontece que esse valor inicializa a semente (RandSeed) do gerador de números pseudoaleatórios do Delphi System.@RandInt. Este é um gerador linear congruencial (LCG, o princípio de tal gerador é descrito em detalhes na Wikipedia). Ele representa a implementação interna da função Random do Delphi. A cada chamada, ele pega o valor atual da variável global System.RandSeed, multiplica-o por uma constante 134775813 e adiciona 1.
Mas o ponto crucial é para que, de fato, esse gerador pseudoaleatório é usado. Ele é usado dentro do carregador personalizado de imagens raster do aplicativo Unit1.TForm1.Image. Acontece que todos os dados raster do aplicativo são armazenados em um único banco de dados, onde as imagens ocultas do usuário não registrado são criptografadas com o código de registro. E como verificação de registro correto, uma imagem de teste criptografada é usada - com sua descriptografia correta, o programa é considerado registrado, e durante o processamento da exceção que ocorre em caso de falha ao carregar um JPEG descriptografado incorretamente, o código de registro é redefinido para o valor padrão.
Como não conhecemos apenas o código em si, mas também o conjunto de caracteres possíveis que o compõem, e até mesmo seu comprimento, a situação é bastante sombria. Se o desenvolvedor fez tudo corretamente, nesta fase a tarefa já deveria ser abandonada como infrutífera. No entanto, como você já adivinhou, a tarefa ainda tem uma solução. Chega de lamentar, vamos em vez disso olhar atentamente para o algoritmo de descriptografia:
O restante do conteúdo está disponível apenas para membros. Materiais das edições mais recentes se tornam disponíveis individualmente apenas dois meses após a publicação. Para continuar a leitura, é necessário se tornar um membro da comunidade "Xakep.ru". Junte-se à comunidade "Xakep.ru"! A adesão à comunidade durante o período especificado lhe dará acesso a TODO o material "Xaker", permitirá o download de edições em PDF, desativará a publicidade no site e aumentará seu desconto acumulado pessoal! Saiba mais. Já sou membro do "Xakep.ru". ← Anterior Grupo ShinyHunters declarou que roubou dados da Kodak.
🛡️⚡
Pare de pesquisar. Comece a hackear.
O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.
Sem cartão para começar · Planos a partir de R$49/mês
Ao investigar um aplicativo antigo desenvolvido em Delphi, nosso objetivo é desvendar o algoritmo de criptografia, analisar o funcionamento do gerador de números pseudoaleatórios e, finalmente, aplicar um ataque baseado em texto conhecido para extrair o código de registro correto. Essa abordagem nos permitirá obter o código sem a necessidade de tentativas de força bruta ou modificações diretas no aplicativo (patching).
Essencialmente, este artigo aborda um cenário incomum de implementação de proteção. Em vez de depender de uma simples comparação de número de série, a proteção se baseia na descriptografia de dados utilizando um código de registro. Um hacker experiente sabe que uma proteção de aplicativo robusta não deve ser quebrada com um simples patch de um byte. Independentemente do quão ofuscado ou virtualizado o código possa ser, se a proteção de um programa se resume à verificação de uma ou algumas condições específicas, encontrá-las e corrigi-las é apenas uma questão de tempo, habilidade do hacker e das ferramentas disponíveis.
A melhor estratégia para dificultar a vida de um engenheiro reverso é ofuscar o código ao máximo, adicionando verificações de integridade e outras funcionalidades que tornem a busca e o contorno dessas proteções mais trabalhosos e demorados do que o lucro potencial obtido com a quebra da proteção. Essa é a clássica guerra de escudo e espada, mas é uma espada de dois gumes. Contrariando o ditado popular "quebrar não é construir", um hacker, teoricamente, investe um esforço consideravelmente maior na engenharia reversa do que o desenvolvedor na proteção do aplicativo.
No entanto, existem casos em que a proteção é intrinsecamente impossível de ser quebrada. Como você já deve ter adivinhado, isso ocorre quando o hacker não possui uma informação crucial, vital para o funcionamento completo do programa. Exemplos incluem a falta de um módulo específico, um pedaço crítico de código, dados carregados de um servidor remoto ou, em último caso, um módulo que é descriptografado usando uma chave que o hacker não possui. Naturalmente, a impossibilidade de quebra de tal esquema é o cenário ideal, onde tudo é meticulosamente planejado: qualquer informação sobre o fragmento de quebra-cabeça ausente está indisponível para o hacker, o servidor remoto é bem protegido, o algoritmo de criptografia é assimétrico, e assim por diante. Mas nosso mundo não é perfeito, e os humanos cometem erros, portanto, toda proteção tem seus pontos fracos. Hoje, examinaremos exatamente um desses casos.
Assim, temos um aplicativo vintage que exibe dados gráficos durante sua operação, cujo conjunto é severamente limitado sem registro. O registro é realizado pela entrada de um código de registro, e este código não possui restrições: seu comprimento é desconhecido, o conjunto de caracteres permitidos é desconhecido, nenhuma validação é realizada, e pode-se digitar qualquer coisa. A correção do código inserido só pode ser verificada reiniciando o programa. Se o registro falhar, um aviso é exibido e, é claro, a maioria dos dados gráficos permanece inacessível. O único ponto otimista é que o código é o mesmo para todos os usuários e não está vinculado ao computador.
Primeiro, determinamos a natureza do nosso aplicativo, alimentando-o no DetectItEasy. O aplicativo é escrito em Delphi antigo, sem qualquer proteção:
Como ferramenta de análise do programa, escolhemos, como de costume, o Interactive Delphi Reconstructor (IDR). Ao descompilar o módulo EXE, encontramos imediatamente duas verificações de validade de registro, bem como uma mensagem sobre a necessidade de registro ao iniciar o aplicativo.
E também um aviso sobre a versão não registrada ao clicar com o mouse em uma imagem inacessível. Poderia parecer que este é o caso do "patch de um byte", mas não é. Ao aplicar um patch nas transições condicionais destacadas (ou mesmo nas variáveis verificadas antes delas), apenas as mensagens de aviso são desativadas; as funções necessárias e os dados gráficos ausentes continuam inacessíveis. Portanto, continuamos a investigar.
Analisando o código das capturas de tela anteriores, descobrimos que o indicador de ausência de registro é o valor padrão da chave de registro "0123456789", que é atribuído quando essa chave não está presente no registro. Paralelamente, encontramos uma validação simples da chave inserida para seu registro no registro ao fechar o formulário de registro. Como você pode ver, ela consiste em uma verificação primitiva do comprimento da chave, que deve ser de pelo menos 3 caracteres. Você provavelmente já adivinhou que o programa não registra com nenhuma chave com mais de 3 caracteres. Além disso, tal chave, embora vá para o registro, por algum motivo não é carregada do registro para o programa ao executar o método de carregamento Form1 da primeira captura de tela - no final, o código de registro padrão 0123456789 ainda será o resultado.
Tentamos entender por que isso acontece, analisando mais atentamente o código deste método. Logo após o carregamento do código do registro, com seus três primeiros caracteres, uma operação estranha é realizada. Agora está claro por que, antes de salvar o código no registro, ele era verificado quanto ao comprimento de três caracteres. Em pseudocódigo humano, isso se parece com:
Procuramos onde a variável dword_45291C é usada posteriormente, e aqui começa a parte mais interessante da nossa missão. Acontece que esse valor inicializa a semente (RandSeed) do gerador de números pseudoaleatórios do Delphi System.@RandInt. Este é um gerador linear congruencial (LCG, o princípio de tal gerador é descrito em detalhes na Wikipedia). Ele representa a implementação interna da função Random do Delphi. A cada chamada, ele pega o valor atual da variável global System.RandSeed, multiplica-o por uma constante 134775813 e adiciona 1.
Mas o ponto crucial é para que, de fato, esse gerador pseudoaleatório é usado. Ele é usado dentro do carregador personalizado de imagens raster do aplicativo Unit1.TForm1.Image. Acontece que todos os dados raster do aplicativo são armazenados em um único banco de dados, onde as imagens ocultas do usuário não registrado são criptografadas com o código de registro. E como verificação de registro correto, uma imagem de teste criptografada é usada - com sua descriptografia correta, o programa é considerado registrado, e durante o processamento da exceção que ocorre em caso de falha ao carregar um JPEG descriptografado incorretamente, o código de registro é redefinido para o valor padrão.
Como não conhecemos apenas o código em si, mas também o conjunto de caracteres possíveis que o compõem, e até mesmo seu comprimento, a situação é bastante sombria. Se o desenvolvedor fez tudo corretamente, nesta fase a tarefa já deveria ser abandonada como infrutífera. No entanto, como você já adivinhou, a tarefa ainda tem uma solução. Chega de lamentar, vamos em vez disso olhar atentamente para o algoritmo de descriptografia:
O restante do conteúdo está disponível apenas para membros. Materiais das edições mais recentes se tornam disponíveis individualmente apenas dois meses após a publicação. Para continuar a leitura, é necessário se tornar um membro da comunidade "Xakep.ru". Junte-se à comunidade "Xakep.ru"! A adesão à comunidade durante o período especificado lhe dará acesso a TODO o material "Xaker", permitirá o download de edições em PDF, desativará a publicidade no site e aumentará seu desconto acumulado pessoal! Saiba mais. Já sou membro do "Xakep.ru". ← Anterior Grupo ShinyHunters declarou que roubou dados da Kodak.
📤 Compartilhar & Baixar
🧰 Ferramentas recomendadas
Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.