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