Hacker1 CTF - H1 Thermostat 🌡️

Another CTF challenge, this time involving an Android app

Prelude

When you visit this challenge it tells you it is generating an APK file and to refresh in order to download it. Each instance of this challenge generates a different APK, and I guessed that either a secret is compiled in, or at least the configuration of what host to communicate with. So, using apktool to decompile the resources and Java bytecode, I wasn't sure what I should look for.

Flag 0

I decided to inspect for network traffic using mitmproxy and setting my Android device to go through my Kali Linux box as a proxy. This quickly revealed the first flag as a header on a normal HTTP REST API call. The instance of the challenge had even reset (shown by the fact that the response was a 404) but the flag is in the outbound request so it didn't matter.

Captured Flow of HTTP traffic in mitmproxy
Android proxy settings

Flag 1

After starting a new instance of the challenge I decided to look at the request some more on a successful connection. No surprises here, the body of the request contains some base64 encoded data indicating the temperature to set the "thermostat" to. There is a header, 'X-MAC' that must be some kind of Message Authentication Code, but how is it computed? I substituted some values via the proxy, but the server just happily returned 500 for bogus formatting, or 200 for well-formed but ridiculous temperatures.

Request:
eyJjbWQiOiJzZXRUZW1wIiwidGVtcCI6Nzd9

{"cmd":"setTemp","temp":77}

Response:
eyJzdWNjZXNzIjogdHJ1ZX0=

{"success": true}

Modified Request:
{"cmd":"setTemp","temp":1000}

eyJjbWQiOiJzZXRUZW1wIiwidGVtcCI6MTAwMH0=

Back to the disassembled APK. Aha! Looking through the bytecode for making a request (PayloadRequest.smali), we stumble upon some key phrases like "MD5" and "MessageDigest" but most importantly, "FLAG".

    const-string p2, "MD5"

    .line 43
    invoke-static {p2}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

    move-result-object p2

    const-string v0, "^FLAG^c8634e21d6a4b2b3763ed0250774e43c7054fe496062d5233ea4d0b37e95f19e$FLAG$"

    .line 44
    invoke-virtual {v0}, Ljava/lang/String;->getBytes()[B

    move-result-object v0

    invoke-virtual {p2, v0}, Ljava/security/MessageDigest;->update([B)V

    .line 45
    invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B

    move-result-object p1

    invoke-virtual {p2, p1}, Ljava/security/MessageDigest;->update([B)V

    .line 46
    invoke-virtual {p2}, Ljava/security/MessageDigest;->digest()[B

    move-result-object p1

    const/4 p2, 0x0

    invoke-static {p1, p2}, Landroid/util/Base64;->encodeToString([BI)Ljava/lang/String;

    move-result-object p1

    .line 47
    iget-object p2, p0, Lcom/hacker101/level11/PayloadRequest;->mHeaders:Ljava/util/HashMap;

All Flags Captured!