private static File ensureExists(final String filePath) { File f = new File(filePath); if (!f.exists()) { Console.die(f.getPath() + " does not exist."); } return f; }
public static void enforceOverwrite(final String filePath, final String whatIsIt) { if (!AllowOverwrites) { if (new File(filePath).exists()) { Console.die( "The " + whatIsIt + " " + filePath + " already exists.\n" + "Delete and try again or use --force option."); } } }
// returns false if options are not valid public static void parseArgs(String[] args) { // https://www.karlin.mff.cuni.cz/network/prirucky/javatut/java/cmdLineArgs/parsing.html String arg; int argPos = 0; while ((argPos < args.length) && args[argPos].startsWith("-")) { arg = args[argPos++]; if (arg.startsWith("-v") || arg.startsWith("--verbose")) { String level = arg.startsWith("-v") ? arg.replace("-v", "") : arg.replace("--verbose", ""); VerboseLevel = level.isEmpty() ? 1 : Integer.parseInt(level); if (VerboseLevel > MaxVerboseLevel) { VerboseLevel = MaxVerboseLevel; } else if (VerboseLevel < 1) { VerboseLevel = 1; } Console.VerboseLevel = VerboseLevel; Console.debug("Option: Verbose level set to " + VerboseLevel); } else if (arg.equals("-s") || arg.equals("--skip-assembly")) { Console.debug("Option: Skipping assembly."); SkipAssembly = true; } else if (arg.equals("--skip-cleanup")) { Console.debug("Option: Skipping cleanup."); SkipCleanup = true; } else if (arg.equals("--decode-res")) { Console.debug("Option: Decoding resources."); DecodeResources = true; } else if (arg.equals("-d") || arg.equals("--detect-only")) { Console.debug("Option: Determining protection information only."); DetectOnly = true; } else if (arg.equals("-f") || arg.equals("--force")) { Console.debug("Option: Allowing file overwrites."); AllowOverwrites = true; } else if (arg.equals("--sign-only")) { Console.debug("Option: Signing only."); SignOnly = true; AllowOverwrites = true; // disable any checking, wont be needed SkipCleanup = true; } else if (arg.equals("--sign-key")) { arg = args[argPos++]; SignKey = new File(arg); if (!SignKey.exists()) { Console.die("Signing key does not exist: " + arg); } } else if (arg.equals("--sign-cert")) { arg = args[argPos++]; SignCert = new File(arg); if (!SignCert.exists()) { Console.die("Signing certificate does not exist: " + arg); } } else if (arg.equals("--sign-pass")) { SignPass = args[argPos++]; } else if (arg.equals("--info-only")) { Console.debug("Option: Getting info only."); InfoOnly = true; AllowOverwrites = true; // disable any checking, wont be needed } else if (arg.equals("--assemble-only")) { Console.debug("Option: Assembling only."); AssembleOnly = true; } else if (arg.equals("--fplist")) { ListFPsOnly = true; } else if (arg.equals("--fpexclude")) { arg = args[argPos++]; String fps[] = arg.split(","); for (String fp : fps) { ExcludedFPs.add(fp.trim()); } Console.debug("Option: Excluding fingerprints: " + arg); } else if (arg.equals("--fpinclude")) { arg = args[argPos++]; String fps[] = arg.split(","); for (String fp : fps) { IncludedFPs.add(fp.trim()); } Console.debug("Option: Including fingerprints: " + arg); } else if (arg.equals("--chksigs")) { arg = args[argPos++]; CheckSigsBehavior = Integer.parseInt(arg); } else if (arg.equals("--getpi")) { arg = args[argPos++]; GetPIBehavior = Integer.parseInt(arg); } else if (arg.equals("--sigvfy")) { arg = args[argPos++]; SigVerifyBehavior = Integer.parseInt(arg); } else if (arg.equals("--spoof-id")) { arg = args[argPos++]; DeviceIDSpoofType = Integer.parseInt(arg); // These are enabled=false by default IncludedFPs.add("Hook Get Secure Setting"); IncludedFPs.add("Hook Device ID"); if (DeviceIDSpoofType == 5) { DeviceIDSpoof = args[argPos++]; // device id must be 15 characters, numeric only if (!DeviceIDSpoof.matches("\\d{15}")) { Console.die("Spoofed device ID is must be 15 digits.", -1); } } } else if (arg.equals("--spoof-account")) { arg = args[argPos++]; AccountNameSpoofType = Integer.parseInt(arg); // These are enabled=false by default IncludedFPs.add("Hook Get Account Name"); if (AccountNameSpoofType == 3) { AccountNameSpoof = args[argPos++].toUpperCase(); if (!AccountNameSpoof.matches("[a-zA-Z0-9\\.]+")) { Console.die("Spoofed Account Name must be alpha-numeric!", -1); } } } else if (arg.equals("--spoof-wifimac")) { arg = args[argPos++]; WifiMacSpoofType = Integer.parseInt(arg); // These are enabled=false by default IncludedFPs.add("Hook Get Wifi Mac"); if (WifiMacSpoofType == 3) { WifiMacSpoof = args[argPos++].toUpperCase(); if (!WifiMacSpoof.matches("(?m)^([0-9A-F]{2}([:-]|$)){6}$")) { Console.die("Spoofed Wifi MAC must be of the form 11:22:33:AA:BB:CC !", -1); } } } else if (arg.equals("--spoof-btmac")) { arg = args[argPos++]; BTMacSpoofType = Integer.parseInt(arg); // These are enabled=false by default IncludedFPs.add("Hook Bluetooth MAC"); if (BTMacSpoofType == 3) { BTMacSpoof = args[argPos++].toUpperCase(); if (!BTMacSpoof.matches("(?m)^([0-9A-F]{2}([:-]|$)){6}$")) { Console.die("Spoofed BT MAC must be of the form 11:22:33:AA:BB:CC !", -1); } } } else if (arg.equals("--spoof-model")) { arg = args[argPos++]; // This is enabled=false by default IncludedFPs.add("Hook Device Model"); Options.DeviceModelSpoof = arg; Console.debug("Spoofing model as " + Options.DeviceModelSpoof); } else if (arg.equals("--spoof-network")) { arg = args[argPos++]; // This is enabled=false by default IncludedFPs.add("Hook Network Operator"); Options.NetworkOperatorSpoof = arg; Console.debug("Spoofing network operator as " + Options.NetworkOperatorSpoof); } else if (arg.equals("--spoof-manufacturer")) { arg = args[argPos++]; // This is enabled=false by default IncludedFPs.add("Hook Device Manufacturer"); Options.DeviceManufacturerSpoof = arg; Console.debug("Spoofing manufacturer as " + Options.DeviceManufacturerSpoof); } else if (arg.equals("--key-apk")) { KeyApkPath = args[argPos++]; File f = new File(KeyApkPath); if (!f.exists()) { Console.die("The key apk " + KeyApkPath + " does not exist!"); } } else if (arg.equals("--trace")) { Console.debug("Option: Enabling method trace and debug hooks."); IncludedFPs.add("Method Trace"); IncludedFPs.add("Method Trace FixLocals"); DebugHooks = true; } else if (arg.equals("--translate")) { Console.debug("Option: Enabling Smali string language translation."); SmaliHinter.enableTranslations(); } else if (arg.equals("-h") || arg.equals("--help")) { showUsage(); System.exit(0); } else if (arg.equals("--dbghooks")) { Console.debug("Option: Using debugging hooks."); DebugHooks = true; } else if (arg.equals("--skip-hints")) { Console.debug("Option: Skipping smali hint generation."); SkipHints = true; } else { Console.die("Unknown option: " + arg + ".", -1); } } // No bother processing the rest of the logic, we just want FP listing if (ListFPsOnly) { return; } // If either is alone, no bueno! if ((SignKey != null) ^ (SignKey != null)) { Console.die("Options --sign-key and --sign-cert must be used together."); } if (argPos == args.length) { Console.error("Oopsy! Dump path / Apk file missing. Please review:"); showUsage(); System.exit(-1); } // Make sure smali dump / apk exists File input = ensureExists(args[argPos]); if (input.isDirectory()) { if (SignOnly) { Console.die("Sign only option enabled but input is not an APK file."); } SmaliDir = input.getPath(); if (!SkipAssembly) { if ((argPos + 1) >= args.length) { Console.die("Output APK required."); } OutputApk = args[argPos + 1]; ensureExists(OutputApk); } } else { if (AssembleOnly) { Console.die("Option --assemble-only only works with smali dump directories."); } ApkPath = input.getPath(); OutputApk = ApkPath.replace(".apk", ""); if (args.length <= (argPos + 1)) { // overwrite original by default if (SignOnly || DetectOnly || SkipAssembly || InfoOnly) { OutputApk = ApkPath; } else { OutputApk += "_sequenced.apk"; } } else { OutputApk = args[argPos + 1]; } } Console.debug("Output apk: " + OutputApk); Console.debug("Apk path: " + ApkPath); if (!DetectOnly && !SkipAssembly) { enforceOverwrite(OutputApk, "output file"); } }