So I’ve passed the first episode (and got a token with my name in reward!). I watched the chapter’s video (very well produced, by the way), and I expect some reverse engineering efforts. Anyway, let’s dig in.

Clicking on the challenge downloads a file with a really long name cec5317acaa111092eef6da3df8e260dccd69ce8b17aa445a26a7a6771f972301ac3ff20108cf86aa868da1463e486347114e0456ba5b5ca2a3a399f69391e76.

As should be the norm for unknown files, running the file utility on them almost always helps.

tal@Tal:~$ file cec5317acaa111092eef6da3df8e260dccd69ce8b17aa445a26a7a6771f972301ac3ff20108cf86aa868da1463e486347114e0456ba5b5ca2a3a399f69391e76

cec5317acaa111092eef6da3df8e260dccd69ce8b17aa445a26a7a6771f972301ac3ff20108cf86aa868da1463e486347114e0456ba5b5ca2a3a399f69391e76: Zip archive data, at least v2.0 to extract, compression method=store

Alright. Let’s open it. Within the zip is another tar-gzip file which I also decompressed (tar -xvf challenge.tar.gz) which resulted in two files, flag and wannacry. Running the file utility on them both yielded additional info:

tal@Tal:~$ file wannacry

wannacry: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=IGPSbKhPf45BQqlR84-9/XWC3eVS4fozNp9uK4nDp/_Styn3U-Z8S6ExnY6QOR/RTzNS5QnFmUHeSBeyHIu, with debug_info, not stripped

While the flag file wasn’t detected as anything meaningful. Cool, so it’s time to jump into the binary.

I opened it in IDA 8.3 (which is given as freeware with cloud-based decompiling - recommended!), and told IDA to freely use the debug information that was found, embedded into the file. It should help greatly since I’ll have function names and whatnot.

IDA threw me into a main_main function. I happened to reverse a compiled Go program in the past, and it’s not too much fun - IDA completely fails on understanding the structures of objects like the built-in string, and it messes up the decompiler. Also, even the disassembly can confuse IDA in some cases - the stack cookie check is notorious for making IDA unsuccessfully parse functions.

Either way, at the beginning of the function, there’s a call to a main_impossible. Without reversing it, I noticed that if it returns a non-zero output, the code calls println to stdout. But what does it print?

.text:0000000000509498    call    main_impossible ; Call Procedure
.text:0000000000509498    
.text:000000000050949D    nop     dword ptr [rax] ; No Operation
.text:00000000005094A0    test    al, al          ; Logical Compare
.text:00000000005094A2    jz      short loc_5094EE ; Jump if Zero (ZF=1)
.text:00000000005094AA    mov     rax, cs:main_site ; val
...
.text:00000000005094B8    call    runtime_convTstring ; Call Procedure
...
.text:00000000005094E9    call    fmt_Fprintln    ; Call Procedure

Looks like a string object named main_site, which points to offset 0x53A7A4 and of length 0x4F. The string at that address turned out to be Keys are here:\nhttps://wannacry-keys-dot-gweb-h4ck1ng-g00gl3.uc.r.appspot.com/\n.

Going to this URL, I was met with a list of 200 .pem files. Clicking on one of them results in a valid PEM-formatted private key, okay…

Back to the binray, after the call to main_impossible that prints the aforementioend URL, I noticed that the code accesses some global variables from the .bss section of the executable, like os_Args, flag_CommandLine, main_keyFile and main_encryptedFile.

.text:0000000000509534    mov     rdx, cs:main_keyFile
.text:000000000050953B    mov     rbx, [rdx+8]
.text:000000000050953F    mov     rax, [rdx]
.text:0000000000509542    test    rbx, rbx        ; Logical Compare
.text:0000000000509545    jz      short loc_509555 ; Jump if Zero (ZF=1)
.text:0000000000509547    mov     rcx, cs:main_encryptedFile
.text:000000000050954E    cmp     qword ptr [rcx+8], 0 ; Compare Two Operands
.text:0000000000509553    jnz     short loc_50956C ; Jump if Not Zero (ZF=0)

