Há coisas no mundo que tomamos como garantidas, embora sejam verdadeiras obras-primas. Uma dessas coisas são os utilitários Linux, como ls e ps. Embora eles geralmente sejam percebidos como simples, isso está longe de ser o caso, se olharmos para dentro. E o mesmo acontece com ELF, executável e formato vinculável. Um formato de arquivo usado universalmente, mas poucos o entendem. Este guia rápido ajudará você a entender.

Depois de ler este guia, você aprenderá:
- Por que o formato ELF é necessário e para quais tipos de arquivos é usado
- Estrutura do arquivo ELF e detalhes do formato
- Como ler e analisar o conteúdo binário de um arquivo ELF
- Quais ferramentas são usadas para analisar arquivos binários?
O que é um arquivo ELF?
ELF significa Executable and Linkable Format e define a estrutura de arquivos binários, bibliotecas e arquivos principais. A especificação de formato permite que o sistema operacional interprete corretamente as instruções da máquina contidas no arquivo. Um arquivo ELF é geralmente o arquivo de saída de um compilador ou vinculador e tem um formato binário. Utilizando ferramentas adequadas, pode ser analisado e estudado.
Por que estudar a ELF em detalhes?
Antes de mergulhar nos detalhes técnicos, não será errado explicar por que entender o formato ELF é útil. Primeiro, ele permite que você estude a operação interna do sistema operacional. Quando algo deu errado, esse conhecimento o ajudará a entender melhor o que exatamente aconteceu e por que motivo. Além disso, a capacidade de examinar arquivos ELF pode ser valiosa para encontrar falhas de segurança e detectar arquivos suspeitos. E, finalmente, para uma melhor compreensão do processo de desenvolvimento. Mesmo se você programar em um idioma de alto nível como o Go, ainda saberá melhor o que está acontecendo nos bastidores.
Então, por que estudar ELF?
- Para uma compreensão geral do sistema operacional
- Para desenvolvimento de software
- Forense digital e resposta a incidentes (DFIR)
- Pesquisa de malware (análise binária)
Da origem ao processo
Qualquer que seja o sistema operacional que usamos, é necessário traduzir de alguma forma as funções do código fonte no idioma da CPU - código da máquina. As funções podem ser as mais básicas, por exemplo, abrir um arquivo no disco ou exibir algo na tela. Em vez de usar a linguagem da CPU diretamente, usamos uma linguagem de programação que possui recursos padrão. O compilador então traduz essas funções em código de objeto. Esse código de objeto é então vinculado ao programa completo, usando o vinculador. O resultado é um arquivo binário que pode ser executado em uma plataforma específica e tipo específico de CPU.
Antes de começar
Este post contém muitas equipes. É melhor executá-los em uma máquina de teste. Copie os binários existentes antes de executar esses comandos neles. Também escreveremos um pequeno programa C que você pode compilar. Por fim, a prática é a melhor maneira de aprender alguma coisa.
Anatomia de um arquivo ELF
Um equívoco comum é que os arquivos ELF são apenas para arquivos binários ou executáveis. Já dissemos que eles podem ser usados para partes de arquivos executáveis (código de objeto). Outro exemplo são os arquivos de biblioteca e dumps principais (arquivos principais e arquivos a.out). A especificação ELF também é usada no Linux para kernel e módulos de kernel.

Estrutura
Devido à extensibilidade dos arquivos ELF, a estrutura pode variar para arquivos diferentes. O arquivo ELF consiste em:
- Cabeçalho ELF
- dados
Com o comando readelf, podemos olhar para a estrutura do arquivo, e será algo parecido com isto:

