Zum Inhalt springen
Startseite » picoCTF Stonks

picoCTF Stonks

Stonks

Heute möchte ich Ihnen zeigen, wie ihr die Challenge „Stonks“ auf picoCTF löst.

Im Text zur Challenge heißt es: „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! “
Dazu erhalten wir eine Quellcodedatei mit dem vertrauenswürdigen Namen vuln.c und eine URL zu einem Server, auf dem das Programm läuft und mit dem wir uns über nc verbinden sollen.

Werfen wir im ersten Schritt einen Blick auf den Quellcode:

#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);
}

Wie können wir nun die Flag, die auf dem Server liegt, bekommen?

Wenn wir uns den gesamten Quellcode angesehen haben, stellen wir fest, dass in der Funktion „buy_stonks“ eine Datei auf dem Server geöffnet und in den Speicher geladen wird. Grund genug, sich die Funktion genauer anzuschauen.

Jeder, der sich schon einmal mit binary exploitation Challenges beschäftigt hat, weiß, dass es in C einige Funktionen gibt, die besonders anfällig für eine Ausnutzung sind. Eine davon ist die Funktion „printf“, die eine formatierte Ausgabe erzeugen soll. Wenn diese Funktion jedoch falsch verwendet wird, ist es möglich, Speicher zu lesen (und sogar in ihn zu schreiben, was wir für diese Herausforderung nicht brauchen). Hier ist die Zeile printf(user_buf); für uns interessant, weil ‚printf‘ die Benutzereingabe ungeprüft übernimmt, die zuvor mit ’scanf‘ eingelesen wurde. Wenn wir hier also den Payload geschickt erstellen, ist es uns möglich, Teile des RAMs und damit den Inhalt der zuvor eingelesenen Datei zu auszugeben.

Diese Form des Angriffs wird als „format string attack“ bezeichnet. Normalerweise soll ‚printf‘ die Ausgabe formatieren. Dazu nimmt es den Ausgabestring mit Platzhaltern als ersten Parameter entgegen. Weitere Parameter werden dann von ‚printf‘ entsprechend der Art des Platzhalters formatiert an der entsprechenden Stelle eingefügt. Mögliche Platzhalter wären z.B. ‚%s‘ für eine Zeichenkette oder ‚%d‘ für eine ganze Zahl. Die ausnutzbare Zeile hätte also printf("%s", user_buf); lauten müssen. Dies hätte den Inhalt der Variablen ‚user_buf‘ als Zeichenkette interpretiert und die Gefahr wäre gebannt gewesen. Auf diese Weise können wir jedoch selbst Platzhalter einfügen, die es uns ermöglichen, den Arbeitsspeicher zu lesen.

Bevor wir ein Skript schreiben, das diesen Fehler ausnutzt, wollen wir sehen, ob wir richtig liegen. Wir stellen eine Verbindung zum Server über nc her, bestätigen die Frage, was wir tun wollen, mit 1 und geben die Zeichenfolge %p ein, wenn wir nach unserem API-Token gefragt werden:

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?
%p
Buying stonks with token:
0x7fa8d9732743
Portfolio as of Thu Jul 28 09:03:30 AM EDT 2022


9 shares of DN
3 shares of OWPE
6 shares of OTJW
4 shares of UDL
20 shares of IQR
29 shares of WJ
9 shares of CXZB
52 shares of MSWS
60 shares of BMA
92 shares of DNB
66 shares of CWXQ
1258 shares of V
Goodbye!

Das hat funktioniert. In der Zeile 0x7fa8d9732743 können wir den einen Teil des RAMs (in hexadezimaler Darstellung) ausgeben.

Jetzt ist es an der Zeit, unseren Exploit zu bauen. Hierfür verwende ich gerne Python in Kombination mit der Bibliothek ‚Pwntools‘. Wie ihr diese z.B. unter Kali installieren könnt, erfahrt ihr hier.

Ich habe ein kleines Python-Skript erstellt und genau dokumentiert, was jeder Schritt bewirkt.
Ich will ehrlich sein: Es gab eine Menge Trial and Error, aber das Wichtigste ist, dass es am Ende funktioniert.

Wenn du dieses Skript verwendest, kann es vorkommen, dass vor und nach der Flagge ein oder zwei zusätzliche Zeichen angezeigt werden, aber da das Format der Flaggen bekannt ist, sollte dies kein Problem darstellen.

Wenn du mehr über den „Format-String-Angriff“ wissen möchtest, empfehle ich diesen Artikel auf owasp.org

Schaue dir auch meine anderen CTF-WriteUps an.

Schlagwörter:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.