Ir para o conteúdo
AZ Tools

Inspetor de binários ELF

Lê um binário ELF do mesmo jeito que o readelf, direto dos bytes e inteiramente no seu navegador. O cabeçalho informa o tamanho de palavra e a ordem de bytes, o OS/ABI, a arquitetura, o ponto de entrada e se o arquivo é um objeto realocável, um executável comum, um executável independente de posição ou uma biblioteca compartilhada — distinção que o cabeçalho sozinho não faz, porque um PIE e um .so são ambos DYN e só o interpretador ou a flag DF_1_PIE os separa. Os cabeçalhos de programa listam o que o carregador mapeia, e o array dinâmico dá o caminho do interpretador, cada biblioteca em DT_NEEDED, o SONAME sob o qual uma biblioteca se publica e qualquer RPATH ou RUNPATH gravado no arquivo; vale conferir esse último em qualquer binário que você não tenha compilado, porque ele muda onde o carregador procura. A linha de endurecimento responde ao que o checksec responde, a partir dos mesmos lugares: PIE pelo tipo e por DF_1_PIE, NX conforme PT_GNU_STACK seja gravável e executável, e RELRO como nenhum, parcial ou completo conforme haja PT_GNU_RELRO e BIND_NOW. Sem símbolos quer dizer que o arquivo não tem SHT_SYMTAB, que é o que o strip remove; a tabela de símbolos dinâmicos permanece, então uma biblioteca sem símbolos continua exportando seus nomes. Duas coisas que ele não faz: não desmonta e não diz se houve protetor de pilha ou FORTIFY_SOURCE, porque ambos exigem ler a tabela de símbolos atrás de nomes específicos, e não uma flag de cabeçalho. Arquivos big-endian e de 32 bits são lidos corretamente, então um binário ARM, MIPS ou PowerPC de um roteador ou de uma imagem embarcada abre tão bem quanto um de x86-64.

Como usar

  1. Solte o binário sobre a caixa: serve um executável sem extensão, um .so, um .o ou um despejo de memória.
  2. Leia a linha do cabeçalho para arquitetura e tipo. Executável PIE e objeto compartilhado são ambos DYN; a linha do interpretador diz qual você tem.
  3. Confira as etiquetas de endurecimento: PIE, NX e RELRO completo é o normal em uma compilação de distribuição, e uma etiqueta âmbar merece explicação antes de distribuir o arquivo.
  4. Veja Precisa de para as bibliotecas que o carregador vai procurar, e RUNPATH para qualquer diretório fora do padrão que o binário acrescenta a essa busca.
  5. Abra a lista de seções quando precisar de tamanhos: .text para o código, .rodata para as constantes e as seções .debug_* se a informação de depuração ficou no arquivo.

Perguntas frequentes

Qual a diferença entre um executável PIE e uma biblioteca compartilhada? Os dois dizem DYN.
O tipo ELF é mesmo o mesmo: código independente de posição que o carregador pode mapear em qualquer endereço. O que os separa é que um executável tem um cabeçalho de programa PT_INTERP nomeando o carregador dinâmico (em geral /lib64/ld-linux-x86-64.so.2) para o núcleo saber iniciá-lo, enquanto uma biblioteca não tem e só é carregada por outro programa. As cadeias de ferramentas atuais também marcam DF_1_PIE nas flags dinâmicas do executável, que é o que o readelf informa e o que esta ferramenta olha primeiro, recorrendo ao interpretador quando um ligador antigo deixou a flag de fora.
Do que PIE, NX e RELRO realmente protegem?
O PIE permite ao núcleo carregar o binário em um endereço base aleatório, de modo que o ASLR vale também para o código do próprio programa e não só para as bibliotecas; sem ele, os endereços dos gadgets são fixos e conhecidos. O NX marca a pilha como não executável, então não dá para simplesmente saltar para o shellcode injetado. O RELRO deixa as tabelas de realocação somente leitura depois da inicialização: o parcial protege as resolvidas antes do main, e o completo ainda resolve todas as funções de antemão para deixar a GOT somente leitura, fechando a clássica sobrescrita da GOT. As três são propriedades da ligação, e por isso podem ser lidas do arquivo sem executá-lo.
Por que diz que o binário está sem símbolos se ainda vejo nomes de funções?
Existem duas tabelas de símbolos. Sem símbolos aqui quer dizer que a SHT_SYMTAB sumiu, que é o que o strip remove e o que um depurador precisa. A tabela dinâmica, SHT_DYNSYM, precisa continuar em qualquer arquivo que exporte ou importe símbolos, senão a ligação dinâmica não funcionaria — por isso uma biblioteca sem símbolos ainda lista tudo o que exporta e um executável sem símbolos ainda lista o que importa. É por isso que o nm diz não haver símbolos enquanto o nm -D imprime uma lista.
Ele lê um binário de outra arquitetura ou big-endian?
Sim. O byte de classe e o de dados no início do cabeçalho dizem se o arquivo é de 32 ou 64 bits e qual ordem de bytes usa, e todos os campos seguintes são lidos através desses dois, então um binário AArch64, ARM, MIPS, RISC-V ou PowerPC big-endian é lido com a mesma exatidão de um x86-64. A arquitetura aparece pelo nome nos tipos comuns e como código bruto nos demais. O que ele não faz é dizer se o binário vai rodar na sua máquina: isso depende do núcleo, da ABI e das bibliotecas, não só do cabeçalho.
Para que serve o Build ID?
É um hash que o ligador guarda em uma seção de notas e que identifica exatamente aquela compilação. As distribuições o usam para casar um binário com os símbolos de depuração distribuídos à parte: o arquivo de depuração vai para /usr/lib/debug/.build-id/xx/yyyy.debug, e um depurador que receba um despejo consegue achar os símbolos certos mesmo com o binário sem símbolos. Também é um jeito confiável de saber se dois arquivos são mesmo a mesma compilação, porque o identificador muda a qualquer mudança na saída compilada.
O binário é enviado para algum lugar?
Não. O arquivo é lido com a File API do navegador e analisado por JavaScript na página. Nada vai para um servidor e não existe componente de servidor para onde mandar, o que aqui pesa mais do que na maioria das ferramentas: um binário que você está inspecionando pode ser justamente o que você não pode enviar. Dá para desconectar da rede depois que a página carrega e tudo continua funcionando.

Ferramentas relacionadas