jailbreak-root-detection 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/JailbreakRootDetection.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +53 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/smartwinnr/plugins/jailbreakrootdetection/Const.java +134 -0
- package/android/src/main/java/com/smartwinnr/plugins/jailbreakrootdetection/JailbreakRootDetection.java +706 -0
- package/android/src/main/java/com/smartwinnr/plugins/jailbreakrootdetection/JailbreakRootDetectionPlugin.java +32 -0
- package/android/src/main/java/com/smartwinnr/plugins/jailbreakrootdetection/NonceUtil.java +26 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +47 -0
- package/dist/esm/definitions.d.ts +13 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +15 -0
- package/dist/esm/web.js +12 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +28 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/JailbreakRootDetectionPlugin/JailbreakRootDetection.swift +361 -0
- package/ios/Sources/JailbreakRootDetectionPlugin/JailbreakRootDetectionPlugin.swift +22 -0
- package/ios/Tests/JailbreakRootDetectionPluginTests/JailbreakRootDetectionPluginTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
package com.smartwinnr.plugins.jailbreakrootdetection;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.pm.PackageInfo;
|
|
6
|
+
import android.content.pm.PackageManager;
|
|
7
|
+
import android.os.Build;
|
|
8
|
+
|
|
9
|
+
import java.io.BufferedReader;
|
|
10
|
+
import java.io.File;
|
|
11
|
+
import java.io.IOException;
|
|
12
|
+
import java.io.InputStreamReader;
|
|
13
|
+
import java.io.InputStream;
|
|
14
|
+
import java.util.ArrayList;
|
|
15
|
+
import java.util.Arrays;
|
|
16
|
+
import java.util.List;
|
|
17
|
+
import java.util.HashMap;
|
|
18
|
+
import java.util.Map;
|
|
19
|
+
import java.util.NoSuchElementException;
|
|
20
|
+
import java.util.Scanner;
|
|
21
|
+
|
|
22
|
+
import java.security.KeyFactory;
|
|
23
|
+
import java.security.PublicKey;
|
|
24
|
+
import java.security.spec.X509EncodedKeySpec;
|
|
25
|
+
import javax.crypto.SecretKey;
|
|
26
|
+
import javax.crypto.spec.SecretKeySpec;
|
|
27
|
+
import java.security.NoSuchAlgorithmException;
|
|
28
|
+
import java.security.spec.InvalidKeySpecException;
|
|
29
|
+
import org.jose4j.jwe.JsonWebEncryption;
|
|
30
|
+
import org.jose4j.jws.JsonWebSignature;
|
|
31
|
+
import org.jose4j.lang.JoseException;
|
|
32
|
+
import org.json.JSONArray;
|
|
33
|
+
import org.json.JSONException;
|
|
34
|
+
import org.json.JSONObject;
|
|
35
|
+
import com.google.android.gms.tasks.Task;
|
|
36
|
+
import com.google.android.gms.tasks.Tasks;
|
|
37
|
+
import com.google.android.play.core.integrity.IntegrityManager;
|
|
38
|
+
import com.google.android.play.core.integrity.IntegrityManagerFactory;
|
|
39
|
+
import com.google.android.play.core.integrity.IntegrityTokenRequest;
|
|
40
|
+
import com.google.android.play.core.integrity.IntegrityTokenResponse;
|
|
41
|
+
|
|
42
|
+
import java.util.Objects;
|
|
43
|
+
import java.util.concurrent.CompletableFuture;
|
|
44
|
+
import java.util.concurrent.ExecutionException;
|
|
45
|
+
import java.util.concurrent.ExecutorService;
|
|
46
|
+
import java.util.concurrent.Executors;
|
|
47
|
+
|
|
48
|
+
import android.util.Base64;
|
|
49
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
50
|
+
import java.io.OutputStreamWriter;
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
public class JailbreakRootDetection extends AppCompatActivity {
|
|
54
|
+
|
|
55
|
+
private static final String TAG = "JailbreakRootDetection";
|
|
56
|
+
static final String BINARY_SU = "su";
|
|
57
|
+
static final String BINARY_BUSYBOX = "busybox";
|
|
58
|
+
private boolean isJailbroken = false;
|
|
59
|
+
public String echo(String value) {
|
|
60
|
+
Log.i("Echo", value);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public Boolean jailbroken(Context context, String verificationKey, String decryptionKey) {
|
|
65
|
+
Log.i("JailbreakRootDetection", "Checking root detectection");
|
|
66
|
+
|
|
67
|
+
boolean rootMethod1Result = checkRootMethod1();
|
|
68
|
+
boolean checkRootMethod2Result = checkRootMethod2();
|
|
69
|
+
boolean checkRootMethod3Result = checkRootMethod3();
|
|
70
|
+
boolean checkRootBypassAppsResult = checkRootBypassApps(context);
|
|
71
|
+
boolean checkKeyLoggerAppsResult = checkKeyLoggerApps(context);
|
|
72
|
+
boolean checkDirPermissionsResult = checkDirPermissions();
|
|
73
|
+
boolean checkforOverTheAirCertificatesResult = checkforOverTheAirCertificates();
|
|
74
|
+
boolean checkForBinaryResultSu = checkForBinary(BINARY_SU);
|
|
75
|
+
boolean checkForDangerousProps = checkForDangerousProps();
|
|
76
|
+
boolean checkForRWPathsResult = checkForRWPaths();
|
|
77
|
+
boolean checkSuResult = checkSuExists();
|
|
78
|
+
boolean checkForMagiskBinaryResult = checkForMagiskBinary();
|
|
79
|
+
|
|
80
|
+
Log.i("JailbreakRootDetection", "Dangerous File " + rootMethod1Result);
|
|
81
|
+
Log.i("JailbreakRootDetection", "Dangerous Command " + checkRootMethod2Result);
|
|
82
|
+
Log.i("JailbreakRootDetection", "Test Keys " + checkRootMethod3Result);
|
|
83
|
+
Log.i("JailbreakRootDetection", "RootBypass App Check " + checkRootBypassAppsResult);
|
|
84
|
+
Log.i("JailbreakRootDetection", "Key Logger App Check " + checkKeyLoggerAppsResult);
|
|
85
|
+
Log.i("JailbreakRootDetection", "Directory Permission " + checkDirPermissionsResult);
|
|
86
|
+
Log.i("JailbreakRootDetection", "OTA Cert "+ checkforOverTheAirCertificatesResult);
|
|
87
|
+
Log.i("JailbreakRootDetection", "SU Binary "+ checkForBinaryResultSu);
|
|
88
|
+
Log.i("JailbreakRootDetection", "Dangerous Properties " + checkForDangerousProps);
|
|
89
|
+
Log.i("JailbreakRootDetection", "Read/Write Path Check "+ checkForRWPathsResult);
|
|
90
|
+
Log.i("JailbreakRootDetection", "SU Commnad Execution "+ checkSuResult);
|
|
91
|
+
Log.i("JailbreakRootDetection", "Magisk Binary "+ checkForMagiskBinaryResult);
|
|
92
|
+
|
|
93
|
+
boolean isJailbroken =
|
|
94
|
+
rootMethod1Result ||
|
|
95
|
+
checkRootMethod2Result ||
|
|
96
|
+
checkRootMethod3Result ||
|
|
97
|
+
checkRootBypassAppsResult ||
|
|
98
|
+
checkKeyLoggerAppsResult ||
|
|
99
|
+
checkDirPermissionsResult ||
|
|
100
|
+
checkforOverTheAirCertificatesResult ||
|
|
101
|
+
checkForBinaryResultSu ||
|
|
102
|
+
checkForDangerousProps ||
|
|
103
|
+
checkForRWPathsResult ||
|
|
104
|
+
checkSuResult ||
|
|
105
|
+
checkForMagiskBinaryResult;
|
|
106
|
+
|
|
107
|
+
CompletableFuture<Boolean> future = performPlayIntegrityCheckAsync(context, verificationKey, decryptionKey);
|
|
108
|
+
// Apply the device integrity test then send the result to the app
|
|
109
|
+
try {
|
|
110
|
+
boolean googlePlayIntegrityCheck = future.get(); // This will block until the future completes
|
|
111
|
+
Log.i(TAG, "Is device jailbroken: " + isJailbroken);
|
|
112
|
+
isJailbroken = googlePlayIntegrityCheck || isJailbroken;
|
|
113
|
+
} catch (InterruptedException | ExecutionException e) {
|
|
114
|
+
Log.i(TAG, "An error occurred: " + e.getMessage());
|
|
115
|
+
}
|
|
116
|
+
isDeviceRooted();
|
|
117
|
+
Log.i("JailbreakRootDetection", "Jailbreak Detection Completed");
|
|
118
|
+
return isJailbroken;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public static boolean isDeviceRooted() {
|
|
122
|
+
String[] commands = {
|
|
123
|
+
"which su",
|
|
124
|
+
"ls -l /sbin/su /system/bin/su /system/xbin/su /data/local/xbin/su /data/local/bin/su /system/sd/xbin/su /system/bin/failsafe/su /data/local/su",
|
|
125
|
+
"ls -l /system/app/Superuser.apk /system/app/SuperSU.apk /system/app/Magisk.apk",
|
|
126
|
+
"which busybox",
|
|
127
|
+
"which magisk",
|
|
128
|
+
"ls -l /sbin/.magisk",
|
|
129
|
+
"ls -l /system/xbin/daemonsu /system/xbin/supolicy",
|
|
130
|
+
"cat /init.rc | grep 'su'",
|
|
131
|
+
"cat /init.environ.rc | grep 'su'",
|
|
132
|
+
"pm list packages | grep 'superuser'",
|
|
133
|
+
"pm list packages | grep 'supersu'",
|
|
134
|
+
"pm list packages | grep 'magisk'"
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
for (String command : commands) {
|
|
138
|
+
if (executeCommand(command)) {
|
|
139
|
+
return true; // If any command indicates root, return true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private static boolean executeCommand(String command) {
|
|
147
|
+
Log.i("executeCommand", String.valueOf(command));
|
|
148
|
+
try {
|
|
149
|
+
Process process = Runtime.getRuntime().exec(command);
|
|
150
|
+
Log.i("executeCommand", String.valueOf(process));
|
|
151
|
+
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
|
152
|
+
String output;
|
|
153
|
+
Log.i("executeCommand", String.valueOf(in.readLine()));
|
|
154
|
+
while ((output = in.readLine()) != null) {
|
|
155
|
+
Log.i("executeCommand", String.valueOf(output));
|
|
156
|
+
if (!output.isEmpty()) {
|
|
157
|
+
return true; // If any output is received, consider it as an indication of root
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
in.close();
|
|
161
|
+
process.waitFor();
|
|
162
|
+
} catch (Exception e) {
|
|
163
|
+
e.printStackTrace();
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
private boolean checkRootMethod1() {
|
|
170
|
+
String[] paths = {
|
|
171
|
+
"/sbin/su",
|
|
172
|
+
"/data/local/xbin/su",
|
|
173
|
+
"/data/local/bin/su",
|
|
174
|
+
"/system/bin/failsafe/su",
|
|
175
|
+
"/data/local/su",
|
|
176
|
+
"/system/xbin/daemonsu",
|
|
177
|
+
"/system/sd/xbin/su",
|
|
178
|
+
"/system/usr/we-need-root/su-backup",
|
|
179
|
+
"/system/bin/busybox",
|
|
180
|
+
"/system/xbin/busybox",
|
|
181
|
+
"/sbin/busybox",
|
|
182
|
+
"/system/su",
|
|
183
|
+
"/data/local/xbin/busybox",
|
|
184
|
+
"/data/local/bin/busybox",
|
|
185
|
+
"/data/local/busybox",
|
|
186
|
+
"/su/bin/busybox",
|
|
187
|
+
"/system/bin/su",
|
|
188
|
+
"/system/xbin/su",
|
|
189
|
+
"/su/bin/su"
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
for (String path : paths) {
|
|
193
|
+
if (new File(path).exists()) return true;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private boolean checkRootMethod2() {
|
|
199
|
+
List<String> commands = new ArrayList<>();
|
|
200
|
+
commands.add("which su");
|
|
201
|
+
commands.add("/system/xbin/which su");
|
|
202
|
+
commands.add("/system/bin/which su");
|
|
203
|
+
return executeCommands(commands);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private boolean checkRootMethod3() {
|
|
207
|
+
String buildTags = Build.TAGS;
|
|
208
|
+
return buildTags != null && buildTags.contains("test-keys");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private boolean executeCommands(List<String> commands) {
|
|
212
|
+
try {
|
|
213
|
+
for (String command : commands) {
|
|
214
|
+
Process process = Runtime.getRuntime().exec(command);
|
|
215
|
+
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
|
216
|
+
if (in.readLine() != null) return true;
|
|
217
|
+
in.close();
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
} catch (Exception e) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private boolean checkRootBypassApps(final Context context) {
|
|
226
|
+
Log.i("checkRootBypassApps", "Root Bypassapp checking ");
|
|
227
|
+
List<String> rootAppsPackages = Arrays.asList(
|
|
228
|
+
"com.topjohnwu.magisk", // Magisk
|
|
229
|
+
"eu.chainfire.supersu", // SuperSU
|
|
230
|
+
"com.koushikdutta.superuser", // Superuser
|
|
231
|
+
"com.noshufou.android.su", // Superuser (older version)
|
|
232
|
+
"com.kingroot.kinguser", // KingRoot
|
|
233
|
+
"com.kingouser.com", // KingRoot
|
|
234
|
+
"com.kingroot.kinguser.activity", // Kingoroot
|
|
235
|
+
"com.kingoroot.kingoapp", // Kingoroot
|
|
236
|
+
"com.alephzain.framaroot", // Framaroot
|
|
237
|
+
"com.baidu.easyroot", // Baidu Root
|
|
238
|
+
"com.oneclickroot", // One Click Root
|
|
239
|
+
"com.shuame.rootgenius", // Root Genius
|
|
240
|
+
"com.mgyun.shua.su", // iRoot
|
|
241
|
+
"com.mgyun.shua", // iRoot
|
|
242
|
+
"com.geohot.towelroot", // Towelroot
|
|
243
|
+
"com.root.master", // Root Master
|
|
244
|
+
"com.z4mod.z4root", // Z4Root
|
|
245
|
+
"com.saurik.Cydia", // Cydia
|
|
246
|
+
"stericson.busybox", // BusyBox
|
|
247
|
+
"stericson.busybox.donate", // BusyBox (Donate)
|
|
248
|
+
"com.zachspong.temprootremovejb",
|
|
249
|
+
"com.ramdroid.appquarantine",
|
|
250
|
+
"eu.chainfire.stickmount",
|
|
251
|
+
"eu.chainfire.mobileodin.pro",
|
|
252
|
+
"eu.chainfire.liveboot",
|
|
253
|
+
"eu.chainfire.pryfi",
|
|
254
|
+
"eu.chainfire.adbd",
|
|
255
|
+
"eu.chainfire.recently",
|
|
256
|
+
"eu.chainfire.flash",
|
|
257
|
+
"eu.chainfire.stickmount.pro",
|
|
258
|
+
"eu.chainfire.triangleaway",
|
|
259
|
+
"org.adblockplus.android"
|
|
260
|
+
);
|
|
261
|
+
return isAnyPackageInstalled(rootAppsPackages, context);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private boolean checkKeyLoggerApps(final Context context) {
|
|
265
|
+
Log.i("checkKeyLoggerApps", "Key Logger App Checking");
|
|
266
|
+
List<String> rootAppsPackages = Arrays.asList(
|
|
267
|
+
"com.abifog.lokiboard",
|
|
268
|
+
"apk.typingrecorder",
|
|
269
|
+
"com.gpow.keylogger",
|
|
270
|
+
"com.onemanarmy.keylogger",
|
|
271
|
+
"com.mni.password.manager.keylogger",
|
|
272
|
+
"com.AwamiSolution.smartkeylogger",
|
|
273
|
+
"monitor.mubeen.androidkeylogger",
|
|
274
|
+
"com.as.keylogger"
|
|
275
|
+
);
|
|
276
|
+
return isAnyPackageInstalled(rootAppsPackages, context);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private boolean isAnyPackageInstalled(List<String> packages, final Context context) {
|
|
280
|
+
PackageManager pm = context.getPackageManager();
|
|
281
|
+
for (String packageName : packages) {
|
|
282
|
+
try {
|
|
283
|
+
PackageInfo isPackageInstalled = pm.getPackageInfo(packageName, 0);
|
|
284
|
+
return true; // Package found
|
|
285
|
+
} catch (PackageManager.NameNotFoundException e) {
|
|
286
|
+
// Package not found
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private boolean checkDirPermissions() {
|
|
293
|
+
boolean isWritableDir;
|
|
294
|
+
boolean isReadableDataDir;
|
|
295
|
+
boolean result = false;
|
|
296
|
+
List<String> pathShouldNotWritable = Arrays.asList(
|
|
297
|
+
"/system",
|
|
298
|
+
"/system/bin",
|
|
299
|
+
"/system/sbin",
|
|
300
|
+
"/system/xbin",
|
|
301
|
+
"/vendor/bin",
|
|
302
|
+
"/sbin",
|
|
303
|
+
"/etc"
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
for (String dirName : pathShouldNotWritable) {
|
|
307
|
+
final File currentDir = new File(dirName);
|
|
308
|
+
|
|
309
|
+
Log.i("currentDir", String.valueOf(currentDir));
|
|
310
|
+
Log.i("exists", String.valueOf(currentDir.exists()));
|
|
311
|
+
Log.i("canWrite", String.valueOf(currentDir.canWrite()));
|
|
312
|
+
Log.i("canRead", String.valueOf(currentDir.canRead()));
|
|
313
|
+
|
|
314
|
+
isWritableDir = currentDir.exists() && currentDir.canWrite();
|
|
315
|
+
isReadableDataDir = (dirName.equals("/data") && currentDir.canRead());
|
|
316
|
+
|
|
317
|
+
if (isWritableDir || isReadableDataDir) {
|
|
318
|
+
result = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private boolean checkforOverTheAirCertificates() {
|
|
325
|
+
File otacerts = new File("/etc/security/otacerts.zip");
|
|
326
|
+
boolean exist = otacerts.exists();
|
|
327
|
+
boolean result = !exist;
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private boolean performPlayIntegrityCheck(final Context context, String verifyKey, String decryptKey) {
|
|
332
|
+
final String nonce = NonceUtil.generateNonce(16);
|
|
333
|
+
Log.i("Nonce", nonce);
|
|
334
|
+
// but can be stored in a safer way, for example on a server
|
|
335
|
+
// and obtained by a secure http request
|
|
336
|
+
final String DECRYPTION_KEY = decryptKey;
|
|
337
|
+
final String VERIFICATION_KEY = verifyKey;
|
|
338
|
+
// Create an instance of IntegrityManager
|
|
339
|
+
IntegrityManager integrityManager = IntegrityManagerFactory.create(context);
|
|
340
|
+
|
|
341
|
+
// Request the integrity token by providing the nonce
|
|
342
|
+
Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager
|
|
343
|
+
.requestIntegrityToken(IntegrityTokenRequest.builder().setNonce(nonce).build())
|
|
344
|
+
.addOnSuccessListener(response -> {
|
|
345
|
+
String integrityToken = response.token();
|
|
346
|
+
Log.i(TAG, "Integrity Token: " + integrityToken);
|
|
347
|
+
|
|
348
|
+
byte[] decryptionKeyBytes = Base64.decode(DECRYPTION_KEY, Base64.DEFAULT);
|
|
349
|
+
SecretKey decryptionKey = new SecretKeySpec(decryptionKeyBytes, 0, decryptionKeyBytes.length, "AES");
|
|
350
|
+
|
|
351
|
+
byte[] encodedVerificationKey = Base64.decode(VERIFICATION_KEY, Base64.DEFAULT);
|
|
352
|
+
PublicKey verificationKey = null;
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
verificationKey = KeyFactory.getInstance("EC")
|
|
356
|
+
.generatePublic(new X509EncodedKeySpec(encodedVerificationKey));
|
|
357
|
+
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
|
358
|
+
Log.i(TAG, e.getMessage());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (verificationKey == null) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
JsonWebEncryption jwe = null;
|
|
366
|
+
try {
|
|
367
|
+
jwe = (JsonWebEncryption) JsonWebSignature.fromCompactSerialization(integrityToken);
|
|
368
|
+
} catch (JoseException e) {
|
|
369
|
+
e.printStackTrace();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (jwe == null) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
jwe.setKey(decryptionKey);
|
|
377
|
+
|
|
378
|
+
String compactJws = null;
|
|
379
|
+
try {
|
|
380
|
+
compactJws = jwe.getPayload();
|
|
381
|
+
} catch (JoseException e) {
|
|
382
|
+
Log.i(TAG, e.getMessage());
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
JsonWebSignature jws = null;
|
|
386
|
+
try {
|
|
387
|
+
jws = (JsonWebSignature) JsonWebSignature.fromCompactSerialization(compactJws);
|
|
388
|
+
} catch (JoseException e) {
|
|
389
|
+
Log.i(TAG, e.getMessage());
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (jws == null) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
jws.setKey(verificationKey);
|
|
397
|
+
|
|
398
|
+
String jsonPlainVerdict = "";
|
|
399
|
+
try {
|
|
400
|
+
jsonPlainVerdict = jws.getPayload();
|
|
401
|
+
} catch (JoseException e) {
|
|
402
|
+
Log.i(TAG, e.getMessage());
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
isJailbroken = parseDeviceIntegrity(jsonPlainVerdict);
|
|
407
|
+
|
|
408
|
+
Log.i(TAG, jsonPlainVerdict);
|
|
409
|
+
})
|
|
410
|
+
.addOnFailureListener(ex -> {
|
|
411
|
+
isJailbroken = true;
|
|
412
|
+
Log.i(TAG, "Error requesting integrity token: " + ex.getMessage());
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
return isJailbroken;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private CompletableFuture<Boolean> performPlayIntegrityCheckAsync(final Context context, String verifyKey, String decryptKey) {
|
|
419
|
+
return CompletableFuture.supplyAsync(() -> {
|
|
420
|
+
final String nonce = NonceUtil.generateNonce(16);
|
|
421
|
+
String decKey = "";
|
|
422
|
+
if (Objects.equals(decryptKey, "NoVerification")) {
|
|
423
|
+
decKey = "nmzeD9LVp6yxJ3kvn3KETASozsqM+yx45G4NqKLeiFc=";
|
|
424
|
+
} else {
|
|
425
|
+
decKey = decryptKey;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
String verKey ="";
|
|
429
|
+
if (Objects.equals(verifyKey, "NoVerification")) {
|
|
430
|
+
verKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOE63FZbxD8193Sz/KwlBfb5LYyBZSYOckuys17CuGp6KWgzju8xUmwy0gXpkSgNIZZxDTdD6mGMBnUOmwk0zSQ==";
|
|
431
|
+
} else {
|
|
432
|
+
verKey = verifyKey;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
final String DECRYPTION_KEY = decKey;
|
|
436
|
+
final String VERIFICATION_KEY = verKey;
|
|
437
|
+
IntegrityManager integrityManager = IntegrityManagerFactory.create(context);
|
|
438
|
+
|
|
439
|
+
Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager
|
|
440
|
+
.requestIntegrityToken(IntegrityTokenRequest.builder().setNonce(nonce).build());
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
IntegrityTokenResponse response = Tasks.await(integrityTokenResponse);
|
|
444
|
+
String integrityToken = response.token();
|
|
445
|
+
Log.i(TAG, "Integrity Token: " + integrityToken);
|
|
446
|
+
|
|
447
|
+
byte[] decryptionKeyBytes = Base64.decode(DECRYPTION_KEY, Base64.DEFAULT);
|
|
448
|
+
SecretKey decryptionKey = new SecretKeySpec(decryptionKeyBytes, 0, decryptionKeyBytes.length, "AES");
|
|
449
|
+
|
|
450
|
+
byte[] encodedVerificationKey = Base64.decode(VERIFICATION_KEY, Base64.DEFAULT);
|
|
451
|
+
PublicKey verificationKey = KeyFactory.getInstance("EC")
|
|
452
|
+
.generatePublic(new X509EncodedKeySpec(encodedVerificationKey));
|
|
453
|
+
|
|
454
|
+
JsonWebEncryption jwe = (JsonWebEncryption) JsonWebSignature.fromCompactSerialization(integrityToken);
|
|
455
|
+
jwe.setKey(decryptionKey);
|
|
456
|
+
|
|
457
|
+
String compactJws = jwe.getPayload();
|
|
458
|
+
|
|
459
|
+
JsonWebSignature jws = (JsonWebSignature) JsonWebSignature.fromCompactSerialization(compactJws);
|
|
460
|
+
jws.setKey(verificationKey);
|
|
461
|
+
|
|
462
|
+
String jsonPlainVerdict = jws.getPayload();
|
|
463
|
+
isJailbroken = parseDeviceIntegrity(jsonPlainVerdict);
|
|
464
|
+
|
|
465
|
+
Log.i(TAG, jsonPlainVerdict);
|
|
466
|
+
} catch (Exception e) {
|
|
467
|
+
// Its going in exception because google play integrity api might be blocked by some third pary service or internet access is not given in this case we will consider the app as jailbroken
|
|
468
|
+
isJailbroken = false;
|
|
469
|
+
Log.i(TAG, "Error requesting integrity token: " + e.getMessage());
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return isJailbroken;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private boolean parseDeviceIntegrity(String jsonString) {
|
|
477
|
+
try {
|
|
478
|
+
// Parse the JSON string into a JSONObject
|
|
479
|
+
JSONObject jsonObject = new JSONObject(jsonString);
|
|
480
|
+
|
|
481
|
+
// Get the deviceIntegrity object
|
|
482
|
+
JSONObject deviceIntegrity = jsonObject.getJSONObject("deviceIntegrity");
|
|
483
|
+
|
|
484
|
+
JSONArray deviceRecognitionVerdict;
|
|
485
|
+
// Device recognistion will come in non rooted device if not coming then device might be rooted
|
|
486
|
+
try {
|
|
487
|
+
// Get the deviceRecognitionVerdict array
|
|
488
|
+
deviceRecognitionVerdict = deviceIntegrity.getJSONArray("deviceRecognitionVerdict");
|
|
489
|
+
} catch(JSONException error) {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
// Log the values for debugging
|
|
495
|
+
for (int i = 0; i < deviceRecognitionVerdict.length(); i++) {
|
|
496
|
+
String verdict = deviceRecognitionVerdict.getString(i);
|
|
497
|
+
Log.d(TAG, "Verdict: " + verdict);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Check if the required values are present
|
|
501
|
+
boolean meetsCriteria = false;
|
|
502
|
+
for (int i = 0; i < deviceRecognitionVerdict.length(); i++) {
|
|
503
|
+
String verdict = deviceRecognitionVerdict.getString(i);
|
|
504
|
+
if (verdict.equals("MEETS_BASIC_INTEGRITY") ||
|
|
505
|
+
verdict.equals("MEETS_DEVICE_INTEGRITY") ||
|
|
506
|
+
verdict.equals("MEETS_STRONG_INTEGRITY")) {
|
|
507
|
+
meetsCriteria = true;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (meetsCriteria) {
|
|
513
|
+
|
|
514
|
+
// The device meets the required integrity criteria
|
|
515
|
+
Log.d(TAG, "Device meets the required integrity criteria.");
|
|
516
|
+
return false;
|
|
517
|
+
} else {
|
|
518
|
+
// The device does not meet the required integrity criteria
|
|
519
|
+
Log.d(TAG, "Device does not meet the required integrity criteria.");
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
} catch (JSONException e) {
|
|
524
|
+
Log.e(TAG, "JSON Exception: " + e.getMessage());
|
|
525
|
+
e.printStackTrace();
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private boolean checkNativeLibraryLoaded() {
|
|
531
|
+
boolean libraryLoaded = false;
|
|
532
|
+
try {
|
|
533
|
+
System.loadLibrary("toolChecker");
|
|
534
|
+
libraryLoaded = true;
|
|
535
|
+
} catch (UnsatisfiedLinkError e) {
|
|
536
|
+
|
|
537
|
+
}
|
|
538
|
+
return libraryLoaded;
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private boolean checkSuExists() {
|
|
543
|
+
Process process = null;
|
|
544
|
+
Log.i("checkSuExists", String.valueOf(process));
|
|
545
|
+
try {
|
|
546
|
+
Log.i("checkSuExists", String.valueOf(479));
|
|
547
|
+
process = Runtime.getRuntime().exec("ls -lart");
|
|
548
|
+
|
|
549
|
+
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
|
550
|
+
Log.i("checkSuExists", String.valueOf(in.readLine()));
|
|
551
|
+
|
|
552
|
+
return in.readLine() != null;
|
|
553
|
+
} catch (Throwable t) {
|
|
554
|
+
Log.i("checkSuExists", String.valueOf(t));
|
|
555
|
+
return false;
|
|
556
|
+
} finally {
|
|
557
|
+
if (process != null) process.destroy();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
public boolean checkForBinary(String filename) {
|
|
562
|
+
|
|
563
|
+
String[] pathsArray = Const.getPaths();
|
|
564
|
+
boolean result = false;
|
|
565
|
+
|
|
566
|
+
for (String path : pathsArray) {
|
|
567
|
+
String completePath = path + filename;
|
|
568
|
+
File f = new File(path, filename);
|
|
569
|
+
boolean fileExists = f.exists();
|
|
570
|
+
if (fileExists) {
|
|
571
|
+
result = true;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private String[] propsReader() {
|
|
579
|
+
try {
|
|
580
|
+
InputStream inputstream = Runtime.getRuntime().exec("getprop").getInputStream();
|
|
581
|
+
if (inputstream == null) return null;
|
|
582
|
+
String propVal = new Scanner(inputstream).useDelimiter("\\A").next();
|
|
583
|
+
return propVal.split("\n");
|
|
584
|
+
} catch (IOException | NoSuchElementException e) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private String[] mountReader() {
|
|
590
|
+
try {
|
|
591
|
+
InputStream inputstream = Runtime.getRuntime().exec("mount").getInputStream();
|
|
592
|
+
if (inputstream == null) return null;
|
|
593
|
+
String propVal = new Scanner(inputstream).useDelimiter("\\A").next();
|
|
594
|
+
return propVal.split("\n");
|
|
595
|
+
} catch (NoSuchElementException | IOException e) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
public boolean checkForDangerousProps() {
|
|
601
|
+
|
|
602
|
+
final Map<String, String> dangerousProps = new HashMap<>();
|
|
603
|
+
dangerousProps.put("ro.debuggable", "1");
|
|
604
|
+
dangerousProps.put("ro.secure", "0");
|
|
605
|
+
|
|
606
|
+
boolean result = false;
|
|
607
|
+
|
|
608
|
+
String[] lines = propsReader();
|
|
609
|
+
|
|
610
|
+
if (lines == null){
|
|
611
|
+
// Could not read, assume false;
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
for (String line : lines) {
|
|
616
|
+
for (String key : dangerousProps.keySet()) {
|
|
617
|
+
if (line.contains(key)) {
|
|
618
|
+
String badValue = dangerousProps.get(key);
|
|
619
|
+
badValue = "[" + badValue + "]";
|
|
620
|
+
if (line.contains(badValue)) {
|
|
621
|
+
result = true;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* When you're root you can change the permissions on common system directories, this method checks if any of these patha Const.pathsThatShouldNotBeWritable are writable.
|
|
631
|
+
* @return true if one of the dir is writable
|
|
632
|
+
*/
|
|
633
|
+
public boolean checkForRWPaths() {
|
|
634
|
+
|
|
635
|
+
boolean result = false;
|
|
636
|
+
|
|
637
|
+
//Run the command "mount" to retrieve all mounted directories
|
|
638
|
+
String[] lines = mountReader();
|
|
639
|
+
|
|
640
|
+
if (lines == null){
|
|
641
|
+
// Could not read, assume false;
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
//The SDK version of the software currently running on this hardware device.
|
|
646
|
+
int sdkVersion = android.os.Build.VERSION.SDK_INT;
|
|
647
|
+
Log.i("sdkVersion", String.valueOf(sdkVersion));
|
|
648
|
+
|
|
649
|
+
for (String line : lines) {
|
|
650
|
+
|
|
651
|
+
// Split lines into parts
|
|
652
|
+
String[] args = line.split(" ");
|
|
653
|
+
|
|
654
|
+
if ((sdkVersion <= android.os.Build.VERSION_CODES.M && args.length < 4)
|
|
655
|
+
|| (sdkVersion > android.os.Build.VERSION_CODES.M && args.length < 6)) {
|
|
656
|
+
// If we don't have enough options per line, skip this and log an error
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
String mountPoint;
|
|
661
|
+
String mountOptions;
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* To check if the device is running Android version higher than Marshmallow or not
|
|
665
|
+
*/
|
|
666
|
+
if (sdkVersion > android.os.Build.VERSION_CODES.M) {
|
|
667
|
+
mountPoint = args[2];
|
|
668
|
+
mountOptions = args[5];
|
|
669
|
+
} else {
|
|
670
|
+
mountPoint = args[1];
|
|
671
|
+
mountOptions = args[3];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
for(String pathToCheck: Const.pathsThatShouldNotBeWritable) {
|
|
675
|
+
if (mountPoint.equalsIgnoreCase(pathToCheck)) {
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* If the device is running an Android version above Marshmallow,
|
|
679
|
+
* need to remove parentheses from options parameter;
|
|
680
|
+
*/
|
|
681
|
+
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) {
|
|
682
|
+
mountOptions = mountOptions.replace("(", "");
|
|
683
|
+
mountOptions = mountOptions.replace(")", "");
|
|
684
|
+
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Split options out and compare against "rw" to avoid false positives
|
|
688
|
+
for (String option : mountOptions.split(",")){
|
|
689
|
+
|
|
690
|
+
if (option.equalsIgnoreCase("rw")){
|
|
691
|
+
result = true;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private boolean checkForMagiskBinary(){ return checkForBinary("magisk"); }
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
}
|