TBTL CTF 2024
https://ctftime.org/event/2324
CHALL’S SOLVED
Category | Challenge |
---|---|
Crypto | Fence Building |
Intro | Sanity.py |
Rev | Flagcheck |
Crypto
Fence Building
Overview
Description: I’ve recently got into woodworking and have build a beautiful fence just like this one.
Now I’m working on a flag, but it turns out all garbled for some reason…
T0n40g5BG03cmk0D1hr}T{dFe_3g_3buL_5_n0
In this crypto challenge, only a ciphertext is given which needs to be decoded and the hint is also given.
This classic challenge ciphertext is text that is encoded using Rail Fence Encoding (also called zigzag cipher) is a transposition cipher. The message is written in a zigzag pattern on an imaginary fence, thus its name. It is not strong as the number of keys is small enough to brute force them.
We can just decode using CyberChef, here’s the recipe Railfence Ciphertext Decode
or here’s the solver implementation using python and bruteforcing the key
1import re
2
3ciphertext = 'T0n40g5BG03cmk0D1hr}T{dFe_3g_3buL_5_n0'
4
5def rail_fence_decrypt(ciphertext, rails):
6 matrix = [['' for _ in range(len(ciphertext))] for _ in range(rails)]
7 idx = 0
8 for rail in range(rails):
9 p = (rail != 0 and rail != (rails - 1))
10 r = rail
11 while r < len(ciphertext):
12 matrix[rail][r] = '*'
13 if p:
14 r += 2 * (rails - rail - 1)
15 else:
16 r += 2 * rail
17 p = not p
18 r = 0
19 for i in range(rails):
20 for j in range(len(ciphertext)):
21 if matrix[i][j] == '*':
22 matrix[i][j] = ciphertext[r]
23 r += 1
24 plaintext = ''
25 for i in range(len(ciphertext)):
26 for j in range(rails):
27 if matrix[j][i] != '':
28 plaintext += matrix[j][i]
29 return plaintext
30
31def find_string(plaintext):
32 pattern = r'TBTL{.*}'
33 match = re.search(pattern, plaintext)
34 if match:
35 return match.group()
36 return None
37
38for rails in range(2, len(ciphertext)):
39 plaintext = rail_fence_decrypt(ciphertext, rails)
40 found_string = find_string(plaintext)
41 if found_string:
42 print(f'Key: {rails} \nFlag: {found_string}')
43 break
$ python3 solver.py
Key: 4
Flag: TBTL{G00d_F3nce5_m4k3_g00D_n31ghb0ur5}
FLAG
TBTL{G00d_F3nce5_m4k3_g00D_n31ghb0ur5}
Intro
Sanity.py
Overview
"}FTC_3h7_y0jn3-!d3s54P_kc3hC_y71n4S{LTBT"[::-1]
This Sanity check is a pretty simple, we can just reverse the bytes [::-1]
of it.
$ python3
>>> flag = "}FTC_3h7_y0jn3-!d3s54P_kc3hC_y71n4S{LTBT"
>>> print(flag[::-1])
TBTL{S4n17y_Ch3ck_P45s3d!-3nj0y_7h3_CTF}
>>>
FLAG
TBTL{S4n17y_Ch3ck_P45s3d!-3nj0y_7h3_CTF}
Rev
Flagcheck
Overview
Description: This one is simple — you provide the flag, and the binary tells you if its correct.
Move on to rev category, this challenge is like Flagchecker of ELF 64 Binary
$ file flagcheck
flagcheck: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=68da80acf7353d56f047fa725e2506428b7c6864, for GNU/Linux 3.2.0, not stripped
Let’s try to executing the binary file
$ chmod +x chall
$ ./chall
Let me check your flag: idk ma boahh
Nope...
Here’s the decompiled of main
function,
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ebx
int seed; // [rsp+4h] [rbp-6Ch]
int i; // [rsp+8h] [rbp-68h]
int j; // [rsp+Ch] [rbp-64h]
char s[72]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v9; // [rsp+58h] [rbp-18h]
v9 = __readfsqword(0x28u);
printf("Let me check your flag: ");
__isoc99_scanf("%s", s);
if ( strlen(s) != 63 )
no();
seed = 1;
for ( i = 0; i < strlen(s); ++i )
seed *= s[i];
srand(seed);
for ( j = 0; j < strlen(s); ++j )
{
v3 = s[j];
if ( ((rand() % 256) ^ v3) != target[j] )
no();
}
puts("Correct!");
return 0;
}
From the decompiled code looks like an xor program and I’m quite curious about what the target
array will do.
.rodata:0000000000002020 ; _DWORD target[63]
.rodata:0000000000002020 _ZL6target dd 33h, 84h, 3Dh, 3Fh, 2Ah, 93h, 7Bh, 82h, 1Ah, 0ACh, 8Eh
.rodata:0000000000002020 ; DATA XREF: main+E4↑o
.rodata:0000000000002020 dd 0F4h, 0B1h, 0CBh, 8Dh, 21h, 0Eh, 0B7h, 67h, 96h, 2Ch
.rodata:0000000000002020 dd 81h, 0D3h, 0BCh, 29h, 6Ch, 4Bh, 0Dh, 0, 0EDh, 0FDh
.rodata:0000000000002020 dd 0EEh, 56h, 40h, 52h, 0D5h, 5, 6Dh, 90h, 3Eh, 7Ah, 1Bh
.rodata:0000000000002020 dd 69h, 23h, 1Fh, 0B6h, 1Dh, 0BCh, 98h, 0D1h, 0A6h, 83h
.rodata:0000000000002020 dd 0E9h, 0EBh, 13h, 21h, 3Dh, 0F8h, 2Bh, 79h, 53h, 4Fh
.rodata:0000000000002020 dd 0A1h
Aha nice! the value of target
array has discovered from .rodata
section.
So here’s the conclusion, The program will reads the flag, checks its length, calculates a seed for the random number generator from the flag, and then checks each character of the flag against a value in the target
array.
The target array is XORed with the result of rand() % 0x100
. Since the seed for rand()
is determined by the flag, we can predict the sequence of random numbers. However, that the seed is calculated by multiplying all the ASCII values of the characters in the flag, which makes it difficult to reverse.
Here’s the high-level approach to solve this:
- Brute force the seed: Since the seed is a product of ASCII values, and ASCII printable characters range from 32 to 126, the maximum value for the seed is
126^63
. However, this is not feasible to brute force. But, considering that the flag format isTBTL{.*}
, we can reduce the search space. The seed is also anuint (32-bit)
, so the maximum value is2^32-1
. This is feasible to brute force. - Predict the random sequence: Once the seed are correct, we can generate the same sequence of random numbers as the program.
- Recover the flag: Now that the same
rand()
sequence, we can manage to recover the flag. For each character in the flag, calculatetarget[i] ^ (rand() % 0x100)
to get the original character.
Here’s the solver,
1import ctypes
2
3# The target array from the .rodata
4target = [0x33, 0x84, 0x3D, 0x3F, 0x2A, 0x93, 0x7B, 0x82, 0x1A, 0xAC, 0x8E, 0xF4, 0xB1, 0xCB, 0x8D, 0x21, 0xE, 0xB7, 0x67, 0x96, 0x2C, 0x81, 0xD3, 0xBC, 0x29, 0x6C, 0x4B, 0xD, 0x0, 0xED, 0xFD, 0xEE, 0x56, 0x40, 0x52, 0xD5, 0x5, 0x6D, 0x90, 0x3E, 0x7A, 0x1B, 0x69, 0x23, 0x1F, 0xB6, 0x1D, 0xBC, 0x98, 0xD1, 0xA6, 0x83, 0xE9, 0xEB, 0x13, 0x21, 0x3D, 0xF8, 0x2B, 0x79, 0x53, 0x4F, 0xA1]
5
6# The libc rand() function
7libc = ctypes.CDLL('libc.so.6')
8rand = libc.rand
9rand.argtypes = []
10rand.restype = ctypes.c_int
11
12# The libc srand() function
13srand = libc.srand
14srand.argtypes = [ctypes.c_uint]
15srand.restype = None
16
17# Brute force the seed
18for seed in range(2**32):
19 srand(seed)
20 flag = []
21 for i in range(len(target)):
22 r = rand() % 0x100
23 flag.append(chr(target[i] ^ r))
24 if flag[0] == 'T' and flag[1] == 'B' and flag[2] == 'T' and flag[3] == 'L':
25 print(''.join(flag))
26 break
$ python3 solver.py
TBTL{l1n3a4_C0ngru3n7i41_6en3r4t0r_b453d_Fl4G_Ch3ckEr_G03z_8rr}
FLAG
TBTL{l1n3a4_C0ngru3n7i41_6en3r4t0r_b453d_Fl4G_Ch3ckEr_G03z_8rr}