• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
6import glob
7import optparse
8import os
9import shutil
10import subprocess
11import sys
12import utils
13
14USAGE = 'usage: %prog [options] <apk>'
15
16def parse_options():
17  parser = optparse.OptionParser(usage=USAGE)
18  parser.add_option('--dex',
19                    help='directory with dex files to use instead of those in the apk',
20                    default=None)
21  parser.add_option('--out',
22                    help='output file (default ./$(basename <apk>))',
23                    default=None)
24  parser.add_option('--keystore',
25                    help='keystore file (default ~/.android/app.keystore)',
26                    default=None)
27  parser.add_option('--install',
28                    help='install the generated apk with adb options -t -r -d',
29                    default=False,
30                    action='store_true')
31  parser.add_option('--adb-options',
32                    help='additional adb options when running adb',
33                    default=None)
34  (options, args) = parser.parse_args()
35  if len(args) != 1:
36    parser.error('Expected <apk> argument, got: ' + ' '.join(args))
37  apk = args[0]
38  if not options.out:
39    options.out = os.path.basename(apk)
40  if not options.keystore:
41    options.keystore = findKeystore()
42  return (options, apk)
43
44def findKeystore():
45  return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
46
47def repack(processed_out, original_apk, temp):
48  processed_apk = os.path.join(temp, 'processed.apk')
49  shutil.copyfile(original_apk, processed_apk)
50  if not processed_out:
51    print 'Using original APK as is'
52    return processed_apk
53  print 'Repacking APK with dex files from', processed_apk
54  with utils.ChangedWorkingDirectory(temp):
55    cmd = ['zip', '-d', 'processed.apk', '*.dex']
56    utils.PrintCmd(cmd)
57    subprocess.check_call(cmd)
58  if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
59    cmd = ['unzip', processed_out, '-d', temp]
60    utils.PrintCmd(cmd)
61    subprocess.check_call(cmd)
62    processed_out = temp
63  with utils.ChangedWorkingDirectory(processed_out):
64    dex = glob.glob('*.dex')
65    cmd = ['zip', '-u', '-9', processed_apk] + dex
66    utils.PrintCmd(cmd)
67    subprocess.check_call(cmd)
68  return processed_apk
69
70def sign(unsigned_apk, keystore, temp):
71  print 'Signing (ignore the warnings)'
72  cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
73  utils.PrintCmd(cmd)
74  subprocess.call(cmd)
75  signed_apk = os.path.join(temp, 'unaligned.apk')
76  cmd = [
77    'jarsigner',
78    '-sigalg', 'SHA1withRSA',
79    '-digestalg', 'SHA1',
80    '-keystore', keystore,
81    '-storepass', 'android',
82    '-signedjar', signed_apk,
83    unsigned_apk,
84    'androiddebugkey'
85  ]
86  utils.PrintCmd(cmd)
87  subprocess.check_call(cmd)
88  return signed_apk
89
90def align(signed_apk, temp):
91  print 'Aligning'
92  aligned_apk = os.path.join(temp, 'aligned.apk')
93  cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
94  print ' '.join(cmd)
95  subprocess.check_call(cmd)
96  return signed_apk
97
98def main():
99  (options, apk) = parse_options()
100  with utils.TempDir() as temp:
101    processed_apk = None
102    if options.dex:
103      processed_apk = repack(options.dex, apk, temp)
104    else:
105      print 'Signing original APK without modifying dex files'
106      processed_apk = os.path.join(temp, 'processed.apk')
107      shutil.copyfile(apk, processed_apk)
108    signed_apk = sign(processed_apk, options.keystore, temp)
109    aligned_apk = align(signed_apk, temp)
110    print 'Writing result to', options.out
111    shutil.copyfile(aligned_apk, options.out)
112    adb_cmd = ['adb']
113    if options.adb_options:
114      adb_cmd.extend(
115          [option for option in options.adb_options.split(' ') if option])
116    if options.install:
117      adb_cmd.extend(['install', '-t', '-r', '-d', options.out]);
118      utils.PrintCmd(adb_cmd)
119      subprocess.check_call(adb_cmd)
120  return 0
121
122if __name__ == '__main__':
123  sys.exit(main())
124