Uma breve história da detecção de packers
O ano era 2001 quando snake e Qwerton, dois hackers (na época, crackers - como eram / ainda são chamadas as pessoas s que faziam engenharia reversa), liberaram a versão 0.7 Beta do PEiD. O nome vem de Portable Executable (PE) iDentifier. Ou seja, um software identificador para arquivos PE capaz de detectar se um packer fora utilizado para comprimir, ou um protector para proteger, o executável de Windows que você estava analisando. O PEiD não foi o primeiro, mas ganhou espaço e cresceu em popularidade na década de 2000. O software vinha com um banco de dados interno de assinaturas de packers e protectors, mas também permitia o uso de um arquivo externo chamado userdb.txt onde você poderia armazenar suas próprias assinaturas (e compartilhar com a galera depois). Tais assinaturas seguiam um padrão como a seguir:
[nome do packer/protector] signature = 50 E8 ?? ?? ?? ?? 58 25 ?? F0 FF FF 8B C8 83 C1 60 51 83 C0 40 83 EA 06 52 FF 20 9D C3 ep_only = trueOs bytes no campo signature eram buscados no entrypoint do binário PE caso a opção ep_only estivesse presente e configurada como true. Do contrário, a sequência de bytes era buscada em todo o binário. Os dois caracteres "??" formavam um coringa que significava "qualquer byte". Se você usa o YARA (ou o YARA-X), deve soar familiar.
Rapidamente, outros hackers começaram a popular e distribuir seus bancos de dados de assinaturas personalizadas, populando-os ao analisar binários compilados em várias linguagens, mas também binários comprimidos ou protegidos. Obviamente isso gerou uma infinidade de versões de userdb.txt pelo mundo afora e uma grande disputa para saber qual era o mais completo. Resumindo, o caos. 🤪
Por volta de 2006, surge o RDG Packer Detector v0.6.4, criado por uma pessoa conhecida por RDGMax. Rapidamente o software ganhou espaço por ter um banco de dados interno de assinaturas de muito boa qualidade.
Para suportar assinaturas personalizadas, o RDG suporta ler um userdb.txt, ou seja, ele suporta assinaturas do PEiD, um movimento bastante inteligente para que as pessoas que tinham seus próprios bancos de assinaturas criado para o PEiD pudessem reaproveitá-las no RDG, caso as assinaturas padrão dele não detectassem o que se esperava ou para casos específicos.
A última versão pública parece ter sido liberada em 2021, mas há rumores de que o desenvolvimento continua e pode ser que vejamos uma nova versão do RDG Packer Detector num futuro próximo! 😊
Em paralelo ao sucesso do RDG, em janeiro de 2013, surge a primeira versão pública do Detect It Easy (DIE). Não tenho certeza sobre as primeiras versões, mas hoje o DIE conta com modernidades interessantíssimas. Um bom exemplo é o fato de ser multiplataforma, uma vez que a onipresença do Windows ficou no passado já que nos últimos anos muitas pessoas migraram para - ou já "nasceram" usando - sistemas operacionais GNU/Linux ou macOS. Outro fator importante para o sucesso do DIE foram as assinaturas. Além de serem boas, são todas abertas, então nós podemos ver todas as assinaturas que já vêm com o software e, claro, criar as nossas baseadas nelas. Além disso, o motor é simples de usar e a linguagem para criar as assinaturas é turing-complete, ou seja, podemos declarar variáveis, fazer repetições, condicionais, cálculos, etc. Ela é implementada com QT Script/QJSEngine, o que na prática nos permite escrever assinaturas em JavaScript e isso certamente contribui para a popularização do DIE. O autor foi para o GitHub e aceita contribuições de novas assinaturas ou modificações nas existentes por lá, que é o tema deste artigo. Vamos agora ver como melhorei uma assinatura do DIE, mas este artigo também te dá as bases para criar assinaturas novas se você precisar.
O desafio
Normalmente você precisa de uma nova assinatura ou precisa modificar uma existente quando o DIE não detecta alguma característica de um binário de seu interesse. Num caso recente, usei o DIE para identificar um binário PE e ele não detectou o compilador. Suspeitei que algo estava errado e olhei a estrutura do binário. Uma das coisas que me chamou atenção no binário foi a presença das seções .pdata e .xdata, pouco comuns na minha experiência, como mostra a Figura 1:
Figura 1 - Seções do binário não detectado pelo DIE
Além disso, no binário haviam algumas ocorrências de strings bastante sugestivas, mostradas na Figura 2:
Figura 2 - Strings intrigantes no binário
Tais strings me lembraram da existência do MinGW, uma suíte de aplicativos GNU para Windows. De fato, MinGW é uma abreviação de Minimalist GNU for Windows. Na prática, você pode escrever código em C/C++ e utilizar o GCC para compilar seu programa para Windows. Compilei então um "hello, world" em C com a última versão do MinGW. A estrutura de seções ficou parecida. Testei a detecção com o DIE e o resultado foi o mesmo com o meu binário inicial: o DIE não conseguiu detectar o compilador (Figura 3).
Figura 3 - DIE não detectou o compilador do meu "hello, world" com MinGW
Decidi então investigar e tentar resolver este problema.
Inspecionando a assinatura existente
Fui na pasta do DIE e em db\PE\MinGW.4.sg encontrei a assinatura que deveria detectar o MinGW:
// DIE's signature file init("compiler","MinGW"); includeScript("FPC"); // -- corte -- function detect(bShowType,bShowVersion,bShowOptions) { if(PE.getMajorLinkerVersion()==2) { if(!bFPC&&(PE.getMinorLinkerVersion()<=30||PE.getMinorLinkerVersion()==36||PE.getMinorLinkerVersion()==56)) { if(PE.compare("'MZ'90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21'This program cannot be run in DOS mode.\r\r\n$'00000000000000'PE'0000")) { if(!PE.section[".rsrc"]) { bDetected=1; } else { var nOffset=PE.section[".rsrc"].FileOffset; var nSize=PE.section[".rsrc"].VirtualSize; if(!PE.isSignaturePresent(nOffset+nSize-512,512,"'Microsoft Corp.'")) { bDetected=1; } } } } } // -- corte -- return result(bShowType,bShowVersion,bShowOptions); }Cortei alguns trechos de código em favor da brevidade, mas o importante é saber que a função detect() é a responsável por detectar (dã!) o compilador y otras cositas más. No final, o return result() vai retornar as informações detectadas, mas estas só serão utilizadas se bDetected, que parece ser uma variável global, for true. Restava saber então qual fluxo este código estava seguindo que não chegava numa das linhas que ligava a bDetected.
Depurando a assinatura
Graças à escolha da Qt, o DIE integrou um depurador (ou debugger, se você preferir a palavra em inglês) completo para encontrar problemas na assinatura. Para acessá-lo, basta:
Clicar no botão Signatures.
Escolher a assinatura que quer depurar no menu da esquerda.
Clicar no botão Debug.
Olha que belezinha:
Figura 4 - Depurando a assinatura MinGW.4.sg do DIE no QT Script Debugger integrado
É possível colocar breakpoints, usar Step Over (F10), Step Into (F11), etc. Assim ficou fácil perceber que, com o meu binário "hello, world", a condição do segundo if não resultava em verdadeiro: (!bFPC && (PE.getMinorLinkerVersion() <= 30 || PE.getMinorLinkerVersion() 36 || PE.getMinorLinkerVersion() 56).
Essa função PE.getMinorLinkerVersion pega o campo MinorLinkVersion do cabeçalho opcional do PE, que no caso precisa ter um valor menor que 30 ou igual a 36 ou 56 para a variável bDetected ser true. Fui verificar no meu binário inicial e o valor dele neste campo era 41. Já no "hello, world" que gerei, era 44. Gerei mais alguns binários usando versões diferentes do MinGW e usei o readpe (projeto nosso! 💚) para filtrar todos os valores possíveis:
$ ls *.exe hello_cygwin32.exe hello_mingw64_ucrt.exe hello_w64devkit_x64.exe nfuncs.exe hello_cygwin64.exe hello_mingw_from_linux.exe hello_w64devkit_x86.exe hello_mingw64_msvcrt.exe hello_mingw_from_linux_stripped.exe hello_winlib_x64.exe $ (for i in *.exe; do readpe -H "$i" | grep Linker; done) | sort -u Linker major version: 2 Linker minor version: 41 Linker minor version: 44Então o caminho parecia ser esse: expandir a assinatura para cobrir os valores 41 e 44 de LinkerMinorVersion. 🤞
Atualizando a assinatura
Só um pouquinho de programação básica foi necessário para modificar a assinatura. Criei uma variável minor para não precisar chamar PE.getMinorLinkerVersion() múltiplas vezes e atualizei a condição assim:
// -- corte -- function detect(bShowType,bShowVersion,bShowOptions) { if(PE.getMajorLinkerVersion()==2) { var minor=PE.getMinorLinkerVersion(); if(!bFPC&&(minor<=30||minor==36||minor==41||minor==44||minor==56)) { // -- corte -- return result(bShowType,bShowVersion,bShowOptions); }O resultado causou aquela liberação de dopamina. Como mostrado na Figura 5, o DIE detectou o MinGW, sua versão, linguagem e tudo mais.
Figura 5 - DIE detectando corretamente o compilador MinGW usado no "hello, word"
Aproveitei e adicionei na assinatura uma informação adicional, pois percebi que quando você usa o comando strip, os dados no overlay (dados que não pertencem à nenhuma seção ou cabeçalho, após o "fim" do arquivo), são removidos. Então adicionei mais um condicional no fim:
if(bDetected) { sVersion=getMinGWVersion(); if (!PE.isOverlayPresent()) sVersion+=", stripped"; }Testei e tudo funcionou bem. Era só enviar a contribuição no GitHub né? Será? 🤔
Enviando o patch
Para minha surpresa, quando cheguei no GitHub, a assinatura estava um pouco diferente da que eu tinha em meu computador com o DIE 3.10. Acontece que o DIE está em pleno desenvolvimento e depois do release da versão 3.10, as assinaturas foram atualizadas e a API mudou um pouco (para melhor). Por sorte, o autor deixa disponível uma versão beta que usa as últimas assinaturas do GitHub. Tratei de estudar a nova assinatura e, no fim, enviei um pull request que acabou corrigindo um pequeno problema com ela: um zero a menos na comparação da PE.compare(). O PR foi aceito (obrigado, @horsicq!) com a correção e as melhorias simples que fiz para detecção do MinGW.
Conclusão
A detecção de compilador e linker é uma parte importante da engenharia reversa porque te dá um norte de que ferramentas usar, como estarão as funções (convenção de chamadas, etc), como estarão as strings (.NET usa, na maioria dos casos, UTF-16-LE por exemplo). Além disso, saber se um compressor (packer) foi utilizado ou um protector já te prepara para o que você vai encontrar no entrypoint. É uma informação útil na inteligência de ameaças também. Portanto, ter um bom software de detecção à mão é essencial. Além disso, se o software permitir, você pode melhorá-lo. Exige algum esforço, algum estudo, mas as coisas boas exigem mesmo. E há prazer em fazer tais coisas, posso te garantir.
Por fim, fica meu agradecimento a todas as pessoas que trabalharam em softwares de detecção do tipo nos últimos anos. Espero que você compartilhe dessa gratidão também. Na próxima vez que o DIE detectar corretamente o que foi usado no seu binário, agradeça a quem o desenvolveu, mas também às suas alternativas como o RDG e o PEiD. Há um trabalho comunitário grande nos bastidores para que você possa dar um duplo-clique e ter sua resposta rapidinho. E de graça.
Ah, e se por acaso seu binário tiver sido compilado pelo MinGW e o DIE detectar corretamente, agradeça a quem criou a primeira versão do MinGW.4.sg e a quem já melhorou essa assinatura que, a partir de agora, inclui a Mente Binária também. 💚
- 561 views
-