HackBlue

Tools · Ghidra · 18 / 22

Ghidra: the Script Manager for navigation and renaming

The Script Manager runs a small program inside the tool, against the program you have open. Used well, a script lists functions, jumps you toward an unnamed one, or applies a name you have already decided. This lesson stays on that side of the line.

16 min read · practice only with authorization

Where a script runs

Window > Script Manager is the list of scripts Ghidra can see, grouped by the directories on the script path. You can edit the path, open a script in the basic editor, run it, and bind a key. A script is Java or Python. The Python that ships inside the tool is Jython 2.7, which means you should avoid f-strings and other syntax from current CPython. Ghidra can be wired to a newer CPython through PyGhidra. The scripts in this lesson use the print form and the percent formatting that both of those engines accept.

When you press Run, the script executes inside CodeBrowser, with the open program as currentProgram and the cursor address as currentAddress. It is not a shell command against a path. If no program is open, there is nothing for it to read. The console at the bottom of the tool shows whatever the script prints, and that console is a scratchpad. Print output is not a symbol and it is not a comment. If the line matters, you still write it into a name, a comment, or the notebook.

A script that helps you navigate

The useful first script is a filter the Symbol Tree does not quite give you. Walk the function manager, and print the entry address of every function whose name still starts with FUN_. You get a list of unread functions, one per line, and you can Go To any address it prints. That is navigation. The script does not decide what the functions mean, and it does not need to. Sorting the unread work is the value.

The same shape answers other navigation questions you already know how to ask by hand. Print every function that references a given address. Print every data item whose type is still undefined inside .rodata. Keep the script specific and short. A script that dumps the entire database into the console is a slower way to look at the Listing.

Snippet
# Callers of the function under the cursor. Read-only.
func = getFunctionContaining(currentAddress)
if func is None:
    print("No function at the cursor")
else:
    entry = func.getEntryPoint()
    print("Callers of %s" % entry)
    for ref in getReferencesTo(entry):
        print("  %s  %s" % (ref.getFromAddress(), ref.getReferenceType()))

A script that applies a name you already chose

Renaming in a script is the same database edit as the L key, which is why the source type matters. SourceType.USER_DEFINED tells later analysis that a person decided. SourceType.ANALYSIS tells later analysis that a tool guessed, and a guess can be replaced. For a name you chose by reading the body, user-defined is the correct source. The script below renames only the function that contains the cursor, so you cannot accidentally sweep the whole program with a stale string.

Do not point a script at the instruction bytes. Clearing code, setting bytes, and assembling new instructions are a different kind of edit, and this curriculum does not teach them. A script that “fixes” a compare by writing a different immediate has left the work of reading and started the work of altering. If you want the decompiler to look different, change a type or a name and let it reprint. The bytes you imported stay the bytes you are studying.

Snippet
from ghidra.program.model.symbol import SourceType

func = getFunctionContaining(currentAddress)
if func is None:
    print("No function at the cursor")
else:
    func.setName("load_config", SourceType.USER_DEFINED)
    print("Named %s" % func.getEntryPoint())

Lab: list, rename, undo, rename by hand

Save the listing script into a directory on the script path, run it from the Script Manager, and Go To one address it prints. Put the cursor in the function you already understand as the file opener. Run the rename script. Confirm the Symbol Tree shows load_config and that the name’s source is user-defined. Then Undo, and watch the placeholder return. Finally press L and set the same name by hand. The script and the key are the same edit. The hand rename is there so you remember the tool is not required.

If the script fails before it prints, read the console line. A missing currentProgram means you ran it from a tool with no open program. A syntax error means the engine rejected a construct from a newer Python. Fix the script. Do not work around it by editing the binary.

Snippet
List unnamed functions, then rename the one under the cursor

# Ghidra Python. Print is parenthesized on purpose.
# Built-in Jython is 2.7 unless you installed PyGhidra.

fm = currentProgram.getFunctionManager()
for func in fm.getFunctions(True):
    name = func.getName()
    if name.startswith("FUN_"):
        print("%s  %s" % (func.getEntryPoint(), name))

Diagram

What a lesson script is allowed to change

  1. Manager

    Window > Script Manager lists scripts from the configured script paths. You can run one, edit one, or assign a key.

  2. Context

    A Ghidra script sees currentProgram and currentAddress. It runs against the program that is open, not against a file path you pass on a shell.

  3. List

    A listing script prints addresses. It is a way to navigate a large function list without clicking every row.

  4. Rename

    A rename script calls setName with a user-defined source type, and only for a decision you already made at the cursor.

Two kinds of automation

Names and lists

Reads. Functions, names, addresses, and references that are already in the database.

Writes. A user-defined name, or a comment, that you have already chosen.

Undo. The script’s edits are a transaction. Undo puts the names back.

Bytes

Reads. The same database, plus the instruction bytes.

Writes. New bytes in the program image. That is a different task, and these lessons do not do it.

Result. A modified database that no longer describes the file you imported.

List unnamed functions, then rename the one under the cursor
# Ghidra Python. Print is parenthesized on purpose.
# Built-in Jython is 2.7 unless you installed PyGhidra.

fm = currentProgram.getFunctionManager()
for func in fm.getFunctions(True):
    name = func.getName()
    if name.startswith("FUN_"):
        print("%s  %s" % (func.getEntryPoint(), name))

Check yourself

1. You run the listing script from the Script Manager with harbor-check open. What does it operate on?

2. You have already decided the function under the cursor is load_config. How do you apply that decision in a script and still survive re-analysis?

3. A script renames the wrong function. What puts the old name back?