darkcrow Postado Janeiro 6, 2020 Postado Janeiro 6, 2020 Olá, reversers, esse é meu primeiro post no fórum. Chamo-me Mateus Gualberto, mais conhecido (ainda nem tão conhecido) por darkcrow em competições CTF pelo time RATF e membro do projeto de extensão Residência em Segurança da Informação (RSI) na Universidade Federal do Ceará. Atenção! Apesar de ser um pequeno resumo de uma parte do formato ELF, é recomendado ao leitor ter conhecimentos prévios de engenharia reversa no geral, assim como termos como virtual address (endereço virtual), offsets, hexdump, arquitetura de computadores, etc. Hoje venho falar um pouco acerca do Executable and Linkable Format, ou simplesmente formato ELF, muito utilizado por sistemas Linux/BSDs. Para começar, falando um pouco a respeito de extensões-padrão, geralmente o mesmo não utiliza extensões, mas é bem comum de vermos executáveis .elf, .bin e bibliotecas Linux como .so. Mas também venho lembrar-lhes que extensão é só uma parte do nome do arquivo, não influencia a estrutura do mesmo. O formato ELF é dividido em: Elf Header Program Header Table Seções/Segmentos (não são a mesma coisa, explicarei mais a frente essa diferença) Section Header Table Uma imagem para esclarecer melhor essa organização: Então esse é o "molde" que nossos programas para Linux irão se encaixar ao serem compilados com sucesso. Antes de prosseguirmos para uma parte mais pesada, vamos a um resumo básico da função de cada parte de um ELF: Elf Header: É a estrutura inicial do ELF, que inicia no byte 0 de todo binário. Ela é responsável pela identificação do formato por ferramentas como o find, além de prover informações importantes sobre o binário, como arquitetura, offsets da Section Header Table e Program Header Table, tamanhos das mesmas e até mesmo o Entry Point do binário. Program Header Table: Estrutura que estarão contidas outras estruturas, chamadas de Program Headers, que contêm informações necessárias para o carregamento de um executável, como a descrição de segmentos. Logo, é mandatório que um executável ELF contenha essa tabela, enquanto que arquivos-objeto ela é opcional. Seções/Segmentos: Podemos pensar em seções como delimitações das partes dentro do formato ELF com um propósito, como armazenar código, dados ou dados read-only. Já os segmentos contém as seções, ou seja, as seções estão dentro de segmentos, e eles organizam as seções de acordo com o tipo das mesmas. Por exemplo: suponha que eu tenha .data e .data1, seções de dados em um binário. As mesmas ficarão no mesmo tipo de segmento, o segmento de dados. Além disso, segmentos são utilizados na execução de um executável, enquanto que seções por si só, não. Section Header Table: Estrutura que guardará estruturas Section Header, que contém informações acerca das seções do binário analisado. Podemos extrair diversas informações da análise desses cabeçalhos, como nome das seções, tipo, tamanho, offset, entre outros dados. Agora que você, leitor, já entendeu o básico de cada uma dessas partes, vamos para uma análise mais técnica, começando pelo Elf Header. Porém, não irei explicar cada opção de cada campo de cada estrutura, pois além disso ser maçante para a leitura, iria render um livro (rs). Como explicado anteriormente, o Elf Header contém informações vitais do nosso binário, como localização das tabelas e do Entry Point. Segundo o manual do ELF (man elf), a struct em C que o forma é: #define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; // Identificação do arquivo e informações de ABI uint16_t e_type; // Tipo do binário, se é um executável, uma lib, um arquivo-objeto uint16_t e_machine; // Arquitetura para qual o binário foi compilado uint32_t e_version; // Versão do arquivo ElfN_Addr e_entry; // Entry Point ElfN_Off e_phoff; // Offset da Program Header Table ElfN_Off e_shoff; // Offset da Section Header Table uint32_t e_flags; // Flags específicas do processador uint16_t e_ehsize; // Tamanho, em bytes, do Elf Header uint16_t e_phentsize; // Tamanho, em bytes, de uma entrada na Program Header Table uint16_t e_phnum; // Número de entradas da Program Header Table uint16_t e_shentsize; // Tamanho, em bytes, de uma entrada na Section Header Table uint16_t e_shnum; // Número de entradas da Section Header Table uint16_t e_shstrndx; // Contém o indíce da entrada da tabela de nomes de seções na Section Header Table } ElfN_Ehdr; Tentei resumir cada entrada para uma melhor leitura e entendimento, mas tenho que explicar algumas coisas: Substitua o N nos campos/estruturas ElfN_ por 32 ou 64, dependendo da arquitetura do binário (isso vem do manual do ELF). Sobre os campos: e_ident: Array de 16 bytes que contém a identificação do arquivo ELF (\x7f E L F) e informações acerca de endianess e sobre a Application Binary Interface (https://en.wikipedia.org/wiki/Application_binary_interface). e_type: define o tipo de arquivo, podendo ser um desses valores: ET_NONE: Tipo desconhecido. ET_REL: Arquivo-objeto, também chamado de relocável ET_EXEC: Arquivo executável. ET_DYN: lib, tamém conhecida como shared object. ET_CORE: core file, arquivo que guarda informações sobre crashes em aplicações. e_machine: Define a arquitetura do arquivo ELF. Valores comuns são: EM_X86_64: amd64 EM_386: i386 e_entry: Contém o endereço virtual de onde o binário deverá ser executado. Geralmente apontará para o segmento de texto, que contém a seção .text, de onde começa a execução dos códigos. e_phoff e e_shoff contêm offsets dentro do arquivo, ou seja, não são offsets em memória. Isso significa que eles apontam para onde começa suas respectivas tabelas dentro do arquivo, não usando um endereço virtual. e_phentsize e e_shentsize se referem não ao tamanho da tabela, e sim de uma entrada de um header dela (um Program Header ou um Section Header). É necessário o tamanho de apenas um cabeçalho pois todas as entradas têm o mesmo tamanho. e_shstrndx se refere a uma tabela de strings que é referenciada pela Section Header Table. A partir daí, você, leitor, consegue ver a imensidão de possibilidades que o formato ELF nos dá, visto que essa estrutura não foi nem citada anteriormente nem está na figura principal desse post. Assim como essa estrutura existem diversas outras que iremos estudar aos poucos nessa série sobre o formato ELF. Dado as devidas explicações técnicas, podemos agora analisar um arquivo ELF usando uma das ferramentas mais úteis para análise binária nesse formato de arquivo: readelf, que faz parte do pacote binutils. Caso ele não esteja instalado ainda, e esteja utilizando uma distribuição baseada em Debian, só utilizar o comando: sudo apt update -y && sudo apt install binutils Vamos à prática! Compilei um binário escrito em C, um simples "Hello World!", compilei-o com o nome hello, com a opção -no-pie no gcc (explicarei em outro post a respeito disso, mas é para o compilador gerar um binário do tipo ET_EXEC) . Vamos ver como o readelf nos mostra o Elf Header: readelf -h hello Saída: ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400400 Start of program headers: 64 (bytes into file) Start of section headers: 6376 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 29 Section header string table index: 28 Como podemos ver, o readelf nos traz informações bem mais explícitas que um hexdump nos traria. Mas isso não quer dizer que devamos deixar de lado ferramentas do tipo hexdump, por isso deixarei um breve desafio para os leitores desse post. Tentem encontrar onde está o Elf Header e procurem assimilar as informações descritas acima só que no dump do mesmo arquivo a seguir. Caso sentirem-se à vontade, podem postar as respostas desse desafio aqui abaixo. Enfim, agradeço-os pela leitura e em breve irei continuar essa série. Dúvidas e sugestões podem ser postadas, irei olhar sempre que tiver tempo ? [OFF] No mais, estou planejando produzir um curso de Engenharia Reversa/Análise de binários focada em Linux e no formato ELF. Estou pensando de postar no youtube para fortalecer a comunidade de engenharia reversa no Brasil. Minha grande inspiração para esse curso e essa grandiosa área foi o Fernando Mercês.
fabioccoelho Postado Janeiro 6, 2020 Postado Janeiro 6, 2020 Ótima explicação sobre o ELF, que venha a PARTE 2 :-) Parabéns.
unc4nny Postado Março 24, 2020 Postado Março 24, 2020 Opa! To atrasado aqui mano, mas eu nao sei se eu to viajando, mas qual a diferenca entre o layout da memoria virtual de um executavel e as secoes ELF? Eles parecem ser 2 coisas diferentes, mas parecem ser muito similares. Eu to viajando?
darkcrow Postado Abril 6, 2020 Autor Postado Abril 6, 2020 Em 24/03/2020 em 18:01, unc4nny disse: Opa! To atrasado aqui mano, mas eu nao sei se eu to viajando, mas qual a diferenca entre o layout da memoria virtual de um executavel e as secoes ELF? Eles parecem ser 2 coisas diferentes, mas parecem ser muito similares. Eu to viajando? O endereçamento virtual definirá o layout da memória da imagem (ou processo) do binário quando o mesmo for executado. O range de memória definido irá abrigar os segmentos, como na foto que você postou, mas também conterá outras informações e regiões de memória, como a Stack, utilizada para variáveis locais e dados que tem uma alta volatilidade, e a memória alocável dinâmica. Segmentos definem uma região de memória com um fim específico, com suas próprias flags e permissões. Eles são construídos com base nas informações das seções de um ET_REL, no processo de linkagem de um executável ou shared object. Uma ou mais seções são utilizadas para a contrução um segmento, que definirá uma região de memória dentre o range estabelecido pelo layout de memória virtual. Em resumo: seções são apenas um componente para um segmento; um segmento define, entre outras informações, uma região de memória que estará no range do layout da memória virtual, que engloba vários outros dados.
Posts Recomendados
Arquivado
Este tópico foi arquivado e está fechado para novas respostas.