From c490aead1649dfc3ecf1e897cf98dcc946b9260c Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 18 Aug 2025 20:37:14 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/basic-stack-binary-exploitation-met --- .../elf-tricks.md | 115 ++++++++++++++++-- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md index a451d2590..58e961c63 100644 --- a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md +++ b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md @@ -37,7 +37,7 @@ Segment Sections... 07 08 .init_array .fini_array .dynamic .got ``` -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**. +O programa anterior possui **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 - Cabeçalho do Programa @@ -47,6 +47,8 @@ Contém as tabelas de cabeçalho do programa e os metadados em si. Indica o caminho do carregador a ser usado para carregar o binário na memória. +> Dica: Binários vinculados estaticamente ou static-PIE não terão uma entrada `INTERP`. Nesses casos, não há carregador dinâmico envolvido, o que desabilita técnicas que dependem dele (por exemplo, `ret2dlresolve`). + ### LOAD Esses cabeçalhos são usados para indicar **como carregar um binário na memória.**\ @@ -56,12 +58,18 @@ Por exemplo, o segundo tem um tamanho de 0x1190, deve estar localizado em 0x1fc4 ### DYNAMIC -Esse cabeçalho ajuda a vincular programas às suas dependências de biblioteca e aplicar realocações. Verifique a seção **`.dynamic`**. +Este cabeçalho ajuda a vincular programas às suas dependências de biblioteca e aplicar realocações. Verifique a seção **`.dynamic`**. ### NOTE Isso armazena informações de metadados do fornecedor sobre o binário. +- No x86-64, `readelf -n` mostrará as flags `GNU_PROPERTY_X86_FEATURE_1_*` dentro de `.note.gnu.property`. Se você ver `IBT` e/ou `SHSTK`, o binário foi construído com CET (Rastreamento de Ramificações Indiretas e/ou Pilha Sombra). Isso impacta ROP/JOP porque os alvos de ramificação indireta devem começar com uma instrução `ENDBR64` e os retornos são verificados contra uma pilha sombra. Veja a página CET para detalhes e notas de bypass. + +{{#ref}} +../common-binary-protections-and-bypasses/cet-and-shadow-stack.md +{{#endref}} + ### GNU_EH_FRAME Define a localização das tabelas de desempilhamento de pilha, usadas por depuradores e funções de tempo de execução de tratamento de exceções em C++. @@ -70,13 +78,21 @@ Define a localização das tabelas de desempilhamento de pilha, usadas por depur Contém a configuração da defesa de prevenção de execução de pilha. Se habilitado, o binário não poderá executar código da pilha. +- Verifique com `readelf -l ./bin | grep GNU_STACK`. Para alternar forçosamente durante os testes, você pode usar `execstack -s|-c ./bin`. + ### 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. +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 execução. 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 RELRO pode ser parcial ou total, a versão parcial não protege a seção **`.plt.got`**, que é usada para **lazy binding** e precisa desse espaço de memória para ter **permissões de escrita** para gravar o endereço das bibliotecas na primeira vez que sua localização é pesquisada. +Note que RELRO pode ser parcial ou total, a versão parcial não protege a seção **`.plt.got`**, que é usada para **vinculação preguiçosa** e precisa desse espaço de memória para ter **permissões de escrita** para escrever o endereço das bibliotecas na primeira vez que sua localização é pesquisada. + +> Para técnicas de exploração e notas de bypass atualizadas, verifique a página dedicada: + +{{#ref}} +../common-binary-protections-and-bypasses/relro.md +{{#endref}} ### TLS @@ -149,9 +165,9 @@ Também indica a localização, deslocamento, permissões, mas também o **tipo ### Seções Meta -- **Tabela de strings**: Contém todas as strings necessárias pelo arquivo ELF (mas não as realmente usadas pelo programa). Por exemplo, contém nomes de seções como `.text` ou `.data`. E se `.text` está no deslocamento 45 na tabela de strings, usará o número **45** no campo **nome**. +- **String table**: Contém todas as strings necessárias pelo arquivo ELF (mas não as realmente usadas pelo programa). Por exemplo, contém nomes de seções como `.text` ou `.data`. E se `.text` está no deslocamento 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 (deslocamento na tabela de strings), endereço, tamanho e mais metadados sobre o símbolo. +- **Symbol table**: Contém informações sobre os símbolos, como o nome (deslocamento na tabela de strings), endereço, tamanho e mais metadados sobre o símbolo. ### Seções Principais @@ -194,6 +210,10 @@ Cada entrada de símbolo contém: - **Valor** (endereço na memória) - **Tamanho** +#### Versionamento de Símbolos GNU (dynsym/dynstr/gnu.version) + +A glibc moderna usa versões de símbolos. Você verá entradas em `.gnu.version` e `.gnu.version_r` e nomes de símbolos como `strlen@GLIBC_2.17`. O vinculador dinâmico pode exigir uma versão específica ao resolver um símbolo. Ao criar realocações manuais (por exemplo, ret2dlresolve), você deve fornecer o índice de versão correto, caso contrário, a resolução falha. + ## Seção Dinâmica ``` readelf -d lnstat @@ -231,6 +251,23 @@ Tag Type Name/Value ``` O diretório NEEDED indica que o programa **precisa carregar a biblioteca mencionada** para continuar. O diretório NEEDED é completado uma vez que a **biblioteca compartilhada está totalmente operacional e pronta** para uso. +### Ordem de busca do carregador dinâmico (RPATH/RUNPATH, $ORIGIN) + +As entradas `DT_RPATH` (obsoleto) e/ou `DT_RUNPATH` influenciam onde o carregador dinâmico procura por dependências. Ordem aproximada: + +- `LD_LIBRARY_PATH` (ignorado para programas setuid/sgid ou de "execução segura" de outra forma) +- `DT_RPATH` (somente se `DT_RUNPATH` estiver ausente) +- `DT_RUNPATH` +- `ld.so.cache` +- diretórios padrão como `/lib64`, `/usr/lib64`, etc. + +`$ORIGIN` pode ser usado dentro de RPATH/RUNPATH para se referir ao diretório do objeto principal. Do ponto de vista de um atacante, isso é importante quando você controla o layout do sistema de arquivos ou o ambiente. Para binários endurecidos (AT_SECURE), a maioria das variáveis de ambiente é ignorada pelo carregador. + +- Inspecione com: `readelf -d ./bin | egrep -i 'r(path|unpath)'` +- Teste rápido: `LD_DEBUG=libs ./bin 2>&1 | grep -i find` (mostra decisões de caminho de busca) + +> Dica de priv-esc: Prefira abusar de RUNPATHs graváveis ou caminhos relativos a `$ORIGIN` mal configurados que você possui. LD_PRELOAD/LD_AUDIT são ignorados em contextos de execução segura (setuid). + ## Relocaçõ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 é dado nas seções dinâmicas RELSZ ou RELASZ. @@ -274,7 +311,6 @@ Offset Info Type Sym. Value Sym. Name + Addend 00000001fea0 000900000402 R_AARCH64_JUMP_SL 0000000000000000 perror@GLIBC_2.17 + 0 00000001fea8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0 00000001feb0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 putc@GLIBC_2.17 + 0 -00000001feb8 000d00000402 R_AARCH64_JUMP_SL 0000000000000000 opendir@GLIBC_2.17 + 0 00000001fec0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 fputc@GLIBC_2.17 + 0 00000001fec8 001100000402 R_AARCH64_JUMP_SL 0000000000000000 snprintf@GLIBC_2.17 + 0 00000001fed0 001200000402 R_AARCH64_JUMP_SL 0000000000000000 __snprintf_chk@GLIBC_2.17 + 0 @@ -314,13 +350,31 @@ Por exemplo, qualquer seção do tipo `R_AARCH64_RELATIV` deve ter modificado o ### Relocações Dinâmicas e GOT -A relocação também pode referenciar 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, escreverá esse endereço na tabela GOT (Global Offset Table) (indicado na tabela de relocação) onde o endereço de malloc deve ser especificado. +A relocação também pode referenciar 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, escreverá esse 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 Ligação de Procedimentos A seção PLT permite realizar vinculação preguiçosa, o que significa que a resolução da localização de uma função será realizada na primeira vez que for acessada. -Assim, quando um programa chama malloc, na verdade chama a localização correspondente de `malloc` na PLT (`malloc@plt`). Na primeira vez que é chamada, resolve o endereço de `malloc` e o armazena para que na próxima vez que `malloc` for chamada, esse endereço seja usado em vez do código PLT. +Assim, quando um programa chama malloc, na verdade chama a localização correspondente de `malloc` no PLT (`malloc@plt`). Na primeira vez que é chamado, resolve o endereço de `malloc` e o armazena, para que na próxima vez que `malloc` seja chamado, esse endereço seja usado em vez do código PLT. + +#### Comportamentos modernos de vinculação que impactam a exploração + +- `-z now` (Full RELRO) desabilita a vinculação preguiçosa; as entradas PLT ainda existem, mas GOT/PLT é mapeado como somente leitura, então técnicas como **sobrescrita de GOT** e **ret2dlresolve** não funcionarão contra o binário principal (as bibliotecas ainda podem ser parcialmente RELRO). Veja: + +{{#ref}} +../common-binary-protections-and-bypasses/relro.md +{{#endref}} + +- `-fno-plt` faz com que o compilador chame funções externas através da **entrada GOT diretamente** em vez de passar pelo stub PLT. Você verá sequências de chamadas como `mov reg, [got]; call reg` em vez de `call func@plt`. Isso reduz o abuso de execução especulativa e altera ligeiramente a busca por gadgets ROP em torno dos stubs PLT. + +- PIE vs static-PIE: PIE (ET_DYN com `INTERP`) precisa do carregador dinâmico e suporta a maquinaria usual PLT/GOT. Static-PIE (ET_DYN sem `INTERP`) tem relocações aplicadas pelo carregador do kernel e sem `ld.so`; espere nenhuma resolução PLT em tempo de execução. + +> Se GOT/PLT não for uma opção, pivote para outros ponteiros de código graváveis ou use ROP/SROP clássico em libc. + +{{#ref}} +../arbitrary-write-2-exec/aw2exec-got-plt.md +{{#endref}} ## Inicialização do Programa @@ -352,16 +406,26 @@ A partir do código C, é possível obter o mesmo resultado usando as extensões __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`, é 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. +Do ponto de vista de um compilador, para executar essas ações antes e depois da função `main`, é 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 dessas é indicado por **`INIT_ARRAYSZ`** e **`FINI_ARRAYSZ`**. Cada entrada é um ponteiro de função que será chamado sem argumentos. +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 delas é 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 **`INIT_ARRAY`**. +#### Nota de Exploração + +- Sob Partial RELRO, esses arrays vivem em páginas que ainda são graváveis antes que `ld.so` mude `PT_GNU_RELRO` para somente leitura. Se você conseguir uma gravação arbitrária cedo o suficiente ou se puder direcionar os arrays graváveis de uma biblioteca, você pode sequestrar o fluxo de controle sobrescrevendo uma entrada com uma função de sua escolha. Sob Full RELRO, eles são somente leitura em tempo de execução. + +- Para abuso de vinculação preguiçosa do vinculador dinâmico para resolver símbolos arbitrários em tempo de execução, veja a página dedicada: + +{{#ref}} +../rop-return-oriented-programing/ret2dlresolve.md +{{#endref}} + ### Ordem de Inicialização 1. 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`**. -2. Todas as **dependências** para o programa ou bibliotecas são **inicializadas** e o **link dinâmico** é executado. +2. Todas as **dependências** para o programa ou bibliotecas são **inicializadas** e a **vinculação dinâmica** é executada. 3. Funções **`PREINIT_ARRAY`** são executadas. 4. Funções **`INIT_ARRAY`** são executadas. 5. Se houver uma entrada **`INIT`**, ela é chamada. @@ -379,4 +443,31 @@ Cada variável terá uma entrada no cabeçalho TLS especificando o tamanho e o d O `__TLS_MODULE_BASE` é um símbolo usado para se referir ao endereço base do armazenamento local de thread e aponta para a área na memória que contém todos os dados locais de thread de um módulo. +## Vetor Auxiliar (auxv) e vDSO + +O kernel Linux passa um vetor auxiliar para processos contendo endereços e flags úteis para o tempo de execução: + +- `AT_RANDOM`: aponta para 16 bytes aleatórios usados pela glibc para o canário de pilha e outras sementes de PRNG. +- `AT_SYSINFO_EHDR`: endereço base do mapeamento vDSO (útil para encontrar chamadas de sistema `__kernel_*` e gadgets). +- `AT_EXECFN`, `AT_BASE`, `AT_PAGESZ`, etc. + +Como um atacante, se você puder ler memória ou arquivos em `/proc`, muitas vezes pode vazar esses dados sem um infoleak no processo alvo: +```bash +# Show the auxv of a running process +cat /proc/$(pidof target)/auxv | xxd + +# From your own process (helper snippet) +#include +#include +int main(){ +printf("AT_RANDOM=%p\n", (void*)getauxval(AT_RANDOM)); +printf("AT_SYSINFO_EHDR=%p\n", (void*)getauxval(AT_SYSINFO_EHDR)); +} +``` +Vazar `AT_RANDOM` fornece o valor do canário se você puder desreferenciar esse ponteiro; `AT_SYSINFO_EHDR` fornece uma base vDSO para minerar gadgets ou para chamar syscalls rápidas diretamente. + +## Referências + +- ld.so(8) – Ordem de busca do carregador dinâmico, RPATH/RUNPATH, regras de execução segura (AT_SECURE): https://man7.org/linux/man-pages/man8/ld.so.8.html +- getauxval(3) – Vetor auxiliar e constantes AT_*: https://man7.org/linux/man-pages/man3/getauxval.3.html {{#include ../../banners/hacktricks-training.md}}