feat: Add warnings for Fuzzy resolver

This commit is contained in:
Lucaskyy 2022-04-14 16:42:16 +02:00 committed by oSumAtrIX
parent 9889ec9d03
commit 715a2ad025
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
5 changed files with 121 additions and 22 deletions

View File

@ -4,6 +4,7 @@ import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchMetadata import app.revanced.patcher.patch.PatchMetadata
import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.proxy.ClassProxy import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.signature.resolver.SignatureResolver import app.revanced.patcher.signature.resolver.SignatureResolver
import app.revanced.patcher.util.ListBackedSet import app.revanced.patcher.util.ListBackedSet
import lanchon.multidexlib2.BasicDexFileNamer import lanchon.multidexlib2.BasicDexFileNamer
@ -116,22 +117,37 @@ class Patcher(
patcherData.patches.addAll(patches) patcherData.patches.addAll(patches)
} }
/**
* Resolves all signatures.
* @throws IllegalStateException if no patches were added or signatures have already been resolved.
*/
fun resolveSignatures(): List<MethodSignature> {
if (signaturesResolved) {
throw IllegalStateException("Signatures have already been resolved.")
}
val signatures = patcherData.patches.flatMap { it.signatures }
if (signatures.isEmpty()) {
throw IllegalStateException("No signatures found to resolve.")
}
SignatureResolver(patcherData.classes, signatures).resolve()
signaturesResolved = true
return signatures
}
/** /**
* Apply patches loaded into the patcher. * Apply patches loaded into the patcher.
* @param stopOnError If true, the patches will stop on the first error. * @param stopOnError If true, the patches will stop on the first error.
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied, * @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
* [PatchResultSuccess] will always be returned to the wrapping Result object. * [PatchResultSuccess] will always be returned to the wrapping Result object.
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object. * If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
* @throws IllegalStateException if signatures have not been resolved.
*/ */
fun applyPatches( fun applyPatches(
stopOnError: Boolean = false, stopOnError: Boolean = false,
callback: (String) -> Unit = {} callback: (String) -> Unit = {}
): Map<PatchMetadata, Result<PatchResultSuccess>> { ): Map<PatchMetadata, Result<PatchResultSuccess>> {
if (!signaturesResolved) { if (!signaturesResolved) {
val signatures = patcherData.patches.flatMap { it.signatures } throw IllegalStateException("Signatures not yet resolved, please invoke Patcher#resolveSignatures() first.")
SignatureResolver(patcherData.classes, signatures).resolve()
signaturesResolved = true
} }
return buildMap { return buildMap {
for (patch in patcherData.patches) { for (patch in patcherData.patches) {

View File

@ -5,14 +5,14 @@ import org.jf.dexlib2.Opcode
/** /**
* Represents the [MethodSignature] for a method. * Represents the [MethodSignature] for a method.
* @param methodSignatureMetadata Metadata for this [MethodSignature]. * @param metadata Metadata for this [MethodSignature].
* @param returnType The return type of the method. * @param returnType The return type of the method.
* @param accessFlags The access flags of the method. * @param accessFlags The access flags of the method.
* @param methodParameters The parameters of the method. * @param methodParameters The parameters of the method.
* @param opcodes The list of opcodes of the method. * @param opcodes The list of opcodes of the method.
*/ */
class MethodSignature( class MethodSignature(
val methodSignatureMetadata: MethodSignatureMetadata, val metadata: MethodSignatureMetadata,
internal val returnType: String?, internal val returnType: String?,
internal val accessFlags: Int?, internal val accessFlags: Int?,
internal val methodParameters: Iterable<String>?, internal val methodParameters: Iterable<String>?,
@ -24,9 +24,13 @@ class MethodSignature(
var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable
get() { get() {
return field ?: throw MethodNotFoundException( return field ?: throw MethodNotFoundException(
"Could not resolve required signature ${methodSignatureMetadata.name}" "Could not resolve required signature ${metadata.name}"
) )
} }
val resolved: Boolean
get() {
return result != null
}
} }
/** /**
@ -70,5 +74,29 @@ interface PatternScanMethod {
/** /**
* When comparing the signature, if [threshold] or more of the opcodes do not match, skip. * When comparing the signature, if [threshold] or more of the opcodes do not match, skip.
*/ */
class Fuzzy(internal val threshold: Int) : PatternScanMethod class Fuzzy(internal val threshold: Int) : PatternScanMethod {
/**
* A list of warnings the resolver found.
*
* This list will be allocated when the signature has been found.
* Meaning, if the signature was not found,
* or the signature was not yet resolved,
* the list will be null.
*/
lateinit var warnings: List<Warning>
/**
* Represents a resolver warning.
* @param expected The opcode the signature expected it to be.
* @param actual The actual opcode it was. Always different from [expected].
* @param expectedIndex The index for [expected]. Relative to the instruction list.
* @param actualIndex The index for [actual]. Relative to the pattern list from the signature.
*/
data class Warning(
val expected: Opcode,
val actual: Opcode,
val expectedIndex: Int,
val actualIndex: Int,
)
}
} }

View File

@ -83,24 +83,26 @@ internal class SignatureResolver(
val count = instructions.count() val count = instructions.count()
val pattern = signature.opcodes!! val pattern = signature.opcodes!!
val size = pattern.count() val size = pattern.count()
var threshold = 0 val method = signature.metadata.patternScanMethod
if (signature.methodSignatureMetadata.patternScanMethod is PatternScanMethod.Fuzzy) { val threshold = if (method is PatternScanMethod.Fuzzy)
threshold = signature.methodSignatureMetadata.patternScanMethod.threshold method.threshold else 0
}
for (instructionIndex in 0 until count) { for (instructionIndex in 0 until count) {
var patternIndex = 0 var patternIndex = 0
var currentThreshold = threshold var currentThreshold = threshold
while (instructionIndex + patternIndex < count) { while (instructionIndex + patternIndex < count) {
if ( val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
instructions.elementAt( val patternOpcode = pattern.elementAt(patternIndex)
instructionIndex + patternIndex if (originalOpcode != patternOpcode && currentThreshold-- == 0) break
).opcode != pattern.elementAt(patternIndex)
&& currentThreshold-- == 0
) break
if (++patternIndex < size) continue if (++patternIndex < size) continue
return PatternScanResult(instructionIndex, instructionIndex + patternIndex) val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
if (method is PatternScanMethod.Fuzzy) {
method.warnings = generateWarnings(
signature, instructions, result
)
}
return result
} }
} }
@ -113,6 +115,24 @@ internal class SignatureResolver(
): Boolean { ): Boolean {
return signature.count() != original.size || !(signature.all { a -> original.any { it.startsWith(a) } }) return signature.count() != original.size || !(signature.all { a -> original.any { it.startsWith(a) } })
} }
private fun generateWarnings(
signature: MethodSignature,
instructions: Iterable<Instruction>,
scanResult: PatternScanResult,
) = buildList {
val pattern = signature.opcodes!!
for ((patternIndex, originalIndex) in (scanResult.startIndex until scanResult.endIndex).withIndex()) {
val originalOpcode = instructions.elementAt(originalIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (originalOpcode != patternOpcode) {
this.add(PatternScanMethod.Fuzzy.Warning(
originalOpcode, patternOpcode,
originalIndex, patternIndex
))
}
}
}
} }
} }

View File

@ -0,0 +1,37 @@
package app.revanced.patcher
import app.revanced.patcher.signature.PatternScanMethod
import app.revanced.patcher.usage.ExamplePatch
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertTrue
internal class PatcherTest {
@Test
fun testPatcher() {
val patcher = Patcher(File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()))
patcher.addPatches(listOf(ExamplePatch()))
for (signature in patcher.resolveSignatures()) {
if (!signature.resolved) {
throw Exception("Signature ${signature.metadata.name} was not resolved!")
}
val patternScanMethod = signature.metadata.patternScanMethod
if (patternScanMethod is PatternScanMethod.Fuzzy) {
val warnings = patternScanMethod.warnings
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
for (warning in warnings) {
println(warning.toString())
}
}
}
for ((metadata, result) in patcher.applyPatches()) {
if (result.isFailure) {
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
} else {
println("Patch ${metadata.shortName} applied successfully!")
}
}
val out = patcher.save()
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
}
}

View File

@ -31,7 +31,6 @@ import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
import org.jf.dexlib2.util.Preconditions import org.jf.dexlib2.util.Preconditions
@Suppress("unused") // TODO: Add tests
class ExamplePatch : Patch( class ExamplePatch : Patch(
metadata = PatchMetadata( metadata = PatchMetadata(
shortName = "example-patch", shortName = "example-patch",
@ -48,7 +47,7 @@ class ExamplePatch : Patch(
definingClass = "TestClass", definingClass = "TestClass",
name = "main", name = "main",
), ),
patternScanMethod = PatternScanMethod.Fuzzy(2), patternScanMethod = PatternScanMethod.Fuzzy(1),
compatiblePackages = listOf("com.example.examplePackage"), compatiblePackages = listOf("com.example.examplePackage"),
description = "The main method of TestClass", description = "The main method of TestClass",
version = "1.0.0" version = "1.0.0"
@ -67,7 +66,6 @@ class ExamplePatch : Patch(
// This function will be executed by the patcher. // This function will be executed by the patcher.
// You can treat it as a constructor // You can treat it as a constructor
override fun execute(patcherData: PatcherData): PatchResult { override fun execute(patcherData: PatcherData): PatchResult {
// Get the resolved method for the signature from the resolver cache // Get the resolved method for the signature from the resolver cache
val result = signatures.first().result!! val result = signatures.first().result!!