From 827e2db34dc3b8b5504c148a09d594b5c0ddbd64 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Sun, 27 Sep 2015 20:40:10 -0700 Subject: [PATCH] Add support for normalizing virtual methods This is useful, for example, when comparing the result of deodexing with the original dex file, to remove the "false" differences caused by the different potential ways to reference a given virtual method. --- .../baksmali/Adaptors/MethodDefinition.java | 5 +- .../main/java/org/jf/baksmali/baksmali.java | 9 +- .../java/org/jf/baksmali/baksmaliOptions.java | 1 + .../src/main/java/org/jf/baksmali/main.java | 10 +- .../dexlib2/analysis/AnalyzedMethodUtil.java | 70 +++++++++++ .../org/jf/dexlib2/analysis/ArrayProto.java | 7 +- .../org/jf/dexlib2/analysis/ClassPath.java | 2 +- .../org/jf/dexlib2/analysis/ClassProto.java | 58 ++++----- .../jf/dexlib2/analysis/MethodAnalyzer.java | 112 ++++++++++++++---- .../jf/dexlib2/analysis/PrimitiveProto.java | 7 +- .../org/jf/dexlib2/analysis/TypeProto.java | 4 +- .../dexlib2/analysis/UnknownClassProto.java | 7 +- .../dexlib2/analysis/util/TypeProtoUtils.java | 12 ++ .../java/org/jf/dexlib2/util/MethodUtil.java | 13 ++ .../java/org/jf/dexlib2/util/TypeUtils.java | 21 ++++ .../analysis/CustomMethodInlineTableTest.java | 6 +- 16 files changed, 270 insertions(+), 74 deletions(-) create mode 100644 dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedMethodUtil.java 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());