Skip to content

Commit

Permalink
Docs update: added details, improved graphs and finished reference
Browse files Browse the repository at this point in the history
  • Loading branch information
cioccarellia committed Jan 23, 2024
1 parent 341de24 commit 893012a
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 93 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ and **app-wise** security (tampering, recompiling, changed signature & metadata)


## Flexibility
Kevlar does not automatically detect a "standard" unsafe environment and gives a 0/1 answer.
Kevlar does not automatically detect a "standard" unsafe environment and give a 0/1 answer.
The kind of environment that is acceptable for your app to run in can be configured in each package individually.

You may be indifferent to some things (e.g. root detection) and very sensitive about others (e.g. app tampering & piracy detection).
Expand All @@ -66,14 +66,14 @@ If you don't explicitly instruct kevlar to check for a feature, then that featur
## Design
Each kevlar package contains custom implementations for what it has to scan for, but they all share the same overall structure, to make it easy to work with. Once you learn how to use a package, then you can transfer that knowledge to the other ones.


``` mermaid
graph LR
I[Inizialization] -.Settings..-> K{Kevlar};
AREQ[Attestation Requests] --> K
K --> |Clear| P[Passed];
K --> |Failed| NP[Not Passed];
P --> ARES[Attestation Result]
NP --> ARES
K --> ARES[Attestation Result]
ARES --> |Clear| P[Passed];
ARES --> |Failed| NP[Not Passed];
```

The founding idea is a flow of attestations. You initialize the package passing to it your settings (what you want to check for). Then you can go ahead and start requesting attestations. An attestation can either be Clear (passed) or Failed (non passed), according to your detection settings.
Expand Down
9 changes: 4 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ and **app-wise** security (tampering, recompiling, changed signature & metadata)


## Flexibility
Kevlar does not automatically detect a "standard" unsafe environment and gives a 0/1 answer.
Kevlar does not automatically detect a "standard" unsafe environment and give a 0/1 answer.
The kind of environment that is acceptable for your app to run in can be configured in each package individually.

You may be indifferent to some things (e.g. root detection) and very sensitive about others (e.g. app tampering & piracy detection).
Expand All @@ -46,10 +46,9 @@ Each kevlar package contains custom implementations for what it has to scan for,
graph LR
I[Inizialization] -.Settings..-> K{Kevlar};
AREQ[Attestation Requests] --> K
K --> |Clear| P[Passed];
K --> |Failed| NP[Not Passed];
P --> ARES[Attestation Result]
NP --> ARES
K --> ARES[Attestation Result]
ARES --> |Clear| P[Passed];
ARES --> |Failed| NP[Not Passed];
```

The founding idea is a flow of attestations. You initialize the package passing to it your settings (what you want to check for). Then you can go ahead and start requesting attestations. An attestation can either be Clear (passed) or Failed (non passed), according to your detection settings.
Expand Down
7 changes: 3 additions & 4 deletions docs/pages/modules/antipiracy/antipiracy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ graph LR
I[Inizialization] -.Settings..-> K{KevlarAntipiracy};
DB[(Dataset)] === K
AREQ[Attestation Requests] --> K
K --> |Clear| P[Passed];
K --> |Failed| NP[Not Passed];
P --> ARES[AntipiracyAttestation]
NP --> ARES
ARES --> |Clear| P[Passed];
ARES --> |Failed| NP[Not Passed];
K --> ARES[AntipiracyAttestation]
```

The antipiracy package contains tools for the detection of different categories of pirate software that may be installed and running on target devices.
Expand Down
29 changes: 16 additions & 13 deletions docs/pages/modules/integrity/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ debug check.

The hardcoded metadata you need to find is the following:

- Your application **package name**: Will check that the running binary's package name matches the
- Your application **package name**: Will be used to check that the running binary's package name matches the
hardcoded package name;
- Your application **signature**: Will check that the running binary's signature is the same as the
- Your application **signature**: Will be used to check that the running binary's signature is the same as the
hardcoded signature.

Additionally, you should consider enabling the other two kinds of checks:
Expand Down Expand Up @@ -115,7 +115,7 @@ integrity.attestate(context)
```


!!! fail "Do not"
!!! danger "Do not"
Maybe, just maybe, you may be tempted to so something like this:

```kotlin title="VERY_BAD_hardcoded_metadata.kt"
Expand All @@ -136,15 +136,18 @@ For the signature, it's not so straightforward to extract because it depends on
You have two different ways to get your keystore signature. In the examples we will find the debug signature, but you need to find the signature of the keystore you use to sign your application when publishing on google play.

#### Direct application extraction
The most practical way to read your keystore it is to put the following line of code in your app, to then sign the application with the key you are interested in acquiring the signature string of, and run it.
The most practical way to read your keystore it is to put the following line of code in your app (it is a courtesy method provided by kevlar to do just that), to then sign the application with the key you are interested in acquiring the signature string of, and run it.

```kotlin
// This returns the signature of the current running application.
val signature: String = KevlarIntegrity.obtainCurrentAppSignature(context)

// Log it to the console for extracting the signature
Log.d("SIGNATURE", signature)
```

