revanced-patcher/docs/2_2_1_fingerprinting.md

276 lines
9.9 KiB
Markdown
Raw Normal View History

2024-02-25 03:30:08 +01:00
<p align="center">
<picture>
<source
width="256px"
media="(prefers-color-scheme: dark)"
srcset="../assets/revanced-headline/revanced-headline-vertical-dark.svg"
>
<img
width="256px"
src="../assets/revanced-headline/revanced-headline-vertical-light.svg"
>
</picture>
<br>
<a href="https://revanced.app/">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="../assets/revanced-logo/revanced-logo.svg" />
<img height="24px" src="../assets/revanced-logo/revanced-logo.svg" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://github.com/ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="http://revanced.app/discord">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://reddit.com/r/revancedapp">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://t.me/app_revanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://x.com/revancedapp">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
</picture>
</a>&nbsp;&nbsp;&nbsp;
<a href="https://www.youtube.com/@ReVanced">
<picture>
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
</picture>
</a>
<br>
<br>
Continuing the legacy of Vanced
</p>
# 🔎 Fingerprinting
In the context of ReVanced, fingerprinting is primarily used to resolve 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.
## ⛳️ Example fingerprint
Throughout the documentation, the following example will be used to demonstrate the concepts of fingerprints:
```kt
package app.revanced.patches.ads.fingerprints
object ShowAdsFingerprint : MethodFingerprint(
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(Opcode.RETURN),
strings = listOf("pro"),
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
)
```
## 🔎 Reconstructing the original code from a fingerprint
The following code is reconstructed from the fingerprint to understand how a fingerprint is created.
The fingerprint contains the following information:
- Method signature:
```kt
returnType = "Z",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
```
- Method implementation:
```kt
opcodes = listOf(Opcode.RETURN)
strings = listOf("pro"),
```
- Package and class name:
```kt
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
```
With this information, the original code can be reconstructed:
```java
package com.some.app.ads;
<accessFlags> class AdsLoader {
public final boolean <methodName>(boolean <parameter>) {
// ...
var userStatus = "pro";
// ...
return <returnValue>;
}
}
```
> [!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 is likely to 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
After creating a fingerprint, add it to the constructor of a `BytecodePatch`:
```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
// ...
}
```
> [!NOTE]
> Fingerprints passed to the constructor of `BytecodePatch` are resolved by ReVanced Patcher before the patch is executed.
> [!TIP]
> Multiple patches can share fingerprints. If a fingerprint is resolved once, it will not be resolved again.
> [!TIP]
> If a fingerprint has an opcode pattern, you can use the `FuzzyPatternScanMethod` annotation 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.
> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`.
Once the fingerprint is resolved, the result can be used in the patch:
```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
// ...
}
}
```
The result of a fingerprint that resolved successfully contains mutable and immutable references to the method and the class it is defined in.
```kt
class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
// ...
) {
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
Unless a fingerprint is added to the constructor of `BytecodePatch`, 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 the `resolve` function of a fingerprint.
You can resolve a fingerprint in the following ways:
- On a **list of classes**, if the fingerprint can resolve on 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:
```kt
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.also { it.resolve(context, context.classes) }.result
?: throw PatchException("ShowAdsFingerprint not found")
// ...
}
```
- On a **single class**, if the fingerprint can resolve on 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:
```kt
override fun execute(context: BytecodeContext) {
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")
// ...
}
```
- On a **single method**, to extract certain information about a method
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.
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
```kt
override fun execute(context: BytecodeContext) {
val adsFingerprintResult = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
val proStringsFingerprint = object : MethodFingerprint(
strings = listOf("free", "trial")
) {}
proStringsFingerprint.also {
it.resolve(context, adsFingerprintResult.method)
}.result?.let { result ->
result.scanResult.stringsScanResult!!.matches.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 [ReVanced Patches](https://github.com/revanced/revanced-patches) repository.
## ⏭️ What's next
The next page discusses the structure and conventions of patches.
Continue: [📜 Project structure and conventions](3_structure_and_conventions.md)