Waiting
Waiting
Prepared By : aris
Difficulty : Medium
Classification : Official
Synopsis
This challenge is about the security of Android's Pending Intents. The vulnerable application
displays the secret (flag) using a Pending Intent, but with the MUTABLE flag set, despite wrapping
an explicit intent (a good point from a security standpoint). The player has to find a security bug
through source code auditing, develop a malicious app able to exploit it and obtain the flag.
Description
The app stores a secret and says it is stored securely even in case the application. Are you able to
retrieve it?
Skills Required
High knowledge of Pending Intent in Android (a topic not very documented, not even in most
famous Android Security courses!).
Learn how to develop custom APKs for automating the exploitation process.
Enumeration
The app is divided into three activities:
MainActivity: where the security bug resides. Whenever this activity goes in the background
(technically when the onPause() method is called), a broadcast intent is sent for a particular
receiver configured to handle the com.example.waiting.RECEIVED action. This intent is broadcasted
every 5 seconds. It wraps a Pending Intent within the com.example.waiting.INTENT extra key which
in turn wraps another intent destinated to the MenuActivity (explicit intent). Because the
MUTABLE flag is set on the pending intent, a malicious application can append or modify extra
information in it. The MenuActivity checks if the intent received has the Secret extra key set to
true in order to display the secret (flag) to the end user. The goal of a malicious app is to add this
extra key to make the app display the secret. Every time the app is launched, anti-patching, anti-
reverse engineering tool and anti-debugging mechanims are activated to protect the app.
MenuActivity: a non-exported activity used to indicate that the secret is safe without revealing it.
Under the hood, this activity is also responsible for determining whether or not the app has been
tampered with.
SecretActivity: this activity displays the HTB Flag. It leverages a JNI function from the native library
secrets.cpp ( getdxXEPMNe() ) to retrieve the flag, which is not hardcoded in the C++ code (see
Secret Hiding paragraph below for more details).
Anti-Tampering
Defense Measures
Code Obfuscation
The java code is obfuscated except for the MainActivity where there is the security bug. The obfuscation was
done with (dProtect)[https://obfuscator.re/dprotect/], an Android bytecode obfuscator based on
Proguard.
Secret Hiding
The plugin hidden-secrets-gradle-plugin was used to hide the string. It uses a combination of
obfuscation techniques to do so:
secret is obfuscated using the reversible XOR operator, so it never appears in plain sight;
obfuscated secret is stored in a NDK binary as an hexadecimal array, so it is really hard to spot /
put together from a disassembly;
the obfuscating string is not persisted in the binary to force runtime evaluation (ie : prevent the
compiler from disclosing the secret by optimizing the de-obfuscation logic):
optionally, anyone can provide its own encoding / decoding algorithm when using the plugin to add
a security layer.
The anti-tampering checks are in place to lead the HTB user to exploit the vulnerability for which this
challenge was created. The implementation of the challenge aims to obtain the flag without tampering
with the app. The anti-tampering checks are:
The lib-native.c native lib for checking Frida's evidence. This library has 3 ways to detect frida hooking:
Compare text section in memory with text section in disk for both libc and native library
Viewing the MenuActivity we analyze the following piece of code to understand how the
SecretActivity will be launched. Every time the MenuActivity is created, it checks if the intent received
has the Secret extra key set to true:
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_menu);
do {
try {
System.out.println(Long.toString(a.a(this.j)));
} catch (IOException e) {
e.printStackTrace();
}
} while ((k + 1) % 2 == 0);
if (getIntent().getBooleanExtra("Secret", false)) {
try {
k();
Intent intent = new Intent(this, SecretActivity.class);
do {
startActivity(intent);
} while ((k + 1) % 2 == 0);
} catch (a.C0031a | IOException unused) {
Toast.makeText(this.j, "App Tampered!!", 0).show();
((TextView) findViewById(R.id.text_menu)).setText(R.string.tampered);
((TextView)
findViewById(R.id.text_menu_closing)).setText(R.string.tampered_closing);
new Handler().postDelayed(new Runnable() { // from class:
com.example.waiting.MenuActivity.1
@Override // java.lang.Runnable
public void run() {
MenuActivity.this.finishAndRemoveTask();
System.exit(0);
}
}, 10000L);
}
}
}
Once understood the code, it's time to develop a malicious application. We should perform the following
steps:
First, we should define, create and register a Broadcast Receiver in the evil app (similar to the code
shown below in the Getting the Flag section).
Open the Waiting app and do not click on the Menu button.
Open the evil app and wait. The SecretActivity will appear with the secret flag displayed.
package com.example.evilapp;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
@Override
public void onReceive(Context context, Intent intent) {