Blog

August 12, 2020

Black Hat USA 2020 Hacking Puzzle Solutions

Written by: Austin Jackson

During Black Hat USA 2020 we here at Cyborg Security released into the ether of the internet a cyber security hacking puzzle, similar to a CTF challenge. We had a couple hundred participants and those that completed various stages of the puzzle won prizes! We gave away 100 Cyborg Security stickers, 10 Cyborg Security t-shirts, and 1 lucky grand winner won an Apple iPad with a Cyborg Security engraving. Thank you very much for all of those who participated in our first hacking puzzle. The solutions to all stages of the puzzle are detailed below. We look forward to doing similar events in the future.

Part 0 – QR Code

Scanning the QR Code began the puzzle, which contains the starting link: http://45.79.17.44

Part 1 – i_w4nt_t0_b3_4_cyb0rg

Browsing to http://45.79.17.44 showed a mysterious webpage:

After sending a DM to @cyb0rgsecur1ty with i_w4nt_t0_b3_4_cyb0rg a password was given back cyb0rg_c4ts_4r3_b3st_c4ts. This password is needed for part 3.

Part 2 – metacatismeta

By taking the advice to “FOLLOW THE WHITE KITTY”, the image above (metacatismeta.png) should be analyzed.

Inspecting the Exif metadata of the image using exiftool (or similar) gives a link to a 7-zip archive at: http://45.79.17.44/d0wn_th3_r4bb1t_h0l3.7z

Part 3 – d0wn_th3_r4bb1t_h0l3

Using the password from part 1, it can be used to extract the contents of the 7-zip archive found in part 2.

$ wget http://45.79.17.44/d0wn_th3_r4bb1t_h0l3.7z
...

$ 7z x d0wn_th3_r4bb1t_h0l3.7z -pcyb0rg_c4ts_4r3_b3st_c4ts
...

$ ls d0wn_th3_r4bb1t_h0l3/
4lph4b3t_bo1z.pcap
FREESTUFFS.txt
new_xxd_who_dis

The first 100 people to get this far won a free Cyborg Security sticker:

$ cat d0wn_th3_r4bb1t_h0l3/FREESTUFFS.txt
Congrats on making it this far!!!
The first 100 people to DM us at @cyb0rgsecur1ty with "pls_g1v3_th1s_h4x0r_fr33_st00fz" will receive a free Cyborg Security sticker! Please include a complete mailing address in your DM.

There's more files in this archive, keep going to win more free stuff.

Part 4 – 4lph4b3t_bo1z.pcap

The extracted archive from part 3 contains a PCAP file, 4lph4b3t_bo1z.pcap. This is a packet capture analysis challenge. The PCAP file can be opened with a tool like Wireshark to view the packets. It contains 26 HTTP GET requests (one for each letter of the alphabet), all for the same file name:

These files can be easily extracted from the PCAP by doing: File -> Export Objects -> HTTP -> Save All. While these files all have the same name, one of these files is different and can be determined by analyzing file size or hash. This unique file will then need to be analyzed further.

Part 5 – my_lips_are_sealed

The correct my_lips_are_sealed file extracted from the PCAP in part 4 is an Linux ELF:

Upon running the program, an unfriendly cyborg is presented:

This is a reverse engineering challenge. Opening the binary in IDA (or similar), the disassembly of the main function looks like so:

Notice a few things in this disassembly:

  • There’s a call to fgets which takes in some user input
  • This user input is then passed to an interesting function, what_is_my_purpose
  • Based on the return value of what_is_my_purpose the program either returns a good or bad output

Based on this information, the next logical step is to view the disassembly of the what_is_my_purpose function. Immediately observable in the disassembly is a large number of mov instructions creating an array of data:

The logic after this data can be more easily understood via decompilation (this is from Ghidra):

The variable param_1 is the user input from fgets and local_a8 is the aforementioned array of data. The length of the user input is decremented as it iterates over the user input. During the iteration of the user input, string characters are compared with the data array by integer value. Because the loop starts from the length of the user input and is decrementing, the string is being compared in reverse.

Therefore, this array of data can be reversed and converted to characters to reveal the password (Python example):

>>> array = [51, 114, 48, 109, 121, 110, 52, 95, 114, 51, 116, 116, 117, 98, 95, 51, 104, 116, 95, 115, 115, 52, 112, 95, 48, 116, 95, 116, 110, 52, 119, 95, 116, 110, 48, 100, 95, 49]
>>> ''.join([chr(i) for i in array[::-1]])
'1_d0nt_w4nt_t0_p4ss_th3_butt3r_4nym0r3'

