File size: 5,244 Bytes
66f8fc1
fbe1b1f
7556a55
ef71e62
7556a55
 
a3de3ad
 
66f8fc1
fbe1b1f
66f8fc1
a3de3ad
 
7556a55
66f8fc1
7556a55
66f8fc1
7556a55
 
a3de3ad
 
dc6986a
7556a55
a3de3ad
7556a55
 
 
 
 
 
 
20ae745
7556a55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef71e62
 
7556a55
 
 
117a3e4
7556a55
 
 
5de8952
117a3e4
7556a55
 
fb860f1
 
 
5de8952
fb860f1
7556a55
abb881c
7556a55
 
 
 
 
 
 
 
 
 
 
 
 
a3de3ad
 
 
cfe2c85
a3de3ad
 
7556a55
a3de3ad
7556a55
 
 
 
 
977757c
7556a55
 
a3de3ad
7556a55
 
6970284
949d4ac
ef71e62
54f2f10
e8ab2e3
 
54f2f10
 
2861534
54f2f10
a9dace7
 
 
 
f29eead
a9dace7
db90425
 
 
 
 
 
a9dace7
 
ef71e62
54f2f10
 
 
 
 
 
 
5de8952
b5c89ee
5de8952
54f2f10
b5c89ee
 
54f2f10
7556a55
 
db90425
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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:
        # output = gr.Textbox("Output")

        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)}...")

            # Yeah, this is kind of dumb, but oh well.
            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)