Skip to main content
Contact us: blog@ukatemi.com
TECHNICAL BLOG ukatemi.com

IX. National IT Competition (OITM) recap

The National IT Competition (aka. OITM, Országos IT Megmérettetés) is a yearly, individual, large-scale Hungarian online competition designed for IT professionals, developers, students, and technology enthusiasts. It runs over several weeks and features practical, real-world challenges across more than twenty IT categories, such as programming, cybersecurity, DevOps, data science, and other modern, mainstream technology fields. Participants complete online tasks that test both theoretical knowledge and hands-on skills, receiving scores and feedback that allow them to compare their performance with other competitors nationwide, win prizes and gain professional recognition.

OITM IX. (2025)

During the Autumn of 2025 the IX. OITM took place with 16 categories and 5 rounds in each category. The major update in this year's competition was that 6 categories had a 6th round which was a live final hosted before the award ceremony on the 6th of February, 2026. Another change from our side was that for the first time we were invited to create and host the IT Security category. This meant a few things:

Three of us were responsible for creating the challenges: Gergő Krátky and Kristóf Tamás (for all rounds) and Zoltán Iuhos (for the final).

This year, our focus was on the IT Security category, but Kristóf Tamás and Balázs Gnandt also participated in other categories. We reached the top 10 multiple times, most notably Kristóf earned the 2nd place in the Technical Dept Management and in the Frontend categories and reached the 6th place on the overall scoreboard (3rd place in the online rounds) in a very tight competition. There were more than 1800 participants in the whole event and 539 in our IT Security category which was one of the most popular categories.

The motto of the competition was There's no AI without wit! (Nincs AI ész nélkül!) which was very apt. Some categories did not support the use of AI, but in our category we allowed everything, just as in a real life scenario. The challenges we created could be solved faster with the help of AI tools, but AI alone was not enough to be amongst the top players, it required criticism, experience and out-of-the-box thinking. AI alone is insufficient in this area, individual preparedness, creativity, and expertise are still a competitive advantage.

The online rounds (round 1-5)

The first five rounds took place in October and November during five consecutive weeks. All five rounds followed a single incident investigation, where the competitors had to identify the entry point of the attacker, investigate the dropper file and the deployed ransomware, use OSINT techniques to find information about the threat actors, and finally hack the infrastructure of the attacker to obtain the ransomware decryption keys.

Solutions of the online rounds

Round 1 - #forensics

wireshark pcap rdp dropper powershell

During the 1st round the competitors were given a PCAP file which they have to analyze:

7 players could solve all questions in this round.

Round 2 - #reverse

python pyinstaller aes rsa kill switch darknet

The task in the 2nd round was to analyze the ransomware itself:

45 players could solve all questions in this round.

Round 3 - #osint

osint, tor, darknet, mastodon, github

In round 3, the competitors had to use OSINT techniques to find the attackers:

25 players could solve all questions in this round.

Round 4 - #mobile

apk, android, root detection, native library, api

Round 4 was about analyzing the mobile application of the threat actor:

23 players could solve all questions in this round.

Round 5 - #web

sqli, decryption

In last online round the goal was to hack the attackers infrastructure to obtain the decryption key:

The offline final (round 6)

The TOP 40 players were invited to the 6th round which was a live final round from which 29 players participated. We decided to create a small CTF event during the 40 minutes allocated for the final. A CTFd infrastructure was deployed to AWS and the competitors could access the challenges through CTFd. As 40 minutes is not much time, we decided to create 10 small challenges in the most common CTF categories:

All challenges were open from the start of the final, the award for solving an easier challenges was 2 points, the award for solving a harder challenges was 3 points. Most of the challenges required a few minutes to solve (for an experiences CTF player) which meant that it was not possible to solve all challenges during 40 minutes, but that was our goal. The live scoreboard and the progress of each player was visible for everyone.

The Stream

During the live finals three categories had an online stream and commentary: Accessibility of websites, Python, and IT Security.

Gergő and Kristóf represented Ukatemi and Szilárd Csordás from ITBN was the host. We've talked about IT security, CTF competitions and the CTF ecosystem and also narrated what is happening during the live final by visiting the screens of some players.

You can rewatch the stream here: https://www.youtube.com/watch?v=zD2nK-w08HE

Inside the studio during the live stream
Inside the studio during the live stream

Results of round 6

