diff --git a/uraniborg/AndroidStudioProject/Hubble/app/build.gradle b/uraniborg/AndroidStudioProject/Hubble/app/build.gradle index 2c8430f8..3321f17a 100644 --- a/uraniborg/AndroidStudioProject/Hubble/app/build.gradle +++ b/uraniborg/AndroidStudioProject/Hubble/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 34 defaultConfig { + compileSdk 35 applicationId "com.uraniborg.hubble" minSdkVersion 23 - targetSdkVersion 34 - versionCode 5 - versionName "1.3.0" + targetSdkVersion 35 + versionCode 10 + versionName "2.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -26,9 +26,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.6.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' implementation 'org.jetbrains:annotations-java5:15.0' } diff --git a/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/MainActivity.java b/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/MainActivity.java index 2c8d06e6..edcbc3ce 100644 --- a/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/MainActivity.java +++ b/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/MainActivity.java @@ -28,6 +28,7 @@ import android.util.Base64; import android.util.Log; +import java.util.TreeMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.jetbrains.annotations.NotNull; @@ -46,10 +47,13 @@ public class MainActivity extends AppCompatActivity { final String TAG = "HUBBLE"; // semantically tie the notion of app version to versionName, which we will update for every - // major and minor release, instead of independently and separately update these values everytime. - private final String VERSION = BuildConfig.VERSION_NAME; + // major and minor release. Unfortunately, for now, we have to independently and separately + // update these values everytime we do any revisions because BuildConfig is phased out. + private final String VERSION = "2.0.0"; - private HashMap mAllPackages; + // We're changing to TreeMap so that package names are sorted. This would ease output comparison. + private TreeMap mAllPackages; + private TreeMap mPreinstalledPackages; private HashMap mAllCertificates; private HashMap mAllBinaries; private HashMap mAllLibraries; @@ -65,7 +69,8 @@ public class MainActivity extends AppCompatActivity { private boolean initialize() { final String tag = TAG + "-INIT"; - mAllPackages = new HashMap<>(); + mAllPackages = new TreeMap<>(); + mPreinstalledPackages = new TreeMap<>(); mAllCertificates = new HashMap<>(); mAllBinaries = new HashMap<>(); mAllLibraries = new HashMap<>(); @@ -115,6 +120,9 @@ private void getInstalledPackagesInformation() { for (PackageInfo pkg : installedPackagesAndApexes) { PackageMetadata pkgMetadata = PackageMetadata.parse(this, pkg, mPackageManager); mAllPackages.put(pkg.packageName, pkgMetadata); + if (pkgMetadata.isPreinstalled) { + mPreinstalledPackages.put(pkg.packageName, pkgMetadata); + } } Log.d(tag, String.format("There are %d packages (including APEX)", mAllPackages.size())); } @@ -166,7 +174,7 @@ private void getAllBinaries() { // first get the system path String binPaths = System.getenv("PATH"); if (binPaths == null) { - Log.e(tag, String.format("Error getting system PATH environment value.")); + Log.e(tag, "Error getting system PATH environment value."); return; } Log.d(tag, String.format("binPaths: %s", binPaths)); @@ -284,6 +292,7 @@ private void getDeviceProperties() { private void writePackagesToFile() { final String tag = TAG + "-W_PKG"; final String PKG_FILENAME = "packages.txt"; + final String PREINSTALL_FILENAME = "preinstalled_packages.txt"; String header = String.format(HEADER_FMT, VERSION, "totalPackages", mAllPackages.size(), "packages"); @@ -299,6 +308,22 @@ private void writePackagesToFile() { i++; } Utilities.writeToFile(this, PKG_FILENAME, FOOTER_STR, true); + + // write preload info to preload file. + header = String.format(HEADER_FMT, VERSION, "totalPreinstalledPackages", + mPreinstalledPackages.size(), "preinstalledPackages"); + Utilities.writeToFile(this, PREINSTALL_FILENAME, header, false); + int j = 0; + for (PackageMetadata preinstalledMetadata : mPreinstalledPackages.values()) { + Utilities.writeToFile(this, PREINSTALL_FILENAME, + preinstalledMetadata.getJSONString(skip), true); + if (j == mPreinstalledPackages.size() - 1) { + continue; + } + Utilities.writeToFile(this, PREINSTALL_FILENAME, ",\n", true); + j++; + } + Utilities.writeToFile(this, PREINSTALL_FILENAME, FOOTER_STR, true); } private void writeCertsToFile() { diff --git a/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/PackageMetadata.java b/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/PackageMetadata.java index b870346d..d24183dd 100644 --- a/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/PackageMetadata.java +++ b/uraniborg/AndroidStudioProject/Hubble/app/src/main/java/com/uraniborg/hubble/PackageMetadata.java @@ -55,6 +55,7 @@ public class PackageMetadata extends BaseInfo { protected boolean isFactoryTest = false; protected boolean isSuspended = false; protected boolean isApex = false; + protected boolean isPreinstalled = false; protected boolean isHidden = false; protected boolean hasCode = true; protected boolean usesCleartextTraffic = true; @@ -102,6 +103,9 @@ static public PackageMetadata parse(Context context, @NotNull PackageInfo packag result.isFactoryTest = ((appInfo.flags & ApplicationInfo.FLAG_FACTORY_TEST) != 0); result.isSuspended = ((appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0); + result.isPreinstalled = (((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) || + ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { result.usesCleartextTraffic = (appInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0; diff --git a/uraniborg/AndroidStudioProject/Hubble/build.gradle b/uraniborg/AndroidStudioProject/Hubble/build.gradle index 61fadd3c..f22282a8 100644 --- a/uraniborg/AndroidStudioProject/Hubble/build.gradle +++ b/uraniborg/AndroidStudioProject/Hubble/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.5.2' + classpath 'com.android.tools.build:gradle:8.6.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/uraniborg/AndroidStudioProject/Hubble/gradle.properties b/uraniborg/AndroidStudioProject/Hubble/gradle.properties index 46309a32..354b4e6b 100644 --- a/uraniborg/AndroidStudioProject/Hubble/gradle.properties +++ b/uraniborg/AndroidStudioProject/Hubble/gradle.properties @@ -16,8 +16,8 @@ org.gradle.jvmargs=-Xmx1536m # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true -android.defaults.buildfeatures.buildconfig=true +android.enableJetifier=false +# android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false diff --git a/uraniborg/README.md b/uraniborg/README.md index 6d6b27f8..97b4473a 100644 --- a/uraniborg/README.md +++ b/uraniborg/README.md @@ -40,5 +40,9 @@ Below are links to more specific documentations. - [Scoring device preload risks](docs/device_scoring.md) - [Adding new baseline for new OS release](docs/adding_new_baseline.md) +## Version +The current version info can be found within the VERSION file, and in the +build.gradle file of the Hubble app. + ## Disclaimer This is not an officially supported Google product. diff --git a/uraniborg/VERSION b/uraniborg/VERSION new file mode 100644 index 00000000..227cea21 --- /dev/null +++ b/uraniborg/VERSION @@ -0,0 +1 @@ +2.0.0 diff --git a/uraniborg/docs/hubble_results.md b/uraniborg/docs/hubble_results.md index 46ce85fd..44f02268 100644 --- a/uraniborg/docs/hubble_results.md +++ b/uraniborg/docs/hubble_results.md @@ -93,6 +93,8 @@ indicating app developer's declaration of whether this package contains code or is purely data/resource APK. - hash: The SHA256 digest of the package/APK. - installLocation: The location where the APK is installed on the system. +- isPreinstalled: A boolean flag indicating whether the APK is preinstalled or +installed post setup. - isApex: A boolean flag indicating if this package is an [APEX](https://source.android.com/devices/tech/ota/apex) or not. - isEnabled: A boolean [flag](https://developer.android.com/reference/android/content/pm/ApplicationInfo.html#enabled) telling whether at the time of observation, this package is "active" or in the diff --git a/uraniborg/prebuilts/APK/Hubble-v2.0.0.apk b/uraniborg/prebuilts/APK/Hubble-v2.0.0.apk new file mode 100644 index 00000000..36c7306a Binary files /dev/null and b/uraniborg/prebuilts/APK/Hubble-v2.0.0.apk differ diff --git a/uraniborg/prebuilts/APK/latest b/uraniborg/prebuilts/APK/latest index c5e44825..1ce777dc 120000 --- a/uraniborg/prebuilts/APK/latest +++ b/uraniborg/prebuilts/APK/latest @@ -1 +1 @@ -Hubble-v1.3.0.apk \ No newline at end of file +Hubble-v2.0.0.apk \ No newline at end of file diff --git a/uraniborg/scripts/python/automate_observation.py b/uraniborg/scripts/python/automate_observation.py index 6744fa59..8830ee32 100644 --- a/uraniborg/scripts/python/automate_observation.py +++ b/uraniborg/scripts/python/automate_observation.py @@ -28,6 +28,7 @@ import shutil # to help move files import sys import tarfile # to untar decompressed adb backup file +from typing import Optional # runtime support for type hints. import zlib # to decompress adb backup import syscall_wrapper @@ -40,7 +41,7 @@ HUBBLE_RESULT_STRING = "Results are available at:" -def parse_arguments(): +def parse_arguments() -> argparse.Namespace: """Parses cmdline arguments. Returns: @@ -69,11 +70,17 @@ def parse_arguments(): action="count", help="If specified, results will be classified using " "old classification using ADB convention.") + parser.add_argument("--pull-all-apks", required=False, + action="count", + help="If specified, the script will attempt to download " + "APKs from the device. The resulting APKs will be " + "stored in an \"apks\" directory within the " + "specific numbered results directory.") args = parser.parse_args() return args -def set_up_logging(args): +def set_up_logging(args: argparse.Namespace) -> logging.Logger: """Sets up various logging parameters. Args: @@ -98,7 +105,7 @@ def set_up_logging(args): return logger -def supported_platform(logger): +def supported_platform(logger: logging.Logger) -> bool: """Checks if this script is running on supported platform. Args: @@ -121,7 +128,7 @@ def supported_platform(logger): return True -def verify_hubble(args, logger): +def verify_hubble(args: argparse.Namespace, logger: logging.Logger) -> bool: # First, resolve the path if it's a symlink args.hubble = os.path.realpath(args.hubble) logger.debug("Hubble's realpath: %s", args.hubble) @@ -133,7 +140,7 @@ def verify_hubble(args, logger): return True -def adb_installed(logger): +def adb_installed(logger: logging.Logger) -> bool: cmd = ["which", "adb"] sw = SyscallWrapper(logger) sw.call_returnable_command(cmd) @@ -149,7 +156,8 @@ def clear_logcat(adb_wrapper): adb_wrapper.logcat_clear() -def is_hubble_installed(adb_wrapper, logger): +def is_hubble_installed(adb_wrapper: syscall_wrapper.AdbWrapper, + logger: logging.Logger) -> bool: """Checks if Hubble is already installed on device. Args: @@ -177,7 +185,8 @@ def is_hubble_installed(adb_wrapper, logger): return False -def is_xiaomi_phone(adb_wrapper, logger): +def is_xiaomi_phone(adb_wrapper: syscall_wrapper.AdbWrapper, + logger: logging.Logger) -> bool: """Checks if the connected device is a Xiami device. We do this by checking various strings within the device property for any @@ -209,13 +218,15 @@ def is_xiaomi_phone(adb_wrapper, logger): return False -def launch_xiaomi_file_explorer(adb_wrapper): +def launch_xiaomi_file_explorer( + adb_wrapper: syscall_wrapper.AdbWrapper) -> bool: return adb_wrapper.am_start( "com.mi.android.globalFileexplorer", "com.android.fileexplorer.FileExplorerTabActivity") -def adb_push_hubble(adb_wrapper, hubble_path): +def adb_push_hubble(adb_wrapper: syscall_wrapper.AdbWrapper, + hubble_path: str): """Drops the Hubble APK onto device (used when direct installation fails). Args: @@ -242,7 +253,9 @@ def adb_push_hubble(adb_wrapper, hubble_path): break -def install_hubble(adb_wrapper, args, logger): +def install_hubble(adb_wrapper: syscall_wrapper.AdbWrapper, + args: argparse.Namespace, + logger: logging.Logger) -> bool: """Performs installation of Hubble via "adb install". Args: @@ -308,11 +321,11 @@ def wait_for_results(adb_wrapper, logger): return adb_wrapper.logcat_find(patterns, terminate_logcat) -def classify_directory_using_adb_format(adb_wrapper, - source, - results_dir, - device, - logger): +def classify_dir_using_adb_format(adb_wrapper: syscall_wrapper.AdbWrapper, + source: str, + results_dir: str, + device: syscall_wrapper.DeviceInfo, + logger: logging.Logger)-> Optional[str]: """Decides which directory in results/ to dump new result to. This method is grandfathered as it was the original way of classification, @@ -349,10 +362,150 @@ def classify_directory_using_adb_format(adb_wrapper, return None -def classify_directory_using_build_fingerprint(adb_wrapper, - source, - results_dir, - logger): +def _retry_apk_extraction(adb_wrapper: syscall_wrapper.AdbWrapper, + retry_packages_dict: dict[str, str], + apks_dir: str, + logger: logging.Logger) -> dict[str, str]: + """Retries APK extraction using some hacks. + + The strategy here is to try to copy the APK to a different location + such as /data/local/tmp and then copying from the new location + instead. + + Args: + adb_wrapper: An AdbWrapper instance. + retry_packages_dict: A dictionary mapping package name to install + location on device to be retried. + apks_dir: A string representing the umbrella apks/ directory where + apks will be extracted into. + logger: A logger object to log debug or error messages. + + Returns: + A dictionary listing APKs that failed to be extracted. + """ + failed_packages_dict = dict() + for pkg_name in retry_packages_dict: + target_dir = os.path.abspath(os.path.join(apks_dir, pkg_name)) + os.makedirs(target_dir, exist_ok=True) + + # first, copy the APK to a "safe" location on device + tmp_dir = "/data/local/tmp" + install_location = retry_packages_dict[pkg_name] + cp_cmd = ["cp", install_location, tmp_dir] + if not adb_wrapper.shell(cp_cmd): + logger.warning("Failed to copy %s into %s.", install_location, + tmp_dir) + failed_packages_dict[pkg_name] = install_location + os.rmdir(target_dir) + continue + + # then, pull the APK from the "safe" location + apk_filename = os.path.basename(install_location) + new_apk_filepath = os.path.join(tmp_dir, apk_filename) + if not adb_wrapper.pull(new_apk_filepath, target_dir): + logger.debug("Failed to pull %s from %s", pkg_name, new_apk_filepath) + os.rmdir(target_dir) + failed_packages_dict[pkg_name] = install_location + continue + + # after successful extraction, delete the tmp copy of the file. + rm_cmd = ["rm", new_apk_filepath] + adb_wrapper.shell(rm_cmd) + + return failed_packages_dict + + +def extract_apks_from_device(adb_wrapper: syscall_wrapper.AdbWrapper, + packages_file_path: str, + apks_dir: str, + logger: logging.Logger) -> dict[str, str]: + """Extracts APKs from device according to a list. + + APKs that are enumerated in a config (JSON) file are extracted + to a local directory. + Note that this excludes the Hubble APK itself. + + Args: + adb_wrapper: An AdbWrapper instance. + packages_file_path: A string pointing to the location of a packages.txt + file containing a list of packages to be extracted. + apks_dir: The umbrella apks/ directory where apps will be + extracted into. + logger: A logger object to log debug or error messages. + + Returns: + A dictionary listing APKs that failed to be extracted. An empty dict is + return if there is no errors, or that there is nothing to be extracted. + """ + if not apks_dir: + logger.info("APKs dir is empty.") + return dict() + logger.debug("apks_dir: {}".format(apks_dir)) + + if not packages_file_path: + logger.error("packages_file_path is empty.") + return dict() + + if not os.path.isfile(packages_file_path): + logger.error("{} is an invalid file path.".format(packages_file_path)) + return dict() + + # Read in the file and convert the content into a JSON object + packages_buff = "" + with open(packages_file_path, "r") as f_in: + packages_buff = f_in.read() + packages_json = json.loads(packages_buff) + + failed_packages_dict = dict() + extracted_count = 0 + for package_json in packages_json["packages"]: + package_name = package_json["name"] + + package_install_location = package_json["installLocation"] + target_dir = os.path.abspath(os.path.join(apks_dir, package_name)) + os.makedirs(target_dir, exist_ok=True) + extracted_count += 1 + if not adb_wrapper.pull(package_install_location, target_dir): + logger.debug("Failed to pull %s from %s", package_name, + package_install_location) + # delete newly created empty directory + os.rmdir(target_dir) + extracted_count -= 1 + failed_packages_dict[package_name] = package_install_location + + failed_retry_packages_dict = _retry_apk_extraction(adb_wrapper, + failed_packages_dict, + apks_dir, + logger) + retry_success_count = (len(failed_packages_dict) - + len(failed_retry_packages_dict)) + extracted_count += retry_success_count + + logger.info("Successfully extracted %d packages (excluding Hubble).", + extracted_count) + logger.info("%d packages failed to be extracted.", + len(failed_retry_packages_dict)) + + return failed_retry_packages_dict + + +def write_dict_as_json_to_file(in_dict, file_path: str): + """Writes a dict as JSON string to file. + + Args: + in_dict: A dictionary containing (key, pair) values to be written. + file_path: A string representing a valid path to a file to be written. + """ + with open(file_path, "w") as f_out: + f_out.write(json.dumps(in_dict, indent=2)) + + +def classify_dir_using_build_fingerprint( + adb_wrapper: syscall_wrapper.AdbWrapper, + source: str, + results_dir: str, + extract_apks: bool, + logger: logging.Logger) -> Optional[str]: """Decides which directory in results/ to dump new result to. This is a renewed method that makes use of build fingerprint to do @@ -363,6 +516,8 @@ def classify_directory_using_build_fingerprint(adb_wrapper, adb_wrapper: An AdbWrapper instance. source: the source directory (on target device) containing new results. results_dir: the umbrella results/ directory. + extract_apks: A boolean indicating whether or not to also extract the APKs + from the target device. logger: A logger object to log debug or error messages. Returns: @@ -429,22 +584,47 @@ def classify_directory_using_build_fingerprint(adb_wrapper, logger.debug("{} does not exist yet! Using it!".format(target_dir)) break + apks_dir = "" + if extract_apks: + apks_dir = os.path.abspath(os.path.join(target_dir, "apks")) + os.makedirs(apks_dir) + if adb_pull_failed: source_dir = os.path.join("/tmp/untarred_hubble_results/apps", HUBBLE_PACKAGE_NAME, "ef", "results") shutil.move(source_dir, target_dir) + failed_extraction_dict = extract_apks_from_device( + adb_wrapper, os.path.join(target_dir, "results", "packages.txt"), + apks_dir, logger) + if failed_extraction_dict: + write_dict_as_json_to_file(failed_extraction_dict, + os.path.join(apks_dir, + "failed_extraction.txt")) return target_dir else: if adb_wrapper.pull(source, target_dir): + failed_extraction_dict = extract_apks_from_device( + adb_wrapper, os.path.join(target_dir, "results", "packages.txt"), + apks_dir, + logger) + if failed_extraction_dict: + write_dict_as_json_to_file(failed_extraction_dict, + os.path.join(apks_dir, + "failed_extraction.txt")) return target_dir return None -def extract_results(adb_wrapper, source, destination, - device, logger, use_old_classification=False): - """Extracts results from Hubble's execution. +def extract_results_and_apks(adb_wrapper: syscall_wrapper.AdbWrapper, + source: str, + destination: str, + device: syscall_wrapper.DeviceInfo, + logger: logging.Logger, + use_old_classification=False, + extract_apks=False) -> Optional[str]: + """Extracts results (and optionally APKs) from Hubble's execution. Args: adb_wrapper: An AdbWrapper object that is used to issue ADB commands. @@ -456,6 +636,8 @@ def extract_results(adb_wrapper, source, destination, classification of result, i.e. based on how ADB displays device information. This is defaulted to False. + extract_apks: A boolean indicating whether to also extract APKs from the + device or not. This is defaulted to False. Returns: A string representing the final directory (on host) where results are copied @@ -484,16 +666,23 @@ def extract_results(adb_wrapper, source, destination, os.makedirs(results_dir, exist_ok=True) if use_old_classification: - return classify_directory_using_adb_format(adb_wrapper, source, results_dir, - device, logger) - - return classify_directory_using_build_fingerprint(adb_wrapper, - source, - results_dir, - logger) - - -def extract_selinux_policies(adb_wrapper, target_dir, logger): + # Note that this method will not support pulling APKs from device. + return classify_dir_using_adb_format(adb_wrapper, + source, + results_dir, + device, + logger) + + return classify_dir_using_build_fingerprint(adb_wrapper, + source, + results_dir, + extract_apks, + logger) + + +def extract_selinux_policies(adb_wrapper: syscall_wrapper.AdbWrapper, + target_dir: str, + logger: logging.Logger): """Extracts SELinux policies from the device. Args: @@ -589,7 +778,7 @@ def main(): else: logger.info("This is not a Xiaomi phone. Regular workflow continues...") if not install_hubble(adb_wrapper, args, logger): - logger.error("Error installing Hubble: %s", args.error_message) + logger.error("Error installing Hubble: %s", adb_wrapper.error_message) continue clear_logcat(adb_wrapper) @@ -602,8 +791,14 @@ def main(): logger.error("Failed to obtain results from Hubble execution.") continue use_old_classification = args.use_old_results_classification is not None - results_dir = extract_results(adb_wrapper, results_source, args.output, - target_device, logger, use_old_classification) + extract_apks = args.pull_all_apks is not None + results_dir = extract_results_and_apks(adb_wrapper, + results_source, + args.output, + target_device, + logger, + use_old_classification, + extract_apks) if not results_dir: logger.error("Failed to extract results from target device (%s).", diff --git a/uraniborg/scripts/python/data/V-GSI.json b/uraniborg/scripts/python/data/V-GSI.json new file mode 100644 index 00000000..f6e91c53 --- /dev/null +++ b/uraniborg/scripts/python/data/V-GSI.json @@ -0,0 +1,358 @@ +{ + "packagesAll": [ + "android", + "android.auto_generated_rro_vendor__", + "android.ext.services", + "android.ext.shared", + "com.android.DeviceAsWebcam", + "com.android.adbd", + "com.android.adservices", + "com.android.adservices.api", + "com.android.apex.cts.shim", + "com.android.apps.tag", + "com.android.appsearch", + "com.android.appsearch.apk", + "com.android.art", + "com.android.avatarpicker", + "com.android.backupconfirm", + "com.android.bips", + "com.android.bluetooth", + "com.android.bluetoothmidiservice", + "com.android.bookmarkprovider", + "com.android.btservices", + "com.android.calendar", + "com.android.calllogbackup", + "com.android.camera2", + "com.android.cameraextensions", + "com.android.captiveportallogin", + "com.android.carrierconfig", + "com.android.carrierdefaultapp", + "com.android.cellbroadcast", + "com.android.cellbroadcastreceiver", + "com.android.cellbroadcastreceiver.module", + "com.android.cellbroadcastservice", + "com.android.certinstaller", + "com.android.companiondevicemanager", + "com.android.compos", + "com.android.compos.payload", + "com.android.configinfrastructure", + "com.android.connectivity.resources", + "com.android.conscrypt", + "com.android.contacts", + "com.android.credentialmanager", + "com.android.cts.ctsshim", + "com.android.cts.priv.ctsshim", + "com.android.deskclock", + "com.android.devicediagnostics", + "com.android.devicediagnostics.auto_generated_rro_vendor__", + "com.android.devicelock", + "com.android.devicelockcontroller", + "com.android.dialer", + "com.android.documentsui", + "com.android.dreams.basic", + "com.android.dreams.phototable", + "com.android.dynsystem", + "com.android.egg", + "com.android.emergency", + "com.android.externalstorage", + "com.android.extservices", + "com.android.federatedcompute.services", + "com.android.gallery3d", + "com.android.hardware.biometrics.face.virtual", + "com.android.hardware.biometrics.fingerprint.virtual", + "com.android.hardware.cas", + "com.android.health.connect.backuprestore", + "com.android.healthconnect.controller", + "com.android.healthfitness", + "com.android.hotspot2.osulogin", + "com.android.htmlviewer", + "com.android.i18n", + "com.android.imsserviceentitlement", + "com.android.inputdevices", + "com.android.inputmethod.latin", + "com.android.intentresolver", + "com.android.internal.display.cutout.emulation.corner", + "com.android.internal.display.cutout.emulation.double", + "com.android.internal.display.cutout.emulation.hole", + "com.android.internal.display.cutout.emulation.tall", + "com.android.internal.display.cutout.emulation.waterfall", + "com.android.internal.systemui.navbar.gestural", + "com.android.internal.systemui.navbar.threebutton", + "com.android.internal.systemui.navbar.transparent", + "com.android.ipsec", + "com.android.keychain", + "com.android.launcher3", + "com.android.localtransport", + "com.android.location.fused", + "com.android.managedprovisioning", + "com.android.media", + "com.android.media.swcodec", + "com.android.mediaprovider", + "com.android.messaging", + "com.android.microdroid.empty_payload", + "com.android.mms.service", + "com.android.modulemetadata", + "com.android.mtp", + "com.android.music", + "com.android.musicfx", + "com.android.networkstack", + "com.android.networkstack.tethering", + "com.android.neuralnetworks", + "com.android.nfc", + "com.android.nfcservices", + "com.android.omadm.service.auto_generated_rro_vendor__", + "com.android.ondevicepersonalization", + "com.android.ondevicepersonalization.services", + "com.android.ons", + "com.android.os.statsd", + "com.android.packageinstaller", + "com.android.pacprocessor", + "com.android.permission", + "com.android.permissioncontroller", + "com.android.phone", + "com.android.phone.auto_generated_rro_vendor__", + "com.android.printservice.recommendation", + "com.android.printspooler", + "com.android.profiling", + "com.android.providers.blockednumber", + "com.android.providers.calendar", + "com.android.providers.contactkeys", + "com.android.providers.contacts", + "com.android.providers.downloads", + "com.android.providers.downloads.ui", + "com.android.providers.media", + "com.android.providers.media.module", + "com.android.providers.partnerbookmarks", + "com.android.providers.settings", + "com.android.providers.settings.auto_generated_rro_vendor__", + "com.android.providers.telephony", + "com.android.providers.userdictionary", + "com.android.provision", + "com.android.proxyhandler", + "com.android.quicksearchbox", + "com.android.resolv", + "com.android.rkpd", + "com.android.rkpdapp", + "com.android.role.notes.enabled", + "com.android.runtime", + "com.android.safetycenter.resources", + "com.android.scheduling", + "com.android.sdkext", + "com.android.sdksandbox", + "com.android.se", + "com.android.server.deviceconfig.resources", + "com.android.server.telecom", + "com.android.settings", + "com.android.settings.auto_generated_rro_vendor__", + "com.android.settings.intelligence", + "com.android.sharedstoragebackup", + "com.android.shell", + "com.android.simappdialog", + "com.android.soundpicker", + "com.android.statementservice", + "com.android.stk", + "com.android.storagemanager", + "com.android.systemui", + "com.android.systemui.accessibility.accessibilitymenu", + "com.android.systemui.auto_generated_rro_vendor__", + "com.android.systemui.gsi.overlay", + "com.android.tethering", + "com.android.theme.font.notoserifsource", + "com.android.traceur", + "com.android.traceur.auto_generated_rro_vendor__", + "com.android.tzdata", + "com.android.uwb", + "com.android.uwb.resources", + "com.android.virt", + "com.android.virtualmachine.res", + "com.android.vndk.v30", + "com.android.vndk.v31", + "com.android.vndk.v32", + "com.android.vndk.v33", + "com.android.vndk.v34", + "com.android.vpndialogs", + "com.android.wallpaper", + "com.android.wallpaper.livepicker", + "com.android.wallpaperbackup", + "com.android.wallpapercropper", + "com.android.webview", + "com.android.wifi", + "com.android.wifi.dialog", + "com.android.wifi.resources", + "com.google.android.flipendo.auto_generated_rro_vendor__", + "com.google.android.hardware.biometrics.face", + "com.google.android.storagemanager.auto_generated_rro_vendor__", + "com.google.android.widevine.nonupdatable", + "com.google.pixel.camera.hal", + "com.google.pixel.euicc.update", + "com.google.pixel.wifi.ext", + "com.uraniborg.hubble", + "org.chromium.webview_shell" + ], + "packagesNoCode": [ + "com.google.android.widevine.nonupdatable", + "com.android.vndk.v32", + "com.android.configinfrastructure", + "android", + "com.android.cts.ctsshim", + "com.android.devicelock", + "com.android.internal.display.cutout.emulation.waterfall", + "com.android.vndk.v33", + "com.android.appsearch", + "com.android.sdkext", + "com.android.healthfitness", + "com.android.virt", + "com.android.runtime", + "com.android.uwb", + "com.android.appsearch.apk", + "com.android.rkpd", + "com.android.vndk.v30", + "com.android.internal.systemui.navbar.threebutton", + "com.android.art", + "com.android.hardware.biometrics.fingerprint.virtual", + "com.android.vndk.v34", + "com.google.pixel.wifi.ext", + "com.android.hardware.biometrics.face.virtual", + "com.android.btservices", + "com.android.virtualmachine.res", + "com.google.pixel.euicc.update", + "com.android.nfcservices", + "com.android.modulemetadata", + "com.android.compos.payload", + "com.android.os.statsd", + "com.android.theme.font.notoserifsource", + "com.google.android.hardware.biometrics.face", + "com.android.extservices", + "com.google.pixel.camera.hal", + "com.android.internal.display.cutout.emulation.hole", + "com.android.mediaprovider", + "com.android.compos", + "com.android.systemui.gsi.overlay", + "com.android.vndk.v31", + "com.android.adservices", + "com.android.role.notes.enabled", + "com.android.internal.display.cutout.emulation.corner", + "com.android.microdroid.empty_payload", + "com.android.connectivity.resources", + "com.android.media", + "com.android.i18n", + "com.android.cts.priv.ctsshim", + "com.android.adbd", + "com.android.internal.display.cutout.emulation.double", + "com.android.cellbroadcast", + "com.android.ondevicepersonalization", + "com.android.scheduling", + "com.android.resolv", + "com.android.internal.systemui.navbar.gestural", + "com.android.uwb.resources", + "com.android.permission", + "com.android.tzdata", + "com.android.wifi.resources", + "com.android.apex.cts.shim", + "com.android.server.deviceconfig.resources", + "com.android.conscrypt", + "com.android.hardware.cas", + "com.android.internal.systemui.navbar.transparent", + "com.android.internal.display.cutout.emulation.tall", + "com.android.wifi", + "com.android.neuralnetworks", + "com.android.tethering", + "com.android.media.swcodec", + "com.android.ipsec", + "com.android.profiling" + ], + "platformAppsAll": [ + "android", + "android.ext.shared", + "com.android.DeviceAsWebcam", + "com.android.avatarpicker", + "com.android.backupconfirm", + "com.android.bips", + "com.android.cameraextensions", + "com.android.carrierconfig", + "com.android.carrierdefaultapp", + "com.android.cellbroadcastreceiver", + "com.android.certinstaller", + "com.android.companiondevicemanager", + "com.android.credentialmanager", + "com.android.devicediagnostics", + "com.android.devicelockcontroller", + "com.android.documentsui", + "com.android.dynsystem", + "com.android.egg", + "com.android.emergency", + "com.android.externalstorage", + "com.android.inputdevices", + "com.android.intentresolver", + "com.android.keychain", + "com.android.localtransport", + "com.android.location.fused", + "com.android.managedprovisioning", + "com.android.messaging", + "com.android.mms.service", + "com.android.ondevicepersonalization.services", + "com.android.ons", + "com.android.packageinstaller", + "com.android.pacprocessor", + "com.android.permissioncontroller", + "com.android.phone", + "com.android.providers.settings", + "com.android.providers.telephony", + "com.android.provision", + "com.android.proxyhandler", + "com.android.role.notes.enabled", + "com.android.se", + "com.android.server.deviceconfig.resources", + "com.android.server.telecom", + "com.android.settings", + "com.android.sharedstoragebackup", + "com.android.shell", + "com.android.statementservice", + "com.android.stk", + "com.android.storagemanager", + "com.android.systemui", + "com.android.systemui.accessibility.accessibilitymenu", + "com.android.traceur", + "com.android.vpndialogs", + "com.android.wallpaperbackup", + "com.android.wallpapercropper" + ], + "platformAppsNoCode": [ + "com.android.role.notes.enabled", + "android", + "com.android.server.deviceconfig.resources" + ], + "packagesSharedUid": { + "android.uid.system": [ + "android", + "com.android.DeviceAsWebcam", + "com.android.dynsystem", + "com.android.inputdevices", + "com.android.keychain", + "com.android.localtransport", + "com.android.location.fused", + "com.android.providers.settings", + "com.android.server.telecom", + "com.android.settings" + ], + "com.android.emergency.uid": [ + "com.android.emergency" + ], + "android.uid.phone": [ + "com.android.mms.service", + "com.android.ons", + "com.android.phone", + "com.android.providers.telephony", + "com.android.stk" + ], + "android.uid.se": [ + "com.android.se" + ], + "android.uid.shell": [ + "com.android.shell" + ], + "android.uid.systemui": [ + "com.android.systemui" + ] + } +} diff --git a/uraniborg/scripts/python/package_whitelists.py b/uraniborg/scripts/python/package_whitelists.py index 1d8838b0..a9c4ec66 100644 --- a/uraniborg/scripts/python/package_whitelists.py +++ b/uraniborg/scripts/python/package_whitelists.py @@ -49,6 +49,7 @@ class BaselinePackages: 31: "data/S-GSI.json", 33: "data/T-GSI.json", # per https://developer.android.com/reference/android/os/Build.VERSION_CODES#TIRAMISU 34: "data/U-GSI.json", # per https://developer.android.com/reference/android/os/Build.VERSION_CODES#UPSIDE_DOWN_CAKE + 35: "data/V-GSI.json", # per https://developer.android.com/reference/android/os/Build.VERSION_CODES#VANILLA_ICE_CREAM } @staticmethod diff --git a/uraniborg/scripts/python/risk_analyzer.py b/uraniborg/scripts/python/risk_analyzer.py index f4a642c7..f345a149 100644 --- a/uraniborg/scripts/python/risk_analyzer.py +++ b/uraniborg/scripts/python/risk_analyzer.py @@ -187,6 +187,8 @@ def __init__(self, logger): @staticmethod def get_scorer(api_level, logger): + if api_level == 35: + return VPlatformSignature(logger) if api_level == 34: logger.debug("Returning UPlatformSignature") return UPlatformSignature(logger) @@ -327,6 +329,11 @@ class UPlatformSignature(PlatformSignature): BASE_SCORE = 50 +class VPlatformSignature(PlatformSignature): + API_LEVEL = 35 + BASE_SCORE = 51 + + class SystemUid(RiskAnalyzer): """Analyzer for system uid risks. """ @@ -340,6 +347,9 @@ def __init__(self, logger): @staticmethod def get_scorer(api_level, logger): + if api_level == 35: + logger.debug("returning VSystemUid") + return VSystemUid(logger) if api_level == 34: logger.debug("Returning USystemUid") return USystemUid(logger) @@ -439,6 +449,11 @@ class USystemUid(SystemUid): BASE_SCORE = 33 +class VSystemUid(SystemUid): + API_LEVEL = 35 + BASE_SCORE = 35 + + class RiskyPermissions(RiskAnalyzer): """The risky permission risk analyzer. @@ -462,12 +477,17 @@ class Permissions: The permissions are evaluated from the Android framework, and stack ranked according to the severity of the impact/risk of the permissions. This was done based on inputs from security experts. + + This list is currently in sync with permissions weighted in + https://github.com/DEKRA-Cybersecurity/MAS-Preloaded-Apps-Scripts/blob/main/config/methods_config.yml + up till Android 14. """ RISK_ASTRONOMICAL = { "android.permission.INSTALL_PACKAGES" } RISK_CRITICAL = { + "android.permission.PROVIDE_REMOTE_CREDENTIALS", "android.permission.COPY_PROTECTED_DATA", "android.permission.WRITE_SECURE_SETTINGS", "android.permission.READ_FRAME_BUFFER", @@ -479,6 +499,7 @@ class Permissions: "android.permission.SYSTEM_CAMERA", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS", "android.permission.MOUNT_UNMOUNT_FILESYSTEMS", + "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE", } RISK_HIGH = { @@ -503,6 +524,9 @@ class Permissions: "android.permission.BLUETOOTH_PRIVILEGED", "android.permission.GET_PASSWORD", "android.permission.INTERNAL_SYSTEM_WINDOW", + "android.permission.MANAGE_ONGOING_CALLS", + "android.permission.READ_RESTRICTED_STATS", + "android.permission.BIND_AUTOFILL_SERVICE", } RISK_MEDIUM = { @@ -522,7 +546,12 @@ class Permissions: "android.permission.READ_CALENDAR", "android.permission.BLUETOOTH_ADMIN", "android.permission.BODY_SENSORS", - + "android.permission.MANAGE_EXTERNAL_STORAGE", + "android.permission.ACCESS_BLOBS_ACROSS_USERS", + "android.permission.BLUETOOTH_ADVERTISE", + "android.permission.READ_MEDIA_AUDIO", + "android.permission.READ_MEDIA_IMAGES", + "android.permission.READ_MEDIA_VIDEO", } RISK_LOW = { @@ -533,10 +562,16 @@ class Permissions: "android.permission.GET_PACKAGE_SIZE", "android.permission.BLUETOOTH", "android.permission.DEVICE_POWER", + "android.permission.READ_PRECISE_PHONE_STATE", + "android.permission.LOG_FOREGROUND_RESOURCE_USE", + "android.permission.MANAGE_DEFAULT_APPLICATIONS", + "android.permission.MANAGE_FACE", } @staticmethod def get_scorer(api_level, logger, google_discount=False): + if api_level == 35: + return VRiskyPermissions(logger, google_discount) if api_level == 34: return URiskyPermissions(logger, google_discount) if api_level == 33: @@ -775,6 +810,13 @@ class URiskyPermissions(RiskyPermissions): BASE_SCORE = 542.50 UNGRANTED_SCORE = 162.50 + +class VRiskyPermissions(RiskyPermissions): + API_LEVEL = 35 + BASE_SCORE = 735 + UNGRANTED_SCORE = 290 + + class CleartextTraffic(RiskAnalyzer): """Cleartext traffic risk analyzer. @@ -786,6 +828,8 @@ class CleartextTraffic(RiskAnalyzer): @staticmethod def get_scorer(api_level, logger, google_discount=False): + if api_level == 35: + return VCleartextTraffic(logger, google_discount) if api_level == 34: return UCleartextTraffic(logger, google_discount) if api_level == 33: @@ -917,6 +961,10 @@ class UCleartextTraffic(CleartextTraffic): BASE_SCORE = 8 +class VCleartextTraffic(CleartextTraffic): + BASE_SCORE = 2 + + class HostileDownloader(RiskAnalyzer): """Analyzer assessing hostile downloader security risk. """