Downloading the next challenge, I got a file with a long name again a5eecbd1dc5ad07e38b062cdabfb3e63da36847e727fa666903f9dc1094e24160d68d0ed95378102ae20c7bca84f3638825c4433833b08886f918b7fa90fec56.

The file utility claims that it’s a zip. Unzipping and I have an executable named wannacry. Very similar to the previous challenge so far.

Opened it in IDA. This time, it’s a much smaller binary - about 16 functions with debug info again leaking the functions’ names.

I decided to run strings on the executable returns. It printed out a LOT of valid-looking strings, about a thousand valid single-word strings. Huh. Among them I spotted https://wannacry-killswitch-dot-gweb-h4ck1ng-g00gl3.uc.r.appspot.com// which is a slightly different appspot subdomain than the one I got in the previous challenge.

Well, checking out main subroutine, it’s basically empty and does nothing. I checked the entry point of the executable (_start) which seems to call main. When I ran the executable, indeed nothing happened. Odd.

Anyway, there’s not many other functions to look through, so let’s pick one. print, judging by the previous challenge, should be a good direction.

It has no xrefs, and simply calls the function correct_code, prints the string in the DOMAIN global variable and then the return value of the correct_code. DOMAIN, unsurpriginly, points to the appspot domain I found.

Going into correct_code, I started to look at it from the end to simply see what it returns:

.text:000000000002F7FD	mov     eax, [rbp+var_8]
.text:000000000002F800	cdqe                    ; EAX -> RAX (with sign)
.text:000000000002F802	lea     rdx, ds:0[rax*8] ; Load Effective Address
.text:000000000002F80A	lea     rax, wordlist   ; Load Effective Address
.text:000000000002F811	mov     rax, [rdx+rax]
.text:000000000002F815	leave                   ; High Level Procedure Exit
.text:000000000002F816	retn                    ; Return Near from Procedure

Seems like it uses whatever stored in var_8 as an index to an array of quadwords pointers named wordlist. The wordlist seems to be an array of pointers to strings, the array seems to contain thousands of alphabetically ordered, different strings. Looks like it’s the same ones strings found at the beginning!

So, I guess that one of the strings in the wordlist is the correct one. But what can I do with it? Let’s try to append it to the domain - by testing a GET request to https://wannacry-killswitch-dot-gweb-h4ck1ng-g00gl3.uc.r.appspot.com//zoom, it responds with Our princess is in another castle.. Testing out a random string (not in the wordlist) appended to the domain returns the same result. Unfortunate, but I still guess that one of the words in wordlist is the right one.

So what chooses the index of the word from wordlist? The var_8 local varible is initialized as 0 and changes during some loops that are being done before correct_code returns. Actually, it seems that exactly 5 iterations are taking place, judging by the cmp before the return block and the increment of var_C taking place right before looping back.

.text:000000000002F7F7	cmp     [rbp+var_C], 4 ; Compare Two Operands
.text:000000000002F7FB	jle     short loc_2F7B3 ; Jump if Less or Equal (ZF=1 | SF!=OF)

So what happens in the loop? First, the return value of the call to totp (which rings to me as the shorthand for one-time password) is taken and some manipulation is done over it. It’s a bit of an odd piece of code - I wrote a short Python code that mimicks its behavior:

# i - the loop index variable, 
# totp - the output of the totp() call, 
# index - the calculated index to take from the wordlist.
while i <= 4:
	# al is 16 bit, zero-extending it to eax just means we're taking these exact bits
	totp_copy = totp & 0xFFFF
	# seems like the code decides to take just 6 bits at the end.
	totp_copy = totp_copy & 0x3F
	# count number of bits turned on. Can be a value between 0 and 6.
	number_of_ones = count_ones(totp_copy) 
	if number_of_ones > 0:
		number_of_ones -= 1
	# deleting the bottom 6 bits from the totp variable
	totp = totp >> 6 

	index = 6 * index
	index += number_of_ones

	i += 1

This code uses the bits in the return value of totp to calculate a 5-digit base-6 number.

In each iteration, it counts the number of turned on bits in the current 6-bit window of the totp, and treat them as the next digit in the number. Since the number of bits turned on in a 6-bit integer can be between 0 and 6, there’s an extra possiblity (6 is an invalid value in base-6) - so the code subtracts 1 from the number of turned on bits, which in turn makes no turned on bits and exactly one turned on both map to 0.

Thus the maximum number the code can output is 55555 in base-6 which equals 7775 in base-10. A quick look over the size of wordlist discovers it’s exactly 7775 words long. Not a coincidence here, huh?

So, given that totp returns a truly random value, I’d have to check all 7775 words.

At this point, I was in a bit of a hurry and didn’t want to wait through the thousands of requests to the server. I decided to take a peek at the totp function in hopes of finding something.

Well, on first sight it looks just like I guessed - it calls time_now to get the time, then hashes the output using sha1, and finally calls a function named extract31 with the hash and the last 4 bits of the hash as parameters.

This latter function has a pretty distinctive name which I vaguely remembered. Searching online and the first result is HMAC-based one-time password which is very similar to the method taken here - hashing a secret value (in this case, the time) and then extracting a code from it, using the last 4 bits as an index for an extraction of exactly 31 bits from the hash (hence the name).

Abstractly looking, without going into the implementation of the time_now, sha1 and extract31 methods, I find it should be pretty hard to “break” this one-time code generation algorithm, as even a “somewhat” correct implementation of this function would likely generate every possible code with some probability (even if not entirely uniform, given that the secret is not truly random).

However, given that I know nothing regarding the time the code I’m meaning to find was generated at, that makes breaking it even less likely.

Well, I guess it’s time to iterate over 7000 words? But wait a second, could the entire exercise (and the reference to a “killswitch”) mean that the webserver runs the same totp code in real-time and so the correct word changes every time the code changes?

It could be that iterating through all words will still solve the challenge, but I might get a bit unlucky with the race condition and miss the right password between making requests.

So, I decided to just try to run the print function and check the word is returnes and the current time.

To do that, I changed the extra uneeded code inside main to call print instead (simply “Patch program” in IDA then “Assemble” and writing call print does the trick, while not forgetting to patch the bytes afterwards with nops).

Don't want to crash the program, right?

I applied the patch and ran the binary -

tal@Tal:~$ ./wannacry_patched

https://wannacry-killswitch-dot-gweb-h4ck1ng-g00gl3.uc.r.appspot.com//deflator

And I got a big Turn it off! icon. Nice, no need for bruteforce here :)

It's so bright in here!