Skip to content

Commit

Permalink
feat: Finish first patcher test
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jun 5, 2022
1 parent a1e909b commit 0d8d19e
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 45 deletions.
Expand Up @@ -55,4 +55,4 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
return MutableMethod(this)
}
}
}
}
132 changes: 88 additions & 44 deletions src/test/kotlin/app/revanced/patcher/PatcherTest.kt
@@ -1,16 +1,21 @@
package app.revanced.patcher

import app.revanced.patcher.cache.Cache
import app.revanced.patcher.extensions.addInstructions
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.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.signature.MethodSignature
import app.revanced.patcher.smali.asInstructions
import com.google.common.collect.ImmutableList
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
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.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.junit.jupiter.api.Test
import java.io.File
Expand All @@ -35,64 +40,103 @@ internal class PatcherTest {
@Test
fun testPatcher() {
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/"),
File(PatcherTest::class.java.getResource("/test1.dex")!!.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"]
val result = cache.methodMap["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.
// The instruction format can be found in 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! Adding bytecode.".
// The format is 21c, so we create a new BuilderInstruction21c
// This instruction will hold the string reference constant in the virtual register 1.
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
// At last, use the method replaceInstruction to replace it at the given index startIndex.
implementation.replaceInstruction(
startIndex,
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!
// Get the class in which the method matching our signature is defined in.
val mainClass = cache.findClass {
it.type == result.definingClassProxy.immutableClass.type
}!!.resolve()

// 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"
)
// Add a new method returning a string
mainClass.methods.add(
ImmutableMethod(
result.definingClassProxy.immutableClass.type,
"returnHello",
null,
"Ljava/lang/String;",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
ImmutableMethodImplementation(
1,
ImmutableList.of(
BuilderInstruction21c(
Opcode.CONST_STRING,
0,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
),
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
),
null,
null
)
).toMutable()
)

// Insert our instructions after the second instruction by our pattern.
implementation.addInstruction(startIndex + 1, stringInstruction2)
implementation.addInstruction(startIndex + 3, invokeInstruction)
// Now lets create a new call to our method and print the return value!
// You can also use the smali compiler to create instructions.
// For this sake of example I reuse the class field System.out inside the virtual register 0.
// Instead an additional instruction could be added at first to re-set this register.
// "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;"
//
// Control flow instructions are not supported as of now.
val instructions = """
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
move-result-object v1
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
""".trimIndent().asInstructions()
implementation.addInstructions(startIndex + 2, instructions)

// TODO: check TODO of the MutableEncodedValue class
//mainClass.fields.add(
// ImmutableField(
// mainClass.type,
// "dummyField",
// "Ljava/io/PrintStream",
// AccessFlags.PRIVATE or AccessFlags.STATIC,
// ImmutableFieldEncodedValue(
// ImmutableFieldReference(
// "Ljava/lang/System;",
// "out",
// "Ljava/io/PrintStream;"
// )
// ),
// null
// ).toMutable()
//)

// Finally, tell the patcher that this patch was a success.
// You can also return PatchResultError with a message.
Expand All @@ -118,11 +162,11 @@ internal class PatcherTest {
@Test
fun `test patcher with no changes`() {
Patcher(
File(PatcherTest::class.java.getResource("/test1.apk")!!.toURI()),
File("/no-changes-test"),
File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()),
File("."),
testSignatures
).save()
// FIXME(Sculas): There seems to be a 1-byte difference, not sure what it is.
// assertEquals(available, out.size())
}
}
}
Binary file added src/test/resources/test1.dex
Binary file not shown.
Binary file removed src/test/resources/test1.jar
Binary file not shown.
Binary file added src/test/resources/test2.dex
Binary file not shown.
Binary file removed src/test/resources/test2.jar
Binary file not shown.

0 comments on commit 0d8d19e

Please sign in to comment.