picoCTF 2021 Stonks Writeup

This is my writeup for Stonks, a Binary Exploitation puzzle put out for picoCTF 2021. This, along with many other Binary Exploitation puzzles are available at play.picoctf.org.

Stonks was not worth a lot of points compared to other challenges, so I figured it would be easy. Comparatively, the highest scoring puzzle in the Binary Exploitation category in picoGym is worth 500 points.

This puzzle provides you with vuln.c, which is a small C program about 150 lines long, and an instance of this code running on the internet, able to be accessed with netcat.

vuln.c is listed below in its entirety:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#define FLAG_BUFFER 128
#define MAX_SYM_LEN 4

typedef struct Stonks {
	int shares;
	char symbol[MAX_SYM_LEN + 1];
	struct Stonks *next;
} Stonk;

typedef struct Portfolios {
	int money;
	Stonk *head;
} Portfolio;

int view_portfolio(Portfolio *p) {
	if (!p) {
		return 1;
	}
	printf("\nPortfolio as of ");
	fflush(stdout);
	system("date"); // TODO: implement this in C
	fflush(stdout);

	printf("\n\n");
	Stonk *head = p->head;
	if (!head) {
		printf("You don't own any stonks!\n");
	}
	while (head) {
		printf("%d shares of %s\n", head->shares, head->symbol);
		head = head->next;
	}
	return 0;
}

Stonk *pick_symbol_with_AI(int shares) {
	if (shares < 1) {
		return NULL;
	}
	Stonk *stonk = malloc(sizeof(Stonk));
	stonk->shares = shares;

	int AI_symbol_len = (rand() % MAX_SYM_LEN) + 1;
	for (int i = 0; i <= MAX_SYM_LEN; i++) {
		if (i < AI_symbol_len) {
			stonk->symbol[i] = 'A' + (rand() % 26);
		} else {
			stonk->symbol[i] = '\0';
		}
	}

	stonk->next = NULL;

	return stonk;
}

int buy_stonks(Portfolio *p) {
	if (!p) {
		return 1;
	}
	char api_buf[FLAG_BUFFER];
	FILE *f = fopen("api","r");
	if (!f) {
		printf("Flag file not found. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);

	int money = p->money;
	int shares = 0;
	Stonk *temp = NULL;
	printf("Using patented AI algorithms to buy stonks\n");
	while (money > 0) {
		shares = (rand() % money) + 1;
		temp = pick_symbol_with_AI(shares);
		temp->next = p->head;
		p->head = temp;
		money -= shares;
	}
	printf("Stonks chosen\n");

	// TODO: Figure out how to read token from file, for now just ask

	char *user_buf = malloc(300 + 1);
	printf("What is your API token?\n");
	scanf("%300s", user_buf);
	printf("Buying stonks with token:\n");
	printf(user_buf);

	// TODO: Actually use key to interact with API

	view_portfolio(p);

	return 0;
}

Portfolio *initialize_portfolio() {
	Portfolio *p = malloc(sizeof(Portfolio));
	p->money = (rand() % 2018) + 1;
	p->head = NULL;
	return p;
}

void free_portfolio(Portfolio *p) {
	Stonk *current = p->head;
	Stonk *next = NULL;
	while (current) {
		next = current->next;
		free(current);
		current = next;
	}
	free(p);
}

int main(int argc, char *argv[])
{
	setbuf(stdout, NULL);
	srand(time(NULL));
	Portfolio *p = initialize_portfolio();
	if (!p) {
		printf("Memory failure\n");
		exit(1);
	}

	int resp = 0;

	printf("Welcome back to the trading app!\n\n");
	printf("What would you like to do?\n");
	printf("1) Buy some stonks!\n");
	printf("2) View my portfolio\n");
	scanf("%d", &resp);

	if (resp == 1) {
		buy_stonks(p);
	} else if (resp == 2) {
		view_portfolio(p);
	}

	free_portfolio(p);
	printf("Goodbye!\n");

	exit(0);
}

Reading the code, I was able to spot the vulnerability on line 93. Using netcat, I connected to the vulnerable instance and tested this vulnerability out. The presence of the hexadecimal address output after the Buying stonks with token: line demonstrates that this bug is indeed present:

Reading into the code more, this program reads the flag from a file, placing it within api_buf on lines 66-72.

    char api_buf[FLAG_BUFFER];
    FILE *f = fopen("api","r");
    if (!f) {
        printf("Flag file not found. Contact an admin.\n");
        exit(1);
    }
    fgets(api_buf, FLAG_BUFFER, f);

Knowing this, and taking the low point value of this puzzle into account, I assumed that the intended method to solve this puzzle is simple information disclosure, and won’t require using a debugger, writing shellcode, or any advanced exploitation methods.

Keeping it simple, I tried using %N$s format strings, but this did not reveal the flag. I tried brute forcing values 1-50 with a small shell script:

for i in {1..50};
do
    (echo 1; echo "%${i}\$s") | nc mercury.picoctf.net 33411
done | less

This didn’t work, so next I tried to view memory as hex bytes and see if I spot anything:

Since I have robot eyes, I was able to see some hexadecimal that is probably ASCII.

I copied the portion of output I thought was a string encoded as hex, and threw it into CyberChef to attempt to decode it.

Using From Hex I was greeted with what looks like the flag:

At first glance, this looks like I need to swap the endianness. I am also missing the end of the flag due to the lack of trailing curly bracket in this decoded output.

I added the missing right curly bracket byte to my Input with some null bytes to account for the endianness, and added an ingredient to my recipe to swap endianness.

This revealed the flag:

References

Exploiting Format String Vulnerabilities

printf man page

ascii(7) man page

Hacking: The Art of Exploitation – 0x351

The Shellcoders Handbook, 2nd Edition. Chapter 4, 18

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s