From d98a279d25f9b9415b00ff4071a33c62e45bce7b Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 10:45:46 +0000 Subject: [PATCH 01/27] remove-support-for-unused-encryptionKeyValue --- .../main/java/com/crux/sdk/model/CruxClientInitConfig.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/src/main/java/com/crux/sdk/model/CruxClientInitConfig.java b/sdk/src/main/java/com/crux/sdk/model/CruxClientInitConfig.java index ac4c8f3..830745b 100644 --- a/sdk/src/main/java/com/crux/sdk/model/CruxClientInitConfig.java +++ b/sdk/src/main/java/com/crux/sdk/model/CruxClientInitConfig.java @@ -10,12 +10,10 @@ public static class Builder { // private String storage; // private String encryption; // private String nameService; - private String _encryptionKeyValue; private String walletClientName; private String privateKey; public Builder() { - this._encryptionKeyValue = "fookey"; } public Builder setWalletClientName(String walletClientName) { @@ -37,14 +35,12 @@ public CruxClientInitConfig create() { private Builder builder; private String privateKey; private String walletClientName; - private String _encryptionKeyValue; protected CruxClientInitConfig(Builder builder) { this.builder = builder; this.privateKey = builder.privateKey; this.walletClientName = builder.walletClientName; - this._encryptionKeyValue = builder._encryptionKeyValue; } public String getCruxClientInitConfigString() throws JSONException { From 4688b0938239e0c7e6792fe18ad14aad8259b93c Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 10:54:12 +0000 Subject: [PATCH 02/27] Textual changes for new comers --- app/src/main/res/layout/activity_main.xml | 4 +++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4fc2444..f1b2987 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,9 +7,11 @@ tools:context=".MainActivity"> - Liquid_Test_2 + CRUX Android Sample From 8142c6da905f1e487c323d1d76061453c709d15d Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 11:19:38 +0000 Subject: [PATCH 03/27] Add progaurd rule to enable code obfuscation --- app/build.gradle | 8 +++++++- app/proguard-rules.pro | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5e78366..44843e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,13 @@ android { } buildTypes { release { - minifyEnabled false + // Enables code shrinking, obfuscation, and optimization for only + // your project's release build type. + minifyEnabled true + + // Enables resource shrinking, which is performed by the + // Android Gradle plugin. + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b4245..c576118 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,3 +19,8 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + +# To fix errors and force R8 to keep certain code, add a -keep line in the ProGuard rules file. For example: +# -keep public class MyClass +-dontwarn com.crux.sdk.** +-keep class com.crux.sdk.** {*;} \ No newline at end of file From d9b85c8d973a4613f402aefe54034b2c36c216e6 Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 12:45:24 +0000 Subject: [PATCH 04/27] Adds basic anti-debug --- .../java/com/example/liquid_test_2/MainActivity.java | 10 +++++++++- sdk/src/main/java/com/crux/sdk/CruxClient.java | 7 +++++++ .../com/crux/sdk/model/AndroidCruxClientErrorCode.java | 1 + .../crux/sdk/model/AndroidCruxClientErrorString.java | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/liquid_test_2/MainActivity.java b/app/src/main/java/com/example/liquid_test_2/MainActivity.java index 3c476cd..faf8020 100644 --- a/app/src/main/java/com/example/liquid_test_2/MainActivity.java +++ b/app/src/main/java/com/example/liquid_test_2/MainActivity.java @@ -2,6 +2,7 @@ import android.content.Context; import android.os.Bundle; +import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; @@ -38,7 +39,13 @@ public String runScript(final Context androidContextObject) throws IOException, .setWalletClientName("cruxdev") .setPrivateKey("cdf2d276caf0c9c34258ed6ebd0e60e0e8b3d9a7b8a9a717f2e19ed9b37f7c6f"); - final CruxClient client = new CruxClient(configBuilder, androidContextObject); + CruxClient client; + try { + client = new CruxClient(configBuilder, androidContextObject); + } catch(CruxClientError e) { + Toast.makeText(androidContextObject, "Debug from vm detected.", 5).show(); + return null; + } final String testAvailabilityCruxId = "yadu007"; client.isCruxIDAvailable(testAvailabilityCruxId, new CruxClientResponseHandler() { @@ -127,6 +134,7 @@ public void onErrorResponse(CruxClientError failureResponse) { } }); + return null; } diff --git a/sdk/src/main/java/com/crux/sdk/CruxClient.java b/sdk/src/main/java/com/crux/sdk/CruxClient.java index 3c1e6c5..fd4bcf8 100644 --- a/sdk/src/main/java/com/crux/sdk/CruxClient.java +++ b/sdk/src/main/java/com/crux/sdk/CruxClient.java @@ -1,10 +1,12 @@ package com.crux.sdk; import android.content.Context; +import android.os.Debug; import com.crux.sdk.bridge.CruxJSBridge; import com.crux.sdk.bridge.CruxJSBridgeAsyncRequest; import com.crux.sdk.bridge.handlerImpl.CruxJSBridgeResponseHandlerImpl; +import com.crux.sdk.model.AndroidCruxClientErrorCode; import com.crux.sdk.model.CruxAddress; import com.crux.sdk.model.CruxClientError; import com.crux.sdk.model.CruxClientInitConfig; @@ -25,6 +27,11 @@ public class CruxClient { private final CruxJSBridge jsBridge; public CruxClient(CruxClientInitConfig.Builder configBuilder, Context androidContextObject) throws IOException, CruxClientError { + if (BuildConfig.BUILD_TYPE.contentEquals("debug")) { + if (Debug.isDebuggerConnected()) { + throw CruxClientError.getCruxClientError(AndroidCruxClientErrorCode.getCruxClientDebuggerFailed); + } + } this.jsBridge = new CruxJSBridge(configBuilder, androidContextObject); } diff --git a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java index f393310..4a0111a 100644 --- a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java +++ b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java @@ -5,6 +5,7 @@ public class AndroidCruxClientErrorCode { // 888s: Android Error Series public static Integer getCruxClientInitConfigStringFailed = 8881000; public static Integer cruxAddressMappingConversionFailed = 8881001; + public static Integer getCruxClientDebuggerFailed = 8881002; } diff --git a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java index 20e948f..bb49025 100644 --- a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java +++ b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java @@ -9,6 +9,7 @@ public class AndroidCruxClientErrorString { static { errorCodeToErrorStringMap = new HashMap(); errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.getCruxClientInitConfigStringFailed, "Could not initialize cruxClientConfig"); + errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.getCruxClientDebuggerFailed, "Could not initialize cruxClient as debugger detected"); errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.cruxAddressMappingConversionFailed, "Could not create CruxAddressMapping"); } } From 87aaef5c94305f26ae52fb426854ba50d7ac3e07 Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 13:21:48 +0000 Subject: [PATCH 05/27] Adds basic anti-emulator --- .../main/java/com/crux/sdk/CruxClient.java | 4 +- .../java/com/crux/sdk/security/AntiDebug.java | 10 +++++ .../com/crux/sdk/security/AntiEmulator.java | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 sdk/src/main/java/com/crux/sdk/security/AntiDebug.java create mode 100644 sdk/src/main/java/com/crux/sdk/security/AntiEmulator.java diff --git a/sdk/src/main/java/com/crux/sdk/CruxClient.java b/sdk/src/main/java/com/crux/sdk/CruxClient.java index fd4bcf8..078d9a5 100644 --- a/sdk/src/main/java/com/crux/sdk/CruxClient.java +++ b/sdk/src/main/java/com/crux/sdk/CruxClient.java @@ -14,6 +14,8 @@ import com.crux.sdk.model.CruxIDState; import com.crux.sdk.model.CruxParams; import com.crux.sdk.model.CruxPutAddressMapSuccess; +import com.crux.sdk.security.AntiDebug; +import com.crux.sdk.security.AntiEmulator; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -28,7 +30,7 @@ public class CruxClient { public CruxClient(CruxClientInitConfig.Builder configBuilder, Context androidContextObject) throws IOException, CruxClientError { if (BuildConfig.BUILD_TYPE.contentEquals("debug")) { - if (Debug.isDebuggerConnected()) { + if (AntiDebug.isDebugging() || AntiEmulator.isEmulator()) { throw CruxClientError.getCruxClientError(AndroidCruxClientErrorCode.getCruxClientDebuggerFailed); } } diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java b/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java new file mode 100644 index 0000000..537187c --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java @@ -0,0 +1,10 @@ +package com.crux.sdk.security; + +import android.os.Debug; + +public class AntiDebug { + + public static boolean isDebugging() { + return Debug.isDebuggerConnected(); + } +} diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiEmulator.java b/sdk/src/main/java/com/crux/sdk/security/AntiEmulator.java new file mode 100644 index 0000000..5d8d451 --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/AntiEmulator.java @@ -0,0 +1,45 @@ +package com.crux.sdk.security; + +import android.os.Build; + +import androidx.core.os.EnvironmentCompat; + +public class AntiEmulator { + public static boolean isEmulator() { + int rating = 0; + if (Build.PRODUCT.equals("sdk") || Build.PRODUCT.equals("google_sdk") || Build.PRODUCT.equals( + "sdk_x86") || Build.PRODUCT.equals("vbox86p")) { + rating = 1; + } + + if (Build.MANUFACTURER.equals(EnvironmentCompat.MEDIA_UNKNOWN) || Build.MANUFACTURER.equals( + "Genymotion")) { + rating++; + } + + if (Build.BRAND.equals("generic") || Build.BRAND.equals("generic_x86")) { + rating++; + } + + if (Build.DEVICE.equals("generic") || Build.DEVICE.equals("generic_x86") || Build.DEVICE.equals( + "vbox86p")) { + rating++; + } + + if (Build.MODEL.equals("sdk") || Build.MODEL.equals("google_sdk") || Build.MODEL.equals( + "Android SDK built for x86")) { + rating++; + } + + if (Build.HARDWARE.equals("goldfish") || Build.HARDWARE.equals("vbox86")) { + rating++; + } + + if (Build.FINGERPRINT.contains("generic/sdk/generic") || Build.FINGERPRINT.contains( + "generic_x86/sdk_x86/generic_x86") || Build.FINGERPRINT.contains( + "generic/google_sdk/generic") || Build.FINGERPRINT.contains("generic/vbox86p/vbox86p")) { + rating++; + } + return rating >= 2; + } +} From 9e278b3c410b59e089a3acf361bd089c405b817c Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Wed, 15 Jan 2020 15:57:09 +0000 Subject: [PATCH 06/27] Adds basic anti-root added --- .../java/com/crux/sdk/security/AntiRoot.java | 209 ++++++++++++++++++ .../com/crux/sdk/security/AntiRootConst.java | 77 +++++++ 2 files changed, 286 insertions(+) create mode 100644 sdk/src/main/java/com/crux/sdk/security/AntiRoot.java create mode 100644 sdk/src/main/java/com/crux/sdk/security/AntiRootConst.java diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java b/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java new file mode 100644 index 0000000..923dad5 --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java @@ -0,0 +1,209 @@ +package com.crux.sdk.security; + +import android.content.Context; +import android.content.pm.PackageManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import android.util.Log; + + +public class AntiRoot { + + + private final Context mContext; + private boolean loggingEnabled = true; + private static final String TAG = "AntiRoot"; + + public AntiRoot(Context context) { + mContext = context; + } + + public boolean isRooted() { + + return _detectTestKeys() || _detectRootManagementApps(null) + || _detectPotentiallyDangerousApps(null) || _checkSuExists() || _checkForBinary(AntiRootConst.BINARY_SU) + || _checkForBinary(AntiRootConst.BINARY_BUSYBOX) || _checkForBinary(AntiRootConst.MAGISK); + + // || _checkForRWPaths() + } + + /** + * Check if any package in the list is installed + * @param packages - list of packages to search for + * @return true if any of the packages are installed + */ + private boolean isAnyPackageFromListInstalled(List packages){ + boolean result = false; + + PackageManager pm = mContext.getPackageManager(); + + for (String packageName : packages) { + try { + // Root app detected + pm.getPackageInfo(packageName, 0); + Log.e(TAG, packageName + " ROOT management app detected!"); + result = true; + } catch (PackageManager.NameNotFoundException e) { + // Exception thrown, package is not installed into the system + } + } + + return result; + } + + + /** + * Release-Keys and Test-Keys has to do with how the kernel is signed when it is compiled. + * Test-Keys means it was signed with a custom key generated by a third-party developer. + * @return true if signed with Test-keys + */ + public boolean _detectTestKeys() { + String buildTags = android.os.Build.TAGS; + return buildTags != null && buildTags.contains("test-keys"); + } + + /** + * Using the PackageManager, check for a list of well known root apps. @link {AntiRootConst.knownRootAppsPackages} + * @param additionalRootManagementApps - array of additional packagenames to search for + * @return true if one of the apps it's installed + */ + public boolean _detectRootManagementApps(String[] additionalRootManagementApps) { + + // Create a list of package names to iterate over from constants any others provided + ArrayList packages = new ArrayList<>(Arrays.asList(AntiRootConst.knownRootAppsPackages)); + if (additionalRootManagementApps!=null && additionalRootManagementApps.length>0){ + packages.addAll(Arrays.asList(additionalRootManagementApps)); + } + + return isAnyPackageFromListInstalled(packages); + } + + + /** + * Using the PackageManager, check for a list of well known apps that require root. @link {AntiRootConst.knownRootAppsPackages} + * @param additionalDangerousApps - array of additional packagenames to search for + * @return true if one of the apps it's installed + */ + public boolean _detectPotentiallyDangerousApps(String[] additionalDangerousApps) { + + // Create a list of package names to iterate over from constants any others provided + ArrayList packages = new ArrayList<>(); + packages.addAll(Arrays.asList(AntiRootConst.knownDangerousAppsPackages)); + if (additionalDangerousApps!=null && additionalDangerousApps.length>0){ + packages.addAll(Arrays.asList(additionalDangerousApps)); + } + + return isAnyPackageFromListInstalled(packages); + } + + /** + * A variation on the checking for SU, this attempts a 'which su' + * @return true if su found + */ + public boolean _checkSuExists() { + Process process = null; + try { + process = Runtime.getRuntime().exec(new String[] { "which", AntiRootConst.BINARY_SU }); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + return in.readLine() != null; + } catch (Throwable t) { + return false; + } finally { + if (process != null) process.destroy(); + } + } + + + /** + * + * @param filename - check for this existence of this file + * @return true if found + */ + public boolean _checkForBinary(String filename) { + + String[] pathsArray = AntiRootConst.suPaths; + + boolean result = false; + + for (String path : pathsArray) { + String completePath = path + filename; + File f = new File(path, filename); + boolean fileExists = f.exists(); + if (fileExists) { + Log.v(TAG, completePath + " binary detected!"); + result = true; + } + } + + return result; + } + + +// private String[] mountReader() { +// try { +// InputStream inputstream = Runtime.getRuntime().exec("mount").getInputStream(); +// if (inputstream == null) return null; +// String propVal = new Scanner(inputstream).useDelimiter("\\A").next(); +// return propVal.split("\n"); +// } catch (IOException | NoSuchElementException e) { +// e.printStackTrace(); +// return null; +// } +// } +// +// /** +// * When you're root you can change the permissions on common system directories, this method checks if any of these paths AntiRootConst.pathsThatShouldNotBeWritable are writable. +// * @return true if one of the dir is writable +// */ +// public boolean _checkForRWPaths() { +// +// boolean result = false; +// +// String[] lines = mountReader(); +// +// if (lines == null){ +// // Could not read, assume false; +// return false; +// } +// +// for (String line : lines) { +// +// // Split lines into parts +// String[] args = line.split(" "); +// +// if (args.length < 4){ +// // If we don't have enough options per line, skip this and log an error +// Log.e(TAG, "Error formatting mount line: "+line); +// continue; +// } +// +// String mountPoint = args[1]; +// String mountOptions = args[3]; +// +// for(String pathToCheck: AntiRootConst.pathsThatShouldNotBeWritable) { +// if (mountPoint.equalsIgnoreCase(pathToCheck)) { +// +// // Split options out and compare against "rw" to avoid false positives +// for (String option : mountOptions.split(",")){ +// +// if (option.equalsIgnoreCase("rw")){ +// Log.v(TAG, pathToCheck+" path is mounted with rw permissions! "+line); +// result = true; +// break; +// } +// } +// } +// } +// } +// +// return result; +// } + + +} diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiRootConst.java b/sdk/src/main/java/com/crux/sdk/security/AntiRootConst.java new file mode 100644 index 0000000..eb36d3a --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/AntiRootConst.java @@ -0,0 +1,77 @@ +package com.crux.sdk.security; + +public final class AntiRootConst { + + public static final String BINARY_SU = "su"; + public static final String BINARY_BUSYBOX = "busybox"; + public static final String MAGISK = "magisk"; + + private AntiRootConst() throws InstantiationException { + throw new InstantiationException("This class is not for instantiation"); + } + + public static final String[] knownRootAppsPackages = { + "com.noshufou.android.su", + "com.noshufou.android.su.elite", + "eu.chainfire.supersu", + "com.koushikdutta.superuser", + "com.thirdparty.superuser", + "com.yellowes.su", + "com.topjohnwu.magisk" + }; + + public static final String[] knownDangerousAppsPackages = { + "com.koushikdutta.rommanager", + "com.koushikdutta.rommanager.license", + "com.dimonvideo.luckypatcher", + "com.chelpus.lackypatch", + "com.ramdroid.appquarantine", + "com.ramdroid.appquarantinepro", + "com.android.vending.billing.InAppBillingService.COIN", + "com.chelpus.luckypatcher" + }; + + public static final String[] knownRootCloakingPackages = { + "com.devadvance.rootcloak", + "com.devadvance.rootcloakplus", + "de.robv.android.xposed.installer", + "com.saurik.substrate", + "com.zachspong.temprootremovejb", + "com.amphoras.hidemyroot", + "com.amphoras.hidemyrootadfree", + "com.formyhm.hiderootPremium", + "com.formyhm.hideroot" + }; + + // These must end with a / + public static final String[] suPaths = { + "/data/local/", + "/data/local/bin/", + "/data/local/xbin/", + "/sbin/", + "/su/bin/", + "/system/bin/", + "/system/bin/.ext/", + "/system/bin/failsafe/", + "/system/sd/xbin/", + "/system/usr/we-need-root/", + "/system/xbin/", + "/cache/", + "/data/", + "/dev/" + }; + + + public static final String[] pathsThatShouldNotBeWritable = { + "/system", + "/system/bin", + "/system/sbin", + "/system/xbin", + "/vendor/bin", + "/sbin", + "/etc", + //"/sys", + //"/proc", + //"/dev" + }; +} From 85abb7eb6828b3f500e4698279050beddeed6a6b Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Thu, 16 Jan 2020 14:21:07 +0000 Subject: [PATCH 07/27] Finalize sad safety with randomize erros --- .../example/liquid_test_2/MainActivity.java | 4 +- .../main/java/com/crux/sdk/CruxClient.java | 12 +- .../sdk/model/AndroidCruxClientErrorCode.java | 2 +- .../model/AndroidCruxClientErrorString.java | 2 +- .../java/com/crux/sdk/security/AntiDebug.java | 112 +++++++++++++++++- .../java/com/crux/sdk/security/AntiRoot.java | 26 ++-- .../com/crux/sdk/security/AntiTamper.java | 106 +++++++++++++++++ .../java/com/crux/sdk/security/SdkSafety.java | 96 +++++++++++++++ 8 files changed, 341 insertions(+), 19 deletions(-) create mode 100644 sdk/src/main/java/com/crux/sdk/security/AntiTamper.java create mode 100644 sdk/src/main/java/com/crux/sdk/security/SdkSafety.java diff --git a/app/src/main/java/com/example/liquid_test_2/MainActivity.java b/app/src/main/java/com/example/liquid_test_2/MainActivity.java index faf8020..18379d5 100644 --- a/app/src/main/java/com/example/liquid_test_2/MainActivity.java +++ b/app/src/main/java/com/example/liquid_test_2/MainActivity.java @@ -43,7 +43,8 @@ public String runScript(final Context androidContextObject) throws IOException, try { client = new CruxClient(configBuilder, androidContextObject); } catch(CruxClientError e) { - Toast.makeText(androidContextObject, "Debug from vm detected.", 5).show(); + Toast.makeText(androidContextObject, "Client caught and reraised:" + e.errorMessage, Toast.LENGTH_LONG).show(); + System.out.println("use debug version for development"); return null; } @@ -134,7 +135,6 @@ public void onErrorResponse(CruxClientError failureResponse) { } }); - return null; } diff --git a/sdk/src/main/java/com/crux/sdk/CruxClient.java b/sdk/src/main/java/com/crux/sdk/CruxClient.java index 078d9a5..548b2e1 100644 --- a/sdk/src/main/java/com/crux/sdk/CruxClient.java +++ b/sdk/src/main/java/com/crux/sdk/CruxClient.java @@ -14,8 +14,7 @@ import com.crux.sdk.model.CruxIDState; import com.crux.sdk.model.CruxParams; import com.crux.sdk.model.CruxPutAddressMapSuccess; -import com.crux.sdk.security.AntiDebug; -import com.crux.sdk.security.AntiEmulator; +import com.crux.sdk.security.SdkSafety; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -29,14 +28,15 @@ public class CruxClient { private final CruxJSBridge jsBridge; public CruxClient(CruxClientInitConfig.Builder configBuilder, Context androidContextObject) throws IOException, CruxClientError { - if (BuildConfig.BUILD_TYPE.contentEquals("debug")) { - if (AntiDebug.isDebugging() || AntiEmulator.isEmulator()) { - throw CruxClientError.getCruxClientError(AndroidCruxClientErrorCode.getCruxClientDebuggerFailed); - } + + SdkSafety sf = new SdkSafety(androidContextObject); + if (sf.checkSafety()) { + throw CruxClientError.getCruxClientError(AndroidCruxClientErrorCode.runningInUnsafeEnvironment); } this.jsBridge = new CruxJSBridge(configBuilder, androidContextObject); } + public void getCruxIDState(final CruxClientResponseHandler handler) { CruxJSBridgeAsyncRequest bridgeRequest = new CruxJSBridgeAsyncRequest("getCruxIDState", new CruxParams(), new CruxJSBridgeResponseHandlerImpl(CruxIDState.class, handler)); jsBridge.executeAsync(bridgeRequest); diff --git a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java index 4a0111a..fdded3f 100644 --- a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java +++ b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorCode.java @@ -5,7 +5,7 @@ public class AndroidCruxClientErrorCode { // 888s: Android Error Series public static Integer getCruxClientInitConfigStringFailed = 8881000; public static Integer cruxAddressMappingConversionFailed = 8881001; - public static Integer getCruxClientDebuggerFailed = 8881002; + public static Integer runningInUnsafeEnvironment = 8881002; } diff --git a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java index bb49025..3411d80 100644 --- a/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java +++ b/sdk/src/main/java/com/crux/sdk/model/AndroidCruxClientErrorString.java @@ -9,7 +9,7 @@ public class AndroidCruxClientErrorString { static { errorCodeToErrorStringMap = new HashMap(); errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.getCruxClientInitConfigStringFailed, "Could not initialize cruxClientConfig"); - errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.getCruxClientDebuggerFailed, "Could not initialize cruxClient as debugger detected"); + errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.runningInUnsafeEnvironment, "CRUX SDK should not run in unsafe environment"); errorCodeToErrorStringMap.put(AndroidCruxClientErrorCode.cruxAddressMappingConversionFailed, "Could not create CruxAddressMapping"); } } diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java b/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java index 537187c..8f4d347 100644 --- a/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java +++ b/sdk/src/main/java/com/crux/sdk/security/AntiDebug.java @@ -1,10 +1,118 @@ package com.crux.sdk.security; +import android.content.Context; import android.os.Debug; +import android.content.pm.ApplicationInfo; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; public class AntiDebug { - public static boolean isDebugging() { - return Debug.isDebuggerConnected(); + private final Context mContext; + + public AntiDebug(Context context) { + mContext = context; + } + + public boolean isDebugging() { + + if (Debug.isDebuggerConnected()) { + return true; + } + + return (mContext.getApplicationContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + + //**************************************** TracerPid begin ************************************ + + public static boolean isTracerPid() { + + if (isLocalPortUsing(23946)) { + return true; + } + + String tracerPid = getTracerPid(); + if (!"0".equals(tracerPid)) { + return true; + } + + return false; + } + + /*** + * true:already in using false:not using + * @param port + */ + private static boolean isLocalPortUsing(int port) { + boolean flag = true; + try { + flag = isPortUsing("127.0.0.1", port); + } catch (Exception e) { + } + return flag; + } + + /*** + * true:already in using false:not using + * @param host + * @param port + * @throws UnknownHostException + */ + private static boolean isPortUsing(String host, int port) throws UnknownHostException { + boolean flag = false; + InetAddress theAddress = InetAddress.getByName(host); + try { + Socket socket = new Socket(theAddress, port); + flag = true; + } catch (IOException e) { + } + return flag; + } + + private static String getTracerPid() { + BufferedReader bufferedReader = null; + String readLine = ""; + try { + bufferedReader = new BufferedReader(new FileReader("/proc/self/status")); + do { + readLine = bufferedReader.readLine(); + if (readLine == null) { + break; + } + + } while (!readLine.startsWith("TracerPid:")); + readLine = readLine.substring("TracerPid:".length()).trim(); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return readLine; + } + //**************************************** TracerPid end ************************************** + + //**************************************** network test begin ********************************* + public static boolean checkGrabData() { + try { + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + return proxyHost != null || proxyPort != null; + } catch (Exception e) { + e.printStackTrace(); + return false; + } } + //**************************************** network test end *********************************** } diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java b/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java index 923dad5..582ef2a 100644 --- a/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java +++ b/sdk/src/main/java/com/crux/sdk/security/AntiRoot.java @@ -26,9 +26,12 @@ public AntiRoot(Context context) { public boolean isRooted() { - return _detectTestKeys() || _detectRootManagementApps(null) - || _detectPotentiallyDangerousApps(null) || _checkSuExists() || _checkForBinary(AntiRootConst.BINARY_SU) - || _checkForBinary(AntiRootConst.BINARY_BUSYBOX) || _checkForBinary(AntiRootConst.MAGISK); + return _detectTestKeys() + || _detectRootManagementApps(null) + || _detectPotentiallyDangerousApps(null) + || _detectRootCloakingApps(null) + || (!_checkForBinary(AntiRootConst.BINARY_SU) || !_checkForBinary(AntiRootConst.BINARY_BUSYBOX) || !_checkForBinary(AntiRootConst.MAGISK)) + || _checkSuExists(); // || _checkForRWPaths() } @@ -84,7 +87,6 @@ public boolean _detectRootManagementApps(String[] additionalRootManagementApps) return isAnyPackageFromListInstalled(packages); } - /** * Using the PackageManager, check for a list of well known apps that require root. @link {AntiRootConst.knownRootAppsPackages} * @param additionalDangerousApps - array of additional packagenames to search for @@ -102,6 +104,16 @@ public boolean _detectPotentiallyDangerousApps(String[] additionalDangerousApps) return isAnyPackageFromListInstalled(packages); } + public boolean _detectRootCloakingApps(String[] additionalRootCloakingApps) { + // Create a list of package names to iterate over from constants any others provided + ArrayList packages = new ArrayList<>(); + packages.addAll(Arrays.asList(AntiRootConst.knownRootCloakingPackages)); + if (additionalRootCloakingApps!=null && additionalRootCloakingApps.length>0){ + packages.addAll(Arrays.asList(additionalRootCloakingApps)); + } + return isAnyPackageFromListInstalled(packages); + } + /** * A variation on the checking for SU, this attempts a 'which su' * @return true if su found @@ -129,7 +141,7 @@ public boolean _checkForBinary(String filename) { String[] pathsArray = AntiRootConst.suPaths; - boolean result = false; + boolean result = true; for (String path : pathsArray) { String completePath = path + filename; @@ -137,11 +149,11 @@ public boolean _checkForBinary(String filename) { boolean fileExists = f.exists(); if (fileExists) { Log.v(TAG, completePath + " binary detected!"); - result = true; + result = false; } } - return result; + return !result; } diff --git a/sdk/src/main/java/com/crux/sdk/security/AntiTamper.java b/sdk/src/main/java/com/crux/sdk/security/AntiTamper.java new file mode 100644 index 0000000..7728812 --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/AntiTamper.java @@ -0,0 +1,106 @@ +package com.crux.sdk.security; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import java.util.Arrays; +import java.util.List; + +public class AntiTamper { + + private final Context mContext; + + public AntiTamper(Context context) { + mContext = context; + } + + //**************************************** hook detection begin **************************** + + /** + * Detects if there is any suspicious installed application. + * + * @return true if some bad application is installed, false otherwise. + */ + public boolean hookDetected() { + PackageManager packageManager = mContext.getPackageManager(); + List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); + String[] dangerousPackages = {"de.robv.android.xposed.installer", "com.saurik.substrate", "de.robv.android.xposed"}; + + for (ApplicationInfo applicationInfo : applicationInfoList) { + if (Arrays.asList(dangerousPackages).contains(applicationInfo.packageName)) { + return true; + } + } + + return this.advancedHookDetection(); + } + + private boolean advancedHookDetection() { + try { + throw new Exception(); + } catch (Exception e) { + int zygoteInitCallCount = 0; + for (StackTraceElement stackTraceElement : e.getStackTrace()) { + if (stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) { + zygoteInitCallCount++; + if (zygoteInitCallCount == 2) { + return true; + } + } + + if (stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") && + stackTraceElement.getMethodName().equals("invoked")) { + return true; + } + + if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && + stackTraceElement.getMethodName().equals("main")) { + return true; + } + + if (stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") && + stackTraceElement.getMethodName().equals("handleHookedMethod")) { + return true; + } + } + } + + return !this.checkFrida(); + } + + private boolean checkFrida() { + ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + List runningServices = activityManager.getRunningServices(300); + + if (runningServices != null) { + for (int i = 0; i < runningServices.size(); ++i) { + if (runningServices.get(i).process.contains("fridaserver")) { + return false; + } + } + } + + return true; + } + + //**************************************** hook detection ends **************************** + + //**************************************** signature testing begin **************************** + public boolean isOwnApp() { + String APP_SIGN = "test"; + + if (mContext == null) { + return false; + } + + String signStr = this.getSignature(); + return APP_SIGN.equals(signStr); + } + + private String getSignature() { + return "test"; + } + //**************************************** signature testing ends ***************************** +} diff --git a/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java b/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java new file mode 100644 index 0000000..8217299 --- /dev/null +++ b/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java @@ -0,0 +1,96 @@ +package com.crux.sdk.security; + +import android.content.Context; +import android.widget.Toast; +import com.crux.sdk.BuildConfig; + +import java.util.Random; + + +public class SdkSafety { + + private final Context mContext; + + public SdkSafety(Context context) { + mContext = context; + } + + public boolean checkSafety() { + + boolean isUnsafe = _checkSafety(); + + if (isUnsafe) { + Toast.makeText(mContext, "sdk informed unsafe env", Toast.LENGTH_LONG).show(); + } + if (isReleaseVersion()) { + return isUnsafe; + // System.exit(0); + } + return false; + } + + private boolean isReleaseVersion() { + // return true; + return BuildConfig.BUILD_TYPE.contentEquals("release"); + } + + private boolean _checkSafety() { + + AntiTamper at = new AntiTamper(mContext); + AntiDebug ad = new AntiDebug(mContext); + AntiRoot ar = new AntiRoot(mContext); + + Random rand = new Random(); + int randomizeCheck = 7; // rand.nextInt(1000)/7; + switch (randomizeCheck) { + case 1: + if (AntiEmulator.isEmulator()) { + return true; + } + break; + + case 2: + boolean ownApp = at.isOwnApp(); + if (!ownApp) { + return true; + } + break; + + case 3: + if (ad.isDebugging()) { + return true; + } + break; + + case 4: + if (AntiDebug.isTracerPid()) { + return true; + } + break; + + case 5: + if (at.hookDetected()) { + return true; + } + + case 6: + if (ar.isRooted()) { + return true; + } + + case 0: + if (AntiDebug.checkGrabData()) { + return true; + } + + default: + return AntiEmulator.isEmulator() || AntiDebug.isTracerPid() || at.hookDetected() + || ar.isRooted() || AntiDebug.checkGrabData() || ad.isDebugging(); + } + + return AntiEmulator.isEmulator() || AntiDebug.isTracerPid() || at.hookDetected() + || ar.isRooted() || AntiDebug.checkGrabData() || ad.isDebugging(); + + } + +} From aa9e9a1420ee63c46c761798d3b026073f0a013b Mon Sep 17 00:00:00 2001 From: mascot6699 Date: Thu, 16 Jan 2020 14:21:35 +0000 Subject: [PATCH 08/27] Randomize error --- sdk/src/main/java/com/crux/sdk/security/SdkSafety.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java b/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java index 8217299..cfcbd7c 100644 --- a/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java +++ b/sdk/src/main/java/com/crux/sdk/security/SdkSafety.java @@ -41,7 +41,7 @@ private boolean _checkSafety() { AntiRoot ar = new AntiRoot(mContext); Random rand = new Random(); - int randomizeCheck = 7; // rand.nextInt(1000)/7; + int randomizeCheck = rand.nextInt(1000)/7; switch (randomizeCheck) { case 1: if (AntiEmulator.isEmulator()) { From aad39a9fbe4d829aab5f323ea4d8d5967bb7da7d Mon Sep 17 00:00:00 2001 From: Sanchay Date: Thu, 16 Jan 2020 20:21:43 +0530 Subject: [PATCH 09/27] * Passing gson instead of creating a new object everytime CruxJSBridgeResponseHandlerImpl is instantiated and updated cruxpay to 0.1.9 --- .../example/liquid_test_2/MainActivity.java | 13 +- sdk/src/main/assets/cruxpay-0.1.9.js | 123592 +++++++++++++++ .../main/java/com/crux/sdk/CruxClient.java | 13 +- .../com/crux/sdk/bridge/CruxJSBridge.java | 3 +- .../CruxJSBridgeResponseHandlerImpl.java | 4 +- 5 files changed, 123610 insertions(+), 15 deletions(-) create mode 100644 sdk/src/main/assets/cruxpay-0.1.9.js diff --git a/app/src/main/java/com/example/liquid_test_2/MainActivity.java b/app/src/main/java/com/example/liquid_test_2/MainActivity.java index 3c476cd..245269b 100644 --- a/app/src/main/java/com/example/liquid_test_2/MainActivity.java +++ b/app/src/main/java/com/example/liquid_test_2/MainActivity.java @@ -6,12 +6,13 @@ import androidx.appcompat.app.AppCompatActivity; import com.crux.sdk.CruxClient; -import com.crux.sdk.model.CruxAddress; -import com.crux.sdk.model.CruxClientError; -import com.crux.sdk.model.CruxClientInitConfig; -import com.crux.sdk.model.CruxClientResponseHandler; -import com.crux.sdk.model.CruxIDState; -import com.crux.sdk.model.CruxPutAddressMapSuccess; +import com.crux.sdk.model.*; +//import com.crux.sdk.model.CruxAddress; +//import com.crux.sdk.model.CruxClientError; +//import com.crux.sdk.model.CruxClientInitConfig; +//import com.crux.sdk.model.CruxClientResponseHandler; +//import com.crux.sdk.model.CruxIDState; +//import com.crux.sdk.model.CruxPutAddressMapSuccess; import java.io.IOException; import java.util.HashMap; diff --git a/sdk/src/main/assets/cruxpay-0.1.9.js b/sdk/src/main/assets/cruxpay-0.1.9.js new file mode 100644 index 0000000..90d48b8 --- /dev/null +++ b/sdk/src/main/assets/cruxpay-0.1.9.js @@ -0,0 +1,123592 @@ +// modules are defined as an array +// [ module function, map of requires ] +// +// map of requires is short require name -> numeric require +// +// anything defined in a previous bundle is accessed via the +// orig method which is the require for previous bundles +parcelRequire = (function (modules, cache, entry, globalName) { + // Save the require from previous bundle to this closure if any + var previousRequire = typeof parcelRequire === 'function' && parcelRequire; + var nodeRequire = typeof require === 'function' && require; + + function newRequire(name, jumped) { + if (!cache[name]) { + if (!modules[name]) { + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = typeof parcelRequire === 'function' && parcelRequire; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error('Cannot find module \'' + name + '\''); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + localRequire.resolve = resolve; + localRequire.cache = {}; + + var module = cache[name] = new newRequire.Module(name); + + modules[name][0].call(module.exports, localRequire, module, module.exports, this); + } + + return cache[name].exports; + + function localRequire(x){ + return newRequire(localRequire.resolve(x)); + } + + function resolve(x){ + return modules[name][1][x] || x; + } + } + + function Module(moduleName) { + this.id = moduleName; + this.bundle = newRequire; + this.exports = {}; + } + + newRequire.isParcelRequire = true; + newRequire.Module = Module; + newRequire.modules = modules; + newRequire.cache = cache; + newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [function (require, module) { + module.exports = exports; + }, {}]; + }; + + var error; + for (var i = 0; i < entry.length; i++) { + try { + newRequire(entry[i]); + } catch (e) { + // Save first error but execute all entries + if (!error) { + error = e; + } + } + } + + if (entry.length) { + // Expose entry point to Node, AMD or browser globals + // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js + var mainExports = newRequire(entry[entry.length - 1]); + + // CommonJS + if (typeof exports === "object" && typeof module !== "undefined") { + module.exports = mainExports; + + // RequireJS + } else if (typeof define === "function" && define.amd) { + define(function () { + return mainExports; + }); + + //