Compreendendo o formato de arquivo ELF

Compreendendo o formato de arquivo ELF

Do código -fonte ao código binário

A programação começa com uma idéia inteligente e escrevendo código -fonte em uma linguagem de programação de sua escolha, por exemplo C, e salvar o código -fonte em um arquivo. Com a ajuda de um compilador adequado, por exemplo, GCC, seu código -fonte é traduzido para o código do objeto, primeiro. Eventualmente, o vinculador traduz o código do objeto em um arquivo binário que vincula o código do objeto com as bibliotecas referenciadas. Este arquivo contém as instruções únicas como código de máquina que são entendidas pela CPU e são executadas assim que o programa compilado é executado.

O arquivo binário mencionado acima segue uma estrutura específica, e um dos mais comuns é chamado Elf que abrevia o formato executável e vinculado. É amplamente utilizado para arquivos executáveis, arquivos de objeto relocáveis, bibliotecas compartilhadas e despejos principais.

Vinte anos atrás - em 1999 - O projeto 86open escolheu o elfo como o formato de arquivo binário padrão para sistemas do tipo UNIX e UNIX nos processadores x86. Felizmente, o formato ELF havia sido documentado anteriormente na interface binária do System V Application e no padrão da interface da ferramenta [4]. Esse fato simplificou enormemente o acordo sobre a padronização entre os diferentes fornecedores e desenvolvedores de sistemas operacionais baseados em UNIX.

A razão por trás dessa decisão foi o design do ELF - flexibilidade, extensibilidade e suporte de plataforma cruzada para diferentes formatos e tamanhos de endereço endianos. O design da ELF não se limita a um processador específico, conjunto de instruções ou arquitetura de hardware. Para uma comparação detalhada dos formatos de arquivo executável, dê uma olhada aqui [3].

Desde então, o formato ELF está em uso por vários sistemas operacionais diferentes. Entre outros, isso inclui Linux, Solaris/Illumos, Livre, Net- e OpenBSD, QNX, Beos/Haiku e Fuchsia OS [2]. Além disso, você o encontrará em dispositivos móveis executando Android, Maemo ou Meego OS/Sailfish OS, bem como em consoles de jogo como o PlayStation Portable, Dreamcast e Wii.

A especificação não esclarece a extensão do nome do arquivo para arquivos ELF. Em uso está uma variedade de combinações de cartas, como .AXF, .BIN, .duende, .o, .Prx, .sopro, .Ko, .então e .mod, ou nenhum.

A estrutura de um arquivo ELF

Em um terminal Linux, o Comando Man Elf fornece um resumo útil sobre a estrutura de um arquivo ELF:

Listagem 1: a manpra da estrutura do elfo

$ MAN ELF
ELF (5) elfo manual do programador Linux (5)
NOME
ELF - Formato de arquivos de formato executável e de vinculação (ELF)
SINOPSE
#incluir
DESCRIÇÃO
O arquivo de cabeçalho define o formato do binário executável da ELF
arquivos. Entre esses arquivos, há arquivos executáveis ​​normais, relocáveis
Arquivos de objeto, arquivos principais e bibliotecas compartilhadas.
Um arquivo executável usando o formato de arquivo ELF consiste em um cabeçalho de elfo,
seguido por uma tabela de cabeçalho do programa ou uma tabela de cabeçalho de seção, ou ambos.
O cabeçalho do elfo está sempre no deslocamento zero do arquivo. O programa
tabela de cabeçalho e o deslocamento da tabela de cabeçalho da seção no arquivo estão
definido no cabeçalho do elfo. As duas tabelas descrevem o resto do
particularidades do arquivo.
..

Como você pode ver na descrição acima, um arquivo ELF consiste em duas seções - um cabeçalho de elfo e dados do arquivo. A seção de dados de arquivos pode consistir em uma tabela de cabeçalho do programa que descreve zero ou mais segmentos, uma tabela de cabeçalho de seção que descreve zero ou mais seções, seguidas de dados referidos pelas entradas da tabela de cabeçalho do programa e a tabela de cabeçalho da seção. Cada segmento contém informações necessárias para a execução do tempo de execução do arquivo, enquanto as seções contêm dados importantes para vincular e realocação. A Figura 1 ilustra isso esquematicamente.

