From bccdc809fa9d1fb227dabf4e04bd27f595529a42 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Mon, 11 Apr 2016 14:15:24 -0700 Subject: [PATCH] Add "list field offsets" and "list vtables" commands to baksmali This is a reimplementation of the "DumpFields" and "DumpVtables" entry points that were previously in dexlib2 --- .../java/org/jf/baksmali/ListCommand.java | 2 + .../jf/baksmali/ListFieldOffsetsCommand.java | 158 +++++++++++++++ .../org/jf/baksmali/ListVtablesCommand.java | 165 ++++++++++++++++ build.gradle | 1 - dexlib2/build.gradle | 1 - .../org/jf/dexlib2/analysis/ClassProto.java | 4 +- .../org/jf/dexlib2/analysis/DumpFields.java | 180 ----------------- .../org/jf/dexlib2/analysis/DumpVtables.java | 184 ------------------ 8 files changed, 327 insertions(+), 368 deletions(-) create mode 100644 baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java delete mode 100644 dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java delete mode 100644 dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java diff --git a/baksmali/src/main/java/org/jf/baksmali/ListCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java index 3ed3afee..8544f59b 100644 --- a/baksmali/src/main/java/org/jf/baksmali/ListCommand.java +++ b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java @@ -59,6 +59,8 @@ public class ListCommand implements Command { subJc.addCommand("types", new ListTypesCommand(subJc), "type", "t"); subJc.addCommand("classes", new ListClassesCommand(subJc), "class", "c"); subJc.addCommand("dex", new ListDexCommand(subJc), "d"); + subJc.addCommand("vtables", new ListVtablesCommand(subJc), "vtable", "v"); + subJc.addCommand("fieldoffsets", new ListFieldOffsetsCommand(subJc), "fieldoffset", "fo"); } @Override public void run() { diff --git a/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java new file mode 100644 index 00000000..31a22179 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java @@ -0,0 +1,158 @@ +/* + * Copyright 2016, 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.baksmali; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import org.jf.dexlib2.analysis.ClassPath; +import org.jf.dexlib2.analysis.ClassProto; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.DexFile; +import org.jf.dexlib2.iface.reference.FieldReference; +import org.jf.util.SparseArray; +import org.jf.util.jcommander.CommaColonParameterSplitter; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.") +public class ListFieldOffsetsCommand extends DexInputCommand { + + @Nonnull private final JCommander jc; + + @Parameter(names = {"-h", "-?", "--help"}, help = true, + description = "Show usage information") + private boolean help; + + @Parameter(names = {"-a", "--api"}, + description = "The numeric api level of the file being loaded.") + private int apiLevel = 15; + + @Parameter(description = " - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " + + "files, you can specify which dex file to disassemble by appending the name of the dex file with a " + + "colon. E.g. \"something.apk:classes2.dex\"") + private List inputList = Lists.newArrayList(); + + @Parameter(names = {"-b", "--bootclasspath"}, + description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " + + "classpath when analyzing the dex file. This will override any automatic selection of " + + "bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + + "BOOTCLASSPATH environment variable.", + splitter = CommaColonParameterSplitter.class) + private List bootClassPath = new ArrayList(); + + @Parameter(names = {"-c", "--classpath"}, + description = "A comma/colon separated list of additional jar/oat files to include in the classpath " + + "when analyzing the dex file. These will be added to the classpath after any bootclasspath " + + "entries.", + splitter = CommaColonParameterSplitter.class) + private List classPath = new ArrayList(); + + @Parameter(names = {"-d", "--classpath-dir"}, + description = "baksmali will search these directories in order for any classpath entries.") + private List classPathDirectories = Lists.newArrayList("."); + + @Parameter(names = "--check-package-private-access", + description = "Use the package-private access check when calculating vtable indexes. This should " + + "only be needed for 4.2.0 odexes. It was reverted in 4.2.1.") + private boolean checkPackagePrivateAccess = false; + + @Parameter(names = "--experimental", + description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " + + "supported in the Android runtime yet.") + private boolean experimentalOpcodes = false; + + public ListFieldOffsetsCommand(@Nonnull JCommander jc) { + this.jc = jc; + } + + @Override public void run() { + if (help || inputList == null || inputList.isEmpty()) { + jc.usage(jc.getParsedCommand()); + return; + } + + if (inputList.size() > 1) { + System.err.println("Too many files specified"); + jc.usage(jc.getParsedCommand()); + return; + } + + String input = inputList.get(0); + DexBackedDexFile dexFile = loadDexFile(input, 15, false); + BaksmaliOptions options = getOptions(dexFile); + + try { + for (ClassDef classDef: dexFile.getClasses()) { + ClassProto classProto = (ClassProto) options.classPath.getClass(classDef); + SparseArray fields = classProto.getInstanceFields(); + String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n"; + System.out.write(className.getBytes()); + for (int i=0;i inputList = Lists.newArrayList(); + + @Parameter(names = {"-b", "--bootclasspath"}, + description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " + + "classpath when analyzing the dex file. This will override any automatic selection of " + + "bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + + "BOOTCLASSPATH environment variable.", + splitter = CommaColonParameterSplitter.class) + private List bootClassPath = new ArrayList(); + + @Parameter(names = {"-c", "--classpath"}, + description = "A comma/colon separated list of additional jar/oat files to include in the classpath " + + "when analyzing the dex file. These will be added to the classpath after any bootclasspath " + + "entries.", + splitter = CommaColonParameterSplitter.class) + private List classPath = new ArrayList(); + + @Parameter(names = {"-d", "--classpath-dir"}, + description = "baksmali will search these directories in order for any classpath entries.") + private List classPathDirectories = Lists.newArrayList("."); + + @Parameter(names = "--check-package-private-access", + description = "Use the package-private access check when calculating vtable indexes. This should " + + "only be needed for 4.2.0 odexes. It was reverted in 4.2.1.") + private boolean checkPackagePrivateAccess = false; + + @Parameter(names = "--experimental", + description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " + + "supported in the Android runtime yet.") + private boolean experimentalOpcodes = false; + + public ListVtablesCommand(@Nonnull JCommander jc) { + this.jc = jc; + } + + @Override public void run() { + if (help || inputList == null || inputList.isEmpty()) { + jc.usage(jc.getParsedCommand()); + return; + } + + if (inputList.size() > 1) { + System.err.println("Too many files specified"); + jc.usage(jc.getParsedCommand()); + return; + } + + String input = inputList.get(0); + DexBackedDexFile dexFile = loadDexFile(input, 15, false); + + BaksmaliOptions options = getOptions(dexFile); + if (options == null) { + return; + } + + try { + for (ClassDef classDef : dexFile.getClasses()) { + ClassProto classProto = (ClassProto) options.classPath.getClass(classDef); + List methods = classProto.getVtable(); + String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n"; + System.out.write(className.getBytes()); + for (int i = 0; i < methods.size(); i++) { + Method method = methods.get(i); + + String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "("; + for (CharSequence parameter : method.getParameterTypes()) { + methodString += parameter; + } + methodString += ")" + method.getReturnType() + "\n"; + System.out.write(methodString.getBytes()); + } + System.out.write("\n".getBytes()); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + protected BaksmaliOptions getOptions(DexFile dexFile) { + final BaksmaliOptions options = new BaksmaliOptions(); + + options.apiLevel = apiLevel; + + try { + options.classPath = ClassPath.fromClassPath(classPathDirectories, + Iterables.concat(bootClassPath, classPath), dexFile, apiLevel, + checkPackagePrivateAccess, experimentalOpcodes); + } catch (Exception ex) { + System.err.println("Error occurred while loading class path files."); + ex.printStackTrace(System.err); + return null; + } + + options.experimentalOpcodes = experimentalOpcodes; + + return options; + } +} diff --git a/build.gradle b/build.gradle index 7f9c36af..358c7f72 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,6 @@ subprojects { antlr_runtime: 'org.antlr:antlr-runtime:3.5.2', antlr: 'org.antlr:antlr:3.5.2', stringtemplate: 'org.antlr:stringtemplate:3.2.1', - commons_cli: 'commons-cli:commons-cli:1.2', jflex: 'de.jflex:jflex:1.4.3', jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2', proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle index 952d0100..8fbe5ffe 100644 --- a/dexlib2/build.gradle +++ b/dexlib2/build.gradle @@ -49,7 +49,6 @@ dependencies { compile project(':util') compile depends.findbugs compile depends.guava - compile depends.commons_cli testCompile depends.junit 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 57aae115..1e0d0fa6 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -373,7 +373,7 @@ public class ClassProto implements TypeProto { return -1; } - @Nonnull SparseArray getInstanceFields() { + @Nonnull public SparseArray getInstanceFields() { if (classPath.isArt()) { return artInstanceFieldsSupplier.get(); } else { @@ -759,7 +759,7 @@ public class ClassProto implements TypeProto { throw new ExceptionWithContext("Invalid type: %s", type); } - @Nonnull List getVtable() { + @Nonnull public List getVtable() { return vtableSupplier.get(); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java deleted file mode 100644 index 2bb3e492..00000000 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2013, 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 com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import org.apache.commons.cli.*; -import org.jf.dexlib2.DexFileFactory; -import org.jf.dexlib2.dexbacked.DexBackedDexFile; -import org.jf.dexlib2.iface.ClassDef; -import org.jf.dexlib2.iface.Field; -import org.jf.dexlib2.iface.Method; -import org.jf.dexlib2.iface.reference.FieldReference; -import org.jf.util.ConsoleUtil; -import org.jf.util.SparseArray; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; - -public class DumpFields { - private static final Options options; - - static { - options = new Options(); - buildOptions(); - } - - public static void main(String[] args) { - CommandLineParser parser = new PosixParser(); - CommandLine commandLine; - - try { - commandLine = parser.parse(options, args); - } catch (ParseException ex) { - usage(); - return; - } - - String[] remainingArgs = commandLine.getArgs(); - - Option[] parsedOptions = commandLine.getOptions(); - ArrayList bootClassPathDirs = Lists.newArrayList(); - String outFile = "fields.txt"; - int apiLevel = 15; - boolean experimental = false; - - for (int i=0; i bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); - ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental); - FileOutputStream outStream = new FileOutputStream(outFile); - - for (ClassDef classDef: dexFile.getClasses()) { - ClassProto classProto = (ClassProto) classPath.getClass(classDef); - SparseArray fields = classProto.getInstanceFields(); - String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n"; - outStream.write(className.getBytes()); - for (int i=0;i"); - } - - private static void buildOptions() { - Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir") - .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " + - "directory") - .hasArg() - .withArgName("DIR") - .create("d"); - - Option outputFileOption = OptionBuilder.withLongOpt("out-file") - .withDescription("output file") - .hasArg() - .withArgName("FILE") - .create("o"); - - Option apiLevelOption = OptionBuilder.withLongOpt("api-level") - .withDescription("The numeric api-level of the file being disassembled. If not " + - "specified, it defaults to 15 (ICS).") - .hasArg() - .withArgName("API_LEVEL") - .create("a"); - - Option experimentalOption = OptionBuilder.withLongOpt("experimental") - .withDescription("Enable dumping experimental opcodes, that aren't necessarily " + - "supported by the android runtime yet.") - .create("X"); - - options.addOption(classPathDirOption); - options.addOption(outputFileOption); - options.addOption(apiLevelOption); - options.addOption(experimentalOption); - } -} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java deleted file mode 100644 index 193c0d39..00000000 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2013, 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 com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import org.apache.commons.cli.*; -import org.jf.dexlib2.DexFileFactory; -import org.jf.dexlib2.dexbacked.DexBackedDexFile; -import org.jf.dexlib2.iface.ClassDef; -import org.jf.dexlib2.iface.Method; -import org.jf.util.ConsoleUtil; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class DumpVtables { - private static final Options options; - - static { - options = new Options(); - buildOptions(); - } - - public static void main(String[] args) { - CommandLineParser parser = new PosixParser(); - CommandLine commandLine; - - try { - commandLine = parser.parse(options, args); - } catch (ParseException ex) { - usage(); - return; - } - - String[] remainingArgs = commandLine.getArgs(); - - Option[] parsedOptions = commandLine.getOptions(); - ArrayList bootClassPathDirs = Lists.newArrayList(); - String outFile = "vtables.txt"; - int apiLevel = 15; - boolean experimental = false; - - for (int i=0; i bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); - ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental); - FileOutputStream outStream = new FileOutputStream(outFile); - - for (ClassDef classDef: dexFile.getClasses()) { - ClassProto classProto = (ClassProto) classPath.getClass(classDef); - List methods = classProto.getVtable(); - String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n"; - outStream.write(className.getBytes()); - for (int i=0;i" + method.getName() + "("; - for (CharSequence parameter: method.getParameterTypes()) { - methodString += parameter; - } - methodString += ")" + method.getReturnType() + "\n"; - outStream.write(methodString.getBytes()); - } - outStream.write("\n".getBytes()); - } - outStream.close(); - } catch (IOException ex) { - System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex); - } - - } - - /** - * Prints the usage message. - */ - private static void usage() { - int consoleWidth = ConsoleUtil.getConsoleWidth(); - if (consoleWidth <= 0) { - consoleWidth = 80; - } - - System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpVtables -d path/to/framework/jar/files "); - } - - private static void buildOptions() { - Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir") - .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " + - "directory") - .hasArg() - .withArgName("DIR") - .create("d"); - - Option outputFileOption = OptionBuilder.withLongOpt("out-file") - .withDescription("output file") - .hasArg() - .withArgName("FILE") - .create("o"); - - Option apiLevelOption = OptionBuilder.withLongOpt("api-level") - .withDescription("The numeric api-level of the file being disassembled. If not " + - "specified, it defaults to 15 (ICS).") - .hasArg() - .withArgName("API_LEVEL") - .create("a"); - - Option experimentalOption = OptionBuilder.withLongOpt("experimental") - .withDescription("Enable dumping experimental opcodes, that aren't necessarily " + - "supported by the android runtime yet.") - .create("X"); - - options.addOption(classPathDirOption); - options.addOption(outputFileOption); - options.addOption(apiLevelOption); - options.addOption(experimentalOption); - } -}