Skip to content

Commit

Permalink
refactor fingerprint apis
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jun 17, 2024
1 parent e36e959 commit eb4db5a
Show file tree
Hide file tree
Showing 17 changed files with 823 additions and 812 deletions.
51 changes: 49 additions & 2 deletions api/revanced-patcher.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
public final class app/revanced/patcher/Fingerprint {
public final fun getMatch ()Lapp/revanced/patcher/Match;
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Z
}

public final class app/revanced/patcher/FingerprintBuilder {
public fun <init> ()V
public final fun accessFlags (I)V
public final fun accessFlags ([Lcom/android/tools/smali/dexlib2/AccessFlags;)V
public final fun custom (Lkotlin/jvm/functions/Function2;)V
public final fun opcodes (Ljava/lang/String;)V
public final fun opcodes ([Lcom/android/tools/smali/dexlib2/Opcode;)V
public final fun parameters ([Ljava/lang/String;)V
public final fun returns (Ljava/lang/String;)V
public final fun strings ([Ljava/lang/String;)V
}

public final class app/revanced/patcher/FingerprintKt {
public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
public static final fun fingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
public static synthetic fun fingerprint$default (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
}

public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
}

public final class app/revanced/patcher/Match {
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/Match$PatternMatch;Ljava/util/List;Lapp/revanced/patcher/patch/BytecodePatchContext;)V
public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
public final fun getStringMatches ()Ljava/util/List;
}

public final class app/revanced/patcher/Match$PatternMatch {
public fun <init> (II)V
public final fun getEndIndex ()I
public final fun getStartIndex ()I
}

public final class app/revanced/patcher/Match$StringMatch {
public fun <init> (Ljava/lang/String;I)V
public final fun getIndex ()I
public final fun getString ()Ljava/lang/String;
}

public final class app/revanced/patcher/PackageMetadata {
public final fun getPackageName ()Ljava/lang/String;
public final fun getPackageVersion ()Ljava/lang/String;
Expand Down Expand Up @@ -168,8 +215,8 @@ public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patch

public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
public final fun getValue (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public final fun invoke (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/MethodFingerprint;
public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
public final fun invoke (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/Fingerprint;
}

public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext {
Expand Down
11 changes: 7 additions & 4 deletions docs/1_patcher_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ In order to create patches for Android applications, you first need to understan

## 馃摍 How it works

ReVanced Patcher is a library that allows you to modify Android applications by applying patches to their APKs. It is built on top of [Smali](https://github.com/google/smali) for bytecode manipulation and [Androlib (Apktool)](https://github.com/iBotPeaches/Apktool) for resource decoding and encoding.
ReVanced Patcher accepts a list of patches and integrations, and applies them to a given APK file. It then returns the modified components of the APK file, such as modified dex files and resources, that can be repackaged into a new APK file.
ReVanced Patcher is a library that allows you to modify Android applications by applying patches to their APKs.
It is built on top of [Smali](https://github.com/google/smali) for bytecode manipulation and [Androlib (Apktool)](https://github.com/iBotPeaches/Apktool) for resource decoding and encoding.
ReVanced Patcher accepts a list of patches and integrations, and applies them to a given APK file.
It then returns the modified components of the APK file, such as modified dex files and resources,
that can be repackaged into a new APK file.

ReVanced Patcher has a simple API that allows you to load patches and integrations from JAR files and apply them to an APK file.
Later on, you will learn how to create patches.
ReVanced Patcher has a simple API that allows you to load patches and integrations from JAR files
and apply them to an APK file. Later on, you will learn how to create patches.

```kt
val patches = loadPatchesFromJar(setOf(File("revanced-patches.jar")))
Expand Down
3 changes: 2 additions & 1 deletion docs/2_1_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ Throughout the documentation, [ReVanced Patches](https://github.com/revanced/rev
3. Open the project in your IDE

> [!TIP]
> It is a good idea to set up a complete development environment for ReVanced, so that you can also test your patches by following the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
> It is a good idea to set up a complete development environment for ReVanced, so that you can also test your patches
> by following the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 鈴笍 What's next

Expand Down
122 changes: 57 additions & 65 deletions docs/2_2_1_fingerprinting.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@

# 馃攷 Fingerprinting

In the context of ReVanced, fingerprinting is primarily used to resolve methods with a limited amount of known information.
In the context of ReVanced, fingerprinting is primarily used to match methods with a limited amount of known information.
Methods with obfuscated names that change with each update are primary candidates for fingerprinting.
The goal of fingerprinting is to uniquely identify a method by capturing various attributes, such as the return type, access flags, an opcode pattern, strings, and more.
The goal of fingerprinting is to uniquely identify a method by capturing various attributes, such as the return type,
access flags, an opcode pattern, strings, and more.

## 鉀筹笍 Example fingerprint

Expand All @@ -72,13 +73,13 @@ Throughout the documentation, the following example will be used to demonstrate

package app.revanced.patches.ads.fingerprints

methodFingerprint {
fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z")
parameters("Z")
opcodes(Opcode.RETURN)
strings("pro")
custom { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
custom { (method, classDef) -> method.definingClass == "Lcom/some/app/ads/AdsLoader;" }
}
```

Expand Down Expand Up @@ -106,7 +107,7 @@ The fingerprint contains the following information:
- Package and class name:

```kt
custom = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
custom = { (method, classDef) -> method.definingClass == "Lcom/some/app/ads/AdsLoader;"}
```

With this information, the original code can be reconstructed:
Expand All @@ -129,20 +130,22 @@ With this information, the original code can be reconstructed:

> [!TIP]
> A fingerprint should contain information about a method likely to remain the same across updates.
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app. In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app.
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
## 馃敤 How to use fingerprints

Fingerprints can be added to a patch by directly creating and adding them or by invoking them manually. Fingerprints added to a patch are resolved by ReVanced Patcher before the patch is executed.
Fingerprints can be added to a patch by directly creating and adding them or by invoking them manually.
Fingerprints added to a patch are matched by ReVanced Patcher before the patch is executed.

```kt
val fingerprint = methodFingerprint {
val fingerprint = fingerprint {
// ...
}

val patch = bytecodePatch {
// Directly create and add a fingerprint.
methodFingerprint {
fingerprint {
// ...
}

Expand All @@ -152,15 +155,15 @@ val patch = bytecodePatch {
```

> [!TIP]
> Multiple patches can share fingerprints. If a fingerprint is resolved once, it will not be resolved again.
> Multiple patches can share fingerprints. If a fingerprint is matched once, it will not be matched again.
> [!TIP]
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode` function to fuzzy match the pattern.
> Opcode pattern arrays can contain `null` values to indicate that the opcode at the index is unknown.
> Any opcode will match to a `null` value:
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
> function to fuzzy match the pattern.
> `null` can be used as a wildcard to match any opcode:
>
> ```kt
> methodFingerprint(fuzzyPatternScanThreshhold = 0.5) {
> fingerprint(fuzzyPatternScanThreshhold = 2) {
> opcodes(
> Opcode.ICONST_0,
> null,
Expand All @@ -170,111 +173,100 @@ val patch = bytecodePatch {
>}
> ```
Once the fingerprint is resolved, the result can be used in the patch:
Once the fingerprint is matched, the match can be used in the patch:

```kt
val patch = bytecodePatch {
// Add a fingerprint and delegate its result to a variable.
val result by showAdsFingerprint()
// Add a fingerprint and delegate its match to a variable.
val match by showAdsFingerprint()

execute {
val method = result.method
val method = match.method
}
}
```

> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`. If the result is delegated to a variable, accessing it will raise an exception.
> If the fingerprint can not be matched to any method, the match of a fingerprint is `null`. If the match is delegated
> to a variable, accessing it will raise an exception.
The result of a fingerprint that resolved successfully contains mutable and immutable references to the method and the class it is defined in.
The match of a fingerprint contains mutable and immutable references to the method and the class it is defined in.

```kt
class MethodFingerprintResult(
class Match(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
val patternMatch: Match.PatternMatch?,
val stringMatches: List<Match.StringMatch>?,
// ...
) {
val mutableClass by lazy { /* ... */ }
val mutableMethod by lazy { /* ... */ }

// ...
}

class MethodFingerprintScanResult(
val patternScanResult: PatternScanResult?,
val stringsScanResult: StringsScanResult?,
) {
class StringsScanResult(val matches: List<StringMatch>) {
class StringMatch(val string: String, val index: Int)
}

class PatternScanResult(
val startIndex: Int,
val endIndex: Int,
// ...
) {
// ...
}
}
```

## 馃徆 Manual resolution of fingerprints
## 馃徆 Manual matching of fingerprints

Unless a fingerprint is added to a patch, the fingerprint will not be resolved automatically by ReVanced Patcher before the patch is executed.
Instead, the fingerprint can be resolved manually using various overloads of a fingerprint's `resolve` function.
Unless a fingerprint is added to a patch, the fingerprint will not be matched automatically by ReVanced Patcher
before the patch is executed.
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function.

You can resolve a fingerprint in the following ways:
You can match a fingerprint the following ways:

- On a **list of classes**, if the fingerprint can resolve on a known subset of classes
- In a **list of classes**, if the fingerprint can match in a known subset of classes

If you have a known list of classes you know the fingerprint can resolve on, you can resolve the fingerprint on the list of classes:
If you have a known list of classes you know the fingerprint can match in,
you can match the fingerprint on the list of classes:

```kt
execute {
val result = showAdsFingerprint.also {
it.resolve(context, this.classes)
}.result ?: throw PatchException("showAdsFingerprint not found")
execute { context ->
val match = showAdsFingerprint.apply {
match(context, context.classes)
}.match ?: throw PatchException("showAdsFingerprint not found")
}
```

- On a **single class**, if the fingerprint can resolve on a single known class
- In a **single class**, if the fingerprint can match in a single known class

If you know the fingerprint can resolve to a method in a specific class, you can resolve the fingerprint on the class:
If you know the fingerprint can match a method in a specific class, you can match the fingerprint in the class:

```kt
execute {
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
execute { context ->
val adsLoaderClass = context.classes.single { it.name == "Lcom/some/app/ads/Loader;" }

val result = showAdsFingerprint.also {
it.resolve(context, adsLoaderClass)
}.result ?: throw PatchException("showAdsFingerprint not found")
val match = showAdsFingerprint.apply {
match(context, adsLoaderClass)
}.match ?: throw PatchException("showAdsFingerprint not found")
}
```

- On a **single method**, to extract certain information about a method
- Match a **single method**, to extract certain information about it

The result of a fingerprint contains useful information about the method, such as the start and end index of an opcode pattern or the indices of the instructions with certain string references.
The match of a fingerprint contains useful information about the method, such as the start and end index of an opcode pattern
or the indices of the instructions with certain string references.
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:

```kt
execute {
val proStringsFingerprint = methodFingerprint {
execute { context ->
val proStringsFingerprint = fingerprint {
strings("free", "trial")
}

proStringsFingerprint.also {
it.resolve(context, adsFingerprintResult.method)
}.result?.let { result ->
result.scanResult.stringsScanResult!!.matches.forEach { match ->
proStringsFingerprint.apply {
match(context, adsFingerprintMatch.method)
}.match?.let { match ->
match.stringMatches.forEach { match ->
println("The index of the string '${match.string}' is ${match.index}")
}
} ?: throw PatchException("pro strings fingerprint not found")
}
```

> [!TIP]
> To see real-world examples of fingerprints, check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
> To see real-world examples of fingerprints,
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
## 鈴笍 What's next

Expand Down
Loading

0 comments on commit eb4db5a

Please sign in to comment.