def __init__(self, view, function): self._view = view self._function = function self.arch = self._view.arch.name if self.arch not in ARCHS: show_message_box( 'RetDec Offline Decompiler', 'Only {} architectures are supported'.format(', '.join(ARCHS)), MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.InformationIcon) raise Exception('unsupported architecture') self.endianness = 'big' if self._view.endianness else 'little' self.cmdline = ['retdec-decompiler.sh'] self.cmdline.append('-m') self.cmdline.append('raw') self.cmdline.append('-a') self.cmdline.append(self.arch) self.cmdline.append('-e') self.cmdline.append(self.endianness) self.cmdline.append('--backend-no-debug-comments') self.cmdline.append('--cleanup')
def run_harness_generation(view, func): """ Experimental automatic fuzzer harness generation support """ template_file = os.path.join(binaryninja.user_plugin_path(), "fuzzable") if view.view_type == "ELF": template_file += "/templates/linux.cpp" else: interaction.show_message_box( "Error", "Experimental harness generation is only supported for ELFs at the moment", ) return # parse out template based on executable format, and start replacing with open(template_file, "r") as fd: template = fd.read() log.log_info("Replacing elements in template") template = template.replace("{NAME}", func.name) template = template.replace("{RET_TYPE}", str(func.return_type)) harness = interaction.get_save_filename_input("Filename to write to?", "cpp") harness = csv_file.decode("utf-8") + ".cpp" log.log_info("Writing new template to workspace") with open(harness, "w+") as fd: fd.write(template) interaction.show_message_box("Success", f"Done, wrote fuzzer harness to {harness}")
def easypatch(bv, addr): """ Shows a dialog containing all memory operands at current instruction and prompts for data to overwrite with. Expression is eval()ed so \n and \0 are valid inputs """ targets = get_memory_operands_at(bv, addr) targets_field = interaction.ChoiceField('Patch Target:', targets) patch_text_field = interaction.TextLineField('Patch text:') valid_input = interaction.get_form_input([targets_field, patch_text_field], 'easypatch') if valid_input: target = targets[targets_field.result].addr patch_text = eval("'%s'" % patch_text_field.result) if not bv.write(target, patch_text): if not make_valid_for_writing(bv, target, len(patch_text)): interaction.show_message_box( 'easypatch', 'Failed to make writable to %x' % target) elif not bv.write(target, patch_text): interaction.show_message_box('easypatch', 'Failed to write to %x' % target) else: function = bv.get_basic_blocks_at(addr)[0].function function.reanalyze() else: function = bv.get_basic_blocks_at(addr)[0].function function.reanalyze()
def install(self, code_start_addr): assert self.is_assembled(), "Invalid Hookstate" self.code_start_addr = code_start_addr hook_str = self.get_hook_format().format(self.code_start_addr - self.hook_addr) try: self.hook_bytes = self.arch.assemble(hook_str) except: err = sys.exc_info()[0] show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(hook_str, err), icon=MessageBoxIcon.ErrorIcon) return False ret_str = self.get_hook_format().format(self.ret_addr - (self.code_start_addr + self.code_length()) + self.get_hook_len()) try: self.ret_bytes = self.arch.assemble(ret_str) except: err = sys.exc_info()[0] show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(ret_str, err), icon=MessageBoxIcon.ErrorIcon) return False written = self.bv.write(self.hook_addr, self.arch.convert_to_nop(self.replaced_bytes, 0)) if written != len(self.replaced_bytes): return False written = self.bv.write(self.hook_addr, self.hook_bytes) if written != len(self.hook_bytes): return False written = self.bv.write(self.code_start_addr, self.code_bytes + self.replaced_bytes + self.ret_bytes) if written != self.code_length(): return False self._state = HookState.INSTALLED return True
def avoid_address(self, bv, addr): if(addr == self.start or addr == self.end): show_message_box("Afl-Unicorn", "Start or End address cannot be avoided !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.WarningIcon) return self.avoid = addr try: blocks = bv.get_basic_blocks_at(addr) for block in blocks: if(addr == self.avoid and addr in self.avoid_addresses): block.function.set_auto_instr_highlight( self.avoid, HighlightStandardColor.NoHighlightColor) self.avoid_addresses = [ x for x in self.avoid_addresses if x != addr] self.avoid = addr else: block.function.set_auto_instr_highlight( addr, HighlightStandardColor.RedHighlightColor) if addr not in self.avoid_addresses: self.avoid_addresses.append(addr) self.avoid = addr binja.log_info("Avoid address: 0x{0:0x}".format(addr)) except: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def _start_unicorn_emulation(self, harness_file, json_file, dumped_memory, input_file): try: output = subprocess.Popen(['python', harness_file.result, '-d', json_file.result, dumped_memory.result, input_file.result], stdout=subprocess.PIPE).communicate()[0] binja.log_info(output) except TypeError: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def clear_end_address(self, bv): if self.end: end_block = bv.get_basic_blocks_at(self.end) self.clear_address(end_block, self.end) self.end = None else: show_message_box("Afl-Unicorn", "End address not set !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.WarningIcon) return
def clear_start_address(self, bv): if self.start: start_block = bv.get_basic_blocks_at(self.start) self.clear_address(start_block, self.start) self.start = None else: show_message_box("Afl-Unicorn", "Start address not set !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.WarningIcon) return
def _menuAction_evaluate_upto_buffer(self, buffer_id): buff = self.current_state.symbolic_buffers[buffer_id][0] n_eval = get_int_input("How many values (upto) ?", "Number of distinct values") r = "" for i, v in enumerate(self.current_state.solver.evaluate_upto(buff, n_eval)): r += "solution %d: %s\n" % (i, hex(v.value)) show_message_box("%s evaluate" % buff.name, r)
def _start_afl_fuzz(self, afl_binary, inputs, outputs, harness_file, json_file, dumped_memory): try: global process process = subprocess.Popen([afl_binary.result, '-U', '-m', 'none', '-i', inputs.result, '-o', outputs.result, '--', 'python', harness_file.result, json_file.result, dumped_memory.result, '@@'], preexec_fn=os.setsid) binja.log_info('Process {0} started'.format( os.getpgid(process.pid))) except: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def solve(bv): if len(bv.session_data.angr_find) == 0: show_message_box("Angr Solve", "You have not specified a goal instruction.\n\n" + "Please right click on the goal instruction and select \"Find Path to This Instruction\" to " + "continue.", MessageBoxButtonSet.OKButtonSet, MessageBoxButtonSet.ErrorIcon) return # Start a solver thread for the path associated with the view s = Solver(bv.session_data.angr_find, bv.session_data.angr_avoid, bv) s.start()
def solve(bv): if len(bv.session_data.angr_find) == 0: show_message_box("Angr Solve", "You have not specified a goal instruction.\n\n" + "Please right click on the goal instruction and select \"Find Path to This Instruction\" to " + "continue.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) return # Start a solver thread for the path associated with the view s = Solver(bv.session_data.angr_find, bv.session_data.angr_avoid, bv) s.start()
def check_client(self, message_box=False): if self._client is None: if message_box: show_message_box( "BinSync client does not exist", "You haven't connected to a binsync repo. Please connect to a binsync repo first.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon, ) return False return True
def launch_binsync_configure(context): if context.binaryView is None: show_message_box( "No binary is loaded", "There is no Binary View available. Please open a binary in Binary Ninja first.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon, ) return d = ConfigDialog(controller) d.exec_()
def on_downloadRequested(self, download): old_path = download.url().path() # download.path() suffix = QFileInfo(old_path).suffix() if (suffix.lower() in ["zip", "svd", "pack", "patched"]): log_debug(f"SVD Browser: Downloading {str(download.url())}") if suffix.lower() == "svd" or suffix.lower() == "patched": download.setDownloadDirectory(svdPath) download.accept() else: with TemporaryDirectory() as tempfolder: log_debug( f"SVD Browser: Downloading pack/zip to {tempfolder}") fname = download.url().fileName() r = requests.get(download.url().toString(), allow_redirects=True) dlfile = os.path.join(tempfolder, fname) open(dlfile, "wb").write(r.content) ''' # TODO: See if the original QT Downloader can be fixed since it would # help with situations where the user entered credentials in the browser. download.setDownloadDirectory(tempfolder) download.accept() while not download.finished: import time time.sleep(100) ''' if fname.endswith(".zip") or fname.endswith(".pack"): destFolder = os.path.join(svdPath, os.path.splitext(fname)[0]) log_debug(f"SVD Browser: Creating {destFolder}") if not os.path.exists(destFolder): os.mkdir(destFolder) with ZipFile(dlfile, 'r') as zipp: for ifname in zipp.namelist(): if ifname.endswith(".svd"): info = zipp.getinfo(ifname) info.filename = os.path.basename( info.filename) log_debug( f"SVD Browser: Extracting {info.filename} from {ifname}" ) zipp.extract(info, path=destFolder) else: #Move file into place shutil.move(dlfile, svdPath) else: show_message_box( "Invalid file", "That download does not appear to be a valid SVD/ZIP/PACK file." ) download.cancel()
def parse_asm_string(self, asm_string): assert self.is_new(), "Invalid Hookstate" try: asm_bytes = self.arch.assemble(asm_string) except: err = sys.exc_info()[0] show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(asm_string, err), icon=MessageBoxIcon.ErrorIcon) return False self.code_bytes += asm_bytes self._state = HookState.ASSEMBLED return True
def test_harness(self, bv): form_menu = False dumped_memory = DirectoryNameField( 'Select folder with dumped memory') input_file = OpenFileNameField('Select input file') json_file = OpenFileNameField('Select json data file') harness_file = OpenFileNameField('Select harness test file') if(self.dumped_memory != None or self.json_file != None or self.input_file != None or self.harness_file != None): form_menu = self._display_menu([self.dumped_memory.result, self.input_file.result, self.harness_file.result, self.json_file.result], "Afl-unicorn Harness Test Menu") if form_menu == True: self._start_unicorn_emulation( self.harness_file, self.json_file, self.dumped_memory, self.input_file) else: result = show_message_box("Afl-Unicorn", "Do you want to clear test data ?", MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.WarningIcon) if(result == 1): self._clear_harness_data() else: form_menu = self._display_menu( [dumped_memory, input_file, harness_file, json_file], "Afl-unicorn Harness Test Menu") if(dumped_memory.result == None or input_file.result == None or harness_file.result == None or json_file == None): return if(len(dumped_memory.result) <= 0 or len(input_file.result) <= 0 or len(harness_file.result) <= 0 or len(json_file.result) <= 0): show_message_box("Afl-Unicorn", "All fields are required !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) return binja.log_info("Selected dumped memory folder: {0}".format( dumped_memory.result)) binja.log_info("Selected input file: {0}".format( input_file.result)) binja.log_info("Selected json data file: {0}".format( json_file.result)) binja.log_info("Selected harness test file: {0}".format( harness_file.result)) self.dumped_memory = dumped_memory self.input_file = input_file self.json_file = json_file self.harness_file = harness_file if form_menu == True: self._start_unicorn_emulation( self.harness_file, self.json_file, self.dumped_memory, self.input_file)
def _evaluate_with_solver(self, address, expr): val = "" if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) self.current_state.mem.store(address, new_expr) self.changes.add( (address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) show_message_box("Expression at %s" % hex(address), "The value was indeed concrete! State modified") else: val = self.current_state.solver.evaluate(expr).value show_message_box("Value at %s (with solver):" % hex(address), hex(val))
def _concretize_ascii_str(self, address, expr): extra_constraints = [] for i in range(expr.size // 8): b = expr.Extract(i * 8 + 7, i * 8) extra_constraints.extend([b <= 0x7e, b >= 0x20]) if not self.current_state.solver.satisfiable(extra_constraints): show_message_box( "Info", "The selected memory is not an ascii str (unsat)") return new_expr = self.current_state.solver.evaluate(expr, extra_constraints) self.current_state.mem.store(address, new_expr) self.current_state.solver.add_constraints(expr == new_expr) self.changes.add((address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state)
def generate_all_platforms(bv): platforms = [i.name for i in list(Platform)] header_file = OpenFileNameField("Select Header File") lib_name = TextLineField("Type Library Name") file_name = TextLineField("File Name") get_form_input([header_file, lib_name, file_name], "Generate Type Library") try: for p in list(Platform): typelib = generate_typelib(p, header_file.result, lib_name.result) path = typelib_path / p.name / "{}.bntl".format(file_name.result) typelib.write_to_file(str(path.resolve())) except SyntaxError as e: show_message_box("Error", e.msg)
def perform_run_thread(): while True: try: self.debug_state.run() execute_on_main_thread_and_wait(perform_run_after) except ConnectionRefusedError: execute_on_main_thread_and_wait( lambda: perform_run_error('ERROR: Connection Refused')) except DebugAdapter.NotExecutableError as e: fpath = e.args[0] if platform.system() != 'Windows': msg = '%s is not executable, would you like to set +x and retry?' % fpath res = show_message_box( 'Error', msg, MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.ErrorIcon) if res == MessageBoxButtonResult.YesButton: os.chmod(fpath, os.stat(fpath).st_mode | 0o100) continue execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Target Not Executable')) except DebugAdapter.NotInstalledError as e: execute_on_main_thread_and_wait( lambda: self.alert_need_install(e.args[0])) execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Debugger Not Installed')) except Exception as e: execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: ' + ' '.join(e.args))) traceback.print_exc(file=sys.stderr) break
def save_svg(bv, function): address = hex(function.start).replace('L', '') path = os.path.dirname(bv.file.filename) origname = os.path.basename(bv.file.filename) filename = os.path.join( path, 'binaryninja-{filename}-{function}.html'.format(filename=origname, function=address)) outputfile = get_save_filename_input('File name for export_svg', 'HTML files (*.html)', filename) if sys.platform == "win32": outputfile = outputfile.replace('/', '\\') if outputfile is None: return content = render_svg(function, origname) output = open(outputfile, 'w') output.write(content) output.close() result = show_message_box("Open SVG", "Would you like to view the exported SVG?", buttons=MessageBoxButtonSet.YesNoButtonSet, icon=MessageBoxIcon.QuestionIcon) if result == MessageBoxButtonResult.YesButton: url = 'file:{}'.format(pathname2url(bytes(outputfile))) webbrowser.open(url)
def generate_single_platform(bv): arch_choices = [i.name for i in list(Platform)] header_file = OpenFileNameField("Select Header File") arch = ChoiceField("Select Architecture", arch_choices) name = TextLineField("Type Library Name") save_file = SaveFileNameField("Save Type Library", ext="bntl") get_form_input([header_file, arch, name, save_file], "Generate Type Library") arch = arch_choices[arch.result] platform = Platform[arch] try: typelib = generate_typelib(platform, header_file.result, name.result) typelib.write_to_file(save_file.result) except SyntaxError as e: show_message_box("Error", e.msg.decode())
def resolve_imports(bv, address): library_bvs = [] libraries = get_linked_libraries(bv) needed_libraries = libraries.copy() all_bvs = get_all_binaryviews() for potential_bv in all_bvs: if potential_bv.file.filename in libraries: try: needed_libraries.remove(potential_bv.file.filename) except ValueError: pass for library in needed_libraries: open_file_tab(library) # TODO: Wait for symbols to be resolved, or does this already happen all_bvs = get_all_binaryviews() for potential_bv in all_bvs: if potential_bv.file.filename in libraries: library_bvs.append(potential_bv) # TODO: Get currently selected symbol instead of checking the address symbols = bv.get_symbols_of_type(SymbolType.ExternalSymbol) external_symbol = False for symbol in symbols: if symbol.address == address: for library_bv in library_bvs: for library_symbol in library_bv.get_symbols_by_name(symbol.name): if ( library_symbol.auto ): # Ensure that renamed symbols are not counted display_block(library_bv, library_symbol.address) return interaction.show_message_box( "Shared Object Symbol Resolution", "Selected symbol not found in shared libraries: {}".format(library_bvs), ) return interaction.show_message_box( "Shared Object Symbol Resolution", "Address not an external symbol." )
def cancel_task(self): global process if(process): result = show_message_box("Afl-Unicorn", "Do you want to cancel afl-unicorn fuzzing ?", MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.WarningIcon) if result == 1: binja.log_info("Cancel process {0}".format( os.getpgid(process.pid))) os.killpg(os.getpgid(process.pid), signal.SIGTERM) process = None
def perform_run_thread(): while True: try: self.debug_state.run() execute_on_main_thread_and_wait(perform_run_after) except ConnectionRefusedError: execute_on_main_thread_and_wait( lambda: perform_run_error('ERROR: Connection Refused')) except DebugAdapter.ProcessStartError as e: execute_on_main_thread_and_wait( lambda: perform_run_error(str(e))) except DebugAdapter.NotExecutableError as e: fpath = e.args[0] if platform.system() != 'Windows': msg = '%s is not executable, would you like to set +x and retry?' % fpath res = show_message_box( 'Error', msg, MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.ErrorIcon) if res == MessageBoxButtonResult.YesButton: os.chmod(fpath, os.stat(fpath).st_mode | 0o100) continue execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Target Not Executable')) except DebugAdapter.NotInstalledError as e: execute_on_main_thread_and_wait( lambda: self.alert_need_install(e.args[0])) execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: Debugger Not Installed')) except DebugAdapter.PermissionDeniedError as e: execute_on_main_thread_and_wait( lambda: perform_run_error('ERROR: Permission denied')) if platform.system() == 'Darwin': res = show_message_box( 'Error', 'Developer tools need to be enabled to debug programs. This can be authorized either from here or by starting a debugger in Xcode.', MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) except Exception as e: execute_on_main_thread_and_wait(lambda: perform_run_error( 'ERROR: ' + ' '.join(e.args))) traceback.print_exc(file=sys.stderr) break
def onSymbClick(self): self.n_args += 1 self.update_label_args() label = QLabel(str(self.n_args) + ": ") combo_box = QComboBox() buffer_names = [b[0].name for b in self.state.symbolic_buffers] if len(buffer_names) == 0: show_message_box("Error", "No symbolic buffer") return for name in buffer_names: combo_box.addItem(name) self._layout.addWidget(label, self.n_args + 2, 0, 1, 1) self._layout.addWidget(combo_box, self.n_args + 2, 1, 1, 9) self.args.append(("symb", label, combo_box))
def save_svg(bv, function): sym = bv.get_symbol_at(function.start) if sym: offset = sym.name else: offset = "%x" % function.start path = Path(os.path.dirname(bv.file.filename)) origname = os.path.basename(bv.file.filename) filename = path / f'binaryninja-{origname}-{offset}.html' functionChoice = TextLineField("Blank to accept default") # TODO: implement linear disassembly settings and output modeChoices = ["Graph"] modeChoiceField = ChoiceField("Mode", modeChoices) if Settings().get_bool('ui.debugMode'): formChoices = [ "Assembly", "Lifted IL", "LLIL", "LLIL SSA", "Mapped Medium", "Mapped Medium SSA", "MLIL", "MLIL SSA", "HLIL", "HLIL SSA" ] formChoiceField = ChoiceField("Form", formChoices) else: formChoices = ["Assembly", "LLIL", "MLIL", "HLIL"] formChoiceField = ChoiceField("Form", formChoices) showOpcodes = ChoiceField("Show Opcodes", ["Yes", "No"]) showAddresses = ChoiceField("Show Addresses", ["Yes", "No"]) saveFileChoices = SaveFileNameField("Output file", 'HTML files (*.html)', str(filename)) if not get_form_input([ f'Current Function: {offset}', functionChoice, formChoiceField, modeChoiceField, showOpcodes, showAddresses, saveFileChoices ], "SVG Export") or saveFileChoices.result is None: return if saveFileChoices.result == '': outputfile = filename else: outputfile = saveFileChoices.result content = render_svg(function, offset, modeChoices[modeChoiceField.result], formChoices[formChoiceField.result], showOpcodes.result == 0, showAddresses.result == 0, origname) output = open(outputfile, 'w') output.write(content) output.close() result = show_message_box("Open SVG", "Would you like to view the exported SVG?", buttons=MessageBoxButtonSet.YesNoButtonSet, icon=MessageBoxIcon.QuestionIcon) if result == MessageBoxButtonResult.YesButton: # might need more testing, latest py3 on windows seems.... broken with these APIs relative to other platforms if sys.platform == 'win32': webbrowser.open(outputfile) else: webbrowser.open('file://' + str(outputfile))
def parse_binary(self): br = BinaryReader(self.rawbv) br.seek(0) binary_bytes = br.read(self.rawbv.end) self.bininfo = ELF('thisbinary', binary_bytes) self.text_seg = None self.text_seg_index = 0 for s in self.bininfo.segments: if s.header.p_type == PT.LOAD and s.header.p_flags & PF.EXEC: self.text_seg = s break self.text_seg_index += 1 if self.text_seg is None: show_message_box('Parse Fail', 'Can\'t find text segment of binary!', icon=MessageBoxIcon.ErrorIcon) return False return True
def _evaluate_upto_with_solver(self, address, expr): val = "" if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) self.current_state.mem.store(address, new_expr) self.changes.add( (address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) show_message_box("Expression at %s" % hex(address), "The value was indeed concrete! State modified") else: n_eval = get_int_input("How many values (upto) ?", "Number of distinct values") r = "" for i, v in enumerate( self.current_state.solver.evaluate_upto(expr, n_eval)): r += "solution %d: %s\n" % (i, hex(v.value)) show_message_box("Value at %s (with solver):" % hex(address), r)
def run_export_report(view): """ Generate a report from a previous analysis, and export as CSV """ log.log_info("Attempting to export results to CSV") try: csv_output = view.query_metadata("csv") except KeyError: interaction.show_message_box( "Error", "Cannot export without running an analysis first.") return # write last analysis to filepath csv_file = interaction.get_save_filename_input( "Filename to export as CSV?", "csv") csv_file = csv_file.decode("utf-8") + ".csv" log.log_info(f"Writing to filepath {csv_file}") with open(csv_file, "w+") as fd: fd.write(csv_output) interaction.show_message_box("Success", f"Done, exported to {csv_file}")
def save_svg(bv, function): address = hex(function.start).replace('L', '') path = os.path.dirname(bv.file.filename) origname = os.path.basename(bv.file.filename) filename = os.path.join(path, 'binaryninja-{filename}-{function}.html'.format(filename=origname, function=address)) outputfile = get_save_filename_input('File name for export_svg', 'HTML files (*.html)', filename) if outputfile is None: return content = render_svg(function) output = open(outputfile, 'w') output.write(content) output.close() result = show_message_box("Open SVG", "Would you like to view the exported SVG?", buttons = MessageBoxButtonSet.YesNoButtonSet, icon = MessageBoxIcon.QuestionIcon) if result == MessageBoxButtonResult.YesButton: url = 'file:{}'.format(pathname2url(outputfile)) webbrowser.open(url)