This password seems to finally appease the cyborg:

Part 6 – new_xxd_who_dis

The other file from the d0wn_th3_r4bb1t_h0l3.7z archive in part 3 is the output from an xxd hex dump:

$ head -n 5 new_xxd_who_dis 
00000000: 504b 0304 0a00 0000 0000 9a53 f850 0000  PK.........S.P..
00000010: 0000 0000 0000 0000 0000 0800 1c00 7465  ..............te
00000020: 7374 696e 672f 5554 0900 0333 fe1a 5f34  sting/UT...3.._4
00000030: fe1a 5f75 780b 0001 04e8 0300 0004 e803  .._ux...........
00000040: 0000 504b 0304 0a00 0900 0000 9d53 f850  ..PK.........S.P

The ASCII file header PK indicated that this file is a zip archive, which comes from the initials of Phil Katz who created the zip file format. This xxd hex dump can be reversed into the original file by a variety of means, the easiest is to use the -r switch option from xxd:

$ xxd -r new_xxd_who_dis > out.zip

The zip archive is password-protected, and can be extracted using the password found in part 5:

$ unzip -P 1_d0nt_w4nt_t0_p4ss_th3_butt3r_4nym0r3 out.zip
Archive:  out.zip
 extracting: out/jeff.png

Part 7 – jeff

The extracted zip archive from part 6 contains an image, jeff.png:

In analyzing the image, binwalk will reveal a zip file contained within the image:

$ binwalk jeff.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 459 x 430, 8-bit/color RGB, non-interlaced
13444         0x3484          Zip archive data, at least v1.0 to extract, name: brut3_m3_pl5_d1g1tz_0nly/
13507         0x34C3          Zip archive data, encrypted at least v1.0 to extract, compressed size: 17, uncompressed size: 5, name: brut3_m3_pl5_d1g1tz_0nly/brut3_m3_pl5_d1g1tz_0nly.txt
13767         0x35C7          End of Zip archive, footer length: 22

The zip file can be extracted using binwalk --extract jeff.png. The zip file is password-protected, and can be extracted using the password from the original image (t4k3_m3_t0_y0ur_l34d3r):

