NahamCon EU CTF 2022 was a 24-Hour CTF contest hosted by JustHacking on December 16th, 12:00 PM - December 17th, 12:00 PM PST. Our team T34M #4294967295
, consisting of me, Anorak and Jokesta, participated and secured 27th place in the CTF. Here are the writeups for some of the flags we captured.
Math Smashers - Scripting
Description:
So this is supposed to progressively get “harder” and has a time cap per equation. Give your answer to the nearest 4 decimal places unless there’s a trailing 0.
Upon visiting the given link, we come up with a page with an arithmetic question in an image which we need to solve and submit within a decreasing time limit. Each correct answer will increase your score by 1, and any wrong answer resets it back to 0.
Approach
We used Pytesseract to generate strings from images and used eval to solve the equations. We had to grayscale the image and whitelist characters in an arithmetic equation for better accuracy.
Final Script:
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
import pytesseract
import requests
import cv2
import numpy as np
from bs4 import BeautifulSoup
import sys
BASE_URL = "http://challenge.nahamcon.com:"+ str(sys.argv[1]) +"/"
flag_found=False
while not flag_found:
img_response = requests.get(BASE_URL + "/static/eqn.png")
image = cv2.imdecode(np.frombuffer(img_response.content, np.uint8), cv2.IMREAD_GRAYSCALE)
config = '--oem 0 --psm 6 -c tessedit_char_whitelist=0123456789+-*/^()'
arithmetic_problem = pytesseract.image_to_string(image, lang='eng', config=config)
result =0
try:
result = eval(arithmetic_problem.replace(" ", ""))
except:
print(arithmetic_problem)
break
result = round(result, 4)
data = {"eqn_ans": result}
response = requests.post(BASE_URL, data=data)
soup = BeautifulSoup(response.text, 'html.parser')
count_element = soup.find('p', class_='count')
print(count_element.text + ": " + str(result))
The Space between Us - Miscellaneous:
Description:
I’ve never felt this close to a character before. I hope the feeling is mutual…
Escalate your privileges and retrieve the flag from the root’s home directory.
Approach:
We were given an NC command to connect to a machine, There were only limited tools installed into the system, And for the Hell, even cd wasn’t present. We could not use the space character directly as it was getting filtered out. We had to use ${IFS}
instead, an env variable for space. Though this worked in most cases, it was terrible. Anorak switched to a python reverse shell as it was installed in the system, making it easier to work around. We discovered that the `/etc/password is writable by the user, so we added a new root-privileged user and gained root.
Rick - Reverse Engineering:
Description:
These files were on a USB I bought from a pawn shop.
Approach.
We were given 2 files, ct.enc
and a binary file called a program. ct.enc
consisted of encrypted text. Upon analyzing with ghidra, we found that it performed AES 128 encryption on the input. The key for it was generated during the execution. We reused the reversed code from the binary to regenerate the key and decrypt our message.
Script:
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
using namespace std;
unsigned char* enc_key =new unsigned char[16];
#include <openssl/evp.h>
#include <cstring>
int dec_func(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv) {
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len;
unsigned char *plaintext;
// Allocate and initialize a new cipher context
ctx = EVP_CIPHER_CTX_new();
// Initialize the context for a decryption operation using the
// AES cipher in CBC mode with the given key and IV
EVP_DecryptInit(ctx, EVP_aes_128_cbc(), key, iv);
// Allocate a buffer to hold the plaintext
plaintext = (unsigned char*)malloc(ciphertext_len);
// Decrypt the ciphertext and store the resulting plaintext in the buffer
EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);
plaintext_len = len;
// Finalize the decryption operation
EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
plaintext_len += len;
// Clean up the cipher context
EVP_CIPHER_CTX_free(ctx);
cout << plaintext;
return plaintext_len;
}
void gen_key(void){
unsigned int local_10;
int local_c;
for (local_c = 0; local_c < 16; local_c = local_c + 1) {
if (local_c == 0) {
local_10 = 0x27e2;
}
else {
local_10 = (local_10 + local_c) * 4 ^ 0x29fa;
}
(enc_key)[local_c] = (char)local_10;
}
return;
}
int main() {
FILE *f = fopen("ct.enc", "rb");
fseek(f, 0, SEEK_END);
long ciphertext_len = ftell(f);
rewind(f);
unsigned char *ciphertext = (unsigned char*)malloc(ciphertext_len);
fread(ciphertext, 1, ciphertext_len, f);
fclose(f);
gen_key();
unsigned char* enc_int= new unsigned char[16];
memset(enc_int,0,16);
dec_func(ciphertext, ciphertext_len, enc_key, enc_int);
}
Byepass - Web
Description:
Help yourself Say Goodbye to days gone by with our easy online service! Upload your photos to capture the memory, cherish them with friends and family, and savor the time we have together!
Approach:
We were given source code, and URL to a PHP(Apache) based web server. It had a file upload functionality and blacklisted several PHP files but not .htaccess
, so we used htshells to generate a web shell, uploaded it and obtained the flag.
ShapeShifter
Description:
These bits are shapepshifting! I need some help getting them back to their original form. Someone told me this might be a Fibonacci LFSR.
Approach:
There was an output.txt with encrypted text and the python encryption program. It encrypted 2 bytes at one time. All we had to do was just script to brute force the LFSR encryption and, done, get the flag.
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
33
34
35
36
37
38
39
40
41
42
from itertools import permutations, product
from Crypto.Util.number import bytes_to_long as b2l
FLAG = open('flag.txt', 'r').read().strip().encode()
class LFSR():
def __init__(self, iv):
self.state = [int(c) for c in iv]
# self.state = self.iv
def shift(self):
s = self.state
newbit = s[15] ^ s[13] ^ s[12] ^ s[10] # ^ s[0]
s.pop()
self.state = [newbit] + s
if __name__ == "__main__":
arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
'd', 'e', 'f', 'l', 'g', '{', '}']
r = 2
chars = [f'1000110001010111', '1111001000000010','1110110010100010','0111111111011101','1000010111110110','0011111000100000','0100001000010110','0001111000001010','0111000100101010','0101100111100011',
'0000000101011010','1010111100001010','0010101111110000','1110100111110111','1001101000100010','0010001111000001','0111101111111100','0011110000010101','0110110010001100'
]
flagdec = ''
mychars = [p for p in product(arr, repeat=2)]
for i, char in enumerate(chars):
for flag in mychars:
strg = ''.join(flag)
curr = bytes(strg, 'utf-8')
chars = f'{b2l(curr):016b}'
assert len(chars) == 16
lfsr = LFSR(chars)
for _ in range(31337):
lfsr.shift()
finalstate = ''.join([str(c) for c in lfsr.state])
if (f'{finalstate}' == f'{char}'):
flagdec = flagdec + strg
break
print(flagdec)
This was it.
:P I will see If I can add writeups for IP Man, RaaS and Dizzy, which were solved by Anorak. The rest we solved were pretty easy and self-explanatory.
Meanwhile, Here’s an eye hurting certificate from the event: