Czech CyberSecurity 2022 Round 2 Writeup

| Tags: Articles in English CTF Writeups

What is the CCSC?

The Czech CyberSecurity Contest, or Kybersoutěž, as it is called in Czech, is by far the largest competition of its kind for Czech (not only) high school students. It is also the only way to get nominated to the Czech team for the European CyberSecurity Challenge and then the International CyberSecurity Challenge. The first round is a fairly short trivia quiz, the second round is a month-long almost-a-CTF-but-not-really, and the third round is the on-site finals divided into an individual contest in the morning and then a team one in the afternoon. Then, if you’re a l33t h4xx0r and a bit lucky too, you can get into the ECSC team.

I have a long history with the contest – I first participated in 2018/19 and even got into the final third round on the first try. For a fairly stupid reason I’m still a bit grumpy about it to this day, I didn’t get to go any further that year, but the next years were more successful. Since then, I’ve helped organise ECSC 2021 in Prague, competed in ECSC 2022 in Vienna and overall made quite a few new friendships. As you can see, even though there are many things about the contest a lot of us have reservations about, I owe the contest a lot and wouldn’t be who I am today without it.

With all that said, if you’re a student between 15 and 25 years from the Czech republic with even the slightest interest in cybersecurity, I must recommend you give Kybersoutěž a try.

If you want to see a writeup for last year’s second round, my friend Sijisu made a great writeup which is actually my main inspiration for writing the writeup myself this year.

Also thanks to @flagisallus for some grammar checks.

Well, without further ado, allons-y, shall we?

1 – Rekonstrukce citlivého dokumentu ze síťového provozu

(Reconstruction of a sensitive document from a network traffic capture)

After a short introduction, I received a .pcap file with a lot of miscellaneous traffic. I opened it in wireshark, noticed a large TCP stream to port 9100 and inspected its contents using Follow TCP Stream. The start of the communication looked like this:

.%-12345X@PJL JOB NAME = "Sk..obn. strana"@PJL ENTER LANGUAGE = POSTSCRIPT
%!PS
%%XRXbegin
%%XRXend
%!PS-Adobe-3.0
%%Title: (Sk\372\232obn\341 strana)
%%Creator: PScript5.dll Version 5.2.2
%%CreationDate: 3/29/2018 11:3:37
%%For: Ati
%%BoundingBox: (atend)
%%Pages: (atend)

I saw the POSTSCRIPT there, so I saved the contents of the stream to a file (by first changing Show data as from ASCII to Raw, waiting for all the data to properly load again and then clicking on Save as). After that, I tried viewing the file directly with GhostScript, but that didn’t work, so I tried Googling what the actual PostScript format looks like and I found it always begins with %!PS, so the first line of the file must be some header of the protocol. After removing the header, I got a valid PostScript file that now began like this:

%!PS
%%XRXbegin
%%XRXend
%!PS-Adobe-3.0
%%Title: (Sk\372\232obn\341 strana)
%%Creator: PScript5.dll Version 5.2.2
%%CreationDate: 3/29/2018 11:3:37
%%For: Ati
%%BoundingBox: (atend)
%%Pages: (atend)

And sure enough, after putting that through GhostScript, we get this:

A Windows printer test page in Slovak
The rendered postscript document

As per the insructions, I submitted Skusobna strana tlaciarne systemu Windows as the flag and got my 4 points.

2 – Behaviorální analýza malware

(Behavioral malware analysis)

This was the first multipart challenge with a few subquestions under it.

The description of the challenge introduced an incident with some malware, gave a hint the malware we’re interested in communicated with 1.248.122.240 and finally contained a list of five hashes, one of which is supposed to belong to the binary of the malware:

  1. 36C6F6214694EF64CC70F4127AC0CCEC668408A93825359D998FB31D24968D67
  2. B5884FA3F05F88BBB617D08584930770C00BBCF675F2865A9161C2358829B605
  3. 8d3f68b16f0710f858d8c1d2c699260e6f43161a5510abb0e7ba567bd72c965b
  4. fbe196a583f84bb52db86ca1de63ddb6e2c8f11828f8632567e66ae2ddc5df22
  5. AB0457BEFEBF51A9737E5CA9E46E34A3C37FFB5EF173CA5859E74D9EC76371C1

Since that was the only information I had available, I punched the hashes into VirusTotal. Numbers 1, 2 and 5 didn’t show up in the database at all, and out of the two other ones that did, number 4 was the one that communicated with the aformentioned IP address, so I figured that’s the hash we’ll be working with from now on.

The first subquestion asked for the size of the file in bytes, so I answered 800768, which I read in the Details tab in VirusTotal and got my first point for the challenge.

The second subquestion claimed the malware modifies some registry settings so that a file called SAMEPLE.EXE is replacing some system process as a startup task. After some non-trivial digging through the Behavior tab in VirusTotal, I saw the third entry in the Registry Keys Set section, which told me SysHelper is the right answer, and I got two additional points.

The third question was worded quite confusingly in my opinion and took me quite a long time to understand what it was actually asking for. I was supposed to “Identify the name of the section that is in the hash function MD5 equal to the string f4ae31f7f77436e624b1667cc00af030.” I later found out this called for the .text section of the PE file, the MD5 of which was truly the desired value. I submitted .text and got 3 points.

The fourth and last question asked a fairly simple question: What does the icon of the executable depict? This would’ve been quite a simple task, but I didn’t have the executable on hand, only the hashes, and VirusTotal, which I was using up til now, doesn’t show the file’s icon anywhere. I had to dig a little deeper, finally being able to download the binary from AnyRun where it also made an appearance. After that, it was as easy as unzipping the file, looking at the icon in the file manager, submitting kalkulacka (calculator in Czech) as the answer and getting the final four points for the challenge.

A screenshot with the file icon visible, it is clearly a calculator
The file's icon

3 – Vulnerabilities

This challenge was just a simple Googling one, we were tasked with finding the CVE with the highest CVSS 3.1 score for the Apache HTTP Server version 2.4.53 published before November 1st 2022. This was as easy as issuing a query to the NIST National Vulnerability Database and picking CVE-2022-31813 as the vulnerability with the highest score. This was worth 5 points.

4 – Munus Gyrari

The challenge here was to understand the following Python script and create a decryption function.

from errno import ENXIO
import math
import random

EEZA = ""

BESKO_YKR = '}abcdefghijhlmnopqrstuvwxyz0123456789{'

random.seed(69)

JVLGU_UTXFC_M = random.sample(BESKO_YKR, len(BESKO_YKR))

JVLGU_UTXFC_M = "".join(JVLGU_UTXFC_M)

WLW = 420

def encrypt(mess, WLW):
  c = ""
  for i in mess:
    h = BESKO_YKR.find(i)
    h += WLW
    h %= len(BESKO_YKR)
    c += JVLGU_UTXFC_M[h]

  return c

c = "4qdmf9veoezotbvonxdx4}hedzvae}zabvzz4j"

This was a fairly simple task, and after adding a few lines to the end of the file, I was able to get the decrypted message easily:

def decrypt(c, WLW):
  mess = ""
  for i in c:
    h = JVLGU_UTXFC_M.find(i)
    h -= WLW
    h %= len(BESKO_YKR)
    mess += BESKO_YKR[h]
  return mess

print(decrypt(c, WLW))

I submitted flag{5b838d3c9b3e0a0f2z8adb182d19bddf} as the answer and got my 4 points.

5 – Odhal API klíč v mobilní aplikaci

(Discover the API key in the mobile app)

This was another fairly straightforward one. I received an .apk and was supposed to find an API key in the UUID format inside.

I decompiled the APK using JADX and then used the following command to find the UUID in the sources:

grep -rPn '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' .

I later found out you could use the same grep on the file itself to find the correct answer even without having to decompile the APK.

grep -aPn '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' TestApplication.apk

Both of those commands gave me cf32f2f8-592e-11ed-934d-c7a5b9711d27 which was a correct answer worth 7 points.

6 – Sledování mobilní komunikace

(Sniffing mobile communication)

This is a followup to the previous challenge. We work with the same APK, but this time, we’re supposed to use network communication capturing to find out which URL does the app send geolocation data to.

This challenge kicked me in the nuts quite solidly, but I ended up solving it nevertheless.

I used the PCAPdroid app inside an emulator to record the traffic. The problem was that the URL I was supposed to find was http://thinxcloud43333, which obviously doesn’t DNS resolve and appeared in the captures as an error, so I dismissed it at first. A few hours of tearing my hair out and a very haphazardly made tunnel to the DNS server in my flat later, I managed to get the app to resolve the address to my computer and send the http request to me, from which I confirmed the path of the request was just /, so I didn’t really have to set all that mess up. Anyway, I submitted http://thinxcloud43333 as the answer and got my 8 points.

