This is my writeup for the "Stonks" binary exploitation challenge with Pico CTF. This was a relatively simple string format vulnerability that leads to information disclosure, through dumping memory data off the stack, and converting those hexadecimal values from big endian to little endian.
The provided code in which we evaluate for vulnerable code was quite long and required a thorough look over. This was far removed from the regular binary exploit challenges that come with other CTF's as it required considerably more thought and review, than simply spotting a very obvious unsanitzied buffer that is defined and called within the first twenty lines of code.
For the purposes of code review, I have gone through and addressed each function, leaving notes along the way to assist in staying organized and moving methodically in pursuit of a vulnerability. My initial pass over this code left me confused as I was expecting to find an unchecked buffer, followed by an overflow to stack execution. However having a string format vulnerability, is not so common in CTF's and I really appreciate it when I do see this type of vulnerability.
Starting off
We start off with a netcat connection to the server provided to us as apart of this challenge and we are immediately presented with an application prompting us to buy some stonks, or view our portfolio.
Selecting the second option terminates the program suggesting we have no stonks to view. Selecting the first option however provides a prompt to pass an API key.
From here we review the code to learn more about this function, the api key and just the program in general. The below section of code is responsible for capturing the users api key when requesting to purchase stocks. There is a request here for the user to pass an api key, and a basic check is performed to ensure a key has been entered. There is however no validation of the api key, to entering anything provides the option to buy stocks.
In the next code snippet, we can see that the developer has left some comments regarding a TODO relating to the API Key call.
In the above code snippet, we see that memory is created for the API key, and at the final line there is the "printf(user_buff" argument, which is the vulnerable piece of code. The "printf" function has no input validation surrounding it. Lets exploit this to read data off the stack.
Format string vulnerabilities 101
The format string exploit occurs when the submitted data of an input string is evaluated as a command by the application. This in essence allows an attacker to execute code, read data from the stack or cause a segmentation fault and overflow. which could lead to a compromise of the system. In order to understand this, we need to understand and clearly outline the smaller parts of the picture.
Format Function - This function is performed by printf and fpintf with converts a variable to human readable language.
Format String Parameter - The specified conversion format for the printf format function to use. Such as %x %x %p.
The attack can be executed when there is a lack of proper validation surrounding the input. %x could be inserted into the user input field and this would be passed directly to the stack.
Vulnerable code
#include <stdio.h>int
main(int argc, char **argv)
{printf(argv[1]);}
Safe Code
#include <stdio.h>
int main(int argc, char **argv)
{printf("%s", argv[1]);}
We can see in the above examples that printf is specifying the format type before passing the user input. This ensures the computer identifies the input value as a formatting character rather than a character.
Print the stack
Having identified a format string vulnerability, we will test this out by inputting %p which is passed directory to the stack and interpreted as read data from the stack.
Doing this provides hexadecimal data directly from the stack. I attempted to pass multiple %p's but this did not work.
From here I passed several %X characters to read data from the stack, and converting it was only providing me a broken flag. See below.
From here I stripped down the unnecessary characters and used a Swap endianness function and retrieved the flag, only having to add a closing curly brace at the end. Note that some teams solved this with pwntools, which could auto convert the output into p32 format.
picoCTF{I_l05t_4ll_my_m0n3y_1cf201a0}
Below is the original source code
#include <stdlib.h> // Standard general utilities
#include <stdio.h> // Standard input output
#include <string.h> // Common string functions
#include <time.h> // Time and date declarations
#define FLAG_BUFFER 128
#define MAX_SYM_LEN 4
// Defines struct to call later - no initial vulnerabilities
typedef struct Stonks {
int shares;
char symbol[MAX_SYM_LEN + 1];
struct Stonks *next;
} Stonk;
// Defines struct to call later - no initial vulnerabilities
typedef struct Portfolios {
int money;
Stonk *head;
} Portfolio;
// View portfolio options
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;
}
// Pick portfolio and share
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;
}
// Option to buy shares
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); // Vulnerable code here
printf("Buying stonks with token:\n");
printf(user_buf); // Print hex data here
// 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);
}
Reviewing the code revealed a string format vulnerability on line 93 in which a buffer was allocated for the users input. However the printf function was used, and without a specified format for input validation. This ultimately allows us to print information off the stack.
Thanks a lot for this. I managed to get the inverted flag, I should have realized that I had to change the endianess. Thanks a lot!