This will output the current app signature.
That's the reference string you need to give to kevlar (which will extract the runtime signature of your app and match it against that string).
That's the reference string you need to give to kevlar through `hardcodedSignatures()` (which, once an attestation is requested, will extract the runtime signature of your app _(which may be tampered with, if someone recompiled your application)_ and match it against that string you just extracted).

!!! example "Android debug signature"
Every android application is signed with some key.
Expand All @@ -154,7 +157,7 @@ That's the reference string you need to give to kevlar (which will extract the r
!!! warning "Signature extraction & Google Play App Signing API"
If you are using Google Play App Signing, the key you sign your application with is not the one your app is distributed with (See the [official docs](https://developer.android.com/studio/publish/app-signing) regarding the matter, and a relevant [issue](https://github.com/kevlar-kt/kevlar/issues/1) in kevlar).

In this case the easiest way to get your actual signature would be to upload a dummy version of your app (which logs the runtime signature) through google play store, let the backend process and sign it, download it (through the archive manager on the play console), install & run it locally on an emulator/device, and save the runtime signature. Once you have done this (quite tedious) procedure, you have your signature and can pass it to kevlar.
In this case the easiest way to get your actual signature would be to upload a dummy version of your app (which logs the runtime signature) through google play store, let the backend process and sign it, download it (through the archive manager on the play console), install & run it locally on an emulator/device, and save the runtime signature. Once you have done this (quite tedious, but once-in-a-lifetime) procedure, you have your signature and can pass it to kevlar.

#### Android studio extraction
Running `./gradlew signingReport` will spit out all the details for all the different keystores in your project.
Expand All @@ -180,22 +183,22 @@ Alias: null
```

We then have to convert it in a string form (like that we have the raw hex bytes, we want a base64 encoding of the binary signature).
In this case the conversion (you can use online tools to do this) yields `J+nqXLfuIO8B2AmhkMYHGE4jDyw=`.
In my case the conversion (you can use [sha1_to_base64](https://emn178.github.io/online-tools/base64_encode.html) online tools to do this) yields `J+nqXLfuIO8B2AmhkMYHGE4jDyw=`.

!!! fail "Play Signing"
Since we don't have access to the keystore file if we use Play Signing, this method is not viable in that case, and you have to resort to uploading a dummy version of the app, download its play-signed version through the releases page, and extract the signature from that APK file.
!!! danger "Incompatible With Play Signing"
Since we don't have access to the "real" keystore file if we're using use Play Signing, this method is not viable in that case, and you have to resort to uploading a dummy version of the app, download its play-signed version through the releases page, and extract the signature from that APK file.


## Obfuscating metadata
The second step (optional but recommended) is obfuscating the metadata you just gathered, so that it
is **saved** in an obfuscated form (in your bytecode, so that automatic tools / unskilled attackers can't easily find it), but passed to kevlar deobfuscated (so that we have the original truth values at run time).
is **stored** in an obfuscated form (in your bytecode, so that automatic tools / unskilled attackers can't easily find it), but passed to kevlar deobfuscated (obviously kevlar has to receive the real, intended value, so that we have the original truth values. The idea is to perform the deobfuscation/decryption just at run time, not leaving a trace of the actual plaintext signature in the application code).

This means that we ship with out app the obfuscated data and the way to convert that obfuscated data back to plaintext to feed kevlar.
This means that we'll ship with our app the obfuscated data, and then at runtime (when kevlar is invoked) we will convert that obfuscated data back to plaintext to feed kevlar.

There are a few different ways to do it, all of them are fully implemented in the `:showcase` module:
There are a few different ways to do it, all of them are fully implemented in the `:showcase` module for you check out:

### No obfuscation (not recommended)
In this case you just save the values as they are, and pass them in `HardcodedMetadata`
In this case you just save the values as they are, and pass them in `HardcodedMetadata`:

```kotlin title="unobfuscated_hardcoded_metadata.kt"
private const val packageName = HardcodedPackageName("com.kevlar.showcase")
Expand Down
13 changes: 6 additions & 7 deletions docs/pages/modules/integrity/integrity.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ graph LR
I[Inizialization] -.Settings..-> K{KevlarIntegrity};
DB([Hardcoded & Obfuscated Metadata]) === K
AR1[Attestation Requests] --> K
K --> |Clear| P[Passed];
K --> |Failed| NP[Not Passed];
P --> A[IntegrityAssestation]
NP --> A
A --> |Clear| P[Passed];
A --> |Failed| NP[Not Passed];
K --> A[IntegrityAssestation]
```

The integrity package contains tools for the detection of tampering attempts against your app.
Expand All @@ -30,7 +29,7 @@ It is capable of detecting:

!!! warning Automatic vs Specific attack
This package is the best defense against automatic and/or unskilled attacks.
If implemented well, it will kill off most of them
If implemented well, it will kill off most of them.


To [implement](implementation.md) this, you initialize `KevlarIntegrity` and provide your desired settings (which influence what is to be checked and what not). Then you can submit attestation requests (which will be executed according to your settings).
Expand Down Expand Up @@ -73,11 +72,11 @@ Thus there are no default configuration: you have to manually specify each item
```kotlin title="Manual configuration (simplified)"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}
Expand Down
42 changes: 21 additions & 21 deletions docs/pages/modules/integrity/reference.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Reference

The complete integrity configuration is as follows.
The complete integrity configuration is as follows:

```kotlin title="Complete settings"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}
Expand Down Expand Up @@ -38,11 +38,11 @@ Once kevlar has all the required data it is able to differentiate between genuin
```kotlin hl_lines="3-6"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}
Expand All @@ -53,24 +53,25 @@ private val integrity = KevlarIntegrity {
}
```

You can find instruction on where to find the right parameters in [implementation](implementation.md).
You can find instruction on how to derive the right parameters for your app in [implementation](implementation.md).
In this case you simply have to pass in the package name of your app, so kevlar knows what is the right package.


## Signature check
The `signature()` function tells kevlar to enable the integrity checks for the application signature.

This is a parametric setting, since kevlar needs to know what is the "right" application signature is.
Once kevlar has all the required data it is able to differentiate between genuine and tampered binaries.

Once kevlar has all the required data, it is able to differentiate between genuine and tampered binaries (by checking the hardcoded data against the runtime-provided information).

```kotlin hl_lines="7-10"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}
Expand All @@ -81,7 +82,7 @@ private val integrity = KevlarIntegrity {
}
```

You can find instruction on where to find the right parameters in [implementation](implementation.md).
You can find instruction on how to derive the right parameters for your app in [implementation](implementation.md).



Expand All @@ -91,11 +92,11 @@ The `debug()` function tells kevlar to enable integrity debug checks.
```kotlin hl_lines="12"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}
Expand All @@ -106,33 +107,32 @@ private val integrity = KevlarIntegrity {
}
```

If debug flags are found on your application it will be reported.
If any debug flag is found on your application, it will be reported.


## Installer check
The `installer()` function tells kevlar to enable installer checks.

Since android R, google introduced APIs to check the original installer of a certain package.
With this check, you can instruct kevlar to analyze that installer (if available) and detect
whether it is allowed or not by your security policy.
Since android R, google introduced new APIs to check for the original installer of a certain package.

With this check, you can instruct kevlar to analyze (if available) which software installed your application, and detect whether it is allowed or not by your security policy.

In this case, the only allowed installer package is the Google Play Store, but you can always
add more through the `allowInstaller` function.
In this case, the only allowed installer package is the Google Play Store, but you can always add more (whitelist) through the `allowInstaller` function.

```kotlin hl_lines="13-15"
private val integrity = KevlarIntegrity {
checks {
packageName() {
packageName {
// Allowed package name
hardcodedPackageName("com.kevlar.showcase")
}
signature() {
signature {
// Allowed signature
hardcodedSignatures("J+nqXLfuIO8B2AmhkMYHGE4jDyw=")
}

debug()
installer() {
installer {
allowInstaller("com.sec.android.app.samsungapps")
}
}
Expand Down
32 changes: 5 additions & 27 deletions docs/pages/modules/rooting/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,37 +59,15 @@ We go ahead and create a working single-attestation example (for system modifica

## Configuration
As we said, the kinds of checks you can run are divided in two different categories, `targets` and `status`.
The first is to check for eventual system modification, the former to check for eventual in-system status.
The first is to check for system modification, the former to check for eventual in-system status.

The following complete configuration runs every check that kevlar disposes.
The following is the default (most commonly chosen) configuration.

In details:

- `flagPermissive()`, if enabled, will report `DetectableSystemStatus.SELINUX` also if selinux status is set to permissive status (which is a stricter criteria), while by default it will only trip if selinux is disabled;
- `allowExplicitRootCheck()`, if enabled, will use more aggressive checks to determine if any of the required targets is installed, including explicitly trying to acquire root access.


```kotlin
private val rooting = KevlarRooting {
targets {
root()
magisk()
busybox()
xposed()
}

allowExplicitRootCheck()

status {
testKeys()
emulator()
selinux {
flagPermissive()
}
}
}
```kotlin title="Automatic settings"
private val antipiracy = KevlarRooting.Defaults.Standard()
```

You can find more information for configuring the rooting module configuration in the [reference](reference.md).

## In-Place
This is the most concise way to implement rooting.
Expand Down
3 changes: 1 addition & 2 deletions docs/pages/modules/rooting/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ Xposed framework requires a specific check on a system file to determine whether

## Notes
The `rooting` module relies on [libsu](https://github.com/topjohnwu/libsu) for shell command execution.
This may be a debatable choice, but it is one of the only well-written libraries and it works both efficiently and reliably.
The alternative would have been implementing a custom shell execution mechanism, which is not hard to do, but it is hard to do *well*.
This may be a debatable choice, but it is one of the only well-written libraries and it works both efficiently and reliably.
Loading

0 comments on commit 893012a

Please sign in to comment.