Hacker1 CTF - Intentional Exercise 🏃

More fun with Android

Disassembled Java Bytecode

For this "exercise" I dove straight into the disassembled Java bytecode using apktool. Apologies for the long code block, but take a read through and see if you can grok what is going on in the app's MainActivity:

    .line 23
    invoke-virtual {p0}, Lcom/hacker101/level13/MainActivity;->getIntent()Landroid/content/Intent;

    move-result-object v0

    .line 24
    invoke-virtual {v0}, Landroid/content/Intent;->getData()Landroid/net/Uri;

    move-result-object v0

    const-string v1, "http://34.74.105.127/5534a1d80f/appRoot"

    const-string v2, ""

    if-eqz v0, :cond_0

    .line 28
    invoke-virtual {v0}, Landroid/net/Uri;->toString()Ljava/lang/String;

    move-result-object v0

    const/16 v2, 0x1c

    invoke-virtual {v0, v2}, Ljava/lang/String;->substring(I)Ljava/lang/String;

    move-result-object v2

    .line 29
    new-instance v0, Ljava/lang/StringBuilder;

    invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    :cond_0
    const-string v0, "?"

    .line 31
    invoke-virtual {v1, v0}, Ljava/lang/String;->contains(Ljava/lang/CharSequence;)Z

    move-result v0

    if-nez v0, :cond_1

    .line 32
    new-instance v0, Ljava/lang/StringBuilder;

    invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    const-string v1, "?"

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    :cond_1
    :try_start_0
    const-string v0, "SHA-256"

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

    move-result-object v0

    const-string v3, "s00p3rs3cr3tk3y"

    .line 35
    sget-object v4, Ljava/nio/charset/StandardCharsets;->UTF_8:Ljava/nio/charset/Charset;

    invoke-virtual {v3, v4}, Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B

    move-result-object v3

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

    .line 36
    sget-object v3, Ljava/nio/charset/StandardCharsets;->UTF_8:Ljava/nio/charset/Charset;

    invoke-virtual {v2, v3}, Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B

    move-result-object v2

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

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

    move-result-object v0

    const-string v2, "%064x"

    const/4 v3, 0x1

    .line 38
    new-array v4, v3, [Ljava/lang/Object;

    const/4 v5, 0x0

    new-instance v6, Ljava/math/BigInteger;

    invoke-direct {v6, v3, v0}, Ljava/math/BigInteger;-><init>(I[B)V

    aput-object v6, v4, v5

    invoke-static {v2, v4}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;

    move-result-object v0

    .line 39
    new-instance v2, Ljava/lang/StringBuilder;

    invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    const-string v1, "&hash="

    invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v0

    .line 40
    invoke-virtual {p1, v0}, Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V
    :try_end_0
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

But First...

So the app is going to open http://34.74.105.127/919a907fa3/appRoot in a WebView (like an embedded browser). What happens if we just open the URL in our desktop browser? Ah... interesting.

<h1>Welcome to Level13</h1><a href="appRoot/flagBearer">Flag</a>

flag-bearer Noun (plural flag-bearers)

  1. One who carries a flag, especially at a ceremony.
  2. One who openly promotes an idea or value and becomes symbolic for it.

So what does the app's code do?

The tl;dr of that code is that it

  1. Takes the URI specified as the data value of the Intent which launched the app
  2. Removes the first 28 characters (which, by the way, is the length of "http://level13.hacker101.com/") by calling substring(int beginIndex))
  3. Appends the rest of the URI from the Intent to "http://34.74.105.127/919a907fa3/appRoot".
  4. Calculates a message digest by using the MessageDigest class initialized to use the SHA-2561 algorithm.
  5. The hash consists of calling update twice with two pieces of data.
      update("s00p3rs3cr3tk3y"`)
      update("appRoot/flagBearer/") // the portion of the URL that follows `"http://level13.hacker101.com/"` from the Intent
    
  6. Ultimately that is appended as a query parameter "?hash=<sha256 message digest>".
  7. And finally tells the WebView to open the URL that's been constructed.That's why you can't just go to that flag bearer url directly because it will reject your request for a missing or incorrect hash parameter.

Why is http://level13.hacker101.com relevant? Because if you look at the AndroidManifest.xml you will see that you can launch the app (via an Intent, hence the play on words in the challenge title) and supply your own url. This is the opening we need to cause the app to query the flagBearer path we witnessed when browsing the base URL.

<intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:host="level13.hacker101.com" android:scheme="http"/>
</intent-filter>

The Exploit

$ adb shell am start -a android.intent.action.VIEW -d "http://level13.hacker101.com/flagBearer" com.hacker101.level13

Yields: http://34.74.105.127/919a907fa3/appRoot/flagBearer?&hash=8743a18df6861ced0b7d472b34278dc29abba81b3fa4cf836013426d6256bd5e and that gets the flag!

All flags captured.

  1. For the purposes of this exercise you don't need to know how SHA-256 works but it is a fascinating topic in general. SHA-256