Você trabalha em uma empresa de cibersegurança? Quer ver sua empresa anunciada no HackTricks? ou quer ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Confira os PLANOS DE ASSINATURA!
O programa anterior tem 9 cabeçalhos de programa, então, o mapeamento de segmentos indica em qual cabeçalho de programa (de 00 a 08) cada seção está localizada.
PHDR - Program HeaDeR
Contém as tabelas de cabeçalho do programa e os metadados em si.
INTERP
Indica o caminho do carregador a ser usado para carregar o binário na memória.
LOAD
Esses cabeçalhos são usados para indicar como carregar um binário na memória.
Cada cabeçalho LOAD indica uma região de memória (tamanho, permissões e alinhamento) e indica os bytes do ELF binário a serem copiados lá dentro.
Por exemplo, o segundo tem um tamanho de 0x1190, deve estar localizado em 0x1fc48 com permissões de leitura e escrita e será preenchido com 0x528 a partir do deslocamento 0xfc48 (não preenche todo o espaço reservado). Esta memória conterá as seções .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Este cabeçalho ajuda a vincular programas às suas dependências de biblioteca e aplicar realocações. Verifique a seção .dynamic.
NOTE
Armazena informações de metadados do fornecedor sobre o binário.
GNU_EH_FRAME
Define a localização das tabelas de desenrolamento de pilha, usadas por depuradores e funções de tempo de execução de tratamento de exceções C++.
GNU_STACK
Contém a configuração da defesa de prevenção de execução de pilha. Se ativado, o binário não poderá executar código da pilha.
GNU_RELRO
Indica a configuração RELRO (Relocation Read-Only) do binário. Essa proteção marcará como somente leitura certas seções da memória (como o GOT ou as tabelas init e fini) após o programa ter sido carregado e antes de começar a ser executado.
No exemplo anterior, está copiando 0x3b8 bytes para 0x1fc48 como somente leitura afetando as seções .init_array .fini_array .dynamic .got .data .bss.
Observe que o RELRO pode ser parcial ou completo, a versão parcial não protege a seção .plt.got, que é usada para ligação preguiçosa e precisa que este espaço de memória tenha permissões de escrita para escrever o endereço das bibliotecas na primeira vez que sua localização é pesquisada.
TLS
Define uma tabela de entradas TLS, que armazena informações sobre variáveis locais de thread.
Cabeçalhos de Seção
Os cabeçalhos de seção fornecem uma visão mais detalhada do binário ELF.
Tabela de strings: Contém todas as strings necessárias pelo arquivo ELF (mas não aquelas realmente usadas pelo programa). Por exemplo, contém nomes de seções como .text ou .data. E se .text estiver no offset 45 na tabela de strings, usará o número 45 no campo name.
Para encontrar onde está a tabela de strings, o ELF contém um ponteiro para a tabela de strings.
Tabela de símbolos: Contém informações sobre os símbolos como o nome (offset na tabela de strings), endereço, tamanho e mais metadados sobre o símbolo.
Seções Principais
.text: As instruções do programa a serem executadas.
.data: Variáveis globais com um valor definido no programa.
.bss: Variáveis globais não inicializadas (ou inicializadas para zero). As variáveis aqui são automaticamente inicializadas para zero, evitando assim zeros inúteis de serem adicionados ao binário.
.rodata: Variáveis globais constantes (seção somente leitura).
.tdata e .tbss: Semelhante ao .data e .bss quando são usadas variáveis locais de thread (__thread_local em C++ ou __thread em C).
.dynamic: Veja abaixo.
Símbolos
Símbolos são locais nomeados no programa que podem ser uma função, um objeto de dados global, variáveis locais de thread...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Cada entrada de símbolo contém:
Nome
Atributos de ligação (fraco, local ou global): Um símbolo local só pode ser acessado pelo programa em si, enquanto os símbolos globais são compartilhados fora do programa. Um objeto fraco é, por exemplo, uma função que pode ser substituída por outra.
Tipo: NOTYPE (nenhum tipo especificado), OBJECT (variável de dados global), FUNC (função), SECTION (seção), FILE (arquivo de código-fonte para depuradores), TLS (variável local de segmento), GNU_IFUNC (função indireta para realocação)
O diretório NEEDED indica que o programa precisa carregar a biblioteca mencionada para continuar. O diretório NEEDED é concluído uma vez que a biblioteca compartilhada está totalmente operacional e pronta para uso.
Realocações
O carregador também deve realocar dependências após tê-las carregado. Essas realocações são indicadas na tabela de realocação nos formatos REL ou RELA e o número de realocações é fornecido nas seções dinâmicas RELSZ ou RELASZ.
Se o programa for carregado em um local diferente do endereço preferido (geralmente 0x400000) porque o endereço já está em uso ou por causa do ASLR ou qualquer outro motivo, uma relocação estática corrige ponteiros que tinham valores esperando que o binário fosse carregado no endereço preferido.
Por exemplo, qualquer seção do tipo R_AARCH64_RELATIV deve ter modificado o endereço no deslocamento da relocação mais o valor do adendo.
Relocações Dinâmicas e GOT
A relocação também pode fazer referência a um símbolo externo (como uma função de uma dependência). Como a função malloc da libC. Então, o carregador ao carregar a libC em um endereço verificando onde a função malloc está carregada, ele escreverá este endereço na tabela GOT (Global Offset Table) (indicado na tabela de relocação) onde o endereço de malloc deve ser especificado.
Tabela de Vínculo de Procedimentos
A seção PLT permite realizar vinculação tardia, o que significa que a resolução da localização de uma função será realizada na primeira vez que ela for acessada.
Portanto, quando um programa chama malloc, na verdade chama a localização correspondente de malloc na PLT (malloc@plt). Na primeira vez que é chamado, ele resolve o endereço de malloc e o armazena para que na próxima vez que malloc for chamado, esse endereço seja usado em vez do código PLT.
Inicialização do Programa
Após o programa ter sido carregado, é hora de executá-lo. No entanto, o primeiro código que é executado nem sempre é a função main. Isso ocorre porque, por exemplo, em C++ se uma variável global for um objeto de uma classe, esse objeto deve ser inicializadoantes da execução do main, como em:
Note que essas variáveis globais estão localizadas em .data ou .bss, mas nas listas __CTOR_LIST__ e __DTOR_LIST__ os objetos a serem inicializados e destruídos são armazenados para acompanhamento.
A partir do código C, é possível obter o mesmo resultado usando as extensões GNU:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
Do ponto de vista de um compilador, para executar essas ações antes e depois da função main ser executada, é possível criar uma função init e uma função fini que seriam referenciadas na seção dinâmica como INIT e FIN e são colocadas nas seções init e fini do ELF.
A outra opção, como mencionado, é referenciar as listas __CTOR_LIST__ e __DTOR_LIST__ nas entradas INIT_ARRAY e FINI_ARRAY na seção dinâmica e o comprimento destas é indicado por INIT_ARRAYSZ e FINI_ARRAYSZ. Cada entrada é um ponteiro de função que será chamado sem argumentos.
Além disso, também é possível ter um PREINIT_ARRAY com ponteiros que serão executados antes dos ponteiros do INIT_ARRAY.
Ordem de Inicialização
O programa é carregado na memória, variáveis globais estáticas são inicializadas em .data e as não inicializadas são zeradas em .bss.
Todas as dependências do programa ou bibliotecas são inicializadas e o linking dinâmico é executado.
As funções PREINIT_ARRAY são executadas.
As funções INIT_ARRAY são executadas.
Se houver uma entrada INIT, ela é chamada.
Se for uma biblioteca, o dlopen termina aqui, se for um programa, é hora de chamar o ponto de entrada real (função main).
Armazenamento Local de Threads (TLS)
São definidos usando a palavra-chave __thread_local em C++ ou a extensão GNU __thread.
Cada thread manterá um local único para essa variável, para que apenas a thread possa acessar sua variável.
Quando isso é usado, as seções .tdata e .tbss são usadas no ELF. Que são como .data (inicializado) e .bss (não inicializado) mas para TLS.
Cada variável terá uma entrada no cabeçalho TLS especificando o tamanho e o deslocamento TLS, que é o deslocamento que será usado na área de dados local da thread.
O __TLS_MODULE_BASE é um símbolo usado para se referir ao endereço base do armazenamento local de threads e aponta para a área na memória que contém todos os dados locais da thread de um módulo.