7 – Hledání skrytého hashe

(Looking for the hidden hash)

In this challenge, I got a JPG image. The task was to find a hash hidden somewhere in the image, find out what hashing “cipher” was used and finally to “decipher” the hash.

It was one of the oldest tricks in the book, just a ZIP file glued to the end of a JPEG. binwalk --extract gave me a text file containing the string 0262176d7e7b9d7ef838290618f2890b, which CrackStation revealed to be the MD5 hash of kockopes.

The “flag” 0262176d7e7b9d7ef838290618f2890b;MD5;kockopes landed me 4 points.

8 – Kyblíková

(This title is untranslatable, the best I can do is Bucketish)

Again, a fairly simple one. A URL links to a funky-looking website with hints about buckets and a clue that the filename we’re looking for is random and not dirbustable. digging the domain returns the following CNAME:

;; QUESTION SECTION:
;bucketlist.ctf.hackinglab.cz.	IN	A

;; ANSWER SECTION:
bucketlist.ctf.hackinglab.cz. 211 IN	CNAME	bucketlist.ctf.hackinglab.cz.s3-website.eu-central-1.amazonaws.com.

Picking the address from the CNAME and replacing s3-website with s3 is enough to get a listing of all files in the bucket. Searching for flag in the output led me to /5244cecd-8218-4ef9-ac1c-43c250b9cf39/flag.txt, which is a file that contains FLAG{AlwaysCheckAWSPermissions-8452}, which is a flag worth 5 points.

9 – List of passwords

You have ZIP file.

It encrypted.

OOGA BOOGA caveman brain password cracking.

Solved by typing “List of passwords” into Google, the fifth link was to SecLists 10 million list, so I tried it, and it worked.

zip2john TOPSECRET.zip > bruh.txt
john bruh.txt --wordlist=/tmp/10-million-password-list-top-1000000.txt

The password to the ZIP was Mailcreated5240, the flag sskb{List_heselJeFakt_Coolvis_coBatm}.

Also this challenge contained a rickroll.

7 points.

10 – Hello Mr. Hejny

You have a ZIP file from the computer of Pavol Hejny.

It encrypted.

OOGA BOOGA caveman brain password cracking using personal info.

Pls don’t contact Pavol Hejny like some of you did last time we made a similar challenge.

OK I’m legit mad about this one. I solved it by accident – while getting a solid case of burnout at 3 in the morning, I just pressed the up arrow in my terminal a bit too much and accidentaly ran the same commands as for the previous challenge, which ended up solving the challenge, becuase the password was not only in the same wordlist, it was actually the same password. YEP.

The password to the ZIP was Mailcreated5240, the flag sskb{Asi_delaBOmbu_aTOJetedaSkandall}.

5 points.

11 – Proudová šifra

(Stream cipher)

A high-speed stream cipher which was first unveiled in February 2003 on the 10th FSE workshop and which was incorporated into the eSTREAM project in May 2005 was used.

Decipher this: U2FsdGVkX19nhDkkP879VHH5qZ8EDTgMg08F5qCzD42XCQasidiGTbaR/roR5ZaQHIzpLxapeCRhZy2O2y7DOqY8BOLyR0WHlmS9Q8XEUrQbQRRo/p7txDx2WvdrGp/Bb37TpovNEcB38Q9PlfbMKbwkSf9DXXNVhjXZF2R6YNk8czic

The 128bit key has something to do with the name of the project where the cipher was presented.

I am fairly certain the cipher that was used is Rabbit, but I wasn’t able to get any further.

FFS, this ended up being the only challenge I didn’t solve, so thanks to @flagisallus for the solution. The cipher used is Rabbit, but it’s actually a very particular implementation of Rabbit used in one particular online encryptor. The task talks about the fact that the key is 128bit, which, since the generator takes strings as passwords, would be 16*8, so 16 bytes or characters. That didn’t end up being true becuase the correct password was 81d8f69189f31267719a9e4876b55cfd, which is the hex-encoded md5 hash of the word eSTREAM. Now, the keen eyed among you might notice this is in fact not 16 bytes or 128 bits, becuase the password is a string, so each individual character gets interpreted as a byte and the actual password is 32 bytes long. The even more keen eyed among you might say that the CryptoJS library doesn’t actually use the password as the key but derives it using its helper functions. It is a bit of a shame the challenge’s author clearly was not aware of any of this.

