This was the first CTF organized by McAfee Advanced Threat Research Team. It was held from February 5th, 2021 to February 18th, 2021 and was initially made for their internal employees.
I was able to complete all but one challenge (the crypto One Time Only!) and finished at the 9th place:
Here is my write-up…
WEB 100 - A DNS query to rule them all!
This was the unique web challenge and was a basic command injection. We acces a webpage where we can do DNS queries:
A basic request returns the following:
This look like the result of a script. Behind the scene what is probably done run is something like:
So we can try to add ;ls
after the website name to try to inject system commands:
That’s it, we can read the flag with ;cat flag.txt
:
MOBILE 100 - Looking for Droids?
The following APK is an application that contains encrypted passwords. Your task, should you choose to accept, is to find the flag in one of these passwords.
We can unzip
the APK and then transform the classes.dex
file to a jar
file with dex2jar
:
We can then use jd-gui
to disassemble and view the application source code. The MainActivity.class
shows that the application is protected by the password notthefl@g
. The password is then store in the SECRET
variable and the intent PwdContainer
is triggered:
The PwdContainer.class
shows that multiple strings are decrypted using AES with the password stored in the SECRET
variable:
Finally, the AES.class
shows that the mode used is AES/ECB/PKCS5Padding
and that the key is compose of the first 16 bytes of the password’s SHA-1 hash:
Now we have all the needed information and we can simply re-use the available code to decrypt all the strings:
The output contains our flag:
TheAnswerIsNot42
Password!
Ilovemymom123
!drowssaP
12345
A=(-b+-SQRT(b^2-4Ac)/(2A)
Mr. Anderson
CrashandBurn
ababstart
(GOLDFISH)
ATR[R2D2] <==
HowdoyouknowMypwd?
passwordallcaps
ialreadytoldyou
MyLittlepwny
qwerty123
Super*!
Platypus
GrendalGrendalwho!
HW 200 - Shack the Secret
A researcher has discovered a file that has been encrypted with AES-128-ECB by an embedded device. The encrypted file has been captured through network analysis and the raw file is called Blob. With no debug ports available on the embedded device, one must extract the encryption key. Luckily, we have captured IC bus traffic while exercising functionality.
We get 2 shady files in the archive:
The .logicdata
file can be opened with tools like Salae. We open the file in Saleae and select the I2C protocol as the analyzer with the default settings. We see the password in the decoded protocol window:
The full password is safepasswordcomm
. We can now decrypt the blob
file with the following openssl
command and read the flag:
$ openssl aes-128-ecb -in blob -d -out flag.txt
$ cat flag.txt
ATR[HWHackIsFun]3flagrepeats4youATR[HWHackIsFun]Reallypoorcrypto-ECBsnotforfilesATR[HWHackIsFun]
RECON 200 - A Picture is Worth a Thousand Vulns
The image shows a D-LINK AC1200 Range Extender:
And we access a webpage with a few questions to answer about the product:
We find the answer to the first question on a Tom’s Hardware product review:
By looking at the specs of this SOC/CPU, we get the second answer:
To get the Linux kernel version we have to download the latest firmware. We can get it here. Make sure to download the firmware (1.03.B07) and not a hotfix.
After unzipping the archive we can analyze the binary file with binwalk
:
The firmware files are stored in a Squashfs filesystem. We can extract it with binwalk -e DAP1650_FW103WWb07.bin
. If sasquatch
is installed it will automatically extract the filesystem. If not, we can use unsquashfs
to retrieve all the files.
The filesystem is found under the squashfs-root
folder:
We can usually find the kernel version in files like /proc/version
or /var/log/dmesg
but those files are missing. Another way to find this out is to get the vermagic
value from a kernel module:
By looking at the list of binaries in /usr/bin
we get the fourth answer:
Finally, to find the password used by telnetd
we can search where the deamon is started with grep -Ri telnetd .
and we get a hit in /etc/init0.d/S80telnetd.sh
:
The username is Alphanetworks
and the password is stored in the file /etc/config/image_sign
:
Once we submit all the answers, we get the flag:
FOR 200 - Password in a Haystack
You have acquired the Strings (*nix command) output for a file. You know that a user’s password is somewhere in the file and need to retrieve it. The username is steve557. Find the password based on the password rules. Only one string will match these rules.
– All passwords must be 6-12 printable characters in length.
– Each password must contain at least 3 unique digits
– Passwords cannot contain 3 consecutive characters of your username nor its reverse. This is case insensitive.
The output.txt
contains a huge list of possible passwords:
19242 output.txt
We just need to follow the rules in the description to find a unique password. The following Python code does exactly that:
The ouput is: 1-r-d4-n33dl
.
FOR 300 - Not Software, not Hardware
I grabbed this file from a Wireshark capture of one of my home devices doing an update of some kind. Can you find the flag?
We analyze the binary file with binwalk
:
This is the image of a firmware that contains a squashfs
filesystem. This is similar to what we had in A Picture is Worth a Thousand Vulns challenge. The same procedure can be followed to extract the filesystem.
We find a hidden folder /root/.secret/
that contains the encrypted flag flag.enc
and a README.txt
that says:
This is the directory where I store all of my encrypted files.
I encrypt them using: echo “secret” | openssl enc -aes256 -salt -out file.enc -e -a -pbkdf2 -k ‘PASSWORD’
and decrypt them using: openssl enc -aes256 -in file.enc -out file.txt -d -a -pbkdf2
P.S.
I haven’t gotten a password manager yet but I know my login password is supper secure so I don’t see a need…
From this message, we can guess that the file has been encrypted with a system user. Let’s look that the /etc/shadow
file:
daemon::0:0:99999:7:::
ftp::0:0:99999:7:::
network::0:0:99999:7:::
nobody::0:0:99999:7:::
dnsmasq:x:0:0:99999:7:::
Only root
has a password set. Let’s try to crack it with hashcat
and the rockyou.txt
wordlist. The hash starts with $6$
which means that it is a SHA512 hash and 19yJir3t
is the salt:
But we get no result. After having tried with some hashcat
rules, we find the right one: leetspeak.rule
.
After almost 6h, we get the root
password: P@55w0rd!
. Maybe I should review the wordlists I use!
We can now decrypt the flag with openssl
:
enter aes-256-cbc decryption password:
ATR[F1rMW4r315N750H4rD]
openssl
is LibreSSL and does not support PBKDF2. Use brew install openssl
then make sure to include the binary folder into your PATH
.
REV 300 - Know your header (part II)
When you have solved the second part of “Know your Header”, you can enter the flag here.
You will need to have solved “Know your Header (Part I)” for this challenge. Use the binary from Part I, named “challenge_encoded” to solve this.
To complete this one, we need first to complete the challenge Know your header (part1). We get a challenge
binary that is a Linux binary:
challenge: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2caaf9304b646a1f86ff892c534aa1483ae6c350, not stripped
When we launch, it we get:
To decode the flag, run:
./challenge password_in_hex
Hint: IDA Free and Ghidra are great tools…
So let’s open it in Ghidra. The binary is not stripped so it will be easier to analyze. The main
function is simple:
It calls transform_password()
and if the returned value is good job!
, the flag is printed. The function looks like:
This is a basic XOR encryption and the key is 0x66
. By XORing good job!
with 0x66
we get the password. For instance, in Python:
|
|
We can now validate the challenge:
…transforming 01090902460c090447 to normal string
Decoded string: F
G
Decoded password: good job!
Flag: ATR[(>)xor___xor(>/Z
The last 2 characters were not correct, the final flag is ATR[(>)xor___xor(>)]
.
REV 400 - Two’s Company
File me once, shame on you. File me twice, shame on me!
Let’s use again Ghidra to analyze the binary. All is happening in the function FUN_0001124d
. Two files are opened and checked. If all the checks pass, we get the message:
Now, figure out how to send your solution(s) to the fileserver at http://challenges.ctfd.io:30465/”
The details of the first check:
The file1
will be:
00000000: 460a 4c0a 410a 0a0a 4731 F.L.A…G1
The second check is:
The file2
will be:
00000000: 0046 004c 0041 0000 0000 0047 3200 0000 .F.L.A.....G2...
00000010: 0000 0000 0000 0000 0000 0000 0000 ..............
We upload the 2 files on the server to get the flag:
RF 400 - Smell like ham to you?
You find 3 files of the form #_samplerate_frequency_bandwidth.iq:
- [1_2Msps_50.090MHz_2MHz.iq.tar.bz2
- 2_xMsps_xMHz_xMHz.iq.tar.bz2
- 3_xMsps_xMHz_xMHz.iq.tar.bz2
These files were captured over the air via a software-defined radio. You have a suspicion these broadcasts contain valuable information. As such, you have decided to try to determine the contents of each file.
We start with the first file. We have an IQ file that is the raw data of a radio capture. We have some specs of the signal in the filename. We know that it has been captured at a frequency of 50.09Mhz and than the sampling rate is of 2Msps. We can import the IQ file in Audacity for analysis. Click the menu File -> Import -> Raw Data
then simply change the sampling rate to 2Mhz. We play the file and quickly recognize Morse code. We can even “see” it:
The Morse code is:
Once decoded we get the specs of the second signal: sample 2 was captured at 146520khz samp rate 2M
.
For the second file, I had some issues importing it correctly into Audacity. Seems like it does not handle high frequencies very well. The solution I found was to convert the IQ file to WAVE with a Windows tool called iqToSharp
:
Back into Aucacity but nothing obvious came out. I played quite a long time with effects until I found out that I could hear some voice when playing it at four times the normal speed. But it was hardly audible and I could not make any sense of it. I exported accelerated sound and I tried some more tools. Finally, another Windows tool, SDRSharp, made it clearer by using the FM mode:
After playing a little with the frequency, we get the following message:
We clearly hear:
The third capture was trickier to get. By searching online, we quickly get that the frequency used is the one used by Slow-scan television (SSTV). We start by converting the IQ file to WAVE:
I tried many different tools for this one like Audacity, SDRSharp, qsstv, gnuradio and mmsstv …and failed for days. In the end, what worked was a combination of gqrx to play the sound and the Android application Robot36 to capture it.
You can follow this procedure to install gqrx on an Ubuntu box or use the macOS package. This is the device configuration I used:
The full device string is:
Then you have to first start the DSP (the start button) before starting the IQ file:
We start our mobile app in auto mode and play with the frequency in gqrx to find the right spot where we start to see an image. At around 7.1773kHz, the mode switched to Scottie1 and we started to see something:
The flag is: ATR[SSTV]
CRYPTO 100 - Light Switch Crypto
Here is a 3-way light switch. Look closely, it may help you uncover the secret message! Solve the secretly coded message to capture the flag!
We are given an encrypted flag, a key file and a piece of Python code that we need to complete in order to decrypt the flag. With that, comes an image that explains how the flag is encrypted:
We have as well some instructions in the source code:
If we take the rules of the circuits one by one:
- Circuit 1 is an OR gate
- Circuit 2 is an NAND gate
- Circuit 3 is an AND gate
The overall circuits correspond to an XOR gate and the only line we need to add to the magic_func
is o = A^B
. The output of the script is:
CRYPTO 200 - IVe seen sites like this before
The website shows that we don’t have enough privileges to access it:
The URL we are redirected to is:
In the source code we get some comments that leak that the session ID is based on AES-256-CBC:
<!– 09/15/2018 - Changed from DES to AES-256-CBC after our latest data breach –>
<!– 05/23/2018 - Todd is and idiot and somehow never knows how to login so I have added the encryption IV and the encrypted session to the URL parameters –>
<!– 03/12/2018 - String compare of the users is acting up I found a different way to validate users –>
<!– 02/08/2018 - Moved the default user from root to unprivlaged users. This way only admins can access this page –>
We can try to play with the IV value and flip some bits to see how it impacts the decryption. We can automate this with Burp Intruder and the Bit Flipper payload. We launch the attack and see a call that returns something different. If we look at the response, we see that the UID changed to 0 and we got the flag:
CRYPTO 200 - Know your header (part I)
The challenge was encrypted, but we have the encryption script. Can you figure out the key?
For this challenge, the flag is the key to decrypt the next stage “Know your header (part II)”. The challenge will become available if you solve part I.
We get a few files from the given archive. The README.md
says:
The next challenge was encoded and we don’t know the key.
We were able to recover the script used to encode the challenge and a hint binary.
What we know is that it is a Linux binary running on a x64 Ubuntu 18.04.
Maybe by looking at the encoding algorithm and the hint binary you can figure out how to decode it?
The flag for this challenge is the ATR[key] where key is the key you found, in hex, and lower case
(for example, if the key is 5566778899aabbcc, the flag will be ATR[5566778899aabbcc])
Good luck!
We get as well the encrypted binary called challenge_encoded
and a hint
binary. When launched, the binary outputs:
I do nothing but I still can be helpful….
Did you know all linux binaries have a ELF header ?
We all start the same, isn’t that cool?
The last piece of information is the source code used to encrypt the binary. We get that the key is <=8 characters:
|
|
Knowing what the ELF first 8 bytes are, we can simply XOR them with the first 8 bytes of the encrypted binary to get the key. If this does not work, we try with the first 7 bytes..and so on.
This gives us the key: 9f05a8dd940a15dd
. We can try to decode the binary with:
And 8 was the right size. The flag is: ATR[9f05a8dd940a15dd]
.
This unlocks the second part of the challenge: Know your header (part II)
PWN 500 - A Winning Attitude
This binary has a function named “winner” which is never executed. Your task is to find a vulnerability in the binary and leverage it to execute “winner”. When “winner” is executed the flag will be sent to you.
challenges.ctfd.io:30461
The link above is running the executable and you can connect to it with netcat. Please use it to retrieve the flag after you get your exploit working on the binary.
We get an ELF binary with the following protections:
- Partial RELRO sets the
.got
section as read-only, however, the section.got.plt
(PLT-dependent GOT) stays writable. You can find here a good explanation of those different binary sections, - NX sets the stack as non-executable.
When we execute the binary, we can send 2 strings and we receive an acknowledgement each time:
Let’s open the binary in Ghidra. The binary is not stripped so this will be easier to analyze. The main
application looks like:
We have a buffer overflow on the heap. With the first overflow, we have control over the address where the second strcpy
call will write. The idea is to write the address of the winner
function in place of the address of a system call in the GOT. We can for instance replace the address of puts
that is called right after.
Let’s disable ASLR and have a look at the heap with gdb
:
The last information we need to build our exploit is the address of puts
in the GOT and of the winner
function we have to call:
We can first test the exploit locally. For that we can create a file flag.txt
with any content and see if our exploit outputs its content. Then we launch it on the remote service. The full exploit is:
We finally get the flag:
PWN 500 - Shellcode Hollaback
We access the folowing webpage:
Ok, so we need to call the store_credentials
function located at 0x5555555551b5
and that takes 2 buffers as argument for the username and password. We are on a 64-bit Linux and have a limitation of 64 bytes for the shellcode.
Knowing that the calling convention for 64-bit is the following:
- first function argument must be stored in
rdi
, - second function argument must be stored in
rsi
,
we come up with the following assembly code:
|
|
To transform this into hex bytes we can use the shell-storm online assembler. We get the following shellcode (33 bytes) that we can pass to the webpage:
We are successful and our credentials were injected into the database. We can log in to get the flag: