CSCTF 2024: Notes

September 02, 2024


These are less writeups, more informal notes for myself to remind myself how I solved certain things in CTFs. For this one, it's CSCTF'24. Formal writeups for official events can be found under Liminova's blog instead. We ended up not placing very high, but certainly going through these in 6 hours is something :D

  1. beginner/encryptor
  2. beginner/key
  3. beginner/Modulus RSA
  4. beginner/Baby Pybash
  5. beginner/cipher block clock
  6. forensics/Geometry Dash 2.1
  7. crypto/flagprinter

beginner/encryptor

Categories: rev, mobile
My friend sent me this app with an encoded flag, but he forgot to implement the decryption algorithm! Can you help me out?

Challenge file: encryptor.apk

  • Decompile the code. It's Blowfish. The key is encryptorencryptor, encoded in Base64.
  • flag.txt is in the assets.
  • Plug it in this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class Decryptor {
    public static void main(String[] args) {
        String encryptedFlag = "YOUR_ENCRYPTED_FLAG";
        String decryptedFlag = decryptText(encryptedFlag);
        System.out.println("Decrypted Flag: " + decryptedFlag);
    }

    private static String getKey() {
        return new String(Base64.getDecoder().decode("ZW5jcnlwdG9yZW5jcnlwdG9y"), StandardCharsets.UTF_8);
    }

    private static String decryptText(String encryptedText) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(getKey().getBytes(StandardCharsets.UTF_8), "Blowfish");

            Cipher cipher = Cipher.getInstance("Blowfish");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

beginner/key

Categories: rev
GDB is cool! Ghidra or IDA is helpful

Challenge file: key

  • Decompile the code using Ghidra.
  • This block of code is the correct values that are used to check the input:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
local_b8[0] = 0x43;
local_b8[1] = 0xa4;
local_b8[2] = 0x41;
local_b8[3] = 0xae;
local_a8 = 0x42;
local_a4 = 0xfc;
local_a0 = 0x73;
local_9c = 0xb0;
local_98 = 0x6f;
local_94 = 0x72;
local_90 = 0x5e;
local_8c = 0xa8;
local_88 = 0x65;
local_84 = 0xf2;
local_80 = 0x51;
local_7c = 0xce;
local_78 = 0x20;
local_74 = 0xbc;
local_70 = 0x60;
local_6c = 0xa4;
local_68 = 0x6d;
local_64 = 0x46;
local_60 = 0x21;
local_5c = 0x40;
local_58 = 0x20;
local_54 = 0x5a;
local_50 = 0x2c;
local_4c = 0x52;
local_48 = 0x2d;
local_44 = 0x5e;
local_40 = 0x2d;
local_3c = 0xc4;
  • Input needs to be 32 characters long, with each character matching this transformation step:
1
aiStack_138[(int)local_140] = ((int)local_38[(int)local_140] ^ local_140) * ((int)local_140 % 2 + 1);
  • Solver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target_values = [
    0x43, 0xa4, 0x41, 0xae, 0x42, 0xfc, 0x73, 0xb0,
    0x6f, 0x72, 0x5e, 0xa8, 0x65, 0xf2, 0x51, 0xce,
    0x20, 0xbc, 0x60, 0xa4, 0x6d, 0x46, 0x21, 0x40,
    0x20, 0x5a, 0x2c, 0x52, 0x2d, 0x5e, 0x2d, 0xc4
]

correct_input = []

for i in range(32):
    transformed_value = target_values[i] // (i % 2 + 1)
    correct_char = chr(transformed_value ^ i)
    correct_input.append(correct_char)

correct_key = ''.join(correct_input)
print("correct input:", correct_key)

beginner/Modulus RSA

Categories: crypto
Modulus tells you everything

Challenge file: chall.py

  • Method derived by pure magic by NamSPro.
  • Solver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from sympy import isprime
from math import lcm
from Crypto.Util.number import long_to_bytes

w = 115017953136750842312826274882950615840
x = 16700949197226085826583888467555942943
y = 20681722155136911131278141581010571320

result = w + y - x

prime_status = isprime(result)

print(f"w + y - x = {result}")
print(f"Is w + y - x prime? {'Yes' if prime_status else 'No'}")

p = result
q = result + w
r = result + w + y

n = p * q * r

print(lcm(p - 1, q - 1, r - 1))

c = 2246028367836066762231325616808997113924108877001369440213213182152044731534905739635043920048066680458409222434813
d = 18856566978629040151892287870666377555281871836968411186744228839975255760844186581692559347083345007851203866545

print(long_to_bytes(pow(c, d, n)).decode("utf-8"))

beginner/Baby Pybash

Categories: jail
I made a very secure bash terminal in Python. I don't think anyone can break in!

Challenge files: handout_baby_pybash.zip

  • The environment is executed in context of run.sh, which has the shebang #!/bin/bash
  • $0 expands to the first argument in bash - in this case /bin/bash.
  • Inputting $0 into the thing bypasses the regex, expanding into /bin/bash. Now you're in.
  • cat flag.txt, gg ez.

beginner/cipher block clock

Categories: crypto
Liquid IV is a lifesaver the next morning.

Challenge files: chall.py, out.txt

  • The encryption is wrongly used: AES.new(iv, AES.MODE_CBC, k) - the IV is passed as the key, and the key is used as the IV instead; this means the key is returned in out.txt.
  • IV only affects the first len(iv) // 2 bytes, and XOR is a symmetrical operation.

forensics/Geometry Dash 2.1

I would give you the flag but I can't let go (haha get it). use GDBrowser for the last step btw.
Note: You do NOT need Geometry Dash purchased to solve this challenge.

Challenge file: CCLocalLevels.dat

  • Use Geometry Dash Save Explorer to open the file.
  • Inspect the level text. There's a lot of Base64-encoded text. extract each of them.
  • This gives you a level ID, plug it into GDBrowser and check the comments.

crypto/flagprinter

Instead of a challenge, here's a solution. Hope you have plenty of RAM!

Challenge files: chall.py, out.py.

  • Method derived by magic by NamSPro (again), citing https://oeis.org/A053735.
  • Solver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from out import enc, R
from math import prod

def to_base_3(n):
    if n == 0:
        return '0'

    digits = []
    while n > 0:
        digits.append(str(n % 3))
        n //= 3

    return ''.join(digits[::-1])

def a(x: int):
    t = to_base_3(x)
    v = 0

    for c in t:
        v += int(c)

    return v

flag = ''
for i in range(355):
    if i%5 == 0:
        flag += chr(enc[i//5] ^ prod([a(_) for _ in R[i//5]]))
        print(flag)