├── version ├── settings.gradle ├── lib └── dx.jar ├── resource ├── Base.dex ├── CClass.dex ├── AClasses.dex ├── ArrayTest.dex └── TryCatchTest.dex ├── src ├── test │ ├── resources │ │ ├── Empty.dex │ │ ├── empty.dex │ │ ├── CFGTest.dex │ │ ├── classes.dex │ │ ├── SingleTest.dex │ │ ├── TryCatch.dex │ │ ├── DataFlowTest.dex │ │ ├── AnnotationTest.dex │ │ └── TryCatch.java │ └── kotlin │ │ └── com │ │ └── dedx │ │ ├── dex │ │ ├── struct │ │ │ ├── ClassInfoTest.kt │ │ │ ├── type │ │ │ │ └── TypeBoxTest.kt │ │ │ └── MethodNodeTest.kt │ │ ├── parser │ │ │ └── AnnotationsParserTest.kt │ │ └── pass │ │ │ ├── CFGBuildPassTest.kt │ │ │ └── DataFlowAnalysisPassTest.kt │ │ ├── transform │ │ ├── MethodTransformerTest.kt │ │ └── passes │ │ │ ├── RemoveNOPTest.kt │ │ │ └── EliminateCodeTest.kt │ │ ├── EmptyResource.kt │ │ └── utils │ │ └── BitArrayTest.kt └── main │ ├── kotlin │ └── com │ │ └── dedx │ │ ├── dex │ │ ├── struct │ │ │ ├── type │ │ │ │ ├── TypeInfo.kt │ │ │ │ ├── ObjectType.kt │ │ │ │ ├── SystemComment.kt │ │ │ │ ├── BasicType.kt │ │ │ │ ├── ArrayType.kt │ │ │ │ └── TypeBox.kt │ │ │ ├── Factory.kt │ │ │ ├── ConstStorage.kt │ │ │ ├── InstArgNode.kt │ │ │ ├── AccessInfo.kt │ │ │ ├── InfoStorage.kt │ │ │ ├── Annotation.kt │ │ │ ├── FieldNode.kt │ │ │ ├── FieldInfo.kt │ │ │ ├── NamedDebugInfo.kt │ │ │ ├── InstNode.kt │ │ │ ├── LocalVarNode.kt │ │ │ ├── TryCatchBlock.kt │ │ │ ├── MethodInfo.kt │ │ │ ├── ClassInfo.kt │ │ │ ├── DexNode.kt │ │ │ ├── AttrNode.kt │ │ │ ├── ClassNode.kt │ │ │ └── MethodNode.kt │ │ ├── pass │ │ │ ├── CFGBuildPass.kt │ │ │ └── DataFlowAnalysisPass.kt │ │ └── parser │ │ │ ├── AnnotationsParser.kt │ │ │ ├── ValueParser.kt │ │ │ └── DebugInfoParser.kt │ │ ├── transform │ │ ├── passes │ │ │ ├── Pass.kt │ │ │ ├── StackAndSlotMark.kt │ │ │ ├── RemoveNOP.kt │ │ │ ├── InstAnalysis.kt │ │ │ └── EliminateCode.kt │ │ ├── ReRopper.kt │ │ ├── InstStorage.kt │ │ ├── MethodDebugInfoVisitor.kt │ │ ├── BasicBlock.kt │ │ ├── ClassTransformer.kt │ │ ├── StackFrame.kt │ │ ├── SymbolInfo.kt │ │ ├── Ops.kt │ │ └── InstTransformer.kt │ │ ├── utils │ │ ├── TypeConfliction.kt │ │ ├── BlockEmptyException.kt │ │ ├── DataFlowAnalyzeException.kt │ │ ├── annotation │ │ │ └── DepAnnotation.kt │ │ ├── DecodeException.kt │ │ └── BitArray.kt │ │ └── tools │ │ ├── Configuration.kt │ │ └── Main.kt │ ├── java │ └── com │ │ └── dedx │ │ └── test │ │ └── DynamicClassLoader.java │ └── groovy │ └── com │ └── dedx │ └── test │ └── ScriptMain.groovy ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .editorconfig ├── error-case └── SwitchError.java ├── README.md ├── gradlew.bat ├── config └── detekt.yml ├── gradlew └── LICENSE /version: -------------------------------------------------------------------------------- 1 | 0.0.3 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dedx' 2 | -------------------------------------------------------------------------------- /lib/dx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/lib/dx.jar -------------------------------------------------------------------------------- /resource/Base.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/resource/Base.dex -------------------------------------------------------------------------------- /resource/CClass.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/resource/CClass.dex -------------------------------------------------------------------------------- /resource/AClasses.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/resource/AClasses.dex -------------------------------------------------------------------------------- /resource/ArrayTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/resource/ArrayTest.dex -------------------------------------------------------------------------------- /resource/TryCatchTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/resource/TryCatchTest.dex -------------------------------------------------------------------------------- /src/test/resources/Empty.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/Empty.dex -------------------------------------------------------------------------------- /src/test/resources/empty.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/empty.dex -------------------------------------------------------------------------------- /src/test/resources/CFGTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/CFGTest.dex -------------------------------------------------------------------------------- /src/test/resources/classes.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/classes.dex -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/SingleTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/SingleTest.dex -------------------------------------------------------------------------------- /src/test/resources/TryCatch.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/TryCatch.dex -------------------------------------------------------------------------------- /src/test/resources/DataFlowTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/DataFlowTest.dex -------------------------------------------------------------------------------- /src/test/resources/AnnotationTest.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penguin-wwy/dedx/HEAD/src/test/resources/AnnotationTest.dex -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | cmake-build-debug 6 | *.class 7 | *.log 8 | build/ 9 | .gradle/ 10 | out/ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # http://editorconfig.org 3 | root = true 4 | 5 | charset = utf-8 6 | insert_final_newline = true 7 | 8 | [*.{java,kt,kts}] 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 23 23:17:49 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip 7 | -------------------------------------------------------------------------------- /error-case/SwitchError.java: -------------------------------------------------------------------------------- 1 | // dx --dex --output=SwitchError.dex SwitchError.class 2 | // dalvik occur error in packed-switch: target address is misplaced( -1 ) 3 | 4 | public class SwitchError { 5 | public int test(int a) { 6 | int res = 0; 7 | switch (a) { 8 | case 1: res = 1; break; 9 | case 2: res = 3; break; 10 | case 3: res = 6; break; 11 | } 12 | return res; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/struct/ClassInfoTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.struct 2 | 3 | import com.dedx.dex.struct.type.ObjectType 4 | import com.dedx.dex.struct.type.TypeBox 5 | import org.junit.Assert.assertFalse 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Test 8 | 9 | class ClassInfoTest { 10 | @Test 11 | fun testClassInfo() { 12 | val info = ClassInfo.fromType(TypeBox.create(ObjectType("java.lang.String"))) 13 | assertTrue(info.fullName == "java.lang.String") 14 | assertTrue(info.pkg == "java.lang") 15 | assertTrue(info.name == "String") 16 | assertFalse(info.isInner) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/struct/type/TypeBoxTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.struct.type 2 | 3 | import org.junit.Assert.assertTrue 4 | import org.junit.Test 5 | 6 | class TypeBoxTest { 7 | 8 | @Test 9 | fun testCreateBox() { 10 | val intBox = TypeBox.create("I") 11 | assertTrue(intBox.getAsBasicType()?.mark == BasicType.INT.mark) 12 | 13 | val objectBox = TypeBox.create("Ljava/lang/String;") 14 | assertTrue(objectBox.getAsObjectType()?.typeName.equals("java.lang.String")) 15 | 16 | val arrayBox = TypeBox.create("[B") 17 | assertTrue(arrayBox.getAsArrayType()?.subType?.getAsBasicType()?.mark == BasicType.BYTE.mark) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/TypeInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | object TypeInfo 20 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/parser/AnnotationsParserTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.parser 2 | 3 | import com.dedx.dex.struct.AttrKey 4 | import com.dedx.dex.struct.DexNode 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | 8 | class AnnotationsParserTest { 9 | @Test 10 | fun testAnnotationsParser() { 11 | val bytes = AnnotationsParser::class.java.getResource("/AnnotationTest.dex").openStream().readBytes() 12 | val dexNode = DexNode.create(bytes) 13 | dexNode.loadClass() 14 | for (clsNode in dexNode.classes) { 15 | if (clsNode.clsInfo.name == "AnnotationTest") { 16 | assertEquals(clsNode.attributes[AttrKey.ANNOTATION].toString(), "[{value:AnnotationTest]") 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/Factory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | interface Factory { 20 | fun create(): T 21 | } 22 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/transform/MethodTransformerTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.transform 2 | 3 | import com.dedx.dex.struct.DexNode 4 | import org.junit.Assert.* 5 | import org.junit.Test 6 | 7 | class MethodTransformerTest { 8 | @Test 9 | fun singleTest() { 10 | val bytes = MethodTransformerTest::class.java.getResource("/SingleTest.dex").openStream().readBytes() 11 | val dexNode = DexNode.create(bytes) 12 | dexNode.loadClass() 13 | val testClazz = dexNode.getClass("com.test.SingleTest") 14 | val classTransformer = ClassTransformer(testClazz!!, "") 15 | classTransformer.visitClass() 16 | val path = MethodTransformerTest::class.java.getResource("/SingleTest.dex") 17 | .file.replace("SingleTest.dex", "com/test/SingleTest.class") 18 | println(path) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/TryCatch.java: -------------------------------------------------------------------------------- 1 | import java.io.BufferedReader; 2 | import java.io.FileReader; 3 | import java.io.IOException; 4 | 5 | public class TryCatch { 6 | public int run(String filePath) { 7 | try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { 8 | if (NextLineSize(reader) > 0) { 9 | return NextLineSize(reader); 10 | } 11 | } catch (IOException e) { 12 | e.printStackTrace(System.err); 13 | } finally { 14 | System.out.println("Read Line Length."); 15 | } 16 | return 0; 17 | } 18 | 19 | protected static int NextLineSize(BufferedReader reader) throws IOException { 20 | String line = reader.readLine(); 21 | System.out.println(line.getBytes()); 22 | return line.length(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/ConstStorage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | object ConstStorage { 20 | fun processConstFields(cls: ClassNode, staticFields: List) { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/passes/Pass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform.passes 18 | 19 | import com.dedx.transform.InstTransformer 20 | 21 | interface Pass { 22 | fun initializaPass() 23 | 24 | fun runOnFunction(instTrans: InstTransformer) 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/TypeConfliction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils 18 | 19 | class TypeConfliction : Exception { 20 | 21 | constructor(message: String) : super(message) 22 | 23 | constructor(message: String, cause: Throwable) : super(message, cause) 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/BlockEmptyException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils 18 | 19 | class BlockEmptyException : Exception { 20 | 21 | constructor(message: String) : super(message) 22 | 23 | constructor(message: String, cause: Throwable) : super(message, cause) 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/DataFlowAnalyzeException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils 18 | 19 | class DataFlowAnalyzeException : Exception { 20 | 21 | constructor(message: String) : super(message) 22 | 23 | constructor(message: String, cause: Throwable) : super(message, cause) 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/dedx/test/DynamicClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.test; 18 | 19 | public class DynamicClassLoader extends ClassLoader { 20 | public Class defineClass(String name, byte[] bytes) { 21 | return defineClass(name, bytes, 0, bytes.length); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/EmptyResource.kt: -------------------------------------------------------------------------------- 1 | package com.dedx 2 | 3 | import com.dedx.dex.struct.ClassNode 4 | import com.dedx.dex.struct.DexNode 5 | import com.dedx.dex.struct.MethodNode 6 | import java.lang.RuntimeException 7 | 8 | object EmptyResource { 9 | val dexNode: DexNode by lazy { 10 | val bytes = EmptyResource::class.java.getResource("/empty.dex").openStream().readBytes() 11 | DexNode.create(bytes) 12 | } 13 | 14 | val classNode: ClassNode by lazy { 15 | dexNode.loadClass() 16 | dexNode.getClass("Empty") ?: throw RuntimeException("Get empty class failed.") 17 | } 18 | 19 | val emptyMethodNode: MethodNode by lazy { 20 | classNode.searchMethodByProto("empty", "()V") ?: throw RuntimeException("Get empty method failed.") 21 | } 22 | 23 | val staticMethodNode: MethodNode by lazy { 24 | classNode.searchMethodByProto("static_empty", "()V") ?: throw RuntimeException("Get static empty method failed.") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/annotation/DepAnnotation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils.annotation 18 | 19 | import kotlin.reflect.KClass 20 | 21 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 22 | @Retention(AnnotationRetention.RUNTIME) 23 | annotation class DepAnnotation( 24 | val klass: KClass, 25 | val method: String = "", 26 | val input: Boolean 27 | ) 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/ReRopper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | class ReRopper(size: Int) { 20 | val frames = arrayOfNulls(size) 21 | 22 | // fun getOrCreateFrame(index: Int): StackFrame { 23 | // if (frames[index] == null) { 24 | // frames[index] = StackFrame().init() 25 | // } 26 | // return frames[index]!! 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/passes/StackAndSlotMark.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform.passes 18 | 19 | import com.dedx.transform.InstTransformer 20 | 21 | object StackAndSlotMark : Pass { 22 | override fun initializaPass() { } 23 | 24 | override fun runOnFunction(instTrans: InstTransformer) { 25 | var maxStackSize = 0 26 | val slotUses = ByteArray(instTrans.mthTransformer.mthNode.regsCount) { 0 } 27 | for (inst in instTrans.imInstList()) { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/struct/MethodNodeTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.struct 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class MethodNodeTest { 7 | @Test 8 | fun testLoadMethodCode() { 9 | val bytes = MethodNodeTest::class.java.getResource("/SingleTest.dex").openStream().readBytes() 10 | val dexNode = DexNode.create(bytes) 11 | dexNode.loadClass() 12 | val singleTestClazz = dexNode.getClass("com.test.SingleTest") 13 | val mthFibonacci = singleTestClazz?.searchMethodByProto("Fibonacci", "(I)I") 14 | assertEquals(mthFibonacci?.argsList?.size, 1) 15 | assertEquals(mthFibonacci?.argsList?.get(0)?.regNum, 2) 16 | } 17 | 18 | @Test 19 | fun testTryCatch() { 20 | val bytes = MethodNodeTest::class.java.getResource("/TryCatch.dex").openStream().readBytes() 21 | val dexNode = DexNode.create(bytes) 22 | dexNode.loadClass() 23 | val testClazz = dexNode.getClass("TryCatch") 24 | val testMethod = testClazz?.searchMethodByProto("NextLineSize", "(Ljava/io/BufferedReader;)I") 25 | testMethod?.load() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/transform/passes/RemoveNOPTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.transform.passes 2 | 3 | import com.dedx.EmptyResource 4 | import com.dedx.transform.* 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.objectweb.asm.Opcodes 8 | 9 | class RemoveNOPTest { 10 | 11 | private fun createNOPInstTrans(): InstTransformer { 12 | val instTransformer = InstTransformer(MethodTransformer(EmptyResource.emptyMethodNode, ClassTransformer(EmptyResource.classNode))) 13 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ISTORE, 0)) 14 | instTransformer.pushJvmInst(JvmInst.CreateSingleInst(Opcodes.NOP)) 15 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ILOAD, 0)) 16 | instTransformer.pushJvmInst(JvmInst.CreateLiteralInst(Opcodes.LDC, 100, SlotType.INT)) 17 | instTransformer.pushJvmInst(JvmInst.CreateSingleInst(Opcodes.IADD)) 18 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ISTORE, 0)) 19 | return instTransformer 20 | } 21 | 22 | @Test 23 | fun testRemoveNOP() { 24 | val instTransformer = createNOPInstTrans() 25 | RemoveNOP.runOnFunction(instTransformer) 26 | assertEquals(5, instTransformer.instListSize()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/DecodeException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils 18 | 19 | import com.dedx.transform.StackFrame 20 | 21 | class DecodeException : Exception { 22 | 23 | constructor(message: String) : super(message) 24 | 25 | constructor(message: String, cause: Throwable) : super(message, cause) 26 | 27 | constructor(message: String, offset: Int) : this("$message [offset: $offset]\n${StackFrame.getFrameOrPut(offset)}") 28 | 29 | constructor(message: String, offset: Int, cause: Exception) : this("$message [offset: $offset]\n" + 30 | "${StackFrame.getFrameOrPut(offset)}\n\t$cause") 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/InstStorage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import org.objectweb.asm.Label 20 | 21 | interface InstStorage { 22 | fun storeInst(labelInst: LabelInst, jvmInst: JvmInst) 23 | 24 | fun getInst(labelInst: LabelInst): JvmInst? 25 | } 26 | 27 | class LabelMap : InstStorage { 28 | private val label2Inst = HashMap() 29 | 30 | override fun storeInst(labelInst: LabelInst, jvmInst: JvmInst) { 31 | label2Inst[labelInst.getValueOrCreate()] = jvmInst 32 | } 33 | 34 | override fun getInst(labelInst: LabelInst) = label2Inst[labelInst.getValue()] 35 | } 36 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/transform/passes/EliminateCodeTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.transform.passes 2 | 3 | import com.dedx.EmptyResource 4 | import com.dedx.transform.* 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.objectweb.asm.Opcodes 8 | 9 | class EliminateCodeTest { 10 | 11 | private fun createLoadAndStoreInstTrans(): InstTransformer { 12 | val instTransformer = InstTransformer(MethodTransformer(EmptyResource.emptyMethodNode, ClassTransformer(EmptyResource.classNode))) 13 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ISTORE, 0)) 14 | instTransformer.pushJvmInst(JvmInst.CreateSingleInst(Opcodes.NOP)) 15 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ILOAD, 0)) 16 | instTransformer.pushJvmInst(JvmInst.CreateLiteralInst(Opcodes.LDC, 100, SlotType.INT)) 17 | instTransformer.pushJvmInst(JvmInst.CreateSingleInst(Opcodes.IADD)) 18 | instTransformer.pushJvmInst(JvmInst.CreateSlotInst(Opcodes.ISTORE, 0)) 19 | return instTransformer 20 | } 21 | @Test 22 | fun testEliminateLoadAndStore() { 23 | val instTransformer = createLoadAndStoreInstTrans() 24 | EliminateCode.runOnFunction(instTransformer) 25 | assertEquals(4, instTransformer.instListSize()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/InstArgNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | class InstArgNode(val regNum: Int, val type: TypeBox, inst: InstNode? = null) : AttrNode { 22 | 23 | override val attributes: MutableMap = HashMap() 24 | val user = ArrayList() 25 | // method argument have no assign site 26 | val site = inst 27 | 28 | fun isArgument() = site == null 29 | 30 | fun setName(name: String) { 31 | attributes[AttrKey.NAME] = AttrValue(Enc.ENC_STRING, name) 32 | } 33 | 34 | fun getName() = attributes[AttrKey.NAME]?.getAsString() 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/AccessInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dx.rop.code.AccessFlags 20 | 21 | interface AccessInfo { 22 | val accFlags: Int 23 | fun isPublic() = (accFlags and AccessFlags.ACC_PUBLIC) != 0 24 | fun isProtected() = (accFlags and AccessFlags.ACC_PROTECTED) != 0 25 | fun isPrivate() = (accFlags and AccessFlags.ACC_PRIVATE) != 0 26 | fun isFinal() = (accFlags and AccessFlags.ACC_FINAL) != 0 27 | fun isStatic() = (accFlags and AccessFlags.ACC_STATIC) != 0 28 | fun isAbstract() = (accFlags and AccessFlags.ACC_ABSTRACT) != 0 29 | fun isInterface() = (accFlags and AccessFlags.ACC_INTERFACE) != 0 30 | } 31 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/pass/CFGBuildPassTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.pass 2 | 3 | import com.dedx.dex.struct.DexNode 4 | import com.dedx.tools.EmptyConfiguration 5 | import com.dedx.transform.ClassTransformer 6 | import com.dedx.transform.MethodTransformer 7 | import org.junit.Assert.assertFalse 8 | import org.junit.Assert.assertTrue 9 | import org.junit.Test 10 | 11 | class CFGBuildPassTest { 12 | @Test 13 | fun CFGBuilding() { 14 | val bytes = CFGBuildPassTest::class.java.getResource("/CFGTest.dex").openStream().readBytes() 15 | val dexNode = DexNode.create(bytes) 16 | dexNode.loadClass() 17 | val testClazz = dexNode.getClass("com.test.CFGTest") 18 | assertFalse(testClazz == null) 19 | val methodNode = testClazz?.searchMethodByProto("testOne", "(Ljava/lang/String;I)Ljava/lang/String;") 20 | assertTrue(methodNode != null) 21 | val transformer = MethodTransformer(methodNode!!, ClassTransformer(testClazz!!, EmptyConfiguration)) 22 | CFGBuildPass.visit(transformer) 23 | assertFalse(transformer.blockMap.isEmpty()) 24 | for (entry in transformer.blockMap) { 25 | for (inst in entry.value.instList) { 26 | print("${java.lang.Integer.toHexString(inst.cursor)} ") 27 | } 28 | println() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/ObjectType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | open class ObjectType(val typeName: String) { 20 | override fun equals(other: Any?): Boolean { 21 | if (other == null || other !is ObjectType) { 22 | return false 23 | } 24 | return typeName == other.typeName 25 | } 26 | 27 | fun descriptor() = "L${nameWithSlash()};" 28 | 29 | fun nameWithDot() = typeName 30 | 31 | fun nameWithSlash() = typeName.replace('.', '/') 32 | 33 | override fun hashCode(): Int { 34 | return toString().hashCode() 35 | } 36 | 37 | override fun toString(): String { 38 | return typeName 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/passes/RemoveNOP.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform.passes 18 | 19 | import com.dedx.transform.InstTransformer 20 | import com.dedx.transform.JvmInst 21 | import com.dedx.transform.SingleInst 22 | import org.objectweb.asm.Opcodes 23 | 24 | object RemoveNOP : Pass { 25 | override fun initializaPass() { } 26 | 27 | override fun runOnFunction(instTrans: InstTransformer) { 28 | val nopList = ArrayList() 29 | instTrans.imInstList().forEach { 30 | if (it.getAs(SingleInst::class)?.opcodes == Opcodes.NOP) { 31 | nopList.add(it) 32 | } 33 | } 34 | instTrans.removeJvmInsts(nopList) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/InfoStorage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | object InfoStorage { 22 | val classes: MutableMap = HashMap() 23 | val methods: MutableMap = HashMap() 24 | val fields: MutableMap = HashMap() 25 | 26 | fun getMethod(dex: DexNode, mthId: Int): MethodInfo? { 27 | return methods[mthId] 28 | } 29 | 30 | fun putMethod(dex: DexNode, mthId: Int, mth: MethodInfo): MethodInfo { 31 | methods[mthId] = mth 32 | return mth 33 | } 34 | 35 | fun clear() { 36 | classes.clear() 37 | methods.clear() 38 | fields.clear() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/Annotation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | enum class Visibility { 22 | BUILD, 23 | RUNTIME, 24 | SYSTEM 25 | } 26 | 27 | class Annotation(val visibility: Visibility?, val type: TypeBox, val values: Map) { 28 | fun getDefaultValue(): Any? { 29 | return values["value"] 30 | } 31 | 32 | fun hasVisibility() = visibility != null 33 | 34 | override fun toString(): String { 35 | val strBuilder = StringBuilder("{") 36 | for (entry in values) { 37 | strBuilder.append("${entry.key}:${entry.value},") 38 | } 39 | strBuilder.deleteCharAt(strBuilder.length - 1) 40 | return strBuilder.toString() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/passes/InstAnalysis.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform.passes 18 | 19 | import com.dedx.transform.InstTransformer 20 | import com.dedx.transform.JumpInst 21 | 22 | object InstAnalysis : Pass { 23 | override fun initializaPass() { } 24 | 25 | override fun runOnFunction(instTrans: InstTransformer) { 26 | fl@ for (i in 0 until instTrans.instListSize()) { 27 | val jvmInst = instTrans.inst(i) 28 | if (jvmInst is JumpInst) { 29 | val target = instTrans.instStorage.getInst(jvmInst.target) ?: continue@fl 30 | if (!instTrans.jumpMap.containsKey(target)) { 31 | instTrans.jumpMap[target] = ArrayList() 32 | } 33 | instTrans.jumpMap[target]!!.add(jvmInst) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/tools/Configuration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.tools 18 | 19 | open class Configuration { 20 | var optLevel = NormalOpt 21 | val classesList: MutableList = ArrayList() 22 | val blackClasses: MutableList = ArrayList() 23 | 24 | lateinit var outDir: String 25 | lateinit var dexFiles: MutableList 26 | 27 | var logFile: String? = null 28 | var debug: Boolean = false 29 | 30 | var successNum = 0 31 | var failedNum = 0 32 | 33 | companion object { 34 | const val NormalFast = 0 35 | const val NormalOpt = 1 36 | const val Optimized = 2 37 | } 38 | 39 | @Synchronized 40 | fun addSuccess(num: Int = 1) = also { 41 | successNum += num 42 | } 43 | 44 | @Synchronized 45 | fun addFailed(num: Int = 1) = also { 46 | failedNum += num 47 | } 48 | } 49 | 50 | val CmdConfiguration = Configuration() 51 | val EmptyConfiguration = Configuration() 52 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/SystemComment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | object DalvikAnnotationDefault : ObjectType("dalvik.annotation.AnnotationDefault") 20 | 21 | object DalvikEnclosingClass : ObjectType("dalvik.annotation.EnclosingClass") 22 | 23 | object DalvikEnclosingMethod : ObjectType("dalvik.annotation.EnclosingMethod") 24 | 25 | object DalvikInnerClass : ObjectType("dalvik.annotation.InnerClass") 26 | 27 | object DalvikMethodParameters : ObjectType("dalvik.annotation.MethodParameters") 28 | 29 | object DalvikSigature : ObjectType("dalvik.annotation.Signature") 30 | 31 | object DalvikThrows : ObjectType("dalvik.annotation.Throws") 32 | 33 | fun isSystemCommentType(other: ObjectType) = (other == DalvikAnnotationDefault) or 34 | (other == DalvikEnclosingClass) || (other == DalvikEnclosingMethod) or 35 | (other == DalvikInnerClass) || (other == DalvikMethodParameters) or 36 | (other == DalvikSigature) || (other == DalvikThrows) 37 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/FieldNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dex.ClassData 20 | 21 | interface FieldFactory { 22 | fun create(parent: ClassNode, field: ClassData.Field): FieldNode 23 | } 24 | 25 | class FieldNode private constructor( 26 | val parent: ClassNode, 27 | val fieldInfo: FieldInfo, 28 | access: Int 29 | ) : AccessInfo, AttrNode { 30 | override val accFlags: Int = access 31 | override var attributes: MutableMap = HashMap() 32 | companion object : FieldFactory { 33 | override fun create(parent: ClassNode, field: ClassData.Field): FieldNode { 34 | val accessFlag = field.accessFlags 35 | return FieldNode(parent, FieldInfo.fromDex(parent.parent, field.fieldIndex), accessFlag) 36 | } 37 | } 38 | 39 | override fun equals(other: Any?): Boolean { 40 | if (other == null || other !is FieldNode) { 41 | return false 42 | } 43 | return fieldInfo == other.fieldInfo 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/BasicType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | enum class BasicType constructor(val signature: String, val typeName: String, val mark: Int) { 20 | VOID("V", "void", 0), 21 | BOOLEAN("Z", "boolean", 1), 22 | CHAR("C", "char", 2), 23 | BYTE("B", "byte", 3), 24 | SHORT("S", "short", 4), 25 | INT("I", "int", 5), 26 | LONG("J", "long", 6), 27 | FLOAT("F", "float", 7), 28 | DOUBLE("D", "double", 8), 29 | OBJECT("L", "object", 9), 30 | ARRAY("[", "array", 10) { 31 | override fun toString(): String { 32 | return "[]" 33 | } 34 | }; 35 | 36 | companion object { 37 | fun get(c: Char): BasicType? { 38 | for (basic in enumValues()) { 39 | if (basic.signature.equals(c.toString())) { 40 | return basic 41 | } 42 | } 43 | return null 44 | } 45 | } 46 | 47 | fun descriptor() = signature 48 | 49 | override fun toString(): String { 50 | return typeName 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/FieldInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | class FieldInfo(val declClass: ClassInfo, val name: String, val type: TypeBox) { 22 | 23 | companion object { 24 | fun from(dex: DexNode, declClass: ClassInfo, name: String, type: TypeBox): FieldInfo { 25 | return FieldInfo(declClass, name, type) 26 | } 27 | 28 | fun fromDex(dex: DexNode, index: Int): FieldInfo { 29 | val field = dex.getFieldId(index) 30 | return from(dex, 31 | ClassInfo.fromDex(dex, field.declaringClassIndex), 32 | dex.getString(field.nameIndex), 33 | dex.getType(field.typeIndex)) 34 | } 35 | } 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (other == null || other !is FieldInfo) { 39 | return false 40 | } 41 | return declClass == other.declClass && name == other.name && type == other.type 42 | } 43 | 44 | override fun toString(): String { 45 | return "$declClass $name" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/NamedDebugInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | class NamedDebugInfo(override val value: MutableList = MutableList(5) { i -> AttrValue.Empty }) : 22 | AttrValueList(value) { 23 | companion object { 24 | val START = 0 25 | val END = 1 26 | val REG = 2 27 | val NAME = 3 28 | val TYPE = 4 29 | } 30 | 31 | fun getStart() = value[START].getAsInt() 32 | 33 | fun setStart(num: Int) { 34 | value[START] = AttrValue(Enc.ENC_INT, num) 35 | } 36 | 37 | fun getEnd() = value[END].getAsInt() 38 | 39 | fun setEnd(num: Int) { 40 | value[END] = AttrValue(Enc.ENC_INT, num) 41 | } 42 | 43 | fun getREG() = value[REG].getAsInt() 44 | 45 | fun setREG(num: Int) { 46 | value[REG] = AttrValue(Enc.ENC_INT, num) 47 | } 48 | 49 | fun getName() = value[NAME].getAsString() 50 | 51 | fun setName(name: String) { 52 | value[NAME] = AttrValue(Enc.ENC_STRING, name) 53 | } 54 | 55 | fun getType() = value[TYPE].getAsType() 56 | 57 | fun setType(type: TypeBox) { 58 | value[TYPE] = AttrValue(Enc.ENC_TYPE, type) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/MethodDebugInfoVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.dedx.dex.parser.DebugInfoParser 20 | import com.dedx.dex.struct.InstNode 21 | import com.dedx.dex.struct.LocalVarNode 22 | import com.dedx.dex.struct.MethodNode 23 | 24 | object MethodDebugInfoVisitor { 25 | 26 | fun visitMethod(mthNode: MethodNode) { 27 | if (mthNode.debugInfoOffset == 0) { 28 | return 29 | } 30 | val insnArr = mthNode.codeList 31 | val parser = DebugInfoParser(mthNode, insnArr, mthNode.debugInfoOffset) 32 | val locals = parser.process() 33 | attachDebugInfo(mthNode, locals, insnArr) 34 | attachSourceInfo(mthNode, insnArr) 35 | } 36 | 37 | fun attachDebugInfo(mthNode: MethodNode, locals: List, insts: Array) { 38 | // TODO 39 | } 40 | 41 | fun attachSourceInfo(mthNode: MethodNode, insts: Array) { 42 | for (inst in insts) { 43 | if (inst != null) { 44 | val line = inst.getLineNumber() 45 | if (line != null) { 46 | mthNode.setLineNumber(line - 1) 47 | } 48 | return 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/utils/BitArrayTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.utils 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | class BitArrayTest { 7 | @Test 8 | fun testBitArray() { 9 | val b1 = BitArray(7) 10 | assertEquals(b1._array.size, 1) 11 | b1.setTrue(0) 12 | assertEquals(b1._array[0].toInt(), 1) 13 | b1.setTrue(5) 14 | assertEquals(b1.get(5), 1) 15 | b1.setFalse(5) 16 | assertEquals(b1.get(5), 0) 17 | } 18 | 19 | @Test 20 | fun testAnd() { 21 | val b1 = BitArray(6) 22 | b1.setTrue(5) 23 | val b2 = BitArray(16) 24 | b2.setTrue(5) 25 | b2.setTrue(15) 26 | val b3 = b1.and(b2) 27 | assertEquals(b3._array[0].toInt(), 32) 28 | assertEquals(b3.size, 16) 29 | assertEquals(b3._array.size, 2) 30 | } 31 | 32 | @Test 33 | fun testOr() { 34 | val b1 = BitArray(6) 35 | b1.setTrue(5) 36 | val b2 = BitArray(16) 37 | b2.setTrue(5) 38 | b2.setTrue(15) 39 | val b3 = b1.or(b2) 40 | assertEquals(b3._array[0].toInt(), 32) 41 | assertEquals(b3._array[1].toUByte(), 128.toUByte()) 42 | } 43 | 44 | @Test 45 | fun testSub() { 46 | val b1 = BitArray(6) 47 | b1.setTrue(5) 48 | val b2 = BitArray(16) 49 | b2.setTrue(5) 50 | b2.setTrue(4) 51 | b2.setTrue(15) 52 | val b3 = b2.sub(b1) 53 | assertEquals(b3._array[1].toUByte(), 128.toUByte()) 54 | assertEquals(b3._array[0].toInt(), 16) 55 | } 56 | 57 | @Test 58 | fun testToString() { 59 | val b1 = BitArray(6) 60 | b1.setTrue(5) 61 | assertEquals(b1.toString(), "{5}") 62 | val b2 = BitArray(16) 63 | b2.setTrue(5) 64 | b2.setTrue(4) 65 | b2.setTrue(15) 66 | assertEquals(b2.toString(), "{4 5 15}") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/InstNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dx.io.instructions.DecodedInstruction 20 | import org.objectweb.asm.Label 21 | 22 | class InstNode(val cursor: Int, val instruction: DecodedInstruction) : AttrNode { 23 | override val attributes: MutableMap = HashMap() 24 | 25 | fun opcode() = instruction.opcode 26 | 27 | fun target() = instruction.target 28 | 29 | fun setLineNumber(line: Int) { 30 | attributes[AttrKey.LINENUMBER] = AttrValue(Enc.ENC_INT, line) 31 | } 32 | 33 | fun getLineNumber() = attributes[AttrKey.LINENUMBER]?.getAsInt() 34 | 35 | fun setLable(label: Label) { 36 | attributes[AttrKey.LABEL] = AttrValueLabel(label) 37 | } 38 | 39 | fun getLabel() = when (attributes.containsKey(AttrKey.LABEL)) { 40 | true -> attributes[AttrKey.LABEL] as AttrValueLabel 41 | false -> null 42 | } 43 | 44 | fun getLabelOrPut() = attributes.getOrPut(AttrKey.LABEL) { 45 | return@getOrPut AttrValueLabel(Label()) 46 | } as AttrValueLabel 47 | 48 | fun setTryEntry(block: TryCatchBlock) { 49 | attributes[AttrKey.TRY_ENTRY] = AttrValue(Enc.ENC_TRY_ENTRY, block) 50 | } 51 | 52 | fun getTryEntry() = attributes[AttrKey.TRY_ENTRY]?.getAsTryEntry() 53 | 54 | override fun toString(): String { 55 | return instruction.toString() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/BasicBlock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.dedx.dex.struct.InstNode 20 | import org.objectweb.asm.Label 21 | 22 | class BasicBlock constructor( 23 | val startLable: Label, 24 | val predecessor: ArrayList, 25 | val successor: ArrayList 26 | ) { 27 | 28 | constructor(startLable: Label, predecessor: ArrayList, successor: ArrayList, cursor: Int) : 29 | this(startLable, predecessor, successor) { 30 | firstCursor = cursor 31 | } 32 | 33 | var terminal: InstNode? = null 34 | val instList = ArrayList() 35 | private var firstCursor: Int? = null 36 | 37 | companion object { 38 | fun create(startLable: Label, predecessor: BasicBlock?): BasicBlock { 39 | val preList = ArrayList() 40 | val succList = ArrayList() 41 | if (predecessor != null) { 42 | preList.add(predecessor!!) 43 | } 44 | return BasicBlock(startLable, preList, succList) 45 | } 46 | } 47 | 48 | fun firstCursor(): Int? { 49 | if (firstCursor != null) { 50 | return firstCursor 51 | } 52 | if (instList.isNotEmpty()) { 53 | firstCursor = instList[0].cursor 54 | return firstCursor 55 | } 56 | return null 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/passes/EliminateCode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform.passes 18 | 19 | import com.dedx.transform.InstTransformer 20 | import com.dedx.transform.JvmInst 21 | import com.dedx.transform.SlotInst 22 | import java.util.Stack 23 | 24 | object EliminateCode : Pass { 25 | override fun initializaPass() { } 26 | 27 | override fun runOnFunction(instTrans: InstTransformer) { 28 | eliminateLoadAndStore(instTrans) 29 | } 30 | 31 | private fun eliminateLoadAndStore(instTrans: InstTransformer) { 32 | val instStack = Stack() 33 | val needToClean = ArrayList() 34 | for (i in 0 until instTrans.instListSize()) { 35 | if (instTrans.inst(i) !is SlotInst) { 36 | continue 37 | } 38 | val slotInst = instTrans.inst(i) as SlotInst 39 | if (slotInst.isStoreInst()) { 40 | instStack.push(slotInst) 41 | } 42 | if (slotInst.isLoadInst() && instStack.isNotEmpty()) { 43 | if (slotInst.slot == instStack.peek().slot) { 44 | needToClean.add(instStack.pop()) 45 | needToClean.add(slotInst) 46 | } else { 47 | // better optmize 48 | instStack.clear() 49 | } 50 | } 51 | } 52 | instTrans.removeJvmInsts(needToClean) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dedx 2 | 3 | This is a tool to convert dex file to class file which similar to [dex2jar](https://github.com/pxb1988/dex2jar) 4 | 5 | ### Description 6 | 7 | [dx](https://github.com/aosp-mirror/platform_dalvik/tree/master/dx) is the home of Dalvik eXchange, the thing that takes in class files and reformulates them for consumption in the dalvik VM. 8 | 9 | Compared to dex2jar, with the help of dx.jar eliminate the trouble of instruction synchronization. 10 | 11 | And through the high version of the asm package, can produce a higher version of jvm bytecode. 12 | 13 | ### Current progress 14 | 15 | ~~Parse Dex File~~ 16 | 17 | ~~Parse Class~~ 18 | 19 | ~~Parse Method~~ 20 | 21 | ~~Parse Debug Info For Every Method~~ 22 | 23 | ~~Instruction Generation~~ 24 | 25 | ~~Create class field writer~~ 26 | 27 | ~~line number table~~ 28 | 29 | --> Rewrite stack frame map 30 | 31 | --> Optmization 32 | 33 | ### Test 34 | 35 | ``` 36 | gradle -q runTest 37 | ``` 38 | 39 | 40 | ### Example 41 | 42 | ``` 43 | dedx -o /path/to/output /project_path/resource/Base.dex 44 | ``` 45 | 46 | Will create Base.class in `output` directory 47 | 48 | And then, you can load this class and invoke method with the following code 49 | 50 | ```java 51 | import java.lang.reflect.Method; 52 | import java.nio.file.Files; 53 | import java.nio.file.Paths; 54 | 55 | public class ExampleLoader extends ClassLoader { 56 | public Class defineClass(String name, byte[] bytes) { 57 | return defineClass(name, bytes, 0, bytes.length); 58 | } 59 | 60 | public static void main(String[] args) { 61 | ExampleLoader loader = new ExampleLoader(); 62 | try { 63 | byte[] bytes = Files.readAllBytes(Paths.get("/path/to/Base.class")); 64 | Class baseClass = loader.defineClass("com.test.Base", bytes); 65 | Method addInt = baseClass.getMethod("addInt", int.class, int.class); 66 | assert (Integer) addInt.invoke(null, 1, 1) == 2; 67 | } catch (Exception e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ### Note 75 | 76 | Master branch not ensure build and run success. 77 | 78 | If you want to use, please checkout tag. -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/LocalVarNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | class LocalVarNode private constructor(val regNum: Int, val name: String?, val type: TypeBox?, val sign: String?) { 22 | var isEnd: Boolean = false 23 | private set 24 | var startAddr: Int = 0 25 | private set 26 | var endAddr: Int = 0 27 | private set 28 | companion object { 29 | fun create(dex: DexNode, rn: Int, nameId: Int, typeId: Int, signId: Int): LocalVarNode { 30 | val name = when (nameId) { 31 | DexNode.NO_INDEX -> null 32 | else -> dex.getString(nameId) 33 | } 34 | val type = when (typeId) { 35 | DexNode.NO_INDEX -> null 36 | else -> dex.getType(typeId) 37 | } 38 | val sign = when (signId) { 39 | DexNode.NO_INDEX -> null 40 | else -> dex.getString(signId) 41 | } 42 | return LocalVarNode(rn, name, type, sign) 43 | } 44 | 45 | fun create(arg: InstArgNode) = LocalVarNode(arg.regNum, arg.getName(), arg.type, null) 46 | 47 | fun create(rn: Int, name: String?, type: TypeBox?) = LocalVarNode(rn, name, type, null) 48 | } 49 | 50 | fun start(addr: Int) { 51 | isEnd = false 52 | startAddr = addr 53 | } 54 | 55 | fun end(addr: Int): Boolean { 56 | if (isEnd) { 57 | return false 58 | } 59 | isEnd = true 60 | endAddr = addr 61 | return true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/ArrayType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | class ArrayType(val subType: TypeBox) { 20 | override fun equals(other: Any?): Boolean { 21 | if (other == null || other !is ArrayType) { 22 | return false 23 | } 24 | return subType == other.subType 25 | } 26 | 27 | fun descriptor(): String { 28 | var desc = StringBuilder("[") 29 | var subType = this.subType 30 | loop@ while (true) { 31 | when (subType.type::class) { 32 | ArrayType::class -> { 33 | desc.append("[") 34 | subType = (subType.type as ArrayType).subType 35 | } 36 | else -> { 37 | desc.append(subType.descriptor()) 38 | break@loop 39 | } 40 | } 41 | } 42 | return desc.toString() 43 | } 44 | 45 | fun nameWithSlash(): String { 46 | var name = StringBuilder("[") 47 | var subType = this.subType 48 | loop@ while (true) { 49 | when (subType.type::class) { 50 | ArrayType::class -> { 51 | name.append("[") 52 | subType = (subType.type as ArrayType).subType 53 | } 54 | ObjectType::class -> { 55 | name.append((subType.type as ObjectType).nameWithSlash()) 56 | break@loop 57 | } 58 | else -> { 59 | name.append(subType.descriptor()) 60 | break@loop 61 | } 62 | } 63 | } 64 | return name.toString() 65 | } 66 | 67 | override fun hashCode(): Int { 68 | return toString().hashCode() 69 | } 70 | 71 | override fun toString(): String { 72 | return "$subType[]" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/TryCatchBlock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import java.util.TreeSet 20 | import kotlin.collections.ArrayList 21 | 22 | class ExceptionHandler(var handlerBlock: TryCatchBlock, val addr: Int, type: ClassInfo?) { 23 | var catchAny = false 24 | val catchTypes = TreeSet() 25 | init { 26 | if (type == null) { 27 | catchAny = true 28 | } else { 29 | catchTypes.add(type) 30 | } 31 | } 32 | 33 | fun addType(type: ClassInfo?) { 34 | if (type == null) { 35 | catchAny = true 36 | } else { 37 | catchTypes.add(type) 38 | } 39 | } 40 | 41 | fun addException(other: ExceptionHandler) { 42 | for (type in other.catchTypes) { 43 | catchTypes.add(type) 44 | } 45 | if (other.catchAny) { 46 | catchAny = true 47 | } 48 | } 49 | 50 | fun typeList(): List { 51 | val result = ArrayList() 52 | for (type in catchTypes) { 53 | result.add(type.className()) 54 | } 55 | if (catchAny) result.add(null) 56 | return result 57 | } 58 | } 59 | 60 | class TryCatchBlock { 61 | 62 | val execHandlers = ArrayList() 63 | val instList = ArrayList() 64 | 65 | fun addHandler(mthNode: MethodNode, addr: Int, type: ClassInfo?) { 66 | val exceHandler = ExceptionHandler(this, addr, type) 67 | val addHandler = mthNode.addExceptionHandler(exceHandler) 68 | if ((addHandler == exceHandler) || (addHandler.handlerBlock != this)) { 69 | execHandlers.add(addHandler) 70 | } 71 | } 72 | 73 | fun containsAllHandlers(other: TryCatchBlock) = execHandlers.containsAll(other.execHandlers) 74 | 75 | fun removeSameHandlers(other: TryCatchBlock) { 76 | for (handler in other.execHandlers) { 77 | if (execHandlers.remove(handler)) { 78 | handler.handlerBlock = other 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/MethodInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | 21 | class MethodInfo private constructor( 22 | val name: String, 23 | val retType: TypeBox, 24 | val args: List, 25 | val declClass: ClassInfo 26 | ) { 27 | 28 | var descriptor: String? = null 29 | companion object { 30 | fun create(dex: DexNode, mthId: Int): MethodInfo { 31 | val mthId = dex.getMethodId(mthId) 32 | val name = dex.getString(mthId.nameIndex) 33 | val declClass = ClassInfo.fromDex(dex, mthId.declaringClassIndex) 34 | 35 | val proto = dex.getProtoId(mthId.protoIndex) 36 | val retType = dex.getType(proto.returnTypeIndex) 37 | val args = dex.getTypeList(proto.parametersOffset) 38 | return MethodInfo(name, retType, args, declClass) 39 | } 40 | 41 | fun fromDex(dex: DexNode, mthId: Int): MethodInfo { 42 | var mth = InfoStorage.getMethod(dex, mthId) 43 | if (mth != null) { 44 | return mth 45 | } 46 | mth = create(dex, mthId) 47 | return InfoStorage.putMethod(dex, mthId, mth) 48 | } 49 | } 50 | 51 | fun parseSignature(): String { 52 | if (this.descriptor == null) { 53 | val descriptor = StringBuilder("(") 54 | for (arg in args) { 55 | descriptor.append(arg.descriptor()) 56 | } 57 | descriptor.append(")${retType.descriptor()}") 58 | this.descriptor = descriptor.toString() 59 | } 60 | return this.descriptor!! 61 | } 62 | 63 | override fun equals(other: Any?): Boolean { 64 | if (other == null || other !is MethodInfo) { 65 | return false 66 | } 67 | return declClass == other.declClass && name == other.name && retType == other.retType && args == other.args 68 | } 69 | 70 | override fun toString(): String { 71 | return "$declClass $name ${parseSignature()}" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/pass/CFGBuildPass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.pass 18 | 19 | import com.android.dx.io.Opcodes 20 | import com.dedx.dex.struct.InstNode 21 | import com.dedx.transform.BasicBlock 22 | import com.dedx.transform.MethodTransformer 23 | import org.objectweb.asm.Label 24 | 25 | object CFGBuildPass { 26 | fun visit(meth: MethodTransformer) { 27 | var prevInst: InstNode? = null 28 | val label0 = Label() 29 | var currBlock = meth.blockMap.getOrPut(label0) { 30 | return@getOrPut BasicBlock.create(label0, null) 31 | } 32 | 33 | for (inst in meth.codeList()) { 34 | if (inst == null) { 35 | continue 36 | } 37 | if (meth.inst2Block.containsKey(inst)) { 38 | currBlock = meth.inst2Block[inst]!! 39 | } else { 40 | meth.inst2Block[inst] = currBlock 41 | } 42 | currBlock.instList.add(inst) 43 | when (inst.opcode()) { 44 | Opcodes.GOTO, Opcodes.GOTO_16, Opcodes.GOTO_32 -> { 45 | connectBlock(meth, currBlock, meth.code(inst.target())!!) 46 | } 47 | in Opcodes.IF_EQ..Opcodes.IF_LEZ -> { 48 | connectBlock(meth, currBlock, meth.nextCode(inst.cursor)!!) 49 | connectBlock(meth, currBlock, meth.code(inst.target())!!) 50 | } 51 | Opcodes.THROW -> { 52 | } 53 | in Opcodes.RETURN_VOID..Opcodes.RETURN_OBJECT -> { 54 | meth.exits.add(currBlock) 55 | } 56 | } 57 | // TODO split try block 58 | prevInst = inst 59 | } 60 | } 61 | 62 | fun connectBlock(meth: MethodTransformer, curr: BasicBlock, target: InstNode) { 63 | var targetBlock: BasicBlock? 64 | if (meth.inst2Block.containsKey(target)) { 65 | targetBlock = meth.inst2Block[target] 66 | } else { 67 | targetBlock = meth.newBlock(curr) 68 | meth.inst2Block[target] = targetBlock 69 | } 70 | curr.successor.add(targetBlock!!) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/ClassInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.ObjectType 20 | import com.dedx.dex.struct.type.TypeBox 21 | 22 | class ClassInfo private constructor( 23 | val type: TypeBox, 24 | val pkg: String, 25 | val name: String, 26 | val fullName: String, 27 | val parentClass: ClassInfo?, 28 | val isInner: Boolean 29 | ) : Comparable { 30 | 31 | companion object { 32 | const val ROOT_CLASS_NAME = "java/lang/Object" 33 | 34 | fun fromType(type: TypeBox): ClassInfo { 35 | return InfoStorage.classes.getOrPut(type) { 36 | val fullName = type.getAsObjectType()?.typeName 37 | val dot = fullName!!.lastIndexOf('.') 38 | val pkg: String 39 | var name: String 40 | if (dot == -1) { 41 | pkg = "" 42 | name = fullName!! 43 | } else { 44 | pkg = fullName!!.substring(0, dot) 45 | name = fullName.substring(dot + 1) 46 | } 47 | val sep = fullName.lastIndexOf('$') 48 | var parentClass: ClassInfo? = null 49 | if (sep > 0 && sep != fullName.length - 1) { 50 | val parentName: String = fullName.substring(0, sep) 51 | parentClass = InfoStorage.classes[TypeBox.create(ObjectType(parentName))] 52 | if (parentClass != null) { 53 | name = fullName.substring(sep + 1) 54 | } 55 | } 56 | return@getOrPut ClassInfo(type, pkg, name, fullName, parentClass, parentClass != null) 57 | } 58 | } 59 | 60 | fun fromDex(dex: DexNode, typeIndex: Int) = fromType(dex.getType(typeIndex)) 61 | fun fromDex(className: String) = fromType(TypeBox.create("L$className;")) 62 | } 63 | 64 | fun className(): String { 65 | return fullName.replace(".", "/") 66 | } 67 | 68 | override fun equals(other: Any?): Boolean { 69 | if (other == null) { 70 | return false 71 | } 72 | if (other is ClassInfo) { 73 | return this.type == other.type 74 | } else if (other is String) { 75 | val otherName: String = other 76 | return (fullName == otherName) || (className() == otherName) 77 | } 78 | return false 79 | } 80 | 81 | override fun hashCode(): Int { 82 | return type.hashCode() 83 | } 84 | 85 | override fun toString(): String { 86 | return fullName 87 | } 88 | 89 | override fun compareTo(other: ClassInfo): Int { 90 | return fullName.compareTo(other.fullName) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /config/detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 1 3 | 4 | complexity: 5 | StringLiteralDuplication: 6 | active: true 7 | excludes: "**/test/**,**/*.Test.kt,**/*.Spec.kt" 8 | threshold: 5 9 | ignoreAnnotation: true 10 | excludeStringsWithLessThan5Characters: true 11 | ignoreStringsRegex: '$^' 12 | ComplexInterface: 13 | active: true 14 | threshold: 10 15 | includeStaticDeclarations: false 16 | ComplexMethod: 17 | active: true 18 | ignoreSingleWhenExpression: true 19 | MethodOverloading: 20 | active: true 21 | 22 | exceptions: 23 | NotImplementedDeclaration: 24 | active: true 25 | InstanceOfCheckForException: 26 | active: true 27 | RethrowCaughtException: 28 | active: true 29 | ReturnFromFinally: 30 | active: true 31 | SwallowedException: 32 | active: false 33 | ThrowingExceptionFromFinally: 34 | active: true 35 | ThrowingExceptionsWithoutMessageOrCause: 36 | active: true 37 | ThrowingNewInstanceOfSameException: 38 | active: true 39 | 40 | formatting: 41 | active: true 42 | android: false 43 | autoCorrect: true 44 | MaximumLineLength: 45 | active: false 46 | 47 | naming: 48 | MemberNameEqualsClassName: 49 | active: true 50 | VariableNaming: 51 | active: true 52 | variablePattern: '[a-z][A-Za-z0-9]*' 53 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' 54 | excludeClassPattern: '$^' 55 | 56 | performance: 57 | ArrayPrimitive: 58 | active: true 59 | 60 | potential-bugs: 61 | EqualsAlwaysReturnsTrueOrFalse: 62 | active: true 63 | InvalidRange: 64 | active: true 65 | IteratorHasNextCallsNextMethod: 66 | active: true 67 | IteratorNotThrowingNoSuchElementException: 68 | active: true 69 | MissingWhenCase: 70 | active: true 71 | RedundantElseInWhen: 72 | active: true 73 | UnsafeCallOnNullableType: 74 | active: true 75 | UnsafeCast: 76 | active: true 77 | excludes: "**/test/**,**/*.Test.kt,**/*.Spec.kt" 78 | UselessPostfixExpression: 79 | active: true 80 | WrongEqualsTypeParameter: 81 | active: true 82 | 83 | style: 84 | CollapsibleIfStatements: 85 | active: true 86 | EqualsNullCall: 87 | active: true 88 | ForbiddenComment: 89 | active: true 90 | values: 'TODO:,FIXME:,STOPSHIP:,@author' 91 | FunctionOnlyReturningConstant: 92 | active: true 93 | LoopWithTooManyJumpStatements: 94 | active: true 95 | LibraryCodeMustSpecifyReturnType: 96 | active: true 97 | excludes: "**/*.kt" 98 | includes: "**/detekt-api/src/main/**/api/*.kt" 99 | MaxLineLength: 100 | excludes: "**/test/**,**/*.Test.kt,**/*.Spec.kt" 101 | excludeCommentStatements: true 102 | MagicNumber: 103 | ignoreHashCodeFunction: true 104 | ignorePropertyDeclaration: true 105 | ignoreAnnotation: true 106 | ignoreEnums: true 107 | ignoreNumbers: '-1,0,1,2,4,8,16,31,32,64,100,1000' 108 | MayBeConst: 109 | active: true 110 | NestedClassesVisibility: 111 | active: true 112 | ProtectedMemberInFinalClass: 113 | active: true 114 | SpacingBetweenPackageAndImports: 115 | active: true 116 | UnnecessaryAbstractClass: 117 | active: true 118 | UnnecessaryInheritance: 119 | active: true 120 | UnusedPrivateClass: 121 | active: true 122 | UnusedPrivateMember: 123 | active: true 124 | allowedNames: "(_|ignored|expected)" 125 | UselessCallOnNotNull: 126 | active: true 127 | UtilityClassWithPublicConstructor: 128 | active: true -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/DexNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dex.ClassData 20 | import com.android.dex.Dex 21 | import com.dedx.dex.struct.type.TypeBox 22 | import com.dedx.tools.Configuration 23 | import com.dedx.tools.EmptyConfiguration 24 | import com.google.common.flogger.FluentLogger 25 | import java.io.File 26 | 27 | class DexNode private constructor(val dex: Dex, private val config: Configuration) { 28 | 29 | val classes = ArrayList() 30 | val clsMap = HashMap() 31 | 32 | companion object { 33 | private final val logger = FluentLogger.forEnclosingClass() 34 | val NO_INDEX: Int = -1 35 | @JvmStatic fun create(filePath: String, config: Configuration = EmptyConfiguration): DexNode? { 36 | val dexFile = File(filePath) 37 | if (dexFile.exists() && dexFile.isFile()) { 38 | val dex = Dex(dexFile) 39 | return DexNode(dex, config) 40 | } 41 | return null 42 | } 43 | 44 | @JvmStatic fun create(bytes: ByteArray, config: Configuration = EmptyConfiguration) = 45 | DexNode(Dex(bytes), config) 46 | } 47 | 48 | fun loadClass() { 49 | InfoStorage.clear() 50 | for (clsDef in dex.classDefs()) { 51 | var clsData: ClassData? = null 52 | if (clsDef.classDataOffset != 0) { 53 | clsData = dex.readClassData(clsDef) 54 | } 55 | val clsNode = ClassNode.ClassNodeFactory(config) 56 | .setDexNode(this) 57 | .setClassDef(clsDef) 58 | .setClassData(clsData) 59 | .create() 60 | ?.load() ?: continue 61 | classes.add(clsNode) 62 | clsMap[clsNode.clsInfo] = clsNode 63 | } 64 | } 65 | 66 | fun getString(index: Int) = dex.strings()[index] 67 | 68 | fun getMethodId(index: Int) = dex.methodIds()[index] 69 | 70 | fun getFieldId(index: Int) = dex.fieldIds()[index] 71 | 72 | fun getProtoId(index: Int) = dex.protoIds()[index] 73 | 74 | fun getType(index: Int) = TypeBox.create(getString(dex.typeIds()[index])) 75 | 76 | fun getTypeList(offset: Int): List { 77 | val paramList = dex.readTypeList(offset) 78 | val results = ArrayList(paramList.types.size) 79 | for (i in paramList.types) { 80 | results.add(getType(i.toInt())) 81 | } 82 | return results 83 | } 84 | 85 | fun readCode(mth: ClassData.Method) = dex.readCode(mth) 86 | 87 | fun openSection(offset: Int) = dex.open(offset) 88 | 89 | fun getClass(name: String): ClassNode? { 90 | for (entry in clsMap) { 91 | if (entry.key.equals(name)) { 92 | return entry.value 93 | } 94 | } 95 | return null 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/type/TypeBox.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct.type 18 | 19 | import java.lang.RuntimeException 20 | 21 | class TypeBox private constructor(val type: Any) { 22 | 23 | companion object { 24 | fun create(type: ObjectType) = TypeBox(type) 25 | 26 | fun create(typeName: String) = when (typeName[0]) { 27 | 'L' -> TypeBox(getType(BasicType.OBJECT, typeName) ?: throw RuntimeException("unknown type $typeName")) 28 | '[' -> TypeBox(getType(BasicType.ARRAY, typeName) ?: throw RuntimeException("unknown type $typeName")) 29 | else -> { 30 | val type = BasicType.get(typeName[0]) ?: throw RuntimeException("unknown type $typeName") 31 | TypeBox(type) 32 | } 33 | } 34 | 35 | fun cleanObjectName(obj: String): String { 36 | if (obj[0] == 'L' && obj[obj.length - 1] == ';') { 37 | return obj.substring(1, obj.length - 1).replace('/', '.') 38 | } 39 | return obj 40 | } 41 | 42 | fun getType(type: BasicType, name: String): Any? { 43 | if (type >= BasicType.VOID && type <= BasicType.DOUBLE) { 44 | return type 45 | } 46 | if (type == BasicType.OBJECT) { 47 | return ObjectType(cleanObjectName(name)) 48 | } 49 | if (type == BasicType.ARRAY) { 50 | return ArrayType(create(name.substring(1))) 51 | } 52 | return null 53 | } 54 | } 55 | 56 | fun getAsBasicType() = when (type is BasicType) { 57 | true -> type 58 | false -> null 59 | } 60 | 61 | fun getAsObjectType() = when (type is ObjectType) { 62 | true -> type 63 | false -> null 64 | } 65 | 66 | fun getAsArrayType() = when (type is ArrayType) { 67 | true -> type 68 | false -> null 69 | } 70 | 71 | fun descriptor() = when (type::class) { 72 | BasicType::class -> getAsBasicType()!!.descriptor() 73 | ObjectType::class -> getAsObjectType()!!.descriptor() 74 | ArrayType::class -> getAsArrayType()!!.descriptor() 75 | else -> "" 76 | } 77 | 78 | fun nameWithSlash() = when (type::class) { 79 | BasicType::class -> descriptor() 80 | ObjectType::class -> getAsObjectType()!!.nameWithSlash() 81 | ArrayType::class -> getAsArrayType()!!.nameWithSlash() 82 | else -> "" 83 | } 84 | 85 | override fun hashCode(): Int { 86 | return type.hashCode() 87 | } 88 | 89 | override fun toString(): String { 90 | return type.toString() 91 | } 92 | 93 | override fun equals(other: Any?): Boolean { 94 | if (other == null || other !is TypeBox) { 95 | return false 96 | } 97 | if (getAsBasicType() != null) { 98 | return getAsBasicType() == other.getAsBasicType() 99 | } 100 | if (getAsObjectType() != null) { 101 | return getAsObjectType() == other.getAsObjectType() 102 | } 103 | if (getAsArrayType() != null) { 104 | return getAsArrayType() == other.getAsArrayType() 105 | } 106 | return false 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/AttrNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.dedx.dex.struct.type.TypeBox 20 | import java.util.Collections 21 | import kotlin.collections.ArrayList 22 | import org.objectweb.asm.Label 23 | 24 | object Enc { 25 | const val ENC_LABEL = -4 26 | const val ENC_TRY_ENTRY = -2 27 | 28 | const val ENC_EMPTY = -1 29 | 30 | const val ENC_BYTE = 0x00 31 | const val ENC_SHORT = 0x02 32 | const val ENC_CHAR = 0x03 33 | const val ENC_INT = 0x04 34 | const val ENC_LONG = 0x06 35 | const val ENC_FLOAT = 0x10 36 | const val ENC_DOUBLE = 0x11 37 | const val ENC_STRING = 0x17 38 | const val ENC_TYPE = 0x18 39 | const val ENC_FIELD = 0x19 40 | const val ENC_ENUM = 0x1B 41 | const val ENC_METHOD = 0x1A 42 | const val ENC_ARRAY = 0x1C 43 | const val ENC_ANNOTATION = 0x1D 44 | const val ENC_NULL = 0x1E 45 | const val ENC_BOOLEAN = 0x1F 46 | } 47 | 48 | enum class AttrKey { 49 | LABEL, 50 | TRY_ENTRY, 51 | LINENUMBER, 52 | SOURCE_FILE, 53 | NAME, 54 | 55 | ANNOTATION, 56 | MTH_PARAMETERS_ANNOTATION, 57 | CONST; 58 | } 59 | 60 | open class AttrValue(val mark: Int, open val value: Any?) { 61 | companion object { 62 | val Empty = AttrValue(-1, null) 63 | } 64 | 65 | override fun toString(): String { 66 | return value.toString() 67 | } 68 | 69 | fun getAsInt() = when (mark) { 70 | Enc.ENC_INT -> value as Int 71 | else -> null 72 | } 73 | 74 | fun getAsString() = when (mark) { 75 | Enc.ENC_STRING -> value as String 76 | else -> null 77 | } 78 | 79 | fun getAsType() = when (mark) { 80 | Enc.ENC_TYPE -> value as TypeBox 81 | else -> null 82 | } 83 | 84 | fun getAsAnnotation() = when (mark) { 85 | Enc.ENC_ANNOTATION -> value as Annotation 86 | else -> null 87 | } 88 | 89 | fun getAsTryEntry() = when (mark) { 90 | Enc.ENC_TRY_ENTRY -> value as TryCatchBlock 91 | else -> null 92 | } 93 | 94 | fun getAsAttrValueList() = if (this is AttrValueList) this else null 95 | } 96 | 97 | open class AttrValueList( 98 | override val value: List = ArrayList() 99 | ) : AttrValue(Enc.ENC_ARRAY, value), Iterable { 100 | companion object { 101 | val EMPTY = AttrValueList(Collections.emptyList()) 102 | } 103 | override fun toString(): String { 104 | val strBuilder = StringBuilder("[") 105 | for (v in super.value as List) { 106 | strBuilder.append("$v ") 107 | } 108 | strBuilder.deleteCharAt(strBuilder.length - 1) 109 | strBuilder.append("]") 110 | return strBuilder.toString() 111 | } 112 | 113 | override fun iterator() = value.iterator() 114 | } 115 | 116 | class AttrValueLabel(override val value: Label) : AttrValue(Enc.ENC_LABEL, value) { 117 | companion object { 118 | val EMPTY = AttrValueLabel(Label()) 119 | } 120 | 121 | override fun toString(): String { 122 | return value.toString() 123 | } 124 | } 125 | 126 | interface AttrNode { 127 | val attributes: MutableMap 128 | 129 | fun getValue(key: AttrKey) = attributes[key] 130 | 131 | fun setValue(key: AttrKey, value: AttrValue) { 132 | attributes[key] = value 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/parser/AnnotationsParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.parser 18 | 19 | import com.android.dex.Dex 20 | import com.dedx.dex.struct.Annotation 21 | import com.dedx.dex.struct.AttrKey 22 | import com.dedx.dex.struct.AttrValue 23 | import com.dedx.dex.struct.AttrValueList 24 | import com.dedx.dex.struct.ClassNode 25 | import com.dedx.dex.struct.DexNode 26 | import com.dedx.dex.struct.Enc 27 | import com.dedx.dex.struct.Visibility 28 | import com.dedx.utils.DecodeException 29 | import com.google.common.flogger.FluentLogger 30 | 31 | class AnnotationsParser(val dex: DexNode, val cls: ClassNode) { 32 | 33 | companion object { 34 | private val logger = FluentLogger.forEnclosingClass() 35 | 36 | private val VISIBILITIES = enumValues() 37 | 38 | fun readAnnotation(dex: DexNode, section: Dex.Section, readVisibility: Boolean): Annotation { 39 | val parser = EncValueParser(dex, section) 40 | var visibility: Visibility? = null 41 | if (readVisibility) { 42 | val v = section.readByte().toUByte() 43 | visibility = VISIBILITIES[v.toInt()] 44 | } 45 | val typeIndex = section.readUleb128() 46 | val size = section.readUleb128() 47 | val values = LinkedHashMap(size) 48 | for (i in 0 until size) { 49 | val name = dex.getString(section.readUleb128()) 50 | values[name] = parser.parseValue() 51 | } 52 | val type = dex.getType(typeIndex) 53 | val annotation = Annotation(visibility, type, values) 54 | if (type.getAsObjectType() == null) { 55 | throw DecodeException("Incorrect type for annotation: $annotation") 56 | } 57 | return annotation 58 | } 59 | } 60 | 61 | fun parse(offset: Int) { 62 | val section = dex.dex.open(offset) 63 | 64 | val classAnnotationOffset = section.readInt() 65 | val fieldsCount = section.readInt() 66 | val annotatedMethodCount = section.readInt() 67 | val annotationParametersCount = section.readInt() 68 | 69 | if (classAnnotationOffset != 0) { 70 | cls.setValue(AttrKey.ANNOTATION, readAnnotationSet(classAnnotationOffset)) 71 | } 72 | 73 | for (i in 0 until fieldsCount) { 74 | val fieldNode = cls.searchFieldById(section.readInt()) 75 | if (fieldNode?.setValue(AttrKey.ANNOTATION, readAnnotationSet(section.readInt())) == null) { 76 | logger.atWarning().log("Not find [${cls.clsInfo.fullName}] field " + 77 | "to add annotation ${readAnnotationSet(section.readInt())}") 78 | } 79 | } 80 | 81 | for (i in 0 until annotatedMethodCount) { 82 | val methodNode = cls.searchMethodById(section.readInt()) 83 | if (methodNode?.setValue(AttrKey.ANNOTATION, readAnnotationSet(section.readInt())) == null) { 84 | logger.atWarning().log("Not find [${cls.clsInfo.fullName}] method " + 85 | "to add annotation ${readAnnotationSet(section.readInt())}") 86 | } 87 | } 88 | 89 | for (i in 0 until annotationParametersCount) { 90 | val methodNode = cls.searchMethodById(section.readInt()) 91 | val ss = dex.openSection(section.readInt()) 92 | val size = ss.readInt() 93 | val annotationList = ArrayList() 94 | for (index in 0 until size) { 95 | annotationList.add(readAnnotationSet(ss.readInt())) 96 | } 97 | if (methodNode?.setValue(AttrKey.MTH_PARAMETERS_ANNOTATION, AttrValueList(annotationList)) == null) { 98 | logger.atWarning().log("Not find [${cls.clsInfo.fullName}] method " + 99 | "to add parameters annotation ${AttrValueList(annotationList)}") 100 | } 101 | } 102 | } 103 | 104 | private fun readAnnotationSet(offset: Int): AttrValueList { 105 | if (offset == 0) { 106 | return AttrValueList.EMPTY 107 | } 108 | val section = dex.dex.open(offset) 109 | val size = section.readInt() 110 | if (size == 0) { 111 | return AttrValueList.EMPTY 112 | } 113 | val list = ArrayList(size) 114 | for (i in 0 until size) { 115 | val anSection = dex.dex.open(section.readInt()) 116 | val annotation = readAnnotation(dex, anSection, true) 117 | list.add(AttrValue(Enc.ENC_ANNOTATION, annotation)) 118 | } 119 | return AttrValueList(list) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/ClassTransformer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.android.dx.rop.code.AccessFlags 20 | import com.dedx.dex.struct.AttrKey 21 | import com.dedx.dex.struct.ClassInfo 22 | import com.dedx.dex.struct.ClassNode 23 | import com.dedx.dex.struct.FieldNode 24 | import com.dedx.tools.Configuration 25 | import com.dedx.tools.EmptyConfiguration 26 | import com.google.common.flogger.FluentLogger 27 | import java.io.File 28 | import java.io.FileOutputStream 29 | import org.objectweb.asm.AnnotationVisitor 30 | import org.objectweb.asm.ClassWriter 31 | import org.objectweb.asm.FieldVisitor 32 | import org.objectweb.asm.Opcodes.V1_8 33 | 34 | class ClassTransformer( 35 | private val clsNode: ClassNode, 36 | val config: Configuration = EmptyConfiguration, 37 | private val filePath: String = "" 38 | ) { 39 | val classWriter = ClassWriter(1) 40 | lateinit var fieldVisitor: FieldVisitor 41 | lateinit var annotationVisitor: AnnotationVisitor 42 | private val sourceFile = clsNode.getSourceFile() 43 | 44 | private var success = 0 45 | private var failed = 0 46 | 47 | companion object { 48 | private val logger = FluentLogger.forEnclosingClass() 49 | } 50 | 51 | fun visitClass() = apply { 52 | classWriter.visit(V1_8, 53 | if (!clsNode.isInterface()) clsNode.accFlags or AccessFlags.ACC_SUPER else clsNode.accFlags, 54 | clsNode.clsInfo.fullName.replace('.', '/'), 55 | null, 56 | if (clsNode.hasSuperClass()) clsNode.superClassNameWithSlash() else ClassInfo.ROOT_CLASS_NAME, 57 | if (clsNode.interfaces.isNotEmpty()) Array(clsNode.interfaces.size) { i -> 58 | clsNode.interfaces[i].nameWithSlash() 59 | } else null) 60 | 61 | visitClassAnnotation() 62 | visitField() 63 | visitMethod() 64 | classWriter.visitSource(sourceFile, null) 65 | classWriter.visitEnd() 66 | config.addSuccess(success).addFailed(failed) 67 | logger.atInfo().log("[${clsNode.clsInfo} end] Success/Failed $success/$failed") 68 | } 69 | 70 | private fun visitMethod() { 71 | clsNode.methods.forEach { 72 | if (MethodTransformer(it, this).visitMethodAnnotation().visitMethodBody()) { 73 | success++ 74 | } else { 75 | failed++ 76 | } 77 | } 78 | } 79 | 80 | private fun visitField() { 81 | val annotationVisit = fun (fn: FieldNode, fv: FieldVisitor) { 82 | val annoList = fn.attributes[AttrKey.ANNOTATION]?.getAsAttrValueList() ?: return 83 | for (value in annoList) { 84 | val annoClazz = value.getAsAnnotation() ?: continue 85 | annotationVisitor = fv.visitAnnotation(annoClazz.type.descriptor(), annoClazz.hasVisibility()) 86 | for (annotationValue in annoClazz.values) { 87 | annotationVisitor.visit(annotationValue.key, annotationValue.value.value) 88 | } 89 | annotationVisitor.visitEnd() 90 | } 91 | } 92 | clsNode.fields.forEach { field -> 93 | run { 94 | fieldVisitor = classWriter.visitField(field.accFlags, field.fieldInfo.name, field.fieldInfo.type.descriptor(), null, null) 95 | annotationVisit(field, fieldVisitor) 96 | fieldVisitor.visitEnd() 97 | } 98 | } 99 | } 100 | 101 | private fun visitClassAnnotation() { 102 | val annoList = clsNode.attributes[AttrKey.ANNOTATION]?.getAsAttrValueList() ?: return 103 | for (value in annoList) { 104 | val annoClazz = value.getAsAnnotation() ?: continue 105 | annotationVisitor = classWriter.visitAnnotation(annoClazz.type.descriptor(), annoClazz.hasVisibility()) 106 | for (annotationValue in annoClazz.values) { 107 | try { 108 | annotationVisitor.visit(annotationValue.key, annotationValue.value.value) 109 | } catch (e: IllegalArgumentException) { 110 | logger.atWarning().withCause(e).log(logInfo()) 111 | } 112 | } 113 | annotationVisitor.visitEnd() 114 | } 115 | } 116 | 117 | private fun logInfo() = "${clsNode.clsInfo}" 118 | 119 | fun dump(): String { 120 | FileOutputStream(File(filePath)).use { w -> 121 | w.write(classWriter.toByteArray()) 122 | } 123 | logger.atInfo().log("Dump ${clsNode.clsInfo.fullName} to $filePath") 124 | return filePath 125 | } 126 | 127 | fun toFile(path: String) = FileOutputStream(File(path)).write(classWriter.toByteArray()) 128 | } 129 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/parser/ValueParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.parser 18 | 19 | import com.android.dex.Dex 20 | import com.android.dex.Leb128 21 | import com.dedx.dex.struct.AttrKey 22 | import com.dedx.dex.struct.AttrValue 23 | import com.dedx.dex.struct.DexNode 24 | import com.dedx.dex.struct.Enc.ENC_ANNOTATION 25 | import com.dedx.dex.struct.Enc.ENC_ARRAY 26 | import com.dedx.dex.struct.Enc.ENC_BOOLEAN 27 | import com.dedx.dex.struct.Enc.ENC_BYTE 28 | import com.dedx.dex.struct.Enc.ENC_CHAR 29 | import com.dedx.dex.struct.Enc.ENC_DOUBLE 30 | import com.dedx.dex.struct.Enc.ENC_ENUM 31 | import com.dedx.dex.struct.Enc.ENC_FIELD 32 | import com.dedx.dex.struct.Enc.ENC_FLOAT 33 | import com.dedx.dex.struct.Enc.ENC_INT 34 | import com.dedx.dex.struct.Enc.ENC_LONG 35 | import com.dedx.dex.struct.Enc.ENC_METHOD 36 | import com.dedx.dex.struct.Enc.ENC_NULL 37 | import com.dedx.dex.struct.Enc.ENC_SHORT 38 | import com.dedx.dex.struct.Enc.ENC_STRING 39 | import com.dedx.dex.struct.Enc.ENC_TYPE 40 | import com.dedx.dex.struct.FieldInfo 41 | import com.dedx.dex.struct.FieldNode 42 | import com.dedx.dex.struct.MethodInfo 43 | import com.dedx.utils.DecodeException 44 | 45 | open class EncValueParser(val dex: DexNode, val section: Dex.Section) { 46 | 47 | fun parseValue(): AttrValue { 48 | val argType = readByte() 49 | val type = argType and 0x1F 50 | val arg = (argType and 0xE0) shr 5 51 | val size = arg + 1 52 | when (type) { 53 | ENC_NULL -> return AttrValue(ENC_NULL, null) 54 | 55 | ENC_BOOLEAN -> return AttrValue(ENC_BYTE, arg == 1) 56 | ENC_BYTE -> return AttrValue(ENC_BOOLEAN, section.readByte()) 57 | 58 | ENC_SHORT -> return AttrValue(ENC_SHORT, parseNumber(size, true).toShort()) 59 | ENC_CHAR -> return AttrValue(ENC_CHAR, parseUnsignedInt(size).toChar()) 60 | ENC_INT -> return AttrValue(ENC_INT, parseNumber(size, true).toInt()) 61 | ENC_LONG -> return AttrValue(ENC_LONG, parseNumber(size, true)) 62 | 63 | ENC_FLOAT -> return AttrValue(ENC_FLOAT, Float.fromBits(parseNumber(size, false, 4).toInt())) 64 | ENC_DOUBLE -> return AttrValue(ENC_DOUBLE, Double.fromBits(parseNumber(size, false, 8))) 65 | 66 | ENC_STRING -> return AttrValue(ENC_STRING, dex.getString(parseUnsignedInt(size))) 67 | 68 | ENC_TYPE -> return AttrValue(ENC_TYPE, dex.getType(parseUnsignedInt(size))) 69 | 70 | ENC_METHOD -> return AttrValue(ENC_METHOD, MethodInfo.fromDex(dex, parseUnsignedInt(size))) 71 | 72 | ENC_FIELD -> return AttrValue(ENC_FIELD, FieldInfo.fromDex(dex, parseUnsignedInt(size))) 73 | ENC_ENUM -> return AttrValue(ENC_ENUM, FieldInfo.fromDex(dex, parseUnsignedInt(size))) 74 | 75 | ENC_ARRAY -> { 76 | val count = Leb128.readUnsignedLeb128(section) 77 | val values: MutableList = ArrayList(count) 78 | for (i in 0 until count) { 79 | values.add(parseValue()) 80 | } 81 | return AttrValue(ENC_ARRAY, values) 82 | } 83 | ENC_ANNOTATION -> return AttrValue(ENC_ANNOTATION, AnnotationsParser.readAnnotation(dex, section, false)) 84 | 85 | else -> throw DecodeException("Unknown encode value type: ${type.toString(16)}") 86 | } 87 | } 88 | 89 | private fun parseNumber(byteCount: Int, isSignExtended: Boolean, fillOnRight: Int): Long { 90 | var result: Long = 0 91 | var last: Long = 0 92 | for (i in 0 until byteCount) { 93 | last = readByte().toLong() 94 | result = result or (last shl (i * 8)) 95 | } 96 | if (fillOnRight != 0) { 97 | for (i in byteCount until fillOnRight) { 98 | result = result shl 8 99 | } 100 | } else { 101 | if (isSignExtended && ((last and 0x80L) != 0L)) { 102 | for (i in byteCount until 8) { 103 | result = result or (0xFFL shl (i * 8)) 104 | } 105 | } 106 | } 107 | return result 108 | } 109 | 110 | private fun parseNumber(byteCount: Int, isSignExtended: Boolean) = parseNumber(byteCount, isSignExtended, 0) 111 | 112 | private fun parseUnsignedInt(byteCount: Int) = parseNumber(byteCount, false, 0).toInt() 113 | 114 | private fun readByte(): Int = section.readByte().toUByte().toInt() 115 | } 116 | 117 | class StaticValuesParser(dex: DexNode, section: Dex.Section) : EncValueParser(dex, section) { 118 | fun processFields(fields: List): Int { 119 | val count = Leb128.readUnsignedLeb128(section) 120 | for (i in 0 until count) { 121 | val value = parseValue() 122 | if (i < fields.size) { 123 | fields[i].setValue(AttrKey.CONST, value) 124 | } 125 | } 126 | return count 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/kotlin/com/dedx/dex/pass/DataFlowAnalysisPassTest.kt: -------------------------------------------------------------------------------- 1 | package com.dedx.dex.pass 2 | 3 | import com.dedx.dex.struct.DexNode 4 | import com.dedx.transform.BasicBlock 5 | import com.dedx.transform.ClassTransformer 6 | import com.dedx.transform.MethodTransformer 7 | import org.junit.Assert.* 8 | import org.junit.Test 9 | import org.objectweb.asm.Label 10 | 11 | class DataFlowAnalysisPassTest { 12 | @Test 13 | fun testUseDef() { 14 | val bytes = DataFlowAnalysisPassTest::class.java.getResource("/DataFlowTest.dex").openStream().readBytes() 15 | val dexNode = DexNode.create(bytes) 16 | dexNode.loadClass() 17 | val testClazz = dexNode.getClass("com.test.DataFlowTest") 18 | assertFalse(testClazz == null) 19 | val methodNode = testClazz?.searchMethodByProto("runFirst", "(Lcom/test/DataFlowTest;IIII)I") 20 | assertTrue(methodNode != null) 21 | val transformer = MethodTransformer(methodNode!!, ClassTransformer(testClazz, "")) 22 | CFGBuildPass.visit(transformer) 23 | val mthInfo = DataFlowAnalysisPass.visit(transformer) 24 | for (block in transformer.blockMap.values) { 25 | val blockInfo = mthInfo.blockInfos[block] 26 | when (block.instList[0].cursor) { 27 | 0 -> { 28 | assertEquals(blockInfo!!.use._array[0], 128.toByte()) 29 | assertEquals(blockInfo.def._array[0], 8.toByte()) 30 | } 31 | 3 -> { 32 | assertEquals(blockInfo!!.use._array[0], 160.toByte()) 33 | assertEquals(blockInfo.def._array[0], 1.toByte()) 34 | } 35 | 9 -> { 36 | assertEquals(blockInfo!!.use._array[1], 3.toByte()) 37 | assertEquals(blockInfo.def._array[0], 1.toByte()) 38 | } 39 | 0x14 -> { 40 | assertEquals(blockInfo!!.use._array[0], 161.toByte()) 41 | assertEquals(blockInfo!!.use._array[1], 3.toByte()) 42 | assertEquals(blockInfo.def._array[0], 6.toByte()) 43 | } 44 | } 45 | } 46 | } 47 | 48 | @Test 49 | fun testLiveness() { 50 | 51 | val bytes = DataFlowAnalysisPassTest::class.java.getResource("/Empty.dex").openStream().readBytes() 52 | val dexNode = DexNode.create(bytes) 53 | dexNode.loadClass() 54 | val testClazz = dexNode.getClass("com.test.Empty") 55 | val methodNode = testClazz?.searchMethodByProto("", "()V") 56 | val transformer = MethodTransformer(methodNode!!, ClassTransformer(testClazz, "")) 57 | val bb1 = BasicBlock(Label(), ArrayList(), ArrayList(), 1) 58 | val bb2 = BasicBlock(Label(), ArrayList(), ArrayList(), 2) 59 | val bb3 = BasicBlock(Label(), ArrayList(), ArrayList(), 3) 60 | val bb4 = BasicBlock(Label(), ArrayList(), ArrayList(), 4) 61 | val exit = BasicBlock(Label(), ArrayList(), ArrayList(), 5) 62 | 63 | bb1.successor.add(bb2) 64 | bb2.predecessor.add(bb1) 65 | bb2.predecessor.add(bb4) 66 | bb2.successor.add(bb3) 67 | bb2.successor.add(bb4) 68 | bb3.predecessor.add(bb2) 69 | bb3.successor.add(bb4) 70 | bb4.predecessor.add(bb2) 71 | bb4.predecessor.add(bb3) 72 | bb4.successor.add(bb2) 73 | bb4.successor.add(exit) 74 | exit.predecessor.add(bb4) 75 | 76 | val bb1Info = DataFlowBlockInfo(bb1, 8) 77 | val bb2Info = DataFlowBlockInfo(bb2, 8) 78 | val bb3Info = DataFlowBlockInfo(bb3, 8) 79 | val bb4Info = DataFlowBlockInfo(bb4, 8) 80 | val exitInfo = DataFlowBlockInfo(exit, 8) 81 | 82 | bb1Info.use.setTrue(0) 83 | bb1Info.use.setTrue(1) 84 | bb1Info.use.setTrue(2) 85 | bb1Info.def.setTrue(3) 86 | bb1Info.def.setTrue(4) 87 | bb1Info.def.setTrue(5) 88 | 89 | bb2Info.use.setTrue(3) 90 | bb2Info.use.setTrue(4) 91 | 92 | bb3Info.use.setTrue(6) 93 | bb3Info.def.setTrue(5) 94 | 95 | bb4Info.use.setTrue(7) 96 | bb4Info.def.setTrue(3) 97 | 98 | val blockInfoMap = HashMap() 99 | blockInfoMap[bb1] = bb1Info 100 | blockInfoMap[bb2] = bb2Info 101 | blockInfoMap[bb3] = bb3Info 102 | blockInfoMap[bb4] = bb4Info 103 | blockInfoMap[exit] = exitInfo 104 | val mthInfo = object : DataFlowMethodInfo(transformer) { 105 | override fun getBlocks(): MutableSet { 106 | return blockInfoMap.keys 107 | } 108 | 109 | override fun getBlockInfos(): MutableCollection { 110 | return blockInfoMap.values 111 | } 112 | 113 | override fun getBlockInfo(block: BasicBlock): DataFlowBlockInfo? { 114 | return blockInfoMap[block] 115 | } 116 | 117 | override fun isExit(block: BasicBlock): Boolean { 118 | if (block === exit) { 119 | return true 120 | } 121 | return false 122 | } 123 | } 124 | 125 | DataFlowAnalysisPass.livenessAnalyzer(mthInfo) 126 | 127 | assertEquals(bb1Info.liveOut.toString(), "{3 4 6 7}") 128 | assertEquals(bb1Info.liveIn.toString(), "{0 1 2 6 7}") 129 | assertEquals(bb2Info.liveOut.toString(), "{4 6 7}") 130 | assertEquals(bb2Info.liveIn.toString(), "{3 4 6 7}") 131 | assertEquals(bb3Info.liveOut.toString(), "{4 6 7}") 132 | assertEquals(bb3Info.liveIn.toString(), "{4 6 7}") 133 | assertEquals(bb4Info.liveOut.toString(), "{3 4 6 7}") 134 | assertEquals(bb4Info.liveIn.toString(), "{4 6 7}") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/StackFrame.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.android.dx.io.Opcodes 20 | import com.dedx.dex.struct.InstNode 21 | import com.dedx.dex.struct.MethodNode 22 | import com.dedx.dex.struct.type.BasicType 23 | import com.dedx.dex.struct.type.TypeBox 24 | import com.dedx.utils.DecodeException 25 | import java.util.TreeSet 26 | 27 | enum class SlotType { 28 | BOOLEAN, 29 | CHAR, 30 | BYTE, 31 | SHORT, 32 | INT, 33 | LONG, 34 | FLOAT, 35 | DOUBLE, 36 | OBJECT, 37 | ARRAY; 38 | 39 | companion object { 40 | const val isValue = 1 41 | const val isRawBits = 2 42 | const val isStrIndex = 4 43 | const val isTypeIndex = 8 44 | 45 | fun convert(type: TypeBox): SlotType? { 46 | if (type.getAsObjectType() != null) { 47 | return OBJECT 48 | } 49 | if (type.getAsArrayType() != null) { 50 | return ARRAY 51 | } 52 | when (type.getAsBasicType()) { 53 | BasicType.BOOLEAN -> return BOOLEAN 54 | BasicType.CHAR -> return CHAR 55 | BasicType.BYTE -> return BYTE 56 | BasicType.SHORT -> return SHORT 57 | BasicType.INT -> return INT 58 | BasicType.LONG -> return LONG 59 | BasicType.FLOAT -> return FLOAT 60 | BasicType.DOUBLE -> return DOUBLE 61 | BasicType.VOID -> return null 62 | else -> throw DecodeException("Convert type $type to SlotType error.") 63 | } 64 | } 65 | } 66 | } 67 | 68 | class StackFrame(val cursor: Int) { 69 | 70 | companion object { 71 | private val InstFrames = HashMap() 72 | 73 | fun getFrameOrPut(index: Int) = InstFrames.getOrPut(index) { 74 | return@getOrPut StackFrame(index) 75 | } 76 | 77 | fun getFrameOrExcept(index: Int) = InstFrames.get(index) ?: throw DecodeException("No frame in $index") 78 | 79 | fun initInstFrame(mthNode: MethodNode) { 80 | InstFrames.clear() 81 | var prevInst: InstNode? = null 82 | for (curr in mthNode.codeList) { 83 | if (curr == null) continue 84 | if (prevInst == null) { 85 | getFrameOrPut(curr.cursor) 86 | } else { 87 | getFrameOrPut(curr.cursor).preFrames.add(prevInst.cursor) 88 | } 89 | prevInst = skipJumper(curr) 90 | } 91 | } 92 | 93 | private fun skipJumper(instNode: InstNode) = when (instNode.instruction.opcode) { 94 | in Opcodes.RETURN_VOID..Opcodes.RETURN_OBJECT, 95 | in Opcodes.THROW..Opcodes.GOTO_32 -> null 96 | else -> instNode 97 | } 98 | } 99 | 100 | private val symbolTable = HashMap() 101 | val preFrames = TreeSet() 102 | 103 | fun init() = apply { 104 | symbolTable.clear() 105 | } 106 | 107 | fun addPreFrame(index: Int) { 108 | preFrames.add(index) 109 | } 110 | 111 | // merge prev inst's frame 112 | fun merge(): StackFrame { 113 | if (preFrames.isNotEmpty()) { 114 | for (frame in preFrames) { 115 | merge(frame) 116 | } 117 | } 118 | return this 119 | } 120 | 121 | private fun merge(frame: Int) { 122 | val other = getFrameOrPut(frame) 123 | for (entry in other.symbolTable) { 124 | if (symbolTable[entry.key] == null) { 125 | symbolTable[entry.key] = entry.value 126 | } else { 127 | val value = symbolTable[entry.key] 128 | if (value != entry.value) { 129 | // TODO type confliction 130 | break 131 | } 132 | } 133 | } 134 | } 135 | 136 | fun pushElement(index: Int, type: TypeBox) { 137 | setSlot(index, SlotType.convert(type)!!) 138 | } 139 | 140 | fun setSlot(index: Int, type: SlotType) { 141 | symbolTable[index] = SymbolInfo.create(type, SymIdentifier.SymbolType) 142 | } 143 | 144 | fun setSlotArray(index: Int, vararg types: SlotType) { 145 | symbolTable[index] = SymbolArrayInfo(types) 146 | } 147 | 148 | fun setSlotLiteral(index: Int, literal: Long, whichType: SymIdentifier) { 149 | symbolTable[index] = SymbolInfo.create(literal, whichType) 150 | } 151 | 152 | fun setSlotWide(index: Int, type: SlotType) { 153 | symbolTable[index] = SymbolInfo.create(type, SymIdentifier.SymbolType) 154 | symbolTable[index + 1] = symbolTable[index]!! 155 | } 156 | 157 | fun getSlot(index: Int) = symbolTable[index] 158 | 159 | fun getArrayTypeExpect(slot: Int): SymbolArrayInfo { 160 | val info = symbolTable[slot] ?: throw DecodeException("slot <$slot> is empty") 161 | if (info !is SymbolArrayInfo) { 162 | throw DecodeException("slot <$slot> is not array type object") 163 | } 164 | return info 165 | } 166 | 167 | fun isStringIndex(slot: Int): Boolean = symbolTable[slot]?.isStringIndex() ?: false 168 | 169 | fun isTypeIndex(slot: Int): Boolean = symbolTable[slot]?.isSymbolTypeIndex() ?: false 170 | 171 | override fun toString(): String { 172 | val outString = StringBuilder("{\n") 173 | for (entry in symbolTable) { 174 | outString.append("\t${entry.key} : ${entry.value}\n") 175 | } 176 | outString.append("}\n") 177 | return outString.toString() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/utils/BitArray.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.utils 18 | 19 | import java.lang.Exception 20 | import kotlin.collections.ArrayList 21 | import kotlin.math.max 22 | 23 | class BitArrayOutIndex : Exception { 24 | constructor(message: String) : super(message) 25 | 26 | constructor(message: String, cause: Throwable) : super(message, cause) 27 | } 28 | 29 | class BitArray(val size: Int) { 30 | val full = size % Byte.SIZE_BITS == 0 31 | val _array = Array((size / Byte.SIZE_BITS) + when (full) { 32 | true -> 0 33 | false -> 1 34 | }) { 0 } 35 | 36 | companion object { 37 | fun sub(b1: BitArray, b2: BitArray): BitArray { 38 | val result = BitArray(b1.size) 39 | for (offset in b1._array.indices) { 40 | if (offset < b2._array.size) { 41 | val tmp = b1._array[offset].toInt() and b2._array[offset].toInt() 42 | result._array[offset] = (b1._array[offset].toInt() - tmp).toByte() 43 | } else { 44 | result._array[offset] = b1._array[offset] 45 | } 46 | } 47 | return result 48 | } 49 | 50 | fun and(b1: BitArray, b2: BitArray): BitArray { 51 | val size = max(b1.size, b2.size) 52 | val result = BitArray(size) 53 | val offset = max(b1._array.size, b2._array.size) 54 | for (i in 0 until offset) { 55 | if ((i < b1._array.size) && (i < b2._array.size)) { 56 | result._array[i] = (b1._array[i].toInt() and b2._array[i].toInt()).toByte() 57 | } else { 58 | result._array[i] = 0 59 | } 60 | } 61 | return result 62 | } 63 | 64 | fun merge(b1: BitArray, b2: BitArray): BitArray { 65 | val dataList = ArrayList() 66 | dataList.add(b1) 67 | dataList.add(b2) 68 | return merge(dataList) 69 | } 70 | 71 | fun merge(dataList: List): BitArray { 72 | if (dataList.size == 1) { 73 | return dataList[0] 74 | } 75 | var result = BitArray(0) 76 | for (data in dataList) { 77 | result = BitArray.or(result, data) 78 | } 79 | return result 80 | } 81 | 82 | fun or(b1: BitArray, b2: BitArray): BitArray { 83 | val size = max(b1.size, b2.size) 84 | val result = BitArray(size) 85 | val offset = max(b1._array.size, b2._array.size) 86 | for (i in 0 until offset) { 87 | if ((i < b1._array.size) && (i < b2._array.size)) { 88 | result._array[i] = (b1._array[i].toInt() or b2._array[i].toInt()).toByte() 89 | } else { 90 | if (i < b1._array.size) { 91 | result._array[i] = b1._array[i] 92 | } else { 93 | result._array[i] = b2._array[i] 94 | } 95 | } 96 | } 97 | return result 98 | } 99 | } 100 | 101 | fun setTrue(index: Int) { 102 | if (index > size) { 103 | throw BitArrayOutIndex("Set $index out of length $size") 104 | } 105 | val base = index / Byte.SIZE_BITS 106 | val offset = index % Byte.SIZE_BITS 107 | val tmp = _array[base].toInt() 108 | _array[base] = (tmp or (0x1 shl offset)).toByte() 109 | } 110 | 111 | fun setFalse(index: Int) { 112 | if (index > size) { 113 | throw BitArrayOutIndex("Set $index out of length $size") 114 | } 115 | val base = index / Byte.SIZE_BITS 116 | val offset = index % Byte.SIZE_BITS 117 | val tmp = _array[base].toInt() 118 | _array[base] = (tmp and (0xFF xor (0x1 shl offset))).toByte() 119 | } 120 | 121 | fun get(index: Int): Int { 122 | if (index > size) { 123 | throw BitArrayOutIndex("Set $index out of length $size") 124 | } 125 | val base = index / Byte.SIZE_BITS 126 | val offset = index % Byte.SIZE_BITS 127 | val tmp = 0x1 shl offset 128 | if ((_array[base].toInt() and tmp) == 0) { 129 | return 0 130 | } 131 | return 1 132 | } 133 | 134 | fun sub(other: BitArray) = BitArray.sub(this, other) 135 | 136 | fun and(other: BitArray) = BitArray.and(this, other) 137 | 138 | fun or(other: BitArray) = BitArray.or(this, other) 139 | 140 | fun clear() { 141 | for (i in 0 until size) { 142 | _array[i] = 0 143 | } 144 | } 145 | 146 | fun equal(data: Array): Boolean { 147 | if (_array.size != data.size) { 148 | return false 149 | } else { 150 | for (offset in _array.indices) { 151 | if (_array[offset] != data[offset]) { 152 | return false 153 | } 154 | } 155 | } 156 | return true 157 | } 158 | 159 | override fun hashCode(): Int { 160 | return _array.hashCode() 161 | } 162 | 163 | override fun toString(): String { 164 | if (size == 0) { 165 | return "{}" 166 | } 167 | val indexList = ArrayList() 168 | for (offset in _array.indices) { 169 | for (i in 0..7) { 170 | val tmp = 1 shl i 171 | if ((_array[offset].toInt() and tmp) != 0) { 172 | if (offset == 0) { 173 | indexList.add(i) 174 | } else { 175 | indexList.add(offset * 8 + i) 176 | } 177 | } 178 | } 179 | } 180 | return "{${indexList.joinToString(" ")}}" 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/SymbolInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.dedx.dex.struct.DexNode 20 | import com.dedx.utils.DecodeException 21 | import java.lang.StringBuilder 22 | 23 | // const val SymbolType = 1 24 | // const val SymbolTypeIndex = 2 25 | // const val StringIndex = 3 26 | // const val NumberLiteral = 4 27 | 28 | enum class SymIdentifier { 29 | SymbolType, 30 | SymbolTypeIndex, 31 | StringIndex, 32 | NumberLiteral 33 | } 34 | 35 | open class SymbolInfo protected constructor(private val symbolIdentifier: SymIdentifier) { 36 | 37 | companion object { 38 | fun create(type: SlotType, indent: SymIdentifier) = SymbolInfo(indent).setType(type) 39 | 40 | fun create(number: Long, indent: SymIdentifier) = SymbolInfo(indent).setNumber(number) 41 | 42 | fun equal(left: SymbolInfo, right: SymbolInfo): Boolean { 43 | if (left.symbolIdentifier != right.symbolIdentifier) { 44 | return false 45 | } 46 | if (left::type.isInitialized && right::type.isInitialized && left.type == right.type) { 47 | return true 48 | } 49 | if (left.number != null && left.number == right.number) { 50 | return true 51 | } 52 | return false 53 | } 54 | } 55 | 56 | private lateinit var sourceDex: DexNode 57 | private lateinit var type: SlotType 58 | private var number: Long? = null 59 | 60 | public fun setSourceDex(dex: DexNode) = also { 61 | sourceDex = dex 62 | } 63 | 64 | protected fun setType(t: SlotType) = also { 65 | it.type = t 66 | } 67 | 68 | protected fun setNumber(n: Long) = also { 69 | it.number = n 70 | } 71 | 72 | fun isSymbolType() = symbolIdentifier == SymIdentifier.SymbolType 73 | fun isSymbolTypeIndex() = symbolIdentifier == SymIdentifier.SymbolTypeIndex 74 | fun isStringIndex() = symbolIdentifier == SymIdentifier.StringIndex 75 | fun isNumberLiteral() = symbolIdentifier == SymIdentifier.NumberLiteral 76 | 77 | fun getType() = if (this::type.isInitialized && symbolIdentifier == SymIdentifier.SymbolType) 78 | type else throw DecodeException("This symbol not type") 79 | 80 | fun getType(dex: DexNode) = if (this::type.isInitialized && symbolIdentifier == SymIdentifier.SymbolTypeIndex) 81 | dex.getType(getNumber().toInt()) else throw DecodeException("This symbol not class type") 82 | 83 | fun getTypeOrNull() = if (this::type.isInitialized && symbolIdentifier == SymIdentifier.SymbolType) type else null 84 | 85 | fun getTypeOrNull(dex: DexNode) = if (this::type.isInitialized && symbolIdentifier == SymIdentifier.SymbolTypeIndex) 86 | dex.getType(getNumber().toInt()) else null 87 | 88 | fun getString(dex: DexNode): String = if (symbolIdentifier == SymIdentifier.StringIndex && number != null) 89 | dex.getString(getNumber().toInt()) else throw DecodeException("This symbol not string") 90 | 91 | fun getString(transformer: InstTransformer): String = if (symbolIdentifier == SymIdentifier.StringIndex && number != null) 92 | transformer.string(getNumber().toInt()) else throw DecodeException("This symbol not string") 93 | 94 | fun getStringOrNull(dex: DexNode) = if (symbolIdentifier == SymIdentifier.StringIndex && number != null) 95 | dex.getString(getNumber().toInt()) else null 96 | 97 | fun getStringOrNull(transformer: InstTransformer) = if (symbolIdentifier == SymIdentifier.StringIndex && number != null) 98 | transformer.string(getNumber().toInt()) else null 99 | 100 | // get number as number literal 101 | fun getNumberLiteral() = if (symbolIdentifier == SymIdentifier.NumberLiteral && number != null) number 102 | else throw DecodeException("This symbol not number literal") 103 | 104 | fun getNumberLiteralOrNull() = if (symbolIdentifier == SymIdentifier.NumberLiteral && number != null) number else null 105 | 106 | // get number as index or number literal 107 | fun getNumber() = if (symbolIdentifier > SymIdentifier.SymbolType && number != null) number as Long else throw DecodeException("This symbol has no number") 108 | 109 | fun getNumberOrNull() = if (symbolIdentifier > SymIdentifier.SymbolType && number != null) number else null 110 | 111 | fun toString(dex: DexNode): String { 112 | val outString = StringBuilder("<$symbolIdentifier> ") 113 | when (symbolIdentifier) { 114 | SymIdentifier.SymbolType -> outString.append("$type") 115 | SymIdentifier.SymbolTypeIndex -> outString.append("${getType(dex)}") 116 | SymIdentifier.StringIndex -> outString.append(getString(dex)) 117 | SymIdentifier.NumberLiteral -> outString.append("${getNumberLiteral()}") 118 | } 119 | return outString.toString() 120 | } 121 | 122 | override fun toString(): String { 123 | if (this::sourceDex.isInitialized) { 124 | return toString(sourceDex) 125 | } 126 | if (symbolIdentifier == SymIdentifier.SymbolType) { 127 | return "<${SymIdentifier.SymbolType}> $type" 128 | } else { 129 | return "<$symbolIdentifier> $number" 130 | } 131 | } 132 | } 133 | 134 | class SymbolArrayInfo() : SymbolInfo(SymIdentifier.SymbolType) { 135 | init { 136 | setType(SlotType.ARRAY) 137 | } 138 | 139 | private val subTypeList = ArrayList() 140 | 141 | constructor(types: Array) : this() { 142 | subTypeList.addAll(types) 143 | } 144 | 145 | fun hasSubType() = subTypeList.isNotEmpty() 146 | 147 | fun addSubType(type: SlotType) = subTypeList.add(type) 148 | 149 | fun lastType() = subTypeList.last() 150 | 151 | fun firstType() = subTypeList.first() 152 | 153 | fun subSize() = subTypeList.size 154 | 155 | fun getType(index: Int) = subTypeList[index] 156 | } 157 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/tools/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.tools 18 | 19 | import com.dedx.dex.struct.ClassNode 20 | import com.dedx.dex.struct.DexNode 21 | import com.dedx.transform.ClassTransformer 22 | import com.dedx.utils.DecodeException 23 | import com.google.common.flogger.FluentLogger 24 | import java.io.File 25 | import java.io.FileReader 26 | import java.util.logging.ConsoleHandler 27 | import java.util.logging.FileHandler 28 | import java.util.logging.Level 29 | import java.util.logging.Logger 30 | import kotlin.system.exitProcess 31 | import org.apache.commons.cli.DefaultParser 32 | import org.apache.commons.cli.HelpFormatter 33 | import org.apache.commons.cli.Option 34 | import org.apache.commons.cli.Options 35 | 36 | fun configLog() { 37 | val root = Logger.getLogger("") 38 | root.level = if (CmdConfiguration.debug) Level.FINEST else Level.INFO 39 | root.handlers.forEach { root.removeHandler(it) } 40 | root.addHandler(CmdConfiguration.logFile?.let { 41 | FileHandler(it) 42 | } ?: ConsoleHandler()) 43 | } 44 | 45 | fun createCmdTable(): Options { 46 | val help = Option.builder("h") 47 | .longOpt("help").desc("Print help message").build() 48 | val version = Option.builder("v") 49 | .longOpt("version").desc("Print version").build() 50 | val output = Option.builder("o") 51 | .longOpt("output") 52 | .hasArg().argName("dirname").desc("Specify output dirname").build() 53 | val optLevel = Option.builder() 54 | .longOpt("opt").hasArg().argName("[fast|normal]") // |optimize 55 | .desc("Specify optimization level").build() 56 | val logFile = Option.builder().longOpt("log").desc("Specify log file").hasArg().build() 57 | val debug = Option.builder("g").longOpt("debug").desc("Print debug info").build() 58 | val classes = Option.builder().longOpt("classes") 59 | .hasArg().argName("[class_name | @file]") 60 | .desc("Specify classes which to load (default all)").build() 61 | val blackList = Option.builder().longOpt("black-classes") 62 | .hasArg().argName("[class_name | @file]") 63 | .desc("Specify classes which not to load (default none)").build() 64 | 65 | val optTable = Options() 66 | optTable.addOption(help) 67 | optTable.addOption(version) 68 | optTable.addOption(output) 69 | optTable.addOption(optLevel) 70 | optTable.addOption(logFile) 71 | optTable.addOption(debug) 72 | optTable.addOption(classes) 73 | optTable.addOption(blackList) 74 | return optTable 75 | } 76 | 77 | fun configFromOptions(args: Array, optTable: Options) { 78 | val parser = DefaultParser() 79 | try { 80 | val cmdTable = parser.parse(optTable, args) 81 | if (cmdTable.hasOption("v") || cmdTable.hasOption("version")) { 82 | exitProcess(0) 83 | } 84 | if (cmdTable.hasOption("h") || cmdTable.hasOption("help")) { 85 | HelpFormatter().printHelp("command [options] ", optTable) 86 | exitProcess(0) 87 | } 88 | if (cmdTable.hasOption("o") || cmdTable.hasOption("output")) { 89 | CmdConfiguration.outDir = cmdTable.getOptionValue("o") ?: cmdTable.getOptionValue("output") 90 | } 91 | if (cmdTable.hasOption("opt")) { 92 | CmdConfiguration.optLevel = when (cmdTable.getOptionValue("opt")) { 93 | "fast" -> Configuration.NormalFast 94 | "normal" -> Configuration.NormalOpt 95 | // "optimize" -> Configuration.Optimized 96 | else -> { 97 | throw RuntimeException("Invalid parameter for 'opt'") 98 | } 99 | } 100 | } 101 | if (cmdTable.hasOption("log")) { 102 | CmdConfiguration.logFile = cmdTable.getOptionValue("log") 103 | } 104 | if (cmdTable.hasOption("g") || cmdTable.hasOption("debug")) { 105 | CmdConfiguration.debug = true 106 | } 107 | if (cmdTable.hasOption("classes")) { 108 | parseClasses(CmdConfiguration.classesList, cmdTable.getOptionValue("classes")) 109 | } 110 | if (cmdTable.hasOption("black-classes")) { 111 | parseClasses(CmdConfiguration.blackClasses, cmdTable.getOptionValue("black-classes")) 112 | } 113 | CmdConfiguration.dexFiles = cmdTable.argList 114 | configLog() 115 | } catch (e: Exception) { 116 | System.err.println("Argument error: ${e.message}") 117 | exitProcess(1) 118 | } 119 | } 120 | 121 | fun parseClasses(classesList: MutableList, value: String) { 122 | if (value.startsWith("@")) { 123 | FileReader(value.substring(1)).useLines { 124 | classesList.addAll(it) 125 | } 126 | } else { 127 | value.split(';').forEach { 128 | classesList.add(it) 129 | } 130 | } 131 | } 132 | 133 | fun compileClass(classNode: ClassNode) { 134 | val path = CmdConfiguration.outDir + File.separator + classNode.clsInfo.className() + ".class" 135 | if (!File(path).parentFile.exists()) { 136 | File(path).parentFile.mkdirs() 137 | } 138 | val transformer = ClassTransformer(classNode, CmdConfiguration, path) 139 | transformer.visitClass().dump() 140 | } 141 | 142 | fun runMain(): Int { 143 | try { 144 | for (dexFile in CmdConfiguration.dexFiles) { 145 | val dexNode = DexNode.create(dexFile, CmdConfiguration) 146 | ?: throw DecodeException("Create dex node failed: $dexFile") 147 | dexNode.loadClass() 148 | for (classNode in dexNode.classes) { 149 | compileClass(classNode) 150 | } 151 | } 152 | FluentLogger.forEnclosingClass().atInfo().log("All method success/fail: " + 153 | "${CmdConfiguration.successNum}/${CmdConfiguration.failedNum}") 154 | return 0 155 | } catch (e: Throwable) { 156 | FluentLogger.forEnclosingClass().atInfo().log("All method success/fail: " + 157 | "${CmdConfiguration.successNum}/${CmdConfiguration.failedNum}") 158 | e.printStackTrace() 159 | } 160 | return 1 161 | } 162 | 163 | fun main(args: Array) { 164 | configFromOptions(args, createCmdTable()) 165 | exitProcess(runMain()) 166 | } 167 | -------------------------------------------------------------------------------- /src/main/groovy/com/dedx/test/ScriptMain.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.test 18 | 19 | import com.dedx.dex.struct.DexNode 20 | import com.dedx.transform.ClassTransformer 21 | import com.dedx.tools.Configuration 22 | 23 | import java.nio.file.Files 24 | import java.nio.file.Paths 25 | import java.util.jar.JarOutputStream 26 | import java.util.zip.ZipEntry 27 | 28 | String compileDexOneClass(String tag, String dexFile) throws Exception { 29 | println "Compile: $dexFile" 30 | def config = new Configuration() 31 | def dexNode = DexNode.create(dexFile, config) 32 | println 'Load class...' 33 | dexNode.loadClass() 34 | def baseClass = dexNode.getClass(tag) 35 | def filePath = dexFile.replace(".dex", ".class") 36 | println "Dump to: $filePath" 37 | def compiler = new ClassTransformer(baseClass, config, filePath) 38 | return compiler.visitClass().dump() 39 | } 40 | 41 | String createClassPath(String parent, String className) { 42 | return String.join(File.separator, parent, className) + ".class" 43 | } 44 | 45 | String createJar(String parent, List files) { 46 | def jarPath = String.join(File.separator, parent, "classes.jar") 47 | def jos = new JarOutputStream(new FileOutputStream(jarPath)) 48 | if (!parent.endsWith("/")) { 49 | parent = parent + "/" 50 | } 51 | for (file in files) { 52 | def className = file.replace(parent, "") 53 | def zipEntry = new ZipEntry(className) 54 | def classFile = new File(file) 55 | zipEntry.setTime(classFile.lastModified()) 56 | jos.putNextEntry(zipEntry) 57 | def fis = new FileInputStream(classFile) 58 | def buffer = new byte[64 * 1024] 59 | int length = fis.read(buffer) 60 | while (length != -1) { 61 | jos.write(buffer, 0, length) 62 | length = fis.read(buffer) 63 | } 64 | fis.close() 65 | jos.closeEntry() 66 | } 67 | jos.close() 68 | return jarPath 69 | } 70 | 71 | String compileDexMultipClass(String tag, String dexFile) throws Exception { 72 | println "Compile: $dexFile" 73 | def parent = dexFile.replace(".dex", "") 74 | new File(parent).mkdirs() 75 | def config = new Configuration() 76 | def dexNode = DexNode.create(dexFile, config) 77 | dexNode.loadClass() 78 | def files = new ArrayList() 79 | for (clazz in dexNode.classes) { 80 | def classPath = createClassPath(parent, clazz.clsInfo.className()) 81 | def compiler = new ClassTransformer(clazz, config, classPath) 82 | files.add(compiler.visitClass().dump()) 83 | } 84 | return createJar(parent, files) 85 | } 86 | 87 | boolean assertResult(tag, String classFile) { 88 | switch (tag) { 89 | case "com.test.Base": return BaseAssert(classFile) 90 | case "AClass" : return AClassAssert(classFile) 91 | case "BClass" : return BClassAssert(classFile) 92 | case "CClass" : return CClassAssert(classFile) 93 | case "TryCatchTest" : return TryCatchAssert(classFile) 94 | case "ArrayTest" : return ArrayTestAssert(classFile) 95 | default: return true 96 | } 97 | } 98 | 99 | def ArrayTestAssert(String classFile) { 100 | def loader = new DynamicClassLoader() 101 | def arrayTestClass = loader.defineClass("ArrayTest", Files.readAllBytes(Paths.get(classFile))) 102 | def arrayString = arrayTestClass.getMethod("arrayString") 103 | assert arrayString.invoke(null).equals("ArrayTest".length()) 104 | return true 105 | } 106 | 107 | def BaseAssert(String classFile) { 108 | def loader = new DynamicClassLoader() 109 | def baseClass = loader.defineClass("com.test.Base", Files.readAllBytes(Paths.get(classFile))) 110 | def addInt = baseClass.getMethod("addInt", int.class, int.class) 111 | def getName = baseClass.getMethod("getName") 112 | assert addInt.invoke(null, 1, 2) == 3 113 | assert getName.invoke(baseClass.newInstance()).equals("com.test.Base") 114 | return true 115 | } 116 | 117 | def AClassAssert(String classFile) { 118 | def urls = new URL[1] 119 | urls[0] = new File(classFile).toURI().toURL() 120 | def loader = new URLClassLoader(urls, this.class.classLoader) 121 | def aClass = loader.loadClass("AClass") 122 | def getName = aClass.getMethod("getName") 123 | assert getName.invoke(aClass.newInstance()).equals("AClass") 124 | def test = aClass.getMethod("test") 125 | assert test.invoke(null) 126 | return true 127 | } 128 | 129 | /* TODO fix */ 130 | def BClassAssert(String classFile) { 131 | def urls = new URL[1] 132 | urls[0] = new File(classFile).toURI().toURL() 133 | def loader = new URLClassLoader(urls, this.class.classLoader) 134 | def bClass = loader.loadClass("BClass") 135 | def descript = bClass.getMethod("descript") 136 | for (con in bClass.getConstructors()) { 137 | println con.genericParameterTypes 138 | } 139 | def constructor = bClass.getConstructor(String.class, boolean.class) 140 | def instance = constructor.newInstance("BClass", true) 141 | assert descript.invoke(instance) 142 | return true 143 | } 144 | /************/ 145 | 146 | def CClassAssert(String classFile) { 147 | def loader = new DynamicClassLoader() 148 | def baseClass = loader.defineClass("CClass", Files.readAllBytes(Paths.get(classFile))) 149 | def toString = baseClass.getMethod("toString") 150 | def instance = baseClass.getConstructor(int.class).newInstance(1) 151 | assert toString.invoke(instance).equals("CClass1") 152 | return true 153 | } 154 | 155 | def TryCatchAssert(String classFile) { 156 | def loader = new DynamicClassLoader() 157 | def tryCatchClass = loader.defineClass("TryCatchTest", Files.readAllBytes(Paths.get(classFile))) 158 | def toString = tryCatchClass.getMethod("throwTest") 159 | assert toString.invoke(null).equals("Throw runtime") 160 | return true 161 | } 162 | 163 | static void main(String[] args) { 164 | println 'ScriptMain Groovy Script.' 165 | def dexFilesToClass = ['com.test.Base' : 'Base.dex', 'CClass' : 'CClass.dex', 'TryCatchTest' : 'TryCatchTest.dex', 166 | 'ArrayTest' : 'ArrayTest.dex'] 167 | def dexFilesToJar = ['AClass' : 'AClasses.dex'] 168 | try { 169 | for (entry in dexFilesToClass) { 170 | assert assertResult(entry.key, compileDexOneClass(entry.key, "${args[0]}/${entry.value}")) 171 | } 172 | for (entry in dexFilesToJar) { 173 | assert assertResult(entry.key, compileDexMultipClass(entry.key, "${args[0]}/${entry.value}")) 174 | } 175 | } catch(Exception e) { 176 | e.printStackTrace() 177 | } 178 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/Ops.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | interface StackOp { 20 | val size: Int 21 | } 22 | 23 | class StackPop(override val size: Int) : StackOp 24 | class StackPush(override val size: Int) : StackOp 25 | 26 | enum class Ops constructor(val opcode: Int, vararg ops: StackOp) { 27 | NOP(0), // visitInsn 28 | ACONST_NULL(1, StackPush(1)), // - 29 | ICONST_M1(2, StackPush(1)), // - 30 | ICONST_0(3, StackPush(1)), // - 31 | ICONST_1(4, StackPush(1)), // - 32 | ICONST_2(5, StackPush(1)), // - 33 | ICONST_3(6, StackPush(1)), // - 34 | ICONST_4(7, StackPush(1)), // - 35 | ICONST_5(8, StackPush(1)), // - 36 | LCONST_0(9, StackPush(2)), // - 37 | LCONST_1(10, StackPush(2)), // - 38 | FCONST_0(11, StackPush(1)), // - 39 | FCONST_1(12, StackPush(1)), // - 40 | FCONST_2(13, StackPush(1)), // - 41 | DCONST_0(14, StackPush(2)), // - 42 | DCONST_1(15, StackPush(2)), // - 43 | BIPUSH(16, StackPush(1)), // visitIntInsn 44 | SIPUSH(17, StackPush(1)), // - 45 | LDC(18, StackPush(1)), // visitLdcInsn 46 | ILOAD(21, StackPush(1)), // visitVarInsn 47 | LLOAD(22, StackPush(2)), // - 48 | FLOAD(23, StackPush(1)), // - 49 | DLOAD(24, StackPush(2)), // - 50 | ALOAD(25, StackPush(1)), // - 51 | IALOAD(46, StackPush(1)), // visitInsn 52 | LALOAD(47, StackPush(1)), // - 53 | FALOAD(48, StackPush(1)), // - 54 | DALOAD(49, StackPush(1)), // - 55 | AALOAD(50, StackPush(1)), // - 56 | BALOAD(51, StackPush(1)), // - 57 | CALOAD(52, StackPush(1)), // - 58 | SALOAD(53, StackPush(1)), // - 59 | ISTORE(54, StackPop(1)), // visitVarInsn 60 | LSTORE(55, StackPop(2)), // - 61 | FSTORE(56, StackPop(1)), // - 62 | DSTORE(57, StackPop(2)), // - 63 | ASTORE(58, StackPop(1)), // - 64 | IASTORE(79, StackPop(1)), // visitInsn 65 | LASTORE(80, StackPop(1)), // - 66 | FASTORE(81, StackPop(1)), // - 67 | DASTORE(82, StackPop(1)), // - 68 | AASTORE(83, StackPop(1)), // - 69 | BASTORE(84, StackPop(1)), // - 70 | CASTORE(85, StackPop(1)), // - 71 | SASTORE(86, StackPop(1)), // - 72 | POP(87, StackPop(1)), // - 73 | POP2(88, StackPop(2)), // - 74 | DUP(89, StackPush(1)), // - 75 | DUP_X1(90), // - 76 | DUP_X2(91), // - 77 | DUP2(92, StackPush(2)), // - 78 | DUP2_X1(93), // - 79 | DUP2_X2(94), // - 80 | SWAP(95), // - 81 | IADD(96, StackPop(2), StackPush(1)), // - 82 | LADD(97, StackPop(4), StackPush(2)), // - 83 | FADD(98, StackPop(2), StackPush(1)), // - 84 | DADD(99, StackPop(4), StackPush(2)), // - 85 | ISUB(100, StackPop(2), StackPush(1)), // - 86 | LSUB(101, StackPop(4), StackPush(2)), // - 87 | FSUB(102, StackPop(2), StackPush(1)), // - 88 | DSUB(103, StackPop(4), StackPush(2)), // - 89 | IMUL(104, StackPop(2), StackPush(1)), // - 90 | LMUL(105, StackPop(4), StackPush(2)), // - 91 | FMUL(106, StackPop(2), StackPush(1)), // - 92 | DMUL(107, StackPop(4), StackPush(2)), // - 93 | IDIV(108, StackPop(2), StackPush(1)), // - 94 | LDIV(109, StackPop(4), StackPush(2)), // - 95 | FDIV(110, StackPop(2), StackPush(1)), // - 96 | DDIV(111, StackPop(4), StackPush(2)), // - 97 | IREM(112, StackPop(2), StackPush(1)), // - 98 | LREM(113, StackPop(4), StackPush(2)), // - 99 | FREM(114, StackPop(2), StackPush(1)), // - 100 | DREM(115, StackPop(4), StackPush(2)), // - 101 | INEG(116), // - 102 | LNEG(117), // - 103 | FNEG(118), // - 104 | DNEG(119), // - 105 | ISHL(120, StackPop(2), StackPush(1)), // - 106 | LSHL(121, StackPop(4), StackPush(2)), // - 107 | ISHR(122, StackPop(2), StackPush(1)), // - 108 | LSHR(123, StackPop(4), StackPush(2)), // - 109 | IUSHR(124, StackPop(2), StackPush(1)), // - 110 | LUSHR(125, StackPop(4), StackPush(2)), // - 111 | IAND(126, StackPop(2), StackPush(1)), // - 112 | LAND(127, StackPop(4), StackPush(2)), // - 113 | IOR(128, StackPop(2), StackPush(1)), // - 114 | LOR(129, StackPop(4), StackPush(2)), // - 115 | IXOR(130, StackPop(2), StackPush(1)), // - 116 | LXOR(131, StackPop(4), StackPush(2)), // - 117 | IINC(132), // visitIincInsn 118 | I2L(133, StackPush(1)), // visitInsn 119 | I2F(134), // - 120 | I2D(135, StackPush(1)), // - 121 | L2I(136, StackPop(1)), // - 122 | L2F(137, StackPop(1)), // - 123 | L2D(138), // - 124 | F2I(139), // - 125 | F2L(140, StackPush(1)), // - 126 | F2D(141, StackPush(1)), // - 127 | D2I(142, StackPop(1)), // - 128 | D2L(143), // - 129 | D2F(144, StackPop(1)), // - 130 | I2B(145), // - 131 | I2C(146), // - 132 | I2S(147), // - 133 | LCMP(148, StackPop(4), StackPush(1)), // - 134 | FCMPL(149, StackPop(2), StackPush(1)), // - 135 | FCMPG(150, StackPop(2), StackPush(1)), // - 136 | DCMPL(151, StackPop(4), StackPush(1)), // - 137 | DCMPG(152, StackPop(4), StackPush(1)), // - 138 | IFEQ(153, StackPop(1)), // visitJumpInsn 139 | IFNE(154, StackPop(1)), // - 140 | IFLT(155, StackPop(1)), // - 141 | IFGE(156, StackPop(1)), // - 142 | IFGT(157, StackPop(1)), // - 143 | IFLE(158, StackPop(1)), // - 144 | IF_ICMPEQ(159, StackPop(2)), // - 145 | IF_ICMPNE(160, StackPop(2)), // - 146 | IF_ICMPLT(161, StackPop(2)), // - 147 | IF_ICMPGE(162, StackPop(2)), // - 148 | IF_ICMPGT(163, StackPop(2)), // - 149 | IF_ICMPLE(164, StackPop(2)), // - 150 | IF_ACMPEQ(165, StackPop(2)), // - 151 | IF_ACMPNE(166, StackPop(2)), // - 152 | GOTO(167), // - 153 | JSR(168, StackPush(1)), // - 154 | RET(169, StackPush(2)), // visitVarInsn 155 | TABLESWITCH(170, StackPop(1)), // visiTableSwitchInsn 156 | LOOKUPSWITCH(171, StackPop(1)), // visitLookupSwitch 157 | IRETURN(172, StackPop(1)), // visitInsn 158 | LRETURN(173, StackPop(2)), // - 159 | FRETURN(174, StackPop(1)), // - 160 | DRETURN(175, StackPop(2)), // - 161 | ARETURN(176, StackPop(1)), // - 162 | RETURN(177), // - 163 | GETSTATIC(178, StackPush(1)), // visitFieldInsn 164 | PUTSTATIC(179, StackPop(1)), // - 165 | GETFIELD(180), // - 166 | PUTFIELD(181, StackPop(2)), // - 167 | INVOKEVIRTUAL(182), // visitMethodInsn 168 | INVOKESPECIAL(183), // - 169 | INVOKESTATIC(184), // - 170 | INVOKEINTERFACE(185), // - 171 | INVOKEDYNAMIC(186), // visitInvokeDynamicInsn 172 | NEW(187, StackPush(1)), // visitTypeInsn 173 | NEWARRAY(188), // visitIntInsn 174 | ANEWARRAY(189), // visitTypeInsn 175 | ARRAYLENGTH(190), // visitInsn 176 | ATHROW(191), // - 177 | CHECKCAST(192), // visitTypeInsn 178 | INSTANCEOF(193), // - 179 | MONITORENTER(194, StackPop(1)), // visitInsn 180 | MONITOREXIT(195, StackPop(1)), // - 181 | MULTIANEWARRAY(197), // visitMultiANewArrayInsn 182 | IFNULL(198, StackPop(1)), // visitJumpInsn 183 | IFNONNULL(199, StackPop(1)); // - 184 | 185 | val stackOps = ops 186 | companion object { 187 | fun toString(value: Int): String { 188 | return values().find { it.opcode == value }.toString() 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/ClassNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dex.ClassData 20 | import com.android.dex.ClassDef 21 | import com.dedx.dex.parser.AnnotationsParser 22 | import com.dedx.dex.parser.StaticValuesParser 23 | import com.dedx.dex.struct.type.ObjectType 24 | import com.dedx.tools.Configuration 25 | import com.dedx.utils.DecodeException 26 | import com.google.common.flogger.FluentLogger 27 | import java.lang.Exception 28 | import java.util.Collections 29 | import kotlin.collections.ArrayList 30 | import kotlin.collections.HashMap 31 | 32 | class ClassNode private constructor( 33 | val parent: DexNode, 34 | private val clsDef: ClassDef, 35 | val clsInfo: ClassInfo, 36 | clsData: ClassData? 37 | ) : AccessInfo, AttrNode { 38 | 39 | override val attributes: MutableMap = HashMap() 40 | override val accFlags: Int = clsDef.accessFlags 41 | val interfaces: Array = Array(clsDef.interfaces.size) { i -> 42 | parent.getType(clsDef.interfaces[i].toInt()).getAsObjectType() ?: throw DecodeException("Interface type error.") 43 | } 44 | val methods: List = addMethods(this, clsData) 45 | val fields: List = addFields(this, clsDef, clsData) 46 | 47 | private val mthCache: MutableMap = HashMap(methods.size) 48 | private val fieldCache: MutableMap = HashMap(fields.size) 49 | 50 | init { 51 | for (mth in methods) { 52 | mthCache[mth.mthInfo] = mth 53 | } 54 | for (field in fields) { 55 | fieldCache[field.fieldInfo] = field 56 | } 57 | 58 | val offset = clsDef.annotationsOffset 59 | if (offset != 0) { 60 | try { 61 | AnnotationsParser(parent, this).parse(offset) 62 | } catch (e: Exception) { 63 | logger.atWarning().withCause(e).log() 64 | } 65 | } 66 | } 67 | 68 | class ClassNodeFactory(private val configuration: Configuration) { 69 | 70 | private lateinit var parent: DexNode 71 | private lateinit var clsDef: ClassDef 72 | private var clsData: ClassData? = null 73 | 74 | fun setDexNode(parent: DexNode) = apply { 75 | this.parent = parent 76 | } 77 | 78 | fun setClassDef(cls: ClassDef) = apply { 79 | clsDef = cls 80 | } 81 | 82 | fun setClassData(clsData: ClassData?) = apply { 83 | this.clsData = clsData 84 | } 85 | 86 | fun create(): ClassNode? { 87 | val clsInfo: ClassInfo = ClassInfo.fromDex(parent, clsDef.typeIndex) 88 | if (configuration.blackClasses.isNotEmpty() && 89 | configuration.blackClasses.find { clsInfo.fullName.startsWith(it) } != null) { 90 | logger.atInfo().log("Class skip by hit black classes list [$clsInfo]") 91 | return null 92 | } 93 | if (configuration.classesList.isNotEmpty() && 94 | configuration.classesList.find { clsInfo.fullName.startsWith(it) } == null) { 95 | logger.atInfo().log("Class skip by no hit classes list [$clsInfo]") 96 | return null 97 | } 98 | return create(parent, clsDef, clsInfo, clsData) 99 | } 100 | } 101 | 102 | companion object { 103 | private val logger = FluentLogger.forEnclosingClass() 104 | 105 | fun addMethods(parent: ClassNode, clsData: ClassData?): List { 106 | if (clsData == null) { 107 | return Collections.emptyList() 108 | } 109 | val mthsCount = clsData.directMethods.size + clsData.virtualMethods.size 110 | val methods = ArrayList(mthsCount) 111 | for (method in clsData.directMethods) { 112 | methods.add(MethodNode(parent, method, false)) 113 | } 114 | for (method in clsData.virtualMethods) { 115 | methods.add(MethodNode(parent, method, true)) 116 | } 117 | return methods 118 | } 119 | 120 | fun addFields(parent: ClassNode, cls: ClassDef, clsData: ClassData?): List { 121 | if (clsData == null) { 122 | return Collections.emptyList() 123 | } 124 | val fieldsCount = clsData.staticFields.size + clsData.instanceFields.size 125 | val fields = ArrayList(fieldsCount) 126 | for (field in clsData.staticFields) { 127 | fields.add(FieldNode.create(parent, field)) 128 | } 129 | loadStaticValues(parent, cls, fields) 130 | for (field in clsData.instanceFields) { 131 | fields.add(FieldNode.create(parent, field)) 132 | } 133 | return fields 134 | } 135 | 136 | private fun loadStaticValues(parent: ClassNode, cls: ClassDef, staticFields: List) { 137 | for (field in staticFields) { 138 | if (field.isFinal()) { 139 | field.setValue(AttrKey.CONST, AttrValue(Enc.ENC_NULL, null)) 140 | } 141 | } 142 | val offset = cls.staticValuesOffset 143 | if (offset == 0) { 144 | return 145 | } 146 | val section = parent.parent.dex.open(offset) 147 | StaticValuesParser(parent.parent, section).processFields(staticFields) 148 | ConstStorage.processConstFields(parent, staticFields) 149 | } 150 | 151 | private fun create(parent: DexNode, clsDef: ClassDef, clsInfo: ClassInfo, clsData: ClassData?) = 152 | ClassNode(parent, clsDef, clsInfo, clsData) 153 | } 154 | 155 | fun load() = apply { methods.forEach { it.load() } } 156 | 157 | fun searchField(fieldInfo: FieldInfo): FieldNode? { 158 | return fieldCache[fieldInfo] 159 | } 160 | 161 | fun searchFieldById(index: Int): FieldNode? { 162 | return searchField(FieldInfo.fromDex(parent, index)) 163 | } 164 | 165 | fun searchMethod(mthInfo: MethodInfo): MethodNode? { 166 | return mthCache[mthInfo] 167 | } 168 | 169 | fun searchMethodById(index: Int): MethodNode? { 170 | return searchMethod(MethodInfo.fromDex(parent, index)) 171 | } 172 | 173 | fun searchMethodByProto(name: String, proto: String): MethodNode? { 174 | for (entry in mthCache) { 175 | if ((entry.key.name == name) && (entry.key.parseSignature() == proto)) { 176 | return entry.value 177 | } 178 | } 179 | return null 180 | } 181 | 182 | fun superClassName() = clsInfo.parentClass?.className() 183 | 184 | fun superClassNameWithSlash() = clsInfo.parentClass?.className()?.replace('.', '/') 185 | 186 | fun hasSuperClass() = clsInfo.parentClass != null 187 | 188 | fun getSourceFile() = parent.getString(clsDef.sourceFileIndex) 189 | 190 | override fun equals(other: Any?): Boolean { 191 | if (other == null || other !is ClassNode) { 192 | return false 193 | } 194 | return clsInfo == other.clsInfo 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/parser/DebugInfoParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.parser 18 | 19 | import com.dedx.dex.struct.DexNode 20 | import com.dedx.dex.struct.InstArgNode 21 | import com.dedx.dex.struct.InstNode 22 | import com.dedx.dex.struct.LocalVarNode 23 | import com.dedx.dex.struct.MethodNode 24 | import com.dedx.utils.DecodeException 25 | import kotlin.math.min 26 | 27 | class DebugInfoParser(val mth: MethodNode, val insnList: Array, debugOffset: Int) { 28 | val dex = mth.dex() 29 | val section = dex.openSection(debugOffset) 30 | 31 | val locals = arrayOfNulls(mth.regsCount) 32 | val activeRegisters = arrayOfNulls(mth.regsCount) 33 | 34 | val resultList = ArrayList() 35 | 36 | companion object { 37 | private const val DBG_END_SEQUENCE = 0x00 38 | private const val DBG_ADVANCE_PC = 0x01 39 | private const val DBG_ADVANCE_LINE = 0x02 40 | private const val DBG_START_LOCAL = 0x03 41 | private const val DBG_START_LOCAL_EXTENDED = 0x04 42 | private const val DBG_END_LOCAL = 0x05 43 | private const val DBG_RESTART_LOCAL = 0x06 44 | private const val DBG_SET_PROLOGUE_END = 0x07 45 | private const val DBG_SET_EPILOGUE_BEGIN = 0x08 46 | private const val DBG_SET_FILE = 0x09 47 | // the smallest special opcode 48 | private const val DBG_FIRST_SPECIAL = 0x0a 49 | // the smallest line number increment 50 | private const val DBG_LINE_BASE = -4 51 | // the number of line increments represented 52 | private const val DBG_LINE_RANGE = 15 53 | } 54 | 55 | fun process(): List { 56 | var varInfoFound = false 57 | var addr: Int = 0 58 | var line = section.readUleb128() 59 | 60 | val paramsCount = section.readUleb128() 61 | val mthArgs = mth.getArguments(false) 62 | for (i in 0 until paramsCount) { 63 | val id = section.readUleb128() - 1 64 | if (id != DexNode.NO_INDEX) { 65 | val name = dex.getString(id) 66 | if (i < mthArgs.size) { 67 | val arg = mthArgs[i] 68 | val lvar = LocalVarNode.create(arg) 69 | startVar(lvar, -1) 70 | varInfoFound = true 71 | } 72 | } 73 | } 74 | 75 | for (arg in mthArgs) { 76 | val rn = arg.regNum 77 | locals[rn] = LocalVarNode.create(arg) 78 | activeRegisters[rn] = arg 79 | } 80 | 81 | addrChange(-1, 1, line) 82 | setLine(addr, line) 83 | 84 | var c = section.readByte().toUByte().toInt() and 0xFF 85 | while (c != DBG_END_SEQUENCE) { 86 | when (c) { 87 | DBG_ADVANCE_PC -> { 88 | val addrInc = section.readUleb128() 89 | val addr = addrChange(addr, addrInc, line) 90 | setLine(addr, line) 91 | } 92 | DBG_ADVANCE_LINE -> { 93 | line += section.readSleb128() 94 | } 95 | DBG_START_LOCAL -> { 96 | val regNum = section.readUleb128() 97 | val nameId = section.readUleb128() - 1 98 | val typeId = section.readUleb128() - 1 99 | val variable = LocalVarNode.create(dex, regNum, nameId, typeId, DexNode.NO_INDEX) 100 | startVar(variable, addr) 101 | varInfoFound = true 102 | } 103 | DBG_START_LOCAL_EXTENDED -> { 104 | val regNum = section.readUleb128() 105 | val nameId = section.readUleb128() - 1 106 | val typeId = section.readUleb128() - 1 107 | val signId = section.readUleb128() - 1 108 | val variable = LocalVarNode.create(dex, regNum, nameId, typeId, signId) 109 | startVar(variable, addr) 110 | varInfoFound = true 111 | } 112 | DBG_END_LOCAL -> { 113 | val regNum = section.readUleb128() 114 | val variable = locals[regNum] 115 | if (variable != null) { 116 | endVar(variable, addr) 117 | } 118 | varInfoFound = true 119 | } 120 | DBG_RESTART_LOCAL -> { 121 | restartVar(section.readUleb128(), addr) 122 | varInfoFound = true 123 | } 124 | DBG_SET_PROLOGUE_END -> {} 125 | DBG_SET_EPILOGUE_BEGIN -> {} 126 | DBG_SET_FILE -> { 127 | val idx = section.readUleb128() - 1 128 | if (idx != DexNode.NO_INDEX) { 129 | val sourceFile = dex.getString(idx) 130 | mth.setSourceFile(sourceFile) 131 | } 132 | } 133 | else -> { 134 | if (c >= DBG_FIRST_SPECIAL) { 135 | val adjustedOpcode = c - DBG_FIRST_SPECIAL 136 | val addrInc = adjustedOpcode / DBG_LINE_RANGE 137 | addr = addrChange(addr, addrInc, line) 138 | line += DBG_LINE_BASE + adjustedOpcode % DBG_LINE_RANGE 139 | setLine(addr, line) 140 | } else { 141 | throw DecodeException("") 142 | } 143 | } 144 | } 145 | c = section.readByte().toUByte().toInt() and 0xFF 146 | } 147 | 148 | if (varInfoFound) { 149 | for (variable in locals) { 150 | if (variable != null && !variable.isEnd) { 151 | endVar(variable, mth.codeSize - 1) 152 | } 153 | } 154 | } 155 | setSourceLine(addr, insnList.size, line) 156 | return resultList 157 | } 158 | 159 | private fun addrChange(addr: Int, addrInc: Int, line: Int): Int { 160 | var newAddr = addr + addrInc 161 | var maxAddr = insnList.size - 1 162 | newAddr = min(newAddr, maxAddr) 163 | setSourceLine(addr, newAddr, line) 164 | return newAddr 165 | } 166 | 167 | private fun setSourceLine(start: Int, end: Int, line: Int) { 168 | for (offset in (start + 1) until end) { 169 | setLine(offset, line) 170 | } 171 | } 172 | 173 | private fun setLine(offset: Int, line: Int) { 174 | val inst = insnList[offset] 175 | inst?.setLineNumber(line) 176 | } 177 | 178 | private fun restartVar(regNum: Int, addr: Int) { 179 | val prev = locals[regNum] 180 | if (prev != null) { 181 | endVar(prev, addr) 182 | val newVar = LocalVarNode.create(regNum, prev.name, prev.type) 183 | startVar(newVar, addr) 184 | } else { 185 | throw DecodeException("Debug info restart failed $regNum") 186 | } 187 | } 188 | 189 | private fun startVar(variable: LocalVarNode, addr: Int) { 190 | val preVar = locals[variable.regNum] 191 | if (preVar != null) { 192 | endVar(preVar, addr) 193 | } 194 | variable.start(addr) 195 | locals[variable.regNum] = variable 196 | } 197 | 198 | private fun endVar(variable: LocalVarNode, addr: Int) { 199 | if (variable.end(addr)) { 200 | resultList.add(variable) 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/struct/MethodNode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.struct 18 | 19 | import com.android.dex.ClassData 20 | import com.android.dex.Code 21 | import com.android.dx.io.OpcodeInfo 22 | import com.android.dx.io.Opcodes 23 | import com.android.dx.io.instructions.DecodedInstruction 24 | import com.android.dx.io.instructions.ShortArrayCodeInput 25 | import com.dedx.dex.struct.type.BasicType 26 | import com.dedx.dex.struct.type.TypeBox 27 | import com.dedx.dex.struct.type.isSystemCommentType 28 | import com.dedx.utils.DecodeException 29 | import java.lang.IllegalArgumentException 30 | import kotlin.collections.ArrayList 31 | import kotlin.collections.HashMap 32 | import kotlin.collections.HashSet 33 | 34 | class MethodNode(val parent: ClassNode, val mthData: ClassData.Method, val isVirtual: Boolean) : AccessInfo, AttrNode { 35 | override val accFlags = mthData.accessFlags 36 | override val attributes: MutableMap = HashMap() 37 | 38 | val mthInfo = MethodInfo.fromDex(parent.parent, mthData.methodIndex) 39 | val descriptor = mthInfo.parseSignature() 40 | val argsList = ArrayList() 41 | var thisArg: InstArgNode? = null 42 | val tryBlockList = ArrayList() 43 | val catchList = ArrayList() 44 | val sysAnnoMap = HashMap() 45 | 46 | val noCode = mthData.codeOffset == 0 47 | 48 | var regsCount: Int = 0 49 | var ins: Int = 0 50 | var outs: Int = 0 51 | var codeSize: Int = 0 52 | var debugInfoOffset: Int = 0 53 | var mthCode: Code? = null 54 | var codeList = emptyArray() 55 | 56 | fun load() { 57 | try { 58 | if (noCode) { 59 | regsCount = 0 60 | codeSize = 0 61 | initMethodTypes() 62 | return 63 | } 64 | mthCode = parent.parent.dex.readCode(mthData) 65 | val instructions = mthCode!!.instructions 66 | codeList = arrayOfNulls(instructions.size) 67 | val codeInput = ShortArrayCodeInput(instructions) 68 | try { 69 | while (codeInput.hasMore()) { 70 | val cursor = codeInput.cursor() 71 | codeList[cursor] = InstNode(cursor, decodeRawInsn(codeInput)) 72 | } 73 | } catch (e: java.lang.Exception) { 74 | e.printStackTrace() 75 | } 76 | regsCount = mthCode!!.registersSize 77 | ins = mthCode!!.insSize 78 | outs = mthCode!!.outsSize 79 | codeSize = codeList.size 80 | debugInfoOffset = mthCode!!.debugInfoOffset 81 | initMethodTypes() 82 | initTryCatches(mthCode!!) 83 | } catch (e: Exception) { 84 | e.printStackTrace() 85 | } 86 | } 87 | 88 | private fun decodeRawInsn(codeInput: ShortArrayCodeInput): DecodedInstruction { 89 | val opcodeUnit = codeInput.read() 90 | val opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit) 91 | try { 92 | return OpcodeInfo.get(opcode).format.decode(opcodeUnit, codeInput) 93 | } catch (e: IllegalArgumentException) { 94 | return OpcodeInfo.NOP.format.decode(opcodeUnit, codeInput) 95 | } 96 | } 97 | 98 | private fun initTryCatches(mthCode: Code) { 99 | val catchBlocks = mthCode.catchHandlers 100 | val tryList = mthCode.tries 101 | if (catchBlocks.isEmpty() && tryList.isEmpty()) { 102 | return 103 | } 104 | var handlerCount = 0 105 | val addrs = HashSet() 106 | for (handler in catchBlocks) { 107 | val tcBlock = TryCatchBlock() 108 | tryBlockList.add(tcBlock) 109 | for (i in handler.addresses.indices) { 110 | tcBlock.addHandler(this, handler.addresses[i], ClassInfo.fromDex(dex(), handler.typeIndexes[i])) 111 | addrs.add(handler.addresses[i]) 112 | handlerCount++ 113 | } 114 | if (handler.catchAllAddress > 0) { 115 | tcBlock.addHandler(this, handler.catchAllAddress, null) 116 | handlerCount++ 117 | } 118 | } 119 | 120 | if ((handlerCount > 0) && (handlerCount != addrs.size)) { 121 | for (outer in tryBlockList) { 122 | for (inner in tryBlockList) { 123 | if ((outer != inner) && inner.containsAllHandlers(outer)) { 124 | inner.removeSameHandlers(outer) 125 | } 126 | } 127 | } 128 | } 129 | 130 | for (exec in catchList) { 131 | // codeList[exec.addr]?.setValue() 132 | } 133 | 134 | for (oneTry in tryList) { 135 | val catchBlock = tryBlockList[oneTry.catchHandlerIndex] 136 | var offset = oneTry.startAddress 137 | val end = offset + oneTry.instructionCount - 1 138 | 139 | val tryEntry = codeList[offset] ?: throw DecodeException("Try block first instruction is null.") 140 | tryEntry.setTryEntry(catchBlock) 141 | catchBlock.instList.add(tryEntry) 142 | offset++ 143 | while ((offset <= end) && (offset >= 0)) { 144 | val insn = codeList[offset] 145 | if (insn != null) { 146 | catchBlock.instList.add(insn) 147 | } 148 | offset++ 149 | } 150 | } 151 | } 152 | 153 | private fun initMethodTypes() { 154 | var argRegOff = regsCount - ins 155 | if (!isStatic()) { 156 | thisArg = InstArgNode(argRegOff, parent.clsInfo.type) 157 | argRegOff++ 158 | argsList.add(thisArg!!) 159 | } 160 | if (noCode) { 161 | return 162 | } 163 | for (args in mthInfo.args) { 164 | if (argRegOff > regsCount) { 165 | throw DecodeException("regs count less argument count in $mthInfo") 166 | } 167 | argsList.add(InstArgNode(argRegOff, args)) 168 | if (args.getAsBasicType() == BasicType.LONG || args.getAsBasicType() == BasicType.DOUBLE) { 169 | argRegOff += 2 170 | } else { 171 | argRegOff++ 172 | } 173 | } 174 | } 175 | 176 | fun getArguments(includeThis: Boolean): List { 177 | if (includeThis && (thisArg != null)) { 178 | val result = ArrayList() 179 | result.add(thisArg!!) 180 | result.addAll(argsList) 181 | return result 182 | } 183 | return argsList 184 | } 185 | 186 | fun dex(): DexNode { 187 | return parent.parent 188 | } 189 | 190 | fun setLineNumber(num: Int) { 191 | attributes[AttrKey.LINENUMBER] = AttrValue(Enc.ENC_INT, num) 192 | } 193 | 194 | fun setSourceFile(fileName: String) { 195 | attributes[AttrKey.SOURCE_FILE] = AttrValue(Enc.ENC_STRING, fileName) 196 | } 197 | 198 | fun getSourceFile() = attributes[AttrKey.SOURCE_FILE]?.getAsString() 199 | 200 | fun getPrevInst(index: Int): InstNode? { 201 | var offset = index - 1 202 | while (offset >= 0) { 203 | if (codeList[offset] != null) { 204 | return codeList[offset] 205 | } else { 206 | offset-- 207 | } 208 | } 209 | return null 210 | } 211 | 212 | fun getNextInst(index: Int): InstNode? { 213 | var offset = index + 1 214 | while (offset < codeList.size) { 215 | if (codeList[offset] != null) { 216 | return codeList[offset] 217 | } else { 218 | offset++ 219 | } 220 | } 221 | return null 222 | } 223 | 224 | fun getInst(index: Int): InstNode? { 225 | if (index < codeList.size) { 226 | return codeList[index] 227 | } 228 | return null 229 | } 230 | 231 | fun addExceptionHandler(exceHandler: ExceptionHandler): ExceptionHandler { 232 | if (!catchList.isEmpty()) { 233 | for (e in catchList) { 234 | if (e == exceHandler) { 235 | return e 236 | } 237 | if (e.addr == exceHandler.addr) { 238 | if (e.handlerBlock == exceHandler.handlerBlock) { 239 | e.addException(exceHandler) 240 | } else { 241 | // merge different block 242 | } 243 | return e 244 | } 245 | } 246 | } 247 | catchList.add(exceHandler) 248 | return exceHandler 249 | } 250 | 251 | fun getReturnType() = mthInfo.retType 252 | 253 | fun isMain(): Boolean { 254 | if (mthInfo.name != "main") return false 255 | if (mthInfo.parseSignature() != "([Ljava/lang/String;)V") return false 256 | if (!isPublic() || !isStatic()) return false 257 | return true 258 | } 259 | 260 | override fun setValue(key: AttrKey, value: AttrValue) { 261 | if (key == AttrKey.ANNOTATION) { 262 | val addSystem = fun(anno: Annotation?) { 263 | if (anno == null) return 264 | if ((anno.visibility == Visibility.SYSTEM) && (isSystemCommentType(anno.type.getAsObjectType()!!))) { 265 | sysAnnoMap[anno.type] = anno 266 | } 267 | } 268 | if (value is AttrValueList) { 269 | for (anno in value.value) { 270 | addSystem(anno.getAsAnnotation()) 271 | } 272 | } else { 273 | addSystem(value.getAsAnnotation()) 274 | } 275 | } else { 276 | super.setValue(key, value) 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/dex/pass/DataFlowAnalysisPass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.dex.pass 18 | 19 | import com.android.dx.io.Opcodes 20 | import com.android.dx.io.instructions.DecodedInstruction 21 | import com.dedx.transform.BasicBlock 22 | import com.dedx.transform.MethodTransformer 23 | import com.dedx.utils.BitArray 24 | import com.dedx.utils.BlockEmptyException 25 | import com.dedx.utils.DataFlowAnalyzeException 26 | import java.util.Collections 27 | import java.util.stream.Collectors 28 | import kotlin.collections.ArrayList 29 | import kotlin.collections.HashMap 30 | 31 | open class DataFlowMethodInfo(val mthTransformer: MethodTransformer) { 32 | val blockInfos = HashMap() 33 | 34 | init { 35 | for (bb in mthTransformer.blockMap.values) { 36 | blockInfos[bb] = DataFlowBlockInfo(bb, mthTransformer.mthNode.regsCount) 37 | } 38 | } 39 | open fun getBlocks() = blockInfos.keys 40 | open fun getBlockInfos() = blockInfos.values 41 | open fun getBlockInfo(block: BasicBlock) = blockInfos[block] 42 | open fun isExit(block: BasicBlock) = mthTransformer.exits.contains(block) 43 | } 44 | 45 | open class DataFlowBlockInfo(val block: BasicBlock, regCount: Int) { 46 | val use = BitArray(regCount) 47 | val def = BitArray(regCount) 48 | var liveIn: BitArray? = null 49 | var liveOut: BitArray? = null 50 | } 51 | 52 | object DataFlowAnalysisPass { 53 | 54 | fun visit(meth: MethodTransformer): DataFlowMethodInfo { 55 | val dfMethodInfo = DataFlowMethodInfo(meth) 56 | usedefAnalysis(dfMethodInfo) 57 | return dfMethodInfo 58 | } 59 | 60 | fun usedefAnalysis(dfMethodInfo: DataFlowMethodInfo) { 61 | for (entry in dfMethodInfo.getBlockInfos()) { 62 | usedefAnalysis(entry) 63 | } 64 | } 65 | 66 | fun livenessAnalyzer(dfMethodInfo: DataFlowMethodInfo) { 67 | for (blockInfo in dfMethodInfo.getBlockInfos()) { 68 | blockInfo.liveIn = BitArray(0) 69 | } 70 | val blockReverseList = ArrayList() 71 | blockReverseList.addAll(dfMethodInfo.getBlocks()) 72 | Collections.sort(blockReverseList, kotlin.Comparator { t1, t2 -> 73 | val c1 = t1.firstCursor() ?: throw BlockEmptyException("Compare to empty block") 74 | val c2 = t2.firstCursor() ?: throw BlockEmptyException("Compare to empty block") 75 | return@Comparator c2.compareTo(c1) 76 | }) 77 | blockReverseList.removeIf { bb -> dfMethodInfo.isExit(bb) } 78 | val lastLiveIn = Array(blockReverseList.size) { 79 | i -> dfMethodInfo.getBlockInfo(blockReverseList[i])?.liveIn!!._array.copyOf() 80 | } 81 | val beChange = fun(): Boolean { 82 | var result = false 83 | for (i in blockReverseList.indices) { 84 | val liveIn = dfMethodInfo.getBlockInfo(blockReverseList[i])?.liveIn 85 | if (!liveIn!!.equal(lastLiveIn[i])) { 86 | result = true 87 | } 88 | lastLiveIn[i] = liveIn._array.copyOf() 89 | } 90 | return result 91 | } 92 | do { 93 | for (i in blockReverseList.indices) { 94 | val block = blockReverseList[i] 95 | val succList = block.successor.stream().map { 96 | basicBlock -> dfMethodInfo.getBlockInfo(basicBlock)?.liveIn 97 | ?: throw DataFlowAnalyzeException("block's LiveIn empty") 98 | }.collect(Collectors.toList()).toList() 99 | val currBlockInfo = dfMethodInfo.getBlockInfo(block) 100 | ?: throw BlockEmptyException("Computer liveness with empty block info") 101 | currBlockInfo.liveOut = BitArray.merge(succList) 102 | currBlockInfo.liveIn = BitArray.merge( 103 | currBlockInfo.use, BitArray.sub(currBlockInfo.liveOut!!, currBlockInfo.def)) 104 | } 105 | } while (beChange()) 106 | } 107 | 108 | private fun usedefAnalysis(blockInfo: DataFlowBlockInfo) { 109 | for (inst in blockInfo.block.instList) { 110 | val (use, def) = getUseDefReg(inst.instruction) 111 | for (u in use) { 112 | if ((blockInfo.def.get(u) == 0) && (blockInfo.use.get(u) == 0)) { 113 | blockInfo.use.setTrue(u) 114 | } 115 | } 116 | for (d in def) { 117 | if ((blockInfo.use.get(d) == 0) && (blockInfo.def.get(d) == 0)) { 118 | blockInfo.def.setTrue(d) 119 | } 120 | } 121 | } 122 | } 123 | 124 | private fun getUseDefReg(inst: DecodedInstruction): Pair { 125 | when (inst.opcode) { 126 | in Opcodes.MOVE..Opcodes.MOVE_16, 127 | in Opcodes.MOVE_OBJECT..Opcodes.MOVE_OBJECT_16, 128 | Opcodes.INSTANCE_OF, Opcodes.ARRAY_LENGTH, 129 | Opcodes.NEW_ARRAY, 130 | Opcodes.IGET, in Opcodes.IGET_OBJECT..Opcodes.IGET_SHORT, 131 | Opcodes.NEG_INT, Opcodes.NOT_INT, Opcodes.NEG_FLOAT, 132 | Opcodes.INT_TO_FLOAT, Opcodes.FLOAT_TO_INT, Opcodes.INT_TO_BYTE, Opcodes.INT_TO_CHAR, Opcodes.INT_TO_SHORT, 133 | in Opcodes.ADD_INT_LIT16..Opcodes.XOR_INT_LIT16, 134 | in Opcodes.ADD_INT_LIT8..Opcodes.USHR_INT_LIT8 -> { 135 | return Pair(intArrayOf(inst.b), intArrayOf(inst.a)) 136 | } 137 | Opcodes.MOVE_RESULT, 138 | in Opcodes.MOVE_RESULT_OBJECT..Opcodes.MOVE_EXCEPTION, 139 | in Opcodes.CONST_4..Opcodes.CONST_HIGH16, 140 | in Opcodes.CONST_STRING..Opcodes.CONST_CLASS, 141 | Opcodes.NEW_INSTANCE, 142 | in Opcodes.IF_EQZ..Opcodes.IF_LEZ, 143 | Opcodes.SGET, in Opcodes.SGET_OBJECT..Opcodes.SGET_SHORT, 144 | Opcodes.CONST_METHOD_HANDLE, Opcodes.CONST_METHOD_TYPE -> { 145 | return Pair(IntArray(0), intArrayOf(inst.a)) 146 | } 147 | in Opcodes.CONST_WIDE_16..Opcodes.CONST_WIDE_HIGH16, 148 | Opcodes.SGET_WIDE -> { 149 | return Pair(IntArray(0), intArrayOf(inst.a, inst.a + 1)) 150 | } 151 | in Opcodes.MOVE_WIDE..Opcodes.MOVE_WIDE_16, 152 | Opcodes.NEG_LONG, Opcodes.NOT_LONG, Opcodes.NEG_DOUBLE, 153 | Opcodes.LONG_TO_DOUBLE, Opcodes.DOUBLE_TO_LONG -> { 154 | return Pair(intArrayOf(inst.b, inst.b + 1), intArrayOf(inst.a, inst.a + 1)) 155 | } 156 | in Opcodes.IF_EQ..Opcodes.IF_LE, 157 | Opcodes.IPUT, in Opcodes.IPUT_OBJECT..Opcodes.IPUT_SHORT, 158 | in Opcodes.ADD_INT_2ADDR..Opcodes.USHR_INT_2ADDR, 159 | in Opcodes.ADD_FLOAT_2ADDR..Opcodes.REM_FLOAT_2ADDR -> { 160 | return Pair(intArrayOf(inst.a, inst.b), IntArray(0)) 161 | } 162 | Opcodes.RETURN, Opcodes.RETURN_OBJECT, Opcodes.MONITOR_ENTER, Opcodes.MONITOR_EXIT, Opcodes.CHECK_CAST, 163 | Opcodes.FILL_ARRAY_DATA, Opcodes.THROW, Opcodes.PACKED_SWITCH, Opcodes.SPARSE_SWITCH, 164 | Opcodes.SPUT, in Opcodes.SPUT_OBJECT..Opcodes.SPUT_SHORT -> { 165 | return Pair(intArrayOf(inst.a), IntArray(0)) 166 | } 167 | Opcodes.RETURN_WIDE, 168 | Opcodes.SPUT_WIDE -> { 169 | return Pair(intArrayOf(inst.a, inst.a + 1), IntArray(0)) 170 | } 171 | in Opcodes.CMPL_FLOAT..Opcodes.CMP_LONG -> { 172 | return Pair(intArrayOf(inst.b, inst.b + 1, inst.c, inst.c + 1), intArrayOf(inst.a)) 173 | } 174 | Opcodes.AGET, in Opcodes.AGET_OBJECT..Opcodes.AGET_SHORT, 175 | in Opcodes.ADD_INT..Opcodes.USHR_INT, 176 | in Opcodes.ADD_FLOAT..Opcodes.REM_FLOAT -> { 177 | return Pair(intArrayOf(inst.b, inst.c), intArrayOf(inst.a)) 178 | } 179 | in Opcodes.ADD_LONG..Opcodes.USHR_LONG, 180 | in Opcodes.ADD_DOUBLE..Opcodes.REM_DOUBLE -> { 181 | return Pair(intArrayOf(inst.b, inst.b + 1, inst.c, inst.c + 1), intArrayOf(inst.a, inst.a + 1)) 182 | } 183 | Opcodes.AGET_WIDE -> { 184 | return Pair(intArrayOf(inst.b, inst.c), intArrayOf(inst.a, inst.a + 1)) 185 | } 186 | Opcodes.APUT, in Opcodes.APUT_OBJECT..Opcodes.APUT_SHORT -> { 187 | return Pair(intArrayOf(inst.a, inst.b, inst.c), IntArray(0)) 188 | } 189 | Opcodes.APUT_WIDE -> { 190 | return Pair(intArrayOf(inst.a, inst.a + 1, inst.b, inst.c), IntArray(0)) 191 | } 192 | Opcodes.IGET_WIDE, 193 | Opcodes.INT_TO_LONG, Opcodes.INT_TO_DOUBLE, Opcodes.FLOAT_TO_LONG, Opcodes.FLOAT_TO_DOUBLE -> { 194 | return Pair(intArrayOf(inst.b), intArrayOf(inst.a, inst.a + 1)) 195 | } 196 | Opcodes.LONG_TO_INT, Opcodes.LONG_TO_FLOAT, Opcodes.DOUBLE_TO_INT, Opcodes.DOUBLE_TO_FLOAT -> { 197 | return Pair(intArrayOf(inst.b, inst.b + 1), intArrayOf(inst.a)) 198 | } 199 | in Opcodes.ADD_LONG_2ADDR..Opcodes.USHR_LONG_2ADDR, 200 | in Opcodes.ADD_DOUBLE_2ADDR..Opcodes.REM_DOUBLE_2ADDR -> { 201 | return Pair(intArrayOf(inst.a, inst.a + 1, inst.b, inst.b + 1), IntArray(0)) 202 | } 203 | Opcodes.IPUT_WIDE -> { 204 | return Pair(intArrayOf(inst.a, inst.a + 1, inst.b), IntArray(0)) 205 | } 206 | Opcodes.FILLED_NEW_ARRAY -> { 207 | // TODO 208 | } 209 | Opcodes.FILLED_NEW_ARRAY_RANGE -> { 210 | // TODO 211 | } 212 | in Opcodes.INVOKE_VIRTUAL..Opcodes.INVOKE_INTERFACE -> { 213 | // TODO 214 | } 215 | in Opcodes.INVOKE_VIRTUAL_RANGE..Opcodes.INVOKE_INTERFACE_RANGE -> { 216 | // TODO 217 | } 218 | Opcodes.INVOKE_POLYMORPHIC -> { 219 | // TODO 220 | } 221 | Opcodes.INVOKE_POLYMORPHIC_RANGE -> { 222 | // TODO 223 | } 224 | Opcodes.INVOKE_CUSTOM -> { 225 | // TODO 226 | } 227 | Opcodes.INVOKE_CUSTOM_RANGE -> { 228 | // TODO 229 | } 230 | else -> { 231 | // TODO 232 | } 233 | } 234 | return Pair(IntArray(0), IntArray(0)) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dedx/transform/InstTransformer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 penguin_wwy<940375606@qq.com> 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.dedx.transform 18 | 19 | import com.dedx.dex.struct.FieldInfo 20 | import com.dedx.dex.struct.MethodInfo 21 | import com.dedx.tools.Configuration 22 | import com.dedx.transform.passes.EliminateCode 23 | import com.dedx.transform.passes.InstAnalysis 24 | import com.dedx.transform.passes.RemoveNOP 25 | import com.dedx.utils.DecodeException 26 | import com.dedx.utils.annotation.DepAnnotation 27 | import com.google.common.flogger.FluentLogger 28 | import java.util.LinkedList 29 | import java.util.Stack 30 | import org.objectweb.asm.Label 31 | import org.objectweb.asm.MethodVisitor 32 | import org.objectweb.asm.Opcodes 33 | 34 | class JumpTarget { 35 | var thenPos: Int? = null 36 | var elsePos: Int? = null 37 | val execPos = ArrayList() 38 | 39 | fun setThen(pos: Int): JumpTarget { 40 | thenPos = pos 41 | return this 42 | } 43 | 44 | fun setElse(pos: Int): JumpTarget { 45 | elsePos = pos 46 | return this 47 | } 48 | 49 | fun addExec(pos: Int): JumpTarget { 50 | execPos.add(pos) 51 | return this 52 | } 53 | } 54 | 55 | class TryCatchTable { 56 | data class TCElement(var startInst: JvmInst, var endInst: JvmInst, var catchInst: JvmInst, var type: String?) 57 | 58 | val elements = ArrayList() 59 | 60 | fun addElement(startInst: JvmInst, endInst: JvmInst, catchInst: JvmInst, type: String?) = 61 | elements.add(TCElement(startInst, endInst, catchInst, type)) 62 | 63 | fun empty() = elements.isEmpty() 64 | 65 | fun visit(methVisitor: MethodVisitor) { 66 | elements.forEach { 67 | val startLabel = it.startInst.label 68 | val endLabel = it.endInst.label 69 | val handLabel = it.catchInst.label 70 | methVisitor.visitTryCatchBlock(startLabel.getValue(), endLabel.getValue(), handLabel.getValue(), it.type) 71 | } 72 | } 73 | 74 | fun updateIfNeed(oldInst: JvmInst, newInst: JvmInst) { 75 | elements.forEach { 76 | if (it.startInst == oldInst) 77 | it.startInst = newInst 78 | if (it.endInst == oldInst) 79 | it.endInst = newInst 80 | if (it.catchInst == oldInst) 81 | it.catchInst = newInst 82 | } 83 | } 84 | } 85 | 86 | class InstTransformer(val mthTransformer: MethodTransformer) { 87 | companion object { 88 | private val logger = FluentLogger.forEnclosingClass() 89 | fun throwErrorMethod(mthVisitor: MethodVisitor) { 90 | mthVisitor.visitCode() 91 | mthVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException") 92 | mthVisitor.visitInsn(Opcodes.DUP) 93 | mthVisitor.visitLdcInsn("Error occur, please see details in log") 94 | mthVisitor.visitMethodInsn( 95 | Opcodes.INVOKESPECIAL, 96 | "java/lang/RuntimeException", 97 | "", 98 | "(Ljava/lang/String;)V", 99 | false) 100 | mthVisitor.visitInsn(Opcodes.ATHROW) 101 | mthVisitor.visitMaxs(0, 0) 102 | mthVisitor.visitEnd() 103 | } 104 | } 105 | 106 | private val jvmInstList = LinkedList() 107 | 108 | private val backwardJumpMap = HashMap() 109 | private val forwardJumpMap = HashMap>() 110 | private val labelMap = HashMap() 111 | private val shadowInsts = HashSet() 112 | private val tryCatchTable = TryCatchTable() 113 | val jumpMap by lazy { HashMap>() } 114 | val instStorage by lazy { LabelMap() } 115 | 116 | fun addTryCatchElement(startInst: JvmInst, endInst: JvmInst, catchInst: JvmInst, type: String?) = 117 | tryCatchTable.addElement(startInst, endInst, catchInst, type) 118 | 119 | fun pushJvmInst(jvmInst: JvmInst) { 120 | if (jvmInst.label.getValue() != null) { 121 | labelMap[jvmInst.label.getValue()!!] = jvmInstList.size 122 | } 123 | if (jvmInst is ShadowInst) { 124 | shadowInsts.add(jvmInstList.size) 125 | } 126 | instStorage.storeInst(jvmInst.label, jvmInst) 127 | jvmInstList.add(jvmInst) 128 | } 129 | 130 | @DepAnnotation(InstAnalysis::class, "runOnFunction", true) 131 | fun removeJvmInst(jvmInst: JvmInst) { 132 | val next = jvmInstList.listIterator(jvmInstList.indexOf(jvmInst) + 1).next() 133 | jumpMap[jvmInst]?.also { 134 | for (inst in it) { 135 | inst.target = next.label 136 | } 137 | }?.remove(jvmInst) 138 | tryCatchTable.updateIfNeed(jvmInst, next) 139 | jvmInstList.remove(jvmInst) 140 | } 141 | 142 | fun removeJvmInsts(jvmInsts: Collection) = jvmInsts.forEach { removeJvmInst(it) } 143 | 144 | fun instListSize() = jvmInstList.size 145 | 146 | fun inst(index: Int) = jvmInstList[index] 147 | 148 | fun instListIterator() = jvmInstList.listIterator() 149 | 150 | fun instListIterator(index: Int) = jvmInstList.listIterator(index) 151 | 152 | fun imInstList() = jvmInstList as List 153 | 154 | fun visitJvmInst(configuration: Configuration) { 155 | // eliminateShadowInst() 156 | if (configuration.optLevel == Configuration.NormalOpt) { 157 | InstAnalysis.runOnFunction(this) 158 | RemoveNOP.runOnFunction(this) 159 | EliminateCode.runOnFunction(this) 160 | } 161 | for (jvmInst in jvmInstList) { 162 | jvmInst.visitLabel(this) 163 | jvmInst.visitInst(this) 164 | } 165 | tryCatchTable.visit(this.methodVisitor()) 166 | } 167 | 168 | fun methodVisitor() = mthTransformer.mthVisit 169 | 170 | fun dexNode() = mthTransformer.dexNode 171 | 172 | fun methodInfo(mthIndex: Int) = MethodInfo.fromDex(dexNode(), mthIndex) 173 | 174 | fun fieldInfo(fieldIndex: Int) = FieldInfo.fromDex(dexNode(), fieldIndex) 175 | 176 | fun string(cIndex: Int) = dexNode().getString(cIndex) 177 | 178 | private fun addForwardJump(pos: Int, target: Int) { 179 | if (!forwardJumpMap.containsKey(pos)) { 180 | forwardJumpMap[pos] = ArrayList() 181 | } 182 | forwardJumpMap[pos]?.add(target) 183 | } 184 | 185 | private fun buildJumpPhase() { 186 | for (i in jvmInstList.indices) { 187 | if (jvmInstList[i] is JumpInst) { 188 | val jumpInst = jvmInstList[i] as JumpInst 189 | when (jumpInst.opcodes) { 190 | in Opcodes.IFEQ..Opcodes.IF_ACMPNE, Opcodes.IFNULL, Opcodes.IFNONNULL -> { 191 | backwardJumpMap[i] = JumpTarget().setThen(i + 1).setElse(labelMap[jumpInst.target.getValue()] 192 | ?: throw DecodeException("")) 193 | addForwardJump(i + 1, i) 194 | addForwardJump(labelMap[jumpInst.target.getValue()]!!, i) 195 | } 196 | Opcodes.GOTO -> { 197 | backwardJumpMap[i] = JumpTarget().setThen(labelMap[jumpInst.target.getValue()] 198 | ?: throw DecodeException("")) 199 | addForwardJump(labelMap[jumpInst.target.getValue()]!!, i) 200 | } 201 | } 202 | } 203 | if (jvmInstList[i] is InvokeInst) { 204 | // TODO set jump to exception handle block 205 | } 206 | } 207 | } 208 | 209 | // TODO if `aget` instruction, use forward traversal, else if like `aput`, use backward traversal(to do) 210 | private fun typeWithExecute(pos: Int, isBackward: Boolean, vararg slots: Int): SlotType? { 211 | val posStack = Stack() 212 | posStack.push(pos) 213 | // DFS algorithm 214 | // TODO if input slot is empty, means value is stack(jvm) top, so must get type instruction which contain type info 215 | if (isBackward) { 216 | while (!posStack.empty()) { 217 | var runPos = posStack.pop() + 1 218 | loop@ while (runPos < jvmInstList.size) { 219 | val inst = jvmInstList[runPos] 220 | if (inst is ShadowInst) { 221 | // TODO 222 | } 223 | if (inst is SlotInst && inst.slot in slots) { 224 | return inst.getExprType() 225 | } 226 | when (inst.opcodes) { 227 | in Opcodes.IRETURN..Opcodes.RETURN, Opcodes.ATHROW -> break@loop 228 | in Opcodes.IFEQ..Opcodes.IF_ACMPNE -> { 229 | posStack.push(backwardJumpMap[runPos]?.thenPos) 230 | posStack.push(backwardJumpMap[runPos]?.elsePos) 231 | break@loop 232 | } 233 | in Opcodes.INVOKEVIRTUAL..Opcodes.INVOKEDYNAMIC -> { 234 | // TODO 235 | } 236 | Opcodes.GOTO -> posStack.push(backwardJumpMap[runPos]?.thenPos) 237 | else -> runPos++ 238 | } 239 | } 240 | } 241 | } else { 242 | while (!posStack.empty()) { 243 | var runPos = posStack.pop() - 1 244 | loop@ while (runPos >= 0) { 245 | val inst = jvmInstList[runPos] 246 | if (inst is ShadowInst) { 247 | // TODO 248 | } 249 | if (inst is SlotInst) { 250 | if (inst.slot in slots) { 251 | return inst.getExprType() 252 | } 253 | } 254 | val jumpList = forwardJumpMap[runPos] 255 | if (jumpList != null) { 256 | for (forwardPos in jumpList) { 257 | posStack.push(forwardPos) 258 | } 259 | break@loop 260 | } 261 | runPos-- 262 | } 263 | } 264 | } 265 | return null 266 | } 267 | 268 | private fun eliminateShadowInst() { 269 | buildJumpPhase() 270 | 271 | for (i in shadowInsts) { 272 | if (jvmInstList[i] !is ShadowInst) { 273 | continue 274 | } 275 | val shadowInst = jvmInstList[i] as ShadowInst 276 | if (shadowInst.isSalve()) { 277 | continue 278 | } 279 | // LSTORE -> backward traversal, LLOAD -> forward traversal 280 | val type = typeWithExecute(i, shadowInst.opcodes == Opcodes.LSTORE, shadowInst.regs[0]) 281 | ?: throw DecodeException("get ShadowInst type error") 282 | for (salve in shadowInst.salves) { 283 | val index = jvmInstList.indexOf(salve) 284 | if (index >= 0 && index < jvmInstList.size) { 285 | jvmInstList[index] = salve.convert(type) 286 | } 287 | } 288 | jvmInstList[i] = shadowInst.convert(type) 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------