diff --git a/README.md b/README.md index ab0c1b1..e8e5088 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # BillingProtector -BillingProtector is and android robust and small library whose aim is to check the device state & the purchases security. +BillingProtector is and android robust and small library aiming to check the device state & purchases security. Its main purpose is to block a transition if the application has been modified or patched; it can also be used to prevent applications from being executed on an unprotected/insecure envoronment. # Setup @@ -20,7 +20,8 @@ dependencies { ``` # Usage -BillingProtector has some core functionalities. Let's see them in a list +BillingProtector has different functionalities and can be used for different purposes. + ## Initialization Create an instance of the BillingProtector class passing as argument the context of your current activity/fragment diff --git a/app/build.gradle b/app/build.gradle index d5f1337..0825a3c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:design:28.0.0' implementation project(path: ':library') } diff --git a/app/src/main/java/com/andreacioccarelli/billingprotectorsample/MainActivity.kt b/app/src/main/java/com/andreacioccarelli/billingprotectorsample/MainActivity.kt index 77fb88e..9bfae02 100644 --- a/app/src/main/java/com/andreacioccarelli/billingprotectorsample/MainActivity.kt +++ b/app/src/main/java/com/andreacioccarelli/billingprotectorsample/MainActivity.kt @@ -22,9 +22,9 @@ class MainActivity : AppCompatActivity() { title = "Kotlin activity" val bp = BillingProtector(baseContext) - val isRootDetected = bp.isRootInstalled - val arePirateAppsInstalled = bp.arePirateAppsInstalled - val pirateList = bp.pirateAppsList + val isRootDetected = bp.isRootInstalled() + val arePirateAppsInstalled = bp.getPirateAppsList() + val pirateList = bp.getPirateAppsList() mxp.text = "isRootInstalled: $isRootDetected\narePirateAppsInstalled: $arePirateAppsInstalled\npirateAppsList: ${pirateList.map { it.packageName }}" diff --git a/app/src/main/java/com/andreacioccarelli/billingprotectorsample/SecondaryActivity.java b/app/src/main/java/com/andreacioccarelli/billingprotectorsample/SecondaryActivity.java index 594c316..1f42b9d 100644 --- a/app/src/main/java/com/andreacioccarelli/billingprotectorsample/SecondaryActivity.java +++ b/app/src/main/java/com/andreacioccarelli/billingprotectorsample/SecondaryActivity.java @@ -27,7 +27,7 @@ protected void onCreate(Bundle savedInstanceState) { TextView mxp = findViewById(R.id.mxp); mxp.setText( "isRootInstalled: " + String.valueOf(bp.isRootInstalled()) + - "\narePirateAppsInstalled: " + bp.getArePirateAppsInstalled() + + "\narePirateAppsInstalled: " + bp.getPirateAppsList() + "\npirateAppsList: " + bp.getPirateAppsList() ); diff --git a/build.gradle b/build.gradle index 70ff96f..93325cb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.71' + ext.kotlin_version = '1.3.0' repositories { google() jcenter() diff --git a/library/build.gradle b/library/build.gradle index b87f638..aa8ddc4 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -12,9 +12,7 @@ android { targetSdkVersion 28 versionCode 1 versionName "1.0.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } buildTypes { @@ -23,13 +21,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation 'com.android.support:appcompat-v7:28.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/library/src/main/java/com/andreacioccarelli/billingprotector/BillingProtector.kt b/library/src/main/java/com/andreacioccarelli/billingprotector/BillingProtector.kt index 983f672..a4aa0ce 100644 --- a/library/src/main/java/com/andreacioccarelli/billingprotector/BillingProtector.kt +++ b/library/src/main/java/com/andreacioccarelli/billingprotector/BillingProtector.kt @@ -14,48 +14,58 @@ import com.andreacioccarelli.billingprotector.utils.RootUtils */ class BillingProtector(private val context: Context) { - private val pirateApps: List = createPirateAppsList() + /** + * Returns a boolean that represents the device root state. + * */ + fun isRootInstalled() = RootUtils.hasRootAccess() - val isRootInstalled = RootUtils.hasRootAccess() + /** + * Returns a String, representing the root binary path, if present. + * */ + fun getRootBinatyPath() = RootUtils.extractPath() - val arePirateAppsInstalled: Boolean - get() { - val appList = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) + /** + * Returns a boolean that indicates the presence of pirate apps in the host system + * */ + fun arePirateAppsInstalled(): Boolean { + val appList = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) - for (app in appList) { - pirateApps.map { - when (it.criteria) { - SelectionCriteria.SLICE -> { - if (it.packageName.contains(app.packageName)) return true - } + for (app in appList) { + createPirateAppsList().map { + when (it.criteria) { + SelectionCriteria.SLICE -> { + if (it.packageName.contains(app.packageName)) return true + } - SelectionCriteria.MATCH -> { - if (it.packageName == app.packageName) return true - } + SelectionCriteria.MATCH -> { + if (it.packageName == app.packageName) return true } } } - return false } + return false + } + + /** + * Returns a list of the installed apps detected as pirate software + * */ + fun getPirateAppsList(): List { + val foundThreats = mutableListOf() + val appList = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) + + for (app in appList) { + createPirateAppsList().map { + when (it.criteria) { + SelectionCriteria.SLICE -> { + if (it.packageName.contains(app.packageName)) foundThreats.add(it) + } - val pirateAppsList: List - get() { - val foundThreats = mutableListOf() - val appList = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) - - for (app in appList) { - pirateApps.map { - when (it.criteria) { - SelectionCriteria.SLICE -> { - if (it.packageName.contains(app.packageName)) foundThreats.add(it) - } - - SelectionCriteria.MATCH -> { - if (it.packageName == app.packageName) foundThreats.add(it) - } + SelectionCriteria.MATCH -> { + if (it.packageName == app.packageName) foundThreats.add(it) } } } - return foundThreats } + return foundThreats.toList() + } } \ No newline at end of file diff --git a/library/src/main/java/com/andreacioccarelli/billingprotector/data/PirateApp.kt b/library/src/main/java/com/andreacioccarelli/billingprotector/data/PirateApp.kt index bd1a852..f6e5aed 100644 --- a/library/src/main/java/com/andreacioccarelli/billingprotector/data/PirateApp.kt +++ b/library/src/main/java/com/andreacioccarelli/billingprotector/data/PirateApp.kt @@ -5,21 +5,20 @@ import android.util.Log /** - * Created by andrea on 2018/Jul. - * Part of the package com.andreacioccarelli.billingprotector.data + * Class representing a pirate app with built-in string sign check */ data class PirateApp(val packageName: String, val encodedPackageName: String, val criteria: SelectionCriteria, val name: String) { init { val check = Base64.encodeToString(packageName.toByteArray(), Base64.DEFAULT) if (check.trim() != encodedPackageName.trim()) { - Log.d("Security error", "pn=$packageName, Check=[$check], encodedPN=[$encodedPackageName]") - throw SecurityException("Package names mismatch") + Log.e("BillingProtector", "Package Name=[$packageName], Sign Check String=[$check], Base64 Encoded Package Name=[$encodedPackageName]") + throw SecurityException("Package names mismatch, apk file damaged or corrupted") } } } -fun createPirateAppsList() = listOf( +internal fun createPirateAppsList() = listOf( PirateApp("com.chelpus.lackypatch", "Y29tLmNoZWxwdXMubGFja3lwYXRjaA==", SelectionCriteria.MATCH, "Chelpus Lucky Patcher"), PirateApp("com.dimonvideo.luckypatcher", "Y29tLmRpbW9udmlkZW8ubHVja3lwYXRjaGVy", SelectionCriteria.MATCH, "Lucky Patcher"), PirateApp("com.forpda.lp", "Y29tLmZvcnBkYS5scA==", SelectionCriteria.MATCH, "4Pda Lucy Patcher"), diff --git a/library/src/main/java/com/andreacioccarelli/billingprotector/data/SelectionCriteria.kt b/library/src/main/java/com/andreacioccarelli/billingprotector/data/SelectionCriteria.kt index a4f6520..4400d2d 100644 --- a/library/src/main/java/com/andreacioccarelli/billingprotector/data/SelectionCriteria.kt +++ b/library/src/main/java/com/andreacioccarelli/billingprotector/data/SelectionCriteria.kt @@ -4,5 +4,4 @@ package com.andreacioccarelli.billingprotector.data * Created by andrea on 2018/Jul. * Part of the package com.andreacioccarelli.billingprotector.data */ - enum class SelectionCriteria { MATCH, SLICE } \ No newline at end of file diff --git a/library/src/main/java/com/andreacioccarelli/billingprotector/utils/RootUtils.kt b/library/src/main/java/com/andreacioccarelli/billingprotector/utils/RootUtils.kt index 907e2e1..77cbb3e 100644 --- a/library/src/main/java/com/andreacioccarelli/billingprotector/utils/RootUtils.kt +++ b/library/src/main/java/com/andreacioccarelli/billingprotector/utils/RootUtils.kt @@ -1,45 +1,69 @@ package com.andreacioccarelli.billingprotector.utils import java.io.BufferedReader +import java.io.IOException import java.io.InputStreamReader /** * Created by andrea on 2018/Jul. * Part of the package com.andreacioccarelli.billingprotector.utils */ -object RootUtils { - val path: String - get() { - return try { - val process = Runtime.getRuntime().exec("which su") +internal object RootUtils { - val output = StringBuffer() - val buffer = BufferedReader(InputStreamReader(process.inputStream)) + /** + * Requests the root binary path by using unix which command, that + * returns the path of a specified executable, in this case, su + * + * This is usually placed in directories like /system/bin, + * /system/xbin, /su, etc.. + * + * While checking the path, root access is not requested or logged + * + * @return The path of the binary, or an empty string if + * nothing is found in the host system + * */ + internal fun extractPath(): String { + val inspectionCommand = "which su" - var l: String? + return try { + val process = Runtime.getRuntime().exec(inspectionCommand) - do { - l = buffer.readLine() + val outputBuffer = StringBuffer() + val bufferedReader = BufferedReader(InputStreamReader(process.inputStream)) - if (l == null) - break + var line: String? - output.append(l) - } while (true) + do { + line = bufferedReader.readLine() - output.toString() - } catch (io: Exception) { - "" - } + if (line == null) + break + + outputBuffer.append(line) + } while (true) + + outputBuffer.toString() + } catch (io: IOException) { + "" + } catch (nil: NullPointerException) { + "" } + } - fun hasRootAccess(): Boolean { - val path = path - if (path.isEmpty()) return false - if (path == "") return false - if (!path.contains("/")) return false - if (!path.contains("su")) return false - return true + /** + * Determines wheter or not the device has root access by + * analyzing the path of the su binary. + * + * @return A Boolean representing the device root state + * */ + internal fun hasRootAccess(): Boolean { + val path = extractPath() + return when { + path.isEmpty() -> false + !path.contains("/") -> false + !path.contains("su") -> false + else -> true + } } } \ No newline at end of file