この大会は2021/5/8 7:00(JST)~2021/5/9 7:00(JST)に開催されました。
今回もチームで参戦。結果は5980点で595チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。
DawgCTF Discord (Misc 5)
Discordに入り、#flagチャネルを見ると、フラグが書いてあった。
DawgCTF{3nj0y_th3_c0mp3t1t10n!}
Third Eye (Audio/Radio 75)
Sonic Visualiserでmp3を開き、スペクトログラムを見る。
44 61 77 67 43 54 46 7b 73 79 6e 33 73 74 68 33 73 31 61 63 73 7d
この16進数の羅列をASCIIコードとしてデコードする。
>>> codes = '44 61 77 67 43 54 46 7b 73 79 6e 33 73 74 68 33 73 31 61 63 73 7d'.split(' ') >>> ''.join([chr(int(code, 16)) for code in codes]) 'DawgCTF{syn3sth3s1acs}'
DawgCTF{syn3sth3s1acs}
Tag, You're It! (Audio/Radio 100)
Mp3tagで開いてみると、コメントにこう書いてある。
Ḑ̶͙̀á̴̡̳͈̏ẃ̸͇͚g̸̭̣̱͂C̵̹̆̂Ṱ̴̡͍̀F̴̻͚͐̿̄{̴̟̃̀̐w̵̺̻͒̔͋h̴̭͛0̵͍̤͒͆͝_̷̟̈́͘̚d̶͙͕͜͝0̶͕͚͎̏̚w̸̦͙̃̽ǹ̷͙͚l̶̛̜̈́0̴̧̱͓͝a̶̘̮͚̿̈́ď̷̡̬́ŝ̴̢͔̌͝ͅ_̶̬̺͛̎̈́ͅm̵̳͗ű̶͎̊s̷̰̀̄͆1̸͕͖̈́c̶͔͆_̷̢̧̔̉̚â̵̙̔ǹ̵̖̦͈̇̿ỵ̴̬̓̔m̸̛͉̩̑0̸̮͓̏̊̀r̴͇͕̈́̄̉3̶̙̭͎͋̚͝?̴͔̟̩͊͛}̴̤̲͂͜
読みにくいが、サクラエディタで見て、文字を抜き出す。
DawgCTF{wh0_d0wnl0ads_mus1c_anym0r3?}
Just A Comment (Fwn (Forensics/Web/Network) 50)
pcapngのコメントを見ると、フラグが書いてあった。
DawgCTF{w3 h34r7 0ur 1r4d 734m}
These Ladies Paved Your Way (Fwn (Forensics/Web/Network) 150)
$ exiftool radia_perlman.jpg ExifTool Version Number : 10.80 File Name : radia_perlman.jpg Directory : . File Size : 10 kB File Modification Date/Time : 2021:04:24 08:11:46+09:00 File Access Date/Time : 2021:05:08 09:10:37+09:00 File Inode Change Date/Time : 2021:04:24 08:11:46+09:00 File Permissions : rwxrwxrwx File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg JFIF Version : 1.01 Resolution Unit : None X Resolution : 1 Y Resolution : 1 Current IPTC Digest : 8d370a1f7871e76616c0f06987707b84 Credit : U3Bhbm5pbmdUcmVlVmlnCg== Application Record Version : 4 Keywords : VpwtPBS{r0m5 0W t4x3IB5} Comment : CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 80. Image Width : 227 Image Height : 244 Encoding Process : Baseline DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2) Image Size : 227x244 Megapixels : 0.055 $ echo U3Bhbm5pbmdUcmVlVmlnCg== | base64 -d SpanningTreeVig
Vigenere暗号と推測し、https://www.dcode.fr/vigenere-cipherで復号する。
暗号:VpwtPBS{r0m5 0W t4x3IB5} 鍵 :SpanningTreeVig
DawgCTF{l0t5 0F p4t3NT5}
BBomb - Phase 1 (Binary Bomb 25)
Ghidraでデコンパイルする。
undefined4 phase1(undefined8 param_1) { char *__s; size_t sVar1; size_t sVar2; undefined4 local_34; int local_30; puts( "\nStarting off easy... reversing (things) is fun! (Wrap all flags in DawgCTF{} when submitting to the scoreboard)" ); local_34 = 1; __s = (char *)calloc(0x29,1); getInput(1,param_1,&DAT_001028d1,__s); local_30 = 0; sVar1 = strlen(__s); while( true ) { sVar2 = strlen("Gn1r7s_3h7_Gn15Rev3R"); if (sVar2 <= (ulong)(long)local_30) break; sVar2 = strlen(__s); if (sVar2 <= (ulong)(long)local_30) break; if ("Gn1r7s_3h7_Gn15Rev3R"[local_30] != __s[(long)((int)sVar1 - local_30) + -1]) { local_34 = 0; } local_30 = local_30 + 1; } sVar1 = strlen("Gn1r7s_3h7_Gn15Rev3R"); if ((long)local_30 != sVar1) { local_34 = 0; } free(__s); return local_34; }
文字列を逆にして比較しているので、逆にして比較する文字列を割り出す。
>>> "Gn1r7s_3h7_Gn15Rev3R"[::-1] 'R3veR51nG_7h3_s7r1nG'
DawgCTF{R3veR51nG_7h3_s7r1nG}
BBomb - Phase 2 (Binary Bomb 50)
Ghidraでデコンパイルする。
undefined4 phase2(undefined8 param_1) { char *__s; size_t sVar1; undefined4 local_30; int local_2c; puts("\nCan you help me find my lost key so I can read my string?"); local_30 = 1; __s = (char *)calloc(0x29,1); getInput(2,param_1,&DAT_001028d1,__s); local_2c = 0; while( true ) { sVar1 = strlen("Dk52m6WZw@s6w0dIZh@2m5a"); if (sVar1 <= (ulong)(long)local_2c) break; sVar1 = strlen(__s); if (sVar1 <= (ulong)(long)local_2c) break; if ("Dk52m6WZw@s6w0dIZh@2m5a"[local_2c] != (byte)(__s[local_2c] ^ 5U)) { local_30 = 0; } local_2c = local_2c + 1; } sVar1 = strlen("Dk52m6WZw@s6w0dIZh@2m5a"); if ((long)local_2c != sVar1) { local_30 = 0; } free(__s); return local_30; }
"Dk52m6WZw@s6w0dIZh@2m5a"の各文字と、入力文字と5をXORしたものを比較している。
>>> ''.join([chr(ord(c) ^ 5) for c in "Dk52m6WZw@s6w0dIZh@2m5a"]) 'An07h3R_rEv3r5aL_mE7h0d'
DawgCTF{An07h3R_rEv3r5aL_mE7h0d}
BBomb - Phase 3 (Binary Bomb 75)
Ghidraでデコンパイルする。
bool phase3(undefined8 param_1) { int iVar1; char *__s1; char *pcVar2; char *local_20; puts("\nReflections? Rotations? Translations? This is starting to sound like geometry..."); __s1 = (char *)calloc(0x29,1); getInput(3,param_1,&DAT_001028d1,__s1); local_20 = __s1; while (*local_20 != '\0') { pcVar2 = (char *)func3_1(local_20); *local_20 = *pcVar2; pcVar2 = (char *)func3_2(local_20); *local_20 = *pcVar2; local_20 = local_20 + 1; } iVar1 = strcmp(__s1,"\"_9~Jb0!=A`G!06qfc8\'_20uf6`2%7"); free(__s1); return iVar1 == 0; } char * func3_1(char *param_1) { char cVar1; if (('@' < *param_1) && (*param_1 < '[')) { *param_1 = *param_1 + -0xd; if (*param_1 < 'A') { cVar1 = '\x1a'; } else { cVar1 = '\0'; } *param_1 = cVar1 + *param_1; } if (('`' < *param_1) && (*param_1 < '{')) { *param_1 = *param_1 + -0xd; if (*param_1 < 'a') { cVar1 = '\x1a'; } else { cVar1 = '\0'; } *param_1 = cVar1 + *param_1; } return param_1; } char * func3_2(char *param_1) { char cVar1; if ((' ' < *param_1) && (*param_1 != '\x7f')) { *param_1 = *param_1 + -0x2f; if (*param_1 < '!') { cVar1 = '^'; } else { cVar1 = '\0'; } *param_1 = cVar1 + *param_1; } return param_1; }
func3_1はrot13、func3_2はrot47。rot13して、rot47したものを"\"_9~Jb0!=A`G!06qfc8\'_20uf6`2%7"と比較しているので、元に戻す。
def rot47(s): d = '' for c in s: code = ord(c) - 47 if code <= 32: code += 94 d += chr(code) return d enc = "\"_9~Jb0!=A`G!06qfc8\'_20uf6`2%7" flag = rot47(enc).decode('rot13') print flag
実行結果は以下の通り。
D0uBl3_Cyc1iC_rO74tI0n_S7r1nGs
DawgCTF{D0uBl3_Cyc1iC_rO74tI0n_S7r1nGs}
BBomb - Phase 4 (Binary Bomb 150)
Ghidraでデコンパイルする。
undefined4 phase4(undefined8 param_1) { long lVar1; int iVar2; void *__ptr; long lVar3; long in_FS_OFFSET; undefined8 uVar4; undefined4 local_5c; int local_58; long local_48 [5]; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28); puts("\nThis is the phase you have been waiting for... one may say it\'s the golden stage!"); puts( "Let\'s switch things up! Numerical inputs map to line numbers in rockyou.txt, and each word is separated by a \'_\' (if the phase\'s solution is 4 5, the flag would be DawgCTF{password_iloveyou})" ); local_5c = 1; local_48[0] = 1; local_48[1] = 0x7b; local_48[2] = 0x3b18; local_48[3] = 0x1c640d; iVar2 = func4(10); uVar4 = 0x10157f; __ptr = calloc(4,4); getInput(4,param_1,"%d%d%d%d",__ptr,(long)__ptr + 4,(long)__ptr + 8,(long)__ptr + 0xc,uVar4); local_58 = 0; while (local_58 < 4) { lVar1 = local_48[local_58]; lVar3 = func4(*(undefined4 *)((long)__ptr + (long)local_58 * 4)); if (lVar1 * iVar2 - lVar3 != 0) { local_5c = 0; } local_58 = local_58 + 1; } free(__ptr); if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return local_5c; } long func4(int param_1) { long lVar1; long lVar2; if (param_1 < 1) { lVar1 = 0; } else { if (param_1 == 1) { lVar1 = 1; } else { lVar2 = func4(param_1 + -1); lVar1 = func4(param_1 + -2); lVar1 = lVar1 + lVar2; } } return lVar1; }
func4はフィボナッチ数列の関数。lVar1 * iVar2 - lVar3 = 0を満たすようなlVar3を求め、それがフィボナッチ数列の何番目かを割り出せばよい。
def fibonacci(n): return round((((1 + 5 ** 0.5) / 2) ** n - ((1 - 5 ** 0.5) / 2) ** n) / 5 ** 0.5) with open('rockyou.txt', 'r') as f: lines = f.readlines() local_48 = [1, 0x7b, 0x3b18, 0x1c640d] iVar2 = fibonacci(10) ns = [] for i in range(100): ns.append(fibonacci(i)) flag = 'DawgCTF{' for i in range(4): lVar1 = local_48[i] num = lVar1 * iVar2 index = ns.index(num) print index, flag += lines[index - 1].rstrip() flag += '_' print flag = flag[:-1] + '}' print flag
実行結果は以下の通り。
10 20 30 40 DawgCTF{abc123_qwerty_anthony_123123}
DawgCTF{abc123_qwerty_anthony_123123}
BBomb - Phase 6 (Binary Bomb 175)
Ghidraでデコンパイルする。
undefined4 phase6(undefined8 param_1) { char *__s; size_t sVar1; long in_FS_OFFSET; undefined4 local_48; int local_44; char local_38 [4]; undefined local_34; undefined local_33; undefined local_32; undefined local_31; undefined local_30; undefined local_2f; undefined local_2e; undefined local_2d; undefined local_2c; undefined local_2b; undefined local_2a; undefined local_29; undefined local_28; undefined local_27; undefined local_26; undefined local_25; undefined local_24; undefined local_23; undefined local_22; undefined local_21; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28); puts("\nOh no... I lost the key to my string again :("); local_48 = 1; local_38[0] = '@'; local_38[1] = 0x77; local_38[2] = 0x23; local_38[3] = 0x91; local_34 = 0xb0; local_33 = 0x72; local_32 = 0x82; local_31 = 0x77; local_30 = 99; local_2f = 0x31; local_2e = 0xa2; local_2d = 0x72; local_2c = 0x21; local_2b = 0xf2; local_2a = 0x67; local_29 = 0x82; local_28 = 0x91; local_27 = 0x77; local_26 = 0x26; local_25 = 0x91; local_24 = 0; local_23 = 0x33; local_22 = 0x82; local_21 = 0xc4; __s = (char *)calloc(0x29,1); getInput(6,param_1,&DAT_001028d1); local_44 = 0; while( true ) { sVar1 = strlen(local_38); if (sVar1 <= (ulong)(long)local_44) break; sVar1 = strlen(__s); if (sVar1 <= (ulong)(long)local_44) break; __s[local_44] = (byte)((int)__s[local_44] << 4) | (byte)__s[local_44] >> 4; __s[local_44] = __s[local_44] ^ 100; if (__s[local_44] != local_38[local_44]) { local_48 = 0; } local_44 = local_44 + 1; } sVar1 = strlen(local_38); if ((long)local_44 != sVar1) { local_48 = 0; } free(__s); if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return local_48; }
各バイトで前半4ビットと後半4ビットを入れ替え、100とのXORした結果を比較している。このことから逆算してフラグを求める。
codes = [ord('@'), 0x77, 0x23, 0x91, 0xb0, 0x72, 0x82, 0x77, 99, 0x31, 0xa2, 0x72, 0x21, 0xf2, 0x67, 0x82, 0x91, 0x77, 0x26, 0x91, 0, 0x33, 0x82, 0xc4] flag = '' for code in codes: code = code ^ 100 code = (code >> 4) | ((code << 4) & 0xff) flag += chr(code) print flag
実行結果は以下の通り。
B1t_Man1pUlaTi0n_1$_Fun
DawgCTF{B1t_Man1pUlaTi0n_1$_Fun}
BBomb - Phase 7 (Binary Bomb 250)
Ghidraでデコンパイルする。
uint phase7(undefined8 param_1) { undefined uVar1; int iVar2; int iVar3; uint uVar4; undefined8 *__ptr; void *pvVar5; uint local_38; int local_34; int local_30; int local_2c; int local_28; int local_24; puts("\nAt least we can say our code is resuable"); local_38 = 1; __ptr = (undefined8 *)malloc(0x18); local_34 = 0; while (local_34 < 3) { pvVar5 = calloc(0x29,1); __ptr[local_34] = pvVar5; local_34 = local_34 + 1; } getInput(7,param_1,"%s%s%s",*__ptr,__ptr[1],__ptr[2]); local_30 = 0; local_2c = 0; do { if (2 < local_2c) { if (local_30 != 0x1fd) { local_38 = 0; } local_24 = 0; while (local_24 < 3) { free((void *)__ptr[local_24]); local_24 = local_24 + 1; } free(__ptr); return local_38; } iVar2 = atoi((char *)__ptr[local_2c]); local_30 = local_30 + iVar2; if (0 < local_2c) { iVar2 = atoi((char *)__ptr[(long)local_2c + -1]); iVar3 = atoi((char *)__ptr[local_2c]); if (iVar3 < iVar2) { local_38 = 0; } } local_28 = 0; while (local_28 < 3) { iVar2 = atoi((char *)__ptr[local_2c]); if (iVar2 < 100) { local_38 = 0; break; } iVar2 = atoi((char *)__ptr[local_2c]); uVar4 = func5(iVar2); local_38 = local_38 & uVar4; uVar1 = *(undefined *)(__ptr[local_2c] + 2); *(undefined *)(__ptr[local_2c] + 2) = *(undefined *)(__ptr[local_2c] + 1); *(undefined *)(__ptr[local_2c] + 1) = *(undefined *)__ptr[local_2c]; *(undefined *)__ptr[local_2c] = uVar1; local_28 = local_28 + 1; } local_2c = local_2c + 1; } while( true ); }
func5は素数判定。3つの数値はすべて素数で、100以上。3つの数値の合計は0x1fd。3つの数値は昇順になっている。3桁の数値をシフトしても(137の場合、713, 371)、100以上、素数の条件を満たす。以上の条件を満たす数値を探す。
from Crypto.Util.number import * import itertools with open('rockyou.txt', 'r') as f: lines = f.readlines() target = 0x1fd ps = [] for p in range(100, 300): if isPrime(p): s = str(p) p1 = int(s[2] + s[:2]) p2 = int(s[1:] + s[0]) if p1 > 100 and p2 > 100 and isPrime(p1) and isPrime(p2): ps.append(p) flag = 'DawgCTF{' for p in itertools.combinations(ps, 3): if sum(p) == target: print ' '.join(map(str, p)) for i in range(len(p)): flag += lines[p[i] - 1].rstrip() flag += '_' break flag = flag[:-1] + '}' print flag
実行結果は以下の通り。
113 197 199 DawgCTF{iloveme_123abc_batman}
DawgCTF{iloveme_123abc_batman}
It's Not RSA! (Crypto 100)
Enigma暗号。CyberChefで復号する。
DAWGC TFSPI NNING ANDRO TATIN GROTO RS
DawgCTF{spinningandrotatingrotors}
Really Secure Algorithm (Crypto 150)
RSA暗号だが、eが非常に大きいので、Wiener attackで復号する。
#!/usr/bin/sage from Crypto.Util.number import * def wiener(e, n): m = 12345 c = pow(m, e, n) q0 = 1 list1 = continued_fraction(Integer(e)/Integer(n)) conv = list1.convergents() for i in conv: k = i.numerator() q1 = i.denominator() for r in range(30): for s in range(30): d = r*q1 + s*q0 m1 = pow(c, d, n) if m1 == m: return d q0 = q1 return None with open('reallysecure.txt', 'r') as f: lines = f.readlines() n = int(lines[0].strip().split(': ')[1]) e = int(lines[1].strip().split(': ')[1]) c = int(lines[2].strip().split(': ')[1]) d = wiener(e, n) m = pow(c, d, n) flag = long_to_bytes(m) print flag
DawgCTF{sm@ll_d_b1g_dr3am5}
The Obligatory RSA Challenge (Crypto 200)
nをfactordbで素因数分解し、phiを計算する。
n = (21816257788879800226266741950501282709401872894176288619472731956291218914324742537604641219560786978413613510633536886641581153942571579359519401327796021367732695476711467566761398025402445133259848384123905962932802004021079944067083532491720877926448099933753336517689984808846750048960375488528766110009254176926887611598941876012437215971816681184483796662759984833863168641346915636162467824574775331116852844756225674938392321848711476249893809700776552828990831593983374320915711192051109410295925205263499219444742867868898381959251178728127024835656647566724333855154762699836449704050690295585931350731821)**2 p = 21816257788879800226266741950501282709401872894176288619472731956291218914324742537604641219560786978413613510633536886641581153942571579359519401327796021367732695476711467566761398025402445133259848384123905962932802004021079944067083532491720877926448099933753336517689984808846750048960375488528766110009254176926887611598941876012437215971816681184483796662759984833863168641346915636162467824574775331116852844756225674938392321848711476249893809700776552828990831593983374320915711192051109410295925205263499219444742867868898381959251178728127024835656647566724333855154762699836449704050690295585931350731821 phi = p * (p - 1)
あとはそのまま復号する。
from Crypto.Util.number import * with open('rsa.txt', 'r') as f: lines = f.readlines() n = int(lines[0].strip().split(' = ')[1]) e = int(lines[1].strip().split(' = ')[1]) c = int(lines[2].strip().split(' = ')[1]) p = 21816257788879800226266741950501282709401872894176288619472731956291218914324742537604641219560786978413613510633536886641581153942571579359519401327796021367732695476711467566761398025402445133259848384123905962932802004021079944067083532491720877926448099933753336517689984808846750048960375488528766110009254176926887611598941876012437215971816681184483796662759984833863168641346915636162467824574775331116852844756225674938392321848711476249893809700776552828990831593983374320915711192051109410295925205263499219444742867868898381959251178728127024835656647566724333855154762699836449704050690295585931350731821 assert n == p**2 phi = p * (p - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m) print flag
DawgCTF{wh0_n33ds_Q_@nyw@y}
TrashChain (Crypto 250)
$ nc umbccd.io 3100 Welcome to TrashChain! In this challenge, you will enter two sequences of integers which are used to compute two hashes. If the two hashes match, you get the flag! Restrictions: - Integers must be greater than 1. - Chain 2 must be at least 3 integers longer than chain 1 - All integers in chain 1 must be less than the smallest element in chain 2 Type "done" when you are finished inputting numbers for each chain. Provide inputs for chain 1. > 2 > done Provide inputs for chain 2. > 3 > 4 > 5 > 6 > done Hash for chain 1: e15e50ea527143a8da8ca4602316de76 Hash for chain 2: 103fe8e38a2cc887b8628c4ef649b81e Sorry, hashes don't match
chainsに2つの数列を指定する。その際、chains[1]はchains[0]より要素数を3つ以上多く指定する。また、chains[0]の最大値はchains[1]の最小値より小さい。この条件で、ハッシュ値が同じものが指定できたら、フラグが表示される。
[a0, a1, a2]の場合のハッシュ h0 = H(a0, 1, 1) = 1 * pow(a0 + 1, B, A) % A h1 = H(a1, h0, 2) = h0 * pow(a1 + 2, B, A) % A h2 = H(a2, h1, 3) = h1 * pow(a2 + 3, B, A) % A h = (pow(a0 + 1, B, A) * pow(a1 + 2, B, A) * pow(a2 + 3, B, A)) % A
pow(A-1, B, A) = A-1になることを使うと、以下の配列の場合、ハッシュが同じになるはず。
[(A-1)-1] [(A*2-1)-1, (A*3-1)-2, (A*4-1)-3, (A*5-1)-4, (A*6-1)-5]
import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) A = 340282366920938460843936948965011886881 B = 127605873542257115442148455720344860097 chains = [[], []] chains[0].append((A-1)-1) chains[1].append((A*2-1)-1) chains[1].append((A*3-1)-2) chains[1].append((A*4-1)-3) chains[1].append((A*5-1)-4) chains[1].append((A*6-1)-5) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('umbccd.io', 3100)) for i in range(len(chains[0])): data = recvuntil(s, '> ') print data + str(chains[0][i]) s.sendall(str(chains[0][i]) + '\n') data = recvuntil(s, '> ') print data + 'done' s.sendall('done\n') for i in range(len(chains[1])): data = recvuntil(s, '> ') print data + str(chains[1][i]) s.sendall(str(chains[1][i]) + '\n') data = recvuntil(s, '> ') print data + 'done' s.sendall('done\n') for _ in range(3): data = recvuntil(s, '\n').rstrip() print data
実行結果は以下の通り。
Welcome to TrashChain! In this challenge, you will enter two sequences of integers which are used to compute two hashes. If the two hashes match, you get the flag! Restrictions: - Integers must be greater than 1. - Chain 2 must be at least 3 integers longer than chain 1 - All integers in chain 1 must be less than the smallest element in chain 2 Type "done" when you are finished inputting numbers for each chain. Provide inputs for chain 1. > 340282366920938460843936948965011886879 > done Provide inputs for chain 2. > 680564733841876921687873897930023773760 > 1020847100762815382531810846895035660640 > 1361129467683753843375747795860047547520 > 1701411834604692304219684744825059434400 > 2041694201525630765063621693790071321280 > done Hash for chain 1: ffffffffffffff720000000000001320 Hash for chain 2: ffffffffffffff720000000000001320 Correct! Here's your flag: DawgCTF{We1rd_RSA_2nd_Pre1m4g3_th1ng}
DawgCTF{We1rd_RSA_2nd_Pre1m4g3_th1ng}
What the Flip?! (Crypto 300)
$ nc umbccd.io 3000 ######################################################################## # Welcome # # All connections are monitored and recorded # # Disconnect IMMEDIATELY if you are not an authorized user! # ######################################################################## username: a a's password: b Leaked ciphertext: 72a98b45a20ee804960da219b43a3ab1193747172a0f5d2dd988eee155a3bb00 enter ciphertext: 0123456789abcdef0123456789abcdef Padding is incorrect.
サーバの処理概要は以下の通り。
・key: ランダム16バイト文字列 ・iv: ランダム16バイト文字列 ・user: 入力 ・passwd: 入力 ・msg = 'logged_username=<user>&password=<passwd>' msgに'admin&password=goBigDawgs123'が含まれるのはNG ・msgの暗号化を表示 ・enc_msg: 入力(16進数) ・enc_msgを復号 →チェックに合格していると、フラグが表示される。 →paddingが妥当でない場合は、そのメッセージが表示される。 ■暗号化 ・PKCS7方式でパディング ・AES暗号化(16進数) ■復号 ・16進数デコード後、AES復号 ・unpadした後に、'admin&password=goBigDawgs123'が含まれていたら、1を返す。 それ以外の場合は0を返す。
1文字だけ変え、前の暗号データを変更することによって、'admin&password=goBigDawgs123'が含まれるようにする。
0123456789abcdef logged_username= p0 ^ iv --AES暗号--> c0 admim&password=g p1 ^ c0 --AES暗号--> c1 oBigDawgs123PPPP p2 ^ c1 --AES暗号--> c2 p1_new = 'admin&password=g'とする。 p1 ^ c0 = p1_new ^ c0_new
これが成り立つようにすれば、最初のブロックの平文はくずれるが、'admin&password=goBigDawgs123'が含まれるようにできる。
import socket from Crypto.Util.strxor import strxor 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(('umbccd.io', 3000)) user = 'admim' data = recvuntil(s, ': ') print data + user s.sendall(user + '\n') passwd = 'goBigDawgs123' data = recvuntil(s, ': ') print data + passwd s.sendall(passwd + '\n') data = recvuntil(s, '\n').rstrip() print data ct = data.split(': ')[1].decode('hex') pt1 = user + '&password=g' pt1_new = 'admin&password=g' ct0 = ct[:16] ct12 = ct[16:] ct0_new = strxor(strxor(pt1, ct0), pt1_new) ct_new = (ct0_new + ct12).encode('hex') data = recvuntil(s, ': ') print data + ct_new s.sendall(ct_new + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '}') print data
実行結果は以下の通り。
######################################################################## # Welcome # # All connections are monitored and recorded # # Disconnect IMMEDIATELY if you are not an authorized user! # ######################################################################## username: admim admim's password: goBigDawgs123 Leaked ciphertext: 72a98b45a20ee804960da219b43a3ab1e4ba207bb23ca5a4690179ae874c62c7f31070ad27e67701a69fedfebe4c2a63 enter ciphertext: 72a98b45a10ee804960da219b43a3ab1e4ba207bb23ca5a4690179ae874c62c7f31070ad27e67701a69fedfebe4c2a63 Logged in successfully! Your flag is: DawgCTF{F1ip4J0y}
DawgCTF{F1ip4J0y}