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
- beginner/encryptor
- beginner/key
- beginner/Modulus RSA
- beginner/Baby Pybash
- beginner/cipher block clock
- forensics/Geometry Dash 2.1
- 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 inout.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)