feat: Add first tests

This commit is contained in:
oSumAtrIX 2022-04-06 02:15:40 +02:00
parent 4543b36616
commit 6767c8fbc1
No known key found for this signature in database
GPG key ID: A9B3094ACDB604B4
5 changed files with 139 additions and 126 deletions

View file

@ -31,8 +31,8 @@ internal class SignatureResolver(
val classProxy = ClassProxy(classDef, index) val classProxy = ClassProxy(classDef, index)
methodMap[signature.name] = SignatureResolverResult( methodMap[signature.name] = SignatureResolverResult(
classProxy, classProxy,
patternScanData,
method.name, method.name,
patternScanData
) )
} }
} }
@ -53,8 +53,8 @@ internal class SignatureResolver(
val result = compareSignatureToMethod(signature, method) ?: continue val result = compareSignatureToMethod(signature, method) ?: continue
return SignatureResolverResult( return SignatureResolverResult(
classProxy, classProxy,
result,
method.name, method.name,
result
) )
} }
return null return null

View file

@ -1,6 +1,7 @@
package app.revanced.patcher.signature package app.revanced.patcher.signature
import app.revanced.patcher.proxy.ClassProxy import app.revanced.patcher.proxy.ClassProxy
import app.revanced.patcher.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.resolver.SignatureResolver import app.revanced.patcher.resolver.SignatureResolver
/** /**
@ -11,9 +12,14 @@ import app.revanced.patcher.resolver.SignatureResolver
*/ */
data class SignatureResolverResult( data class SignatureResolverResult(
val definingClassProxy: ClassProxy, val definingClassProxy: ClassProxy,
val resolvedMethodName: String, val scanData: PatternScanResult,
val scanData: PatternScanResult? private val resolvedMethodName: String,
) { ) {
fun resolveAndGetMethod(): MutableMethod {
return definingClassProxy.resolve().methods.single { it.name == resolvedMethodName }
}
@Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method. @Suppress("Unused") // TODO(Sculas): remove this when we have coverage for this method.
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? { fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
return SignatureResolver.resolveFromProxy(definingClassProxy, signature) return SignatureResolver.resolveFromProxy(definingClassProxy, signature)

View file

@ -0,0 +1,128 @@
package app.revanced.patcher
import app.revanced.patcher.cache.Cache
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.signature.MethodSignature
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.junit.jupiter.api.Test
import java.io.File
internal class PatcherTest {
companion object {
val testSignatures: Array<MethodSignature> = arrayOf(
MethodSignature(
"main-method",
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
setOf("[L"),
arrayOf(
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
)
)
)
}
@Test
fun testPatcher() {
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/"),
testSignatures
)
patcher.addPatches(
object : Patch("TestPatch") {
override fun execute(cache: Cache): PatchResult {
// Get the result from the resolver cache
val result = cache.resolvedMethods["main-method"]
// Get the implementation for the resolved method
val implementation = result.resolveAndGetMethod().implementation!!
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
// Get the start index of our opcode pattern.
// This will be the index of the instruction with the opcode CONST_STRING.
val startIndex = result.scanData.startIndex
// the instruction format can be found via the docs at https://source.android.com/devices/tech/dalvik/dalvik-bytecode
// in our case we want an instruction with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
// the format is 21c, so we create a new BuilderInstruction21c
// with the opcode CONST_STRING and the string "Hello, ReVanced! Editing bytecode."
// This instruction will store the constant string reference in the register v1
// For that a reference to the string is needed. It can be created by creating a ImmutableStringReference
val stringInstruction1 = BuilderInstruction21c(
Opcode.CONST_STRING,
1,
ImmutableStringReference("Hello, ReVanced! Editing bytecode.")
)
// Replace the instruction at index startIndex with a new instruction
// We make sure to use this method to handle references to it via labels in any case
// If we are sure that the instruction is not referenced by any label, we can use the index operator overload
// of the instruction list:
// implementation.instructions[startIndex] = instruction
implementation.replaceInstruction(startIndex, stringInstruction1)
// Now lets print our string twice!
// Create the necessary instructions (we could also clone the existing ones)
val stringInstruction2 = BuilderInstruction21c(
Opcode.CONST_STRING,
1,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
)
val invokeInstruction = BuilderInstruction35c(
Opcode.INVOKE_VIRTUAL,
2, 0, 1, 0, 0, 0,
ImmutableMethodReference(
"Ljava.io.PrintStream;",
"println",
setOf("Ljava/lang/String;"),
"V"
)
)
// Insert our instructions after the second instruction by our pattern.
implementation.addInstruction(startIndex + 1, stringInstruction2)
implementation.addInstruction(startIndex + 3, invokeInstruction)
// Finally, tell the patcher that this patch was a success.
// You can also return PatchResultError with a message.
// If an exception is thrown inside this function,
// a PatchResultError will be returned with the error message.
return PatchResultSuccess()
}
}
)
// Apply all patches loaded in the patcher
val patchResult = patcher.applyPatches()
// You can check if an error occurred
for ((patchName, result) in patchResult) {
if (result.isFailure) {
throw Exception("Patch $patchName failed", result.exceptionOrNull()!!)
}
}
patcher.save()
}
@Test
fun `test patcher with no changes`() {
Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/no-changes-test"),
testSignatures
).save()
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
// assertEquals(available, out.size())
}
}

View file

@ -1,121 +0,0 @@
package patcher
import app.revanced.patcher.Patcher
import app.revanced.patcher.cache.Cache
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.smali.asInstruction
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.instruction.BuilderInstruction21t
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
import java.io.File
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
fun main() {
val signatures = arrayOf(
MethodSignature(
"main-method",
"V",
AccessFlags.STATIC or AccessFlags.PUBLIC,
listOf("[O"),
arrayOf(
Opcode.SGET_OBJECT,
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
)
)
)
val patcher = Patcher(
File("black.apk"),
File("./"),
signatures
)
patcher.addPatches(
object : Patch("main-method-patch-via-proxy") {
override fun execute(cache: Cache): PatchResult {
val proxy = cache.findClass("XAdRemover")
?: return PatchResultError("Class 'XAdRemover' could not be found")
val xAdRemoverClass = proxy.resolve()
val hideReelMethod = xAdRemoverClass.methods.find {
it.name.contains("HideReel")
}!!
val implementation = hideReelMethod.implementation!!
val readSettingsInstructionCompiled =
"invoke-static { }, Lfi/razerman/youtube/XGlobals;->ReadSettings()V"
.asInstruction() as BuilderInstruction35c
val readSettingsInstructionAssembled = BuilderInstruction35c(
Opcode.INVOKE_STATIC,
0, 0, 0, 0, 0, 0,
ImmutableMethodReference(
"Lfi/razerman/youtube/XGlobals;",
"ReadSettings",
emptyList(),
"V"
)
)
assertEquals(readSettingsInstructionAssembled.opcode, readSettingsInstructionCompiled.opcode)
assertEquals(
readSettingsInstructionAssembled.referenceType,
readSettingsInstructionCompiled.referenceType
)
assertEquals(
readSettingsInstructionAssembled.registerCount,
readSettingsInstructionCompiled.registerCount
)
assertEquals(readSettingsInstructionAssembled.registerC, readSettingsInstructionCompiled.registerC)
assertEquals(readSettingsInstructionAssembled.registerD, readSettingsInstructionCompiled.registerD)
assertEquals(readSettingsInstructionAssembled.registerE, readSettingsInstructionCompiled.registerE)
assertEquals(readSettingsInstructionAssembled.registerF, readSettingsInstructionCompiled.registerF)
assertEquals(readSettingsInstructionAssembled.registerG, readSettingsInstructionCompiled.registerG)
run {
val compiledRef = readSettingsInstructionCompiled.reference as MethodReference
val assembledRef = readSettingsInstructionAssembled.reference as MethodReference
assertEquals(assembledRef.name, compiledRef.name)
assertEquals(assembledRef.definingClass, compiledRef.definingClass)
assertEquals(assembledRef.returnType, compiledRef.returnType)
assertContentEquals(assembledRef.parameterTypes, compiledRef.parameterTypes)
}
implementation.addInstruction(
21,
readSettingsInstructionCompiled
)
// fix labels
// create a new label for the instruction we want to jump to
val newLabel = implementation.newLabelForIndex(21)
// replace all instances of the old label with the new one
implementation.replaceInstruction(4, BuilderInstruction21t(Opcode.IF_NEZ, 0, newLabel))
return PatchResultSuccess()
}
},
object : Patch("main-method-patch-via-signature") {
override fun execute(cache: Cache): PatchResult {
val mainMethodMap = cache.resolvedMethods["main-method"]
mainMethodMap.definingClassProxy.immutableClass.methods.single { method ->
method.name == mainMethodMap.resolvedMethodName
}
return PatchResultSuccess()
}
}
)
patcher.applyPatches()
patcher.save()
}