$ ls _jeff2.png.extracted/*.zip
3484.zip

$ unzip -P t4k3_m3_t0_y0ur_l34d3r 3484.zip
Archive:  3484.zip
 extracting: 3484/MOREFREESTUFFS.txt
 extracting: 3484/brut3_m3_pl5_d1g1tz_0nly.zip

The first 10 people to get this far won a free Cyborg Security shirt:

$ cat MOREFREESTUFFS.txt
Congrats on making it this far!!!

The first 10 people to DM us at @cyb0rgsecur1ty with "1m_c0ld_c4n_h45_cyb0rg_cl0thz_pl0x" will receive a free Cyborg Security shirt! Please include a complete mailing address in your DM.

Part 8 – brut3_m3_pl5_d1g1tz_0nly.zip

The zip file brut3_m3_pl5_d1g1tz_0nly.zip extracted from part 7 is password-protected with the only clue being the file name. The file is begging to be brute-forced using only digits. This can be done with John the Ripper (or similar) password cracker:

$ zip2john brut3_m3_pl5_d1g1tz_0nly.zip > hash.txt

$ john --incremental=digits hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 12 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
72339853041      (brut3_m3_pl5_d1g1tz_0nly.zip/f1nd_th3_cyb0rg_tr34sur3)
1g 0:02:39:04 DONE (2020-07-30 02:21) 0.000104g/s 22967Kp/s 22967Kc/s 22967KC/s 72339894832..72339834956
Use the "--show" option to display all of the cracked passwords reliably
Session completed

The password for the zip file once cracked is discovered to be, 72339853041. Using John with default options, in incremental mode, on a common Intel CPU, cracking this password took about two and a half hours. It would be significantly faster using a GPU, a better CPU, and/or better cracking options.

$ unzip -P 72339853041 brut3_m3_pl5_d1g1tz_0nly.zip
Archive:  brut3_m3_pl5_d1g1tz_0nly.zip
 extracting: brut3_m3_pl5_d1g1tz_0nly/f1nd_th3_cyb0rg_tr34sur3
 extracting: brut3_m3_pl5_d1g1tz_0nly/the_singularity_is_the_end.7z

Part 9 – f1nd_th3_cyb0rg_tr34sur3

By extracting the archive in part 8, the file f1nd_th3_cyb0rg_tr34sur3 is given which is comprised of a many 8-bit binary strings.

$ more f1nd_th3_cyb0rg_tr34sur3
1000111 1000010 1011001 1101011 1010001 1000111 1100011 1111010 111
1100 1001010 1000101 1101001 1101111 1100000 1001101 1000101 111011
 110010 1010000 1000010 1100100 111110 1111101 1000111 110010 10001
01 101101 1011110 1000100 1010010 1001000 1111001 1111100 1111000 1
000010 1000110 1100111 1010000 1110111 1010101 1001000 110110 10101
10 1001110 101101 1000111 1000010 1011001 1101011 1010001 1000111 1
...

Using Python, the file can be opened and the binary strings placed in a list:

$ python
>>> with open('f1nd_th3_cyb0rg_tr34sur3') as f:
...     data = f.read().split()
... 
>>> data
['1000111', '1000010', '1011001', ... ]

These binary strings can then be converted to integers, and look like they might be ASCII characters:

>>> int_list = [int(i, 2) for i in data]
>>> int_list
[71, 66, 89, 107, 81, 71, 99, 122, 124, 74, ... ]

Converted to characters, it’s revealed to be a base85 encoded blob (this can be determined by a variety of means):

>>> encoded_blob = ''.join([chr(i) for i in int_list])
>>> encoded_blob
'GBYkQGcz|JEio`ME;2PBd>}G2E-^DR...'
>>> import base64
>>> decoded_blob = base64.b85decode(encoded_blob).decode()
>>> decoded_blob
'23.1337 -102.25 | 23.1337 -108.25 | ...'
>>> decoded_blob.split(' | ')
['23.1337 -102.25', '23.1337 -108.25', ... ]
>>> len(decoded_blob.split(' | '))
174

This decoded blob contains a list of 174 pairs of numbers. These happen to be longitudes and latitudes. Plotting these coordinates on a map will reveal the password for the last part: IHATETHEHOOMANZ

Part 10 – the_singularity_is_the_end.exe

After extracting the final 7-zip archive using the password found in part 9, a Windows EXE file is given:

$ 7z x the_singularity_is_the_end.7z -pIHATETHEHOOMANZ
...

$ file the_singularity_is_the_end.exe 
the_singularity_is_the_end.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

When this file is executed on Windows, some text is displayed giving a hint about GameBoy games…

This is a reverse engineering challenge. Using a static analysis tool for disassembly and/or decompilation (such as IDA or Ghidra) will quickly reveal that that this binary was written in Golang. By observing the functions in IDA we can see this Go binary is using fmt, base64, and a lz4 compression library from GitHub.

After this there are also ~90 functions labeled “decrypt”:

By viewing the disassembly from the main function entry point, we can see that after the hints are printed out to the screen all of these decrypt functions are called:

These decrypt functions are extremely similar in logic. By looking at the disassembly for one of these decrypt functions we can see a base64 encoded blob (main_blob) is base64 decoded and then lz4 decompressed:

This plaintext, base64 encoded blob can be extracted using strings or IDA with ease:

After this blob is decoded some further logic is applied to the bytes. It might be easier to understand what is happening using a decompiler, this is from Ghidra and is shortened to show the relevant parts:

Notice here that two operations are occurring on the bytes of the decoded blob. First the bytes are XOR’d by the number corresponding to the decrypt function (10 in this case) and after this any byte matching that same number is rewritten to 0. This function doesn’t write anything to a file or return any value, these decrypt functions aren’t actually producing any results. It might be interesting to get the results of these decrypt functions and see if they produce anything useful.

Using this information we can write a quick Golang program that uses the same lz4 library to reproduce the operations of the decrypt functions. This program will need to: take the encoded blob -> base64 decode -> lz4 decompress -> XOR the bytes by a number -> replace any bytes with that number with a null byte. The results of each of these reversed “decrypt” functions (10 thru 99) will then be dumped to a file for further analysis.

package main

import (
    "fmt"
    "io/ioutil"
    "encoding/base64"
    lz4 "github.com/bkaradzic/go-lz4"
)

var blob = "AIAAAPcGZQYqKyI3JDE2ZSorZSgkLiwrImVrAQD4BWUsMWUxKmUxLSBlMyA3PGUgKyFkIQBBAQhlMDwAqBEyLDExIDdlJDEfAPkFayY8J3U3IjYgJjA3dDE8ZTIsMS0gAPoEZQl0Ag0RGhVxARoDdRcaAxd2diAA4SQrIWUsI2U8KjBlJDcgiQAHnwBxa2UjLDc2MR8AejIsKSllIiCAAP0AJCtlDBUkIWRlAgJleXYAAQD9JbaGFUyLqCMjiUgATkY2AMYASQBIAE1UWs3MAEuZiSujmJic3P7+IiYrS6mJmJnc2v78dntFAAQRADSif6QLAA8IAP/bX00ASQBN8gEDFcUBAEAAAMXFJABgKSkpKQ0NCgAABABRDQ25uQ0BAAAIAPABAABVVTk51dU9PVFRvb1VVSIACgQA4CUl1dUVFSUl0dHd3SkpHABkfX19fU1NLAA1XV1lAQB1XV0AAF1dQQEAABAAAGIAZBERfX0RETIAIFVVdAAABgAATgAEBABkdXV1dSUlDgAkOTkKAAYIACIlJSAAIEFBfgBAZWUFBeAAUXV1HR2JAQDALS11dQAAdXU1NbW1TgDAdXW5uQAAPT2JiV1dXAAihYUQAPEIyclJSX19SUnJyT09AAB9fR0d3d25uV0BAOAAALm5hYWFhT09SUmJiSAAAEAAQIWFvb1jAAAQAGC5uUlJSUktAABkAAJgAAAqAAQgAAAwAESJiTk5QAAgAABQAAQGAADOAAQQAALAAKBBQV1dJSXFxSUlOgEAIAAAhAACBAAADgAEGgACJgACcAAA0ABAZWUAAAQAAAYAMTU1vQEAADAAoHV1DQ3BwcHBubkGAEAAAL29CAAEBgBAAAB5eXgAACUCoAUFeXkAALW1zc0iAGDBwc3NtbV4AAAcACK5uQYAChAAAHABIDk5GgAg+fk0AAAQAQAIAARuAGDBwQAAOTnaAQIEAADGAQUNAnBBQUG9vQAAaACg1dXl5aWl1dXJyaQCAFAAAgQAAKABYsHBiYnx8VYAAFAAoMHBgYHh4dHRyckUAAAAAQAIAAIEAACAAATaAAJCAAYgACLh4bACBCAAINXV6AAAoAAAKgBgPT1BQcHBQAAIsAAAogICWgACBgAAIAACCgAABgAgDQ1gAgAKAAAEAELx8YmJoAAgwcEaAAJ2ATEAAAEBACJtbQIBAEABBrACAOAAJX19LwMwZX19sgH2AcXFBQVlZVVVTU1BQQAAWVkyASBZWT4DIG1tJgAPBAADQrq6hYUWAwggAADeACI5OeAAAO4AIsXFBgEA0AAAKAACxAEmxcWgAQAuAAIwAAAgAGI9PcHBvb0gAAIyAjG5uQUBAAAeAAJkASI5OdABAGAAAl4AABYBACAAAEYBAv4AAAoAAMIAAAQAABIAIqWl8AGAwcHd3aWl3d1AAQAYAAYEAABAAFMAAC0t0QEAAhAAAJAAAF4AADAAABIABBAABNAAAhAAALIAAB4AAqAAIMHBogACEABA+fmFhagAAMACABIAAAABACQBAJACYQAABQW9vd0AARADACAAAmwABhABABAAADMFAAACBiAAQOHhHR0OAKAAAMnJFRVlZRUV0AIAEgAAMAAAfgACYAAABAJgTU11dQUFAAICMAUA8gEgZWUgBRlVAQAAwAQgTU0KAgAGAARwAGANDe3t1dVaAAAEAJFNTUlJS0tJSU0CBvAotQG71X2/6qUFZADVVBVERABNX2dWTj30Zb1kANxUAABfZ1biZb97oaUC6qUHpQalY3vEpQVdu0kADwQA/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9NQAAAAAAA="

func main() {
    data, _ := base64.StdEncoding.DecodeString(blob)
    data, _ = lz4.Decode(nil, data)

    for i := 1; i < 100; i++ {
        var new_bytes []byte
        for _, b := range data {
            b_xor := b ^ byte(i)
            if (b_xor == byte(i)) {
                b_xor = 0
            }
            new_bytes = append(new_bytes, b_xor)
        }

        filename := fmt.Sprintf("decrypt%d", i)
        err := ioutil.WriteFile(filename, new_bytes, 0644)
        if err != nil {
            fmt.Println(err)
        }
    }
}

This program will output ~90 files, and by viewing the file type of each of these output files we can see that one file stands out. The decrypt69 function produces a GameBoy ROM!

By changing the file extension to decrypt69.gb and loading it up in a GameBoy emulator we can play the ROM. This is using VisualBoyAdvance for example:

Until Next Time

Thanks everyone who participated by playing our Black Hat USA 2020 hacking puzzle. We look forward to doing similar challenges in the future and giving away more goodies to worthy hackers! So long for now.

About the Author