Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# Gradle & Build outputs
.gradle/
build/
**/build/
.idea/
*.iml
*.apk
*.aar
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ I've released security advisories for the Cellebrite UFED which you may also be
- [KL-001-2020-002: Cellebrite Restricted Desktop Escape and Escalation of User Privilege](https://korelogic.com/Resources/Advisories/KL-001-2020-002.txt)
- [KL-001-2020-001: Cellebrite Hardcoded ADB Authentication Keys](https://korelogic.com/Resources/Advisories/KL-001-2020-001.txt)

## Audit & Bug Fix Summary

The codebase was audited, refactored, and updated with several key fixes and enhancements:

### 1. Duress / Lockup Password Matching (`LockUpPlausibleService.java` & `preferences.xml`)
- **Bug Fixed**: Previously, `LockUpPlausibleService` used a cumulative character-by-character counter (`pointsHave`) that persisted across events and keystrokes without resetting. This caused false positives (accidental wipes) when typing normal text on the lock screen.
- **Fix Applied**: Replaced the accumulator loop with a clean, stateless string parser. The entered text is sanitized (trimming whitespace and removing list string wrappers `[text]`) and directly compared against the configured duress password (`deniabilityPw`) and its prefix.
- **Password Setup**: Enabled `deniabilityPw` (`EditTextPreference`) in `preferences.xml` and `SettingsActivity.java` so users can easily set up their custom lockup password in Settings.

### 2. Device Wipe Switch Fallthrough (`Defense.java`)
- **Bug Fixed**: In `Defense.java`, the `switch (response)` block lacked `break` statements. Choosing "Lock" fell through directly into `wipeData(0)`, causing any defensive action to perform a full factory reset.
- **Fix Applied**: Added `break;` statements to each case to ensure "Lock" and "Factory Reset" operate independently.
- **Refactoring**: Removed `extends AppCompatActivity` from `Defense.java`, converting it into a clean helper class that uses `Context`.

### 3. Restricted USB Monitoring & Launch-Time Wipe Prevention (`LockUpService.java`)
- **USB Monitoring**: Updated `mUsbAttachReceiver` so forensic staging directory monitoring (`monitor_staging_dir()`) ONLY starts when an external USB device or accessory is connected (`ACTION_USB_DEVICE_ATTACHED` or `ACTION_USB_ACCESSORY_ATTACHED`). Added proper `break` statements to prevent false triggers on `DETACHED` or `default` cases.
- **First-Launch Wipes**: Updated `mAppInstallReceiver` to inspect only newly installed packages (`ACTION_PACKAGE_ADDED`), preventing the app from scanning all pre-existing installed applications and wiping the phone on first launch.

### 4. Settings Preferences UI Fixes (`preferences.xml` & `SettingsActivity.java`)
- **Missing Preferences**: Added missing UI elements (`desiredResponse` ListPreference and `notifyUser` SwitchPreference) to `preferences.xml`.
- **Initialization**: Fixed `SettingsActivity.java` to set `"initialized"` to `true` after initial setup.

### 5. Build Environment & Modern Android Compatibility
- **Gradle Configuration**: Added `settings.gradle`, `build.gradle`, `gradle.properties`, and `app/build.gradle` targeting Android SDK 34 with Java 8 compatibility (supporting Android Studio and AIDE).
- **AndroidX Migration**: Migrated all deprecated Support Library v7 imports and layout elements (e.g. `ConstraintLayout`) to `androidx`.
- **Android 12+ Compliance**: Added explicit `android:exported` attributes to components with intent filters in `AndroidManifest.xml`.

### 6. Automated Testing
- Added `PlausibleServiceTest.java` JVM unit test suite to verify text sanitization, password matching, and USB attachment filter actions.

## License

[Creative Commons Zero 1.0](https://github.com/mbkore/lockup/blob/main/LICENSE)
Expand Down
35 changes: 35 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
id 'com.android.application'
}

android {
namespace 'com.lockup'
compileSdk 34

defaultConfig {
applicationId "com.lockup"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0-beta"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
}
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.lockup"
android:versionCode="1"
android:versionName="1.0-beta">

<uses-sdk
android:minSdkVersion="26"
android:targetSdkVersion="34" />

<supports-screens android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
Expand Down Expand Up @@ -75,6 +78,7 @@
<receiver
android:name=".AdminReceiver"
android:label="LockUp"
android:exported="true"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data
android:name="android.app.device_admin"
Expand All @@ -88,6 +92,7 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -103,6 +108,7 @@
<activity
android:name=".SettingsActivity"
android:label="@string/app_name"
android:exported="false"
android:theme="@style/AppTheme"></activity>
</application>

Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/com/lockup/Defense.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class Defense extends AppCompatActivity {
public class Defense {

Context context;

Expand All @@ -32,12 +31,15 @@ public void protect_device_run() {
switch (response) {
case "Lock":
devicePolicyManager.lockNow();
break;
case "Factory Reset":
devicePolicyManager.wipeData(0);
devicePolicyManager.lockNow();
break;
default:
devicePolicyManager.wipeData(0);
devicePolicyManager.lockNow();
break;
}
} else {
Log.d("LockUp", "Unable to properly defend this device. Failing open.");
Expand Down
28 changes: 11 additions & 17 deletions app/src/main/java/com/lockup/LockUpPlausibleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ public void onDestroy() {
}
}

Integer pointsHave = 0;
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if (preferences.getBoolean("accessibility", false) && preferences.getBoolean("plausible", false)) {
String deniabilityPw = preferences.getString("deniabilityPw", "LockUp");
Integer pointsNeeded = deniabilityPw.length() - 1;
PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
boolean isAwake = (Build.VERSION.SDK_INT < 20 ? powerManager.isScreenOn() : powerManager.isInteractive());
if (isAwake) {
Expand All @@ -73,24 +71,20 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
if (keyMgr.inKeyguardRestrictedInputMode()) {
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
char[] txtCharArray = event.getText().toString().toCharArray();
char[] deniabilityPwCharArray = deniabilityPw.toCharArray();
for (int i = 0; i < deniabilityPwCharArray.length; i++) {
try {
if (Integer.valueOf(deniabilityPwCharArray[i]).equals(Integer.valueOf(txtCharArray[i + 1]))) {
pointsHave++;
if (pointsHave >= pointsNeeded) {
Defense defense = new Defense(getApplicationContext());
defense.protect_device_run();
}
}
} catch (Exception e) {
if (event.getText() != null && !event.getText().isEmpty()) {
String enteredText = event.getText().get(0).toString();
enteredText = enteredText.trim();
if (enteredText.startsWith("[") && enteredText.endsWith("]")) {
enteredText = enteredText.substring(1, enteredText.length() - 1);
}
enteredText = enteredText.trim();
if (enteredText.equals(deniabilityPw) || (deniabilityPw.length() > 1 && enteredText.equals(deniabilityPw.substring(0, deniabilityPw.length() - 1)))) {
Defense defense = new Defense(getApplicationContext());
defense.protect_device_run();
}
}
break;
}
} else {
// phone unlocked
pointsHave = 0;
}
}
}
Expand Down
112 changes: 60 additions & 52 deletions app/src/main/java/com/lockup/LockUpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,81 +195,89 @@ public LockUpService() {
}
};

private Thread monitorThread = null;

private final BroadcastReceiver mUsbAttachReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Thread monitor_thread = new Thread() {
@Override
public void run() {
monitor_staging_dir();
}
};
if (intent == null || intent.getAction() == null) {
return;
}
switch (intent.getAction()) {
case UsbManager.ACTION_USB_ACCESSORY_ATTACHED:
if (!monitor_thread.isAlive()) {
monitor_thread.start();
}
case UsbManager.ACTION_USB_ACCESSORY_DETACHED:
if (monitor_thread.isAlive()) {
monitor_thread.interrupt();
}
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
if (!monitor_thread.isAlive()) {
monitor_thread.start();
if (monitorThread == null || !monitorThread.isAlive()) {
monitorThread = new Thread(new Runnable() {
@Override
public void run() {
monitor_staging_dir();
}
});
monitorThread.start();
}
break;
case UsbManager.ACTION_USB_ACCESSORY_DETACHED:
case UsbManager.ACTION_USB_DEVICE_DETACHED:
if (monitor_thread.isAlive()) {
monitor_thread.interrupt();
if (monitorThread != null && monitorThread.isAlive()) {
monitorThread.interrupt();
monitorThread = null;
}
break;
default:
if (!monitor_thread.isAlive()) {
monitor_thread.start();
} else {
monitor_thread.interrupt();
}
break;
}
}
};

private final BroadcastReceiver mAppInstallReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
return;
}
try {
if (intent.getData() == null) {
return;
}
String packageName = intent.getData().getSchemeSpecificPart();
if (packageName == null || packageName.isEmpty()) {
return;
}
PackageManager pm = context.getPackageManager();
List<ApplicationInfo> applications = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo metadata : applications) {
Integer count = 0;
PackageInfo appInfo = pm.getPackageInfo(metadata.packageName, PackageManager.GET_SIGNATURES);
for (Signature appSig : appInfo.signatures) {
byte[] signature = appSig.toByteArray();
InputStream input = new ByteArrayInputStream(signature);
CertificateFactory factory = CertificateFactory.getInstance("X509");
X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
MessageDigest md = MessageDigest.getInstance("SHA256");
byte[] appPubKey = md.digest(x509.getEncoded());
StringBuffer appPubKeyHex = new StringBuffer();
for (int i = 0; i < 32; i++) {
appPubKeyHex.append(String.format("%02x", appPubKey[i]));
PackageInfo appInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
if (appInfo == null || appInfo.signatures == null) {
return;
}
Integer count = 0;
for (Signature appSig : appInfo.signatures) {
byte[] signature = appSig.toByteArray();
InputStream input = new ByteArrayInputStream(signature);
CertificateFactory factory = CertificateFactory.getInstance("X509");
X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
MessageDigest md = MessageDigest.getInstance("SHA256");
byte[] appPubKey = md.digest(x509.getEncoded());
StringBuffer appPubKeyHex = new StringBuffer();
for (int i = 0; i < 32; i++) {
appPubKeyHex.append(String.format("%02x", appPubKey[i]));
}
for (String appKey : bannedKeys) {
if (appKey.toUpperCase().equals(appPubKeyHex.toString().toUpperCase())) {
count++;
}
for (String appKey : bannedKeys) {
if (appKey.toUpperCase().equals(appPubKeyHex.toString().toUpperCase())) {
}
Principal principal_subject = x509.getSubjectDN();
String subjectDn = principal_subject.getName();
Principal principal_issuer = x509.getIssuerDN();
String issuerDn = principal_issuer.getName();
for (Map.Entry<Integer,String[]> entry : bannedIssuers.entrySet()) {
for (String piece : entry.getValue()) {
if (subjectDn.toUpperCase().contains(piece.toUpperCase()) || issuerDn.toUpperCase().contains(piece.toUpperCase())) {
count++;
}
}
Principal principal_subject = x509.getSubjectDN();
String subjectDn = principal_subject.getName();
Principal principal_issuer = x509.getIssuerDN();
String issuerDn = principal_issuer.getName();
for (Map.Entry<Integer,String[]> entry : bannedIssuers.entrySet()) {
for (String piece : entry.getValue()) {
if (subjectDn.toUpperCase().contains(piece.toUpperCase()) || issuerDn.toUpperCase().contains(piece.toUpperCase())) {
count++;
}
}
}
if (count > 0) {
defense.protect_device_run();
}
}
if (count > 0) {
defense.protect_device_run();
}
}
} catch (Exception e) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/lockup/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/lockup/SettingsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void onCreate(final Bundle savedInstanceState)
prefEditor.putBoolean("accessibility", false);
prefEditor.putBoolean("runAtBoot", true);
prefEditor.putBoolean("compromised", false);
prefEditor.putBoolean("initialized", false);
prefEditor.putBoolean("initialized", true);
prefEditor.putBoolean("notifyUser", true);
prefEditor.apply();
}
Expand Down
Loading