这个堆利用方式相较于以往的利用方式存在利用目标的不同,这一方式的利用我感觉比较适合与glibc2.31之后,比较之前都可以直接用FSOP,但是在2.31之后FSOP的利用方式就是house of pig不过如果题目禁止了__free_hook之类的被篡改的话也就没法利用了,而house of banana把攻击层面转向了ld
void _dl_fini (void) { /* Lots of fun ahead. We have to call the destructors for all still loaded objects, in all namespaces. The problem is that the ELF specification now demands that dependencies between the modules are taken into account. I.e., the destructor for a module is called before the ones for any of its dependencies. To make things more complicated, we cannot simply use the reverse order of the constructors. Since the user might have loaded objects using `dlopen' there are possibly several other modules with its dependencies to be taken into account. Therefore we have to start determining the order of the modules once again from the beginning. */
/* We run the destructors of the main namespaces last. As for the other namespaces, we pick run the destructors in them in reverse order of the namespace ID. */ #ifdef SHARED int do_audit = 0; again: #endif for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns) { /* Protect against concurrent loads and unloads. */ __rtld_lock_lock_recursive (GL(dl_load_lock));
unsignedint nloaded = GL(dl_ns)[ns]._ns_nloaded; /* No need to do anything for empty namespaces or those used for auditing DSOs. */ if (nloaded == 0 #ifdef SHARED || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit #endif ) __rtld_lock_unlock_recursive (GL(dl_load_lock)); else { /* Now we can allocate an array to hold all the pointers and copy the pointers in. */ struct link_map *maps[nloaded];
unsignedint i; structlink_map *l; assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL); for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) /* Do not handle ld.so in secondary namespaces. */ if (l == l->l_real) { assert (i < nloaded);
maps[i] = l; l->l_idx = i; ++i;
/* Bump l_direct_opencount of all objects so that they are not dlclose()ed from underneath us. */ ++l->l_direct_opencount; } assert (ns != LM_ID_BASE || i == nloaded); assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1); unsignedint nmaps = i;
/* Now we have to do the sorting. We can skip looking for the binary itself which is at the front of the search list for the main namespace. */ _dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE), NULL, true);
/* We do not rely on the linked list of loaded object anymore from this point on. We have our own list here (maps). The various members of this list cannot vanish since the open count is too high and will be decremented in this loop. So we release the lock so that some code which might be called from a destructor can directly or indirectly access the lock. */ __rtld_lock_unlock_recursive (GL(dl_load_lock));
/* 'maps' now contains the objects in the right order. Now call the destructors. We have to process this array from the front. */ for (i = 0; i < nmaps; ++i) { structlink_map *l = maps[i];
if (l->l_init_called) { /* Make sure nothing happens if we are called twice. */ l->l_init_called = 0;
/* Is there a destructor function? */ if (l->l_info[DT_FINI_ARRAY] != NULL || l->l_info[DT_FINI] != NULL) { /* When debugging print a message first. */ if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0)) _dl_debug_printf ("\ncalling fini: %s [%lu]\n\n", DSO_FILENAME (l->l_name), ns);
/* First see whether an array is given. */ if (l->l_info[DT_FINI_ARRAY] != NULL) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsignedint i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0) ((fini_t) array[i]) (); }
/* Next try the old-style destructor. */ if (l->l_info[DT_FINI] != NULL) DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr); } ... ...
可以看到最后调用了array[i](),不过我们需要控制array的话就需要进一步知道这是什么。
利用原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) /* Do not handle ld.so in secondary namespaces. */ if (l == l->l_real) { assert (i < nloaded);
maps[i] = l; l->l_idx = i; ++i;
/* Bump l_direct_opencount of all objects so that they are not dlclose()ed from underneath us. */ ++l->l_direct_opencount; }
structrtld_global { #endif /* Don't change the order of the following elements. 'dl_loaded' must remain the first element. Forever. */
/* Non-shared code has no support for multiple namespaces. */ #ifdef SHARED # define DL_NNS 16 #else # define DL_NNS 1 #endif EXTERN structlink_namespaces { /* A pointer to the map for the main map. */ structlink_map *_ns_loaded; /* Number of object in the _dl_loaded list. */ unsignedint _ns_nloaded; /* Direct pointer to the searchlist of the main object. */ structr_scope_elem *_ns_main_searchlist; /* This is zero at program start to signal that the global scope map is allocated by rtld. Later it keeps the size of the map. It might be reset if in _dl_close if the last global object is removed. */ unsignedint _ns_global_scope_alloc;
/* During dlopen, this is the number of objects that still need to be added to the global scope map. It has to be taken into account when resizing the map, for future map additions after recursive dlopen calls from ELF constructors. */ unsignedint _ns_global_scope_pending_adds;
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) /* Do not handle ld.so in secondary namespaces. */ if (l == l->l_real) { assert (i < nloaded);
maps[i] = l; l->l_idx = i; ++i;
/* Bump l_direct_opencount of all objects so that they are not dlclose()ed from underneath us. */ ++l->l_direct_opencount; } assert (ns != LM_ID_BASE || i == nloaded); assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);