1#!/usr/bin/python3 2# 3# Copyright (C) 2021 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 17# 18# This script can get info out of dexfiles using libdexfile.so external API 19# 20 21from abc import ABC 22from ctypes import * 23import os.path 24import functools 25import zipfile 26 27libdexfile = CDLL( 28 os.path.expandvars("$ANDROID_HOST_OUT/lib64/libdexfiled.so")) 29 30ExtDexFile = c_void_p 31 32class ExtMethodInfo(Structure): 33 """Output format for MethodInfo""" 34 _fields_ = [("sizeof_struct", c_size_t), 35 ("addr", c_int32), 36 ("size", c_int32), 37 ("name", POINTER(c_char)), 38 ("name_size", c_size_t)] 39 40AllMethodsCallback = CFUNCTYPE(c_int, c_void_p, POINTER(ExtMethodInfo)) 41libdexfile.ExtDexFileOpenFromMemory.argtypes = [ 42 c_void_p, 43 POINTER(c_size_t), 44 c_char_p, 45 POINTER(ExtDexFile) 46] 47libdexfile.ExtDexFileOpenFromMemory.restype = c_int 48libdexfile.ExtDexFileGetAllMethodInfos.argtypes = [ 49 ExtDexFile, c_int, AllMethodsCallback, c_void_p 50] 51 52class DexClass(object): 53 """Accessor for DexClass Data""" 54 55 def __init__(self, name): 56 self.name = name.strip() 57 self.arrays = name.count("[") 58 self.base_name = self.name if self.arrays == 0 else self.name[:-( 59 self.arrays * 2)] 60 61 def __repr__(self): 62 return self.name 63 64 @functools.cached_property 65 def descriptor(self): 66 """The name as a descriptor""" 67 if self.base_name == "int": 68 return "[" * self.arrays + "I" 69 elif self.base_name == "short": 70 return "[" * self.arrays + "S" 71 elif self.base_name == "long": 72 return "[" * self.arrays + "J" 73 elif self.base_name == "char": 74 return "[" * self.arrays + "C" 75 elif self.base_name == "boolean": 76 return "[" * self.arrays + "Z" 77 elif self.base_name == "byte": 78 return "[" * self.arrays + "B" 79 elif self.base_name == "float": 80 return "[" * self.arrays + "F" 81 elif self.base_name == "double": 82 return "[" * self.arrays + "D" 83 elif self.base_name == "void": 84 return "[" * self.arrays + "V" 85 else: 86 return "[" * self.arrays + "L{};".format("/".join( 87 self.base_name.split("."))) 88 89 90class Method(object): 91 """Method info wrapper""" 92 93 def __init__(self, mi): 94 self.offset = mi.addr 95 self.len = mi.size 96 self.name = string_at(mi.name, mi.name_size).decode("utf-8") 97 98 def __repr__(self): 99 return "(" + self.name + ")" 100 101 @functools.cached_property 102 def descriptor(self): 103 """name as a descriptor""" 104 ret = DexClass(self.name.split(" ")[0]) 105 non_ret = self.name[len(ret.name) + 1:] 106 arg_str = non_ret[non_ret.find("(") + 1:-1] 107 args = [] if arg_str == "" else map( 108 lambda a: DexClass(a.strip()).descriptor, arg_str.split(",")) 109 class_and_meth = non_ret[0:non_ret.find("(")] 110 class_only = DexClass(class_and_meth[0:class_and_meth.rfind(".")]) 111 meth = class_and_meth[class_and_meth.rfind(".") + 1:] 112 return "{cls}->{meth}({args}){ret}".format( 113 cls=class_only.descriptor, 114 meth=meth, 115 args="".join(args), 116 ret=ret.descriptor) 117 118 @functools.cached_property 119 def name_only(self): 120 """name without the return-type or arguments in java format""" 121 ret = DexClass(self.name.split(" ")[0]) 122 non_ret = self.name[len(ret.name) + 1:] 123 class_and_meth = non_ret[0:non_ret.find("(")] 124 return class_and_meth 125 126 @functools.cached_property 127 def klass(self): 128 """declaring DexClass.""" 129 ret = DexClass(self.name.split(" ")[0]) 130 non_ret = self.name[len(ret.name) + 1:] 131 class_and_meth = non_ret[0:non_ret.find("(")] 132 return DexClass(class_and_meth[0:class_and_meth.rfind(".")]) 133 134 135class BaseDexFile(ABC): 136 """DexFile base class""" 137 138 def __init__(self): 139 self.ext_dex_file_ = None 140 return 141 142 @functools.cached_property 143 def methods(self): 144 """Methods in the dex-file""" 145 meths = [] 146 147 @AllMethodsCallback 148 def my_cb(_, info): 149 """Callback visitor for method infos""" 150 meths.append(Method(info[0])) 151 return 0 152 153 libdexfile.ExtDexFileGetAllMethodInfos(self.ext_dex_file_, 154 c_int(1), my_cb, c_void_p()) 155 return meths 156 157class MemDexFile(BaseDexFile): 158 """DexFile using memory""" 159 160 def __init__(self, dat, loc): 161 assert type(dat) == bytes 162 super().__init__() 163 # Don't want GC to screw us over. 164 self.mem_ref = (c_byte * len(dat)).from_buffer_copy(dat) 165 res_fle_ptr = pointer(c_void_p()) 166 res = libdexfile.ExtDexFileOpenFromMemory( 167 self.mem_ref, byref(c_size_t(len(dat))), 168 create_string_buffer(bytes(loc, "utf-8")), res_fle_ptr) 169 if res != 0: 170 raise Exception("Failed to open file: {}. Error {}.".format(loc, res)) 171 self.ext_dex_file_ = res_fle_ptr.contents 172 173class FileDexFile(MemDexFile): 174 """DexFile using a file""" 175 176 def __init__(self, file, loc): 177 if type(file) == str: 178 self.file = open(file, "rb") 179 self.loc = file 180 else: 181 self.file = file 182 self.loc = "file_obj" 183 super().__init__(self.file.read(), self.loc) 184 185def OpenJar(fle): 186 """Opens all classes[0-9]*.dex files in a zip archive""" 187 res = [] 188 with zipfile.ZipFile(fle) as zf: 189 for f in zf.namelist(): 190 if f.endswith(".dex") and f.startswith("classes"): 191 res.append( 192 MemDexFile(zf.read(f), "classes" if type(fle) != str else fle)) 193 return res 194