This page looks best with JavaScript enabled

MacAfee ATR CTF 2021

 ·  ☕ 17 min read  ·  🧔🏻 noobintheshell

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:

landing page
landing page

A basic request returns the following:

dns request
dns request

This look like the result of a script. Behind the scene what is probably done run is something like:

$ dns_script <input_name> <input_website>

So we can try to add ;ls after the website name to try to inject system commands:

command injection
command injection

That’s it, we can read the flag with ;cat flag.txt:


MOBILE 100 - Looking for Droids?

We can unzip the APK and then transform the classes.dex file to a jar file with dex2jar:

$ ./ -f -o apk.jar classes.dex

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:

Mr. Anderson
ATR[R2D2] <==

HW 200 - Shack the Secret

We get 2 shady files in the archive:

file types
file types

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

RECON 200 - A Picture is Worth a Thousand Vulns

The image shows a D-LINK AC1200 Range Extender:

D-LINK AC1200 Range Extender
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:

binary file analysis
binary file analysis

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:

kernel version
kernel version

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/

telnetd credentials
telnetd credentials

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

The output.txt contains a huge list of possible passwords:

$ wc -l output.txt
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.

Flag: ATR[1-r-d4-n33dl]

FOR 300 - Not Software, not Hardware

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

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:


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:

$ hashcat -O -m 1800 -a 0 -o found.txt hash.txt rockyou.txt

But we get no result. After having tried with some hashcat rules, we find the right one: leetspeak.rule.

$ hashcat -O -m 1800 -a 0 -o found.txt hash.txt rockyou.txt -r 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:

$ openssl enc -aes256 -in flag.enc -d -a -pbkdf2
enter aes-256-cbc decryption password:
On macOS, the default 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)

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:

$ file challenge
challenge: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 3.2.0, BuildID[sha1]=2caaf9304b646a1f86ff892c534aa1483ae6c350, not stripped

When we launch, it we get:

$ ./challenge
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:

"".join([hex(ord(a)^0x66)[2:].zfill(2) for a in "good job!"])

We can now validate the challenge:

$ ./challenge 01090902460c090447
…transforming 01090902460c090447 to normal string
Decoded string:  F
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

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:

“High Fives All Around!
Now, figure out how to send your solution(s) to the fileserver at”

The details of the first check:

check file1
check file1

The file1 will be:

$ xxd file1
00000000: 460a 4c0a 410a 0a0a 4731 F.L.A…G1

The second check is:

check file2
check file2

The file2 will be:

$ xxd file2
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:

Flag: ATR[h5&HTS]

RF 400 - Smell like ham to you?

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:

morse code
morse code

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:

C:\> .\iqToSharp.exe -f 146520000 -i .\ -o iq2

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:

FM radio signal
FM radio signal

After playing a little with the frequency, we get the following message:

We clearly hear:

the third file was captured at 7.171Mhz and 2.5Msps

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:

C:\> .\iqToSharp.exe -f 7171000 -s 2500000 -i .\ -o iq3

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:

gqrx device configuration
gqrx device configuration

The full device string is:


Then you have to first start the DSP (the start button) before starting the IQ file:

gqrx usage
gqrx usage

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:

SSTV capture
SSTV capture

The flag is: ATR[SSTV]

CRYPTO 100 - Light Switch Crypto

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:

landing page
landing page

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:

<!– Changelog –>
<!– 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:

AES bit flipping attack
AES bit flipping attack

CRYPTO 200 - Know your header (part I)

We get a few files from the given archive. The 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:

$ ./hint
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:

if (len(key) > 8):
   print "This version of the encoder only works with short keys"

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.

ELF header
ELF header

This gives us the key: 9f05a8dd940a15dd. We can try to decode the binary with:

$ ./ 9f05a8dd940a15dd challenge_encoded challenge

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

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:

static analysis
static analysis

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:

heap status
heap status

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:

GOT puts
GOT puts

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:

remote exploit
remote exploit

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:

xor rax, rax
push rax                # string end null byte
push 0x41414141         # username "AAAA"
mov rdi, rsp            # first param in rdi
push rax                # string end null byte
push 0x42424242         # password "BBBB"
mov rsi, rsp            # second param in rsi
mov rax, 0x5555555551b5 # store_credentials address
call rax

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:

Share on

AppSec Engineer and CTFer