mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# WWW2Exec - atexit(), TLS Storage & Inne zniekształcone wskaźniki
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## **\_\_atexit Struktury**
|
|
|
|
> [!CAUTION]
|
|
> Obecnie jest bardzo **dziwne, aby to wykorzystać!**
|
|
|
|
**`atexit()`** to funkcja, do której **przekazywane są inne funkcje jako parametry.** Te **funkcje** będą **wykonywane** podczas wykonywania **`exit()`** lub **powrotu** z **main**.\
|
|
Jeśli możesz **zmodyfikować** **adres** którejkolwiek z tych **funkcji**, aby wskazywał na shellcode na przykład, zyskasz **kontrolę** nad **procesem**, ale obecnie jest to bardziej skomplikowane.\
|
|
Obecnie **adresy funkcji** do wykonania są **ukryte** za kilkoma strukturami, a ostatecznie adres, na który wskazują, nie jest adresem funkcji, lecz jest **szyfrowany za pomocą XOR** i przesunięć z **losowym kluczem**. Tak więc obecnie ten wektor ataku jest **niezbyt użyteczny przynajmniej na x86** i **x64_86**.\
|
|
Funkcja **szyfrująca** to **`PTR_MANGLE`**. **Inne architektury** takie jak m68k, mips32, mips64, aarch64, arm, hppa... **nie implementują funkcji szyfrującej**, ponieważ **zwraca to samo**, co otrzymała jako wejście. Tak więc te architektury byłyby atakowalne przez ten wektor.
|
|
|
|
Możesz znaleźć szczegółowe wyjaśnienie, jak to działa w [https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html](https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html)
|
|
|
|
## link_map
|
|
|
|
Jak wyjaśniono [**w tym poście**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure), jeśli program kończy się za pomocą `return` lub `exit()`, uruchomi `__run_exit_handlers()`, który wywoła zarejestrowane destruktory.
|
|
|
|
> [!CAUTION]
|
|
> Jeśli program kończy się za pomocą funkcji **`_exit()`**, wywoła **`exit` syscall** i procedury końcowe nie zostaną wykonane. Aby potwierdzić, że `__run_exit_handlers()` jest wykonywane, możesz ustawić punkt przerwania na nim.
|
|
|
|
Ważny kod to ([source](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
|
|
}
|
|
```
|
|
Zauważ, jak `map -> l_addr + fini_array -> d_un.d_ptr` jest używane do **obliczenia** pozycji **tablicy funkcji do wywołania**.
|
|
|
|
Istnieje **kilka opcji**:
|
|
|
|
- Nadpisanie wartości `map->l_addr`, aby wskazywała na **fałszywy `fini_array`** z instrukcjami do wykonania dowolnego kodu
|
|
- Nadpisanie wpisów `l_info[DT_FINI_ARRAY]` i `l_info[DT_FINI_ARRAYSZ]` (które są mniej więcej kolejno w pamięci), aby sprawić, że **wskazują na sfałszowaną strukturę `Elf64_Dyn`**, która ponownie sprawi, że **`array` będzie wskazywać na strefę pamięci** kontrolowaną przez atakującego.
|
|
- [**Ten opis**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) nadpisuje `l_info[DT_FINI_ARRAY]` adresem kontrolowanej pamięci w `.bss`, zawierającej fałszywy `fini_array`. Ta fałszywa tablica zawiera **najpierw** adres [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md), który zostanie wykonany, a następnie **różnicę** między adresem tej **fałszywej tablicy** a **wartością `map->l_addr`**, aby `*array` wskazywało na fałszywą tablicę.
|
|
- Zgodnie z głównym postem tej techniki i [**tym opisem**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet) ld.so zostawia wskaźnik na stosie, który wskazuje na binarny `link_map` w ld.so. Dzięki dowolnemu zapisowi możliwe jest nadpisanie go i sprawienie, aby wskazywał na fałszywy `fini_array` kontrolowany przez atakującego z adresem do [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md), na przykład.
|
|
|
|
Po poprzednim kodzie można znaleźć kolejny interesujący fragment z kodem:
|
|
```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));
|
|
}
|
|
```
|
|
W tym przypadku możliwe byłoby nadpisanie wartości `map->l_info[DT_FINI]`, wskazującej na sfałszowaną strukturę `ElfW(Dyn)`. Znajdź [**więcej informacji tutaj**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure).
|
|
|
|
## Nadpisanie dtor_list w TLS-Storage w **`__run_exit_handlers`**
|
|
|
|
Jak [**wyjaśniono tutaj**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite), jeśli program kończy działanie za pomocą `return` lub `exit()`, wykona **`__run_exit_handlers()`**, które wywoła wszelkie zarejestrowane funkcje destruktorów.
|
|
|
|
Kod z `_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 ();
|
|
```
|
|
Kod z **`__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);
|
|
[...]
|
|
}
|
|
}
|
|
```
|
|
Dla każdej zarejestrowanej funkcji w **`tls_dtor_list`**, zostanie zdemanglowany wskaźnik z **`cur->func`** i wywołana z argumentem **`cur->obj`**.
|
|
|
|
Używając funkcji **`tls`** z tej [**forka GEF**](https://github.com/bata24/gef), można zobaczyć, że tak naprawdę **`dtor_list`** jest bardzo **blisko** **stack canary** i **PTR_MANGLE cookie**. Tak więc, przy przepełnieniu możliwe byłoby **nadpisanie** **cookie** i **stack canary**.\
|
|
Nadpisując PTR_MANGLE cookie, możliwe byłoby **obejście funkcji `PTR_DEMANLE`** ustawiając ją na 0x00, co oznacza, że **`xor`** użyte do uzyskania rzeczywistego adresu to po prostu skonfigurowany adres. Następnie, pisząc na **`dtor_list`**, możliwe jest **połączenie kilku funkcji** z adresem funkcji i jej **argumentem.**
|
|
|
|
Na koniec zauważ, że przechowywany wskaźnik nie tylko będzie xored z cookie, ale także obrócony o 17 bitów:
|
|
```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
|
|
```
|
|
Musisz wziąć to pod uwagę przed dodaniem nowego adresu.
|
|
|
|
Znajdź przykład w [**oryginalnym poście**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite).
|
|
|
|
## Inne zniekształcone wskaźniki w **`__run_exit_handlers`**
|
|
|
|
Ta technika jest [**wyjaśniona tutaj**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite) i ponownie zależy od tego, że program **kończy działanie, wywołując `return` lub `exit()`**, więc **`__run_exit_handlers()`** jest wywoływane.
|
|
|
|
Sprawdźmy więcej kodu tej funkcji:
|
|
```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);
|
|
```
|
|
Zmienna `f` wskazuje na strukturę **`initial`** i w zależności od wartości `f->flavor` będą wywoływane różne funkcje.\
|
|
W zależności od wartości, adres funkcji do wywołania będzie w innym miejscu, ale zawsze będzie **demangled**.
|
|
|
|
Ponadto, w opcjach **`ef_on`** i **`ef_cxa`** możliwe jest również kontrolowanie **argumentu**.
|
|
|
|
Można sprawdzić strukturę **`initial`** w sesji debugowania z uruchomionym GEF za pomocą **`gef> p initial`**.
|
|
|
|
Aby to wykorzystać, musisz albo **leak** lub usunąć cookie `PTR_MANGLE`, a następnie nadpisać wpis `cxa` w initial za pomocą `system('/bin/sh')`.\
|
|
Możesz znaleźć przykład tego w [**oryginalnym poście na blogu o technice**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#6---code-execution-via-other-mangled-pointers-in-initial-structure).
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|