def save_html_report(output_dir, smalitree, app_name, granularity): templates = PageTemplateLoader(config.templates_path) resources_dir = os.path.join(output_dir, '.resources') Utils2.copytree(config.html_resources_dir_path, resources_dir) class_template = templates['class.pt'] for cl in smalitree.classes: save_class(cl, class_template, output_dir, app_name, granularity) save_coverage(smalitree, templates, output_dir, app_name, granularity)
def get_xml(self): report = Element("report") report.set("name", self.app_name) groups = Utils2.get_groupped_classes(self.smalitree) for g in groups: package = SubElement(report,"package") package.set("name", Utils2.get_package_name(g[0].name)) for cl in g: if (cl.is_coverable()): self.add_xml_class(package, cl) return etree.tostring(report, pretty_print=True)
def save_reporter_array_stats(self, classes_info, verbose=False): log_path = os.path.join("allocation_log.csv") csv_text = "" if self.mem_stats == "verbose": entries = [ "{},{},{}".format(self.package, cl[0], cl[1]) for cl in classes_info ] csv_text = "\n".join(entries) else: memory = sum(cl[1] for cl in classes_info) logging.info( "{} bytes allocated in AcvReporter.smali".format(memory)) csv_text = "{},{}".format(self.package, memory) Utils.log_entry(log_path, csv_text + '\n')
def save_package_indexhtml(class_group, templates, output_dir, app_name, granularity): folder = class_group[0].folder.replace('\\', '/') class_name_with_pkg = class_group[0].name package_name = Utils2.get_standart_package_name(class_name_with_pkg) init_table = templates['init_table.pt'] init_row = templates['init_row.pt'] index_template = templates['index.pt'] slash_num = class_name_with_pkg.count('/') root_path = '' for i in range(slash_num): root_path += '../' total_coverage_data = CoverageData() rows = [] for cl in class_group: elementlink = os.path.join(root_path, folder, cl.file_name + '.html').replace('\\', '/') elementname = cl.file_name coverage_data = CoverageData(lines=cl.coverable(), lines_missed=cl.not_covered(), lines_covered=cl.covered(), methods_covered=cl.mtds_covered(), methods_missed=cl.mtds_not_covered(), methods=cl.mtds_coverable()) coverage_data.update_coverage_for_single_class_from_methods() coverage = coverage_data.get_coverage(granularity) row = init_row( elementlink=elementlink, type='class', elementname=elementname, coverage=coverage_data.format_coverage(coverage), respath=root_path, coverage_data=coverage_data, is_instruction_level=Granularity.is_instruction(granularity), progress_missed=coverage_data.missed(granularity), progress_covered=coverage_data.covered(granularity)) rows.append(Markup(row)) total_coverage_data.add_data(coverage_data) total_coverage = total_coverage_data.get_formatted_coverage(granularity) table = init_table( rows=Markup("\n".join(rows)), total_coverage=total_coverage, is_instruction_level=Granularity.is_instruction(granularity), total_coverage_data=total_coverage_data, progress_covered=total_coverage_data.covered(granularity), progress_all=total_coverage_data.coverable(granularity)) html = index_template(table=Markup(table), appname=app_name, title=folder, package=package_name, respath=root_path, file_name=None, granularity=Granularity.get(granularity)) rel_path = os.path.join(folder, 'index.html').replace('\\', '/') path = os.path.join(output_dir, rel_path).replace('\\', '/') with open(path, 'w') as f: f.write(html) return (package_name, rel_path, total_coverage_data)
def instrument_apk(apk_path, result_dir, dbg_start=None, dbg_end=None, installation=False, granularity=Granularity.default, mem_stats=None): ''' I assume that the result_dir is empty is checked. ''' apktool = ApktoolInterface(javaPath=config.APKTOOL_JAVA_PATH, javaOpts=config.APKTOOL_JAVA_OPTS, pathApktool=Libs.APKTOOL_PATH, jarApktool=Libs.APKTOOL_PATH) package = get_apk_properties(apk_path).package unpacked_data_path = decompile_apk(apktool, apk_path, package, result_dir) manifest_path = get_path_to_manifest(unpacked_data_path) logging.info("decompiled {0}".format(package)) instrument_manifest(manifest_path) smali_code_path = get_path_to_smali_code(unpacked_data_path) pickle_path = get_pickle_path(apk_path, result_dir) instrument_smali_code(smali_code_path, pickle_path, package, granularity, dbg_start, dbg_end, mem_stats) logging.info("instrumented") instrumented_package_path = get_path_to_instrumented_package( apk_path, result_dir) remove_if_exits(instrumented_package_path) build_apk(apktool, unpacked_data_path, instrumented_package_path) Utils.rm_tree(unpacked_data_path) logging.info("built") instrumented_apk_path = get_path_to_insrumented_apk( instrumented_package_path, result_dir) sign_align_apk(instrumented_package_path, instrumented_apk_path) logging.info("apk instrumented: {0}".format(instrumented_apk_path)) logging.info("package name: {0}".format(package)) if installation: install(instrumented_apk_path) return (package, instrumented_apk_path, pickle_path)
def save_instrumented_smali(self, output_dir, instrument=True): '''Saves instrumented smali to the specified directory/''' print("saving instrumented smali: %s..." % output_dir) if os.path.exists(output_dir): shutil.rmtree(output_dir) os.makedirs(output_dir) classes_info = [] class_number = 0 # to make array name unique # Helps to find specific method that cased a fail after the instrumentation. # See '# Debug purposes' below method_number = 0 # dbg_ means specific part of the code defined by dbg_start-dbg_end # numbers will be instrumented dbg_instrument = instrument for class_ in self.smalitree.classes: class_path = os.path.join(output_dir, class_.folder, class_.file_name) code, cover_index, method_number, is_instrumented = self.instrument_class( class_, class_number, method_number=method_number, instrument=dbg_instrument, dbg_start=self.dbg_start, dbg_end=self.dbg_end) if dbg_instrument and is_instrumented: classes_info.append((class_.name, cover_index, class_number)) class_number += 1 self.save_class(class_path, code) if self.dbg and dbg_instrument and method_number > self.dbg_end: # Now leave other code not instrumented. dbg_instrument = False if self.dbg: print("Number of methods instrumented: {0}-{1} from {2}".format( self.dbg_start, self.dbg_end, method_number)) if instrument: self.generate_reporter_class(classes_info, output_dir) if self.mem_stats: self.save_reporter_array_stats(classes_info) Utils.copytree(self.instrumentation_smali_path, output_dir)
def save_coverage(tree, templates, output_dir, app_name, granularity): groups = Utils2.get_groupped_classes(tree) init_row = templates['init_row.pt'] init_table = templates['init_table.pt'] index_template = templates['index.pt'] rows = [] total_coverage_data = CoverageData() for g in groups: (package, path, coverage_data) = save_package_indexhtml(g, templates, output_dir, app_name, granularity) coverage = coverage_data.get_formatted_coverage(granularity) row = init_row( elementlink=path, type='package', elementname=package, coverage=coverage, respath='', coverage_data=coverage_data, is_instruction_level=Granularity.is_instruction(granularity), progress_covered=coverage_data.covered(granularity), progress_missed=coverage_data.missed(granularity)) rows.append(Markup(row)) total_coverage_data.add_data(coverage_data) total_coverage = total_coverage_data.get_formatted_coverage(granularity) table = init_table( rows=Markup("\n".join(rows)), total_coverage=total_coverage, total_coverage_data=total_coverage_data, is_instruction_level=Granularity.is_instruction(granularity), progress_covered=total_coverage_data.covered(granularity), progress_all=total_coverage_data.coverable(granularity)) root_path = '' html = index_template(table=Markup(table), appname=app_name, title=app_name, package=None, respath=root_path, file_name=None, granularity=Granularity.get(granularity)) path = os.path.join(output_dir, 'index.html') with open(path, 'w') as f: f.write(html)
def get_instrumented_insns_and_labels(self, method, reg_map, regs, cover_index, instrument=True): lines = [] block_move_insn = False labels = method.labels.values() labels_search = LabelReversedLoopSearch(labels) # The last insn is always goto, return or throw. We dont track them. last_insn_index = len(method.insns) - 1 labels = labels_search.find_reversed_by_index(last_insn_index + 1) if labels: insns, cover_index = self.get_instrumented_labels( labels, regs, cover_index, instrument and Granularity.is_instruction(self.granularity)) lines[0:0] = insns goto_hack_i = 0 throw_safe_indexes = [] if method.synchronized: throw_safe_indexes = Utils.scan_synchronized_tries(method) # we start reading the instructions from the end of the method in reversed loop for i in range(last_insn_index, -1, -1): insns = [] if instrument: line = self.get_insn_change_registers(method.insns[i], reg_map) else: line = method.insns[i].get_line() is_throw_safe = Utils.is_in_ranges(i, throw_safe_indexes) # dont track 'return*'insns if instrument and Granularity.is_instruction(self.granularity) and \ not block_move_insn and \ not method.insns[i].buf.startswith('return') and \ not method.insns[i].buf.startswith('goto') and \ not method.insns[i].buf.startswith('throw'): safe_insns, throwable_insns = self.get_throw_safe_tracking( line, regs, cover_index, goto_hack_i) lines[0:0] = safe_insns lines.extend(throwable_insns) if len(throwable_insns) > 0: goto_hack_i += 1 method.insns[i].cover_code = cover_index cover_index += 1 lines.insert(0, line) # set this flag if instruction before current should not be instrumented block_move_insn = instrument and Granularity.is_instruction(self.granularity) and \ self.not_instr_regex.match(method.insns[i].buf) is not None labels = labels_search.find_reversed_by_index(i) if labels: safe_insns, throwable_insns, cover_index = self.get_throw_safe_instr_labels( labels, regs, cover_index, instrument and Granularity.is_instruction(self.granularity) and \ not block_move_insn, goto_hack_i) lines[0:0] = safe_insns lines.extend(throwable_insns) if len(throwable_insns) > 0: goto_hack_i += 1 # :try_end_x goes immediatly after monitor-enter if instrument and not block_move_insn and labels and method.insns[ i - 1].buf.startswith('monitor-enter') and any( [any(l.tries) for l in labels]): block_move_insn = True #first tracking statement in the method if instrument: insns = self.get_tracking_insns(regs, cover_index) lines[0:0] = insns method.cover_code = cover_index cover_index += 1 return (lines, cover_index)
def save_class(cl, class_template, output_dir, app_name, granularity): dir = os.path.join(output_dir, cl.folder) if not os.path.exists(dir): os.makedirs(dir) class_path = os.path.join(dir, cl.file_name + '.html') buf = [LI_TAG(d) for d in cl.get_class_description()] buf.append(LI_TAG('')) buf.extend([LI_TAG(a) for a in cl.get_annotations()]) buf.append(LI_TAG('')) buf.extend([LI_TAG(f) for f in cl.get_fields()]) buf.append(LI_TAG('')) for m in cl.methods: ins_buf = [] labels = m.labels.values() labels = sorted(labels, key=attrgetter('index')) for i in range(len(m.insns)): ins = m.insns[i] if ins.covered: ins_buf.append(span_tab_tag(ins.buf, COV_CLASS)) else: if ins.buf.startswith("return"): lbl = get_first_lbl_by_index(labels, i) if lbl is not None: if lbl.covered: ins_buf.append(span_tab_tag(ins.buf, EXEC_CLASS)) else: if m.insns[i - 1].covered: ins_buf.append(span_tab_tag(ins.buf, EXEC_CLASS)) else: if i < len(m.insns) - 1 and m.insns[ i + 1].covered and not_instr_regex.match( m.insns[i + 1].buf): ins_buf.append(span_tab_tag(ins.buf, EXEC_CLASS)) else: ins_buf.append(span_tab_tag(ins.buf)) # insert labels and tries # sort the labels by index count = 0 for l in labels: if l.covered: ins_buf.insert(l.index + count, span_tab_tag(l.buf, COV_CLASS)) else: ins_buf.insert(l.index + count, span_tab_tag(l.buf)) count += 1 for t in l.tries: ins_buf.insert(l.index + count, span_tab_tag(t.buf)) count += 1 if l.switch: for sl in l.switch.buf: ins_buf.insert(l.index + count, span_tab_tag(sl)) count += 1 if l.array_data: for sl in l.array_data.buf: ins_buf.insert(l.index + count, span_tab_tag(sl)) count += 1 ins_buf.insert(0, LI_TAG('')) for a in m.annotations: a.reload() ins_buf[0:0] = [span_tab_tag(d) for d in a.buf] for p in reversed(m.parameters): p.reload() ins_buf[0:0] = [span_tab_tag(d) for d in p.buf] ins_buf.insert(0, span_tab_tag(m.get_registers_line())) html_method_line = span_tag( html_escape(m.get_method_line()), COV_CLASS) if m.called else m.get_method_line() ins_buf.insert(0, html_method_line) ins_buf.append(LI_TAG(".end method")) buf.append(LI_TAG('')) buf.extend(ins_buf) slash_num = cl.name.count('/') respath = '' for i in range(slash_num): respath += '../' html = class_template(code=Markup("\n".join(buf)), appname=app_name, title=cl.file_name, package=Utils2.get_standart_package_name(cl.name), respath=respath, granularity=Granularity.get(granularity)) with open(class_path, 'w') as f: f.write(html)