Pearl CTF Writeup

この大会は2024/3/8 21:00(JST)~2024/3/10 9:00(JST)に開催されました。
今回もチームで参戦。結果は2729点で601チーム中34位でした。
自分で解けた問題をWriteupとして書いておきます。

input-validator (Reversing)

classファイルをjadx-guiデコンパイルする。

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}