Cabeçalho ELF
Como você pode ver na captura de tela, o cabeçalho ELF começa com um "número mágico". Este "número mágico" fornece informações sobre o arquivo. Os primeiros 4 bytes determinam que este é um arquivo ELF (45 = E, 4c = L, 46 = F, eles são precedidos por 7f).
O cabeçalho ELF é obrigatório. É necessário para que os dados sejam corretamente interpretados durante a vinculação e execução. Para uma melhor compreensão da operação interna de um arquivo ELF, é útil saber para que essas informações são usadas.
Class
Depois de declarar um tipo ELF, um campo de classe segue. Este valor significa a arquitetura para a qual o arquivo é destinado. Pode ser 01 (arquitetura de 32 bits) ou 02 (64 bits). Aqui vemos 02, que é traduzido pelo comando readelf como um arquivo ELF64, ou seja, esse arquivo usa uma arquitetura de 64 bits. Isso não é surpreendente, um processador moderno está instalado no meu carro.
Dados
A seguir, vem o campo "data", que tem duas opções: 01 - LSB (bit menos significativo), também conhecido como little-endian, ou 02 - MSB (bit mais significativo, big-endian). Esses valores ajudam a interpretar o restante dos objetos no arquivo. Isso é importante porque diferentes tipos de processadores lidam com estruturas de dados de maneira diferente. No nosso caso, o LSB é usado, uma vez que o processador possui uma arquitetura AMD64.
O efeito LSB fica visível ao usar o utilitário hexdump em um arquivo binário. Vamos ver o cabeçalho ELF para / bin / ps.
$ hexdump -n 16 /bin/ps 0000000 457f 464c 0102 0001 0000 0000 0000 0000 0000010
Vemos que os pares de valores são diferentes, devido à interpretação da ordem dos dados.
Versão
Em seguida, segue outro valor mágico "01", que é o número da versão. Somente a versão 01 está disponível no momento, portanto esse número não significa nada de interessante.
OS / ABI
Cada sistema operacional tem sua própria maneira de chamar funções, eles têm muito em comum, mas, além disso, cada sistema tem pequenas diferenças. A ordem da chamada de função é determinada pela ABI (Application Binary Interface). Os campos OS / ABI descrevem qual ABI é usado e sua versão. No nosso caso, o valor é 00, o que significa que extensões específicas não são usadas. Na saída, isso é mostrado como Sistema V.
Versão ABI
Se necessário, uma versão ABI pode ser indicada.
Carro
O título também indica o tipo de máquina esperado (AMD64).
Tipo
O campo type indica para que serve o arquivo. Aqui estão alguns tipos de arquivos comuns.
NÚCLEO (valor 4)
DYN (arquivo de objeto compartilhado), biblioteca (valor 3)
EXEC (arquivo executável), arquivo executável (valor 2)
REL (arquivo realocável), arquivo antes da vinculação (valor 1)
Veja o título completo
Embora alguns campos possam ser visualizados por si mesmo, na verdade existem mais. Por exemplo, você pode descobrir para qual processador o arquivo se destina. Use hexdump para ver o cabeçalho ELF completo e todos os valores.
7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 02 00 3e 00 01 00 00 00 a8 2b 40 00 00 00 00 00 |..>......+@.....| 40 00 00 00 00 00 00 00 30 65 01 00 00 00 00 00 |@.......0e......| 00 00 00 00 40 00 38 00 09 00 40 00 1c 00 1b 00 |....@.8...@.....|
(saída hexdump -C -n 64 / bin / ps)
O campo destacado determina o tipo de máquina. O valor 3e é decimal 62, que corresponde ao AMD64. Para ter uma idéia de todos os tipos de arquivo, consulte
este arquivo de cabeçalho.
Embora você possa fazer tudo isso em um dump hexadecimal, faz sentido usar uma ferramenta que faça o trabalho para você. O utilitário dumpelf pode ser útil. Ele mostra a saída formatada correspondente ao cabeçalho ELF. Será bom estudar quais campos são usados e quais são seus valores típicos.
Agora, onde explicamos o significado desses campos, é hora de olhar para o que há por trás da mágica real e passar para os próximos títulos!
Dados do arquivo
Além do cabeçalho, os arquivos ELF consistem em três partes.
- Cabeçalhos ou segmentos de programa
- Seção ou títulos de seção
- Dados
Antes de mergulharmos nesses cabeçalhos, seria útil saber que o arquivo ELF possui dois "tipos" diferentes. Um deles é projetado para o vinculador e permite a execução de código (segmentos). O outro é para comandos e dados (seções). Dependendo da finalidade, o tipo de cabeçalho apropriado é usado. Vamos começar com o cabeçalho do programa, localizado nos arquivos executáveis do ELF.
Títulos do programa
Um arquivo ELF consiste em zero ou mais segmentos e descreve como criar um processo, uma imagem de memória para execução em tempo de execução. Quando o kernel vê esses segmentos, os coloca no espaço de endereço virtual usando a chamada de sistema mmap (2). Em outras palavras, converte instruções pré-preparadas em uma imagem na memória. Se o arquivo ELF for um binário regular, ele requer esses cabeçalhos de programa, caso contrário, simplesmente não funcionará. Esses cabeçalhos são usados, juntamente com as estruturas de dados correspondentes, para formar o processo. Para bibliotecas compartilhadas, o processo é semelhante.