The best player kristof345 was able to solve all but one challenge (the hard web challenge), which was our expected progress, every player was able to solve at least one challenge and every challenge was solved by at least a few competitors.

Scoreboard of round 6
Scoreboard of round 6

The TOP 10 players were above 10 points which means that they were able to solve at least around 5 challenges, they might be more experiences CTF players. All other players were able to solve around 2-3 challenges, which is also great as getting familiar with the platform, choosing between the tasks and understanging them is not trivial within only 40 minutes. Furthermore, they had to submit all flags to the website of OITM too.

The most solved challenge was surprisingly the easy Java reverse challenge with 22 solve out of the 29 players. The other easy challenges followed: crypto, forensics, web and misc with 13-15 solves.

Most solved challenges during round 6
Most solved challenges during round 6

Solutions of the finals

Crypto - I fly like paper, get high like planes (easy)

In this challenge we get a text file called papirrepulo.txt, containing 3 pairs of numbers. The numbers are labeled as n1, e1, n2, e2, c1 and c2.

Having a little bit of crypto knowledge, we are suspicious, that this will be an RSA challenge.

The first thing we notice is that n1 equals n2, meaning that both moduli are the same. We also see that the public exponents e1 and e2 are different, while their Greatest Common Denominator (GCD) is 1. We also have two different ciphertexts c1 and c2.

At this point it is clear that we need to use a Common Modulus Attack to solve this challenge without factoring n.

The Common Modulus Attack is a well documented attack, we can find several resources explaining every detail, with even example solving scripts. Without much explanation, we can quickly write a python script based on the resources found, to solve the challenge:

from Crypto.Util.number import inverse

