227 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WWW2Exec - atexit(), TLS存储和其他混淆指针
{{#include ../../banners/hacktricks-training.md}}
## **\_\_atexit 结构**
> [!CAUTION]
> 现在利用这个是非常**奇怪的!**
**`atexit()`** 是一个函数,**其他函数作为参数传递给它。** 这些 **函数** 将在执行 **`exit()`** 或 **main****返回** 时被 **执行**。\
如果你能 **修改** 这些 **函数****地址** 使其指向一个 shellcode例如你将 **获得对进程的控制**,但这目前更复杂。\
目前要执行的 **函数地址****隐藏** 在几个结构后面,最终指向的地址不是函数的地址,而是 **用 XOR 加密****随机密钥** 进行位移的。因此,目前这个攻击向量在 **x86****x64_86****不是很有用**。\
**加密函数****`PTR_MANGLE`**。 **其他架构** 如 m68k、mips32、mips64、aarch64、arm、hppa... **不实现加密** 函数,因为它 **返回与输入相同** 的内容。因此,这些架构可以通过这个向量进行攻击。
你可以在 [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
如 [**在这篇文章中**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure) 所述,如果程序通过 `return``exit()` 退出,它将运行 `__run_exit_handlers()`,这将调用注册的析构函数。
> [!CAUTION]
> 如果程序通过 **`_exit()`** 函数退出,它将调用 **`exit` syscall**,并且退出处理程序将不会被执行。因此,为了确认 `__run_exit_handlers()` 被执行,你可以在它上面设置一个断点。
重要代码是 ([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
}
```
注意 `map -> l_addr + fini_array -> d_un.d_ptr` 是如何用来**计算**要调用的**函数数组**的位置的。
有**几种选择**
- 覆盖 `map->l_addr` 的值,使其指向一个**假的 `fini_array`**,其中包含执行任意代码的指令。
- 覆盖 `l_info[DT_FINI_ARRAY]``l_info[DT_FINI_ARRAYSZ]` 条目(在内存中大致是连续的),使它们**指向一个伪造的 `Elf64_Dyn`** 结构,这将使得**`array` 再次指向攻击者控制的内存**区域。
- [**这个写作**](https://github.com/nobodyisnobody/write-ups/tree/main/DanteCTF.2023/pwn/Sentence.To.Hell) 用一个受控内存的地址覆盖 `l_info[DT_FINI_ARRAY]`,该内存位于 `.bss` 中,包含一个假的 `fini_array`。这个假数组**首先包含一个** [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) **地址**,将被执行,然后是这个**假数组**的地址与**`map->l_addr`**的**差值**,使得 `*array` 指向假数组。
- 根据该技术的主要帖子和[**这个写作**](https://activities.tjhsst.edu/csc/writeups/angstromctf-2021-wallstreet)ld.so 在栈上留下一个指向 ld.so 中二进制 `link_map` 的指针。通过任意写入,可以覆盖它并使其指向一个由攻击者控制的假 `fini_array`,其中包含一个 [**one gadget**](../rop-return-oriented-programing/ret2lib/one-gadget.md) 的地址,例如。
在前面的代码之后,您可以找到另一个有趣的代码部分:
```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));
}
```
在这种情况下,可以通过指向伪造的 `ElfW(Dyn)` 结构来覆盖 `map->l_info[DT_FINI]` 的值。找到 [**更多信息这里**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#2---targetting-ldso-link_map-structure)。
## TLS-Storage dtor_list 在 **`__run_exit_handlers`** 中的覆盖
正如 [**这里解释的**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite),如果程序通过 `return``exit()` 退出,它将执行 **`__run_exit_handlers()`**,该函数将调用任何注册的析构函数。
来自 `_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 ();
```
**`__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);
[...]
}
}
```
对于 **`tls_dtor_list`** 中的每个注册函数,它将从 **`cur->func`** 解码指针,并使用参数 **`cur->obj`** 调用它。
使用这个 [**GEF 的分支**](https://github.com/bata24/gef) 中的 **`tls`** 函数,可以看到实际上 **`dtor_list`** 非常 **接近** **栈保护****PTR_MANGLE cookie**。因此,通过对其进行溢出,可以 **覆盖** **cookie****栈保护**。\
覆盖 PTR_MANGLE cookie将其设置为 0x00将意味着用于获取真实地址的 **`xor`** 只是配置的地址。然后,通过写入 **`dtor_list`**,可以 **链接多个函数** 及其 **地址****参数**
最后注意,存储的指针不仅会与 cookie 进行异或运算,还会旋转 17 位:
```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
```
在添加新地址之前,您需要考虑这一点。
在[**原始帖子**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite)中找到一个示例。
## **`__run_exit_handlers`** 中的其他损坏指针
此技术在[**这里**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#5---code-execution-via-tls-storage-dtor_list-overwrite)中进行了说明,并再次依赖于程序**通过调用 `return``exit()` 退出**,因此调用了**`__run_exit_handlers()`**。
让我们检查一下该函数的更多代码:
```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);
```
变量 `f` 指向 **`initial`** 结构,具体调用哪个函数取决于 `f->flavor` 的值。\
根据该值,调用的函数地址会在不同的位置,但它始终是 **demangled** 的。
此外,在选项 **`ef_on`** 和 **`ef_cxa`** 中,也可以控制一个 **argument**
可以在调试会话中使用 GEF 检查 **`initial` 结构**,命令为 **`gef> p initial`**。
要利用这一点,你需要 **leak 或者擦除 `PTR_MANGLE`cookie**,然后用 `system('/bin/sh')` 覆盖 initial 中的 `cxa` 条目。\
你可以在 [**关于该技术的原始博客文章**](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}}