The flag is KRYPTOLOG and is worth 7 points.

12 – Krejčíř

(This is a name of a famous Czech criminal)

Ok I really have no idea how does this challenge even exist.

I got a link to https://ks.ncko.cz that is only available from South Africa as Krejčíř was hiding there from the law for some time.

The solution was to get a VPN and connect from South Africa to the above URL, after which it gave the flag GE0L0CATION_CH3CK. This challenge is worth 8 points, which makes it the highest rated challenge this year (whyyyy).

I used the torbrowser and set it to use a South African exit node by modifying the torrc file located in ~/.local/share/torbrowser/tbb/x86_64/tor-browser/Browser/TorBrowser/Data/Tor and adding this line to it:

ExitNodes {za} StrictNodes 1

This challenge was sponsored by NordVPN. Click the link in the description or use the code KybersoutěžSellsOut to get 20% off an annual membership.

13 – Volby

(Elections)

Again, fairly simple premise: Find the most common first name and most common surname in the last (Czech) municipal elections and submit the MD5 hash of the two frequencies.

At first, I did not think that hard about it, so I just looked at volby.cz and found a list for each municipality so I just started scraping using a very simple bash script:

curl https://www.volby.cz/pls/kv2022/kv222\?xjazyk\=CZ\&xid\=0\&xv\=11\&xnumnuts\=8106 | sed 's/xdz=\([[:digit:]]\+\)&xnumnuts=\([[:digit:]]\+\)&xobec=\([[:digit:]]\+\)/#####\1 \2 \3#####/g' | grep -oP "####.+####" | sed 's/#//g' > územní-celky
mkdir obce
cat územní-celky |  while read line; do curl https://www.volby.cz/pls/kv2022/kv222\?xjazyk\=CZ\&xid\=0\&xv\=11\&xnumnuts\=$line | sed 's/xdz=\([[:digit:]]\+\)&xnumnuts=\([[:digit:]]\+\)&xobec=\([[:digit:]]\+\)/#####\1 \2 \3#####/g' | grep -oP "####.+####" | sed 's/#//g' > obce/$line; sleep .5; done
for celek in $(ls obce);
do
  cat obce/$celek | while read line;
  do
    druh=$(echo $line | cut -d " " -f 1)
    obec=$(echo $line | cut -d " " -f 3)
    curl https://www.volby.cz/pls/kv2022/kv21111\?xjazyk\=CZ\&xid\=1\&xv\=11\&xdz\=$druh\&xnumnuts\=$celek\&xobec\=$obec\&xstrana\=0 > output/$celek-$obec
    sleep 1
  done
done
cat */output | grep '<td class="overflow_name" '| grep -oP '>.+<' | tr -d '<>' > ../names
cat names | cut -d " " -f 1 | sort | uniq -c | sort -b -g | tail -n 1
cat names | cut -d " " -f 2 | sort | uniq -c | sort -b -g | tail -n 1
A meme with a soldier looking guy and Jack Sparrow. The soldier says 'Your code is without a doubt the worst I have ever run' and Jack holds up his index figers and says: 'But it does run'.

This gave me the following output:

    894 Novák
   9135 Petr

I tried submitting that, but it didn’t work, so I left the challenge alone for a bit.

After a day or two, I found out there’s an OpenData package with the data I needed. 🤦 From there, I downloaded the CSV files for the registries (Registry - CSV (CSVW)) and opened the kvrk.csv file in LibreOffice Calc. I just copied the respective columns and with a quick oneliner for each column:

xclip -selection c -o | sort | uniq -c | sort -b -g | tail -n 1

And that gave me this:

    892 Novák
   9146 Petr

And this ended up being correct, so I just had to make an MD5 hash of the numbers and submitted c2aee86157b4a40b78132f1e71a9e6f1;ea33b4fd0fc1ea0a40344be8a8641123 as the answer and received 6 points.

14 – Analýza malware a C2 komunikace

(Malware and C2 communication analysis)

Ok, this one was the hardest for me. Also, the solution that I ended up using to crack the challenge was probably the weirdest by far. Anyway. This was another multipart, this time centered around an EXE file.