def egcd(e1, e2):
    if e2 == 0:
        return e1, 1, 0
    g, x1, y1 = egcd(e2, e1 % e2)
    return g, y1, x1 - (e1 // e2) * y1

def common_modulus_attack(n, e1, e2, c1, c2):
    g, a, b = egcd(e1, e2)
    print(a,b)

    if g != 1:
        raise ValueError("Exponents are not coprime")

    if a < 0:
        c1 = inverse(c1, n)
        a = -a

    if b < 0:
        c2 = inverse(c2, n)
        b = -b

    m = (pow(c1, a, n) * pow(c2, b, n)) % n

    plaintext = m.to_bytes((m.bit_length() + 7) // 8, "big")
    return plaintext

Crypto - TODO test this! (hard)

For this challenge, we are provided with two files. The first is called messages.txt and contains 5 lines of timestamps and base64 encoded strings. The second file is called exfiltrate_secrets_final_2.py, which is a Python script, that connects to a server and sends out encrypted messages to it. The description of the challenge also mentions, that the encryption script contains an error, and we might not be able to decrypt the messages.

Upon closer inspection the encryption of the messages is the following: For every message, at the time of sending the message, a new random number generator is seeded with the current time, and then a XOR key is generated using this random number generator. Then the encrypted message is base64 encoded, and concatented to the current timestamp.

However, the error in the script is that the timestamp only contains the minutes, and anything beyond that is not sent to the server.

In summary, we have down to the minutes of when each random number is seeded with the current timestamp, and we have to bruteforce the rest of the timestamp to restore the original messages.

For this task we can create the following script:

import random
import time
import base64

def XOR_decrypt(ciphertext: bytes, key):
    decrypted_message = bytes(b ^ k for b, k in zip(ciphertext, key))
    try:
        return decrypted_message.decode('utf-8')
    except:
        pass

def decrypt_message(ciphertext: bytes, timestamp):
    for n in range(60):
        random.seed(timestamp+n)
        key = bytes(random.randint(0, 255) for _ in range(len(ciphertext)))
        decrypted_message = XOR_decrypt(ciphertext, key)
        if decrypted_message is not None:
            print(decrypted_message)

def process_messages():
    for line in messages.splitlines():
        datetime, encoded_message = line.split(' - ')
        timestamp = int(time.mktime(time.strptime(datetime, '%Y.%m.%d %H:%M')))
        encrypted_message = base64.b64decode(encoded_message)
        print(f'{timestamp}~{encrypted_message}')
        decrypt_message(encrypted_message, timestamp)

Forensics - Data exfiltration (easy)

In this challenge we are given a network packet capture file called capture.pcapng.

Opening and inspecting the file reveals that it has mostly DNS and TLS traffic in it.

The protocol hierarchy in the capture file
The protocol hierarchy in the capture file

Hoping that the flag is in the DNS data and not in the TLS, we filter the packets to the DNS traffic with the dns display filter.

The DNS traffic in the pcap file
The DNS traffic in the pcap file

Visually inspecting the remaining packages we can spot a pattern of many DNS requests for ukatemi.com, with every request querying a different, one character long subdomain. Which is even more promising that the first few subdomain characters are the following: o, i, t, m, {.

We can extend our display filter, to only contain these interesting packages: dns contains "ukatemi" && ip.src == 10.0.2.15. Now the only remaining task is to read the subdomain characters one after another.

Forensics - Oopsie daisy (hard)

In this challenge we receive a QEMU disk image file called ntfs.qcow2. Our task, according to the description of the challenge is to restore some files from this filesystem.

Based on the name of the file, we can assume that the file contains an NTFS file system.

First we need to access the filesystem, which is achieved by connecting the given file to a QEMU Disk Network Block Device Server. The command for this is the following: sudo qemu-nbd --connect=/dev/nbd0 ./ntfs.qcow2.

For NTFS volumes there is a tool called ntfsundelete, with which we can try to restore any previously deleted file. The command for this is as follows: sudo ntfsundelete -u -m '*' /dev/nbd0p1. When running this command we are greeted with an output, stating that one deleted file was restored, called secret.txt.gz. Extracting the GZIP archive reveals the flag.

Misc - What does the owl say? (easy)

For this challenge we get a message.wav file, which - according to the description of the challenge - is a desperate sound recorded by a listening device.

Listening to the file, we can hear an intresting beeping sound, and some strange noise under it.

Our best bet for an easy misc challenge with a .wav file in it, is to open the file in Audacity. Maybe we get lucky and spot our next clue in the waveform or the spectrogram view of the file.

And just by selecting the spectrogram view, we can already see the flag in its full glory.

The flag displayed in the spectrogram view of Audacity
The flag displayed in the spectrogram view of Audacity

Misc - Titkos üzenet száll a széllel / A secret message is carried on the wind (hard)

In this challenge we are provided a fenykep.jpg file, which depicts an animated owl cosplaying Sherlock Holmes.

The image file of the challenge
The image file of the challenge

After checking the very basic CTF checklist when it comes to misc challenges (file, strings, exif, binwalk), we steer in the direction of steganography tools and techniques. StegOnline is a great image steganography tool available as a web application, in which we start by inspecting the color and the bit planes of the image. Upon browsing through the bit planes, we get a weird phenomenon in the blue 0 bit plane.

The blue 0 bit plane contains some weird pattern
The blue 0 bit plane contains some weird pattern

This is a plane with a repeating, lower entropy pattern at the top. This usually means there is some data embedded in the image, into the bitplane with the phenomenon.

To extract the data we can also use the online tool called StegOnline, or we can just as easily write a quick Python script using the Pillow package.

The extracted data is another image file, visually containing the flag.

Reverse - Flag a javából / Flag from Java (easy)

In this challenge we are given a flag_check.jar file.

JADX-GUI is a Dex to Java decompiler, which can decompile the JAR into readable code.

The decompiled Java code shown in JADX-GUI
The decompiled Java code shown in JADX-GUI

The encoded flag uses a simple XOR encryption, so the same value which is used for the encryption can also be used for the decryption.

Example decode script in python:

flag = [69, 67, 94, 71, 81, 126, 66, 79, 117, 75, 68, 89, 93, 79, 88, 117, 67, 89, 117, 26, 82, 24, 107, 87]
print(''.join(chr(i^42) for i in flag))

Reverse - Útvesztő / Maze (hard)

The challenge starts with a binary file called maze.

The binary does not use packing/obfuscation techniques, and the compilation was done without the use of optimization. The symbol table is left intact, and most of the logic in the decompiled code is very similar to the source code.

Running the binary, or by decompiling it and checking out the print_help_and_exit function, we can see that the executable expects a 15 character long command line argument consisting of the characters u, d, l and r. Examining the code further we can deduce that these are directions used to navigate a maze, and by providing the correct input sequence, we can reach the end of the maze and get our flag, which itself consists of the input sequence.

To get the flag, we must understand how the map is stored and how the correctness of the input is checked. The latter is handled by the take_step function.

By examining this function we can deduce that the map is a 6x6 grid stored in a 36 size array. From the start point (index 0) we must reach the end point (marked by the value 2) without moving out of the grid and without hitting any walls (marked by the value 1).

The next step is to find the values of the map array. It's a global variable stored in the .data segment, which can be easily read using either Ghidra or gdb.

Ghidra will most likely interpret it as a byte array instead of an int array, so it must be cast to an int array first to read the values correctly.

The values can also be read using gdb:

(gdb) p &map
$1 = ( *) 0x4040 
(gdb) p * 0x4040@36
$2 = {0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 2, 0, 1, 1}

The correct path can be easily seen by transforming the array into a 6x6 grid:

0 0 1 1 1 1
1 0 1 0 0 0
1 0 0 0 1 0
1 1 1 1 0 0
1 1 1 0 0 1
1 1 2 0 1 1

Since we start at the top left (index 0), the correct path is rddrrurrddldldl. We can get the flag by putting this into the oitm{...} format (based on how the flag is printed by the program) or by running the binary with the sequence.

Web - Kedves naplóm / My dear diary (easy)

We are provided a URL to a web application.

We can login using the credentials provided in the challenge description.

Our session information is stored in a JWT cookie. If we put it into a JWT decoder (or just decode the base64), we can see that the signature is missing, and the signing algorithm is set to none.

The note after logging in gives us a hint that there's also an admin user. We can access the admin user's notes by changing the username claim in the token to admin and refreshing the page. Here one of the notes contains the flag.

Web - Irattár / Archives (hard)

We are provided a URL to a web application.

Clicking the List Files button we get 5 files. They are not too useful, only HELPME.txt gives us a hint that an "important file" (the flag) is most likely in the parent folder of static/files/public/.

We can find the REST API endpoint /api/list_public either in the javascript code, or by intercepting the API call when the button is clicked. The request contains the parameter page=1, but in the response max_page is set to 2. Calling the endpoint with page=2 we can find the swagger.yml file. We can download it by going to /static/files/public/swagger.yml.

In the swagger we can find an additional root_dir parameter, and an endpoint called list_private.

Experimenting with the root_dir parameter we can find two important discoveries:

Unfortunately we don't know the absolute path of static/files directory.

Calling the /api/list_private endpoint will only return an error message:

{"message": "Directory '/app_2Ls9qpPF/static/files/private' does not exist"}

What a delightful turning of events! There's the absolute path! We can call /api/list_public with root_dir=/app_2Ls9qpPF/static/files to get the name of the flag file:

{
  "files": [
    { "name": "flag_KP7Rev2v.txt" },
    { "name": "public" }
  ],
  "max_page": 1
}

And then read the flag by accessing /static/files/flag_KP7Rev2v.txt.

Award ceremony and results

The award ceremony of the whole OITM competition was held on the evening of February 6th.

The best players through all of the 6 rounds in the IT Security category were:

  1. Alex Hornyai
  2. Máté Szén
  3. Bence Kádár-Szél

Talking about Ukatemi and the created challenges
Talking about Ukatemi and the created challenges
The TOP 3 players of the IT Security category
The TOP 3 players of the IT Security category

Congratulations to the winners and all other competitors and thank you for participating in our category!

Our history with OITM

Employees of Ukatemi have been participating in OITM since 2019. In the last 7 competitions we have won several awards, most notably:

Closing thoughts

Three things we learned as organizers:

  1. It takes much more time to create tasks than to solve them. Compared to previous years and other competitions, we wanted to create tasks that were of the highest professional quality, that could satisfy a diverse audience, that covered a sufficient range of topics, and that were crystal clear. There is nothing worse than someone losing points because of the ambiguity of a challenge.
  2. AI is a tool, not a solution. In our work, we have also found that AI-based, automated testing does not provide real protection. It can speed up answers to some simpler questions (which is important and good, of course), but human creativity, experience, passion, thirst for knowledge, and expertise deliver much more reliable results. The contestants illustrated this perfectly.
  3. It feels great to belong to communities based on professionalism. The organizers, other partner companies, the competitors were all a great source of inspiration. In all the aspects of our work, we look for similar communities, in Hungary and abroad too.

Ukatemi Team at the award ceremony
Ukatemi Team at the award ceremony

Many thanks to Human Priority who is the creator of the competition and namely many thanks to the organizers - Gellért Pulay, Levente László, Barnabás Varga - for choosing us and for making this unique event!

Want to message us? Contact us: blog@ukatemi.com