この大会は2024/3/8 21:00(JST)~2024/3/10 9:00(JST)に開催されました。
今回もチームで参戦。結果は2729点で601チーム中34位でした。
自分で解けた問題をWriteupとして書いておきます。
input-validator (Reversing)
package defpackage; import java.util.Scanner; /* renamed from: input_validator reason: default package */ /* loaded from: input_validator.class */ public class input_validator { private static final int FLAG_LEN = 34; private static boolean validate(String str, String str2) { int[] iArr = new int[FLAG_LEN]; int[] iArr2 = {1102, 1067, 1032, 1562, 1612, 1257, 1562, 1067, 1012, 902, 882, 1397, 1472, 1312, 1442, 1582, 1067, 1263, 1363, 1413, 1379, 1311, 1187, 1285, 1217, 1313, 1297, 1431, 1137, 1273, 1161, 1339, 1267, 1427}; for (int i = 0; i < FLAG_LEN; i++) { iArr[i] = str.charAt(i) ^ str2.charAt(i); } for (int i2 = 0; i2 < FLAG_LEN; i2++) { iArr[i2] = iArr[i2] - str2.charAt(33 - i2); } int[] iArr3 = new int[FLAG_LEN]; for (int i3 = 0; i3 < 17; i3++) { iArr3[i3] = iArr[1 + (i3 * 2)] * 5; iArr3[i3 + 17] = iArr[i3 * 2] * 2; } for (int i4 = 0; i4 < FLAG_LEN; i4++) { int i5 = i4; iArr3[i5] = iArr3[i5] + 1337; } for (int i6 = 0; i6 < FLAG_LEN; i6++) { if (iArr3[i6] != iArr2[i6]) { return false; } } return true; } public static void main(String[] strArr) { Scanner scanner = new Scanner(System.in); System.out.print("Enter input: "); String nextLine = scanner.nextLine(); if (nextLine.length() == FLAG_LEN) { if (validate(new String(nextLine), "oF/M5BK_U<rqxCf8zWCPC(RK,/B'v3uARD")) { System.out.println("Correct"); return; } else { System.out.println("Wrong"); return; } } System.out.println("Input length does not match!"); } }
iArr2から逆算する。
#!/usr/bin/env python3 iArr2 = [1102, 1067, 1032, 1562, 1612, 1257, 1562, 1067, 1012, 902, 882, 1397, 1472, 1312, 1442, 1582, 1067, 1263, 1363, 1413, 1379, 1311, 1187, 1285, 1217, 1313, 1297, 1431, 1137, 1273, 1161, 1339, 1267, 1427] iArr3 = [i - 1337 for i in iArr2] iArr = [0] * 34 for i in range(17): iArr[1 + (i * 2)] = iArr3[i] // 5 iArr[i * 2] = iArr3[i + 17] // 2 str2 = "oF/M5BK_U<rqxCf8zWCPC(RK,/B'v3uARD" for i in range(34): iArr[i] = iArr[i] + ord(str2[33 - i]) flag = '' for i in range(34): flag += chr(iArr[i] ^ ord(str2[i])) print(flag)
pearl{w0w_r3v3r51ng_15_50_Ea5y_!!}
pcap-busterz-1 (Forensics)
TCP Streamを見てみると、以下のようになっている。
x=38, y=56, color=white x=73, y=33, color=white x=94, y=49, color=white x=12, y=82, color=black x=90, y=25, color=black x=55, y=46, color=white x=98, y=17, color=white : :
QRコードになりそうなので、このデータをエクスポートして、座標の位置に黒か白か該当する色で画像にしてみる。
#!/usr/bin/env python3 from PIL import Image with open('tcp_stream.txt', 'r') as f: lines = f.read().splitlines() img = Image.new('RGB', (100, 100), (255, 255, 255)) for line in lines: data = line.split(', ') x = int(data[0][2:]) y = int(data[1][2:]) color = data[2][6:] if color == 'black': img.putpixel((x, y), (0, 0, 0)) img.save('qr.png')
QRコードをデコードすると、フラグになった。
pearl{QR_rev0lution1ses_mod3rn_data_handl1ng}
WiFi broken (Forensics)
Wi-Fiのパスワードを答える問題。
$ aircrack-ng -w /usr/share/wordlists/rockyou.txt findme.cap Reading packets, please wait... Opening findme.cap Resetting EAPOL Handshake decoder state. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Resetting EAPOL Handshake decoder state. Read 30613 packets. # BSSID ESSID Encryption 1 1A:C7:E2:EC:22:DA shenoy_harry WPA (1 handshake) Choosing first network as target. Reading packets, please wait... Opening findme.cap Resetting EAPOL Handshake decoder state. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Inter-frame timeout period exceeded. Resetting EAPOL Handshake decoder state. Read 30613 packets. 1 potential targets Aircrack-ng 1.7 [00:11:40] 3822615/14344392 keys tested (5534.76 k/s) Time left: 31 minutes, 41 seconds 26.65% KEY FOUND! [ shenoydx ] Master Key : 65 BB E2 8B 45 5B B1 BE 5D 96 78 82 14 CC 4B A1 1A 15 BC 85 B0 BA 31 A7 9C 83 9E 9D 47 D2 3E 32 Transient Key : 9D D4 35 8B A2 DC 7D 85 AB DF 04 77 F8 B9 7B 8F FE 44 CC 98 6C 9C 83 98 80 D5 EC 72 98 A7 FC D7 23 9B 87 C8 CD BF CF 62 5A 97 41 DB 4B 8C AE 62 C2 B0 1F D4 D8 57 DF 80 08 50 9C 98 9D 5A 2F 8F EAPOL HMAC : 00 65 7D DC 8B 14 31 78 37 A8 10 86 BC 5B 70 08
pearl{shenoydx}
3 spies (Crypto)
RSA暗号でeは3、異なるnで暗号化されているものが3つある。Hastad's Broadcast Attackで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * from sympy.ntheory.modular import crt from gmpy2 import iroot with open('encrypted-messages.txt', 'r') as f: params = f.read().splitlines() ns = [] cs = [] for i in range(3): n = int(params[i*4].split(': ')[1]) e = int(params[i*4+1].split(': ')[1]) c = int(params[i*4+2].split(': ')[1]) ns.append(n) cs.append(c) assert e == 3 me, _ = crt(ns, cs) m, success = iroot(me, e) assert success flag = long_to_bytes(m).decode() print(flag)
復号結果は以下の通り。
This is your destination: "https://pastes.io/1yjswxlvl2"
https://pastes.io/1yjswxlvl2にアクセスすると、JPEGデータらしきもののbase64文字列が見つかる。paste_data.txtにその内容を保存し、デコードしてJPEGとして保存する。
#!/usr/bin/env python3 from base64 import * with open('paste_data.txt', 'r') as f: data = f.read().splitlines()[1] with open('flag.jpg', 'wb') as f: f.write(b64decode(data))
base64デコードしたJPEG画像データにフラグが書いてあった。
pearl{g00d_j0b_bu7_7h15_15_4_b4by_0n3}
Security++ (Crypto)
奇数ブロックと偶数ブロックで鍵が異なるブロック暗号であるが、1文字ずつはみ出させながらフラグを割り出すことができる。
$ nc dyn.ctf.pearlctf.in 30015 Enter plaintext: a bbUbmwzdnlfHaJFmoQH+YEr39/kP/1FPlhdE3pFmw+UpiOlXBKYarsz0ajS1c48w Enter plaintext: aa 6P9PUomGlQnjgYzzd5SlEMXQUcazc10V5NWiRoi/vVeMVNaDzulNDQkLp3eWqaXF Enter plaintext: aaa 8ERCLdnYRHZLAqRc9xPWCDkuKmZyzdcX1vGSZriMF43DE1lxA/5q6iT0lyoglr3e Enter plaintext: aaaa U647U9Ym4KTGMPc6YXDFCcdtc7PO8pOICYNEx8TaomQHkdR950+sKFoUrLJNiomU Enter plaintext: aaaaa yFUb7bkWXF3m0NEhCTj8csRJjdytUuwqsC3xI7aais2uNM7sWcnb2x0+QBxp+e46 Enter plaintext: aaaaaa p4Jg8l1ke1FGiVJHtSmHfKR2RWKDzd4mYA9kevVpin5rCSVq+2Mw42AUnONqhzYB Enter plaintext: aaaaaaa +tBLra1ihl1VNrdxhZ6JuGa518DeW+5lfsgAya+t3bUw2LGQTPzAl7UdR1Ou3vF3 Enter plaintext: aaaaaaaa hx8gyerKjBifhdwbye7a6jqmlesAXeXrll7AhGL42Y+VTM2bBwYM/8pQ+Nt9zrl5 Enter plaintext: aaaaaaaaa 2K1s69xF2nT758Ybb0NJ3GlOe9GH5ZCUTDjpb79xRygEG1gbnANZs2/tx4TAQiyy Enter plaintext: aaaaaaaaaa +Y/R7MlmKX/QmiBw8IgojjEjG0o5y73kI5mBA4ovKBRDYHnEEcqaQ/SVHIppmDFZ Enter plaintext: aaaaaaaaaaa j0yD6oWIzF31ZitRad0+fAUD/C7PAxzRFNFQOfXbrDV7K6mwLMTfj1sWAuAqCXWm Enter plaintext: aaaaaaaaaaaa 05d8wscinze4CF9/VFDSBx6YpDIqkXPl2DGu2y7v73eab//y41PVnv+T/DHg8HT+ Enter plaintext: aaaaaaaaaaaaa EDg8B9aMsB133AlPp//Hr6O6qO2SdwfCULpbIPIYctHyggcR6oKI1iBGNPBxRHAr Enter plaintext: aaaaaaaaaaaaaa S0zoMgUA1j6uA50qbNUnV8IrnKp13L543qTiWbPG526gdkuADjFykjxXtqU8MMqi Enter plaintext: aaaaaaaaaaaaaaa 2MuxuSRrdbWU/+zuxp0zKicq7QBvcqi/iG5iuT15ur8J3Ze/oJKiwhQtHtwZX9cz Enter plaintext: aaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc7l5hqdodjwWDK6SukcxSU5dwPAy5DPzJHqruBG/SvTi Enter plaintext: aaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drcy7m1KXkejN5nSH4CHu2ivS/3xC8Ey6cWf50AbW/URurnrM5ONsJiSVErKhfCL39tQ==
以下のような平文の暗号化イメージになり、フラグの長さは32バイトであることがわかる。
0123456789abcdef aaaaaaaaaaaaaaaa FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
0123456789abcdef aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaX aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFF.
このような形で第2ブロックと第4ブロックを比較していき、同じ暗号になる文字を探す。
#!/usr/bin/env python3 import socket from base64 import * 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(('dyn.ctf.pearlctf.in', 30015)) flag = '' for i in range(32): for code in range(32, 127): pt = 'a' * (31 - i) + flag + chr(code) + 'a' * (31 - i) data = recvuntil(s, b': ') print(data + pt) s.sendall(pt.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) ct = b64decode(data) data = recvuntil(s, b'\n').rstrip() print(data) if ct[16:32] == ct[48:64]: flag += chr(code) break print(flag)
実行結果は以下の通り。
Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc7L2wHkPmsJYgPtMTF6gYwTRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc523AG6GCjSIiJNMqCCd5p7RDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc5eOdAqaVjbJM1lw5S6pnDzRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc58CopH3CKhXjPK4UedJP5DRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0QzSbdnkOH1wvKHl55drc2LT+k3qtobqCrHbo3/3Q2PRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR : : Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n{ /XXtfavLXsb1cNtDhA0vmUaHRhbeQWRNbnJWmBJ1Xtj9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg== Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n| /XXtfavLXsb1cNtDhA0vmeiooNDVB0GMDTZ/YY2fjeL9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg== Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n} /XXtfavLXsb1cNtDhA0vmcGEKBjSoIcWZFvPaDxIjib9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg== pearl{n0t_sn34ky_A3S_3ncrypt10n}
pearl{n0t_sn34ky_A3S_3ncrypt10n}