Você conhece o pev, nosso kit de ferramentas para análise de binários PE (usados no Windows)? Se conhece, acho que vai se identificar com essa história. Se não conhece, vai conhecê-lo muito bem agora. Nesse artigo vou passear brevemente sobre a inspiração que deu origem ao projeto, o que foi feito desde então e o que vem por aí. De modo cronolagicamente organizado, começamos por um passado de mais de uma década. Partiu embarcar nessa viagem no tempo? ⌛
Passado 💭
Lá em 2010 eu queria fazer um programa que lesse a string de versão “File version” de executáveis PE. Essa que aparece quando você vai nas Propriedades de um executável .exe no Windows:
Essa informação só poderia estar no arquivo .exe, mas eu não sabia exatamente onde. Depois de muito estudo, consegui entender que essa informação fica numa estrutura chamada VS_FIXEDFILEINFO que, por sua vez, pertence a uma estrutura chamada VS_VERSIONINFO. Dava para encontrar essa estrutura por nome no binário em uma string UTF-16-LE na seção de recursos .rsrc:
$ strings -tx -el putty.exe | grep VS_VERSION_INFO 15a2c6 VS_VERSION_INFO $ hd -s 0x15a2c6 -n64 putty.exe 0015a2c6 56 00 53 00 5f 00 56 00 45 00 52 00 53 00 49 00 |V.S._.V.E.R.S.I.| 0015a2d6 4f 00 4e 00 5f 00 49 00 4e 00 46 00 4f 00 00 00 |O.N._.I.N.F.O...| 0015a2e6 00 00 bd 04 ef fe 00 00 01 00 4d 00 0a 00 00 00 |..........M.....| 0015a2f6 00 00 4d 00 00 00 00 00 00 00 0b 00 00 00 00 00 |..M.............| 0015a306
Mas onde estava o 0.77.0.0 ninguém me contava... Bem, segundo a documentação, a estrutura é definida assim:
typedef struct tagVS_FIXEDFILEINFO { DWORD dwSignature; DWORD dwStrucVersion; DWORD dwFileVersionMS; DWORD dwFileVersionLS; DWORD dwProductVersionMS; DWORD dwProductVersionLS; DWORD dwFileFlagsMask; DWORD dwFileFlags; DWORD dwFileOS; DWORD dwFileType; DWORD dwFileSubtype; DWORD dwFileDateMS; DWORD dwFileDateLS; } VS_FIXEDFILEINFO;
Para facilitar a visualização dos campos de interesse dessa estrutura no dump hexa, coloquei comentários nela usando o rehex (parte integrante do nosso retoolkit) :
De acordo a documentação, o primeiro membro, chamado dwSignature, deve conter o valor 0xfeef04bd. Considerando o ensianness em little-endian, essa assinatura tá no dump hexa em 0x15a2e8, logo após a string VS_VERSION_INFO em UTF-16-LE. A missão então era achar como o número de “File version” era gerado, no exemplo aqui, a string 0.77.0.0. Vamos em frente.
Logo depois da dwSignature tem a dwStrucVersion de quatro bytes, seguida da dwFileVersionMS, que também é uma DWORD (Double WORD) de quatro bytes. Nesta última, temos os bytes 4D 00 (destacados em verde) e 00 00 (laranja). Lendo a documentação, descobri que A WORD mais significativa, tomando este número em little-endian, é o nosso primeiro zero da string "0.77.0.0", destacado em laranja. Já a WORD menos significativa é o 77 (0x4D), destacado em verde. O próximo membro é a dwFileVersionLS (amarelo e rosa), que segue a mesma lógica, formando o "0.0" final.
Na época, fiz um script em Python para buscar esse número de versão e chamei de pev.py (pev vem de “PE Version”). Não tenho mais o script, mas hoje seria algo nessa linha:
import sys if len(sys.argv) < 2: print("Usage: pev.py <pefile>") sys.exit(1) with open(sys.argv[1], "rb") as f: d = f.read() vinfo = "VS_VERSION_INFO".encode("utf-16-le") # Busca a string nos conteúdo do arquivo pos = d.find(vinfo) # Avança para depois da string vinfo + 4 pos += len(vinfo) + 4 sig = int.from_bytes(d[pos:pos+4], "little") if sig != 0xfeef04bd: print(f"Signature not found at {pos:#x}") sys.exit(1) pos += 8 # Avança para o dwFileVersionMS # Lê a word mais significativa major1 = int.from_bytes(d[pos+2:pos+2], "little") # Lê a word menos significativa major2 = int.from_bytes(d[pos:pos+2], "little") # Avança para o dwFileVersionLS pos += 4 # Lê a word mais significativa minor1 = int.from_bytes(d[pos+2:pos+2], "little") # Lê a word menos significativa minor2 = int.from_bytes(d[pos:pos+2], "little") print(f"{major1}.{major2}.{minor1}.{minor2}")
Assim como o script da época, este script funciona:
$ python pev.py putty.exe 0.77.0.0
Mas sem dúvida, pode ser melhor por vários motivos. O principal, é que ele é lento, especialmente para arquivos grandes pois ele busca a string VS_VERSION_INFO no binário inteiro. É a famosa “marretada” em programação...
Era lento, não tinha jeito. Resolvi então estudar a documentação do PE e cheguei num código muito maior, mas que rodava muito mais rápido, porque eu lia todos os cabeçalhos do PE necessários para encontrar a posição no arquivo da estrutura VS_VERSION_INFO e, por consequência, da VS_FIXEDFILEINFO. O código não envolvia busca. Ao contrário, ele ia direto ao ponto. O pev 0.22 implementava esse algoritmo em aproximadamente 300 linhas de código em C. Lançado em 2010 no extinto Google Code, era assinado pelo Coding 40º, um grupo de programação que criei na faculdade com mais três amigos (@Francivan Bezerra, Thiago e Eduardo). No ano seguinte, o Coding 40º teve uma breve vida como hackerspace na Tijuca, no Rio de Janeiro, onde inclusive o @julio neves foi nos visitar. 💚
O projeto andou, mas em algum momento pensei: se eu passo por todos os cabeçalhos do PE, por que não imprimir os valores dos campos também? Isso transformaria o pev num parser de PE completo! E foi o que fizemos. Aí o projeto cresceu mais, ganhou relevância na área de segurança (era o único parser de PE livre e multiplataforma do planeta) e atraiu colaboradores do Brasil e de fora. A ponto do meu empregador na época ficar preocupado de eu estar recebendo dinheiro de outra empresa por conta do pev (como sempre, a p***a do capitalismo na nossa cola). Mas eu estava só aprendendo mesmo.
Na versão 0.50 eu segui a dica do @bsdaemon de “quebrar” o pev em programas menores, onde cada um cumpriria uma função. Assim, o pev virou um kit de ferramentas. E tivemos que aprender a lidar com tanto código, depois veio o Git e o GitHub e tivemos até uma sessão presencial onde vários colaboradores do projeto se uniram para estudar Git:
(Da esquerda para a direita, Álvaro Justen, Renato Augusto, Álvaro Rios, Thiago Bordini, @Felipe Esposito, Jan Seidl, Igor Rincon, @l0gan, @Gustavo Roberto, Slayer, @Fernando Mercês, Rogy e unk)
Dentre as muitas pessoas que colaboraram, teve o @jweyrich que reescreveu a libpe (na qual o pev se baseia) quase toda, corrigiu muito código, adicionou recursos e levou o projeto para outro nível. Junto com várias pessoas que reportaram e corrigiram bugs, implementaram recursos e testaram o pev, lançamos várias versões até chegarmos na atual 0.8x. 🙃
Presente 🎁
Hoje com aproximamente 30.000 linhas de código, o pev está estabelecido. Está presente em vários tutorias sobre análise de malware, e forense e em praticamente todas as distribuições Linux, incluindo Debian, Kali, Ubuntu, etc. Tem builds para Windows também. Veja alguns dos lugares onde você encontra o pev:
- Pacote no Debian (eu fui o primeiro mantenedor, depois deixei o pacote órfão e o time do Debian assumiu! 💗):
- Pacote no Ubuntu:
- Incluso no Kali Linux por padrão:
- Incluso no REMnux por padrão:
- Incluso no Tsurugi Linux por padrão:
Paper Process Injection Techniques and Detection using the Volatility Framework no vx-underground:
Tem também pev no Arch Linux, Void Linux, vários tutoriais em português, inglês, chinês.. Em algum ponto eu até parei de monitorar. 😄
Neste ínterim, tive a oportunidade de aprender com programadores incírveis que puseram código no pev. Só para citar alguns: @jweyrich, @Jan Seidl, Marcelo Fleury, @fredericopissarra e muitos outros que não conseguiria nomear aqui (mas estão todos na lista de colaboradores do projeto no GitHub). Muito obrigado! 💚
Muito tempo passou e muitos projetos livres similares surgiram, sendo o principal deles a pefile, que habilitou programadores e programadoras em Python a "parsear" binários PE facilmente. O mundo mudou, minhas prioridades mudaram, as prioridades dos colaboradores do pev mudaram também. Decidi então passar o bastão para quem tivesse interesse e em 28 de janeiro de 2022 comuniquei a comunidade que o projeto precisava de uma nova pessoa para mantê-lo. Surgiram algumas perguntas, alguns e-mails, até que em dezembro do mesmo ano, uma programadora da Alemanha entrou em contato informando do seu interesse em manter o projeto.
Conversa vai, conversa vem e é isso. O pev tem uma nova mantenedora! 🥳
Futuro 🔮
A nova mantenedora usa o apelido GoGoOtaku. Embora eu não a conheça, ela se mostrou bastante confiável e corrigiu vários bugs no pev já. Penso que é quase mágico o fato de alguém, bem longe de mim física e socialmente, tenha encontrado o pev e tenha tido o interesse em mantê-lo. São coisas que só o software livre, aliado a linguagens universais como C, idiomas universais como o inglês, e redes sociais como o GitHub, conseguem realizar. Acho isso muito incrível! 🙌
Em conversas com a nova mantenedora, expressei meu desejo que de o que pev voltasse a ser um programa só, mas com sub-comandos. Sugeri que usássemos o nome readpe, já que este é o programa mais usado do toolkit do pev e sugere um belo contraste com a ferramenta readelf do GNU Binutils. Ela gostou da ideia, afinal, o pev não tem mais nada a ver com “PE Version”. O mantenedor do pacote no Debian nos alertou que mudar de nome é um processo delicado, especialmente porque o pev já é bastante conhecido, mas eu insisti. Minha ideia é que o readpe tenha sub-comandos que implementam a função dos programas separados hoje. Algo nessa linha:
Exibir a ajuda:
readpe help
Imprimir os campos e valores dos cabeçalhos do PE (funcionalidade provida pelo readpe que seria dividida em sub-comandos):
readpe headers <pefile> # todos os cabeçalhos readpe headers dos <pefile> # somente o cabeçalho do DOS
Exibir strings no PE (funcionalidade provida pelo pestr, que viraria um sub-comando do readpe):
readpe strings <pefile> # todas as strings readpe strings ascii -s <pefile> # somente strings ASCII exibindo as seções às quais elas pertencem readpe strings unicode <pefile> # somente strings UNICODE
E por aí vai. Por hora, movi o pev para um repositório “embaixo” da Mente Binária, que chamei de readpe e dei todas as permissões para a nova mantenedora nele. Ela que vai decidir sobre o futuro do projeto. Talvez ela aceite minha sugestão de transformar o pev num único programa readpe. Talvez não. A mágica disso é que agora a decisão dela, afinal, filho a gente cria pro mundo e não pra gente né? 😄
Estou muito feliz que o pev tenha encontrado uma nova mantenedora (que já lançou a versão 0.82 com várias correções de bugs!). E que ele tem ajudado pessoas interessadas em "parsear" PEs pelo mundo afora durante seus 13 anos de existência.
Vou seguir acompanhando o projeto de perto e, quem sabe, até programando um pouquinho (se a nova mantenedora aceitar meus patches), mas acima de tudo, sou grato a todo mundo que contribuiu, usou e apoiou o pev durante todos esses anos. Ele continua sendo um dos poucos parsers livres de PE de linha de comando, multiplataforma, super versátil (com saídas em XML, JSON, HTML, etc), com recursos únicos (é o único livre que extrai certificados do PE, por exemplo) e claro, com raízes brasileiras. Foi o maior projeto de software livre da minha vida e eu continuo vibrando a cada commit que vejo nele. 😭
- 3