O cabeçalho do elfo

O cabeçalho do elfo tem 32 bytes de comprimento e identifica o formato do arquivo. Começa com uma sequência de quatro bytes únicos que são 0x7f, seguidos por 0x45, 0x4c e 0x46, que se traduz nas três letras e, l e f. Entre outros valores, o cabeçalho também indica se é um arquivo ELF para formato de 32 ou 64 bits, usa pouco ou grande endianness, mostra a versão ELF, bem como para qual sistema operacional o arquivo foi compilado para interoperar com o Interface binária do aplicativo direito (ABI) e conjunto de instruções da CPU.

O hexdump do arquivo binário touch parece o seguinte:

.Listagem 2: O hexdump do arquivo binário

$ hd/usr/bin/touch | cabeça -5
00000000 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 |.Elfo… |
00000010 02 00 3E 00 01 00 00 00 E3 25 40 00 00 00 00 00 |…>… %@… |
00000020 40 00 00 00 00 00 00 00 28 E4 00 00 00 00 00 00 |@… (… |
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1B 00 1A 00 | @ @.8… @… |
00000040 06 00 00 05 00 00 00 40 00 00 00 00 00 00 00 00 |… @… |

Debian GNU/Linux oferece o comando de leitura que é fornecido no pacote GNU 'Binutils'. Acompanhado pelo switch -h (versão curta para "-file-header"), ele exibe bem o cabeçalho de um arquivo ELF. Listagem 3 ilustra isso para o comando toque.

.Listagem 3: Exibindo o cabeçalho de um arquivo ELF

$ leitel -h/usr/bin/touch
Cabeçalho de elfo:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Classe: ELF64
Dados: complemento de 2, Little Endian
Versão: 1 (atual)
OS/ABI: UNIX - System V
Versão abi: 0
Tipo: EXEC (arquivo executável)
Máquina: Micro Dispositivos Avançados X86-64
Versão: 0x1
Endereço do ponto de entrada: 0x4025e3
Início dos cabeçalhos do programa: 64 (bytes no arquivo)
Início dos cabeçalhos da seção: 58408 (bytes no arquivo)
Bandeiras: 0x0
Tamanho deste cabeçalho: 64 (bytes)
Tamanho dos cabeçalhos do programa: 56 (bytes)
Número de cabeçalhos do programa: 9
Tamanho dos cabeçalhos da seção: 64 (bytes)
Número de cabeçalhos de seção: 27
Seção Cabeçalho Tabela Tabela Índice: 26

O cabeçalho do programa

O cabeçalho do programa mostra os segmentos usados ​​em tempo de execução e diz ao sistema como criar uma imagem de processo. O cabeçalho da Listagem 2 mostra que o arquivo ELF consiste em 9 cabeçalhos do programa que têm um tamanho de 56 bytes cada, e o primeiro cabeçalho começa no Byte 64.

Novamente, o comando de leitura ajuda a extrair as informações do arquivo ELF. O Switch -l (abreviação para -programas ou -segmentos) revela mais detalhes, como mostrado na Listagem 4.

.Listagem 4: Exibir informações sobre os cabeçalhos do programa

$ leitel -l/usr/bin/touch
O tipo de arquivo ELF é exec (arquivo executável)
Ponto de entrada 0x4025E3
Existem 9 cabeçalhos do programa, começando no deslocamento 64
Cabeçalhos do programa:
Tipo Offset Virtaddr Physaddr
Filesiz Memsiz sinalizadores alinhados
PHDR 0X0000000000000040 0X0000000000400040 0X00000000400040
0x00000000000001F8 0x0000000000000001F8 R E 8
Interp 0x0000000000000238 0x000000000000400238 0x0000000000400238
0x000000000000001C 0x00000000000000001C R 1
[Programa de solicitação de intérprete: /lib64 /ld-linux-x86-64.então.2]
Carregar 0x0000000000000000 0x000000000000400000 0x0000000000400000
0x000000000000D494 0x000000000000D494 R E 200000
Carregar 0x000000000000DE10 0x000000000060DE10 0x0000000060DE10
0x0000000000000524 0x0000000000000748 RW 200000
Dinâmico 0x000000000000DE28 0x000000000060DE28 0x0000000060DE28
0x00000000000001D0 0x00000000000001D0 RW 8
Nota 0x0000000000000254 0x000000000000400254 0x0000000000400254
0x0000000000000044 0x000000000000000044 R 4
GNU_EH_FRAME 0X0000000000BC40 0X0000000040BC40
0x00000000000003A4 0x00000000000003A4 R 4
GNU_STACK 0X0000000000000000 0X0000000000000000 0X0000000000000000
0x0000000000000000 0x000000000000000000 RW 10
GNU_RELRO 0X0000000000DE10 0X000000000060DE10 0X0000000060DE10
0x00000000000001F0 0x0000000000000001F0 R 1
Seção para mapeamento de segmento:
Seções de segmento…
00
01 .interp
02 .interp .observação.Abi-tag .observação.gnu.Build-id .gnu.cerquilha .DynSym .Dyr .gnu.versão .gnu.versão_r .RELA.Dyn .RELA.plt .iniciar .plt .texto .Fini .Rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dinâmico .pegou .pegou.plt .dados .BSS
04 .dinâmico
05 .observação.Abi-tag .observação.gnu.Build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dinâmico .pegou

O cabeçalho da seção

A terceira parte da estrutura dos elfos é o cabeçalho da seção. Deve listar as seções únicas do binário. O Switch -s (abreviação de cabeçalhos ou -seções) lista os diferentes cabeçalhos. Quanto ao comando Touch, existem 27 cabeçalhos de seção, e a lista 5 mostra os quatro primeiros deles mais o último, apenas. Cada linha cobre o tamanho da seção, o tipo de seção, bem como seu endereço e deslocamento de memória.

.Listagem 5: Detalhes da seção revelados pelo READELL

$ readfl -s/usr/bin/touch
Existem 27 cabeçalhos de seção, começando no deslocamento 0xe428:
Cabeçalhos de seção:
[NR] Offset de endereço do tipo de nome
Tamanho Entsize sinalizadores link Informações alinhadas
[0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[1] .Interp Progbits 0000000000400238 00000238
000000000000001C 0000000000000000 A 0 0 1
[2] .observação.ABI-TAG Nota 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[3] .observação.gnu.Build-i Nota 0000000000400274 00000274
..
..
[26] .shstrtab strtab 000000000000000000 0000E334
00000000000000EF 0000000000000000 0 0 1
Chave para sinalizadores:
W (escreva), a (aloc), x (execute), m (mescla), s (strings), l (grande)
I (info), l (ordem do link), g (grupo), t (tls), e (exclua), x (desconhecido)
O (Processamento OS extra necessário) o (específico do sistema operacional), P (específico do processador)

Ferramentas para analisar um arquivo ELF

Como você deve ter observado nos exemplos acima, GNU/Linux é desenvolvido com várias ferramentas úteis que ajudam a analisar um arquivo ELF. O primeiro candidato que vamos dar uma olhada é o utilitário de arquivos.

Arquivo Exibe informações básicas sobre arquivos ELF, incluindo a arquitetura do conjunto de instruções para o qual o código em um arquivo de objeto relocável, executável ou compartilhado é pretendido. Na Listagem 6, ele diz que/bin/touch é um arquivo executável de 64 bits seguindo a base padrão do Linux (LSB), ligada dinamicamente e construída para o kernel GNU/Linux versão 2.6.32.

.Listagem 6: Informações básicas usando o arquivo

$ arquivo /bin /touch
/bin/toque: elf 64 bits LSB Executável, x86-64, versão 1 (sysv), ligado dinamicamente, intérprete/lib64/l,
Para GNU/Linux 2.6.32, BuildId [sha1] = EC08D609E9E8E73D4BE6134541A472AD0AEA34502, despojado
$

O segundo candidato está pertencente. Ele exibe informações detalhadas sobre um arquivo ELF. A lista de comutadores é comparável e abrange todos os aspectos do formato ELF. Usando o switch -n (curta para -notes) Listagem 7 mostra as seções de anotações, apenas, que existem no toque do arquivo -a tag de versão da ABI e o idiota Build ID BitString.

.Listagem 7: Exibir seções selecionadas de um arquivo ELF

$ readefl -n/usr/bin/touch
Exibindo notas encontradas no arquivo Offset 0x00000254 com comprimento 0x00000020:
Descrição do tamanho dos dados do proprietário
GNU 0x00000010 NT_GNU_ABI_TAG (tag de versão ABI)
OS: Linux, ABI: 2.6.32
Exibindo notas encontradas no deslocamento do arquivo 0x00000274 com comprimento 0x00000024:
Descrição do tamanho dos dados do proprietário
GNU 0x00000014 nt_gnu_build_id (bitstring de identificação de construção exclusiva)
ID da construção: EC08D609E9E8E73D4BE6134541A472AD0AEA34502

Observe que em Solaris e FreeBSD, o utilitário elfdump [7] corresponde ao Readfl. A partir de 2019, não houve um novo lançamento ou atualização desde 2003.

O número três é o pacote chamado Elfutils [6] que está puramente disponível para Linux. Ele fornece ferramentas alternativas para o GNU binutils e também permite validar os arquivos ELF. Observe que todos os nomes dos utilitários fornecidos no pacote começam com a UE para 'elfs utils'.

Por último, mas não menos importante, mencionaremos o objdump. Esta ferramenta é semelhante ao Readef, mas se concentra nos arquivos de objeto. Ele fornece uma gama semelhante de informação sobre arquivos ELF e outros formatos de objeto.

.Listagem 8: Informações de arquivo extraídas por Objdump

$ objdump -f /bin /touch
/bin/toque: formato de arquivo elf64-x86-64
Arquitetura: i386: x86-64, sinalizadores 0x00000112:
Exec_p, has_syms, d_paged
Endereço inicial 0x00000000004025E3
$

Há também um pacote de software chamado 'elfkickers' [9] que contém ferramentas para ler o conteúdo de um arquivo ELF, além de manipulá -lo. Infelizmente, o número de lançamentos é bastante baixo, e é por isso que apenas mencionamos, e não mostra mais exemplos.

Como desenvolvedor, você pode dar uma olhada em 'pax-utils' [10,11], em vez disso. Este conjunto de utilitários fornece uma série de ferramentas que ajudam a validar arquivos ELF. Como exemplo, o DumpEfl analisa o arquivo ELF e retorna um arquivo de cabeçalho C contendo os detalhes - veja a Figura 2.

Conclusão

Graças a uma combinação de design inteligente e excelente documentação, o formato ELF funciona muito bem e ainda está em uso após 20 anos. Os utilitários mostrados acima permitem uma visão de insight em um arquivo ELF e permite descobrir o que um programa está fazendo. Estes são os primeiros passos para analisar o software - Happy Hacking!

Links e referências
  • [1] Formato executável e vinculado (ELF), Wikipedia
  • [2] FUCHSIA OS
  • [3] Comparação de formatos de arquivo executável, Wikipedia
  • [4] Linux Foundation, Especificações referenciadas
  • [5] Ciro Santilli: Elf Hello World Tutorial
  • [6] ELFUTILS Debian Package
  • [7] Elfdump
  • [8] Michael Boelen: os 101 dos arquivos ELF no Linux: Compreensão e análise
  • [9] Elfkickers
  • [10] Utilitários endurecidos/Pax
  • [11] Pax-utils, pacote Debian
Reconhecimentos

O escritor gostaria de agradecer a Axel Beckert por seu apoio em relação à preparação deste artigo.