├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── edu │ │ └── amd │ │ └── spbstu │ │ └── rootcheckermeasure │ │ ├── GetTimeStampService.java │ │ ├── MainActivity.java │ │ └── experiments │ │ ├── ExperimentListener.java │ │ ├── TimeExperiment.java │ │ └── features │ │ ├── Feature.java │ │ ├── PackageManagerFeature.java │ │ ├── ShellTimeFeature.java │ │ └── app_start_time │ │ ├── AppStartTimeFeature.java │ │ ├── HideTimeFeature.java │ │ └── NormTimeFeature.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── array.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── roothidelist ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher_round.zip │ ├── java │ └── edu │ │ └── amd │ │ └── spbstu │ │ └── roothidelist │ │ ├── CreationTimeService.java │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── rootnormalapp ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── edu │ │ └── amd │ │ └── spbstu │ │ └── rootnormalapp │ │ ├── CreationTimeService.java │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Root Checker (Measurer Module) 2 | A project to gather information about Android device configurations based on some features (a.e. execution time of a command in a shell). 3 | 4 | This module is used as a part of my bachelor thesis about detection an access to the superuser account on an Android device, which is hidden by Magisk. 5 | The target is to find Magisk on a device, assuming that the module of detection can be placed into Magisk Hide List. 6 | The approach uses side-channel attacks. This project is used to gather data about a device to build a statistical/ML models then (not in the project). 7 | 8 | ## Project structure 9 | The project consists of 3 Android apps. 10 | * `app` is an app to gather information about device (main module) 11 | * `roothidelist` is an app that needs to be placed into Magisk Hide List 12 | * It is used by main module to determine the launch time of an app in Magisk Hide List 13 | * `rootnormalapp` is an app that is not placed into Magisk Hide List 14 | * It is used by main module to determine the launch time of an app not in Magisk Hide List 15 | 16 | ## Gathered features 17 | * Time needed to execute a shell command that needs superuser account to run (a.e. `su`) 18 | * Time needed for `PackageManager` to return the list of installed apps 19 | * Time needed for an app from Magisk Hide List to launch 20 | * Time needed for an app not from Magisk Hide List to launch 21 | 22 | Barinov Fedor, 43601/2, St.Petersburg Polytechnic University, 2019. 23 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "edu.amd.spbstu.rootcheckermeasure" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/GetTimeStampService.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure; 2 | 3 | import android.app.IntentService; 4 | import android.app.Notification; 5 | import android.app.NotificationChannel; 6 | import android.app.NotificationManager; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import android.support.v4.app.NotificationCompat; 11 | 12 | /** 13 | * A service which is used to obtain timestamp from another app and the kind of this app (Hide List or not) 14 | */ 15 | 16 | public class GetTimeStampService extends IntentService { 17 | public static final String ROOT_CHECKER_PROCEED_TIME_STAMP = "ROOT_CHECKER_PROCEED_TIME_STAMP"; 18 | 19 | public GetTimeStampService() { 20 | super("GetTimeStampService"); 21 | } 22 | 23 | @Override 24 | public void onCreate() { 25 | super.onCreate(); 26 | //Create notification channel used to obtain data from another app 27 | if (Build.VERSION.SDK_INT >= 26) { 28 | final String CH_ID = "GS_SERV_CH_ID"; 29 | NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 30 | NotificationChannel channel = new NotificationChannel(CH_ID, "Time Stamp Service", NotificationManager.IMPORTANCE_LOW); 31 | channel.setVibrationPattern(new long[]{ 0 }); 32 | channel.enableVibration(false); 33 | manager.createNotificationChannel(channel); 34 | Notification notification = new NotificationCompat.Builder(GetTimeStampService.this, CH_ID).setContentTitle("").setContentText("").build(); 35 | startForeground(1, notification); 36 | } 37 | } 38 | 39 | @Override 40 | protected void onHandleIntent(Intent intent) { 41 | Intent broadcastIntent = new Intent(ROOT_CHECKER_PROCEED_TIME_STAMP); 42 | broadcastIntent.putExtra("ROOT_CHECKER_TIME_STAMP", intent.getLongExtra("ROOT_CHECKER_TIME_STAMP", 0)); 43 | broadcastIntent.putExtra("ROOT_CHECKER_APP", intent.getIntExtra("ROOT_CHECKER_APP", 0)); 44 | broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 45 | sendBroadcast(broadcastIntent); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/MainActivity.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Environment; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.os.Bundle; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.ProgressBar; 14 | import android.widget.SeekBar; 15 | import android.widget.TextView; 16 | 17 | import java.io.File; 18 | import java.io.FileWriter; 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | import edu.amd.spbstu.rootcheckermeasure.experiments.ExperimentListener; 23 | import edu.amd.spbstu.rootcheckermeasure.experiments.TimeExperiment; 24 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.Feature; 25 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time.HideTimeFeature; 26 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time.NormTimeFeature; 27 | 28 | /** 29 | * REFERENCE: 30 | * * MEASUREMENT is a tuple of features that reflects instant device configuration 31 | * * * Features used in a measurement is set up manually (predefined in the experiment object) 32 | * * * Only time measurements are used at the moment so TimeExperiment object is used 33 | * * EXPERIMENT is a process of gathering measurements. 34 | * * * Can be started by pushing 'start' button 35 | * * * Each experiment gathers info about current configuration 36 | * * * The number of configuration measurements can be set manually (with SeekBar) 37 | * * * Info gathered from experiment is written to external storage folder 38 | * * * * If permission was not granted then 'start' button is disabled 39 | * * EXPERIMENT RUN = MEASUREMENT 40 | */ 41 | 42 | public class MainActivity extends AppCompatActivity implements ExperimentListener { 43 | private static final int WRITE_REQUEST_CODE = 1; // A code for a permission to write to an external storage 44 | 45 | private int[] seekBarValues; // Possible experiments number, used in the seek bar 46 | private boolean experimentRunning; // Current data gathering status 47 | private TimeExperiment experiment; // An experiment object 48 | 49 | /** 50 | * Measurements number seek bar initialization 51 | */ 52 | private void initializeSeekBar() { 53 | SeekBar seekBar = findViewById(R.id.seekBar); 54 | 55 | seekBar.setMax(seekBarValues.length - 1); 56 | seekBar.setOnSeekBarChangeListener( 57 | new SeekBar.OnSeekBarChangeListener() 58 | { 59 | @Override 60 | public void onStopTrackingTouch(SeekBar seekBar) {} 61 | 62 | @Override 63 | public void onStartTrackingTouch(SeekBar seekBar) {} 64 | 65 | /** 66 | * Update current number of measurements on the screen 67 | * @param seekBar The seek bar with measurements quantity 68 | * @param progress Current seek bar index 69 | * @param fromUser NOT USED 70 | */ 71 | @Override 72 | public void onProgressChanged(SeekBar seekBar, int progress, 73 | boolean fromUser) { 74 | TextView selectedRuns = findViewById(R.id.seekBarSelectedRuns); 75 | String message = Integer.toString(seekBarValues[progress]); 76 | selectedRuns.setText(message); 77 | } 78 | }); 79 | } 80 | 81 | @Override 82 | protected void onCreate(Bundle savedInstanceState) { 83 | super.onCreate(savedInstanceState); 84 | setContentView(R.layout.activity_main); 85 | seekBarValues = getResources().getIntArray(R.array.seek_bar_values); // Possible measurements number is predefined (see res) 86 | initializeSeekBar(); 87 | 88 | // Setting up initial number of measurements (since listener is attached only to SB position change) 89 | TextView currentRuns = findViewById(R.id.seekBarSelectedRuns); 90 | currentRuns.setText(Integer.toString(seekBarValues[0])); 91 | 92 | // Check for external storage permission since experiment info is written to external storage folder 93 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) 94 | ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_REQUEST_CODE); 95 | 96 | experimentRunning = false; 97 | findViewById(R.id.runTest).setOnClickListener(new View.OnClickListener() { 98 | @Override 99 | public void onClick(View run_button) { 100 | if (!experimentRunning) { // Double click prevention (only one experiment can be launched) 101 | experimentRunning = true; 102 | run_button.setEnabled(false); 103 | onClickStartButton(); 104 | } 105 | } 106 | }); 107 | 108 | experiment = new TimeExperiment(this); 109 | } 110 | 111 | @Override 112 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 113 | switch (requestCode) { 114 | case WRITE_REQUEST_CODE: 115 | // If access was not granted then disable a launch button since experiments data can not be saved 116 | if (!(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { 117 | Button run = findViewById(R.id.runTest); 118 | run.setEnabled(false); 119 | } 120 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 121 | } 122 | } 123 | 124 | @Override 125 | protected void onDestroy() { 126 | experiment.destroy(); 127 | super.onDestroy(); 128 | } 129 | 130 | /** 131 | * Write an experiment measurements to a predefined folder at external storage 132 | * * TSV convention is used 133 | * * A file is placed at an external storage in a predefined folder (see res). 134 | * * A filename is modified with current timestamp so a couple of experiments in a row may be launched 135 | * @param features List of Feature objects (Feature object is a collection of all the data about one of the features) 136 | * @param filename A name of the file to which a measurement is written 137 | */ 138 | private void writeExperimentData(List features, String filename) { 139 | try { 140 | File measurementsDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), getString(R.string.write_dir_name)); 141 | if (!measurementsDir.exists()) 142 | measurementsDir.mkdirs(); 143 | File measurements = new File(measurementsDir, filename + Long.toString(System.currentTimeMillis()) + ".txt"); 144 | FileWriter fileWriter = new FileWriter(measurements); 145 | 146 | // Write header 147 | for (Feature feature : features) 148 | fileWriter.write(feature.getFeatureName() + "\t"); 149 | fileWriter.write("\n"); 150 | 151 | // Write measurements 152 | int entriesNum = features.get(0).size(); 153 | for (int i = 0; i < entriesNum; ++i) { 154 | for (Feature feature : features) 155 | fileWriter.write(feature.getString(i) + "\t"); 156 | fileWriter.write("\n"); 157 | } 158 | fileWriter.flush(); 159 | fileWriter.close(); 160 | } catch (IOException e) { 161 | //Do nothing 162 | } 163 | } 164 | 165 | /** 166 | * Write measurements and end the experiment 167 | * @param features 168 | */ 169 | @Override 170 | public void onExperimentReady(List features) { 171 | writeExperimentData(features, getString(R.string.experiment_file_name)); 172 | 173 | // Update current experiment status to the 'done' 174 | runOnUiThread(new Runnable() { 175 | @Override 176 | public void run() { 177 | TextView text = findViewById(R.id.status); 178 | text.setText(R.string.status_done); 179 | experimentRunning = false; 180 | Button run_button = findViewById(R.id.runTest); 181 | run_button.setEnabled(true); 182 | } 183 | }); 184 | } 185 | 186 | /** 187 | * Update current experiment status (show current measurement number) 188 | * @param run A current measurement number 189 | * @param runs A total number of measurements 190 | */ 191 | @Override 192 | public void onExperimentRunReady(int run, int runs) { 193 | final String status = getString(R.string.experiment_name) + " " + Integer.toString(run + 1) + "/" + Integer.toString(runs); 194 | final int currentRun = run; 195 | 196 | runOnUiThread(new Runnable() { 197 | @Override 198 | public void run() { 199 | TextView statusView = findViewById(R.id.status); 200 | statusView.setText(status); 201 | ProgressBar progressBar = findViewById(R.id.progressBar); 202 | progressBar.setProgress(currentRun); 203 | } 204 | }); 205 | } 206 | 207 | /** 208 | * Launch an experiment on 'start' button click 209 | * * The experiment routine is launched on another thread since being time consuming 210 | */ 211 | public void onClickStartButton() { 212 | SeekBar seekBar = findViewById(R.id.seekBar); 213 | final int runs = seekBarValues[seekBar.getProgress()]; 214 | ProgressBar progressBar = findViewById(R.id.progressBar); 215 | progressBar.setMax(runs - 1); 216 | progressBar.setProgress(0); 217 | new Thread(new Runnable() { 218 | @Override 219 | public void run() { 220 | experiment.run(runs); 221 | } 222 | }).start(); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/ExperimentListener.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments; 2 | 3 | import java.util.List; 4 | 5 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.Feature; 6 | 7 | /** 8 | * Features measuring experiment listener 9 | */ 10 | public interface ExperimentListener { 11 | /** 12 | * On single measurement is gathered 13 | * @param run A current measurement index 14 | * @param runs A total number of measurements 15 | */ 16 | void onExperimentRunReady(int run, int runs); 17 | 18 | /** 19 | * On experiment is over 20 | * @param featuresStamps List of gathered features 21 | */ 22 | void onExperimentReady(List featuresStamps); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/TimeExperiment.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments; 2 | 3 | import android.app.Activity; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import edu.amd.spbstu.rootcheckermeasure.MainActivity; 10 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.Feature; 11 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.PackageManagerFeature; 12 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.ShellTimeFeature; 13 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time.HideTimeFeature; 14 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time.NormTimeFeature; 15 | 16 | /** 17 | * Experiment, all features of which are time features 18 | */ 19 | public class TimeExperiment { 20 | private static Class[] defaultFeaturesClasses; // Classes of features used by default 21 | private static Class[][] defaultFeaturesClassesConstructors; // Constructors of features used by default 22 | 23 | // Setting features used by default 24 | static { 25 | defaultFeaturesClasses = new Class[]{HideTimeFeature.class, NormTimeFeature.class, PackageManagerFeature.class, ShellTimeFeature.class}; 26 | defaultFeaturesClassesConstructors = new Class[][] { 27 | new Class[] {MainActivity.class}, 28 | new Class[] {MainActivity.class}, 29 | new Class[] {Activity.class}, 30 | new Class[] {Activity.class} 31 | }; 32 | } 33 | 34 | private List featuresArray; // A list of Feature objects (each object is a collection of a single feature measurements) 35 | private MainActivity currentActivity; // A current activity (needs to be an Activity to be passed to Features constructors, needs to be ExperimentListener since it is an experiment) 36 | private Class[] featuresClasses; // Classes of features used in a current experiment 37 | private Class[][] featuresClassesConstructors; // Constructors of features used in an experiment 38 | 39 | public TimeExperiment(MainActivity activity) { 40 | this.currentActivity = activity; 41 | this.featuresClasses = defaultFeaturesClasses; 42 | this.featuresClassesConstructors = defaultFeaturesClassesConstructors; 43 | } 44 | 45 | /** 46 | * Use custom set if features 47 | * @param featuresClasses Classes of features to be used 48 | * @param featuresClassesConstructors Constructors of features to be used 49 | */ 50 | public void useFeatures(Class[] featuresClasses, Class[][] featuresClassesConstructors) { 51 | this.featuresClasses = featuresClasses; 52 | this.featuresClassesConstructors = featuresClassesConstructors; 53 | } 54 | 55 | /** 56 | * Create features objects from classes used in a current experiment 57 | */ 58 | private void createFeatures() { 59 | this.featuresArray = new ArrayList<>(); 60 | for (int i = 0; i < featuresClasses.length; ++i) { 61 | try { 62 | featuresArray.add((Feature)featuresClasses[i].getConstructor(featuresClassesConstructors[i]).newInstance(currentActivity)); 63 | } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { 64 | //Do nothing 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Clear features storage 71 | */ 72 | private void clearFeatures() { 73 | for (Feature feature : featuresArray) 74 | feature.clear(); 75 | } 76 | 77 | /** 78 | * Run the experiment 79 | * @param runs A total number of runs 80 | */ 81 | public void run(int runs) { 82 | createFeatures(); 83 | clearFeatures(); 84 | // For each feature run it's gatherer routine 85 | for (int i = 0; i < runs; ++i) { 86 | for (Feature feature : featuresArray) 87 | feature.runRoutine(); 88 | currentActivity.onExperimentRunReady(i, runs); 89 | } 90 | currentActivity.onExperimentReady(featuresArray); 91 | } 92 | 93 | public void destroy() { 94 | for (Feature feature : featuresArray) 95 | feature.destroy(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/Feature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features; 2 | 3 | /** 4 | * A collection of measurements made during an experiment for a single feature and its' gatherer 5 | */ 6 | public interface Feature { 7 | /** 8 | * Run the routine which collects a single measurement of a feature 9 | */ 10 | void runRoutine(); 11 | 12 | /** 13 | * Get a measurement by index as a string 14 | * @param i A measurement index 15 | * @return A measurement as a string 16 | */ 17 | String getString(int i); 18 | 19 | /** 20 | * Clear the storage 21 | */ 22 | void clear(); 23 | 24 | /** 25 | * Get current collection size 26 | * @return A collection size 27 | */ 28 | int size(); 29 | 30 | /** 31 | * Get the name of a feature 32 | * @return The name of a feature 33 | */ 34 | String getFeatureName(); 35 | 36 | /** 37 | * Routine to be called on activity destroy 38 | */ 39 | void destroy(); 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/PackageManagerFeature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.pm.ResolveInfo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import edu.amd.spbstu.rootcheckermeasure.R; 11 | 12 | /** 13 | * PackageManager response time feature 14 | * The time needed for PackageManager to return the list of installed packages 15 | */ 16 | public class PackageManagerFeature implements Feature { 17 | private List stamps; 18 | private long timeStamp; 19 | private Activity activity; 20 | 21 | public PackageManagerFeature(Activity activity) { 22 | this.activity = activity; 23 | stamps = new ArrayList<>(); 24 | } 25 | 26 | @Override 27 | public void runRoutine() { 28 | String pkgName = callPackageManager(); 29 | // Consider execution time as zero if a request to PackageManager has failed 30 | stamps.add(pkgName == null ? 0 : timeStamp); 31 | } 32 | 33 | /** 34 | * Call the PackageManager method and measure the time needed to execute it 35 | * * The execution time is returned in timeStamp field 36 | * @return The name of the first package in a package list if obtained, null otherwise 37 | */ 38 | private String callPackageManager() { 39 | Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 40 | mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 41 | timeStamp = System.nanoTime(); 42 | List pkgAppsList = activity.getPackageManager().queryIntentActivities( mainIntent, 0); 43 | timeStamp = System.nanoTime() - timeStamp; 44 | return pkgAppsList == null || pkgAppsList.isEmpty() ? null : pkgAppsList.get(0).activityInfo.packageName; 45 | } 46 | 47 | @Override 48 | public String getFeatureName() { 49 | return activity.getString(R.string.feature_pm); 50 | } 51 | 52 | @Override 53 | public String getString(int i) { 54 | return Long.toString(stamps.get(i)); 55 | } 56 | 57 | @Override 58 | public int size() { 59 | return stamps.size(); 60 | } 61 | 62 | @Override 63 | public void clear() { 64 | stamps.clear(); 65 | } 66 | 67 | @Override 68 | public void destroy() { 69 | //Do nothing 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/ShellTimeFeature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features; 2 | 3 | import android.app.Activity; 4 | 5 | import java.io.DataOutputStream; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import edu.amd.spbstu.rootcheckermeasure.R; 11 | 12 | /** 13 | * Shell command execution time feature 14 | * * The command demands root access (a.e. 'su') 15 | */ 16 | public class ShellTimeFeature implements Feature { 17 | private List stamps; 18 | private long timeStamp; 19 | private Activity activity; 20 | 21 | public ShellTimeFeature(Activity activity) { 22 | this.activity = activity; 23 | stamps = new ArrayList<>(); 24 | } 25 | 26 | @Override 27 | public void runRoutine() { 28 | boolean rooted = callShell(); 29 | // Consider execution time as zero if command graph differs from the one used on not-rooted device (means device is rooted) 30 | stamps.add(rooted ? 0 : timeStamp); 31 | } 32 | 33 | /** 34 | * Launch shell command routine 35 | * @return A device is rooted (determined on different command flow graph than expected) or an error has occured 36 | */ 37 | private boolean callShell() { 38 | try { 39 | return suExecutionCheck(); 40 | } catch (IOException | InterruptedException e) { 41 | return true; 42 | } 43 | } 44 | 45 | /** 46 | * Exit the SU terminal 47 | * @param suProcess A shell with SU terminal 48 | */ 49 | private void suWriteExitSequence(Process suProcess) throws IOException { 50 | try (DataOutputStream outputStream = new DataOutputStream(suProcess.getOutputStream())) { 51 | outputStream.writeBytes("exit\n"); 52 | outputStream.flush(); 53 | } 54 | } 55 | 56 | /** 57 | * A SU command execution time measurement routine 58 | * @return Check whether CFG differs from expected one 59 | */ 60 | private boolean suExecutionCheck() throws IOException, InterruptedException { 61 | Process suProcess; 62 | try { 63 | timeStamp = System.nanoTime(); 64 | suProcess = Runtime.getRuntime().exec("su"); 65 | } catch (IOException e) { 66 | timeStamp = System.nanoTime() - timeStamp; 67 | return false; 68 | } 69 | suWriteExitSequence(suProcess); 70 | suProcess.waitFor(); 71 | return true; 72 | } 73 | 74 | @Override 75 | public String getString(int i) { 76 | return Long.toString(stamps.get(i)); 77 | } 78 | 79 | @Override 80 | public String getFeatureName() { 81 | return activity.getString(R.string.feature_shell); 82 | } 83 | 84 | @Override 85 | public int size() { 86 | return stamps.size(); 87 | } 88 | 89 | @Override 90 | public void clear() { 91 | stamps.clear(); 92 | } 93 | 94 | @Override 95 | public void destroy() { 96 | //Do nothing 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/app_start_time/AppStartTimeFeature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.support.v4.content.ContextCompat; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import edu.amd.spbstu.rootcheckermeasure.GetTimeStampService; 13 | import edu.amd.spbstu.rootcheckermeasure.MainActivity; 14 | import edu.amd.spbstu.rootcheckermeasure.experiments.features.Feature; 15 | 16 | /** 17 | * App launch time feature (abstract) 18 | */ 19 | abstract class AppStartTimeFeature implements Feature { 20 | static final String SERVICE_NAME = ".CreationTimeService"; // A name of the service that returns timestamp at the time of launch 21 | 22 | private List stamps; 23 | MainActivity activity; 24 | 25 | private long timeStampBegin; 26 | private TimeStampReceiver timeStampReceiver; 27 | private Intent serviceIntent; 28 | private boolean readyTest; 29 | 30 | /** 31 | * Receiver of broadcasts from GetTimeStampService that is called by launched apps 32 | */ 33 | private class TimeStampReceiver extends BroadcastReceiver { 34 | @Override 35 | public void onReceive(Context context, Intent intent) { 36 | // Get app ID and launch timestamp from CreationTimeService implemented in launched app 37 | int app_id = intent.getIntExtra("ROOT_CHECKER_APP", 0); 38 | if (app_id == getAppId()) 39 | stamps.add(intent.getLongExtra("ROOT_CHECKER_TIME_STAMP", 0) - timeStampBegin); 40 | readyTest = true; 41 | } 42 | } 43 | 44 | /** 45 | * Get app ID which allows to determine what kind of app was launched 46 | * @return An app ID 47 | */ 48 | protected abstract int getAppId(); 49 | 50 | /** 51 | * Create service intent which allows to start a CreationTimeService service of a specific app 52 | * @return A service intent 53 | */ 54 | protected abstract Intent createServiceIntent(); 55 | 56 | AppStartTimeFeature(MainActivity activity) { 57 | this.activity = activity; 58 | this.stamps = new ArrayList<>(); 59 | 60 | timeStampReceiver = new TimeStampReceiver(); 61 | IntentFilter intentFilter = new IntentFilter(GetTimeStampService.ROOT_CHECKER_PROCEED_TIME_STAMP); 62 | this.activity.registerReceiver(timeStampReceiver, intentFilter); 63 | 64 | serviceIntent = createServiceIntent(); 65 | } 66 | 67 | @Override 68 | public String getString(int i) { 69 | return Long.toString(stamps.get(i)); 70 | } 71 | 72 | @Override 73 | public void runRoutine() { 74 | try { 75 | timeStampBegin = System.nanoTime(); 76 | ContextCompat.startForegroundService(activity, serviceIntent); 77 | readyTest = false; 78 | // Busy waiting until the launch time is obtained 79 | while (!readyTest) 80 | Thread.sleep(200); 81 | } 82 | catch (Exception e){ 83 | //Do nothing 84 | } 85 | } 86 | 87 | @Override 88 | public void clear() { 89 | stamps.clear(); 90 | } 91 | 92 | @Override 93 | public int size() { 94 | return stamps.size(); 95 | } 96 | 97 | @Override 98 | public void destroy() { 99 | activity.unregisterReceiver(timeStampReceiver); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/app_start_time/HideTimeFeature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | 6 | import edu.amd.spbstu.rootcheckermeasure.MainActivity; 7 | import edu.amd.spbstu.rootcheckermeasure.R; 8 | 9 | /** 10 | * Launch time of an app from Magisk Hide List 11 | */ 12 | public class HideTimeFeature extends AppStartTimeFeature { 13 | private static final String HIDELIST_APP_NAME = "edu.amd.spbstu.roothidelist"; 14 | 15 | public HideTimeFeature(MainActivity activity) { 16 | super(activity); 17 | } 18 | 19 | @Override 20 | protected int getAppId() { 21 | return 1; // Predefined ID determines whether it is 'Hide List' app 22 | } 23 | 24 | @Override 25 | protected Intent createServiceIntent() { 26 | Intent serviceIntentHide = new Intent(); 27 | serviceIntentHide.setComponent(new ComponentName(HIDELIST_APP_NAME, HIDELIST_APP_NAME + SERVICE_NAME)); 28 | return serviceIntentHide; 29 | } 30 | 31 | @Override 32 | public String getFeatureName() { 33 | return activity.getString(R.string.feature_hide); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/edu/amd/spbstu/rootcheckermeasure/experiments/features/app_start_time/NormTimeFeature.java: -------------------------------------------------------------------------------- 1 | package edu.amd.spbstu.rootcheckermeasure.experiments.features.app_start_time; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | 6 | import edu.amd.spbstu.rootcheckermeasure.MainActivity; 7 | import edu.amd.spbstu.rootcheckermeasure.R; 8 | 9 | /** 10 | * Launch time of an app NOT from Magisk Hide List 11 | */ 12 | public class NormTimeFeature extends AppStartTimeFeature { 13 | private static final String NORMAL_APP_NAME = "edu.amd.spbstu.rootnormalapp"; 14 | 15 | public NormTimeFeature(MainActivity activity) { 16 | super(activity); 17 | } 18 | 19 | @Override 20 | protected int getAppId() { 21 | return 2; // Predefined ID determines whether it is NOT 'Hide List' app 22 | } 23 | 24 | @Override 25 | protected Intent createServiceIntent() { 26 | Intent serviceIntentNorm = new Intent(); 27 | serviceIntentNorm.setComponent(new ComponentName(NORMAL_APP_NAME, NORMAL_APP_NAME + SERVICE_NAME)); 28 | return serviceIntentNorm; 29 | } 30 | 31 | @Override 32 | public String getFeatureName() { 33 | return activity.getString(R.string.feature_norm); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |