This is the third in a series of writeups on challenges from the BSidesSF CTF. You can see a writeup of the first challenge, Blink, here and the second, Yay or Nay, here.
Weather Companion was the final mobile challenge in the CTF, this time worth 350 points! We’re provided with an apk file and a prompt that doesn’t set us up with much:
A simple weather application that fetches and displays the weather. What hides within?
Launching up the app we find that they were true to their word. A simple weather application:
Given that the only information we have is that we are fetching the weather, I launched Charles Proxy to try and get some more information on the network request. It turned out that the request was using TLS and we couldn’t see the payload. Having had to go through the pain of getting the various keystores on an Android device and Charles Proxy working together before, I put this strategy off for now. The one thing I did learn was that the weather request was going to a Google owned IP address.
Next I decompiled the apk with jadx. The application was super obfuscated (presumably via ProGuard). All of the imported packages were renamed to single letters, but the main application was left easy to find.
Going straight to the MainActivity we find an onCreate() method that instantiates an example.MyApplication.a object and calls a#execute().
The a class is pretty long, I’ve removed some parts for brevity:
We can see that the bulk of the work here is assembling a string into a URL, making a request to it, and parsing the response into the weather data. The URL is assembled from a few parts: dks() and ss() both call out to C functions (that we have not decompiled or disassembled yet) via JNI, String a is a base64 encoded value that decodes to the first half of a private key, and String a2 is another encoded value. After that there is an email address for a GCP service account, and 2 ROT13 encoded URLs that decode to Google APIs authentication endpoints. It’s pretty clear that the bulk of this is service account credentials.
The two options at this point are:
1. Reverse each of the functions that go into obfuscating the credentials.
2. Log the assembled credentials after the stringBuilder4 = stringBuilder5.toString(); line where it’s loaded into memory.
I went with the latter. I had a feeling that trying to recompile the obfuscated jadx output I had in front of me might be unnecessarily sadistic1 so I went up one level in the decompilation pipeline and used Apktool to only disassemble, not decompile, the apk. The big difference here is that after disassembly we don’t get java source code but smali, a human readable format of Dalvik bytecode.
The smali filetree is 1:1 with the java one we examined earlier with each java file having a smali equivalent. Unfortunately a.smali is about 6x the size of a.java. I searched the smali code to see what a call to the logging methods looks like and found this snippet:
and added the line invoke-static {v2, v5}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I beneath it. The last assignment to v2 was "weather-companion" so it should make a good tag.
You’ll notice that I uninstalled the app before installing the new version. If I hadn’t done that then Android would have noticed that the com.example.myapplication package on the device was signed by a different key than the new one and refuse to install it as a security measure. Alternatively, we could have changed the package name and have both installed at the same time.
Loading the app and filtering the logs to the weather-companion tag spits the key right out! Using a similar smali trick to log the generated URL we can see that it’s a signed request to https://storage.googleapis.com/weather-companion/weather.json, a GCS bucket named weather-companion.
I authenticated the gcloud tool using my new service account credentials and started to poke around GCS. After a quick detour where we and the organizers realized that the flag wasn’t searchable 😅 all it took was a simple ls on the bucket!
🎉
While writing this post I decided to give this method a shot. I gave up quickly. ↩