The first question asked how often (rounded to the nearest 10s of seconds) of DNS requests the program makes. I first tried decompiling it in Ghidra and failed miserably, the code was super convoluted and I wasn’t able to figure out anything from it. What I found out fairly easily was that the program uses the nslookup command to make the DNS queries, which also ment I couldn’t just run it in WINE, because WINE doesn’t have nslookup by default. After a few more attempts at some more elegant solutions, I said “f**k it”, booted up Windows and replaced nslookup.exe in my System32 folder with this simple Go script:

package main

import (
	"fmt"
	"log"
	"os"
	"time"
)

func main() {
	f, err := os.OpenFile("text.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Println(err)
	}
	fmt.Fprintln(f, time.Now(), os.Args)
}

Then I just ran the EXE and let it do its thing for a bit. The first few lines of the file looked like this:

2022-12-28 19:04:41.065258 +0100 CET m=+0.002689401 [nslookup -q=A 1r40.cz]
2022-12-28 19:04:51.0273004 +0100 CET m=+0.003115401 [nslookup -q=A 9y88.cz]
2022-12-28 19:05:00.9870746 +0100 CET m=+0.002739201 [nslookup -q=A 2o55.cz]
2022-12-28 19:05:10.952896 +0100 CET m=+0.003289401 [nslookup -q=A 1c11.cz]
2022-12-28 19:05:20.9198564 +0100 CET m=+0.002706001 [nslookup -q=A 5r76.cz]

From this, I was clearly able to pick out the period was 10 seconds. This was worth the first 1 point.

The second question asked which system tool the application uses for the lookups and since I already knew this, I just answered nslookup for another 1 point.

The third question asked which WinAPI function is used to invoke nslookup. For this, I looked through the Imports section of the Symbol Tree panel in Ghidra and the only remotely suitable function it could be was WinExec. 3 points.

The fourth question asked what is the period (rounded to the nearest 10s of minutes) at which the application makes a DNS query to a domain probably owned by the attacker – a domain, which the application contacts multiple times. When inspecting the log created by my modified nslookup.exe, I found multiple queries to 0x90.cz, which I clearly knew was the right answer because a) 0x90 is the opcode for the NOP instruction, and b) the domain is owned by the challenge author. I also noticed the timestamps beside the log entries for this domain are almost exactly 10 minutes apart, so I submitted 10 and got 2 points.

Finally, the last question asked about what domain was being contacted in the previous question and since I already knew that, I just answered 0x90.cz and got the last 3 points.

15 – Analýza (nejen) síťové komunikace

((not just) network traffic analysis)

Another multipart challenge, this time centered around a PCAP. I actually went through most of the challenge before answering any of the subquestions.

The supplied PCAP contained a lot of extra data (like 80 MBs, even though the interesting data was just a few kBs) but after a short search, I found repeated DNS queries to _something_.x90.cz and that something looked like base64, so I filtered the packets out with the following query:

dns.qry.name matches "x90.cz$" && ip.src==10.0.2.15
A screenshot from wireshark with the DNS packets displayed
A screenshot from wireshark with the DNS packets displayed

I exported the packets using Export packet dissections as plain text and then grepped the base64 out:

cat packets.txt | grep -oP 'TXT _.*_' | grep -oP '_.*_' | tr -d "_" | base64 -d

This spat out what looked like a PNG and sure enough, after writing it to a file, I got this:

A small PNG image with the text 'x/98_sc_pSa'.

This looked pretty promising, so I looked back at what the questions actually were:

Q1: What protocol was used for the password exfiltration? DNS – 1 point

Q2: Identify the second level domain which was used for the exfiltration. x90.cz – 1 point

Q3: Identify the algorithm that was used for encoding the data before the transfer. base64 – 1 point

Q4: In how many packets was the data encoded? 183 (I think, it might’ve been 184) – 1 point

Q5: What filetype was the password saved to? PNG – 2 points

Q6: What was the password? x/98_sc_pSa – 4 points

16 – Analýza souborů zašifrovaných ransomwarem

(Analysis of files encrypted by ransomware)

I received three files ({1..3}.enc) and the following string 2245023265ae4cf87d02c8b6ba991139, which “might be the key to some of the files”. The files all contained only one line, which looked like this:

original_filename:logo_272x92.png,date_created:9/30/22,settings:null,content:598485bd740fd3f1337c51...

