Wargame NDH2K17 Write-up TIME_IS_THE_KEY

This year we participated to the NDH2K17 wargame and arrived at the 16th position with a very small team.

sys_pwn ranking

Amongst the challenges we managed to flag, one was quite difficult and took a bit of time solving. Hence, we though we should do a small write-up. As the challenge has not been released online yet, we might lack a few screenshots.

Time_is_the_key

This was the title of a challenge which gave us a very simple apk. Being angry apk reversers we began by decompiling the apk both in smali and java format. Warsang already wrote a blogpost about how to go through this but just for a sanity check we'll remind you the commands used:

To get smali files:

apktool d Android_auth.apk -o output_file

To get java files:

jadx -d Android_auth.apk -o java_output

After taking a rough look at the app it was decided to run it in a rooted environment which we use for these kind of things:

We are greeted by the following Activity asking for a username and a password.

When taking a look at the LoginActivity.java file, we notice a few interesting things:

A URI :

exampleUrl

By looking at the code, this URI seems to be actually a login page which accepts 'username' and 'password' as POST parameters.

Interestingly, a validate() method was present in the code, which runs some offline checks on the username and password before actually issuing the POST request. So far so good. Let's take a closer look at this method:

Validate() method

The credentials to send to the server must meet some criteria:

  • 'username' and 'password' should not be blank (obviously)
  • 'password' must be exactly 15 characters long
  • 'password' is alphabetical lower-case ([a-z])

Having these info at hand, we tried to make a login request. The server tells us that it doesn't understand our request, probably because we added some extra headers or omitted some (sorry, we forgot to take a screenshot for that ¯\(ツ)/¯ )

So the next step was to proxify the requests made by the phone via Burp, to see what the headers looked like.

Guess what? Yep... Certificate pinning prevented us from listening in on the trafic. Well warsang always wanted to do a post about how to remove certificate pinning. There are plenty of ways to do so. It usually depends on how the certificate pinning logic is implemented on the apk. One could use this module to remove cert pinning. This blogpost discusses the most basic and probably most encountered case (on apps not using cert pinning modules) where you get a function in java which looks like so:

@Singleton
CertificatePinner provideCertificatePinner() {
   return new Builder().add(“api.example.com”, “sha1/XXXXXXXXXXXXXXX=”).build();
}

These are usually pretty simple to bypass. Another option is to use Frida, a dynamic execution tool which allows to "hook" functions in the apk by injecting javascript at runtime. We could have hooked the certificate pinning function and replaced it's pinning logic with our own.
Even though this is probably the best approach, being still Frida novices (and needing the flag!) we decided to recompile the apk.

The certificate handling logic was located in com.example.android_auth/http . More specifically the pinning logic was in the CustomTrustManager.java file

We were soon to uncover a function called checkServerTrusted.

Certif pinning method

The validator.validate() method was probably what performed the certificate verification. Hence we patched it in the smalli code: Smali modifications

After recompiling the app, voila! We finally manage to intercept traffic with Burp.

Now that we know what the request should look like, we tried to replay it with Burp:

400 response

Uh oh. We need a client certificate. Luckily, the client certificate was easily found in apktool output /res/raw. The certificate password was also easy to retrieve from the decompiled sources, and was literally 'password':

Client certificate

Client certificate password

After adding the client certificate in Burp, we got a 401 unauthorized answer, saying that the password is incorrect. Yay !

401 unauthorized

At this point, we tried to see if we can enumerate users. Since we first tried with 'admin', we removed one char and tried with 'amin':

Users enumeration

We get a different response. That's a good sign. We now know that admin is a valid user.

At some point, we shamefully thought that we can bruteforce the password. What seemed first as a good idea turned out to be an impossible task (at least during the limited time span of the wargame). We were never good at maths anyway, but we think all the possible combinations should be something like 15^26 passwords which is... A lot of combinations.

We took a step back and tried to reassemble all the elements that we got so far to see what part we were missing. And then it struck us ! The challenge's title: Time is the key !

So we manually tried to replace the password's first character by iterating through the alphabet. The letter 's' seemed to trigger a longer response time. That's it ! It's a timing attack !

The server compared the password character by character, and its response time grew by 1 second whenever a valid character was found. Hence we wrote a short script to bruteforce the password based on response time.

#!/usr/bin/env python2.7                                                                                                                                                       

import requests                                                                                                                                                                
import string                                                                                                                                                                  
import time                                                                                                                                                                                                                                                                                                                                        

url = "https://zxylieipe.wargame.ndh/login"                                                                                                                                    
username = "admin"                                                                                                                                                             

printable = string.printable[10:36]                                                                                                                                            

for i in printable:                                                                                                                                                            
    password = "sfgzoshnkgkuxs" + i                                                                                                                                            
    data = {'username':username,
            'password':password                                                                                                                                                
          }                                                                                                                                                                  
    start = time.time()                                                                                                                                                        
    res = requests.post(url, data=data, verify=False)                                                                                                                          
    finish = time.time()                                                                                                                                                       
    duration = finish - start                                                                                                                                                  
    print password + ':' + str(duration) 

The script is actually lame, as we had to update it once we found a valid character. It could of course be improved with multi-threading, but at 6 a.m we were in no mental condition to code things properly 눈_눈. But if the challenge makes it online, we promise we will write a sexy fully-automated script.

Note that we didn't use the client certificate in the python script. What we did instead was proxify the script through Burp by setting the http_proxy environment variable to the address Burp was listening on, which conveniently added the certificate for us.

After some waiting, we finally got the flag!

Awesome flag

A repo containing the script and the modified apk is available here.