hacktricks/binary-exploitation/arbitrary-write-2-exec/www2exec-atexit.md

12 KiB
Raw Permalink Blame History

WWW2Exec - atexit(), TLS Depolama ve Diğer Karışık İşaretçiler

{% hint style="success" %} AWS Hacking'i öğrenin ve uygulayın:HackTricks Eğitim AWS Kırmızı Takım Uzmanı (ARTE)
GCP Hacking'i öğrenin ve uygulayın: HackTricks Eğitim GCP Kırmızı Takım Uzmanı (GRTE)

HackTricks'i Destekleyin
{% endhint %}

__atexit Yapıları

{% hint style="danger" %} Günümüzde bunu sömürmek çok garip! {% endhint %}

atexit(), diğer fonksiyonların parametre olarak geçirildiği bir fonksiyondur. Bu fonksiyonlar, bir exit() veya main'in dönüşü sırasında çalıştırılacaklardır.
Eğer bu fonksiyonlardan herhangi birinin adresini değiştirebilirseniz ve örneğin bir shellcode'a işaret ederse, işlem kontrolünü ele geçireceksiniz, ancak bu şu anda daha karmaşıktır.
Şu anda çalıştırılacak fonksiyonların adresleri birkaç yapı arkasında gizlenmiştir ve sonunda işaret ettiği adresler fonksiyonların adresleri değil, XOR ile şifrelenmiş ve rastgele bir anahtarla kaydırılmıştır. Bu nedenle bu saldırı vektörü şu anda en azından x86 ve x64_86 üzerinde çok kullanışlı değildir.
Şifreleme fonksiyonu PTR_MANGLE'dır. m68k, mips32, mips64, aarch64, arm, hppa gibi diğer mimariler, girdi olarak aldığı gibi şifrelemeyi uygulamaz. Bu nedenle bu mimariler bu vektör tarafından saldırıya uğrayabilir.

Bu nasıl çalıştığını ayrıntılı olarak https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html adresinde bulabilirsiniz.

Bu gönderideıklandığı gibi, program return veya exit() kullanarak çıkarsa, __run_exit_handlers()'ı çalıştıracak ve kayıtlı yıkıcıları çağıracaktır.

{% hint style="danger" %} Program _exit() fonksiyonu ile çıkarsa, exit sistem çağrısını yapacak ve çıkış işleyicileri çalıştırılmayacaktır. Bu nedenle, __run_exit_handlers()'ın çalıştırıldığını doğrulamak için bir kesme noktası ayarlayabilirsiniz. {% endhint %}

Önemli kod (kaynak):

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
}

Not: map -> l_addr + fini_array -> d_un.d_ptr'ın konumunu hesaplamak için nasıl kullanıldığına dikkat edin.

Birkaç seçenek bulunmaktadır:

  • map->l_addr değerini üzerine yazarak, yürütülecek keyfi kod talimatları içeren sahte bir fini_array'ya işaret etmesini sağlamak
  • Bellekte daha çok ardışık olan l_info[DT_FINI_ARRAY] ve l_info[DT_FINI_ARRAYSZ] girişlerini üzerine yazarak, yine array'ın saldırganın kontrol ettiği bir bellek bölgesine işaret etmesini sağlayacak sahte bir Elf64_Dyn yapısına işaret etmelerini sağlamak.
  • Bu yazıda l_info[DT_FINI_ARRAY]'ı .bss içindeki sahte bir fini_array içeren kontrol edilen bellek adresiyle üzerine yazıyor. Bu sahte dizi önce yürütülecek bir one gadget adresini içerir ve ardından bu sahte dizinin adresi ile map->l_addr'ın değeri arasındaki fark ve böylece *array sahte diziye işaret eder.
  • Bu tekniğin ana yazısına ve bu yazıya göre ld.so, ld.so'da binary link_map'e işaret eden bir işaretçi bırakır. Keyfi yazma ile üzerine yazarak, saldırganın kontrol ettiği sahte bir fini_array'a işaret eden ve örneğin bir one gadget adresine işaret eden bu işaretçiyi yapabilirsiniz.

Önceki kodun devamında, ilginç bir bölüm daha bulabilirsiniz:

/* 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));
}

Bu durumda, map->l_info[DT_FINI] değerinin üzerine yazılması mümkün olacaktır ve sahte bir ElfW(Dyn) yapısına işaret edilecektir. Daha fazla bilgi için buraya bakabilirsiniz.

TLS-Depolama dtor_list üzerine yazma işlemi __run_exit_handlers içinde

Eğer bir program return veya exit() ile çıkış yaparsa, __run_exit_handlers() fonksiyonunu çalıştıracaktır ve kayıtlı olan herhangi bir yıkıcı fonksiyonu çağıracaktır.

_run_exit_handlers() fonksiyonundan gelen kod:

/* 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() fonksiyonundan kod:

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);
[...]
}
}

Her bir kayıtlı fonksiyon için tls_dtor_list içindeki işaretçiyi cur->func'dan çözer ve cur->obj argümanı ile çağırır.

Bu GEF'in bu çatalından tls fonksiyonunu kullanarak, aslında dtor_list'in stack canary ve PTR_MANGLE cookie'ye çok yakın olduğunu görmek mümkündür. Bu nedenle, üzerine taşma gerçekleştirilirse cookie ve stack canary'yi üzerine yazmak mümkün olacaktır.
PTR_MANGLE cookie üzerine yazıldığında, bunu 0x00 olarak ayarlayarak PTR_DEMANLE fonksiyonunu atlayabilir ve gerçek adresi elde etmek için kullanılan xor sadece yapılandırılmış adres olacaktır. Ardından, dtor_list üzerine yazarak, fonksiyon adresi ve argümanı ile birkaç fonksiyonu zincirleme olasılığı vardır.

Son olarak, depolanan işaretçinin sadece cookie ile xor işlemine tabi tutulmayacağını, aynı zamanda 17 bit döndürüleceğini unutmayın:

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

Yeni bir adres eklemeden önce bunu dikkate almanız gerekmektedir.

Örnek bulun: orijinal gönderide.

Diğer bozulmuş işaretçiler __run_exit_handlers içinde

Bu teknik burada açıklanmıştır ve yine programın return veya exit() çağrısı yaparak çıkması ve dolayısıyla __run_exit_handlers() fonksiyonunun çağrılmasına bağlıdır.

Bu fonksiyonun daha fazla kodunu kontrol edelim:

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);

Değişken f, initial yapısına işaret eder ve f->flavor değerine bağlı olarak farklı fonksiyonlar çağrılacaktır.
Değerine bağlı olarak çağrılacak fonksiyonun adresi farklı bir yerde olacak, ancak her zaman demangled olacaktır.

Ayrıca, ef_on ve ef_cxa seçeneklerinde bir argümanı kontrol etmek de mümkündür.

Hata ayıklama oturumunda gef> p initial komutunu çalıştırarak initial yapısını kontrol etmek mümkündür.

Bunu kötüye kullanmak için ya PTR_MANGLEcookie'yi sızdırmanız ya da silmeniz ve ardından initial içindeki bir cxa girdisini system('/bin/sh') ile üzerine yazmanız gerekir.
Bu tekniğe ilişkin bir örneği orijinal teknik hakkındaki blog yazısında bulabilirsiniz.