Since I had the supposed key, I tried decrypting the hex data in all three files using AES-CBC in CyberChef (with the IV set to 0). The third file was the first one to give valid output, and it looked like some xml.

A screenshot from CyberChef decrypting the second encrypted file.
A screenshot from CyberChef decrypting the second encrypted file.

After that, I tried to see how the key was derived since I knew for which file it was. I figured it would be some kind of hash, so I popped it into CrackStaiton again and got that it was an MD5 hash of config. This corresponded to the filename without extension, which was noted in the 2.enc file. I tried MD5 hashing the other two filenames, and sure enough, I was able to decrypt the files using those. Number one was a PNG with a white Google logo and number three was a docx file with a todo list.

Now, since this was a multipart again, to the questions:

Q1: How is the encryption key derived? nazev souboru (filename in Czech) – 1 point

Q2: What algorithm was used to derive the key? MD5 – 1 point

Q3: Identify the encryption algorithm which was used. The answer should include the key length in the following format: XXX-YYY AES-128 – 3 points

Q4: What IV was used? Include all bytes in hex. 00000000000000000000000000000000 – 1 point

Q5: Identify which file was encrypted using the key 2245023265ae4cf87d02c8b6ba991139. config.bak – 1 point

Q6: Decrypt 1.enc and identify the company whose logo is in the file. Google – 1 point

Q7: What is the first URL in config.bak? http://schemas.microsoft.com/appx/2010/blockmap – 1 point

Q8: What is the second task in the todo list from 3.enc? Updatovat webový portál – 1 point

17 – Macrohard without Macro

This was an interesting one again, we received an empty and innocuous looking docx file. It actually uses the CVE-2021-40444 vulnerability which is an RCE in MSHTML. Actually getting the flag was fairly easy, I unzipped the docx file and looked at the files inside. The only thing that seemed out of place was this URL https://nakit.cz/kyb3rs0u73z/ in word/_rels/document.xml.rels. After visiting the URL I received my flag flag{7ffcc039ab0a99fc545e22cec7442b5adfb15085}.

6 points.

18 – Hvizd

(Whistle)

I got a PDF, an XLSX file and an encrypted zip file. Opening the PDF in vim I saw there was a malformed note containing the text Password for Decrypt.zip is muheheh153. After decrypting the ZIP using the supplied password, I got a python file:

import cv2
from tkinter import filedialog, Tk, Button, Label, Text, WORD, INSERT
from PIL import Image, ImageTk
import numpy as np

image_display_size = 500, 350


def decrypt():

    global lowsjhgf
    nekrfhjek = 42
    HEXwdssap = "afcea"
    hexbaserc2baserot13="This is the way"
    lowsjhgf = filedialog.askopenfilename()
    kdsjhkieju = Image.open(lowsjhgf)
    kdsjhkieju.thumbnail(image_display_size, Image.ANTIALIAS)
    kdsjhkieju = np.asarray(kdsjhkieju)
    kdsjhkieju = Image.fromarray(np.uint8(kdsjhkieju))
    nsogjrrg = ImageTk.PhotoImage(kdsjhkieju)
    mdekigjedkgf = Label(app, image=nsogjrrg)
    mdekigjedkgf.image = nsogjrrg
    mdekigjedkgf.place(x=100, y=50)
    mdekigjedkgf = cv2.imread(lowsjhgf)
    kjwehtked = 64695
    kdeggegnj = 61463
    kehekgorr = 0
    kjwehtked = nekrfhjek * kjwehtked
    datatata = []
    stop = False
    for index_i, i in enumerate(mdekigjedkgf):
        i.tolist()
        for index_j, j in enumerate(i):
            if((index_j) % 3 == 2):
                datatata.append(bin(j[0])[-1])
                datatata.append(bin(j[1])[-1])
                if(bin(j[2])[-1] == '1'):
                    stop = True
                    break
            else:
                datatata.append(bin(j[0])[-1])
                datatata.append(bin(j[1])[-1])
                datatata.append(bin(j[2])[-1])
                deleteThis = kjwehtked/kehekgorr
        if(stop):
            break

    message = []
    for i in range(int((len(datatata)+1)/8)):
        message.append(datatata[i*8:(i*8+8)])
        andThis = kdeggegnj/kehekgorr
    message = [chr(int(''.join(i), 2)) for i in message]
    message = ''.join(message)
    label = Label(app, text="Pokud pravdu nevidíš, kód nejdřív upravíš",
                bg='lavender', font=("Arial", 23))

    i = kjwehtked/kehekgorr
    txt.insert(INSERT, message)
    label.place(x=80, y=400)


