mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-21 20:23:18 +00:00
exploiting WWW
This commit is contained in:
parent
7eb8286d65
commit
e40f450b54
4 changed files with 243 additions and 236 deletions
|
@ -38,14 +38,13 @@ This was abused in one of the example from the page abusing a fast bin attack af
|
||||||
[unsorted-bin-attack.md](../libc-heap/unsorted-bin-attack.md)
|
[unsorted-bin-attack.md](../libc-heap/unsorted-bin-attack.md)
|
||||||
{% endcontent-ref %}
|
{% endcontent-ref %}
|
||||||
|
|
||||||
A nice trick (from [**here**](https://guyinatuxedo.github.io/41-house\_of\_force/bkp16\_cookbook/index.html)) to find the location of the free hook if the binary has symbols is to **do something like**:
|
It's posisble to find the address of `__free_hook` if the binary has symbols with the following command:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
gef➤ set __free_hook = 0xfacade
|
gef➤ p &__free_hook
|
||||||
gef➤ search-pattern 0xfacade
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In the same post you can find a step by step guide on how to locate the address of the free hook without symbols. As summary, in the free function:
|
[In the post](https://guyinatuxedo.github.io/41-house\_of\_force/bkp16\_cookbook/index.html) you can find a step by step guide on how to locate the address of the free hook without symbols. As summary, in the free function:
|
||||||
|
|
||||||
<pre class="language-armasm"><code class="lang-armasm">gef➤ x/20i free
|
<pre class="language-armasm"><code class="lang-armasm">gef➤ x/20i free
|
||||||
0xf75dedc0 <free>: push ebx
|
0xf75dedc0 <free>: push ebx
|
||||||
|
@ -54,9 +53,9 @@ In the same post you can find a step by step guide on how to locate the address
|
||||||
0xf75dedcc <free+12>: sub esp,0x8
|
0xf75dedcc <free+12>: sub esp,0x8
|
||||||
0xf75dedcf <free+15>: mov eax,DWORD PTR [ebx-0x98]
|
0xf75dedcf <free+15>: mov eax,DWORD PTR [ebx-0x98]
|
||||||
0xf75dedd5 <free+21>: mov ecx,DWORD PTR [esp+0x10]
|
0xf75dedd5 <free+21>: mov ecx,DWORD PTR [esp+0x10]
|
||||||
0xf75dedd9 <free+25>: mov eax,DWORD PTR [eax]
|
<strong>0xf75dedd9 <free+25>: mov eax,DWORD PTR [eax]--- BREAK HERE
|
||||||
<strong>0xf75deddb <free+27>: test eax,eax ;<--- BREAK HERE
|
</strong>0xf75deddb <free+27>: test eax,eax ;<
|
||||||
</strong>0xf75deddd <free+29>: jne 0xf75dee50 <free+144>
|
0xf75deddd <free+29>: jne 0xf75dee50 <free+144>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
In the mentioned break in the previous code in `$eax` will be located the address of the free hook.
|
In the mentioned break in the previous code in `$eax` will be located the address of the free hook.
|
||||||
|
|
|
@ -36,7 +36,7 @@ Get the address to the GOT table with: **`objdump -s -j .got ./exec`**
|
||||||
|
|
||||||
![](<../../.gitbook/assets/image (121).png>)
|
![](<../../.gitbook/assets/image (121).png>)
|
||||||
|
|
||||||
Observe how after **loading** the **executable** in GEF you can **see** the **functions** that are in the **GOT**: `gef➤ x/20x 0xDIR_GOT`
|
Observe how after **loading** the **executable** in GEF you can **see** the **functions** that are in the **GOT**: `gef➤ x/20x 0xADDR_GOT`
|
||||||
|
|
||||||
![](<../../.gitbook/assets/image (620) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (2) (2).png>)
|
![](<../../.gitbook/assets/image (620) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (2) (2).png>)
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ In a binary the GOT has the **addresses to the functions or** to the **PLT** sec
|
||||||
|
|
||||||
Ideally, you will **override** the **GOT** of a **function** that is **going to be called with parameters controlled by you** (so you will be able to control the parameters sent to the system function).
|
Ideally, you will **override** the **GOT** of a **function** that is **going to be called with parameters controlled by you** (so you will be able to control the parameters sent to the system function).
|
||||||
|
|
||||||
If **`system`** **isn't used** by the script, the system function **won't** have an entry in the PLT. In this scenario, you will **need to leak first the address** of the `system` function and then overwrite the GOT to point to this address.
|
If **`system`** **isn't used** by the binary, the system function **won't** have an entry in the PLT. In this scenario, you will **need to leak first the address** of the `system` function and then overwrite the GOT to point to this address.
|
||||||
|
|
||||||
You can see the PLT addresses with **`objdump -j .plt -d ./vuln_binary`**
|
You can see the PLT addresses with **`objdump -j .plt -d ./vuln_binary`**
|
||||||
|
|
||||||
|
@ -66,6 +66,12 @@ Find [**more information about this technique here**](https://github.com/nobodyi
|
||||||
|
|
||||||
In heap exploitation CTFs it's common to be able to control the content of chunks and at some point even overwrite the GOT table. A simple trick to get RCE if one gadgets aren't available is to overwrite the `free` GOT address to point to `system` and to write inside a chunk `"/bin/sh"`. This way when this chunk is freed, it'll execute `system("/bin/sh")`.
|
In heap exploitation CTFs it's common to be able to control the content of chunks and at some point even overwrite the GOT table. A simple trick to get RCE if one gadgets aren't available is to overwrite the `free` GOT address to point to `system` and to write inside a chunk `"/bin/sh"`. This way when this chunk is freed, it'll execute `system("/bin/sh")`.
|
||||||
|
|
||||||
|
## **Strlen2system**
|
||||||
|
|
||||||
|
Another common technique is to overwrite the **`strlen`** GOT address to point to **`system`**, so if this function is called with user input it's posisble to pass the string `"/bin/sh"` and get a shell.
|
||||||
|
|
||||||
|
Moreover, if `puts` is used with user input, it's possible to overwrite the `puts` GOT address to point to `system` and pass the string `"/bin/sh"` to get a shell because **`puts` will call `strlen` with the user input**.
|
||||||
|
|
||||||
## **One Gadget**
|
## **One Gadget**
|
||||||
|
|
||||||
{% content-ref url="../rop-return-oriented-programing/ret2lib/one-gadget.md" %}
|
{% content-ref url="../rop-return-oriented-programing/ret2lib/one-gadget.md" %}
|
||||||
|
@ -75,7 +81,7 @@ In heap exploitation CTFs it's common to be able to control the content of chunk
|
||||||
## **Abusing GOT from Heap**
|
## **Abusing GOT from Heap**
|
||||||
|
|
||||||
A common way to obtain RCE from a heap vulnerability is to abuse a fastbin so it's possible to add the part of the GOT table into the fast bin, so whenever that chunk is allocated it'll be possible to **overwrite the pointer of a function, usually `free`**.\
|
A common way to obtain RCE from a heap vulnerability is to abuse a fastbin so it's possible to add the part of the GOT table into the fast bin, so whenever that chunk is allocated it'll be possible to **overwrite the pointer of a function, usually `free`**.\
|
||||||
Then, pointing `free` to `system` and freeing a chunk were was written `/bin/sh\x00` will execute a shell.
|
Then, pointing `free` to `system` and freeing a chunk where was written `/bin/sh\x00` will execute a shell.
|
||||||
|
|
||||||
It's possible to find an [**example here**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/chunk\_extend\_overlapping/#hitcon-trainging-lab13)**.**
|
It's possible to find an [**example here**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/chunk\_extend\_overlapping/#hitcon-trainging-lab13)**.**
|
||||||
|
|
||||||
|
|
|
@ -60,236 +60,14 @@ Note that entries in `.fini_array` are called in **reverse** order, so you proba
|
||||||
In order to abuse **`.fini_array`** to get an eternal loop you can [**check what was done here**](https://guyinatuxedo.github.io/17-stack\_pivot/insomnihack18\_onewrite/index.html)**:** If you have at least 2 entries in **`.fini_array`**, you can:
|
In order to abuse **`.fini_array`** to get an eternal loop you can [**check what was done here**](https://guyinatuxedo.github.io/17-stack\_pivot/insomnihack18\_onewrite/index.html)**:** If you have at least 2 entries in **`.fini_array`**, you can:
|
||||||
|
|
||||||
* Use your first write to **call the vulnerable arbitrary write function** again
|
* Use your first write to **call the vulnerable arbitrary write function** again
|
||||||
* Then, calculate the return address in the stack stored by **`__libc_csu_fini`** (the function that it calling all the `.fini_array` functions) and put there the **address of `__libc_csu_fini`**
|
* Then, calculate the return address in the stack stored by **`__libc_csu_fini`** (the function that is calling all the `.fini_array` functions) and put there the **address of `__libc_csu_fini`**
|
||||||
* This will make **`__libc_csu_fini`** call himself again executing the **`.fini_array`** functions again which will call the vulnerable WWW function 2 times: one for **arbitrary write** and another one to overwrite again the **return address of `__libc_csu_fini`** on the stack to call itself again.
|
* This will make **`__libc_csu_fini`** call himself again executing the **`.fini_array`** functions again which will call the vulnerable WWW function 2 times: one for **arbitrary write** and another one to overwrite again the **return address of `__libc_csu_fini`** on the stack to call itself again.
|
||||||
|
|
||||||
{% hint style="danger" %}
|
{% hint style="danger" %}
|
||||||
Note that with [**Full RELRO**](../common-binary-protections-and-bypasses/relro.md)**,** the section **`.fini_array`** is made **read-only**.
|
Note that with [**Full RELRO**](../common-binary-protections-and-bypasses/relro.md)**,** the section **`.fini_array`** is made **read-only**.
|
||||||
|
In newer versions, even with [**Partial RELRO**] the section **`.fini_array`** is made **read-only** also.
|
||||||
{% endhint %}
|
{% endhint %}
|
||||||
|
|
||||||
## link\_map
|
|
||||||
|
|
||||||
As explained [**in this post**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure), If the program exist using `return` or `exit()` it'll run `__run_exit_handlers()` which will call registered destructors.
|
|
||||||
|
|
||||||
{% hint style="danger" %}
|
|
||||||
If the program exits via **`_exit()`** function, it'll call the **`exit` syscall** and the exit handlers will not be executed. So, to confirm `__run_exit_handlers()` is executed you can set a breakpoint on it.
|
|
||||||
{% endhint %}
|
|
||||||
|
|
||||||
The important code is ([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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note how `map -> l_addr + fini_array -> d_un.d_ptr` is used to **calculate** the position of the **array of functions to call**.
|
|
||||||
|
|
||||||
There are a **couple of options**:
|
|
||||||
|
|
||||||
* Overwrite the value of `map->l_addr` to make it point to a **fake `fini_array`** with instructions to execute arbitrary code
|
|
||||||
* Overwrite `l_info[DT_FINI_ARRAY]` and `l_info[DT_FINI_ARRAYSZ]` entries (which are more or less consecutive in memory) , to make them **points to a forged `Elf64_Dyn`** structure that will make again **`array` points to a memory** zone the attacker controlled. 
|
|
||||||
* [**This writeup**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) overwrites `l_info[DT_FINI_ARRAY]` with the address of a controlled memory in `.bss` containing a fake `fini_array`. This fake array contains **first a** [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) **address** which will be executed and then the **difference** between in the address of this **fake array** and the v**alue of `map->l_addr`** so `*array` will point to the fake array.
|
|
||||||
* According to main post of this technique and [**this writeup**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet) ld.so leave a pointer on the stack that points to the binary `link_map` in ld.so. With an arbitrary write it's possible to overwrite it and make it point to a fake `fini_array` controlled by the attacker with the address to a [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) for example.
|
|
||||||
|
|
||||||
Following the previous code you can find another interesting section with the code:
|
|
||||||
|
|
||||||
```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));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case it would be possible to overwrite the value of `map->l_info[DT_FINI]` pointing to a forged `ElfW(Dyn)` structure. Find [**more information here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure).
|
|
||||||
|
|
||||||
## TLS-Storage dtor\_list overwrite in **`__run_exit_handlers`**
|
|
||||||
|
|
||||||
As [**explained here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite), if a program exits via `return` or `exit()`, it'll execute **`__run_exit_handlers()`** which will call any destructors function registered.
|
|
||||||
|
|
||||||
Code from `_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 ();
|
|
||||||
```
|
|
||||||
|
|
||||||
Code from **`__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);
|
|
||||||
[...]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For each registered function in **`tls_dtor_list`**, it'll demangle the pointer from **`cur->func`** and call it with the argument **`cur->obj`**.
|
|
||||||
|
|
||||||
Using the **`tls`** function from this [**fork of GEF**](https://github.com/bata24/gef), it's possible to see that actually the **`dtor_list`** is very **close** to the **stack canary** and **PTR\_MANGLE cookie**. So, with an overflow on it's it would be possible to **overwrite** the **cookie** and the **stack canary**.\
|
|
||||||
Overwriting the PTR\_MANGLE cookie, it would be possible to **bypass the `PTR_DEMANLE` function** as setting it to 0x00, will mean that the **`xor`** used to get the real address is just the address configured. Then, writing on the **`dtor_list`** it's possible **chain several functions** with the function **address** and it's **argument.**
|
|
||||||
|
|
||||||
Finally notice that the stored pointer is not only going to be xored with the cookie but also rotated 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
|
|
||||||
```
|
|
||||||
|
|
||||||
So you need to take this into account before adding a new address.
|
|
||||||
|
|
||||||
Find an example in the [**original post**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite).
|
|
||||||
|
|
||||||
## Other mangled pointers in **`__run_exit_handlers`**
|
|
||||||
|
|
||||||
This technique is [**explained here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite) and depends again on the program **exiting calling `return` or `exit()`** so **`__run_exit_handlers()`** is called.
|
|
||||||
|
|
||||||
Let's check more code of this function:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```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);
|
|
||||||
```
|
|
||||||
|
|
||||||
The variable `f` points to the **`initial`** structure and depending on the value of `f->flavor` different functions will be called.\
|
|
||||||
Depending on the value, the address of the function to call will be in a different place, but it'll always be **demangled**.
|
|
||||||
|
|
||||||
Moreover, in the options **`ef_on`** and **`ef_cxa`** it's also possible to control an **argument**.
|
|
||||||
|
|
||||||
It's possible to check the **`initial` structure** in a debugging session with GEF running **`gef> p initial`**.
|
|
||||||
|
|
||||||
To abuse this you need either to **leak or erase the `PTR_MANGLE`cookie** and then overwrite a `cxa` entry in initial with `system('/bin/sh')`.\
|
|
||||||
You can find an example of this in the [**original blog post about the technique**](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>
|
<details>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# WWW2Exec - atexit()
|
# WWW2Exec - atexit(), TLS Storage & Other mangled Pointers
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
|
@ -27,6 +27,230 @@ The **encryption function** is **`PTR_MANGLE`**. **Other architectures** such as
|
||||||
|
|
||||||
You can find an in depth explanation on how this works in [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)
|
You can find an in depth explanation on how this works in [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
|
||||||
|
|
||||||
|
As explained [**in this post**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure), If the program exits using `return` or `exit()` it'll run `__run_exit_handlers()` which will call registered destructors.
|
||||||
|
|
||||||
|
{% hint style="danger" %}
|
||||||
|
If the program exits via **`_exit()`** function, it'll call the **`exit` syscall** and the exit handlers will not be executed. So, to confirm `__run_exit_handlers()` is executed you can set a breakpoint on it.
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
|
The important code is ([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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how `map -> l_addr + fini_array -> d_un.d_ptr` is used to **calculate** the position of the **array of functions to call**.
|
||||||
|
|
||||||
|
There are a **couple of options**:
|
||||||
|
|
||||||
|
* Overwrite the value of `map->l_addr` to make it point to a **fake `fini_array`** with instructions to execute arbitrary code
|
||||||
|
* Overwrite `l_info[DT_FINI_ARRAY]` and `l_info[DT_FINI_ARRAYSZ]` entries (which are more or less consecutive in memory) , to make them **points to a forged `Elf64_Dyn`** structure that will make again **`array` points to a memory** zone the attacker controlled. 
|
||||||
|
* [**This writeup**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) overwrites `l_info[DT_FINI_ARRAY]` with the address of a controlled memory in `.bss` containing a fake `fini_array`. This fake array contains **first a** [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) **address** which will be executed and then the **difference** between in the address of this **fake array** and the v**alue of `map->l_addr`** so `*array` will point to the fake array.
|
||||||
|
* According to main post of this technique and [**this writeup**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet) ld.so leave a pointer on the stack that points to the binary `link_map` in ld.so. With an arbitrary write it's possible to overwrite it and make it point to a fake `fini_array` controlled by the attacker with the address to a [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) for example.
|
||||||
|
|
||||||
|
Following the previous code you can find another interesting section with the code:
|
||||||
|
|
||||||
|
```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));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case it would be possible to overwrite the value of `map->l_info[DT_FINI]` pointing to a forged `ElfW(Dyn)` structure. Find [**more information here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link\_map-structure).
|
||||||
|
|
||||||
|
## TLS-Storage dtor\_list overwrite in **`__run_exit_handlers`**
|
||||||
|
|
||||||
|
As [**explained here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite), if a program exits via `return` or `exit()`, it'll execute **`__run_exit_handlers()`** which will call any destructors function registered.
|
||||||
|
|
||||||
|
Code from `_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 ();
|
||||||
|
```
|
||||||
|
|
||||||
|
Code from **`__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);
|
||||||
|
[...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For each registered function in **`tls_dtor_list`**, it'll demangle the pointer from **`cur->func`** and call it with the argument **`cur->obj`**.
|
||||||
|
|
||||||
|
Using the **`tls`** function from this [**fork of GEF**](https://github.com/bata24/gef), it's possible to see that actually the **`dtor_list`** is very **close** to the **stack canary** and **PTR\_MANGLE cookie**. So, with an overflow on it's it would be possible to **overwrite** the **cookie** and the **stack canary**.\
|
||||||
|
Overwriting the PTR\_MANGLE cookie, it would be possible to **bypass the `PTR_DEMANLE` function** by setting it to 0x00, will mean that the **`xor`** used to get the real address is just the address configured. Then, by writing on the **`dtor_list`** it's possible **chain several functions** with the function **address** and it's **argument.**
|
||||||
|
|
||||||
|
Finally notice that the stored pointer is not only going to be xored with the cookie but also rotated 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
|
||||||
|
```
|
||||||
|
|
||||||
|
So you need to take this into account before adding a new address.
|
||||||
|
|
||||||
|
Find an example in the [**original post**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite).
|
||||||
|
|
||||||
|
## Other mangled pointers in **`__run_exit_handlers`**
|
||||||
|
|
||||||
|
This technique is [**explained here**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor\_list-overwrite) and depends again on the program **exiting calling `return` or `exit()`** so **`__run_exit_handlers()`** is called.
|
||||||
|
|
||||||
|
Let's check more code of this function:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```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);
|
||||||
|
```
|
||||||
|
|
||||||
|
The variable `f` points to the **`initial`** structure and depending on the value of `f->flavor` different functions will be called.\
|
||||||
|
Depending on the value, the address of the function to call will be in a different place, but it'll always be **demangled**.
|
||||||
|
|
||||||
|
Moreover, in the options **`ef_on`** and **`ef_cxa`** it's also possible to control an **argument**.
|
||||||
|
|
||||||
|
It's possible to check the **`initial` structure** in a debugging session with GEF running **`gef> p initial`**.
|
||||||
|
|
||||||
|
To abuse this you need either to **leak or erase the `PTR_MANGLE`cookie** and then overwrite a `cxa` entry in initial with `system('/bin/sh')`.\
|
||||||
|
You can find an example of this in the [**original blog post about the technique**](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>
|
<details>
|
||||||
|
|
||||||
<summary><strong>Learn AWS hacking from zero to hero with</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
<summary><strong>Learn AWS hacking from zero to hero with</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
||||||
|
|
Loading…
Reference in a new issue