mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-25 04:23:33 +00:00
288 lines
15 KiB
Markdown
288 lines
15 KiB
Markdown
# WWW2Exec - .dtors & .fini\_array
|
|
|
|
<details>
|
|
|
|
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
|
|
|
Outras maneiras de apoiar o HackTricks:
|
|
|
|
* Se você quiser ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF** Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
|
|
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|
* **Compartilhe seus truques de hacking enviando PRs para os** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
|
|
|
|
</details>
|
|
|
|
## .dtors
|
|
|
|
{% hint style="danger" %}
|
|
Atualmente é muito **estranho encontrar um binário com uma seção .dtors!**
|
|
{% endhint %}
|
|
|
|
Os destrutores são funções que são **executadas antes do programa terminar** (após o retorno da função `main`).\
|
|
Os endereços dessas funções são armazenados dentro da seção **`.dtors`** do binário e, portanto, se você conseguir **escrever** o **endereço** de um **shellcode** em **`__DTOR_END__`**, isso será **executado** antes do programa terminar.
|
|
|
|
Obtenha o endereço desta seção com:
|
|
```bash
|
|
objdump -s -j .dtors /exec
|
|
rabin -s /exec | grep “__DTOR”
|
|
```
|
|
Normalmente você encontrará os marcadores **DTOR** **entre** os valores `ffffffff` e `00000000`. Portanto, se você apenas ver esses valores, significa que **não há nenhuma função registrada**. Portanto, **sobrescreva** o **`00000000`** com o **endereço** do **shellcode** para executá-lo.
|
|
|
|
{% hint style="warning" %}
|
|
Claro, primeiro você precisa encontrar um **local para armazenar o shellcode** para depois chamá-lo.
|
|
{% endhint %}
|
|
|
|
## **.fini\_array**
|
|
|
|
Essencialmente, esta é uma estrutura com **funções que serão chamadas** antes do programa terminar, como **`.dtors`**. Isso é interessante se você puder chamar seu **shellcode apenas pulando para um endereço**, ou em casos em que você precisa voltar para o `main` novamente para **explorar a vulnerabilidade uma segunda vez**.
|
|
```bash
|
|
objdump -s -j .fini_array ./greeting
|
|
|
|
./greeting: file format elf32-i386
|
|
|
|
Contents of section .fini_array:
|
|
8049934 a0850408
|
|
|
|
#Put your address in 0x8049934
|
|
```
|
|
Note que quando uma função do **`.fini_array`** é executada, ela passa para a próxima, então não será executada várias vezes (evitando loops eternos), mas também só lhe dará 1 **execução da função** colocada aqui.
|
|
|
|
Note que as entradas em `.fini_array` são chamadas em ordem **inversa**, então provavelmente você quer começar a escrever a partir da última.
|
|
|
|
#### Loop eterno
|
|
|
|
Para abusar do **`.fini_array`** e obter um loop eterno, você pode [**ver o que foi feito aqui**](https://guyinatuxedo.github.io/17-stack\_pivot/insomnihack18\_onewrite/index.html)**:** Se você tiver pelo menos 2 entradas em **`.fini_array`**, você pode:
|
|
|
|
* Usar sua primeira escrita para **chamar a função de escrita arbitrária vulnerável** novamente
|
|
* Em seguida, calcular o endereço de retorno na pilha armazenado por **`__libc_csu_fini`** (a função que está chamando todas as funções `.fini_array`) e colocar lá o **endereço de `__libc_csu_fini`**
|
|
* Isso fará com que **`__libc_csu_fini`** chame a si mesmo novamente executando as funções do **`.fini_array`** novamente, o que chamará a função WWW vulnerável 2 vezes: uma para a **escrita arbitrária** e outra para sobrescrever novamente o **endereço de retorno de `__libc_csu_fini`** na pilha para chamar a si mesmo novamente.
|
|
|
|
{% hint style="danger" %}
|
|
Note que com [**Full RELRO**](../common-binary-protections-and-bypasses/relro.md)**,** a seção **`.fini_array`** é tornada **somente leitura**.
|
|
{% endhint %}
|
|
|
|
## link\_map
|
|
|
|
Como explicado [**neste post**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure), se o programa sair usando `return` ou `exit()`, ele executará `__run_exit_handlers()` que chamará os destruidores registrados.
|
|
|
|
{% hint style="danger" %}
|
|
Se o programa sair via função **`_exit()`**, ele chamará a **chamada de sistema `exit`** e os manipuladores de saída não serão executados. Portanto, para confirmar que `__run_exit_handlers()` está sendo executado, você pode definir um breakpoint nele.
|
|
{% endhint %}
|
|
|
|
O código importante é ([fonte](https://elixir.bootlin.com/glibc/glibc-2.32/source/elf/dl-fini.c#L131)):
|
|
```c
|
|
ElfW(Dyn) *fini_array = map->l_info[DT_FINI_ARRAY];
|
|
if (fini_array != NULL)
|
|
{
|
|
ElfW(Addr) *array = (ElfW(Addr) *) (map->l_addr + fini_array->d_un.d_ptr);
|
|
size_t sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));
|
|
|
|
while (sz-- > 0)
|
|
((fini_t) array[sz]) ();
|
|
}
|
|
[...]
|
|
|
|
|
|
|
|
|
|
// This is the d_un structure
|
|
ptype l->l_info[DT_FINI_ARRAY]->d_un
|
|
type = union {
|
|
Elf64_Xword d_val; // address of function that will be called, we put our onegadget here
|
|
Elf64_Addr d_ptr; // offset from l->l_addr of our structure
|
|
}
|
|
```
|
|
Observe como `map -> l_addr + fini_array -> d_un.d_ptr` é usado para **calcular** a posição do **array de funções a serem chamadas**.
|
|
|
|
Existem **algumas opções**:
|
|
|
|
* Sobrescrever o valor de `map->l_addr` para fazê-lo apontar para um **`fini_array` falso** com instruções para executar código arbitrário
|
|
* Sobrescrever as entradas `l_info[DT_FINI_ARRAY]` e `l_info[DT_FINI_ARRAYSZ]` (que são mais ou menos consecutivas na memória), para fazer com que elas apontem para uma estrutura `Elf64_Dyn` forjada que fará novamente **`array` apontar para uma zona de memória** controlada pelo atacante. 
|
|
* [**Este relatório**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) sobrescreve `l_info[DT_FINI_ARRAY]` com o endereço de uma memória controlada em `.bss` contendo um `fini_array` falso. Este array falso contém **primeiro um** [**endereço de one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) que será executado e depois a **diferença** entre o endereço deste **array falso** e o **valor de `map->l_addr`** para que `*array` aponte para o array falso.
|
|
* De acordo com a postagem principal desta técnica e [**este relatório**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet) ld.so deixa um ponteiro na pilha que aponta para o `link_map` binário em ld.so. Com uma escrita arbitrária é possível sobrescrevê-lo e fazê-lo apontar para um `fini_array` falso controlado pelo atacante com o endereço de um [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md), por exemplo.
|
|
|
|
Seguindo o código anterior, você pode encontrar outra seção interessante com o código:
|
|
```c
|
|
/* Next try the old-style destructor. */
|
|
ElfW(Dyn) *fini = map->l_info[DT_FINI];
|
|
if (fini != NULL)
|
|
DL_CALL_DT_FINI (map, ((void *) map->l_addr + fini->d_un.d_ptr));
|
|
}
|
|
```
|
|
Neste caso, seria possível sobrescrever o valor de `map->l_info[DT_FINI]` apontando para uma estrutura `ElfW(Dyn)` forjada. Encontre [**mais informações aqui**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure).
|
|
|
|
## Sobrescrevendo a lista de dtor\_list de TLS-Storage em **`__run_exit_handlers`**
|
|
|
|
Conforme [**explicado aqui**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite), se um programa encerra via `return` ou `exit()`, ele executará **`__run_exit_handlers()`** que chamará qualquer função de destruição registrada.
|
|
|
|
Código de `_run_exit_handlers()`:
|
|
```c
|
|
/* Call all functions registered with `atexit' and `on_exit',
|
|
in the reverse of the order in which they were registered
|
|
perform stdio cleanup, and terminate program execution with STATUS. */
|
|
void
|
|
attribute_hidden
|
|
__run_exit_handlers (int status, struct exit_function_list **listp,
|
|
bool run_list_atexit, bool run_dtors)
|
|
{
|
|
/* First, call the TLS destructors. */
|
|
#ifndef SHARED
|
|
if (&__call_tls_dtors != NULL)
|
|
#endif
|
|
if (run_dtors)
|
|
__call_tls_dtors ();
|
|
```
|
|
Código de **`__call_tls_dtors()`**:
|
|
```c
|
|
typedef void (*dtor_func) (void *);
|
|
struct dtor_list //struct added
|
|
{
|
|
dtor_func func;
|
|
void *obj;
|
|
struct link_map *map;
|
|
struct dtor_list *next;
|
|
};
|
|
|
|
[...]
|
|
/* Call the destructors. This is called either when a thread returns from the
|
|
initial function or when the process exits via the exit function. */
|
|
void
|
|
__call_tls_dtors (void)
|
|
{
|
|
while (tls_dtor_list) // parse the dtor_list chained structures
|
|
{
|
|
struct dtor_list *cur = tls_dtor_list; // cur point to tls-storage dtor_list
|
|
dtor_func func = cur->func;
|
|
PTR_DEMANGLE (func); // demangle the function ptr
|
|
|
|
tls_dtor_list = tls_dtor_list->next; // next dtor_list structure
|
|
func (cur->obj);
|
|
[...]
|
|
}
|
|
}
|
|
```
|
|
Para cada função registrada em **`tls_dtor_list`**, ele irá desembaralhar o ponteiro de **`cur->func`** e chamá-lo com o argumento **`cur->obj`**.
|
|
|
|
Usando a função **`tls`** deste [**fork do GEF**](https://github.com/bata24/gef), é possível ver que na verdade a **`dtor_list`** está muito **próxima** do **canário de pilha** e do **cookie PTR\_MANGLE**. Portanto, com um estouro nela, seria possível **sobrescrever** o **cookie** e o **canário de pilha**.\
|
|
Sobrescrevendo o cookie PTR\_MANGLE, seria possível **burlar a função `PTR_DEMANLE`** ao defini-lo como 0x00, o que significa que o **`xor`** usado para obter o endereço real é apenas o endereço configurado. Em seguida, escrevendo na **`dtor_list`**, é possível **encadear várias funções** com o endereço da função e seu **argumento**.
|
|
|
|
Por fim, observe que o ponteiro armazenado não apenas será xorado com o cookie, mas também rotacionado 17 bits:
|
|
```armasm
|
|
0x00007fc390444dd4 <+36>: mov rax,QWORD PTR [rbx] --> mangled ptr
|
|
0x00007fc390444dd7 <+39>: ror rax,0x11 --> rotate of 17 bits
|
|
0x00007fc390444ddb <+43>: xor rax,QWORD PTR fs:0x30 --> xor with PTR_MANGLE
|
|
```
|
|
Portanto, você precisa levar isso em consideração antes de adicionar um novo endereço.
|
|
|
|
Encontre um exemplo no [**post original**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite).
|
|
|
|
## Outros ponteiros corrompidos em **`__run_exit_handlers`**
|
|
|
|
Essa técnica é [**explicada aqui**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite) e depende novamente do programa **sair chamando `return` ou `exit()`** para que **`__run_exit_handlers()`** seja chamado.
|
|
|
|
Vamos verificar mais código desta função:
|
|
```c
|
|
while (true)
|
|
{
|
|
struct exit_function_list *cur;
|
|
|
|
restart:
|
|
cur = *listp;
|
|
|
|
if (cur == NULL)
|
|
{
|
|
/* Exit processing complete. We will not allow any more
|
|
atexit/on_exit registrations. */
|
|
__exit_funcs_done = true;
|
|
break;
|
|
}
|
|
|
|
while (cur->idx > 0)
|
|
{
|
|
struct exit_function *const f = &cur->fns[--cur->idx];
|
|
const uint64_t new_exitfn_called = __new_exitfn_called;
|
|
|
|
switch (f->flavor)
|
|
{
|
|
void (*atfct) (void);
|
|
void (*onfct) (int status, void *arg);
|
|
void (*cxafct) (void *arg, int status);
|
|
void *arg;
|
|
|
|
case ef_free:
|
|
case ef_us:
|
|
break;
|
|
case ef_on:
|
|
onfct = f->func.on.fn;
|
|
arg = f->func.on.arg;
|
|
PTR_DEMANGLE (onfct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
onfct (status, arg);
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
break;
|
|
case ef_at:
|
|
atfct = f->func.at;
|
|
PTR_DEMANGLE (atfct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
atfct ();
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
break;
|
|
case ef_cxa:
|
|
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
|
|
we must mark this function as ef_free. */
|
|
f->flavor = ef_free;
|
|
cxafct = f->func.cxa.fn;
|
|
arg = f->func.cxa.arg;
|
|
PTR_DEMANGLE (cxafct);
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
cxafct (arg, status);
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
break;
|
|
}
|
|
|
|
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
|
|
/* The last exit function, or another thread, has registered
|
|
more exit functions. Start the loop over. */
|
|
goto restart;
|
|
}
|
|
|
|
*listp = cur->next;
|
|
if (*listp != NULL)
|
|
/* Don't free the last element in the chain, this is the statically
|
|
allocate element. */
|
|
free (cur);
|
|
}
|
|
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
```
|
|
A variável `f` aponta para a estrutura **`initial`** e dependendo do valor de `f->flavor`, diferentes funções serão chamadas.\
|
|
Dependendo do valor, o endereço da função a ser chamada estará em um local diferente, mas sempre será **desembaralhado**.
|
|
|
|
Além disso, nas opções **`ef_on`** e **`ef_cxa`**, também é possível controlar um **argumento**.
|
|
|
|
É possível verificar a estrutura **`initial`** em uma sessão de depuração com o GEF executando **`gef> p initial`**.
|
|
|
|
Para abusar disso, você precisa vazar ou apagar o **cookie `PTR_MANGLE`** e então sobrescrever uma entrada `cxa` em initial com `system('/bin/sh')`.\
|
|
Você pode encontrar um exemplo disso no [**post original do blog sobre a técnica**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#6---code-execution-via-other-mangled-pointers-in-initial-structure).
|
|
|
|
<details>
|
|
|
|
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
|
|
|
Outras maneiras de apoiar o HackTricks:
|
|
|
|
* Se você quiser ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF**, confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
|
|
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou nos siga no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|
* **Compartilhe seus truques de hacking enviando PRs para os repositórios do** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|