diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index 4081a75c..6e009fb7 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -366,7 +366,8 @@ public class MethodDefinition { private List getMethodItems() { ArrayList methodItems = new ArrayList(); - if ((classDef.options.registerInfo != 0) || (classDef.options.deodex && needsAnalyzed())) { + if ((classDef.options.registerInfo != 0) || (classDef.options.normalizeVirtualMethods) || + (classDef.options.deodex && needsAnalyzed())) { addAnalyzedInstructionMethodItems(methodItems); } else { addInstructionMethodItems(methodItems); @@ -460,7 +461,7 @@ public class MethodDefinition { private void addAnalyzedInstructionMethodItems(List methodItems) { MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method, - classDef.options.inlineResolver); + classDef.options.inlineResolver, classDef.options.normalizeVirtualMethods); AnalysisException analysisException = methodAnalyzer.getAnalysisException(); if (analysisException != null) { diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 47fa406d..1e297029 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -44,19 +44,18 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; import java.io.*; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.*; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.parsers.ParserConfigurationException; - public class baksmali { public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) { - if (options.registerInfo != 0 || options.deodex) { + if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) { try { Iterable extraClassPathEntries; if (options.extraClassPathEntries != null) { diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java index 24bf0b96..5dd060f1 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java @@ -76,6 +76,7 @@ public class baksmaliOptions { public boolean ignoreErrors = false; public boolean checkPackagePrivateAccess = false; public boolean useImplicitReferences = false; + public boolean normalizeVirtualMethods = false; public File customInlineDefinitions = null; public InlineMethodResolver inlineResolver = null; public int registerInfo = 0; diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index 7589d3e7..17ec3839 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -219,6 +219,9 @@ public class main { case 'k': options.checkPackagePrivateAccess = true; break; + case 'n': + options.normalizeVirtualMethods = true; + break; case 'N': disassemble = false; break; @@ -282,7 +285,7 @@ public class main { options.deodex = false; } - if (!setBootClassPath && (options.deodex || options.registerInfo != 0)) { + if (!setBootClassPath && (options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) { if (dexFile instanceof DexBackedOdexFile) { options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies(); } else { @@ -457,6 +460,10 @@ public class main { "4.2.1.") .create("k"); + Option normalizeVirtualMethods = OptionBuilder.withLongOpt("normalize-virtual-methods") + .withDescription("Normalize virtual method references to the reference the base method.") + .create("n"); + Option dumpOption = OptionBuilder.withLongOpt("dump-to") .withDescription("dumps the given dex file into a single annotated dump file named FILE" + " (.dump by default), along with the normal disassembly") @@ -506,6 +513,7 @@ public class main { basicOptions.addOption(noImplicitReferencesOption); basicOptions.addOption(dexEntryOption); basicOptions.addOption(checkPackagePrivateAccessOption); + basicOptions.addOption(normalizeVirtualMethods); debugOptions.addOption(dumpOption); debugOptions.addOption(ignoreErrorsOption); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedMethodUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedMethodUtil.java new file mode 100644 index 00000000..775a819d --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedMethodUtil.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.analysis; + +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.analysis.util.TypeProtoUtils; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.util.MethodUtil; +import org.jf.dexlib2.util.TypeUtils; + +import javax.annotation.Nonnull; + +public class AnalyzedMethodUtil { + public static boolean canAccess(@Nonnull TypeProto type, @Nonnull Method virtualMethod, boolean checkPackagePrivate, + boolean checkProtected, boolean checkClass) { + if (checkPackagePrivate && MethodUtil.isPackagePrivate(virtualMethod)) { + String otherPackage = TypeUtils.getPackage(virtualMethod.getDefiningClass()); + String thisPackage = TypeUtils.getPackage(type.getType()); + if (!otherPackage.equals(thisPackage)) { + return false; + } + } + + if (checkProtected && (virtualMethod.getAccessFlags() & AccessFlags.PROTECTED.getValue()) != 0) { + if (!TypeProtoUtils.extendsFrom(type, virtualMethod.getDefiningClass())) { + return false; + } + } + + if (checkClass) { + ClassPath classPath = type.getClassPath(); + ClassDef methodClassDef = classPath.getClassDef(virtualMethod.getDefiningClass()); + if (!TypeUtils.canAccessClass(type.getType(), methodClassDef)) { + return false; + } + } + + return true; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java index 8fcfc8c5..4aa9a5e4 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java @@ -32,6 +32,7 @@ package org.jf.dexlib2.analysis; import com.google.common.base.Strings; +import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.immutable.reference.ImmutableFieldReference; @@ -160,7 +161,11 @@ public class ArrayProto implements TypeProto { @Override @Nullable - public MethodReference getMethodByVtableIndex(int vtableIndex) { + public Method getMethodByVtableIndex(int vtableIndex) { return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex); } + + @Override public int findMethodIndexInVtable(@Nonnull MethodReference method) { + return classPath.getClass("Ljava/lang/Object;").findMethodIndexInVtable(method); + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index 83104b2f..4b8920f4 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -146,7 +146,7 @@ public class ClassPath { } @Nonnull - public TypeProto getClass(CharSequence type) { + public TypeProto getClass(@Nonnull CharSequence type) { return loadedClasses.getUnchecked(type.toString()); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index d011c1e7..d66e8ebd 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -34,7 +34,10 @@ package org.jf.dexlib2.analysis; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.*; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.primitives.Ints; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.analysis.util.TypeProtoUtils; @@ -44,6 +47,7 @@ import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.immutable.ImmutableMethod; +import org.jf.dexlib2.util.MethodUtil; import org.jf.util.AlignmentUtils; import org.jf.util.ExceptionWithContext; import org.jf.util.SparseArray; @@ -346,7 +350,7 @@ public class ClassProto implements TypeProto { @Override @Nullable - public MethodReference getMethodByVtableIndex(int vtableIndex) { + public Method getMethodByVtableIndex(int vtableIndex) { List vtable = getVtable(); if (vtableIndex < 0 || vtableIndex >= vtable.size()) { return null; @@ -355,6 +359,20 @@ public class ClassProto implements TypeProto { return vtable.get(vtableIndex); } + public int findMethodIndexInVtable(@Nonnull MethodReference method) { + List vtable = getVtable(); + for (int i=0; i getInstanceFields() { if (classPath.isArt) { return artInstanceFieldsSupplier.get(); @@ -790,8 +808,9 @@ public class ClassProto implements TypeProto { outer: for (Method virtualMethod: methods) { for (int i=0; i"); } + public static boolean isPackagePrivate(@Nonnull Method method) { + return (method.getAccessFlags() & (AccessFlags.PRIVATE.getValue() | + AccessFlags.PROTECTED.getValue() | + AccessFlags.PUBLIC.getValue())) == 0; + } + public static int getParameterRegisterCount(@Nonnull Method method) { return getParameterRegisterCount(method, MethodUtil.isStatic(method)); } @@ -109,5 +116,11 @@ public final class MethodUtil { return sb.toString(); } + public static boolean methodSignaturesMatch(@Nonnull MethodReference a, @Nonnull MethodReference b) { + return (a.getName().equals(b.getName()) && + a.getReturnType().equals(b.getReturnType()) && + CharSequenceUtils.listEquals(a.getParameterTypes(), b.getParameterTypes())); + } + private MethodUtil() {} } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java index 02890b87..6be21af0 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/util/TypeUtils.java @@ -31,6 +31,8 @@ package org.jf.dexlib2.util; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.reference.TypeReference; import javax.annotation.Nonnull; @@ -49,5 +51,24 @@ public final class TypeUtils { return type.length() == 1; } + @Nonnull + public static String getPackage(@Nonnull String type) { + int lastSlash = type.lastIndexOf('/'); + if (lastSlash < 0) { + return ""; + } + return type.substring(1, lastSlash); + } + + public static boolean canAccessClass(@Nonnull String accessorType, @Nonnull ClassDef accesseeClassDef) { + if (AccessFlags.PUBLIC.isSet(accesseeClassDef.getAccessFlags())) { + return true; + } + + // Classes can only be public or package private. Any private or protected inner classes are actually + // package private. + return getPackage(accesseeClassDef.getType()).equals(getPackage(accessorType)); + } + private TypeUtils() {} } diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java index 65a82f05..25f7778d 100644 --- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java @@ -71,7 +71,7 @@ public class CustomMethodInlineTableTest { ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile, 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); - MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); Assert.assertEquals(Opcode.INVOKE_VIRTUAL, deodexedInstruction.getOpcode()); @@ -98,7 +98,7 @@ public class CustomMethodInlineTableTest { ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile, 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); - MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); Assert.assertEquals(Opcode.INVOKE_STATIC, deodexedInstruction.getOpcode()); @@ -125,7 +125,7 @@ public class CustomMethodInlineTableTest { ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile, 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); - MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); Assert.assertEquals(Opcode.INVOKE_DIRECT, deodexedInstruction.getOpcode());