| | import gradio as gr |
| | import logging |
| | import os |
| | import shlex |
| | import tempfile |
| | import subprocess |
| | import pyghidra |
| |
|
| |
|
| | logging.basicConfig(level=logging.INFO) |
| |
|
| | pyghidra.start() |
| |
|
| | GHIDRA_PROJECT_DIR = f"{os.getenv('HOME')}/ghidra_project" |
| |
|
| | os.makedirs(GHIDRA_PROJECT_DIR, exist_ok=True) |
| |
|
| | def get_functions(file): |
| |
|
| | with pyghidra.open_program(file) as flat_api: |
| | program = flat_api.getCurrentProgram() |
| | function_addrs = [(f.getName(), f.getEntryPoint().getOffset()) for f in program.getFunctionManager().getFunctions(True)] |
| |
|
| | return function_addrs |
| |
|
| | with gr.Blocks() as demo: |
| |
|
| | state = gr.State() |
| |
|
| | intro = gr.Markdown( |
| | """ |
| | This is a space to experiment with GhidraFunctionCPPExporter, a Ghidra scripts that outputs *rich* C decompilations of functions in a binary. It notably includes declarations for variables, functions, and data types. To get started, upload an executable file below. |
| | """ |
| | ) |
| |
|
| | file_widget = gr.File(label="Executable file") |
| |
|
| | with gr.Column(visible=False) as col: |
| | |
| |
|
| | gr.Markdown( |
| | """ |
| | Great, you selected an executable! Now pick the function you would like |
| | to analyze. |
| | """ |
| | ) |
| |
|
| | fun_dropdown = gr.Dropdown( |
| | label="Select a function", choices=["Woohoo!"], interactive=True |
| | ) |
| |
|
| | gr.Markdown( |
| | """ |
| | Below you can find some information. |
| | """ |
| | ) |
| |
|
| | extra_args = gr.Textbox(label="Extra args to export.bash", placeholder="emit_type_definitions false", value="") |
| |
|
| | with gr.Row(visible=True) as result: |
| | disassembly = gr.Code( |
| | label="Disassembly", lines=20, |
| | max_lines=20, |
| | ) |
| | original_decompile = gr.Code( |
| | language="c", |
| | label="Decompilation", lines=20, |
| | max_lines=20, |
| | ) |
| |
|
| | example_widget = gr.Examples( |
| | examples=[f.path for f in os.scandir(os.path.join(os.path.dirname(__file__), "examples"))], |
| | inputs=file_widget, |
| | outputs=[state, disassembly, original_decompile], |
| | ) |
| |
|
| | @file_widget.change(inputs=file_widget, outputs=[intro, state, col, fun_dropdown]) |
| | def file_change_fn(file): |
| |
|
| | if file is None: |
| | return {col: gr.update(visible=False), state: {"file": None}} |
| | else: |
| |
|
| | try: |
| | progress = gr.Progress() |
| | progress( |
| | 0, |
| | desc=f"Analyzing binary {os.path.basename(file.name)} with Ghidra...", |
| | ) |
| | fun_data = get_functions(file.name) |
| | print(fun_data) |
| |
|
| | addrs = [ |
| | (f"{name} ({hex(int(addr))})", int(addr)) |
| | for name, addr in fun_data |
| | ] |
| |
|
| | print(addrs) |
| |
|
| | except Exception as e: |
| | raise gr.Error(f"Unable to analyze binary with Ghidra: {e}") |
| |
|
| | return { |
| | col: gr.update(visible=True), |
| | fun_dropdown: gr.Dropdown(choices=addrs, value=addrs[0][1] if addrs else None), |
| | state: {"file": file, |
| | "addrs": addrs}, |
| | } |
| | |
| | @fun_dropdown.change(inputs=[fun_dropdown, state, extra_args], outputs=[disassembly, original_decompile]) |
| | @extra_args.submit(inputs=[fun_dropdown, state, extra_args], outputs=[disassembly, original_decompile]) |
| | def function_change_fn(selected_fun, state, extra_args, progress=gr.Progress()): |
| |
|
| | print("function_change_fn called with", selected_fun, state, extra_args) |
| |
|
| | with tempfile.TemporaryDirectory() as TEMP_DIR: |
| |
|
| | progress(0, desc=f"Running GhidraFunctionCPPExporter on {hex(selected_fun)}...") |
| |
|
| | |
| | with pyghidra.open_program(state['file']) as flat_api: |
| | program = flat_api.getCurrentProgram() |
| | fm = program.getFunctionManager() |
| | func = fm.getFunctionAt(program.getAddressFactory().getAddress(hex(selected_fun))) |
| | listing = program.getListing() |
| | disassembly_str = "\n".join( |
| | [ |
| | f"0x{i.getAddress().getOffset():x}: {i.toString()}" |
| | for i in listing.getInstructions(func.getBody(), True) |
| | ] |
| | ) |
| |
|
| |
|
| | o = subprocess.run(["/code/GhidraFunctionCPPExporter/export.bash", state['file'], "base_name", "file", "address_set_str", hex(selected_fun), "output_dir", TEMP_DIR] + shlex.split(extra_args), shell=False, capture_output=True, encoding="utf8") |
| |
|
| | print(o.stdout) |
| | print(o.stderr) |
| |
|
| | if (o.returncode != 0): |
| | raise Exception(f"Ghidra export failed with return code {o.returncode}: {o.stderr}") |
| |
|
| | with open(os.path.join(TEMP_DIR, "file.c"), "r") as f: |
| | decompile_str = f.read() |
| |
|
| | return { |
| | disassembly: gr.Textbox(value=disassembly_str), |
| | original_decompile: gr.Textbox(value=decompile_str), |
| | } |
| |
|
| | demo.queue() |
| | demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True, debug=True) |
| |
|