1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*8975f5c5SAndroid Build Coastguard Worker# 3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2018 The Chromium Authors 4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 6*8975f5c5SAndroid Build Coastguard Worker 7*8975f5c5SAndroid Build Coastguard Workerimport argparse 8*8975f5c5SAndroid Build Coastguard Workerimport collections 9*8975f5c5SAndroid Build Coastguard Workerimport functools 10*8975f5c5SAndroid Build Coastguard Workerimport logging 11*8975f5c5SAndroid Build Coastguard Workerimport re 12*8975f5c5SAndroid Build Coastguard Workerimport subprocess 13*8975f5c5SAndroid Build Coastguard Workerimport sys 14*8975f5c5SAndroid Build Coastguard Worker 15*8975f5c5SAndroid Build Coastguard WorkerDEX_CLASS_NAME_RE = re.compile(r'\'L(?P<class_name>[^;]+);\'') 16*8975f5c5SAndroid Build Coastguard WorkerDEX_METHOD_NAME_RE = re.compile(r'\'(?P<method_name>[^\']+)\'') 17*8975f5c5SAndroid Build Coastguard WorkerDEX_METHOD_TYPE_RE = re.compile( # type descriptor method signature re 18*8975f5c5SAndroid Build Coastguard Worker r'\'' 19*8975f5c5SAndroid Build Coastguard Worker r'\(' 20*8975f5c5SAndroid Build Coastguard Worker r'(?P<method_params>[^)]*)' 21*8975f5c5SAndroid Build Coastguard Worker r'\)' 22*8975f5c5SAndroid Build Coastguard Worker r'(?P<method_return_type>[^\']+)' 23*8975f5c5SAndroid Build Coastguard Worker r'\'') 24*8975f5c5SAndroid Build Coastguard WorkerDEX_METHOD_LINE_NR_RE = re.compile(r'line=(?P<line_number>\d+)') 25*8975f5c5SAndroid Build Coastguard Worker 26*8975f5c5SAndroid Build Coastguard WorkerPROFILE_METHOD_RE = re.compile( 27*8975f5c5SAndroid Build Coastguard Worker r'(?P<tags>[HSP]+)' # tags such as H/S/P 28*8975f5c5SAndroid Build Coastguard Worker r'(?P<class_name>L[^;]+;)' # class name in type descriptor format 29*8975f5c5SAndroid Build Coastguard Worker r'->(?P<method_name>[^(]+)' 30*8975f5c5SAndroid Build Coastguard Worker r'\((?P<method_params>[^)]*)\)' 31*8975f5c5SAndroid Build Coastguard Worker r'(?P<method_return_type>.+)') 32*8975f5c5SAndroid Build Coastguard Worker 33*8975f5c5SAndroid Build Coastguard WorkerPROGUARD_CLASS_MAPPING_RE = re.compile( 34*8975f5c5SAndroid Build Coastguard Worker r'(?P<original_name>[^ ]+)' 35*8975f5c5SAndroid Build Coastguard Worker r' -> ' 36*8975f5c5SAndroid Build Coastguard Worker r'(?P<obfuscated_name>[^:]+):') 37*8975f5c5SAndroid Build Coastguard WorkerPROGUARD_METHOD_MAPPING_RE = re.compile( 38*8975f5c5SAndroid Build Coastguard Worker # line_start:line_end: (optional) 39*8975f5c5SAndroid Build Coastguard Worker r'((?P<line_start>\d+):(?P<line_end>\d+):)?' 40*8975f5c5SAndroid Build Coastguard Worker r'(?P<return_type>[^ ]+)' # original method return type 41*8975f5c5SAndroid Build Coastguard Worker # original method class name (if exists) 42*8975f5c5SAndroid Build Coastguard Worker r' (?:(?P<original_method_class>[a-zA-Z_\d.$]+)\.)?' 43*8975f5c5SAndroid Build Coastguard Worker r'(?P<original_method_name>[^.\(]+)' 44*8975f5c5SAndroid Build Coastguard Worker r'\((?P<params>[^\)]*)\)' # original method params 45*8975f5c5SAndroid Build Coastguard Worker r'(?:[^ ]*)' # original method line numbers (ignored) 46*8975f5c5SAndroid Build Coastguard Worker r' -> ' 47*8975f5c5SAndroid Build Coastguard Worker r'(?P<obfuscated_name>.+)') # obfuscated method name 48*8975f5c5SAndroid Build Coastguard Worker 49*8975f5c5SAndroid Build Coastguard WorkerTYPE_DESCRIPTOR_RE = re.compile( 50*8975f5c5SAndroid Build Coastguard Worker r'(?P<brackets>\[*)' 51*8975f5c5SAndroid Build Coastguard Worker r'(?:' 52*8975f5c5SAndroid Build Coastguard Worker r'(?P<class_name>L[^;]+;)' 53*8975f5c5SAndroid Build Coastguard Worker r'|' 54*8975f5c5SAndroid Build Coastguard Worker r'[VZBSCIJFD]' 55*8975f5c5SAndroid Build Coastguard Worker r')') 56*8975f5c5SAndroid Build Coastguard Worker 57*8975f5c5SAndroid Build Coastguard WorkerDOT_NOTATION_MAP = { 58*8975f5c5SAndroid Build Coastguard Worker '': '', 59*8975f5c5SAndroid Build Coastguard Worker 'boolean': 'Z', 60*8975f5c5SAndroid Build Coastguard Worker 'byte': 'B', 61*8975f5c5SAndroid Build Coastguard Worker 'void': 'V', 62*8975f5c5SAndroid Build Coastguard Worker 'short': 'S', 63*8975f5c5SAndroid Build Coastguard Worker 'char': 'C', 64*8975f5c5SAndroid Build Coastguard Worker 'int': 'I', 65*8975f5c5SAndroid Build Coastguard Worker 'long': 'J', 66*8975f5c5SAndroid Build Coastguard Worker 'float': 'F', 67*8975f5c5SAndroid Build Coastguard Worker 'double': 'D' 68*8975f5c5SAndroid Build Coastguard Worker} 69*8975f5c5SAndroid Build Coastguard Worker 70*8975f5c5SAndroid Build Coastguard Worker 71*8975f5c5SAndroid Build Coastguard Worker@functools.total_ordering 72*8975f5c5SAndroid Build Coastguard Workerclass Method: 73*8975f5c5SAndroid Build Coastguard Worker def __init__(self, name, class_name, param_types=None, return_type=None): 74*8975f5c5SAndroid Build Coastguard Worker self.name = name 75*8975f5c5SAndroid Build Coastguard Worker self.class_name = class_name 76*8975f5c5SAndroid Build Coastguard Worker self.param_types = param_types 77*8975f5c5SAndroid Build Coastguard Worker self.return_type = return_type 78*8975f5c5SAndroid Build Coastguard Worker 79*8975f5c5SAndroid Build Coastguard Worker def __str__(self): 80*8975f5c5SAndroid Build Coastguard Worker return '{}->{}({}){}'.format(self.class_name, self.name, 81*8975f5c5SAndroid Build Coastguard Worker self.param_types or '', self.return_type or '') 82*8975f5c5SAndroid Build Coastguard Worker 83*8975f5c5SAndroid Build Coastguard Worker def __repr__(self): 84*8975f5c5SAndroid Build Coastguard Worker return 'Method<{}->{}({}){}>'.format(self.class_name, self.name, 85*8975f5c5SAndroid Build Coastguard Worker self.param_types or '', self.return_type or '') 86*8975f5c5SAndroid Build Coastguard Worker 87*8975f5c5SAndroid Build Coastguard Worker @staticmethod 88*8975f5c5SAndroid Build Coastguard Worker def serialize(method): 89*8975f5c5SAndroid Build Coastguard Worker return (method.class_name, method.name, method.param_types, 90*8975f5c5SAndroid Build Coastguard Worker method.return_type) 91*8975f5c5SAndroid Build Coastguard Worker 92*8975f5c5SAndroid Build Coastguard Worker def __eq__(self, other): 93*8975f5c5SAndroid Build Coastguard Worker return self.serialize(self) == self.serialize(other) 94*8975f5c5SAndroid Build Coastguard Worker 95*8975f5c5SAndroid Build Coastguard Worker def __lt__(self, other): 96*8975f5c5SAndroid Build Coastguard Worker return self.serialize(self) < self.serialize(other) 97*8975f5c5SAndroid Build Coastguard Worker 98*8975f5c5SAndroid Build Coastguard Worker def __hash__(self): 99*8975f5c5SAndroid Build Coastguard Worker # only hash name and class_name since other fields may not be set yet. 100*8975f5c5SAndroid Build Coastguard Worker return hash((self.name, self.class_name)) 101*8975f5c5SAndroid Build Coastguard Worker 102*8975f5c5SAndroid Build Coastguard Worker 103*8975f5c5SAndroid Build Coastguard Workerclass Class: 104*8975f5c5SAndroid Build Coastguard Worker def __init__(self, name): 105*8975f5c5SAndroid Build Coastguard Worker self.name = name 106*8975f5c5SAndroid Build Coastguard Worker self._methods = [] 107*8975f5c5SAndroid Build Coastguard Worker 108*8975f5c5SAndroid Build Coastguard Worker def AddMethod(self, method, line_numbers): 109*8975f5c5SAndroid Build Coastguard Worker self._methods.append((method, set(line_numbers))) 110*8975f5c5SAndroid Build Coastguard Worker 111*8975f5c5SAndroid Build Coastguard Worker def FindMethodsAtLine(self, method_name, line_start, line_end=None): 112*8975f5c5SAndroid Build Coastguard Worker """Searches through dex class for a method given a name and line numbers 113*8975f5c5SAndroid Build Coastguard Worker 114*8975f5c5SAndroid Build Coastguard Worker The dex maps methods to line numbers, this method, given the a method name 115*8975f5c5SAndroid Build Coastguard Worker in this class as well as a start line and an optional end line (which act as 116*8975f5c5SAndroid Build Coastguard Worker hints as to which function in the class is being looked for), returns a list 117*8975f5c5SAndroid Build Coastguard Worker of possible matches (or none if none are found). 118*8975f5c5SAndroid Build Coastguard Worker 119*8975f5c5SAndroid Build Coastguard Worker Args: 120*8975f5c5SAndroid Build Coastguard Worker method_name: name of method being searched for 121*8975f5c5SAndroid Build Coastguard Worker line_start: start of hint range for lines in this method 122*8975f5c5SAndroid Build Coastguard Worker line_end: end of hint range for lines in this method (optional) 123*8975f5c5SAndroid Build Coastguard Worker 124*8975f5c5SAndroid Build Coastguard Worker Returns: 125*8975f5c5SAndroid Build Coastguard Worker A list of Method objects that could match the hints given, or None if no 126*8975f5c5SAndroid Build Coastguard Worker method is found. 127*8975f5c5SAndroid Build Coastguard Worker """ 128*8975f5c5SAndroid Build Coastguard Worker found_methods = [] 129*8975f5c5SAndroid Build Coastguard Worker if line_end is None: 130*8975f5c5SAndroid Build Coastguard Worker hint_lines = set([line_start]) 131*8975f5c5SAndroid Build Coastguard Worker else: 132*8975f5c5SAndroid Build Coastguard Worker hint_lines = set(range(line_start, line_end+1)) 133*8975f5c5SAndroid Build Coastguard Worker 134*8975f5c5SAndroid Build Coastguard Worker named_methods = [(method, l) for method, l in self._methods 135*8975f5c5SAndroid Build Coastguard Worker if method.name == method_name] 136*8975f5c5SAndroid Build Coastguard Worker 137*8975f5c5SAndroid Build Coastguard Worker if len(named_methods) == 1: 138*8975f5c5SAndroid Build Coastguard Worker return [method for method, l in named_methods] 139*8975f5c5SAndroid Build Coastguard Worker if len(named_methods) == 0: 140*8975f5c5SAndroid Build Coastguard Worker return None 141*8975f5c5SAndroid Build Coastguard Worker 142*8975f5c5SAndroid Build Coastguard Worker for method, line_numbers in named_methods: 143*8975f5c5SAndroid Build Coastguard Worker if not hint_lines.isdisjoint(line_numbers): 144*8975f5c5SAndroid Build Coastguard Worker found_methods.append(method) 145*8975f5c5SAndroid Build Coastguard Worker 146*8975f5c5SAndroid Build Coastguard Worker if len(found_methods) > 0: 147*8975f5c5SAndroid Build Coastguard Worker if len(found_methods) > 1: 148*8975f5c5SAndroid Build Coastguard Worker logging.warning('ambigous methods in dex %s at lines %s in class "%s"', 149*8975f5c5SAndroid Build Coastguard Worker found_methods, hint_lines, self.name) 150*8975f5c5SAndroid Build Coastguard Worker return found_methods 151*8975f5c5SAndroid Build Coastguard Worker 152*8975f5c5SAndroid Build Coastguard Worker for method, line_numbers in named_methods: 153*8975f5c5SAndroid Build Coastguard Worker if (max(hint_lines) >= min(line_numbers) 154*8975f5c5SAndroid Build Coastguard Worker and min(hint_lines) <= max(line_numbers)): 155*8975f5c5SAndroid Build Coastguard Worker found_methods.append(method) 156*8975f5c5SAndroid Build Coastguard Worker 157*8975f5c5SAndroid Build Coastguard Worker if len(found_methods) > 0: 158*8975f5c5SAndroid Build Coastguard Worker if len(found_methods) > 1: 159*8975f5c5SAndroid Build Coastguard Worker logging.warning('ambigous methods in dex %s at lines %s in class "%s"', 160*8975f5c5SAndroid Build Coastguard Worker found_methods, hint_lines, self.name) 161*8975f5c5SAndroid Build Coastguard Worker return found_methods 162*8975f5c5SAndroid Build Coastguard Worker logging.warning( 163*8975f5c5SAndroid Build Coastguard Worker 'No method named "%s" in class "%s" is ' 164*8975f5c5SAndroid Build Coastguard Worker 'mapped to lines %s', method_name, self.name, hint_lines) 165*8975f5c5SAndroid Build Coastguard Worker return None 166*8975f5c5SAndroid Build Coastguard Worker 167*8975f5c5SAndroid Build Coastguard Worker 168*8975f5c5SAndroid Build Coastguard Workerclass Profile: 169*8975f5c5SAndroid Build Coastguard Worker def __init__(self): 170*8975f5c5SAndroid Build Coastguard Worker # {Method: set(char)} 171*8975f5c5SAndroid Build Coastguard Worker self._methods = collections.defaultdict(set) 172*8975f5c5SAndroid Build Coastguard Worker self._classes = [] 173*8975f5c5SAndroid Build Coastguard Worker 174*8975f5c5SAndroid Build Coastguard Worker def AddMethod(self, method, tags): 175*8975f5c5SAndroid Build Coastguard Worker for tag in tags: 176*8975f5c5SAndroid Build Coastguard Worker self._methods[method].add(tag) 177*8975f5c5SAndroid Build Coastguard Worker 178*8975f5c5SAndroid Build Coastguard Worker def AddClass(self, cls): 179*8975f5c5SAndroid Build Coastguard Worker self._classes.append(cls) 180*8975f5c5SAndroid Build Coastguard Worker 181*8975f5c5SAndroid Build Coastguard Worker def WriteToFile(self, path): 182*8975f5c5SAndroid Build Coastguard Worker with open(path, 'w') as output_profile: 183*8975f5c5SAndroid Build Coastguard Worker for cls in sorted(self._classes): 184*8975f5c5SAndroid Build Coastguard Worker output_profile.write(cls + '\n') 185*8975f5c5SAndroid Build Coastguard Worker for method in sorted(self._methods): 186*8975f5c5SAndroid Build Coastguard Worker tags = sorted(self._methods[method]) 187*8975f5c5SAndroid Build Coastguard Worker line = '{}{}\n'.format(''.join(tags), str(method)) 188*8975f5c5SAndroid Build Coastguard Worker output_profile.write(line) 189*8975f5c5SAndroid Build Coastguard Worker 190*8975f5c5SAndroid Build Coastguard Worker 191*8975f5c5SAndroid Build Coastguard Workerclass ProguardMapping: 192*8975f5c5SAndroid Build Coastguard Worker def __init__(self): 193*8975f5c5SAndroid Build Coastguard Worker # {Method: set(Method)} 194*8975f5c5SAndroid Build Coastguard Worker self._method_mapping = collections.defaultdict(set) 195*8975f5c5SAndroid Build Coastguard Worker # {String: String} String is class name in type descriptor format 196*8975f5c5SAndroid Build Coastguard Worker self._class_mapping = dict() 197*8975f5c5SAndroid Build Coastguard Worker 198*8975f5c5SAndroid Build Coastguard Worker def AddMethodMapping(self, from_method, to_method): 199*8975f5c5SAndroid Build Coastguard Worker self._method_mapping[from_method].add(to_method) 200*8975f5c5SAndroid Build Coastguard Worker 201*8975f5c5SAndroid Build Coastguard Worker def AddClassMapping(self, from_class, to_class): 202*8975f5c5SAndroid Build Coastguard Worker self._class_mapping[from_class] = to_class 203*8975f5c5SAndroid Build Coastguard Worker 204*8975f5c5SAndroid Build Coastguard Worker def GetMethodMapping(self, from_method): 205*8975f5c5SAndroid Build Coastguard Worker return self._method_mapping.get(from_method) 206*8975f5c5SAndroid Build Coastguard Worker 207*8975f5c5SAndroid Build Coastguard Worker def GetClassMapping(self, from_class): 208*8975f5c5SAndroid Build Coastguard Worker return self._class_mapping.get(from_class, from_class) 209*8975f5c5SAndroid Build Coastguard Worker 210*8975f5c5SAndroid Build Coastguard Worker def MapTypeDescriptor(self, type_descriptor): 211*8975f5c5SAndroid Build Coastguard Worker match = TYPE_DESCRIPTOR_RE.search(type_descriptor) 212*8975f5c5SAndroid Build Coastguard Worker assert match is not None 213*8975f5c5SAndroid Build Coastguard Worker class_name = match.group('class_name') 214*8975f5c5SAndroid Build Coastguard Worker if class_name is not None: 215*8975f5c5SAndroid Build Coastguard Worker return match.group('brackets') + self.GetClassMapping(class_name) 216*8975f5c5SAndroid Build Coastguard Worker # just a native type, return as is 217*8975f5c5SAndroid Build Coastguard Worker return match.group() 218*8975f5c5SAndroid Build Coastguard Worker 219*8975f5c5SAndroid Build Coastguard Worker def MapTypeDescriptorList(self, type_descriptor_list): 220*8975f5c5SAndroid Build Coastguard Worker return TYPE_DESCRIPTOR_RE.sub( 221*8975f5c5SAndroid Build Coastguard Worker lambda match: self.MapTypeDescriptor(match.group()), 222*8975f5c5SAndroid Build Coastguard Worker type_descriptor_list) 223*8975f5c5SAndroid Build Coastguard Worker 224*8975f5c5SAndroid Build Coastguard Worker 225*8975f5c5SAndroid Build Coastguard Workerclass MalformedLineException(Exception): 226*8975f5c5SAndroid Build Coastguard Worker def __init__(self, message, line_number): 227*8975f5c5SAndroid Build Coastguard Worker super().__init__(message) 228*8975f5c5SAndroid Build Coastguard Worker self.message = message 229*8975f5c5SAndroid Build Coastguard Worker self.line_number = line_number 230*8975f5c5SAndroid Build Coastguard Worker 231*8975f5c5SAndroid Build Coastguard Worker def __str__(self): 232*8975f5c5SAndroid Build Coastguard Worker return self.message + ' at line {}'.format(self.line_number) 233*8975f5c5SAndroid Build Coastguard Worker 234*8975f5c5SAndroid Build Coastguard Worker 235*8975f5c5SAndroid Build Coastguard Workerclass MalformedProguardMappingException(MalformedLineException): 236*8975f5c5SAndroid Build Coastguard Worker pass 237*8975f5c5SAndroid Build Coastguard Worker 238*8975f5c5SAndroid Build Coastguard Worker 239*8975f5c5SAndroid Build Coastguard Workerclass MalformedProfileException(MalformedLineException): 240*8975f5c5SAndroid Build Coastguard Worker pass 241*8975f5c5SAndroid Build Coastguard Worker 242*8975f5c5SAndroid Build Coastguard Worker 243*8975f5c5SAndroid Build Coastguard Workerdef _RunDexDump(dexdump_path, dex_file_path): 244*8975f5c5SAndroid Build Coastguard Worker return subprocess.check_output([dexdump_path, 245*8975f5c5SAndroid Build Coastguard Worker dex_file_path]).decode('utf-8').splitlines() 246*8975f5c5SAndroid Build Coastguard Worker 247*8975f5c5SAndroid Build Coastguard Worker 248*8975f5c5SAndroid Build Coastguard Workerdef _ReadFile(file_path): 249*8975f5c5SAndroid Build Coastguard Worker with open(file_path, 'r') as f: 250*8975f5c5SAndroid Build Coastguard Worker return f.readlines() 251*8975f5c5SAndroid Build Coastguard Worker 252*8975f5c5SAndroid Build Coastguard Worker 253*8975f5c5SAndroid Build Coastguard Workerdef _ToTypeDescriptor(dot_notation): 254*8975f5c5SAndroid Build Coastguard Worker """Parses a dot notation type and returns it in type descriptor format 255*8975f5c5SAndroid Build Coastguard Worker 256*8975f5c5SAndroid Build Coastguard Worker eg: 257*8975f5c5SAndroid Build Coastguard Worker org.chromium.browser.ChromeActivity -> Lorg/chromium/browser/ChromeActivity; 258*8975f5c5SAndroid Build Coastguard Worker boolean -> Z 259*8975f5c5SAndroid Build Coastguard Worker int[] -> [I 260*8975f5c5SAndroid Build Coastguard Worker 261*8975f5c5SAndroid Build Coastguard Worker Args: 262*8975f5c5SAndroid Build Coastguard Worker dot_notation: trimmed string with a single type in dot notation format 263*8975f5c5SAndroid Build Coastguard Worker 264*8975f5c5SAndroid Build Coastguard Worker Returns: 265*8975f5c5SAndroid Build Coastguard Worker A string with the type in type descriptor format 266*8975f5c5SAndroid Build Coastguard Worker """ 267*8975f5c5SAndroid Build Coastguard Worker dot_notation = dot_notation.strip() 268*8975f5c5SAndroid Build Coastguard Worker prefix = '' 269*8975f5c5SAndroid Build Coastguard Worker while dot_notation.endswith('[]'): 270*8975f5c5SAndroid Build Coastguard Worker prefix += '[' 271*8975f5c5SAndroid Build Coastguard Worker dot_notation = dot_notation[:-2] 272*8975f5c5SAndroid Build Coastguard Worker if dot_notation in DOT_NOTATION_MAP: 273*8975f5c5SAndroid Build Coastguard Worker return prefix + DOT_NOTATION_MAP[dot_notation] 274*8975f5c5SAndroid Build Coastguard Worker return prefix + 'L' + dot_notation.replace('.', '/') + ';' 275*8975f5c5SAndroid Build Coastguard Worker 276*8975f5c5SAndroid Build Coastguard Worker 277*8975f5c5SAndroid Build Coastguard Workerdef _DotNotationListToTypeDescriptorList(dot_notation_list_string): 278*8975f5c5SAndroid Build Coastguard Worker """Parses a param list of dot notation format and returns it in type 279*8975f5c5SAndroid Build Coastguard Worker descriptor format 280*8975f5c5SAndroid Build Coastguard Worker 281*8975f5c5SAndroid Build Coastguard Worker eg: 282*8975f5c5SAndroid Build Coastguard Worker org.chromium.browser.ChromeActivity,boolean,int[] -> 283*8975f5c5SAndroid Build Coastguard Worker Lorg/chromium/browser/ChromeActivity;Z[I 284*8975f5c5SAndroid Build Coastguard Worker 285*8975f5c5SAndroid Build Coastguard Worker Args: 286*8975f5c5SAndroid Build Coastguard Worker dot_notation_list_string: single string with multiple comma separated types 287*8975f5c5SAndroid Build Coastguard Worker in dot notation format 288*8975f5c5SAndroid Build Coastguard Worker 289*8975f5c5SAndroid Build Coastguard Worker Returns: 290*8975f5c5SAndroid Build Coastguard Worker A string with the param list in type descriptor format 291*8975f5c5SAndroid Build Coastguard Worker """ 292*8975f5c5SAndroid Build Coastguard Worker return ''.join(_ToTypeDescriptor(param) for param in 293*8975f5c5SAndroid Build Coastguard Worker dot_notation_list_string.split(',')) 294*8975f5c5SAndroid Build Coastguard Worker 295*8975f5c5SAndroid Build Coastguard Worker 296*8975f5c5SAndroid Build Coastguard Workerdef ProcessDex(dex_dump): 297*8975f5c5SAndroid Build Coastguard Worker """Parses dexdump output returning a dict of class names to Class objects 298*8975f5c5SAndroid Build Coastguard Worker 299*8975f5c5SAndroid Build Coastguard Worker Parses output of the dexdump command on a dex file and extracts information 300*8975f5c5SAndroid Build Coastguard Worker about classes and their respective methods and which line numbers a method is 301*8975f5c5SAndroid Build Coastguard Worker mapped to. 302*8975f5c5SAndroid Build Coastguard Worker 303*8975f5c5SAndroid Build Coastguard Worker Methods that are not mapped to any line number are ignored and not listed 304*8975f5c5SAndroid Build Coastguard Worker inside their respective Class objects. 305*8975f5c5SAndroid Build Coastguard Worker 306*8975f5c5SAndroid Build Coastguard Worker Args: 307*8975f5c5SAndroid Build Coastguard Worker dex_dump: An array of lines of dexdump output 308*8975f5c5SAndroid Build Coastguard Worker 309*8975f5c5SAndroid Build Coastguard Worker Returns: 310*8975f5c5SAndroid Build Coastguard Worker A dict that maps from class names in type descriptor format (but without the 311*8975f5c5SAndroid Build Coastguard Worker surrounding 'L' and ';') to Class objects. 312*8975f5c5SAndroid Build Coastguard Worker """ 313*8975f5c5SAndroid Build Coastguard Worker # class_name: Class 314*8975f5c5SAndroid Build Coastguard Worker classes_by_name = {} 315*8975f5c5SAndroid Build Coastguard Worker current_class = None 316*8975f5c5SAndroid Build Coastguard Worker current_method = None 317*8975f5c5SAndroid Build Coastguard Worker reading_positions = False 318*8975f5c5SAndroid Build Coastguard Worker reading_methods = False 319*8975f5c5SAndroid Build Coastguard Worker method_line_numbers = [] 320*8975f5c5SAndroid Build Coastguard Worker for line in dex_dump: 321*8975f5c5SAndroid Build Coastguard Worker line = line.strip() 322*8975f5c5SAndroid Build Coastguard Worker if line.startswith('Class descriptor'): 323*8975f5c5SAndroid Build Coastguard Worker # New class started, no longer reading methods. 324*8975f5c5SAndroid Build Coastguard Worker reading_methods = False 325*8975f5c5SAndroid Build Coastguard Worker current_class = Class(DEX_CLASS_NAME_RE.search(line).group('class_name')) 326*8975f5c5SAndroid Build Coastguard Worker classes_by_name[current_class.name] = current_class 327*8975f5c5SAndroid Build Coastguard Worker elif (line.startswith('Direct methods') 328*8975f5c5SAndroid Build Coastguard Worker or line.startswith('Virtual methods')): 329*8975f5c5SAndroid Build Coastguard Worker reading_methods = True 330*8975f5c5SAndroid Build Coastguard Worker elif reading_methods and line.startswith('name'): 331*8975f5c5SAndroid Build Coastguard Worker assert current_class is not None 332*8975f5c5SAndroid Build Coastguard Worker current_method = Method( 333*8975f5c5SAndroid Build Coastguard Worker DEX_METHOD_NAME_RE.search(line).group('method_name'), 334*8975f5c5SAndroid Build Coastguard Worker "L" + current_class.name + ";") 335*8975f5c5SAndroid Build Coastguard Worker elif reading_methods and line.startswith('type'): 336*8975f5c5SAndroid Build Coastguard Worker assert current_method is not None 337*8975f5c5SAndroid Build Coastguard Worker match = DEX_METHOD_TYPE_RE.search(line) 338*8975f5c5SAndroid Build Coastguard Worker current_method.param_types = match.group('method_params') 339*8975f5c5SAndroid Build Coastguard Worker current_method.return_type = match.group('method_return_type') 340*8975f5c5SAndroid Build Coastguard Worker elif line.startswith('positions'): 341*8975f5c5SAndroid Build Coastguard Worker assert reading_methods 342*8975f5c5SAndroid Build Coastguard Worker reading_positions = True 343*8975f5c5SAndroid Build Coastguard Worker method_line_numbers = [] 344*8975f5c5SAndroid Build Coastguard Worker elif reading_positions and line.startswith('0x'): 345*8975f5c5SAndroid Build Coastguard Worker line_number = DEX_METHOD_LINE_NR_RE.search(line).group('line_number') 346*8975f5c5SAndroid Build Coastguard Worker method_line_numbers.append(int(line_number)) 347*8975f5c5SAndroid Build Coastguard Worker elif reading_positions and line.startswith('locals'): 348*8975f5c5SAndroid Build Coastguard Worker if len(method_line_numbers) > 0: 349*8975f5c5SAndroid Build Coastguard Worker current_class.AddMethod(current_method, method_line_numbers) 350*8975f5c5SAndroid Build Coastguard Worker # finished reading method line numbers 351*8975f5c5SAndroid Build Coastguard Worker reading_positions = False 352*8975f5c5SAndroid Build Coastguard Worker return classes_by_name 353*8975f5c5SAndroid Build Coastguard Worker 354*8975f5c5SAndroid Build Coastguard Worker 355*8975f5c5SAndroid Build Coastguard Workerdef ProcessProguardMapping(proguard_mapping_lines, dex): 356*8975f5c5SAndroid Build Coastguard Worker """Parses a proguard mapping file 357*8975f5c5SAndroid Build Coastguard Worker 358*8975f5c5SAndroid Build Coastguard Worker This takes proguard mapping file lines and then uses the obfuscated dex to 359*8975f5c5SAndroid Build Coastguard Worker create a mapping of unobfuscated methods to obfuscated ones and vice versa. 360*8975f5c5SAndroid Build Coastguard Worker 361*8975f5c5SAndroid Build Coastguard Worker The dex is used because the proguard mapping file only has the name of the 362*8975f5c5SAndroid Build Coastguard Worker obfuscated methods but not their signature, thus the dex is read to look up 363*8975f5c5SAndroid Build Coastguard Worker which method with a specific name was mapped to the lines mentioned in the 364*8975f5c5SAndroid Build Coastguard Worker proguard mapping file. 365*8975f5c5SAndroid Build Coastguard Worker 366*8975f5c5SAndroid Build Coastguard Worker Args: 367*8975f5c5SAndroid Build Coastguard Worker proguard_mapping_lines: Array of strings, each is a line from the proguard 368*8975f5c5SAndroid Build Coastguard Worker mapping file (in order). 369*8975f5c5SAndroid Build Coastguard Worker dex: a dict of class name (in type descriptor format but without the 370*8975f5c5SAndroid Build Coastguard Worker enclosing 'L' and ';') to a Class object. 371*8975f5c5SAndroid Build Coastguard Worker Returns: 372*8975f5c5SAndroid Build Coastguard Worker Two dicts the first maps from obfuscated methods to a set of non-obfuscated 373*8975f5c5SAndroid Build Coastguard Worker ones. It also maps the obfuscated class names to original class names, both 374*8975f5c5SAndroid Build Coastguard Worker in type descriptor format (with the enclosing 'L' and ';') 375*8975f5c5SAndroid Build Coastguard Worker """ 376*8975f5c5SAndroid Build Coastguard Worker mapping = ProguardMapping() 377*8975f5c5SAndroid Build Coastguard Worker reverse_mapping = ProguardMapping() 378*8975f5c5SAndroid Build Coastguard Worker to_be_obfuscated = [] 379*8975f5c5SAndroid Build Coastguard Worker current_class_orig = None 380*8975f5c5SAndroid Build Coastguard Worker current_class_obfs = None 381*8975f5c5SAndroid Build Coastguard Worker for index, line in enumerate(proguard_mapping_lines): 382*8975f5c5SAndroid Build Coastguard Worker if line.strip() == '': 383*8975f5c5SAndroid Build Coastguard Worker continue 384*8975f5c5SAndroid Build Coastguard Worker if not line.startswith(' '): 385*8975f5c5SAndroid Build Coastguard Worker match = PROGUARD_CLASS_MAPPING_RE.search(line) 386*8975f5c5SAndroid Build Coastguard Worker if match is None: 387*8975f5c5SAndroid Build Coastguard Worker raise MalformedProguardMappingException( 388*8975f5c5SAndroid Build Coastguard Worker 'Malformed class mapping', index) 389*8975f5c5SAndroid Build Coastguard Worker current_class_orig = match.group('original_name') 390*8975f5c5SAndroid Build Coastguard Worker current_class_obfs = match.group('obfuscated_name') 391*8975f5c5SAndroid Build Coastguard Worker mapping.AddClassMapping(_ToTypeDescriptor(current_class_obfs), 392*8975f5c5SAndroid Build Coastguard Worker _ToTypeDescriptor(current_class_orig)) 393*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.AddClassMapping(_ToTypeDescriptor(current_class_orig), 394*8975f5c5SAndroid Build Coastguard Worker _ToTypeDescriptor(current_class_obfs)) 395*8975f5c5SAndroid Build Coastguard Worker continue 396*8975f5c5SAndroid Build Coastguard Worker 397*8975f5c5SAndroid Build Coastguard Worker assert current_class_orig is not None 398*8975f5c5SAndroid Build Coastguard Worker assert current_class_obfs is not None 399*8975f5c5SAndroid Build Coastguard Worker line = line.strip() 400*8975f5c5SAndroid Build Coastguard Worker match = PROGUARD_METHOD_MAPPING_RE.search(line) 401*8975f5c5SAndroid Build Coastguard Worker # check if is a method mapping (we ignore field mappings) 402*8975f5c5SAndroid Build Coastguard Worker if match is not None: 403*8975f5c5SAndroid Build Coastguard Worker # check if this line is an inlining by reading ahead 1 line. 404*8975f5c5SAndroid Build Coastguard Worker if index + 1 < len(proguard_mapping_lines): 405*8975f5c5SAndroid Build Coastguard Worker next_match = PROGUARD_METHOD_MAPPING_RE.search( 406*8975f5c5SAndroid Build Coastguard Worker proguard_mapping_lines[index+1].strip()) 407*8975f5c5SAndroid Build Coastguard Worker if (next_match and match.group('line_start') is not None 408*8975f5c5SAndroid Build Coastguard Worker and next_match.group('line_start') == match.group('line_start') 409*8975f5c5SAndroid Build Coastguard Worker and next_match.group('line_end') == match.group('line_end')): 410*8975f5c5SAndroid Build Coastguard Worker continue # This is an inlining, skip 411*8975f5c5SAndroid Build Coastguard Worker 412*8975f5c5SAndroid Build Coastguard Worker original_method = Method( 413*8975f5c5SAndroid Build Coastguard Worker match.group('original_method_name'), 414*8975f5c5SAndroid Build Coastguard Worker _ToTypeDescriptor( 415*8975f5c5SAndroid Build Coastguard Worker match.group('original_method_class') or current_class_orig), 416*8975f5c5SAndroid Build Coastguard Worker _DotNotationListToTypeDescriptorList(match.group('params')), 417*8975f5c5SAndroid Build Coastguard Worker _ToTypeDescriptor(match.group('return_type'))) 418*8975f5c5SAndroid Build Coastguard Worker 419*8975f5c5SAndroid Build Coastguard Worker if match.group('line_start') is not None: 420*8975f5c5SAndroid Build Coastguard Worker obfs_methods = (dex[current_class_obfs.replace('.', '/')] 421*8975f5c5SAndroid Build Coastguard Worker .FindMethodsAtLine( 422*8975f5c5SAndroid Build Coastguard Worker match.group('obfuscated_name'), 423*8975f5c5SAndroid Build Coastguard Worker int(match.group('line_start')), 424*8975f5c5SAndroid Build Coastguard Worker int(match.group('line_end')))) 425*8975f5c5SAndroid Build Coastguard Worker 426*8975f5c5SAndroid Build Coastguard Worker if obfs_methods is None: 427*8975f5c5SAndroid Build Coastguard Worker continue 428*8975f5c5SAndroid Build Coastguard Worker 429*8975f5c5SAndroid Build Coastguard Worker for obfs_method in obfs_methods: 430*8975f5c5SAndroid Build Coastguard Worker mapping.AddMethodMapping(obfs_method, original_method) 431*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.AddMethodMapping(original_method, obfs_method) 432*8975f5c5SAndroid Build Coastguard Worker else: 433*8975f5c5SAndroid Build Coastguard Worker to_be_obfuscated.append( 434*8975f5c5SAndroid Build Coastguard Worker (original_method, match.group('obfuscated_name'))) 435*8975f5c5SAndroid Build Coastguard Worker 436*8975f5c5SAndroid Build Coastguard Worker for original_method, obfuscated_name in to_be_obfuscated: 437*8975f5c5SAndroid Build Coastguard Worker obfuscated_method = Method( 438*8975f5c5SAndroid Build Coastguard Worker obfuscated_name, 439*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.GetClassMapping(original_method.class_name), 440*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.MapTypeDescriptorList(original_method.param_types), 441*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.MapTypeDescriptor(original_method.return_type)) 442*8975f5c5SAndroid Build Coastguard Worker mapping.AddMethodMapping(obfuscated_method, original_method) 443*8975f5c5SAndroid Build Coastguard Worker reverse_mapping.AddMethodMapping(original_method, obfuscated_method) 444*8975f5c5SAndroid Build Coastguard Worker return mapping, reverse_mapping 445*8975f5c5SAndroid Build Coastguard Worker 446*8975f5c5SAndroid Build Coastguard Worker 447*8975f5c5SAndroid Build Coastguard Workerdef ProcessProfile(input_profile, proguard_mapping): 448*8975f5c5SAndroid Build Coastguard Worker """Parses an android profile and uses the proguard mapping to (de)obfuscate it 449*8975f5c5SAndroid Build Coastguard Worker 450*8975f5c5SAndroid Build Coastguard Worker This takes the android profile lines and for each method or class for the 451*8975f5c5SAndroid Build Coastguard Worker profile, it uses the mapping to either obfuscate or deobfuscate (based on the 452*8975f5c5SAndroid Build Coastguard Worker provided mapping) and returns a Profile object that stores this information. 453*8975f5c5SAndroid Build Coastguard Worker 454*8975f5c5SAndroid Build Coastguard Worker Args: 455*8975f5c5SAndroid Build Coastguard Worker input_profile: array of lines of the input profile 456*8975f5c5SAndroid Build Coastguard Worker proguard_mapping: a proguard mapping that would map from the classes and 457*8975f5c5SAndroid Build Coastguard Worker methods in the input profile to the classes and methods 458*8975f5c5SAndroid Build Coastguard Worker that should be in the output profile. 459*8975f5c5SAndroid Build Coastguard Worker 460*8975f5c5SAndroid Build Coastguard Worker Returns: 461*8975f5c5SAndroid Build Coastguard Worker A Profile object that stores the information (ie list of mapped classes and 462*8975f5c5SAndroid Build Coastguard Worker methods + tags) 463*8975f5c5SAndroid Build Coastguard Worker """ 464*8975f5c5SAndroid Build Coastguard Worker profile = Profile() 465*8975f5c5SAndroid Build Coastguard Worker for index, line in enumerate(input_profile): 466*8975f5c5SAndroid Build Coastguard Worker line = line.strip() 467*8975f5c5SAndroid Build Coastguard Worker if line.startswith('L'): 468*8975f5c5SAndroid Build Coastguard Worker profile.AddClass(proguard_mapping.GetClassMapping(line)) 469*8975f5c5SAndroid Build Coastguard Worker continue 470*8975f5c5SAndroid Build Coastguard Worker match = PROFILE_METHOD_RE.search(line) 471*8975f5c5SAndroid Build Coastguard Worker if not match: 472*8975f5c5SAndroid Build Coastguard Worker raise MalformedProfileException("Malformed line", index) 473*8975f5c5SAndroid Build Coastguard Worker 474*8975f5c5SAndroid Build Coastguard Worker method = Method( 475*8975f5c5SAndroid Build Coastguard Worker match.group('method_name'), 476*8975f5c5SAndroid Build Coastguard Worker match.group('class_name'), 477*8975f5c5SAndroid Build Coastguard Worker match.group('method_params'), 478*8975f5c5SAndroid Build Coastguard Worker match.group('method_return_type')) 479*8975f5c5SAndroid Build Coastguard Worker 480*8975f5c5SAndroid Build Coastguard Worker mapped_methods = proguard_mapping.GetMethodMapping(method) 481*8975f5c5SAndroid Build Coastguard Worker if mapped_methods is None: 482*8975f5c5SAndroid Build Coastguard Worker logging.warning('No method matching "%s" has been found in the proguard ' 483*8975f5c5SAndroid Build Coastguard Worker 'mapping file', method) 484*8975f5c5SAndroid Build Coastguard Worker continue 485*8975f5c5SAndroid Build Coastguard Worker 486*8975f5c5SAndroid Build Coastguard Worker for original_method in mapped_methods: 487*8975f5c5SAndroid Build Coastguard Worker profile.AddMethod(original_method, match.group('tags')) 488*8975f5c5SAndroid Build Coastguard Worker 489*8975f5c5SAndroid Build Coastguard Worker return profile 490*8975f5c5SAndroid Build Coastguard Worker 491*8975f5c5SAndroid Build Coastguard Worker 492*8975f5c5SAndroid Build Coastguard Workerdef ObfuscateProfile(nonobfuscated_profile, dex_file, proguard_mapping, 493*8975f5c5SAndroid Build Coastguard Worker dexdump_path, output_filename): 494*8975f5c5SAndroid Build Coastguard Worker """Helper method for obfuscating a profile. 495*8975f5c5SAndroid Build Coastguard Worker 496*8975f5c5SAndroid Build Coastguard Worker Args: 497*8975f5c5SAndroid Build Coastguard Worker nonobfuscated_profile: a profile with nonobfuscated symbols. 498*8975f5c5SAndroid Build Coastguard Worker dex_file: path to the dex file matching the mapping. 499*8975f5c5SAndroid Build Coastguard Worker proguard_mapping: a mapping from nonobfuscated to obfuscated symbols used 500*8975f5c5SAndroid Build Coastguard Worker in the dex file. 501*8975f5c5SAndroid Build Coastguard Worker dexdump_path: path to the dexdump utility. 502*8975f5c5SAndroid Build Coastguard Worker output_filename: output filename in which to write the obfuscated profile. 503*8975f5c5SAndroid Build Coastguard Worker """ 504*8975f5c5SAndroid Build Coastguard Worker dexinfo = ProcessDex(_RunDexDump(dexdump_path, dex_file)) 505*8975f5c5SAndroid Build Coastguard Worker _, reverse_mapping = ProcessProguardMapping( 506*8975f5c5SAndroid Build Coastguard Worker _ReadFile(proguard_mapping), dexinfo) 507*8975f5c5SAndroid Build Coastguard Worker obfuscated_profile = ProcessProfile( 508*8975f5c5SAndroid Build Coastguard Worker _ReadFile(nonobfuscated_profile), reverse_mapping) 509*8975f5c5SAndroid Build Coastguard Worker obfuscated_profile.WriteToFile(output_filename) 510*8975f5c5SAndroid Build Coastguard Worker 511*8975f5c5SAndroid Build Coastguard Worker 512*8975f5c5SAndroid Build Coastguard Workerdef main(args): 513*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 514*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 515*8975f5c5SAndroid Build Coastguard Worker '--dexdump-path', 516*8975f5c5SAndroid Build Coastguard Worker required=True, 517*8975f5c5SAndroid Build Coastguard Worker help='Path to dexdump binary.') 518*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 519*8975f5c5SAndroid Build Coastguard Worker '--dex-path', 520*8975f5c5SAndroid Build Coastguard Worker required=True, 521*8975f5c5SAndroid Build Coastguard Worker help='Path to dex file corresponding to the proguard mapping file.') 522*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 523*8975f5c5SAndroid Build Coastguard Worker '--proguard-mapping-path', 524*8975f5c5SAndroid Build Coastguard Worker required=True, 525*8975f5c5SAndroid Build Coastguard Worker help='Path to input proguard mapping file corresponding to the dex file.') 526*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 527*8975f5c5SAndroid Build Coastguard Worker '--output-profile-path', 528*8975f5c5SAndroid Build Coastguard Worker required=True, 529*8975f5c5SAndroid Build Coastguard Worker help='Path to output profile.') 530*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 531*8975f5c5SAndroid Build Coastguard Worker '--input-profile-path', 532*8975f5c5SAndroid Build Coastguard Worker required=True, 533*8975f5c5SAndroid Build Coastguard Worker help='Path to output profile.') 534*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 535*8975f5c5SAndroid Build Coastguard Worker '--verbose', 536*8975f5c5SAndroid Build Coastguard Worker action='store_true', 537*8975f5c5SAndroid Build Coastguard Worker default=False, 538*8975f5c5SAndroid Build Coastguard Worker help='Print verbose output.') 539*8975f5c5SAndroid Build Coastguard Worker obfuscation = parser.add_mutually_exclusive_group(required=True) 540*8975f5c5SAndroid Build Coastguard Worker obfuscation.add_argument('--obfuscate', action='store_true', 541*8975f5c5SAndroid Build Coastguard Worker help='Indicates to output an obfuscated profile given a deobfuscated ' 542*8975f5c5SAndroid Build Coastguard Worker 'one.') 543*8975f5c5SAndroid Build Coastguard Worker obfuscation.add_argument('--deobfuscate', dest='obfuscate', 544*8975f5c5SAndroid Build Coastguard Worker action='store_false', help='Indicates to output a deobfuscated profile ' 545*8975f5c5SAndroid Build Coastguard Worker 'given an obfuscated one.') 546*8975f5c5SAndroid Build Coastguard Worker options = parser.parse_args(args) 547*8975f5c5SAndroid Build Coastguard Worker 548*8975f5c5SAndroid Build Coastguard Worker if options.verbose: 549*8975f5c5SAndroid Build Coastguard Worker log_level = logging.WARNING 550*8975f5c5SAndroid Build Coastguard Worker else: 551*8975f5c5SAndroid Build Coastguard Worker log_level = logging.ERROR 552*8975f5c5SAndroid Build Coastguard Worker logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) 553*8975f5c5SAndroid Build Coastguard Worker 554*8975f5c5SAndroid Build Coastguard Worker dex = ProcessDex(_RunDexDump(options.dexdump_path, options.dex_path)) 555*8975f5c5SAndroid Build Coastguard Worker proguard_mapping, reverse_proguard_mapping = ProcessProguardMapping( 556*8975f5c5SAndroid Build Coastguard Worker _ReadFile(options.proguard_mapping_path), dex) 557*8975f5c5SAndroid Build Coastguard Worker if options.obfuscate: 558*8975f5c5SAndroid Build Coastguard Worker profile = ProcessProfile( 559*8975f5c5SAndroid Build Coastguard Worker _ReadFile(options.input_profile_path), 560*8975f5c5SAndroid Build Coastguard Worker reverse_proguard_mapping) 561*8975f5c5SAndroid Build Coastguard Worker else: 562*8975f5c5SAndroid Build Coastguard Worker profile = ProcessProfile( 563*8975f5c5SAndroid Build Coastguard Worker _ReadFile(options.input_profile_path), 564*8975f5c5SAndroid Build Coastguard Worker proguard_mapping) 565*8975f5c5SAndroid Build Coastguard Worker profile.WriteToFile(options.output_profile_path) 566*8975f5c5SAndroid Build Coastguard Worker 567*8975f5c5SAndroid Build Coastguard Worker 568*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 569*8975f5c5SAndroid Build Coastguard Worker main(sys.argv[1:]) 570