| /* |
| * [The "BSD licence"] |
| * Copyright (c) 2010 Ben Gruver (JesusFreke) |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.google.common.collect.Lists; |
| import org.apache.commons.cli.*; |
| import org.jf.dexlib2.DexFileFactory; |
| import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException; |
| import org.jf.dexlib2.analysis.InlineMethodResolver; |
| import org.jf.dexlib2.dexbacked.DexBackedDexFile; |
| import org.jf.dexlib2.dexbacked.DexBackedOdexFile; |
| import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; |
| import org.jf.dexlib2.iface.DexFile; |
| import org.jf.util.ConsoleUtil; |
| import org.jf.util.SmaliHelpFormatter; |
| |
| import javax.annotation.Nonnull; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Properties; |
| |
| public class main { |
| |
| public static final String VERSION; |
| |
| private static final Options basicOptions; |
| private static final Options debugOptions; |
| private static final Options options; |
| |
| static { |
| options = new Options(); |
| basicOptions = new Options(); |
| debugOptions = new Options(); |
| buildOptions(); |
| |
| InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties"); |
| if (templateStream != null) { |
| Properties properties = new Properties(); |
| String version = "(unknown)"; |
| try { |
| properties.load(templateStream); |
| version = properties.getProperty("application.version"); |
| } catch (IOException ex) { |
| // ignore |
| } |
| VERSION = version; |
| } else { |
| VERSION = "[unknown version]"; |
| } |
| } |
| |
| /** |
| * This class is uninstantiable. |
| */ |
| private main() { |
| } |
| |
| /** |
| * A more programmatic-friendly entry point for baksmali |
| * |
| * @param options a baksmaliOptions object with the options to run baksmali with |
| * @param inputDexFile The DexFile to disassemble |
| * @return true if disassembly completed with no errors, or false if errors were encountered |
| */ |
| public static boolean run(@Nonnull baksmaliOptions options, @Nonnull DexFile inputDexFile) throws IOException { |
| if (options.bootClassPathEntries.isEmpty() && |
| (options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) { |
| if (inputDexFile instanceof DexBackedOdexFile) { |
| options.bootClassPathEntries = ((DexBackedOdexFile)inputDexFile).getDependencies(); |
| } else { |
| options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel, |
| options.experimental); |
| } |
| } |
| |
| if (options.customInlineDefinitions == null && inputDexFile instanceof DexBackedOdexFile) { |
| options.inlineResolver = |
| InlineMethodResolver.createInlineMethodResolver( |
| ((DexBackedOdexFile)inputDexFile).getOdexVersion()); |
| } |
| |
| boolean errorOccurred = false; |
| if (options.disassemble) { |
| errorOccurred = !baksmali.disassembleDexFile(inputDexFile, options); |
| } |
| |
| if (options.dump) { |
| if (!(inputDexFile instanceof DexBackedDexFile)) { |
| throw new IllegalArgumentException("Annotated hex-dumps require a DexBackedDexFile"); |
| } |
| dump.dump((DexBackedDexFile)inputDexFile, options.dumpFileName, options.apiLevel); |
| } |
| |
| return !errorOccurred; |
| } |
| |
| /** |
| * Run! |
| */ |
| public static void main(String[] args) throws IOException { |
| Locale locale = new Locale("en", "US"); |
| Locale.setDefault(locale); |
| |
| CommandLineParser parser = new PosixParser(); |
| CommandLine commandLine; |
| |
| try { |
| commandLine = parser.parse(options, args); |
| } catch (ParseException ex) { |
| usage(); |
| return; |
| } |
| |
| baksmaliOptions options = new baksmaliOptions(); |
| |
| String[] remainingArgs = commandLine.getArgs(); |
| Option[] clOptions = commandLine.getOptions(); |
| |
| for (int i=0; i<clOptions.length; i++) { |
| Option option = clOptions[i]; |
| String opt = option.getOpt(); |
| |
| switch (opt.charAt(0)) { |
| case 'v': |
| version(); |
| return; |
| case '?': |
| while (++i < clOptions.length) { |
| if (clOptions[i].getOpt().charAt(0) == '?') { |
| usage(true); |
| return; |
| } |
| } |
| usage(false); |
| return; |
| case 'o': |
| options.outputDirectory = commandLine.getOptionValue("o"); |
| break; |
| case 'p': |
| options.noParameterRegisters = true; |
| break; |
| case 'l': |
| options.useLocalsDirective = true; |
| break; |
| case 's': |
| options.useSequentialLabels = true; |
| break; |
| case 'b': |
| options.outputDebugInfo = false; |
| break; |
| case 'd': |
| options.bootClassPathDirs.add(option.getValue()); |
| break; |
| case 'f': |
| options.addCodeOffsets = true; |
| break; |
| case 'r': |
| String[] values = commandLine.getOptionValues('r'); |
| int registerInfo = 0; |
| |
| if (values == null || values.length == 0) { |
| registerInfo = baksmaliOptions.ARGS | baksmaliOptions.DEST; |
| } else { |
| for (String value: values) { |
| if (value.equalsIgnoreCase("ALL")) { |
| registerInfo |= baksmaliOptions.ALL; |
| } else if (value.equalsIgnoreCase("ALLPRE")) { |
| registerInfo |= baksmaliOptions.ALLPRE; |
| } else if (value.equalsIgnoreCase("ALLPOST")) { |
| registerInfo |= baksmaliOptions.ALLPOST; |
| } else if (value.equalsIgnoreCase("ARGS")) { |
| registerInfo |= baksmaliOptions.ARGS; |
| } else if (value.equalsIgnoreCase("DEST")) { |
| registerInfo |= baksmaliOptions.DEST; |
| } else if (value.equalsIgnoreCase("MERGE")) { |
| registerInfo |= baksmaliOptions.MERGE; |
| } else if (value.equalsIgnoreCase("FULLMERGE")) { |
| registerInfo |= baksmaliOptions.FULLMERGE; |
| } else { |
| usage(); |
| return; |
| } |
| } |
| |
| if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) { |
| registerInfo &= ~baksmaliOptions.MERGE; |
| } |
| } |
| options.registerInfo = registerInfo; |
| break; |
| case 'c': |
| String bcp = commandLine.getOptionValue("c"); |
| if (bcp != null && bcp.charAt(0) == ':') { |
| options.addExtraClassPath(bcp); |
| } else { |
| options.setBootClassPath(bcp); |
| } |
| break; |
| case 'x': |
| options.deodex = true; |
| break; |
| case 'X': |
| options.experimental = true; |
| break; |
| case 'm': |
| options.noAccessorComments = true; |
| break; |
| case 'a': |
| options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); |
| break; |
| case 'j': |
| options.jobs = Integer.parseInt(commandLine.getOptionValue("j")); |
| break; |
| case 'i': |
| String rif = commandLine.getOptionValue("i"); |
| options.setResourceIdFiles(rif); |
| break; |
| case 't': |
| options.useImplicitReferences = true; |
| break; |
| case 'e': |
| options.dexEntry = commandLine.getOptionValue("e"); |
| break; |
| case 'k': |
| options.checkPackagePrivateAccess = true; |
| break; |
| case 'n': |
| options.normalizeVirtualMethods = true; |
| break; |
| case 'N': |
| options.disassemble = false; |
| break; |
| case 'D': |
| options.dump = true; |
| options.dumpFileName = commandLine.getOptionValue("D"); |
| break; |
| case 'I': |
| options.ignoreErrors = true; |
| break; |
| case 'T': |
| options.customInlineDefinitions = new File(commandLine.getOptionValue("T")); |
| break; |
| default: |
| assert false; |
| } |
| } |
| |
| if (remainingArgs.length != 1) { |
| usage(); |
| return; |
| } |
| |
| String inputDexPath = remainingArgs[0]; |
| File dexFileFile = new File(inputDexPath); |
| if (!dexFileFile.exists()) { |
| System.err.println("Can't find the file " + inputDexPath); |
| System.exit(1); |
| } |
| |
| //Read in and parse the dex file |
| DexBackedDexFile dexFile = null; |
| try { |
| dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel, options.experimental); |
| } catch (MultipleDexFilesException ex) { |
| System.err.println(String.format("%s contains multiple dex files. You must specify which one to " + |
| "disassemble with the -e option", dexFileFile.getName())); |
| System.err.println("Valid entries include:"); |
| |
| for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) { |
| System.err.println(oatDexFile.filename); |
| } |
| System.exit(1); |
| } |
| |
| if (dexFile.hasOdexOpcodes()) { |
| if (!options.deodex) { |
| System.err.println("Warning: You are disassembling an odex file without deodexing it. You"); |
| System.err.println("won't be able to re-assemble the results unless you deodex it with the -x"); |
| System.err.println("option"); |
| options.allowOdex = true; |
| } |
| } else { |
| options.deodex = false; |
| } |
| |
| if (options.dump) { |
| if (options.dumpFileName == null) { |
| options.dumpFileName = inputDexPath + ".dump"; |
| } |
| } |
| |
| try { |
| if (!run(options, dexFile)) { |
| System.exit(1); |
| } |
| } catch (IllegalArgumentException ex) { |
| System.err.println(ex.getMessage()); |
| System.exit(1); |
| } |
| } |
| |
| /** |
| * Prints the usage message. |
| */ |
| private static void usage(boolean printDebugOptions) { |
| SmaliHelpFormatter formatter = new SmaliHelpFormatter(); |
| int consoleWidth = ConsoleUtil.getConsoleWidth(); |
| if (consoleWidth <= 0) { |
| consoleWidth = 80; |
| } |
| |
| formatter.setWidth(consoleWidth); |
| |
| formatter.printHelp("java -jar baksmali.jar [options] <dex-file>", |
| "disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null); |
| } |
| |
| private static void usage() { |
| usage(false); |
| } |
| |
| /** |
| * Prints the version message. |
| */ |
| protected static void version() { |
| System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)"); |
| System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); |
| System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); |
| System.exit(0); |
| } |
| |
| @SuppressWarnings("AccessStaticViaInstance") |
| private static void buildOptions() { |
| Option versionOption = OptionBuilder.withLongOpt("version") |
| .withDescription("prints the version then exits") |
| .create("v"); |
| |
| Option helpOption = OptionBuilder.withLongOpt("help") |
| .withDescription("prints the help message then exits. Specify twice for debug options") |
| .create("?"); |
| |
| Option outputDirOption = OptionBuilder.withLongOpt("output") |
| .withDescription("the directory where the disassembled files will be placed. The default is out") |
| .hasArg() |
| .withArgName("DIR") |
| .create("o"); |
| |
| Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers") |
| .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " + |
| "parameters") |
| .create("p"); |
| |
| Option deodexerantOption = OptionBuilder.withLongOpt("deodex") |
| .withDescription("deodex the given odex file. This option is ignored if the input file is not an " + |
| "odex file") |
| .create("x"); |
| |
| Option experimentalOption = OptionBuilder.withLongOpt("experimental") |
| .withDescription("enable experimental opcodes to be disassembled, even if they aren't necessarily supported in the Android runtime yet") |
| .create("X"); |
| |
| Option useLocalsOption = OptionBuilder.withLongOpt("use-locals") |
| .withDescription("output the .locals directive with the number of non-parameter registers, rather" + |
| " than the .register directive with the total number of register") |
| .create("l"); |
| |
| Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels") |
| .withDescription("create label names using a sequential numbering scheme per label type, rather than " + |
| "using the bytecode address") |
| .create("s"); |
| |
| Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info") |
| .withDescription("don't write out debug info (.local, .param, .line, etc.)") |
| .create("b"); |
| |
| Option registerInfoOption = OptionBuilder.withLongOpt("register-info") |
| .hasOptionalArgs() |
| .withArgName("REGISTER_INFO_TYPES") |
| .withValueSeparator(',') |
| .withDescription("print the specificed type(s) of register information for each instruction. " + |
| "\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " + |
| "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " + |
| "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " + |
| "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " + |
| "pre-instruction register has been merged from more than 1 different post-instruction " + |
| "register from its predecessors\nFULLMERGE: For each register that would be printed by " + |
| "MERGE, also show the incoming register types that were merged") |
| .create("r"); |
| |
| Option classPathOption = OptionBuilder.withLongOpt("bootclasspath") |
| .withDescription("A colon-separated list of bootclasspath jar/oat files to use for analysis. Add an " + |
| "initial colon to specify that the jars/oats should be appended to the default bootclasspath " + |
| "instead of replacing it") |
| .hasOptionalArg() |
| .withArgName("BOOTCLASSPATH") |
| .create("c"); |
| |
| 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 codeOffsetOption = OptionBuilder.withLongOpt("code-offsets") |
| .withDescription("add comments to the disassembly containing the code offset for each address") |
| .create("f"); |
| |
| Option noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments") |
| .withDescription("don't output helper comments for synthetic accessors") |
| .create("m"); |
| |
| 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 jobsOption = OptionBuilder.withLongOpt("jobs") |
| .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + |
| "maximum of 6") |
| .hasArg() |
| .withArgName("NUM_THREADS") |
| .create("j"); |
| |
| Option resourceIdFilesOption = OptionBuilder.withLongOpt("resource-id-files") |
| .withDescription("the resource ID files to use, for analysis. A colon-separated list of prefix=file " + |
| "pairs. For example R=res/values/public.xml:" + |
| "android.R=$ANDROID_HOME/platforms/android-19/data/res/values/public.xml") |
| .hasArg() |
| .withArgName("FILES") |
| .create("i"); |
| |
| Option noImplicitReferencesOption = OptionBuilder.withLongOpt("implicit-references") |
| .withDescription("Use implicit (type-less) method and field references") |
| .create("t"); |
| |
| Option checkPackagePrivateAccessOption = OptionBuilder.withLongOpt("check-package-private-access") |
| .withDescription("When deodexing, use the package-private access check when calculating vtable " + |
| "indexes. It should only be needed for 4.2.0 odexes. The functionality was reverted for " + |
| "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" + |
| " (<dexfile>.dump by default), along with the normal disassembly") |
| .hasOptionalArg() |
| .withArgName("FILE") |
| .create("D"); |
| |
| Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors") |
| .withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," + |
| " ignoring the class if needed, and continuing with the next class. The default" + |
| " behavior is to stop disassembling and exit once an error is encountered") |
| .create("I"); |
| |
| Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly") |
| .withDescription("suppresses the output of the disassembly") |
| .create("N"); |
| |
| Option inlineTableOption = OptionBuilder.withLongOpt("inline-table") |
| .withDescription("specify a file containing a custom inline method table to use for deodexing") |
| .hasArg() |
| .withArgName("FILE") |
| .create("T"); |
| |
| Option dexEntryOption = OptionBuilder.withLongOpt("dex-file") |
| .withDescription("looks for dex file named DEX_FILE, defaults to classes.dex") |
| .withArgName("DEX_FILE") |
| .hasArg() |
| .create("e"); |
| |
| basicOptions.addOption(versionOption); |
| basicOptions.addOption(helpOption); |
| basicOptions.addOption(outputDirOption); |
| basicOptions.addOption(noParameterRegistersOption); |
| basicOptions.addOption(deodexerantOption); |
| basicOptions.addOption(experimentalOption); |
| basicOptions.addOption(useLocalsOption); |
| basicOptions.addOption(sequentialLabelsOption); |
| basicOptions.addOption(noDebugInfoOption); |
| basicOptions.addOption(registerInfoOption); |
| basicOptions.addOption(classPathOption); |
| basicOptions.addOption(classPathDirOption); |
| basicOptions.addOption(codeOffsetOption); |
| basicOptions.addOption(noAccessorCommentsOption); |
| basicOptions.addOption(apiLevelOption); |
| basicOptions.addOption(jobsOption); |
| basicOptions.addOption(resourceIdFilesOption); |
| basicOptions.addOption(noImplicitReferencesOption); |
| basicOptions.addOption(dexEntryOption); |
| basicOptions.addOption(checkPackagePrivateAccessOption); |
| basicOptions.addOption(normalizeVirtualMethods); |
| |
| debugOptions.addOption(dumpOption); |
| debugOptions.addOption(ignoreErrorsOption); |
| debugOptions.addOption(noDisassemblyOption); |
| debugOptions.addOption(inlineTableOption); |
| |
| for (Object option: basicOptions.getOptions()) { |
| options.addOption((Option)option); |
| } |
| for (Object option: debugOptions.getOptions()) { |
| options.addOption((Option)option); |
| } |
| } |
| |
| @Nonnull |
| private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) { |
| if (apiLevel < 9) { |
| return Lists.newArrayList( |
| "/system/framework/core.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/services.jar"); |
| } else if (apiLevel < 12) { |
| return Lists.newArrayList( |
| "/system/framework/core.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/services.jar", |
| "/system/framework/core-junit.jar"); |
| } else if (apiLevel < 14) { |
| return Lists.newArrayList( |
| "/system/framework/core.jar", |
| "/system/framework/apache-xml.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/services.jar", |
| "/system/framework/core-junit.jar"); |
| } else if (apiLevel < 16) { |
| return Lists.newArrayList( |
| "/system/framework/core.jar", |
| "/system/framework/core-junit.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/services.jar", |
| "/system/framework/apache-xml.jar", |
| "/system/framework/filterfw.jar"); |
| } else if (apiLevel < 21) { |
| // this is correct as of api 17/4.2.2 |
| return Lists.newArrayList( |
| "/system/framework/core.jar", |
| "/system/framework/core-junit.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/telephony-common.jar", |
| "/system/framework/mms-common.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/services.jar", |
| "/system/framework/apache-xml.jar"); |
| } else if (apiLevel < 26) { |
| return Lists.newArrayList( |
| "/system/framework/core-libart.jar", |
| "/system/framework/conscrypt.jar", |
| "/system/framework/okhttp.jar", |
| "/system/framework/core-junit.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/telephony-common.jar", |
| "/system/framework/voip-common.jar", |
| "/system/framework/ims-common.jar", |
| "/system/framework/mms-common.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/apache-xml.jar"); |
| } else { // api >= 26 |
| // TODO: verify, add new ones? |
| return Lists.newArrayList( |
| "/system/framework/core-libart.jar", |
| "/system/framework/conscrypt.jar", |
| "/system/framework/okhttp.jar", |
| "/system/framework/bouncycastle.jar", |
| "/system/framework/ext.jar", |
| "/system/framework/framework.jar", |
| "/system/framework/telephony-common.jar", |
| "/system/framework/voip-common.jar", |
| "/system/framework/ims-common.jar", |
| "/system/framework/mms-common.jar", |
| "/system/framework/android.policy.jar", |
| "/system/framework/apache-xml.jar"); |
| } |
| } |
| } |