From bfe4e3e298ac963936ca9621e12aefbe56260826 Mon Sep 17 00:00:00 2001 From: Lucaskyy Date: Sun, 3 Apr 2022 23:51:01 +0200 Subject: [PATCH] feat: add inline smali compiler --- build.gradle.kts | 7 +- .../patcher/smali/InlineSmaliCompiler.kt | 57 ++++ .../patcher/smali/InstructionConverter.kt | 261 ++++++++++++++++++ src/test/kotlin/patcher/PatcherTest.kt | 58 ++-- 4 files changed, 361 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt create mode 100644 src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6d8e562e..0ba6df4a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,9 +12,10 @@ repositories { dependencies { implementation(kotlin("stdlib")) - implementation("com.github.lanchon.dexpatcher:multidexlib2:2.3.4") - implementation("io.github.microutils:kotlin-logging:2.1.21") - testImplementation("ch.qos.logback:logback-classic:1.2.11") // use your own logger! + + implementation("com.github.lanchon.dexpatcher:multidexlib2:2.3.4.r2") + implementation("org.smali:smali:2.3.4") + testImplementation(kotlin("test")) } diff --git a/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt b/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt new file mode 100644 index 00000000..3496a9e8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/smali/InlineSmaliCompiler.kt @@ -0,0 +1,57 @@ +package app.revanced.patcher.smali + +import org.antlr.runtime.CommonTokenStream +import org.antlr.runtime.TokenSource +import org.antlr.runtime.tree.CommonTreeNodeStream +import org.jf.dexlib2.Opcodes +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.writer.builder.DexBuilder +import org.jf.smali.LexerErrorInterface +import org.jf.smali.smaliFlexLexer +import org.jf.smali.smaliParser +import org.jf.smali.smaliTreeWalker +import java.io.InputStreamReader + +private const val METHOD_TEMPLATE = """ +.class public Linlinecompiler; +.super Ljava/lang/Object; +.method public static compiler()V + .registers 1 + %s +.end method +""" + +class InlineSmaliCompiler { + companion object { + /** + * Compiles a string of Smali code to a list of instructions. + * Do not cross the boundaries of the control flow (if-nez insn, etc), + * as that will result in exceptions since the labels cannot be calculated. + * Do not create dummy labels to fix the issue, since the code addresses will + * be messed up and results in broken Dalvik bytecode. + */ + fun compileMethodInstructions(instructions: String): List { + val input = METHOD_TEMPLATE.format(instructions) + val reader = InputStreamReader(input.byteInputStream()) + val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15) + val tokens = CommonTokenStream(lexer as TokenSource) + val parser = smaliParser(tokens) + val result = parser.smali_file() + if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) { + throw IllegalStateException( + "Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!" + ) + } + val treeStream = CommonTreeNodeStream(result.tree) + treeStream.tokenStream = tokens + val dexGen = smaliTreeWalker(treeStream) + dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault())) + val classDef = dexGen.smali_file() + return classDef.methods.first().implementation!!.instructions.map { it.toBuilderInstruction() } + } + } +} + +fun String.asInstructions() = InlineSmaliCompiler.compileMethodInstructions(this) +fun String.asInstruction() = this.asInstructions().first() diff --git a/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt b/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt new file mode 100644 index 00000000..57363d74 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/smali/InstructionConverter.kt @@ -0,0 +1,261 @@ +package app.revanced.patcher.smali + +import org.jf.dexlib2.Format +import org.jf.dexlib2.builder.instruction.* +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.formats.* +import org.jf.util.ExceptionWithContext + +fun Instruction.toBuilderInstruction() = + when (this.opcode.format) { + Format.Format10x -> InstructionConverter.newBuilderInstruction10x(this as Instruction10x) + Format.Format11n -> InstructionConverter.newBuilderInstruction11n(this as Instruction11n) + Format.Format11x -> InstructionConverter.newBuilderInstruction11x(this as Instruction11x) + Format.Format12x -> InstructionConverter.newBuilderInstruction12x(this as Instruction12x) + Format.Format20bc -> InstructionConverter.newBuilderInstruction20bc(this as Instruction20bc) + Format.Format21c -> InstructionConverter.newBuilderInstruction21c(this as Instruction21c) + Format.Format21ih -> InstructionConverter.newBuilderInstruction21ih(this as Instruction21ih) + Format.Format21lh -> InstructionConverter.newBuilderInstruction21lh(this as Instruction21lh) + Format.Format21s -> InstructionConverter.newBuilderInstruction21s(this as Instruction21s) + Format.Format22b -> InstructionConverter.newBuilderInstruction22b(this as Instruction22b) + Format.Format22c -> InstructionConverter.newBuilderInstruction22c(this as Instruction22c) + Format.Format22cs -> InstructionConverter.newBuilderInstruction22cs(this as Instruction22cs) + Format.Format22s -> InstructionConverter.newBuilderInstruction22s(this as Instruction22s) + Format.Format22x -> InstructionConverter.newBuilderInstruction22x(this as Instruction22x) + Format.Format23x -> InstructionConverter.newBuilderInstruction23x(this as Instruction23x) + Format.Format31c -> InstructionConverter.newBuilderInstruction31c(this as Instruction31c) + Format.Format31i -> InstructionConverter.newBuilderInstruction31i(this as Instruction31i) + Format.Format32x -> InstructionConverter.newBuilderInstruction32x(this as Instruction32x) + Format.Format35c -> InstructionConverter.newBuilderInstruction35c(this as Instruction35c) + Format.Format35mi -> InstructionConverter.newBuilderInstruction35mi(this as Instruction35mi) + Format.Format35ms -> InstructionConverter.newBuilderInstruction35ms(this as Instruction35ms) + Format.Format3rc -> InstructionConverter.newBuilderInstruction3rc(this as Instruction3rc) + Format.Format3rmi -> InstructionConverter.newBuilderInstruction3rmi(this as Instruction3rmi) + Format.Format3rms -> InstructionConverter.newBuilderInstruction3rms(this as Instruction3rms) + Format.Format51l -> InstructionConverter.newBuilderInstruction51l(this as Instruction51l) + else -> throw ExceptionWithContext("Instruction format %s not supported", this.opcode.format) + } + +internal class InstructionConverter { + companion object { + internal fun newBuilderInstruction10x(instruction: Instruction10x): BuilderInstruction10x { + return BuilderInstruction10x( + instruction.opcode + ) + } + + internal fun newBuilderInstruction11n(instruction: Instruction11n): BuilderInstruction11n { + return BuilderInstruction11n( + instruction.opcode, + instruction.registerA, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction11x(instruction: Instruction11x): BuilderInstruction11x { + return BuilderInstruction11x( + instruction.opcode, + instruction.registerA + ) + } + + internal fun newBuilderInstruction12x(instruction: Instruction12x): BuilderInstruction12x { + return BuilderInstruction12x( + instruction.opcode, + instruction.registerA, + instruction.registerB + ) + } + + internal fun newBuilderInstruction20bc(instruction: Instruction20bc): BuilderInstruction20bc { + return BuilderInstruction20bc( + instruction.opcode, + instruction.verificationError, + instruction.reference + ) + } + + internal fun newBuilderInstruction21c(instruction: Instruction21c): BuilderInstruction21c { + return BuilderInstruction21c( + instruction.opcode, + instruction.registerA, + instruction.reference + ) + } + + internal fun newBuilderInstruction21ih(instruction: Instruction21ih): BuilderInstruction21ih { + return BuilderInstruction21ih( + instruction.opcode, + instruction.registerA, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction21lh(instruction: Instruction21lh): BuilderInstruction21lh { + return BuilderInstruction21lh( + instruction.opcode, + instruction.registerA, + instruction.wideLiteral + ) + } + + internal fun newBuilderInstruction21s(instruction: Instruction21s): BuilderInstruction21s { + return BuilderInstruction21s( + instruction.opcode, + instruction.registerA, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction22b(instruction: Instruction22b): BuilderInstruction22b { + return BuilderInstruction22b( + instruction.opcode, + instruction.registerA, + instruction.registerB, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction22c(instruction: Instruction22c): BuilderInstruction22c { + return BuilderInstruction22c( + instruction.opcode, + instruction.registerA, + instruction.registerB, + instruction.reference + ) + } + + internal fun newBuilderInstruction22cs(instruction: Instruction22cs): BuilderInstruction22cs { + return BuilderInstruction22cs( + instruction.opcode, + instruction.registerA, + instruction.registerB, + instruction.fieldOffset + ) + } + + internal fun newBuilderInstruction22s(instruction: Instruction22s): BuilderInstruction22s { + return BuilderInstruction22s( + instruction.opcode, + instruction.registerA, + instruction.registerB, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction22x(instruction: Instruction22x): BuilderInstruction22x { + return BuilderInstruction22x( + instruction.opcode, + instruction.registerA, + instruction.registerB + ) + } + + internal fun newBuilderInstruction23x(instruction: Instruction23x): BuilderInstruction23x { + return BuilderInstruction23x( + instruction.opcode, + instruction.registerA, + instruction.registerB, + instruction.registerC + ) + } + + internal fun newBuilderInstruction31c(instruction: Instruction31c): BuilderInstruction31c { + return BuilderInstruction31c( + instruction.opcode, + instruction.registerA, + instruction.reference + ) + } + + internal fun newBuilderInstruction31i(instruction: Instruction31i): BuilderInstruction31i { + return BuilderInstruction31i( + instruction.opcode, + instruction.registerA, + instruction.narrowLiteral + ) + } + + internal fun newBuilderInstruction32x(instruction: Instruction32x): BuilderInstruction32x { + return BuilderInstruction32x( + instruction.opcode, + instruction.registerA, + instruction.registerB + ) + } + + internal fun newBuilderInstruction35c(instruction: Instruction35c): BuilderInstruction35c { + return BuilderInstruction35c( + instruction.opcode, + instruction.registerCount, + instruction.registerC, + instruction.registerD, + instruction.registerE, + instruction.registerF, + instruction.registerG, + instruction.reference + ) + } + + internal fun newBuilderInstruction35mi(instruction: Instruction35mi): BuilderInstruction35mi { + return BuilderInstruction35mi( + instruction.opcode, + instruction.registerCount, + instruction.registerC, + instruction.registerD, + instruction.registerE, + instruction.registerF, + instruction.registerG, + instruction.inlineIndex + ) + } + + internal fun newBuilderInstruction35ms(instruction: Instruction35ms): BuilderInstruction35ms { + return BuilderInstruction35ms( + instruction.opcode, + instruction.registerCount, + instruction.registerC, + instruction.registerD, + instruction.registerE, + instruction.registerF, + instruction.registerG, + instruction.vtableIndex + ) + } + + internal fun newBuilderInstruction3rc(instruction: Instruction3rc): BuilderInstruction3rc { + return BuilderInstruction3rc( + instruction.opcode, + instruction.startRegister, + instruction.registerCount, + instruction.reference + ) + } + + internal fun newBuilderInstruction3rmi(instruction: Instruction3rmi): BuilderInstruction3rmi { + return BuilderInstruction3rmi( + instruction.opcode, + instruction.startRegister, + instruction.registerCount, + instruction.inlineIndex + ) + } + + internal fun newBuilderInstruction3rms(instruction: Instruction3rms): BuilderInstruction3rms { + return BuilderInstruction3rms( + instruction.opcode, + instruction.startRegister, + instruction.registerCount, + instruction.vtableIndex + ) + } + + internal fun newBuilderInstruction51l(instruction: Instruction51l): BuilderInstruction51l { + return BuilderInstruction51l( + instruction.opcode, + instruction.registerA, + instruction.wideLiteral + ) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/patcher/PatcherTest.kt b/src/test/kotlin/patcher/PatcherTest.kt index 2846b8c2..1b49beb3 100644 --- a/src/test/kotlin/patcher/PatcherTest.kt +++ b/src/test/kotlin/patcher/PatcherTest.kt @@ -8,11 +8,15 @@ 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.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( @@ -43,33 +47,49 @@ fun main() { ?: return PatchResultError("Class 'XAdRemover' could not be found") val xAdRemoverClass = proxy.resolve() - val hideReelMethod = xAdRemoverClass.methods.single { method -> method.name.contains("HideReel") } + val hideReelMethod = xAdRemoverClass.methods.find { + it.name.contains("HideReel") + }!! - val readSettingsMethodRef = ImmutableMethodReference( - "Lfi/razerman/youtube/XGlobals;", - "ReadSettings", - emptyList(), - "V" - ) - - val instructions = hideReelMethod.implementation!!.instructions + val instructions = hideReelMethod.implementation!! - val readSettingsInstruction = BuilderInstruction35c( + val readInsn = + "invoke-static { }, Lfi/razerman/youtube/XGlobals;->ReadSettings()V" + .asInstruction() as BuilderInstruction35c + val testInsn = BuilderInstruction35c( Opcode.INVOKE_STATIC, - 0, - 0, - 0, - 0, - 0, - 0, - readSettingsMethodRef + 0, 0, 0, 0, 0, 0, + ImmutableMethodReference( + "Lfi/razerman/youtube/XGlobals;", + "ReadSettings", + emptyList(), + "V" + ) ) + assertEquals(testInsn.opcode, readInsn.opcode) + assertEquals(testInsn.referenceType, readInsn.referenceType) + assertEquals(testInsn.registerCount, readInsn.registerCount) + assertEquals(testInsn.registerC, readInsn.registerC) + assertEquals(testInsn.registerD, readInsn.registerD) + assertEquals(testInsn.registerE, readInsn.registerE) + assertEquals(testInsn.registerF, readInsn.registerF) + assertEquals(testInsn.registerG, readInsn.registerG) + run { + val tref = testInsn.reference as MethodReference + val rref = readInsn.reference as MethodReference + + assertEquals(tref.name, rref.name) + assertEquals(tref.definingClass, rref.definingClass) + assertEquals(tref.returnType, rref.returnType) + assertContentEquals(tref.parameterTypes, rref.parameterTypes) + } + // TODO: figure out control flow // otherwise the we would still jump over to the original instruction at index 21 instead to our new one - instructions.add( + instructions.addInstruction( 21, - readSettingsInstruction + readInsn ) return PatchResultSuccess() }