app = Tk()
app.configure(background='green yellow')
app.title("Dekóduj")
app.geometry('800x800')
main_button = Button(app, text="Vybrat Obrázek", bg='white', fg='black', command=decrypt)
main_button.place(x=350, y=10)
txt = Text(app, wrap=WORD, width=30)
#txt.place(x=275, y=555, height=195)
app.mainloop()

After removing all the lines where the code explicitly divides by zero and uncommenting the second to last line, the script seemed to run correctly. There is a button with “Vybrat Obrázek” (Choose Image in Czech). I tried looking through the XLSX file (again by opening it as a ZIP), and I found image1.png. After inputting the image into the tool, I got some hex output out:

4d47526a5a5446684d6a67345a57466d4e44526a59324d774f54686a4e445a6c5a5445314f574d77596d51334d6a67324e6d517a4f57466b4d5441344e57526c4d7a41324e5467775a545a68597a526d4d6a426a5a474a6a4d3249304e6a4134597a51304d574d795a6d49795a6a45315a544a6c4f544a6a4d44686c4e546c6d
A screenshot of the tool, with the inputted image at the top (Yoda saying I'll need RC2) and the output hex at the bottom
A screenshot of the tool, with the inputted image at the top (Yoda saying I'll need RC2) and the output hex at the bottom

This is where I got stuck for a few days. While writing this writeup I noticed these two lines in the source:

    HEXwdssap = "afcea"
    hexbaserc2baserot13="This is the way"

I followed the directions here and after a bit of fiddling with CyberChef, I got the desired output: [WIN FLAG {You_are_The_Best}] for 5 points.

A screenshot of CyberChef deciphering the data
A screenshot of CyberChef deciphering the data

19 – Trezor

(The safe)

We need to get into a safe with a five-digit combination. There’s a hint that we need to subtract the individual numbers from 10 and the following text

alfa ffc9b67ac0ed957f2cbcc7d72c61e39d, gama 9a98f94567ed01a13380e8d4ee6cc270,
beta 3c7947ba721fc844f40b52ea7cc4f64f, epsilon 115158b59ce9ad6dce51825fc51ed1f7,
delta d8ea17ef44ab34fe8c5d60dd7cc21053

(What seems to be a theme in this year), all of those are MD5 hashes. Inputting them into CrackStation (again, *sigh*) we get the following output:

ffc9b67ac0ed957f2cbcc7d72c61e39d	md5	SEST
3c7947ba721fc844f40b52ea7cc4f64f	md5	JEDNA
9a98f94567ed01a13380e8d4ee6cc270	md5	DVA
d8ea17ef44ab34fe8c5d60dd7cc21053	md5	SEDM
115158b59ce9ad6dce51825fc51ed1f7	md5	OSM

All of these are Czceh words for numbers, 61278 to be exact (I sorted them using the greek alphabet – alpha, beta, gamma, delta, epsilon). When subtracting all those numbers from 10, we get 49832, which is indeed the correct answer worth five points.

20 – Učení je dřina

(Studying is hard)

We get the following text:

Utajená česká evangelická národní instituce je apriori komerční organizací, poskytující racionální odborně technické informace. Proto radí ohleduplně udržovat diplomatická ujednání při licitacích a veřejných akademických neformálních iniciativách. U setkání takových abnormalit nejsou ekonomické sliby lichotivé. I na abonentních, charismatických, hlučně vypadajících iniciativách lichotí invektivy. V rádoby anarchistických teritoriích iniciativně soupeří s ekonomickou ortodoxní melancholií imperialisté lokálně indiferentní.

It is mostly nonsensical, and immedeately I looked at the first letter in each word.

We get UčenijakoprotiProuduplavaniUstaneslInachviliVratisseomili, after cleaning that a bit, it is a Czech saying “Učení – jako proti proudu plavání: ustaneš-li na chvíli – vrátíš se o míli”. The instructions ask for the first and sixth word without diacritics and separated by a semicolon: uceni;ustanes. 4 points.

Conclusion

That’s it for this year. Hope you learned at least a bit or got inspired to try the contest for yourself. If you have any corrections or want to add some info, feel free to contact me however you want – I’m fairly active on the CCSC discord for instance.