This post documents Part 1 of my attempt to complete Google CTF: Beginners Quest. If you are uncomfortable with spoilers, please stop reading now.
On this post
Background
Google concluded their Google CTF not too long ago. I didn’t take part, so I thought of giving a go at the Beginners Quest first. I was thinking to myself, “how hard could this be?“—boy was I wrong. It’s not that easy.
The quest has nineteen challenges as shown in the quest map—each color representing a category: purple (misc), green (pwn/pwn-re), yellow (re), and blue (web). Every challenge, if there’s a need—contains an attachment—an archive file with its SHA256 hash as filename.
Click or tap on the circles above to go to the respective challenge and its write-up. If the hyperlink is not working for a challenge, I’ve not worked on it yet. That’s what Part 2 is for.
A special shoutout to ktbonefish, tsuro_ and Pharisaeus. They gave constructive comment and feedback that helped to improve the quality of this write-up.
Letter
Let’s start with the first challenge—Letter. The attachment is here.
First, let’s rename the file as letter.zip
. I’ll do the same for any challenge that comes with an attachment; I’ll download the attachment and rename it as <challenge>.zip
. For example, if the next challenge is Floppy, I’ll rename the attachment as floppy.zip
.
# unzip -l letter.zip
Archive: letter.zip
Length Date Time Name
--------- ---------- ----- ----
59922 1980-00-00 00:00 challenge.pdf
--------- -------
59922 1 file
The file letter.zip
contains a PDF file challenge.pdf
. This is how challenge.pdf
looks like in a modern browser.
The challenge is to read the password. That’s trivial. Select the password field, copy it, and then paste it, say in a terminal.
The flag is CTF{ICanReadDis}
.
Floppy
The attachment is here.
Let’s unzip floppy.zip
.
# unzip -l floppy.zip
Archive: floppy.zip
Length Date Time Name
--------- ---------- ----- ----
1414 1980-00-00 00:00 foo.ico
--------- -------
1414 1 file
There’s more to foo.ico
than meets the eye.
# binwalk foo.ico
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
765 0x2FD Zip archive data, at least v2.0 to extract, compressed size: 123, uncompressed size: 136, name: driver.txt
956 0x3BC Zip archive data, at least v2.0 to extract, compressed size: 214, uncompressed size: 225, name: www.com
1392 0x570 End of Zip archive
We can use unzip
to extract what’s in foo.ico
.
# unzip foo.ico
Archive: foo.ico
warning [foo.ico]: 765 extra bytes at beginning or within zipfile
(attempting to process anyway)
inflating: driver.txt
inflating: www.com
The flag for this challenge is in driver.txt
.
# cat driver.txt
This is the driver for the Aluminum-Key Hardware password storage device.
CTF{qeY80sU6Ktko8BJW}
In case of emergency, run www.com
The flag is CTF{qeY80sU6Ktko8BJW}
.
Floppy 2
There’s no attachment in this challenge. The challenge is basically an exercise in compiling DOSBox debugger and debugging a 16-bit DOS application, for those old enough to recognize the “.com” extension in www.com
.
The trick to enabling debugger in DOSBox is to specify --enable-debug=heavy
during configuration of compile options. Having said that, the steps for compiling DOSBox is beyond the scope of this article.
The debugger will appear beside DOSBox upon execution.
The DOSBox command prompt.
The DOSBox debugger.
The next step is to mount the directory containing www.com
as a virtual C: drive with the MOUNT
command.
Once the virtual drive is mounted, we can start to debug www.com
with the DEBUG
command.
The debugger pauses at the first instruction of the debugged application.
According to Wikipedia, the COM binary format stores all its code and data in one segment. This is clear in the debugger view above—both the code and data segment are at 0x1FE
.
As you can see in the image below, the flag is in display. int 21
accesses the DOS API and the AH
register contains 09h
which is the command to print the string “The Foobanizer9000 is no longer on the OffHub DMZ.” to stdout
.
The flag is CTF{g00do1dDOS-FTW}
.
Moar
There’s no attachment in this challenge. Instead, there’s a hint to connect to moar.ctfcompetition.com
at port 1337 with nc
.
Let’s do that.
The man page of socat
is in display. A common method to execute shell command is to prepend the command with a bang (!).
Awesome.
The flag is in /home/moar/disable_dmz.sh
.
The flag is CTF{SOmething-CATastr0phic}
.
Admin UI
There’s no attachment in this challenge. Instead, there’s a hint to connect to mngmnt-iface.ctfcompetition.com
at port 1337 with nc
.
This is how the interface looks like.
The first clue lies in Option 2 - Read EULA/patch notes as I request for a non-existent file path. The error suggests some kind of directory traversal vulnerability is in place.
I was able to read /etc/passwd
.
If I had to guess, I would say the flag is at /home/user
.
The flag is CTF{I_luv_buggy_sOFtware}
.
Admin UI 2
There’s no attachment in this challenge. Instead, we are to continue from the previous challenge.
The challenge lies in guessing the location of the binary and how to get a pristine copy for reverse engineering. After a couple of rounds of guessing, the binary is at /home/user/main
.
I use the following command to get a pristine copy of main
.
echo -ne '2\n../main\n3\n' \
| nc mngmnt-iface.ctfcompetition.com 1337 \
| sed '9,$!d' \
| head -n -3 > main
Update: You can use the traversal technique to read /proc/self/exe
for the file or /proc/self/cmdline
for the path—both ways are better than guessing.
Here comes the next challenge—reverse engineering. The obvious place to look for password is in function that deal with authentication. We have two such functions: primary_login()
and secondary_login()
.
# readelf -s main | grep login
55: 000000004141456b 221 FUNC GLOBAL DEFAULT 1 _Z13primary_loginv
93: 0000000041414446 293 FUNC GLOBAL DEFAULT 1 _Z15secondary_loginv
Comparison of the first password with the file flag
.
The first password is whatever that’s in the file flag
, which happens to be the flag for Admin UI. The second password is a bit more hidden.
Checking the length of the second password.
Turns out it doesn’t matter what the second password is—as long as it’s thirty-five characters long—you’ll have access to a limited shell.
Well, this still doesn’t give us the flag. We’ve to dig deeper in the memory.
XOR operation with 0xc7
.
This will go on for thirty-five times—at least we know the flag has thirty-five characters.
The encrypted flag is at RSP.
The XOR routine, hidden in the secondary_login
function, encrypts the flag with 0xc7
, and place it at the stack. To get to the bytes at the stack, I place a breakpoint at *secondary_login+229
where we can then examine the bytes with x/35b $rsp
.
Let’s save the output above to dump
.
# cat dump
0x7fffffffde10: 0x84 0x93 0x81 0xbc 0x93 0xb0 0xa8 0x98
0x7fffffffde18: 0x97 0xa6 0xb4 0x94 0xb0 0xa8 0xb5 0x83
0x7fffffffde20: 0xbd 0x98 0x85 0xa2 0xb3 0xb3 0xa2 0xb5
0x7fffffffde28: 0x98 0xb3 0xaf 0xf3 0xa9 0x98 0xf6 0x98
0x7fffffffde30: 0xac 0xf8 0xba
We’ve to XOR the bytes with 0xc7
to retrieve back the flag. To that end, I wrote a script decrypt.sh
to automate this process.
#!/bin/bash
MAGIC=0xc7
BYTES=$(cut -d':' -f2- $1 \
| sed -r -e 's/\s+//g' -e 's/0x//g' \
| tr -d '\n' \
| sed -r 's/(..)/\1 /g')
for b in $BYTES; do
printf "%02x" $((0x$b ^ $MAGIC));
done | xxd -p -r && echo
# ./decrypt.sh dump
CTF{Two_PasSworDz_Better_th4n_1_k?}
The flag is CTF{Two_PasSworDz_Better_th4n_1_k?}
.
OCR is Cool
The attachment is here.
Let’s unzip OCR_is_cool.zip
.
# unzip -l ocr_is_cool.zip
Archive: ocr_is_cool.zip
Length Date Time Name
--------- ---------- ----- ----
141505 1980-00-00 00:00 OCR_is_cool.png
--------- -------
141505 1 file
This is how OCR_is_cool.png
looks like—or rather how the encrypted flag looks like.
I made the assumption that “VMY” represents “CTF” after encryption. Note the curly braces after “VMY”—another good hint. It’s obvious that the contents of the email is not in plaintext, encrypted by some kind of substitution cipher—possibly Caesar cipher.
|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z| |·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|C|·|·|·|·| |·|·|·|·|·|·|·|·|·|·|·|·|T|·|·|·|·|·|·|·|·|·|·|·|·|·| |·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|·|F|·| |H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|A|B|C|D|E|F|G|
The tr
utility is perfect for such one-to-one transformation from SET1 to SET2. To that end, I wrote caesar.sh
, a bash
script wrapped around tr
.
#!/bin/bash
cat $1 | tr 'a-zA-Z' 'h-za-gH-ZA-G'
I made a copy of the flag with OCR and used caesar.sh
to decrypt it.
# ./caesar.sh flag.txt
CTF{caesarcipherisasubstitutioncipher}
The flag is CTF{caesarcipherisasubstitutioncipher}
.
Security by Obscurity
The attachment is here.
Let’s unzip security_by_obscurity.zip
.
# unzip -l security_by_obscurity.zip
Archive: security_by_obscurity.zip
Length Date Time Name
--------- ---------- ----- ----
11100 1980-00-00 00:00 password.x.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.p.o.n.m.l.k.j.i.h.g.f.e.d.c.b.a.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p
--------- -------
11100 1 file
This challenge involves the recursive extraction of different types: zip, xz, bzip2 and gzip, in that order. To that end, I wrote extract.sh
, a bash
script using 7z
as the general extraction utility.
#!/bin/bash
START=$1
while :; do
if file -b $START | grep -Eio '^(zip|xz|bzip2|gzip)' &>/dev/null; then
echo "[+] Extracting $START"
7z e $START &>/dev/null
if [ $? -eq 0 ]; then
START=$(7z l $START | grep -A2 Name | sed '$!d' | awk '{ print $NF }')
continue
else
break
fi
else
break
fi
done
The final extracted file password.x
is a password-protected zip file. Using John the Ripper, I was able to determine the password—asdf
, and the flag is in password.txt
after extraction.
# cat password.txt
CTF{CompressionIsNotEncryption}
The flag is CTF{CompressionIsNotEncryption}
.
JS Safe
The attachment is here.
Let’s unzip js-safe.zip
.
# unzip -l js-safe.zip
Archive: js-safe.zip
Length Date Time Name
--------- ---------- ----- ----
6983 1980-00-00 00:00 js_safe_1.html
--------- -------
6983 1 file
This is how js_safe_1.html
looks like in the browser.
Modern browsers these days come with a JS debugger, and that’s what I’m using to tackle this challenge. Whenever the value of the textbox changes, the JS engine calls the asynchronous function open_safe()
.
We can see from above that the password must match the pattern /^CTF{([[email protected]!?-]+)}$/
to proceed. The challenge lies in determining the password to unlock the safe. And guess what—the password is the flag, judging from the password format.
The string inside CTF{...}
is then supplied as argument to another asynchronous function x()
. This function is the key to determining the password.
The logic of the function x()
is in the long string starting with icff
and ending with ьcee
—encoded. The decoding regime will iterate the string, four characters at a time—where each character represents the index to the property of the env
object. Since we are looking at inline JS, we can always include our own code to decode the function x()
.
I’ve added the above code to display the decoded function in the console. Towards the end of the decoding regime is where the comparison between the supplied hash and the correct hash occurs, XOR
ing them one byte at a time, checking if it evaluates to zero. SHA256 is the cryptographic function used to create a 32-byte hash.
Armed with this knowledge, we can add another round of code—three lines to be exact—to extract the bytes of the correct hash.
Whenever the function is ѡ
, we extract the second member of the first argument, resulting in the following hash getting printed to the console.
We can do a Google search as suggested in the comment of function x()
or we can crack the hash with John the Ripper.
// TODO: check if they can just use Google to get the password once they understand how this works.
In any case, both ways result in the same answer: Passw0rd!
# /opt/john/john --format=raw-sha256 -w:/usr/share/wordlists/rockyou.txt hash.txt
Loaded 1 password hash (Raw-SHA256 [SHA256 128/128 AVX 4x])
Warning: poor OpenMP scalability for this hash type, consider --fork=4
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Passw0rd! (?)
1g 0:00:00:00 DONE (2018-07-07 13:52) 4.761g/s 1404Kp/s 1404Kc/s 1404KC/s bedshaped..redsox45
Use the "--show" option to display all of the cracked passwords reliably
Session completed
The flag is CTF{Passw0rd!}
.