WWW2Exec - .dtors & .fini_array

जानें AWS हैकिंग को शून्य से हीरो तक htARTE (HackTricks AWS Red Team Expert) के साथ!

HackTricks का समर्थन करने के अन्य तरीके:

.dtors

आजकल किसी भी बाइनरी में .dtors सेक्शन के साथ एक बहुत अजीब चीज है!

डिस्ट्रक्टर्स वे फ़ंक्शन हैं जो प्रोग्राम समाप्त होने से पहले (जब main फ़ंक्शन वापस लौटता है) क्रियान्वित होते हैं। इन फ़ंक्शनों के पते बाइनरी के .dtors सेक्शन में संग्रहीत होते हैं और इसलिए, यदि आप __DTOR_END__ में एक शैलकोड के पते को लिखने में सफल होते हैं, तो वह प्रोग्राम समाप्त होने से पहले क्रियान्वित हो जाएगा।

इस सेक्शन का पता प्राप्त करें:

objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”

.fini_array

मौलिक रूप से यह एक संरचना है जिसमें फ़ंक्शन होते हैं जो कार्यक्रम समाप्त होने से पहले कॉल किए जाएंगे, जैसे .dtors। यह दिलचस्प है अगर आप अपने शैलकोड को एक पते पर जाकर कॉल कर सकते हैं, या उन मामलों में जहां आपको दोबारा main पर जाने की आवश्यकता होती है ताकि आप सुरक्षा दोष का दूसरी बार शार्ट कर सकें

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
8049934 a0850408

#Put your address in 0x8049934

ध्यान दें कि जब .fini_array से एक फ़ंक्शन को निष्पादित किया जाता है, तो यह अगले फ़ंक्शन पर जाता है, इसलिए यह कई बार नहीं निष्पादित होगा (अनंत लूप को रोकना), लेकिन यह आपको यहाँ एक फ़ंक्शन के निष्पादन का केवल 1 बार देगा।

.fini_array में प्रविष्टियों को उल्टे क्रम में बुलाया जाता है, इसलिए आपको संभावित रूप से आखिरी से लिखना चाहिए।

अनंत लूप

अनंत लूप प्राप्त करने के लिए .fini_array का दुरुपयोग करने के लिए आप यहाँ क्या किया गया था देखें: यदि आपके पास कम से कम 2 प्रविष्टियाँ .fini_array में हैं, तो आप:

  • अपनी पहली लेखन का उपयोग करें विकल्पी लेखन फ़ंक्शन को बुलाने के लिए फिर से

  • फिर, __libc_csu_fini द्वारा संग्रहित स्टैक में वापसी पता करें (जो सभी .fini_array फ़ंक्शनों को बुलाने वाला फ़ंक्शन है) और वहाँ __libc_csu_fini का पता डालें

  • यह __libc_csu_fini को फिर से बुलाएगा और .fini_array फ़ंक्शनों को फिर से निष्पादित करेगा जो विकल्पी WWW फ़ंक्शन को 2 बार बुलाएगा: एक बार के लिए विकल्पी लेखन और एक और बार __libc_csu_fini के वापसी पते को फिर से ओवरराइट करने के लिए स्टैक पर अपने आप को बुलाने के लिए।

ध्यान दें कि पूर्ण RELRO** के साथ, खंड .fini_array को केवल पढ़ने योग्य बनाया जाता है।

जैसा कि इस पोस्ट में समझाया गया है, यदि कार्यक्रम return या exit() का उपयोग करके बाहर निकलता है, तो यह __run_exit_handlers() को चलाएगा जो पंजीकृत नाशकों को बुलाएगा।

यदि कार्यक्रम _exit() फ़ंक्शन का उपयोग करके बाहर निकलता है, तो यह exit सिस्टम कॉल को बुलाएगा और बाहर निकालने वाले हैंडलर निष्पादित नहीं होंगे। इसलिए, __run_exit_handlers() को निष्पादित होने की पुष्टि करने के लिए आप उस पर एक ब्रेकपॉइंट सेट कर सकते हैं।

महत्वपूर्ण कोड है (स्रोत):

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 को एक स्मृति क्षेत्र की ओर दिखाए जिसे हमलावर नियंत्रित करता है।

  • इस लेख में l_info[DT_FINI_ARRAY] को .bss में एक नकली fini_array को नियंत्रित मेमोरी के पते के साथ ओवरराइट किया जाता है। यह नकली एरे पहले एक वन गैजेट का पता बताता है जो निष्पादित किया जाएगा और फिर इस नकली एरे के पते और map->l_addr के मान के बीच अंतर है ताकि *array नकली एरे की ओर पहुंचे।

  • इस तकनीक के मुख्य पोस्ट और इस लेख के अनुसार ld.so स्टैक पर एक पॉइंटर छोड़ता है जो ld.so में बाइनरी link_map की ओर पहुंचता है। एक अनियंत्रित लेखन के साथ इसे ओवरराइट करना संभव है और इसे एक हमलावर द्वारा नियंत्रित नकली fini_array की ओर पहुंचाना संभव है जिसमें वन गैजेट के लिए एक पता हो।

पिछले कोड के अनुसरन आपको एक और दिलचस्प खंड मिलेगा जिसमें कोड है:

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

इस मामले में map->l_info[DT_FINI] के मान को ओवरराइट करना संभव होगा जो एक जाली ElfW(Dyn) संरचना को दिखा रहा है। यहाँ अधिक जानकारी प्राप्त करें.

__run_exit_handlers में TLS-स्टोरेज dtor_list ओवरराइट

जैसा यहाँ स्पष्ट किया गया है, अगर कोई प्रोग्राम return या exit() के माध्यम से बंद होता है, तो यह __run_exit_handlers() को निष्पादित करेगा जो किसी भी नष्टकर्ता कार्य को कॉल करेगा जो पंजीकृत है।

कोड _run_exit_handlers() से:

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

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 के fork से tls फ़ंक्शन का उपयोग करके, यह संभव है कि वास्तव में dtor_list बहुत क्लोज है stack canary और PTR_MANGLE cookie के पास। इसलिए, इस पर ओवरफ़्लो होने पर cookie और stack canary को ओवरराइट किया जा सकता है। PTR_MANGLE कुकी को ओवरराइट करने से, PTR_DEMANLE फ़ंक्शन को बाईपास किया जा सकता है क्योंकि इसे 0x00 पर सेट करने से यह अर्थ होगा कि वास्तविक पता प्राप्त करने के लिए उपयोग किया गया xor केवल पता कॉन्फ़िगर किया गया है। फिर, dtor_list पर लिखने से यह संभव है कि फ़ंक्शन पता और इसके विवाद के साथ कई फ़ंक्शनों को चेन किया जा सकता है।

अंत में ध्यान दें कि स्टोर किया गया पॉइंटर केवल कुकी के साथ xored होने वाला है बल्कि 17 बिट घुमाया जाएगा:

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.

अन्य मैंगल्ड पॉइंटर __run_exit_handlers में

यह तकनीक यहाँ समझाई गई है और फिर से कार्यक्रम return या exit() को बुलाकर बाहर निकलने पर निर्भर है ताकि __run_exit_handlers() को बुलाया जाए।

इस फ़ंक्शन को और अधिक कोड देखते हैं:

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 में एक आर्ग्यूमेंट को भी नियंत्रित किया जा सकता है।

डीबगिंग सत्र में GEF चलाते समय gef> p initial के साथ initial संरचना की जांच की जा सकती है।

इसे उपयोग करने के लिए आपको या तो PTR_MANGLE कुकी को लीक करना होगा या मिटाना होगा और फिर system('/bin/sh') के साथ cxa प्रविष्टि को ओवरराइट करना होगा। आप इसका एक उदाहरण तकनीक के बारे में मूल ब्लॉग पोस्ट में देख सकते हैं।

Last updated