1#!/usr/bin/env python 2# 3# Copyright 2017 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from __future__ import print_function 18from xml.dom import minidom 19 20import argparse 21import itertools 22import os 23import re 24import subprocess 25import sys 26import tempfile 27import shutil 28 29DEVICE_PREFIX = 'device:' 30ANDROID_NAME_REGEX = r'A: android:name\([\S]+\)=\"([\S]+)\"' 31ANDROID_PROTECTION_LEVEL_REGEX = \ 32 r'A: android:protectionLevel\([^\)]+\)=\(type [\S]+\)0x([\S]+)' 33BASE_XML_FILENAME = 'privapp-permissions-platform.xml' 34 35HELP_MESSAGE = """\ 36Generates privapp-permissions.xml file for priv-apps. 37 38Usage: 39 Specify which apk to generate priv-app permissions for. If no apk is \ 40specified, this will default to all APKs under "<ANDROID_PRODUCT_OUT>/\ 41<all the partitions>/priv-app/". 42 43 To specify a target partition(s), use "-p <PARTITION>," where <PARTITION> \ 44can be "system", "product", "system/product", "system_ext", \ 45"system/system_ext", "system,system/product,vendor,system_ext", etc. 46 47 When using adb, adb pull can take a long time. To see the adb pull \ 48progress, use "-v" 49 50Examples: 51 52 For all APKs under $ANDROID_PRODUCT_OUT/<all partitions>/priv-app/: 53 # If the build environment has not been set up, do so: 54 . build/envsetup.sh 55 lunch product_name 56 m -j32 57 # then use: 58 cd development/tools/privapp_permissions/ 59 ./privapp_permissions.py 60 # or to search for apks in "product" partition 61 ./privapp_permissions.py -p product 62 # or to search for apks in system, product, and vendor partitions 63 ./privapp_permissions.py -p system,product,vendor 64 65 For an APK against $ANDROID_PRODUCT_OUT/<all partitions>/etc/permissions/: 66 ./privapp_permissions.py path/to/the.apk 67 # or against /product/etc/permissions/ 68 ./privapp_permissions.py path/to/the.apk -p product 69 70 For an APK already on the device against /<all partitions>/etc/permissions/: 71 ./privapp_permissions.py device:/device/path/to/the.apk 72 # or against /product/etc/permissions/ 73 ./privapp_permissions.py path/to/the.apk -p product 74 75 For all APKs on a device under /<all partitions>/priv-app/: 76 ./privapp_permissions.py -d 77 # or if more than one device is attached 78 ./privapp_permissions.py -s <ANDROID_SERIAL> 79 # or for all APKs on the "system" partitions 80 ./privapp_permissions.py -d -p system 81""" 82 83# An array of all generated temp directories. 84temp_dirs = [] 85# An array of all generated temp files. 86temp_files = [] 87 88def vprint(enable, message, *args): 89 if enable: 90 # Use stderr to avoid poluting print_xml result 91 sys.stderr.write(message % args + '\n') 92 93class MissingResourceError(Exception): 94 """Raised when a dependency cannot be located.""" 95 96 97class Adb(object): 98 """A small wrapper around ADB calls.""" 99 100 def __init__(self, path, serial=None, verbose=False): 101 self.path = path 102 self.serial = serial 103 self.verbose = verbose 104 105 def pull(self, src, dst=None): 106 """A wrapper for `adb -s <SERIAL> pull <src> <dst>`. 107 Args: 108 src: The source path on the device 109 dst: The destination path on the host 110 111 Throws: 112 subprocess.CalledProcessError upon pull failure. 113 """ 114 if not dst: 115 if self.call('shell \'if [ -d "%s" ]; then echo True; fi\'' % src): 116 dst = tempfile.mkdtemp() 117 temp_dirs.append(dst) 118 else: 119 _, dst = tempfile.mkstemp() 120 temp_files.append(dst) 121 self.call('pull %s %s' % (src, dst), False, self.verbose) 122 return dst 123 124 def call(self, cmdline, getoutput=True, verbose=False): 125 """Calls an adb command. 126 127 Throws: 128 subprocess.CalledProcessError upon command failure. 129 """ 130 command = '%s -s %s %s' % (self.path, self.serial, cmdline) 131 if getoutput: 132 return get_output(command) 133 else: 134 # Handle verbose mode only when the output is not needed 135 # This is mainly for adb pull, which can take a long time 136 extracmd = ' > /dev/null 2>&1' 137 if verbose: 138 # Use stderr to avoid poluting print_xml result 139 extracmd = ' 1>&2' 140 os.system(command + extracmd) 141 142class Aapt(object): 143 def __init__(self, path): 144 self.path = path 145 146 def call(self, arguments): 147 """Run an aapt command with the given args. 148 149 Args: 150 arguments: a list of string arguments 151 Returns: 152 The output of the aapt command as a string. 153 """ 154 output = subprocess.check_output([self.path] + arguments, 155 stderr=subprocess.STDOUT) 156 return output.decode(encoding='UTF-8') 157 158 159class Resources(object): 160 """A class that contains the resources needed to generate permissions. 161 162 Attributes: 163 adb: A wrapper class around ADB with a default serial. Only needed when 164 using -d, -s, or "device:" 165 _aapt_path: The path to aapt. 166 """ 167 168 def __init__(self, adb_path=None, aapt_path=None, use_device=None, 169 serial=None, partitions=None, verbose=False, 170 writetodisk=None, systemfile=None, productfile=None, 171 apks=None): 172 self.adb = Resources._resolve_adb(adb_path) 173 self.aapt = Resources._resolve_aapt(aapt_path) 174 175 self.verbose = self.adb.verbose = verbose 176 self.writetodisk = writetodisk 177 self.systemfile = systemfile; 178 self.productfile = productfile; 179 180 self._is_android_env = 'ANDROID_PRODUCT_OUT' in os.environ and \ 181 'ANDROID_HOST_OUT' in os.environ 182 use_device = use_device or serial or \ 183 (apks and DEVICE_PREFIX in '&'.join(apks)) 184 185 self.adb.serial = self._resolve_serial(use_device, serial) 186 187 if self.adb.serial: 188 self.adb.call('root') 189 self.adb.call('wait-for-device') 190 191 if self.adb.serial is None and not self._is_android_env: 192 raise MissingResourceError( 193 'You must either set up your build environment, or specify a ' 194 'device to run against. See --help for more info.') 195 196 if apks and (partitions == "all" or partitions.find(',') != -1): 197 # override the partition to "system 198 print('\n# Defaulting the target partition to "system". ' 199 'Use -p option to specify the target partition ' 200 '(must provide one target instead of a list).\n', 201 file=sys.stderr) 202 partitions = "system" 203 204 if partitions == "all": 205 # This is the default scenario 206 # Find all the partitions where priv-app exists 207 self.partitions = self._get_partitions() 208 else: 209 # Initialize self.partitions with the specified partitions 210 self.partitions = [] 211 for p in partitions.split(','): 212 if p.endswith('/'): 213 p = p[:-1] 214 self.partitions.append(p) 215 # Check if the directory exists 216 self._check_dir(p + '/priv-app') 217 218 vprint(self.verbose, 219 '# Examining the partitions: ' + str(self.partitions)) 220 221 # Create dictionary of array (partition as the key) 222 self.privapp_apks = self._resolve_apks(apks, self.partitions) 223 self.permissions_dirs = self._resolve_sys_paths('etc/permissions', 224 self.partitions) 225 self.sysconfig_dirs = self._resolve_sys_paths('etc/sysconfig', 226 self.partitions) 227 228 # Always use the one in /system partition, 229 # as that is the only place we will find framework-res.apk 230 self.framework_res_apk = self._resolve_sys_path('system/framework/' 231 'framework-res.apk') 232 @staticmethod 233 def _resolve_adb(adb_path): 234 """Resolves ADB from either the cmdline argument or the os environment. 235 236 Args: 237 adb_path: The argument passed in for adb. Can be None. 238 Returns: 239 An Adb object. 240 Raises: 241 MissingResourceError if adb cannot be resolved. 242 """ 243 if adb_path: 244 if os.path.isfile(adb_path): 245 adb = adb_path 246 else: 247 raise MissingResourceError('Cannot resolve adb: No such file ' 248 '"%s" exists.' % adb_path) 249 else: 250 try: 251 adb = get_output('which adb').strip() 252 except subprocess.CalledProcessError as e: 253 print('Cannot resolve adb: ADB does not exist within path. ' 254 'Did you forget to setup the build environment or set ' 255 '--adb?', 256 file=sys.stderr) 257 raise MissingResourceError(e) 258 # Start the adb server immediately so server daemon startup 259 # does not get added to the output of subsequent adb calls. 260 try: 261 get_output('%s start-server' % adb) 262 return Adb(adb) 263 except: 264 print('Unable to reach adb server daemon.', file=sys.stderr) 265 raise 266 267 @staticmethod 268 def _resolve_aapt(aapt_path): 269 """Resolves AAPT from either the cmdline argument or the os environment. 270 271 Returns: 272 An Aapt Object 273 """ 274 if aapt_path: 275 if os.path.isfile(aapt_path): 276 return Aapt(aapt_path) 277 else: 278 raise MissingResourceError('Cannot resolve aapt: No such file ' 279 '%s exists.' % aapt_path) 280 else: 281 try: 282 return Aapt(get_output('which aapt').strip()) 283 except subprocess.CalledProcessError: 284 print('Cannot resolve aapt: AAPT does not exist within path. ' 285 'Did you forget to setup the build environment or set ' 286 '--aapt?', 287 file=sys.stderr) 288 raise 289 290 def _resolve_serial(self, device, serial): 291 """Resolves the serial used for device files or generating permissions. 292 293 Returns: 294 If -s/--serial is specified, it will return that serial. 295 If -d or device: is found, it will grab the only available device. 296 If there are multiple devices, it will use $ANDROID_SERIAL. 297 Raises: 298 MissingResourceError if the resolved serial would not be usable. 299 subprocess.CalledProcessError if a command error occurs. 300 """ 301 if device: 302 if serial: 303 try: 304 output = get_output('%s -s %s get-state' % 305 (self.adb.path, serial)) 306 except subprocess.CalledProcessError: 307 raise MissingResourceError( 308 'Received error when trying to get the state of ' 309 'device with serial "%s". Is it connected and in ' 310 'device mode?' % serial) 311 if 'device' not in output: 312 raise MissingResourceError( 313 'Device "%s" is not in device mode. Reboot the phone ' 314 'into device mode and try again.' % serial) 315 return serial 316 317 elif 'ANDROID_SERIAL' in os.environ: 318 serial = os.environ['ANDROID_SERIAL'] 319 command = '%s -s %s get-state' % (self.adb, serial) 320 try: 321 output = get_output(command) 322 except subprocess.CalledProcessError: 323 raise MissingResourceError( 324 'Device with serial $ANDROID_SERIAL ("%s") not ' 325 'found.' % serial) 326 if 'device' in output: 327 return serial 328 raise MissingResourceError( 329 'Device with serial $ANDROID_SERIAL ("%s") was ' 330 'found, but was not in the "device" state.') 331 332 # Parses `adb devices` so it only returns a string of serials. 333 get_serials_cmd = ('%s devices | tail -n +2 | head -n -1 | ' 334 'cut -f1' % self.adb.path) 335 try: 336 output = get_output(get_serials_cmd) 337 # If multiple serials appear in the output, raise an error. 338 if len(output.split()) > 1: 339 raise MissingResourceError( 340 'Multiple devices are connected. You must specify ' 341 'which device to run against with flag --serial.') 342 return output.strip() 343 except subprocess.CalledProcessError: 344 print('Unexpected error when querying for connected ' 345 'devices.', file=sys.stderr) 346 raise 347 348 def _get_partitions(self): 349 """Find all the partitions to examine 350 351 Returns: 352 The array of partitions where priv-app exists 353 Raises: 354 MissingResourceError find command over adb shell fails. 355 """ 356 if not self.adb.serial: 357 privapp_dirs = get_output('cd %s; find * -name "priv-app"' 358 % os.environ['ANDROID_PRODUCT_OUT'] 359 + ' -type d | grep -v obj').split() 360 else: 361 try: 362 privapp_dirs = self.adb.call('shell find \'/!(proc)\' \ 363 -name "priv-app" -type d').split() 364 except subprocess.CalledProcessError: 365 raise MissingResourceError( 366 '"adb shell find / -name priv-app -type d" did not succeed' 367 ' on device "%s".' % self.adb.serial) 368 369 # Remove 'priv-app' from the privapp_dirs 370 partitions = [] 371 for i in range(len(privapp_dirs)): 372 partitions.append('/'.join(privapp_dirs[i].split('/')[:-1])) 373 374 return partitions 375 376 def _check_dir(self, directory): 377 """Check if a given directory is valid 378 379 Raises: 380 MissingResourceError if a given directory does not exist. 381 """ 382 if not self.adb.serial: 383 if not os.path.isdir(os.environ['ANDROID_PRODUCT_OUT'] 384 + '/' + directory): 385 raise MissingResourceError( 386 '%s does not exist' % directory) 387 else: 388 try: 389 self.adb.call('shell ls %s' % directory) 390 except subprocess.CalledProcessError: 391 raise MissingResourceError( 392 '"adb shell ls %s" did not succeed on ' 393 'device "%s".' % (directory, self.adb.serial)) 394 395 396 def _resolve_apks(self, apks, partitions): 397 """Resolves all APKs to run against. 398 399 Returns: 400 If no apk is specified in the arguments, return all apks in 401 priv-app in all the partitions. 402 Otherwise, returns a list with the specified apk. 403 Throws: 404 MissingResourceError if the specified apk or 405 <partition>/priv-app cannot be found. 406 """ 407 results = {} 408 if not apks: 409 for p in partitions: 410 results[p] = self._resolve_all_privapps(p) 411 return results 412 413 # The first element is what is passed via '-p' option 414 # (default is overwritten to 'system' when apk is specified) 415 p = partitions[0] 416 results[p] = [] 417 for apk in apks: 418 if apk.startswith(DEVICE_PREFIX): 419 device_apk = apk[len(DEVICE_PREFIX):] 420 try: 421 apk = self.adb.pull(device_apk) 422 except subprocess.CalledProcessError: 423 raise MissingResourceError( 424 'File "%s" could not be located on device "%s".' % 425 (device_apk, self.adb.serial)) 426 results[p].append(apk) 427 elif not os.path.isfile(apk): 428 raise MissingResourceError('File "%s" does not exist.' % apk) 429 else: 430 results[p].append(apk) 431 return results 432 433 def _resolve_all_privapps(self, partition): 434 """Resolves all APKs in <partition>/priv-app 435 436 Returns: 437 Return all apks in <partition>/priv-app 438 Throws: 439 MissingResourceError <partition>/priv-app cannot be found. 440 """ 441 if not self.adb.serial: 442 priv_app_dir = os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 443 partition + '/priv-app') 444 else: 445 try: 446 priv_app_dir = self.adb.pull(partition + '/priv-app/') 447 except subprocess.CalledProcessError: 448 raise MissingResourceError( 449 'Directory "%s/priv-app" could not be pulled from on ' 450 'device "%s".' % (partition, self.adb.serial)) 451 return get_output('find %s -name "*.apk"' % priv_app_dir).split() 452 453 def _resolve_sys_path(self, file_path): 454 """Resolves a path that is a part of an Android System Image.""" 455 if not self.adb.serial: 456 return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], file_path) 457 else: 458 return self.adb.pull(file_path) 459 460 def _resolve_sys_paths(self, file_path, partitions): 461 """Resolves a path that is a part of an Android System Image, for the 462 specified partitions.""" 463 results = {} 464 for p in partitions: 465 results[p] = self._resolve_sys_path(p + '/' + file_path) 466 return results 467 468 469def get_output(command): 470 """Returns the output of the command as a string. 471 472 Throws: 473 subprocess.CalledProcessError if exit status is non-zero. 474 """ 475 output = subprocess.check_output(command, shell=True) 476 # For Python3.4, decode the byte string so it is usable. 477 return output.decode(encoding='UTF-8') 478 479 480def parse_args(): 481 """Parses the CLI.""" 482 parser = argparse.ArgumentParser( 483 description=HELP_MESSAGE, 484 formatter_class=argparse.RawDescriptionHelpFormatter) 485 parser.add_argument( 486 '-d', 487 '--device', 488 action='store_true', 489 default=False, 490 required=False, 491 help='Whether or not to generate the privapp_permissions file for the ' 492 'build already on a device. See -s/--serial below for more ' 493 'details.' 494 ) 495 parser.add_argument( 496 '-v', 497 '--verbose', 498 action='store_true', 499 default=False, 500 required=False, 501 help='Whether or not to enable more verbose logs such as ' 502 'adb pull progress to be shown' 503 ) 504 parser.add_argument( 505 '--adb', 506 type=str, 507 required=False, 508 metavar='<ADB_PATH>', 509 help='Path to adb. If none specified, uses the environment\'s adb.' 510 ) 511 parser.add_argument( 512 '--aapt', 513 type=str, 514 required=False, 515 metavar='<AAPT_PATH>', 516 help='Path to aapt. If none specified, uses the environment\'s aapt.' 517 ) 518 parser.add_argument( 519 '-s', 520 '--serial', 521 type=str, 522 required=False, 523 metavar='<SERIAL>', 524 help='The serial of the device to generate permissions for. If no ' 525 'serial is given, it will pick the only device connected over ' 526 'adb. If multiple devices are found, it will default to ' 527 '$ANDROID_SERIAL. Otherwise, the program will exit with error ' 528 'code 1. If -s is given, -d is not needed.' 529 ) 530 parser.add_argument( 531 '-p', 532 '--partitions', 533 type=str, 534 required=False, 535 default='all', 536 metavar='<PARTITION>', 537 help='The target partition(s) to examine permissions for. ' 538 'It is set to "all" by default, which means all the partitions ' 539 'where priv-app diectory exists will be examined' 540 'Use "," as a delimiter when specifying multiple partitions. ' 541 'E.g. "system,product"' 542 ) 543 parser.add_argument( 544 'apks', 545 nargs='*', 546 type=str, 547 help='A list of paths to priv-app APKs to generate permissions for. ' 548 'To make a path device-side, prefix the path with "device:".' 549 ) 550 parser.add_argument( 551 '-w', 552 '--writetodisk', 553 action='store_true', 554 default=False, 555 required=False, 556 help='Whether or not to store the generated permissions directly to ' 557 'a file. See --systemfile/--productfile for more information.' 558 ) 559 parser.add_argument( 560 '--systemfile', 561 default='./system.xml', 562 required=False, 563 help='Path to system permissions file. Default value is ./system.xml' 564 ) 565 parser.add_argument( 566 '--productfile', 567 default='./product.xml', 568 required=False, 569 help='Path to system permissions file. Default value is ./product.xml' 570 ) 571 cmd_args = parser.parse_args() 572 573 return cmd_args 574 575 576def create_permission_file(resources): 577 """Prints out/creates permission file with missing permissions.""" 578 # First extract privileged permissions from framework-res.apk 579 priv_permissions = extract_priv_permissions(resources.aapt, 580 resources.framework_res_apk) 581 582 results = {} 583 for p in resources.partitions: 584 results[p], apps_redefine_base = \ 585 generate_missing_permissions(resources, priv_permissions, p) 586 enable_print = True 587 vprint(enable_print, '#' * 80) 588 vprint(enable_print, '#') 589 if resources.writetodisk: 590 # Check if it is likely a product partition 591 if p.endswith('product'): 592 out_file_name = resources.productfile; 593 # Check if it is a system partition 594 elif p.endswith('system'): 595 out_file_name = resources.systemfile 596 # Fallback to the partition name itself 597 else: 598 out_file_name = str(p).replace('/', '_') + '.xml' 599 600 out_file = open(out_file_name, 'w') 601 vprint(enable_print, '# %s XML written to %s:', p, out_file_name) 602 vprint(enable_print, '#') 603 vprint(enable_print, '#' * 80) 604 print_xml(results[p], apps_redefine_base, p, out_file) 605 out_file.close() 606 else: 607 vprint(enable_print, '# %s XML:', p) 608 vprint(enable_print, '#') 609 vprint(enable_print, '#' * 80) 610 611 # Print it to stdout regardless of whether writing to a file or not 612 print_xml(results[p], apps_redefine_base, p) 613 614 615def generate_missing_permissions(resources, priv_permissions, partition): 616 """Generates the missing permissions for the specified partition.""" 617 # Parse base XML files in /etc dir, permissions listed there don't have 618 # to be re-added 619 base_permissions = {} 620 base_xml_files = itertools.chain( 621 list_xml_files(resources.permissions_dirs[partition]), 622 list_xml_files(resources.sysconfig_dirs[partition])) 623 624 for xml_file in base_xml_files: 625 parse_config_xml(xml_file, base_permissions) 626 627 apps_redefine_base = [] 628 results = {} 629 for priv_app in resources.privapp_apks[partition]: 630 pkg_info = extract_pkg_and_requested_permissions(resources.aapt, 631 priv_app) 632 pkg_name = pkg_info['package_name'] 633 # get intersection of what's requested by app and by framework 634 priv_perms = get_priv_permissions(pkg_info['permissions'], 635 priv_permissions) 636 # Compute diff against permissions defined in base file 637 if base_permissions and (pkg_name in base_permissions): 638 base_permissions_pkg = base_permissions[pkg_name] 639 priv_perms = remove_base_permissions(priv_perms, 640 base_permissions_pkg) 641 if priv_perms: 642 apps_redefine_base.append(pkg_name) 643 if priv_perms: 644 results[pkg_name] = sorted(priv_perms) 645 646 return results, apps_redefine_base 647 648 649def print_xml(results, apps_redefine_base, partition, fd=sys.stdout): 650 """Print results to the given file.""" 651 fd.write('<?xml version="1.0" encoding="utf-8"?>\n') 652 fd.write('<!-- for the partition: /%s -->\n' % partition) 653 fd.write('<permissions>\n') 654 for package_name in sorted(results): 655 if package_name in apps_redefine_base: 656 fd.write(' <!-- Additional permissions on top of %s -->\n' % 657 BASE_XML_FILENAME) 658 fd.write(' <privapp-permissions package="%s">\n' % package_name) 659 for p in results[package_name]: 660 fd.write(' <permission name="%s"/>\n' % p) 661 fd.write(' </privapp-permissions>\n') 662 fd.write('\n') 663 664 fd.write('</permissions>\n') 665 666 667def remove_base_permissions(priv_perms, base_perms): 668 """Removes set of base_perms from set of priv_perms.""" 669 if (not priv_perms) or (not base_perms): 670 return priv_perms 671 return set(priv_perms) - set(base_perms) 672 673 674def get_priv_permissions(requested_perms, priv_perms): 675 """Return only permissions that are in priv_perms set.""" 676 return set(requested_perms).intersection(set(priv_perms)) 677 678 679def list_xml_files(directory): 680 """Returns a list of all .xml files within a given directory. 681 682 Args: 683 directory: the directory to look for xml files in. 684 """ 685 xml_files = [] 686 for dirName, subdirList, file_list in os.walk(directory): 687 for file in file_list: 688 if file.endswith('.xml'): 689 file_path = os.path.join(dirName, file) 690 xml_files.append(file_path) 691 return xml_files 692 693 694def extract_pkg_and_requested_permissions(aapt, apk_path): 695 """ 696 Extract package name and list of requested permissions from the 697 dump of manifest file 698 """ 699 aapt_args = ['d', 'permissions', apk_path] 700 txt = aapt.call(aapt_args) 701 702 permissions = [] 703 package_name = None 704 raw_lines = txt.split('\n') 705 for line in raw_lines: 706 regex = r"uses-permission.*: name='([\S]+)'" 707 matches = re.search(regex, line) 708 if matches: 709 name = matches.group(1) 710 permissions.append(name) 711 regex = r'package: ([\S]+)' 712 matches = re.search(regex, line) 713 if matches: 714 package_name = matches.group(1) 715 716 return {'package_name': package_name, 'permissions': permissions} 717 718 719def extract_priv_permissions(aapt, apk_path): 720 """Extract signature|privileged permissions from dump of manifest file.""" 721 aapt_args = ['d', 'xmltree', apk_path, 'AndroidManifest.xml'] 722 txt = aapt.call(aapt_args) 723 raw_lines = txt.split('\n') 724 n = len(raw_lines) 725 i = 0 726 permissions_list = [] 727 while i < n: 728 line = raw_lines[i] 729 if line.find('E: permission (') != -1: 730 i += 1 731 name = None 732 level = None 733 while i < n: 734 line = raw_lines[i] 735 if line.find('E: ') != -1: 736 break 737 matches = re.search(ANDROID_NAME_REGEX, line) 738 if matches: 739 name = matches.group(1) 740 i += 1 741 continue 742 matches = re.search(ANDROID_PROTECTION_LEVEL_REGEX, line) 743 if matches: 744 level = int(matches.group(1), 16) 745 i += 1 746 continue 747 i += 1 748 if name and level and level & 0x12 == 0x12: 749 permissions_list.append(name) 750 else: 751 i += 1 752 753 return permissions_list 754 755 756def parse_config_xml(base_xml, results): 757 """Parse an XML file that will be used as base.""" 758 dom = minidom.parse(base_xml) 759 nodes = dom.getElementsByTagName('privapp-permissions') 760 for node in nodes: 761 permissions = (node.getElementsByTagName('permission') + 762 node.getElementsByTagName('deny-permission')) 763 package_name = node.getAttribute('package') 764 plist = [] 765 if package_name in results: 766 plist = results[package_name] 767 for p in permissions: 768 perm_name = p.getAttribute('name') 769 if perm_name: 770 plist.append(perm_name) 771 results[package_name] = plist 772 return results 773 774 775def cleanup(): 776 """Cleans up temp files.""" 777 for directory in temp_dirs: 778 shutil.rmtree(directory, ignore_errors=True) 779 for file in temp_files: 780 os.remove(file) 781 del temp_dirs[:] 782 del temp_files[:] 783 784if __name__ == '__main__': 785 args = parse_args() 786 try: 787 tool_resources = Resources( 788 aapt_path=args.aapt, 789 adb_path=args.adb, 790 use_device=args.device, 791 serial=args.serial, 792 partitions=args.partitions, 793 verbose=args.verbose, 794 writetodisk=args.writetodisk, 795 systemfile=args.systemfile, 796 productfile=args.productfile, 797 apks=args.apks 798 ) 799 create_permission_file(tool_resources) 800 except MissingResourceError as e: 801 print(str(e), file=sys.stderr) 802 exit(1) 803 finally: 804 cleanup() 805