この大会は2020/5/30 5:00(JST)~2020/6/1 5:00(JST)に開催されました。
今回もチームで参戦。結果は5725点で500チーム中37位でした。
自分で解けた問題をWriteupとして書いておきます。
Readme (Welcome)
ルールのページの賞金の記載の箇所に白字でフラグが書いてあった。
castorsCTF{0u7_0f_5173_0u7_0f_m1nd}
Password Crack 1 (Misc)
CrackStationで3c80b091de0981ec64e43262117d618aをクラックする。
irocktoo
castorsCTF{irocktoo}
Password Crack 2 (Misc)
CrackStationで867c9e11faa64d7a5257a56c415a42725e17aa6dをクラックする。
pi3141592653589
castorsCTF{pi3141592653589}
Password Crack 3 (Misc)
フラグフォーマットにして、rockyou.txtをブルートフォースしてsha256が一致するものを探す。
import hashlib with open('dict/rockyou.txt', 'r') as f: words = [word.rstrip() for word in f.readlines()] h = '7adebe1e15c37e23ab25c40a317b76547a75ad84bf57b378520fd59b66dd9e12' for word in words: flag = 'castorsCTF{%s}' % word if hashlib.sha256(flag).hexdigest() == h: print flag break
castorsCTF{theformat!}
Gif (Misc)
アニメーションGIFに表示される数字を順に並べてみる。
15 13 7 19 15 6 21 14 14 25 12 15 12
アルファベットのインデックスとしてあてはめてみる。
11111111111222222 12345678901234567890123456 abcdefghijklmnopqrstuvwxyz →omgsofunnylol
castorsCTF{omgsofunnylol}
Arithmetics (Coding)
$ nc chals20.cybercastors.com 14429 ------------------Welcome to Beginner Arithmetics!------------------ To get the flag you'll have to solve a series of arithmetic challenges. The problems may be addition, substraction, multiplication or integer division. Numbers can range from 1 to 9. Easy right? You'll have very little time to answer so do your best! Hit <enter> when ready. What is 6 * 1 ? 6 Correct answer! What is 9 + 5 ? 14 Too slow!
四則演算の計算結果を答えていくだけ。ただ、数値や演算が英語になるので変換が必要。
import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14429)) data = recvuntil(s, 'ready.\n').rstrip() print data + '\n' s.sendall('\n') for i in range(100): print 'Round %d' % (i+1) data = recvuntil(s, '\n').rstrip() print data data = data.replace('zero', '0') data = data.replace('one', '1') data = data.replace('two', '2') data = data.replace('three', '3') data = data.replace('four', '4') data = data.replace('five', '5') data = data.replace('six', '6') data = data.replace('seven', '7') data = data.replace('eight', '8') data = data.replace('nine', '9') data = data.replace('plus', '+') data = data.replace('minus', '-') data = data.replace('multiplied-by', '*') data = data.replace('divided-by', '//') ans = str(eval(data[8:-2])) print ans s.sendall(ans + '\n') data = recvuntil(s, '\n').rstrip() print data data = s.recv(8192) print data
実行結果は以下の通り。
: Round 100 What is nine - 3 ? 6 Correct answer! Wow! You're fast! Here's your flag: castorsCTF(n00b_pyth0n_4r17hm3t1c5}
castorsCTF(n00b_pyth0n_4r17hm3t1c5}
Base Runner (Coding)
$ nc chals20.cybercastors.com 14430 ______ ______ ______ ______ ______ __ __ __ __ __ __ ______ ______ /\ == \/\ __ \/\ ___\/\ ___\ /\ == \/\ \/\ \/\ "-.\ \/\ "-.\ \/\ ___\/\ == \ \ \ __<\ \ __ \ \___ \ \ __\ \ \ __<\ \ \_\ \ \ \-. \ \ \-. \ \ __\\ \ __< \ \_____\ \_\ \_\/\_____\ \_____\ \ \_\ \_\ \_____\ \_\\ \_\ \_\\ \_\ \_____\ \_\ \_\ \/_____/\/_/\/_/\/_____/\/_____/ \/_/ /_/\/_____/\/_/ \/_/\/_/ \/_/\/_____/\/_/ /_/ Listen up! We're down 0 - 49 with 2 outs on the final inning but we got this! Don't worry about getting hits, just run those bases as fast as you can. They have The Flash fielding for their team! Hit <enter> when ready. 00110110 00110101 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110110 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110001 00110100 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110111 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110000 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110001 00110100 00110101 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110101 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110101 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110001 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110011 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110001 00110100 00110001 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110110 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110110 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110110 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110111 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110000 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110000 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110110 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110001 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110001 00110100 00110100 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110001 00110100 00110100
次々とbaseを変更してデコードする。
2進数→8進数→16進数→base64
import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14430)) data = recvuntil(s, 'ready.\n').rstrip() print data + '\n' s.sendall('\n') for i in range(50): print 'Round %d' % (i+1) data = recvuntil(s, '\n').rstrip() print data codes = data.split(' ') msg = '' for code in codes: msg += chr(int(code, 2)) #print msg codes = msg.split(' ') msg = '' for code in codes: msg += chr(int(code, 8)) #print msg codes = msg.split(' ') msg = '' for code in codes: msg += chr(int(code, 16)) #print msg dec = msg.decode('base64') print dec s.sendall(dec) data = recvuntil(s, '\n').rstrip() print data data = s.recv(8192) print data
実行結果は以下の通り。
: Round 50 00110110 00110101 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110110 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110001 00110100 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110110 00110111 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110111 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110000 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110001 00110100 00110101 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110101 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110101 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110010 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110001 00110100 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110100 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110001 00110100 00110101 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110001 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110110 00110011 00100000 00110100 00110000 00100000 00110110 00110110 00100000 00110001 00110100 00110011 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110110 00110010 00100000 00110100 00110000 00100000 00110110 00110111 00100000 00110110 00110101 00100000 00110100 00110000 00100000 00110110 00110101 00100000 00110001 00110100 00110001 00100000 00110100 00110000 00100000 00110110 00110100 00100000 00110111 00110000 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110110 00110000 00100000 00110100 00110000 00100000 00110110 00110011 00100000 00110001 00110100 00110100 castorsCTF{mrKYwsurTnd} Correct answer! Wow! The Flash is impressed! Here's your flag: castorsCTF[m4j0r_l34gu3_py7h0n_b4s3_runn3r}
castorsCTF[m4j0r_l34gu3_py7h0n_b4s3_runn3r}
Flag Gods (Coding)
$ nc chals20.cybercastors.com 14431 ______ __ ______ ______ ______ ______ _____ ______ /\ ___/\ \ /\ __ \/\ ___\ /\ ___\/\ __ \/\ __-./\ ___\ \ \ __\ \ \___\ \ __ \ \ \__ \ \ \ \__ \ \ \/\ \ \ \/\ \ \___ \ \ \_\ \ \_____\ \_\ \_\ \_____\ \ \_____\ \_____\ \____-\/\_____\ \/_/ \/_____/\/_/\/_/\/_____/ \/_____/\/_____/\/____/ \/_____/ We have a small problem... The flag gods are trying to send us a message, but our transmitter isn't calibrated to decode it! If you can find the hamming distance for the following messages we may be able to calibrate the transmitter in time. Entering the wrong distance will lock the machine. Good luck, we'll only have 90 seconds! Hit <enter> when ready. The machine is currently 20% calibrated. Transmitted message: A clueless boy runs a boy merrily. Received message: 0e9e9a9382be9796ad9ff29d0087cfaa0e11d7cd995fbd91a6df839a8cbf839304d9 Enter hamming distance: 12 The machine locked up. The correct distance was: 213
Hamming distanceを答える必要がある。この問題の場合、Transmitted messageの2進表記とReceived messageの2進表記の差のあるビットの数を答えればよい。
import socket from Crypto.Util.number import * def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) def get_hamming_distance(t, r): bin_r = bin(int(r, 16))[2:].zfill(len(r)*4) bin_t = bin(bytes_to_long(t))[2:].zfill(len(r)*4) count = 0 for i in range(len(bin_r)): if bin_r[i] != bin_t[i]: count += 1 return count s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14431)) data = recvuntil(s, 'ready.\n').rstrip() print data + '\n' s.sendall('\n') for i in range(80): print 'Round %d' % (i+1) data = recvuntil(s, 'calibrated.\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data t_msg = data.split(': ')[1] data = recvuntil(s, '\n').rstrip() print data r_msg = data.split(': ')[1] distance = str(get_hamming_distance(t_msg, r_msg)) data = recvuntil(s, ': ') print data + distance s.sendall(distance + '\n') data = recvuntil(s, '\n').rstrip() print data data = s.recv(8192) print data
実行結果は以下の通り。
: Round 80 The machine is currently 99% calibrated. Transmitted message: The clueless girl hits a rabbit foolishly. Received message: 54686520636c77656c65737320636d726c206869747320612072616260697420664f6f6c6973686c792f Enter hamming distance: 6 Correct answer! Machine calibration successful! Here's the transmitted flag: castorsCTF{c0mmun1ng_w17h_7h3_f14g_g0d5}
castorsCTF{c0mmun1ng_w17h_7h3_f14g_g0d5}
Vault0 (Reversing)
checkしているhex文字列を結合してhexデコードすればよい。
636173746f72734354467b72317854795f6d316e757433735f67745f73317874795f6d316e757433737d
$ echo 636173746f72734354467b72317854795f6d316e757433735f67745f73317874795f6d316e757433737d | xxd -r -p castorsCTF{r1xTy_m1nut3s_gt_s1xty_m1nut3s}
castorsCTF{r1xTy_m1nut3s_gt_s1xty_m1nut3s}
XoR (Reversing)
Ghidraでデコンパイルする。
undefined8 FUN_001008b1(void) { int iVar1; long in_FS_OFFSET; char local_48 [56]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); printf("Enter flag: "); fgets(local_48,0x2c,stdin); iVar1 = FUN_0010080a(local_48); if (iVar1 == 0) { puts("Correct!"); } else { puts("Wrong flag!"); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; } ulong FUN_0010080a(char *pcParm1) { int iVar1; size_t sVar2; int local_18; sVar2 = strlen(pcParm1); local_18 = 0; while (local_18 < (int)sVar2) { pcParm1[(long)local_18] = pcParm1[(long)local_18] ^ (char)local_18 + 10U; pcParm1[(long)local_18] = pcParm1[(long)local_18] + -2; local_18 = local_18 + 1; } iVar1 = strcmp(pcParm1,&DAT_00301020); return (ulong)(iVar1 != 0); } DAT_00301020 XREF[1]: FUN_0010080a:00100893(*) 00301020 67 ?? 67h g 00301021 68 ?? 68h h 00301022 7d ?? 7Dh } 00301023 77 ?? 77h w 00301024 5f ?? 5Fh _ 00301025 7b ?? 7Bh { 00301026 61 ?? 61h a 00301027 50 ?? 50h P 00301028 44 ?? 44h D 00301029 53 ?? 53h S 0030102a 6d ?? 6Dh m 0030102b 6b ?? 6Bh k 0030102c 24 ?? 24h $ 0030102d 63 ?? 63h c 0030102e 68 ?? 68h h 0030102f 26 ?? 26h & 00301030 72 ?? 72h r 00301031 2b ?? 2Bh + 00301032 41 ?? 41h A 00301033 68 ?? 68h h 00301034 2d ?? 2Dh - 00301035 26 ?? 26h & 00301036 46 ?? 46h F 00301037 7c ?? 7Ch | 00301038 14 ?? 14h 00301039 7a ?? 7Ah z 0030103a 11 ?? 11h 0030103b 50 ?? 50h P 0030103c 15 ?? 15h 0030103d 10 ?? 10h 0030103e 1d ?? 1Dh 0030103f 52 ?? 52h R 00301040 1e ?? 1Eh
以下の処理をして比較している。
・入力文字の1文字ずつに対して以下の処理 ・(index値 +10)とXOR - 2
逆算してフラグにする。
def decrypt(s): dec = '' for i in range(len(s)): code = (ord(s[i]) + 2) ^ (i + 10) dec += chr(code) return dec enc = 'gh}w_{aPDSmk$ch&r+Ah-&F|\x14z\x11P\x15\x10\x1dR\x1e' flag = decrypt(enc) print flag
castorsCTF{x0rr1n6_w17h_4_7w157}
Reverse-me (Reversing)
Ghidraでデコンパイルする。
undefined8 FUN_00100b3e(void) { FILE *__stream; size_t sVar1; undefined8 uVar2; long in_FS_OFFSET; int local_74; char local_68 [32]; char local_48 [40]; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28); __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { printf("flag.txt not found."); fflush(stdout); uVar2 = 1; } else { fgets(&DAT_00302030,0x1e,__stream); fclose(__stream); FUN_0010096a(&DAT_00302030); strcpy(local_68,&DAT_00302030); FUN_00100a68(local_68); FUN_001009c7(local_68); puts("System Error...\nDumping memory..."); local_74 = 0; while( true ) { sVar1 = strlen(local_68); if (sVar1 <= (ulong)(long)local_74) break; printf("%x ",(ulong)(uint)(int)local_68[(long)local_74]); local_74 = local_74 + 1; } printf("\nEnter password: "); fflush(stdout); fgets(local_48,0x32,stdin); FUN_0010096a(local_48); FUN_00100a68(local_48); FUN_001009c7(local_48); FUN_00100abf(local_48,local_68,local_68); uVar2 = 0; } if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar2; } void FUN_00100a68(char *pcParm1) { size_t sVar1; int local_10; sVar1 = strlen(pcParm1); local_10 = 0; while (local_10 < (int)sVar1) { pcParm1[(long)local_10] = pcParm1[(long)local_10] + 2; local_10 = local_10 + 1; } return; } void FUN_001009c7(char *pcParm1) { size_t sVar1; int local_10; sVar1 = strlen(pcParm1); local_10 = 0; while (local_10 < (int)sVar1) { if (('`' < pcParm1[(long)local_10]) && (pcParm1[(long)local_10] < '{')) { pcParm1[(long)local_10] = (char)((int)pcParm1[(long)local_10] + -0x57) + (char)(((int)pcParm1[(long)local_10] + -0x57) / 0x1a) * -0x1a + 'a'; } local_10 = local_10 + 1; } return; } void FUN_00100abf(char *pcParm1,char *pcParm2) { int iVar1; iVar1 = strcmp(pcParm1,pcParm2); if (iVar1 == 0) { puts("Correct!"); printf("castorsCTF{%s}",&DAT_00302030); fflush(stdout); } else { puts("Wrong!"); fflush(stdout); } return; }
以下の処理をしている。
・フラグ文字の1文字ずつに対して以下の処理 ・a = フラグ文字 + 2 ・aが英小文字の場合は、さらに以下の処理 b = (((a - 0x57) - ((((a - 0x57) % 256) / 0x1a) % 256) * 0x1a + ord('a')) % 256
英小文字の場合のみ逆算の計算が面倒なので、あらかじめ英小文字の結果を取得しておき、元に戻す。
flag.txtに以下を書き、試す。
_`abcdefghijklmnopqrstuvwx
$ ./reverse_me System Error... Dumping memory... 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a Enter password: _`abcdefghijklmnopqrstuvwx Correct! castorsCTF{_`abcdefghijklmnopqrstuvwx}
以上のことから元に戻すスクリプトを作成し、サーバに対して実行する。
import socket lower_codes = ['6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a'] chars = '_`abcdefghijklmnopqrstuvwx' def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14427)) data = recvuntil(s, ': ') codes = data.split('\n')[-2].rstrip().split(' ') password = '' for code in codes: if code in lower_codes: index = lower_codes.index(code) password += chars[index] else: password += chr(int(code, 16) - 2) print data + password s.sendall(password + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '}') print data
実行結果は以下の通り。
System Error... Dumping memory... 64 35 68 35 64 37 33 7a 38 6b 33 37 6b 72 67 7a Enter password: r3v3r51n6_15_fun Correct! castorsCTF{r3v3r51n6_15_fun}
castorsCTF{r3v3r51n6_15_fun}
Goose Chase (Crypto)
2つの画像のRGBの数値をXORして画像を生成する。
from PIL import Image img1 = Image.open('chal.png').convert('RGB') img2 = Image.open('goose_stole_the_key.png').convert('RGB') w, h = img1.size output_img = Image.new('RGB', (w, h), (255, 255, 255)) for y in range(h): for x in range(w): r1, g1, b1 = img1.getpixel((x, y)) r2, g2, b2 = img2.getpixel((x, y)) r = r1 ^ r2 g = g1 ^ g2 b = b1 ^ b2 output_img.putpixel((x, y), (r, g, b)) output_img.save('flag.png')
生成した画像にフラグが書いてあった。
castorsCTF{m355_w1th_7h3_h0nk_y0u_g3t_7h3_b0nk}
One Trick Pony (Crypto)
$ nc chals20.cybercastors.com 14422 > 1 b'R' > 123 b'RS@' > 11111111111111111111111111111111111111111111 b'RPBE^CBrewJZ\x02\x02AnH\x01DCnZ\x02H\x04n\x04\x02RC\x02\x06n\x05_UnU\x01_\x06nC\x02' >
入力文字列とXORされたものが出力されているようだ。XOR鍵がフラグと推測する。\x00の文字列を平文として指定すれば、フラグが表示されるはず。
import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) def str_xor(s1, s2): return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14422)) try_pt = '\x00' * 64 data = recvuntil(s, '> ') print data + try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data
実行結果は以下の通り。
> b'castorsCTF{k33p_y0ur_k3y5_53cr37_4nd_d0n7_r3u53_7h3m!}castorsCTF'
castorsCTF{k33p_y0ur_k3y5_53cr37_4nd_d0n7_r3u53_7h3m!}
Bagel Bytes (Crypto)
$ nc chals20.cybercastors.com 14420 ______ ______ ______ ______ __ ______ __ __ ______ ______ ______ /\ == \/\ __ \/\ ___\/\ ___\/\ \ /\ == \/\ \_\ \/\__ _/\ ___\/\ ___\ \ \ __<\ \ __ \ \ \__ \ \ __\\ \ \____ \ \ __<\ \____ \/_/\ \\ \ __\\ \___ \ \ \_____\ \_\ \_\ \_____\ \_____\ \_____\ \ \_____\/\_____\ \ \_\\ \_____\/\_____\ \/_____/\/_/\/_/\/_____/\/_____/\/_____/ \/_____/\/_____/ \/_/ \/_____/\/_____/ Welcome to Bagel Bytes! Our ovens are known for baking ExtraCrispyBagels! We bake 16 bagels per rack and the last rack is always full! Today's special is flag bagels! Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 1 Thank you for baking with us! Here are your baked bytes: 5aa7c425b025966d8cf06149038fe69e2749234fac85fec430becdb8c824ef6c23a6f85858a2f04f13fc5758c2afba57 Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 2 Thank you for baking with us! Here are your baked bytes: ba1a164991d63589a4cd4bb3a9f7f55d2749234fac85fec430becdb8c824ef6c23a6f85858a2f04f13fc5758c2afba57 Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 1 Add your bagels: > 11 Thank you for baking with us! Here are your baked bytes: 2477b150ef383a265ea6a453184e6552 Select: 1) Bake your own bagels! 2) Bake the flag! Your choice:
AES暗号のようだが、Add your bagelsの意味がわからない。
Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 111111 Thank you for baking with us! Here are your baked bytes: 1686f128021d97491fbcc16c4807ad82ae782968412a8a6a73fb3fc05f507a2a93c0f899b9a1e5060f1fac207d850461 Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 1111111111111111111111 Thank you for baking with us! Here are your baked bytes: 66e32e645b7ee61b72962e7717b50e2a1686f128021d97491fbcc16c4807ad82ae782968412a8a6a73fb3fc05f507a2a93c0f899b9a1e5060f1fac207d850461 1686f128021d97491fbcc16c4807ad82ae782968412a8a6a73fb3fc05f507a2a93c0f899b9a1e5060f1fac207d850461 66e32e645b7ee61b72962e7717b50e2a 1686f128021d97491fbcc16c4807ad82ae782968412a8a6a73fb3fc05f507a2a93c0f899b9a1e5060f1fac207d850461
前に文字をaddして暗号化していると思われる。16の倍数のデータの場合、通常と違いパディングなし
Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 111111 Thank you for baking with us! Here are your baked bytes: 1686f128021d97491fbcc16c4807ad82ae782968412a8a6a73fb3fc05f507a2a93c0f899b9a1e5060f1fac207d850461 Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 2 Add your bagels: > 1111111 Thank you for baking with us! Here are your baked bytes: b59584584b933c35549d97f5a53e48051cb1d5c0b2d1bf1700a7443ebd49aa90d4dc5054965afba3dde5fa80ec3d7a427123e38a8266811053a13936adffb5f1
以下のようなイメージで、1文字ずつはみ出させながら、暗号結果が一致するものを探していく。
0123456789abcdef AAAAAAFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 0123456789abcdef AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF FFFFFFFFFPPPPPPP
import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14420)) flag = '' for i in range(42): try_pt = '#' * (47 - i) data = recvuntil(s, 'choice: ') print data + '2' s.sendall('2\n') data = recvuntil(s, '> ') print data + try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data ct2 = data[64:96] for code in range(32, 127): if i < 16: try_pt = '#' * (15 - i) + flag + chr(code) else: try_pt = flag[-15:] + chr(code) data = recvuntil(s, 'choice: ') print data + '1' s.sendall('1\n') data = recvuntil(s, '> ') print data + try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data print ct2 if data == ct2: flag += chr(code) break print flag
実行結果は以下の通り。
: Select: 1) Bake your own bagels! 2) Bake the flag! Your choice: 1 Add your bagels: > l5_3x7r4_cr15pY} Thank you for baking with us! Here are your baked bytes: 93c0f899b9a1e5060f1fac207d850461 93c0f899b9a1e5060f1fac207d850461 castorsCTF{I_L1k3_muh_b4G3l5_3x7r4_cr15pY}
castorsCTF{I_L1k3_muh_b4G3l5_3x7r4_cr15pY}
Magic School Bus (Crypto)
$ nc chals20.cybercastors.com 14421 __ __ ______ ______ __ ______ ______ ______ __ __ ______ ______ __ ______ __ __ ______ /\ "-./ \/\ __ \/\ ___\/\ \/\ ___\ /\ ___\/\ ___\/\ \_\ \/\ __ \/\ __ \/\ \ /\ == \/\ \/\ \/\ ___\ \ \ \-./\ \ \ __ \ \ \__ \ \ \ \ \____ \ \___ \ \ \___\ \ __ \ \ \/\ \ \ \/\ \ \ \____ \ \ __<\ \ \_\ \ \___ \ \ \_\ \ \_\ \_\ \_\ \_____\ \_\ \_____\ \/\_____\ \_____\ \_\ \_\ \_____\ \_____\ \_____\ \ \_____\ \_____\/\_____\ \/_/ \/_/\/_/\/_/\/_____/\/_/\/_____/ \/_____/\/_____/\/_/\/_/\/_____/\/_____/\/_____/ \/_____/\/_____/\/_____/ All right kids! The Magic Flag Bus is about to leave! Make sure to fill each row as you step in. Wait, what! All the kids got moved around. I *knew* I should've stayed home today! Nevermind... Load your own bus and let's leave. Select: 1) Load magic school bus 2) View magic flag bus Your choice: 2 Flag bus seating: SNESYT3AYN1CTISL7SRS31RAFSKV3C4I0SOCNTGER0COM5 Select: 1) Load magic school bus 2) View magic flag bus Your choice: 1 Who's riding the bus?: aaaaaaaa Bus seating: AAAAAAAA Select: 1) Load magic school bus 2) View magic flag bus Your choice: 1 Who's riding the bus?: abcdefgh Bus seating: GDACBHEF Select: 1) Load magic school bus 2) View magic flag bus Your choice: 1 Who's riding the bus?: ABCDEFGHIJKL Bus seating: GDLAICKBJHEF Select: 1) Load magic school bus 2) View magic flag bus Your choice:
どうやら並び替えをしているようだ。フラグを同じ長さの文字列を入れて、変更後の順序をあてはめて、戻していけばよい。
import socket import string def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chals20.cybercastors.com', 14421)) data = recvuntil(s, 'choice: ') print data + '2' s.sendall('2\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data flag_enc = data.split(' ')[-1] assert len(flag_enc) == 46 indexes = [] try_pt = string.uppercase[:23] + 'Z' * 23 data = recvuntil(s, 'choice: ') print data + '1' s.sendall('1\n') data = recvuntil(s, ': ') print data + try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data try_ct = data.split(' ')[-1] for i in range(23): index = try_ct.index(string.uppercase[i]) indexes.append(index) try_pt = 'Z' * 23 + string.uppercase[:23] data = recvuntil(s, 'choice: ') print data + '1' s.sendall('1\n') data = recvuntil(s, ': ') print data + try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data try_ct = data.split(' ')[-1] for i in range(23): index = try_ct.index(string.uppercase[i]) indexes.append(index) flag = '' for index in indexes: flag += flag_enc[index] print flag
実行結果は以下の通り。
: __ __ ______ ______ __ ______ ______ ______ __ __ ______ ______ __ ______ __ __ ______ /\ "-./ \/\ __ \/\ ___\/\ \/\ ___\ /\ ___\/\ ___\/\ \_\ \/\ __ \/\ __ \/\ \ /\ == \/\ \/\ \/\ ___\ \ \ \-./\ \ \ __ \ \ \__ \ \ \ \ \____ \ \___ \ \ \___\ \ __ \ \ \/\ \ \ \/\ \ \ \____ \ \ __<\ \ \_\ \ \___ \ \ \_\ \ \_\ \_\ \_\ \_____\ \_\ \_____\ \/\_____\ \_____\ \_\ \_\ \_____\ \_____\ \_____\ \ \_____\ \_____\/\_____\ \/_/ \/_/\/_/\/_/\/_____/\/_/\/_____/ \/_____/\/_____/\/_/\/_/\/_____/\/_____/\/_____/ \/_____/\/_____/\/_____/ All right kids! The Magic Flag Bus is about to leave! Make sure to fill each row as you step in. Wait, what! All the kids got moved around. I *knew* I should've stayed home today! Nevermind... Load your own bus and let's leave. Select: 1) Load magic school bus 2) View magic flag bus Your choice: 2 Flag bus seating: SNESYT3AYN1CTISL7SRS31RAFSKV3C4I0SOCNTGER0COM5 Select: 1) Load magic school bus 2) View magic flag bus Your choice: 1 Who's riding the bus?: ABCDEFGHIJKLMNOPQRSTUVWZZZZZZZZZZZZZZZZZZZZZZZ Bus seating: GOWZZDLTZZZAIQZZZCKSZZZBJRZZZHPZZZEMUZZZFNVZZZ Select: 1) Load magic school bus 2) View magic flag bus Your choice: 1 Who's riding the bus?: ZZZZZZZZZZZZZZZZZZZZZZZABCDEFGHIJKLMNOPQRSTUVW Bus seating: ZZZHPZZZEMUZZZBJRZZZDLTZZZCKSZZAIQZZZFNVZZZGOW CASTORSCTFR3C0N4ISSANCEISK3YTOS0LV1NGMYS73R1E5
CASTORSCTF{R3C0N4ISSANCE_IS_K3Y_TO_S0LV1NG_MYS73R1E5}
Jigglypuff's Song (Crypto)
高さ5ピクセルごとに別の画像が入っているので、切り出す。
from PIL import Image img = Image.open('chal.png').convert('RGB') w, h = img.size output_img = Image.new('RGB', (w, h / 5 + 1), (255, 255, 255)) for y in range(0, h, 5): for x in range(0, w): r, g, b = img.getpixel((x, y)) output_img.putpixel((x, y / 5), (r, g, b)) output_img.save('flag.png')
切り出した画像が粗いので、Stegsolveで開き、[Analyse]-[Data Extract]の画面からRGBのMSB(7)すべてにチェックを入れると、文章が出てくる。
We're no strangers to love You know the rules and so do I A full commitment's what I'm thinking of You wouldn't get this from any other guy I just wanna tell you how I'm feeling Gotta make you understand Never gonna give you up Never gonna let you down : Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you castorsCTF{r1ck_r0ll_w1ll_n3v3r_d3s3rt_y0uuuu}We're no strangers to love You know the rules and so do I A full commitment's what I'm thinking of You wouldn't get this from any other guy I just wanna tell you how I'm feeling : Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you
この文章中にフラグがあった。
castorsCTF{r1ck_r0ll_w1ll_n3v3r_d3s3rt_y0uuuu}
Warmup (Crypto)
chal.pngの直角三角形の画像から以下の法則のことがわかる。
・a**2 + b**2 = c**2 ・a * b / 2 = A
chal.txtにはこう書いてある。
・a = p + q ・b = p - q
このことから式を変換していく。
a * b = A * 2 p = (a + b) / 2 q = (a - b) / 2 (a + b)**2 = a**2 + b**2 + 2*a*b = c**2 + A*4 ->(2*p)**2 = c**2 + A*4 -> p = sqrt(c**2 + A*4) / 2 (a - b)**2 = a**2 + b**2 - 2*a*b = c**2 - A*4 ->(2*q)**2 = c**2 - A*4 -> q = sqrt(c**2 - A*4) / 2
sqrtの値が整数にならない。chal.txtの中のcはc**2の値として計算する。
import gmpy2 from Crypto.Util.number import long_to_bytes c = 41027546415588921135190519817388916847442693284567375482282571314638757544938653824671437300971782426302443281077457253827782026089649732942648771306702020 A = 1780602199528179468577178612383888301611753776788787799581979768613992169436352468580888042155360498830144442282937213247708372597613226926855391934953064 e = 0x10001 enc = 825531027337680366509171870396193970230179478931882005355846498785843598000659828635030935743236266080589740863128695174980645084614454653557872620514117 p = gmpy2.iroot(c + A * 4, 2)[0] / 2 assert pow(p, 2) * 4 == c + A * 4 q = gmpy2.iroot(c - A * 4, 2)[0] / 2 assert pow(q, 2) * 4 == c - A * 4 phi = (p - 1) * (q - 1) d = gmpy2.invert(e, phi) m = pow(enc, d, p * q) flag = long_to_bytes(m) print flag
castorsCTF{n0th1ng_l1k3_pr1m3_numb3r5_t0_w4rm_up_7h3_3ng1n3s}
Two Paths (Crypto)
PNGの後ろに2進数8桁の文字列が複数くっついている。デコードすると、URLになる。
https://go.aws/2zuCFCp
ここにアクセスすると、以下のURLになり、換字式暗号ようなページになる。
https://ctf-emoji-cipher.s3.amazonaws.com/decode_this.html
文字に置き換える。
ABCDEFGHIFGJBCK!_JL_MBH_AFC_ENFO_GPJK,_GPNC_MBH_PFQN_KBIQNO_GPN_AJRPNE!_SN_THKG_PBRN_MBH_LBHCO_F_UBEN_NLLJAJNCG_SFM_LBE_ONAJRPNEJCD_GPFC_DBJCD_BCN_VM_BCN_BE_NIKN_MBH_SBC'G_DNG_GPEBHDP_GPN_CNWG_RFEG_QNEM_XHJAYIM._UFMVN_GEM_VNJCD_F_IJGGIN_UBEN_IFZM! ALC_YJWRZVC_DWVEBYPPWP_UHK{OHJPHAQ_HPYY}KOTPLB_YH{WHPCRCU}O_ZECRCCNM_LR_AYCWEEN{BZI_AGDGEPCA}WK_{BLFLOTRBDG_BMRILABW}ZV_MPB{GPLWXWL_XN}WRZBNVHQ_ACMKWG_GZIS_ZOC{QDONTSB_TI_WNTKGUXF_HQ}TVBLQNAH_UYW{ERVLXKA_L}SJESTLTTC_GL{TTKDAT}PG_PDXEKOFRZY_KK{BRRBMXFD_CB}VECZZFGH_CWMIOYI{RRI_X}LPWZMUALF_KO{RRNTNDPS_UVTPAPZ_CN_ERFSMMCEZH_RW}ETYTLLMQ_JFIWMEW{QYN_KHJE_SNYGHB_GL{TTKDAT}PG_PDXEKOFRZY_KK{BRRBMX_FD_CB}VECZZFGH_CWMIOYI{RRI_MPB{GPLPB{GPLWXWL_XN}WRZBNVHQ_ACMKWG_GZIS_ZOC{QDONTSB_TI_WNTKGUXF_HQ}TVBLQNAH_UYW{ERVLXKA_L}SJESTLTTC_GL{TTKDAT}PG_PDXEKOFRZY_KK{BRRBMXFD_CB}VECZZFGHWXWL_XN}WRZBNVHQ_ACMKWGGZIS_ZOC{Q_DONTSB_TIWNTKGUXF_HQ}TVBLQNAH_UY_W{ERVLXKA_L}SJESTL_KHJE_SNYGHB_GL{TTKDAT}PG_PDXEKOFRZY_KK{BRRBMX_FD_CB}VECZDWVEBYPPWP_UHK{OHJPHAQ_HPYY}KOTPLB_YH{WHPCRCU}O_ZECRCCNM_LR_AYCWEEN{BZI_AGDGEPCA}WK_{BLFLOTRBDGZFGH_CWMIOYI{RRI_MPB{GPLWXWL_XN}WRZBNVHQ_ACMKWGGZISTTC_GL{TTKDAT}PG_PDXEKOFRZY_UVTPAPZW_CN_ERFSMMCEZH_RW}ETYTLLMQ_JFIW_MEW{QYN_KHJESNYGHB_VIQ{FAVSGPD_OFVNBZMNSB_VBCQWIFBJP_DF}HNFMUCDX_I{VCTCNACLC_N}W_VMYGDNMG_VBTKUTWLFU_DKUQ{VCGZSL_QVNFXGKVSV_DHRVBGYQV}E_AFKGBEKAGL{KFCABAPB_LIFD_XTZUIRD}_RL{GSXIJEXE_BYVEZZGSEN_UZUIDRUXYW_SOYHUNCVRY_QA}HLGNNXGZ_CGK{ZYKTJGO_JGOSOZ_H}H_AYP{MXPGMW_WAFCVENWLC_Z}ZYZZTYYLD_JSBRJ{SBNEA_OIFU}POLS_PS
_をスペースにしてquipqiupで復号すると、冒頭部分はこうなる。
CONGRATULATIONS! IF YOU CAN READ THIS, THEN YOU HAVE SOLVED THE CIPHER! WE JUST HOPE YOU FOUND A MORE EFFICIENT WAY FOR DECIPHERING THAN GOING ONE BY ONE OR ELSE YOU WON'T GET THROUGH THE NEXT PART VERY QUICKLY. MAYBE TRY BEING A LITTLE MORE LAZY!
あとは同じ対応ですべてを復号する。
with open('msg_enc.txt', 'r') as f: enc = f.read() C = 'FVAONLDPJTYIUCBRXEKGHQSWMZ' P = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' dec = '' for c in enc: if c in C: index = C.index(c) dec += P[index] else: dec += c print dec
実行結果は以下の通り。
CONGRATULATIONS!_IF_YOU_CAN_READ_THIS,_THEN_YOU_HAVE_SOLVED_THE_CIPHER!_WE_JUST_HOPE_YOU_FOUND_A_MORE_EFFICIENT_WAY_FOR_DECIPHERING_THAN_GOING_ONE_BY_ONE_OR_ELSE_YOU_WON'T_GET_THROUGH_THE_NEXT_PART_VERY_QUICKLY._MAYBE_TRY_BEING_A_LITTLE_MORE_LAZY! CFN_KIXPZBN_GXBROKHHXH_MUS{DUIHUCV_UHKK}SDJHFO_KU{XUHNPNM}D_ZRNPNNEY_FP_CKNXRRE{OZL_CTGTRHNC}XS_{OFAFDJPOGT_OYPLFCOX}ZB_YHO{THFXQXF_QE}XPZOEBUV_CNYSXT_TZLW_ZDN{VGDEJWO_JL_XEJSTMQA_UV}JBOFVECU_MKX{RPBFQSC_F}WIRWJFJJN_TF{JJSGCJ}HT_HGQRSDAPZK_SS{OPPOYQAG_NO}BRNZZATU_NXYLDKL{PPL_Q}FHXZYMCFA_SD{PPEJEGHW_MBJHCHZ_NE_RPAWYYNRZU_PX}RJKJFFYV_IALXYRX{VKE_SUIR_WEKTUO_TF{JJSGCJ}HT_HGQRSDAPZK_SS{OPPOYQ_AG_NO}BRNZZATU_NXYLDKL{PPL_YHO{THFHO{THFXQXF_QE}XPZOEBUV_CNYSXT_TZLW_ZDN{VGDEJWO_JL_XEJSTMQA_UV}JBOFVECU_MKX{RPBFQSC_F}WIRWJFJJN_TF{JJSGCJ}HT_HGQRSDAPZK_SS{OPPOYQAG_NO}BRNZZATUXQXF_QE}XPZOEBUV_CNYSXTTZLW_ZDN{V_GDEJWO_JLXEJSTMQA_UV}JBOFVECU_MK_X{RPBFQSC_F}WIRWJF_SUIR_WEKTUO_TF{JJSGCJ}HT_HGQRSDAPZK_SS{OPPOYQ_AG_NO}BRNZGXBROKHHXH_MUS{DUIHUCV_UHKK}SDJHFO_KU{XUHNPNM}D_ZRNPNNEY_FP_CKNXRRE{OZL_CTGTRHNC}XS_{OFAFDJPOGTZATU_NXYLDKL{PPL_YHO{THFXQXF_QE}XPZOEBUV_CNYSXTTZLWJJN_TF{JJSGCJ}HT_HGQRSDAPZK_MBJHCHZX_NE_RPAWYYNRZU_PX}RJKJFFYV_IALX_YRX{VKE_SUIRWEKTUO_BLV{ACBWTHG_DABEOZYEWO_BONVXLAOIH_GA}UEAYMNGQ_L{BNJNECNFN_E}X_BYKTGEYT_BOJSMJXFAM_GSMV{BNTZWF_VBEAQTSBWB_GUPBOTKVB}R_CASTORSCTF{SANCOCHO_FLAG_QJZMLPG}_PF{TWQLIRQR_OKBRZZTWRE_MZMLGPMQKX_WDKUMENBPK_VC}UFTEEQTZ_NTS{ZKSJITD_ITDWDZ_U}U_CKH{YQHTYX_XCANBREXFN_Z}ZKZZJKKFG_IWOPI{WOERC_DLAM}HDFW_HW
この中でCASTORSCTF{で始まる部分が1箇所ある。
castorsCTF{sancocho_flag_qjzmlpg}