I wanted to know where they’re initialized and looked at their cross references. Looks like there are 3 init functions os_init, flag_init and main_init that initialize them and are probably called before main_main. I looked at main_init specifically, finding that the objects main_keyFile and main_encryptedFile contain name, usage and value members behind the scenes.

Looking at the initialization flow, main_keyFile is initalized with the name "key_file", usage "File name of the private key" and an empty value, while main_encryptedFile is initialzied with the name "encrypted_file", usage "File name to decrypt" and an empty value too.

.text:00000000005096EC    lea     rbx, aEncryptedFile ; name="encrypted_file"
.text:00000000005096F3    mov     ecx, 14         ; name_length=14
.text:00000000005096F8    xor     edi, edi        ; value=null
.text:00000000005096FA    xor     esi, esi        ; value_length=0
.text:00000000005096FC    lea     r8, aFileNameToDecr ; usage="File name to decrypt."
.text:0000000000509703    mov     r9d, 21         ; usage_length=21
.text:0000000000509709    call    flag__ptr_FlagSet_String ; creates object, return in eax 
.text:0000000000509717    mov     cs:main_encryptedFile, rax

Alright, now that I know that, back to main_main I go. As seen in the code above, I realized that the check on offset 8 of main_encryptedFile (and later also at the same offset of main_keyFile) verifies that its value isn’t empty. If any of them is, the code calls a function taking from the global variable flag_Usage.

Seeing this it’s enough for me to simply go and run the executable - since seems like it’s designed for command-line usage.

tal@Tal:~$ chmod +x wannacry & ./wannacry

Usage of ./wannacry:
  -encrypted_file string
        File name to decrypt.
  -key_file string
        File name of the private key.

Ah, very nice, seems like the objects I looked at above are a special kind of objects used to take input from the command line. The value of the objects above are probably the string supplied by the user.

Skimming forwards in the main_main function, I noticed that there’s a call to main_readKey using the main_keyFile object’s value, and then a os_ReadFile call using the main_encryptedFile object’s value. The output of these calls are saved into local variables key and data respectively. Shortly after, a call to main_decrypt is made with key and data being two paramteres passed into it.

.text:00000000005095FE    mov     rbx, [rsp+data] ; data                        
.text:0000000000509603    mov     rdi, [rsp+key]  ; key
.text:0000000000509608    call    main_decrypt    ; Call Procedure

Peeking at the function list, a lot of crypto-related functions stand out. Looks like a cryptographic library was compiled into this binary. The functions imported are mostly related to elliptic-curve cryptography, which main_decrypt probably utilizes.

At this point it’s pretty clear the flag was encrypted using one version or another of ECDSA. I have those 200 keys given at the website. Well, it’s not too many - I thought and decided to simply try them all.

import requests

DOMAIN = 'https://wannacry-keys-dot-gweb-h4ck1ng-g00gl3.uc.r.appspot.com'
KEYS = ['01087458-4d66-4677-af0d-da2024cc2111.pem', '02bdbf0d-48c6-4fb5-b5d2-71be3f4f071f.pem', # ...
]

for key in KEYS:
    res = requests.get(DOMAIN + "/" + key)
    assert(res.status_code == 200)
    with open("keys/" + key, "wb") as f:
        f.write(res.content)

Grabbing a drink and a minute later, I had all the keys saved to the keys folder. Time to loop over all of them and run the wannacry binary on each, searching for an output that looks like a flag.

for key in keys/*; do 
	output=$(./wannacry -encrypted_file=flag -key_file="$key"); 
	if echo "$output" | grep -q "google"; then 
		echo "$output" > "decrypted_flag"; 
		break
	fi; 
done

And there’s a decrypted_flag in the directory :)