HackBlue

Tools · Ghidra · 05 / 22

Ghidra: the Listing and the Decompiler

The Listing is the addresses, the bytes, and the instructions. The Decompiler is a C-like reading of those instructions. They describe one database, and they are allowed to disagree. When they do, the bytes win.

17 min read · practice only with authorization

The Listing is the database, row by row

The Listing window is the center of CodeBrowser. Each row is an address. The row is either an instruction, a data item, or undefined bytes. Instructions show a mnemonic and operands. Above a labeled address you often see the cross-references that point at it. The left margin collects markers for bookmarks, changes, and the cursor. This window is as close as the tool gets to the bytes, short of the hex field itself.

Disassembly is still an interpretation. Bytes become an instruction because an analyzer, or you, said “decode here.” If the cursor is sitting on undefined bytes inside an executable block, the Disassemble action (the D key in the Listing) starts a decode at that address. Clearing a bad decode gives the bytes back. Both operations edit the database. The imported file stays as it was.

The Decompiler is a recovered reading

The Decompiler window prints a C-like function. Names like param_1, local_14, uVar1, and iVar2 are invented. A for loop means the decompiler found a loop in the control flow. A cast means it needed a type and guessed. The text can look like source you would commit, and it is not that source. It is a summary of the instructions, regenerated whenever types and names change.

Ghidra gets there by lifting instructions to P-code, then rewriting P-code into the C-like form. You do not need to read P-code to use the decompiler. When an operand is doing something the mnemonic hides, turning on P-code display for that instruction shows the lift: the loads, the stores, and the address arithmetic, one small operation at a time. It is the middle layer, useful for a single confusing line and noisy if you leave it on for the whole function.

Keep them on the same address

Clicking an instruction in the Listing highlights the decompiler line that came from it, when the mapping exists. Clicking a line in the decompiler moves the Listing. Double-clicking a called name in either window follows the call. That link is how you avoid describing a function you are not actually looking at. If the highlight cannot find a match, you are in a region the decompiler collapsed or failed on, and the Listing address is the one to write down.

Edits flow both ways because both windows read the same database. Rename a variable in the decompiler and the Listing’s references pick up the name. Retype a parameter and the decompiler reprints the body. Commit Locals, from the decompiler menu, writes the decompiler’s current local names back into the database in one step. Commit after the types make sense. Committing a run of undefined temporaries only freezes names that did not mean anything yet.

Snippet
When you quote a finding, include one concrete line from each view:

Listing:   CALL fopen
Decompiler: file = fopen(path, "r");

The call is the evidence. The assignment is the reading.
If they ever disagree about which function is called, quote the Listing.

Lab: one branch, two windows

In harbor-check, go to the function that references the config path string. You are looking for the compare against 1 and the jump that chooses between argv and the default path, or for the decompiler if that tests the first parameter. Click the compare in the Listing and confirm the decompiler highlights the if. Click the call that passes the chosen pointer onward and confirm both windows name the same target.

Write two lines in the notebook, in the shape of the sample above: the Listing instruction, and the decompiler line you believe it corresponds to. Then find one undefined type in that function and leave it undefined on purpose. The point of this lab is the correspondence, and a later lesson spends the type.

Snippet
A shape you can match with your own addresses

Listing
  CMP EDI, 1
  JLE LAB_default
  MOV RAX, qword ptr [RSI + 0x8]
  JMP LAB_join
LAB_default
  LEA RAX, [s_/etc/harbor/check.conf_...]

Decompiler
  if (param_1 < 2) {
    pcVar2 = s_/etc/harbor/check.conf_...;
  } else {
    pcVar2 = *(char **)(param_2 + 8);
  }

Diagram

Three views of the same function

  1. Bytes

    The program database holds the bytes at the image base you imported.

  2. Listing

    The disassembler groups bytes into instructions and data. The Listing window shows that grouping.

  3. P-code

    Ghidra lifts each instruction into P-code, a small intermediate language. You can display it when an operand is confusing.

  4. Decompiler

    The decompiler turns P-code into the C-like window. Renaming a variable there renames it in the database the Listing also reads.

What each view is willing to say

  • Listing

    One row is one instruction or one data item, with its address. A branch is a branch. A call is a call. This is the view you quote when you need to be exact.

  • Decompiler

    The same bytes, lifted through P-code and printed as C-like text. Types, loops, and arguments are recovered. Recovered text can be a confident guess.

  • Both

    Click an instruction and the matching decompiler line highlights when a mapping exists. Use that link to keep the two readings on the same address.

A shape you can match with your own addresses
Listing
  CMP EDI, 1
  JLE LAB_default
  MOV RAX, qword ptr [RSI + 0x8]
  JMP LAB_join
LAB_default
  LEA RAX, [s_/etc/harbor/check.conf_...]

Decompiler
  if (param_1 < 2) {
    pcVar2 = s_/etc/harbor/check.conf_...;
  } else {
    pcVar2 = *(char **)(param_2 + 8);
  }

Check yourself

1. The decompiler shows a tidy if/else, and the Listing shows a compare and two jumps. What are you looking at?

2. The decompiler prints undefined4 for a stack temporary, and the Listing shows a 4-byte move into that slot. What does undefined4 tell you?

3. You click a call in the Listing and the decompiler does not move. The decompiler’s header says it failed on this function. What do you read?