Cabeçalho do programa no arquivo ELF binário
Vemos 9 títulos de programas neste exemplo. No começo, é difícil entender o que eles significam. Vamos mergulhar nos detalhes.
GNU_EH_FRAME
Essa é a fila classificada usada pelo compilador GCC. Ele armazena manipuladores de exceção. Se algo der errado, eles são usados para lidar corretamente com a situação.
GNU_STACK
Este cabeçalho é usado para salvar as informações da pilha. Um recurso interessante é que a pilha não precisa ser executável, pois isso pode acarretar vulnerabilidades de segurança.
Se o segmento GNU_STACK estiver ausente, a pilha executável será usada. Os utilitários scanelf e execstack mostram os detalhes do dispositivo de pilha.
Comandos para visualizar o cabeçalho do programa:
- dumpelf (pax-utils)
- elfls -S / bin / ps
- eu-readelf - cabeçalhos de programa / bin / ps
Seções ELF
Cabeçalhos de Seção
Cabeçalhos de seção definem todas as seções de um arquivo. Como já mencionado, essas informações são usadas para vinculação e realocação.
As seções são exibidas em um arquivo ELF após o compilador GNU C converter o código C em assembler, e o assembler GNU cria objetos.
Conforme mostrado na figura acima, um segmento pode ter 0 ou mais seções. Existem quatro seções principais para arquivos executáveis: .text, .data, .rodata e .bss. Cada uma dessas seções é inicializada com permissões diferentes, que podem ser visualizadas com readelf -S.
.text
Contém código executável. Ele será empacotado em um segmento com direitos de leitura e execução. É baixado uma vez e seu conteúdo não muda. Isso pode ser visto com o utilitário objdump.
12 .text 0000a3e9 0000000000402120 0000000000402120 00002120 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE
.data
Dados inicializados com permissões de leitura e gravação.
.rodata
Dados inicializados com permissões somente leitura. (= A).
.bss
Dados não inicializados com permissões de leitura / gravação. (= WA)
[24] .data PROGBITS 00000000006172e0 000172e0 0000000000000100 0000000000000000 WA 0 0 8 [25] .bss NOBITS 00000000006173e0 000173e0 0000000000021110 0000000000000000 WA 0 0 32
Comandos para visualizar seções e títulos.
- despejar
- elfls -p / bin / ps
- eu-readelf - cabeçalhos de seção / bin / ps
- readelf -S / bin / ps
- objdump -h / bin / ps
Grupos de Seção
Algumas seções podem ser agrupadas como se elas formassem um único todo. Novos vinculadores suportam essa funcionalidade. Mas enquanto isso não é comum.
Embora isso possa não parecer muito interessante, o conhecimento das ferramentas de análise de arquivo ELF oferece grandes benefícios. Por esse motivo, é apresentada uma visão geral dessas ferramentas e seus objetivos no final do artigo.
Binários estáticos e dinâmicos
Ao lidar com binários ELF, será útil saber como esses dois tipos de arquivos estão vinculados. Eles podem ser estáticos e dinâmicos, e isso se aplica às bibliotecas que eles usam. Se o binário for "dinâmico", significa que ele usa bibliotecas externas que contêm algumas funções comuns, como abrir um arquivo ou criar um soquete de rede. Os binários estáticos, por outro lado, incluem todas as bibliotecas necessárias.
Se você deseja verificar se o arquivo é estático ou dinâmico, use o comando file. Ela mostrará algo assim:
$ file /bin/ps /bin/ps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), <b>dynamically linked (uses shared libs)</b>, for GNU/Linux 2.6.24, BuildID[sha1]=2053194ca4ee8754c695f5a7a7cff2fb8fdd297e, stripped
Para determinar quais bibliotecas externas são usadas, basta usar ldd no mesmo binário:
$ ldd /bin/ps linux-vdso.so.1 => (0x00007ffe5ef0d000) libprocps.so.3 => /lib/x86_64-linux-gnu/libprocps.so.3 (0x00007f8959711000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f895934c000) /lib64/ld-linux-x86-64.so.2 (0x00007f8959935000)
Dica: Para ver mais dependências, é melhor usar o utilitário lddtree.
Ferramentas de análise binária
Se você deseja analisar arquivos ELF, definitivamente será útil examinar primeiro as ferramentas existentes. Existem kits de ferramentas para o desenvolvimento reverso de binários e código executável. Se você é novo na análise de arquivos ELF, comece com a análise estática. A análise estática implica que examinemos os arquivos sem iniciá-los. Quando você começar a entender melhor o trabalho deles, passe para a análise dinâmica. Execute os exemplos e observe o comportamento real deles.
Ferramentas populares
Radare2
O kit de ferramentas Radare2 foi criado por Sergi Alvarez. O número 2 implica que o código foi completamente reescrito em comparação com a primeira versão. Agora, é usado por muitos pesquisadores para estudar a operação do código.
Pacotes de software
A maioria dos sistemas Linux possui binutils instalados. Outros pacotes podem ajudá-lo a ver mais informações. O kit de ferramentas correto simplificará seu trabalho, especialmente se você estiver analisando arquivos ELF. Eu compilei aqui uma lista de pacotes e utilitários para analisar arquivos ELF.
elfutils/ usr / bin / eu-addr2line
/ usr / bin / eu-ar - uma alternativa ao ar, para criar e processar arquivos compactados
/ usr / bin / eu-elfcmp
/ usr / bin / eu-elflint - verifique a conformidade com as especificações gABI e psABI
/ usr / bin / eu-findtextrel - pesquisa por realocações de texto
/ usr / bin / eu-ld - combina arquivos de objetos e archive
/ usr / bin / eu-make-debug-archive
/ usr / bin / eu-nm - mostra os símbolos do objeto e dos arquivos executáveis
/ usr / bin / eu-objdump - mostra informações do arquivo de objeto
/ usr / bin / eu-ranlib - cria um índice de arquivos compactados
/ usr / bin / eu-readelf - mostra o arquivo ELF em formato legível
/ usr / bin / eu-size - mostra o tamanho de cada seção (texto, dados, bss, etc)
/ usr / bin / eu-stack - mostra a pilha do processo atual ou despejo do kernel
/ usr / bin / eu-strings - mostra as strings de texto (como o utilitário strings)
/ usr / bin / eu-strip - remove a tabela de caracteres do arquivo ELF
/ usr / bin / eu-unstrip - adiciona símbolos e informações de depuração ao binário
Nota: o pacote elfutils será um bom começo, contém a maioria das ferramentas de análise
elfkickers/ usr / bin / ebfc - Compilador de idiomas do Brainfuck
/ usr / bin / elfls - mostra cabeçalhos de programas e cabeçalhos de seção com sinalizadores
/ usr / bin / elftoc - converte um binário em um programa C
/ usr / bin / infect - um utilitário que injeta um conta-gotas cria um arquivo setuid em / tmp
/ usr / bin / objres - cria um objeto a partir de dados regulares ou binários
/ usr / bin / rebind - altera a ligação e a visibilidade dos caracteres nos arquivos ELF
/ usr / bin / sstrip - remove componentes desnecessários de um arquivo ELF
Nota: o autor do pacote ELFKickers se concentra na manipulação de arquivos ELF, o que permite obter mais informações ao trabalhar com os binários ELF "errados"
pax-utils/ usr / bin / dumpelf - despejo da estrutura ELF interna
/ usr / bin / lddtree - como o ldd, com a configuração do nível de dependências exibido
/ usr / bin / pspax - exibe informações de ELF / PaX sobre processos em execução
/ usr / bin / scanelf - uma ampla variedade de informações, incluindo detalhes de PaX
/ usr / bin / scanmacho - mostra detalhes dos binários Mach-O (Mac OS X)
/ usr / bin / symtree - mostra os caracteres da árvore
Nota: alguns utilitários deste pacote podem varrer recursivamente os diretórios e são ideais para analisar todo o conteúdo de um diretório. O foco está nas ferramentas de pesquisa PaX. Além de oferecer suporte ao ELF, você pode extrair informações dos binários Mach-O.
Exemplo de saída
scanelf -a /bin/ps TYPE PAX PERM ENDIAN STK/REL/PTL TEXTREL RPATH BIND FILE ET_EXEC PeMRxS 0755 LE RW- R-- RW- - - LAZY /bin/ps
pré-ligação/ usr / bin / execstack - você pode exibir ou alterar informações sobre se a pilha é executável
/ usr / bin / prelink - realoca chamadas em arquivos ELF para acelerar o processo
Perguntas frequentes
O que é uma ABI?
ABI é a Interface Binária do Aplicativo e define uma interface de baixo nível entre o sistema operacional e o código executável.
O que é ELF?
ELF é um formato executável e vinculável. Essa é uma especificação de formato que define como as instruções são escritas no código executável.
Como posso ver o tipo de arquivo?
Use o comando file para o primeiro estágio da análise. Este comando é capaz de mostrar os detalhes extraídos dos números e títulos "mágicos".
Conclusão
Os arquivos ELF são para execução e vinculação. Dependendo da finalidade, eles contêm os segmentos e seções necessários. O kernel do sistema operacional varre os segmentos e os mapeia na memória (usando o mmap). As seções são exibidas por um vinculador que cria um arquivo executável ou objeto compartilhado.
Os arquivos ELF são muito flexíveis e suportam vários tipos de CPUs, arquiteturas de máquinas e sistemas operacionais. Também é extensível, cada arquivo é projetado de maneira diferente, dependendo das peças necessárias. Usando as ferramentas certas, você pode descobrir a finalidade do arquivo e examinar o conteúdo dos arquivos binários. Você pode visualizar as funções e linhas contidas no arquivo. Um bom começo para quem pesquisa malware ou para entender por que o processo se comporta (ou não) de uma certa maneira.
Recursos para estudos adicionais
Se você quiser saber mais sobre ELF e engenharia reversa, poderá ver o trabalho que fazemos no Linux Security Expert. Como parte do currículo, temos
um módulo de engenharia reversa com trabalho prático em laboratório.
Para aqueles que gostam de ler, um documento bom e profundo: o
formato ELF e um
artigo de autoria de Brian Raiter, também conhecido como ELFkickers. Para quem gosta de entender a fonte, veja o
cabeçalho ELF documentado da Apple.
Dica:
Se você deseja melhorar a análise de arquivos, comece a usar as
populares ferramentas de análise atualmente disponíveis.