def inject(self, smali_tree, level): # get a copy of smali tree st = copy.deepcopy(smali_tree) # load api database print "Loading and processing API database..." level = self.load_api(level) print "Target API Level: %d" % level # check and fix apis in API_LIST method_descs = [] for m in self.entries: c = "" api_name = "" method_name = "" ia = m.find("->") ilb = m.find('(') if ia >= 0: c = m[:ia] if ilb >= 0: method_name = m[ia + 2:ilb] api_name = m[ia + 2:] else: method_name = m[ia + 2:] else: c = m if not self.android_api.classes.has_key(c): print "[Warn] Class not found in API-%d db: %s" % (level, m) continue # just class name if not method_name: ms = self.android_api.classes[c].methods.keys() method_descs.extend(ms) # full signature elif api_name: if not self.android_api.classes[c].methods.has_key(m): if method_name == "<init>": print "[Warn] Method not found in API-%d db: %s" % (level, m) continue c_obj = self.android_api.classes[c] existed = False q = c_obj.supers while q: cn = q.pop(0) c_obj = self.android_api.classes[cn] nm = c_obj.desc + "->" + api_name if c_obj.methods.has_key(nm): existed = True if not nm in self.entries: print "[Warn] Inferred API: %s" % (nm, ) method_descs.append(nm) else: q.extend(self.android_api.classes[cn].supers) if not existed: print "[Warn] Method not found in API-%d db: %s" % (level, m) else: method_descs.append(m) # signature without parameters else: own = False if self.android_api.classes[c].methods_by_name.has_key(method_name): ms = self.android_api.classes[c].methods_by_name[method_name] method_descs.extend(ms) own = True if method_name == "<init>": continue c_obj = self.android_api.classes[c] existed = False q = c_obj.supers while q: cn = q.pop(0) c_obj = self.android_api.classes[cn] if c_obj.methods_by_name.has_key(method_name): existed = True inferred = "%s->%s" % (c_obj.desc, method_name) if not inferred in self.entries: print "[Warn] Inferred API: %s" % inferred method_descs.extend(c_obj.methods_by_name[method_name]) else: q.extend(self.android_api.classes[cn].supers) if (not own) and (not existed): print "[Warn] Method not found in API-%d db: %s" % (level, m) self.method_descs = list(set(method_descs)) """ print "**************************" self.method_descs.sort() print "\n".join(self.method_descs) print "**************************" """ for m in self.method_descs: self.api_dict[m] = "" ia = m.find("->") ilb = m.find('(') if m[ia + 2:ilb] != "<init>": self.api_name_dict[m[ia + 2:]] = m[:ia] print "Done!" print "Injecting..." for c in st.classes: class_ = AndroidClass() class_.isAPI = False class_.desc = c.name class_.name= c.name[1:-1].replace('/', '.') class_.access = c.access if "interface" in c.access: class_.supers.extend(c.implements) else: class_.implements = c.implements class_.supers.append(c.super_name) for m in c.methods: method = AndroidMethod() method.isAPI = False method.desc = "%s->%s" % (c.name, m.descriptor) method.name = m.descriptor.split('(', 1)[0] #print method.desc method.sdesc = method.desc[:method.desc.rfind(')') + 1] method.access = m.access class_.methods[method.sdesc] = method self.android_api.add_class(class_) self.android_api.build_connections(False) #self.android_api.show_not_API() for c in st.classes: for m in c.methods: i = 0 while i < len(m.insns): insn = m.insns[i] if insn.fmt == "35c": md = insn.obj.method_desc on = insn.opcode_name irb = md.find(')') smd = md[:irb + 1] if self.api_dict.has_key(smd): method_type = METHOD_TYPE_BY_OPCODE[on] new_on = OPCODE_MAP[on] if not self.method_map.has_key(md): self.add_stub_method(on, md) if method_type == "constructor": insn_m = copy.deepcopy(insn) insn_m.obj.replace(new_on, \ self.method_map[md]) r = insn_m.obj.registers.pop(0) m.insert_insn(insn_m, i , 0) i += 1 """ insn.obj.replace(new_on, \ self.method_map[md]) r = insn.obj.registers.pop(0) m.insert_insn(InsnNode(\ "move-result-object %s" % r), i + 1, 0) i += 1 """ else: insn.obj.replace(new_on, \ self.method_map[md]) else: ia = md.find("->") cn = md[:ia] api_name = smd[ia + 2:] if self.api_name_dict.has_key(api_name): if self.android_api.classes.has_key(cn): if not self.android_api.classes[cn].methods.has_key(smd): api_cn = self.api_name_dict[api_name] if api_cn in self.android_api.classes[cn].ancestors: self.api_dict[smd] = "" i -= 1 elif insn.fmt == "3rc": md = insn.obj.method_desc on = insn.opcode_name smd = md[:md.rfind(')') + 1] if self.api_dict.has_key(smd): method_type = METHOD_TYPE_BY_OPCODE[on] new_on = OPCODE_MAP[on] if not self.method_map.has_key(md): self.add_stub_method(on, md) if method_type == "constructor": insn_m = copy.deepcopy(insn) insn_m.obj.replace(new_on, \ self.method_map[md]) r = insn_m.obj.reg_start nr = r[0] + str(int(r[1:]) + 1) insn_m.obj.set_reg_start(nr) m.insert_insn(insn_m, i , 0) i += 1 """ insn.obj.replace(new_on, \ self.method_map[md]) r = insn.obj.reg_start nr = r[0] + str(int(r[1:]) + 1) insn.obj.set_reg_start(nr) m.insert_insn(InsnNode(\ "move-result-object %s" % r), i + 1, 0) i += 1 """ else: insn.obj.replace(new_on, \ self.method_map[md]) else: ia = md.find("->") cn = md[:ia] api_name = smd[ia + 2:] if self.api_name_dict.has_key(api_name): if self.android_api.classes.has_key(cn): if not self.android_api.classes[cn].methods.has_key(smd): api_cn = self.api_name_dict[api_name] if api_cn in self.android_api.classes[cn].ancestors: self.api_dict[smd] = "" i -= 1 i += 1 for c in self.stub_classes.values(): st.add_class(c) st.add_class(self.helper) print "Done!" return st
def inject(self, smali_tree, level, hooks): # get a copy of smali tree assert isinstance(hooks,dict) st = copy.deepcopy(smali_tree) print "Injecting hooks at %s"%hooks # load api database print "Loading and processing API database..." level = self.load_api(level) print "Target API Level: %d" % level # check and fix apis in API_LIST method_descs = [] for m in self.entries: #self.entries = list of APIs to instrument c = "" api_name = "" method_name = "" ia = m.find("->") ilb = m.find('(') if ia >= 0: c = m[:ia] # c is the class name (e.g. Landroid/content/Intent;) if ilb >= 0: method_name = m[ia + 2:ilb] api_name = m[ia + 2:] else: method_name = m[ia + 2:] # name of method (e.g., <init>) else: c = m if not self.android_api.classes.has_key(c): print "[Warn] Class not found in API-%d db: %s" % (level, m) continue # just class name if not method_name: ms = self.android_api.classes[c].methods.keys() method_descs.extend(ms) # full signature elif api_name: if not self.android_api.classes[c].methods.has_key(m): if method_name == "<init>": print "[Warn] Method not found in API-%d db: %s" % (level, m) continue c_obj = self.android_api.classes[c] existed = False q = c_obj.supers while q: cn = q.pop(0) c_obj = self.android_api.classes[cn] nm = c_obj.desc + "->" + api_name if c_obj.methods.has_key(nm): existed = True if not nm in self.entries: print "[Warn] Inferred API: %s" % (nm, ) method_descs.append(nm) else: q.extend(self.android_api.classes[cn].supers) if not existed: print "[Warn] Method not found in API-%d db: %s" % (level, m) else: method_descs.append(m) # signature without parameters else: own = False if self.android_api.classes[c].methods_by_name.has_key(method_name): ms = self.android_api.classes[c].methods_by_name[method_name] method_descs.extend(ms) own = True if method_name == "<init>": continue c_obj = self.android_api.classes[c] # an instance of AndroidClass existed = False q = c_obj.supers # super classes of the current class represented by c_obj while q: cn = q.pop(0) c_obj = self.android_api.classes[cn] if c_obj.methods_by_name.has_key(method_name): existed = True inferred = "%s->%s" % (c_obj.desc, method_name) if not inferred in self.entries: print "[Warn] Inferred API: %s" % inferred method_descs.extend(c_obj.methods_by_name[method_name]) else: q.extend(self.android_api.classes[cn].supers) if (not own) and (not existed): print "[Warn] Method not found in API-%d db: %s" % (level, m) self.method_descs = list(set(method_descs)) print "Injecting..." for c in st.classes: # we now transform a SmaliTree-Class to and AndroidClass class_ = AndroidClass() class_.isAPI = False class_.desc = c.name class_.name= c.name[1:-1].replace('/', '.') # the "real" class name in canonical form class_.access = c.access if "interface" in c.access: # if this is an Interface ... class_.supers.extend(c.implements) else: class_.implements = c.implements class_.supers.append(c.super_name) for m in c.methods: method = AndroidMethod() method.isAPI = False method.desc = "%s->%s" % (c.name, m.descriptor) method.name = m.descriptor.split('(', 1)[0] #print method.desc method.sdesc = method.desc[:method.desc.rfind(')') + 1] method.access = m.access class_.methods[method.sdesc] = method self.android_api.add_class(class_) self.android_api.build_connections(False) #add TrackerClass st.classes.append(ClassNode(os.path.join(os.path.dirname(__file__), 'TrackerClass.smali'))) # Number of registers added to a method MORE_REGS = 6 methods_to_fix = {} #key: classname.methodname, value: method object ######## CHECK FOR NEED OF FIXING PARAMS SHIFTED ABOVE v15 AND REPLACE pX by vY ################ for c in st.classes: # iterate over all classes ... for m in c.methods: # ... and all methods. qualified_name = self.to_java_notation(c,m) # if qualified_name in hooks: #Test if we need to rewrite registers (has pX been moved to X>15?) if m.registers-len(m.paras)+MORE_REGS>15 and m.registers-len(m.paras)-1 <= 15: if ''.join([c.name,m.name]) not in methods_to_fix: for i, insn in enumerate(m.insns): if insn.opcode_name in OPCODE_MAP: if len([value for key, value in hooks.items() if key in insn.buf])>0: print insn print m.descriptor methods_to_fix[''.join([c.name,m.name,m.descriptor])] = m all_ps = re.findall(r'[\s|\{]p(\d)', str(insn)) ins_buf = insn.buf for p in all_ps: pos = m.registers-self.count_params(m) + int(p[0]) print ' this statement uses parameter %s which should be rewritten to v%s'%(p,pos) ins_buf = ins_buf.replace('p%s'%p,'v%d'%pos) new_ins = InsnNode(ins_buf) print ' the new ins is now %s '%new_ins m.insns[i] = new_ins ################ MOVE PARAMETERS TO THEIR ORIGINAL POSITIONS ############################### for methodname in methods_to_fix: m = methods_to_fix[methodname] #p0 is not contained in m.paras pos = m.registers-self.count_params(m) i=0 if 'static' not in m.access: #non-static methods have this->p0 instr = InsnNode("move-object/16 v%d, p0"%(pos)) m.insert_insn(instr, 0, 0) i += 1 for p in m.paras: print 'Fixing %s in %s'%(i,methodname) # Rename pX to v(old_reg_count + MORE_REGS) pos = m.registers-self.count_params(m) + i if p.basic and not p.dim > 0: if p.words <= 1: instr = InsnNode("move/16 v%d, p%s"%(pos,i)) i += 1 elif p.words >= 2: instr = InsnNode("move-wide/16 v%d, p%s"%(pos,i)) #@todo have wide regs been considered when adding registers to the method? i += 2 else: instr = InsnNode("move-object/16 v%d, p%s"%(pos,i)) i+=1 m.insert_insn(instr, 0, 0) ######################################################################### for c in st.classes: # iterate over all classes ... assert isinstance(c, ClassNode) print "CLASS %s"%c.name for f in c.fields: print " %s"%f for intf in c.implements: print " %s"%intf for m in c.methods: # ... and all methods. assert isinstance(m, MethodNode) print " METHOD %s %s"%(m.name,m.descriptor) i = 0 ADDED_LINES = 0 if ''.join([c.name,m.name,m.descriptor]) in methods_to_fix: ADDED_LINES = len(m.paras)+1 i += ADDED_LINES if 'static' in m.access: i -= 1 ADDED_LINES -= 1 new_i = i #new_i: saves position of new code and jumps over newly added code #print "new_i starts at %d (%d parameters and %d added hooks. This is 0:%s)"%(new_i,len(m.paras),ADDED_LINES, (i-ADDED_LINES)) # Check if method contains call to instrument skip_method = True for ix in m.insns: print " INS %s"%ix.buf if ix.opcode_name.startswith('invoke'): if any(func in ix.buf for func,_ in hooks.items()): skip_method = False print "YEAH: %s"%ix.buf break if skip_method: continue print "relevant: %s"%ix.opcode_name #Add MORE_REGS more registers for additional info m.set_registers(m.registers+MORE_REGS) new_regs = [] for i in range(0,MORE_REGS-1): print "j: %s , %s"%(i,m.registers-MORE_REGS+i) new_regs.append("v%d"%(m.registers-MORE_REGS+i)) while new_i < len(m.insns): # m.insns = list of bytecode instructions as strings (e.g. "invoke-direct {p0}, ..") insn = m.insns[new_i] # iterate over all statements of the current method if str(insn).startswith('#'): # getter and setter comments in smali which would otherwise be considered a statement new_i += 1 insn = m.insns[new_i] if insn.buf.startswith('invoke-'): applicable_hooks = [value for key, value in hooks.items() if key in insn.buf] if len(applicable_hooks)>0: print "YES: %s"%insn.buf else: print "NO: %s"%insn.buf if len(applicable_hooks)>0: # Get parameters assert isinstance(insn, InsnNode) print "Searching for parameters in %s"%(insn.buf) params = self._parse_paras(insn.buf) regs = insn.obj.registers for j,p in enumerate(params): if p.basic: if str(p) == 'I': # cast int to Integer instr = InsnNode("new-instance %s, Ljava/lang/Integer;"%(new_regs[1])) m.insert_insn(instr, new_i, 0) new_i += 1 instr = InsnNode("invoke-direct {%s, %s}, Ljava/lang/Integer;-><init>(I)V"%(new_regs[1], insn.obj.registers[j])) m.insert_insn(instr, new_i, 0) new_i += 1 instr = InsnNode("return-object %s"%(new_regs[1])) m.insert_insn(instr, new_i, 0) new_i += 1 if 'range' in insn.opcode_name: print "Invoke-Range not yet implemented!: %s, %s"%regs,insn.buf #create an array instr = InsnNode("const/4 %s, %d"%(new_regs[1], len(regs))) m.insert_insn(instr, new_i, 0) new_i += 1 instr = InsnNode("new-array %s, %s, [Ljava/lang/Object;"%(new_regs[0],new_regs[1])) #TODO type inference required m.insert_insn(instr, new_i, 0) new_i += 1 for j,r in enumerate(regs): instr = InsnNode("aput-object %s, %s, %s "%(new_regs[j+2],new_regs[0],r)) m.insert_insn(instr, new_i, 0) new_i += 1 # Call hook instr = InsnNode("invoke-static %s %s"%(new_regs[0],applicable_hooks[0])) #TODO call more than one hook, if any m.insert_insn(instr, new_i, 0) new_i += 1 # Copy back results instr = InsnNode("move-result %s"%(new_regs[0])) m.insert_insn(instr, new_i, 0) new_i += 1 for j,r in enumerate(regs): instr = InsnNode("aget-object %s, %s, %s "%(new_regs[j+2],new_regs[0],r)) m.insert_insn(instr, new_i, 0) new_i += 1 for p in params: if p.basic: instr = InsnNode("CASTMEBACK %s, %d"%(new_regs[1], len(regs))) #TODO Cast from Box typ m.insert_insn(instr, new_i, 0) new_i += 1 i += 1 new_i += 1 print "Instrumentation done!" return st