HackBlue

Tools · Ghidra · 22 / 22

Ghidra: read one function from the bytes to a sentence

This is the whole curriculum in one function. You start from a string, land on the instructions, check the decompiler against them, name only what you can defend, and leave with a sentence a teammate can use.

16 min read · practice only with authorization

Choose one function on purpose

A program the size of a real service has more functions than a sitting will read. The way through is to choose one function from an anchor you trust. A string you recognize, an import you can interpret, or a bookmark you left yesterday is an anchor. Follow its references until you are inside a single function object, and then stop traveling. The other functions will still be there. This one gets a complete pass: bounds, convention, frame, types, names, and a sentence.

If the anchor’s reference lands in a thunk, follow the thunk once and work on the real target, or walk back to the caller that meant to use the import. Thunks do not deserve the full pass. Functions that contain a decision do.

Listing first, decompiler second

Read the Listing from the entry to the return. Mark the compares, the calls, and the registers those calls fill. For the lab’s file-opening function that means: a call to fopen with the path, a failure branch that returns 1, a call to fgets into a 128-byte local, a parse, a failure return of 2, a range check on the port, and a success return of 0. Your build’s instruction order may differ. The returns are what you are accounting for. If the body continues into another prologue, fix the bounds before you continue. A sentence about two functions glued together is two sentences, both wrong.

Then read the decompiler. Where it names the same calls and the same returns, let it teach you the shape, and retype the parameters so the shape stays. Where it invents a field the Listing never touches, undo the type that caused it. Where it fails entirely, bookmark a Warning and write the sentence from the Listing. You already know how to do each of those steps. The pass is only the order.

The sentence is the deliverable

The deliverable is a paragraph another person can check against the database. It names the function, the arguments you typed, the calls that matter, the status codes you saw returned, and the things you left unnamed. It can mention a bookmark. It does not need a screenshot of every basic block, and it does not need the word FUN_ unless a function is genuinely still unread. Save the program after the names settle. The paragraph goes in the lab log, so it survives even if the project does not.

That paragraph is also the unit of a larger reading. Version Tracking tells you which paragraphs you must rewrite after a new build. The import list tells you which capabilities you have not found a paragraph for yet. The triage note is a stack of these paragraphs, deliberately short, grouped by what the references are for. None of the paragraphs require the executable to be rewritten. The file you were given is still the file you were given.

Lab: one function, one paragraph, one save

Choose load_config on the -O0 build, or the function that contains its body if you are on the -O2 build. Do the pass in order, without renaming anything until the Listing and the decompiler agree on the calls and the return codes. Then name the function and the parameters you can defend, set one Info bookmark, and write the paragraph in the form of the diagram. Compare it with the source you compiled. Where the paragraph and the source disagree, the paragraph is wrong, and the Listing will tell you which line you misread.

Save the program and append the paragraph to the lab log with the date and the program name. You now have the habit the rest of Ghidra is in service of: a database that remembers your decisions, and a sentence that could stand without the database open.

Diagram

The order that keeps the sentence honest

  1. Anchor

    Pick one string or one import you already understand. Follow the reference into a single function. That function is the whole assignment.

  2. Listing

    Read the branches and the calls in the Listing. Note the convention at each call: which register carries the first argument.

  3. Decompiler

    Read the C-like view second. Where it agrees, use it to name the structure. Where it disagrees, quote the Listing and leave a Warning bookmark.

  4. Sentence

    One paragraph: what the function takes, what it calls, what it returns, and what you refused to name.

    A finished sentence for the file-opening function
    load_config(path, host, host_len, port) opens path.
    On failure to open or read, it returns 1.
    It copies one line into a 128-byte local and parses a host and a port.
    A bad parse or a port outside 1..65535 returns 2.
    Success returns 0 and writes through the port pointer.
    Names came from those calls, on the -O0 build.
    The executable was not modified.
A finished sentence for the file-opening function
load_config(path, host, host_len, port) opens path.
On failure to open or read, it returns 1.
It copies one line into a 128-byte local and parses a host and a port.
A bad parse or a port outside 1..65535 returns 2.
Success returns 0 and writes through the port pointer.
Names came from those calls, on the -O0 build.
The executable was not modified.

Check yourself

1. You have ten minutes and a program you have never opened. What is the first anchor that leads you into one function?

2. The decompiler shows fopen taking the path, and the Listing shows the path pointer in RDI just before the call. What belongs in the sentence?

3. You still do not know what one local is for. How does the sentence handle it?