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


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.

Letter Floppy Floppy 2 Moar Admin UI Admin UI 2 JS Safe OCR is Cool Security by Obscurity

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. :smile:

A special shoutout to ktbonefish, tsuro_ and Pharisaeus. They gave constructive comment and feedback that helped to improve the quality of this write-up.


Let’s start with the first challenge—Letter. The attachment is here.

First, let’s rename the file as 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

# unzip -l
  Length      Date    Time    Name
---------  ---------- -----   ----
    59922  1980-00-00 00:00   challenge.pdf
---------                     -------
    59922                     1 file

The file 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}.


The attachment is here.

Let’s unzip

# unzip -l
  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

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:
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              

The flag for this challenge is in driver.txt.

# cat driver.txt
This is the driver for the Aluminum-Key Hardware password storage device.

In case of emergency, run

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

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 as a virtual C: drive with the MOUNT command.

Once the virtual drive is mounted, we can start to debug 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}.


There’s no attachment in this challenge. Instead, there’s a hint to connect to 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 (!).


The flag is in /home/moar/

The flag is CTF{SOmething-CATastr0phic}.

Admin UI

There’s no attachment in this challenge. Instead, there’s a hint to connect to 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 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. :laughing:

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 to automate this process.


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
# ./ dump

The flag is CTF{Two_PasSworDz_Better_th4n_1_k?}.

OCR is Cool

The attachment is here.

Let’s unzip

# unzip -l
  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.


The tr utility is perfect for such one-to-one transformation from SET1 to SET2. To that end, I wrote, a bash script wrapped around tr.

cat $1 | tr 'a-zA-Z' 'h-za-gH-ZA-G'

I made a copy of the flag with OCR and used to decrypt it.

# ./ flag.txt

The flag is CTF{caesarcipherisasubstitutioncipher}.

Security by Obscurity

The attachment is here.

Let’s unzip

# unzip -l
  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, a bash script using 7z as the general extraction utility.


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 }')

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

The flag is CTF{CompressionIsNotEncryption}.

JS Safe

The attachment is here.

Let’s unzip

# unzip -l
  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, XORing 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!}.