Running Ghidra Debugger IN-VM

Olof Astrand
4 min readJan 21, 2024

Starting up ghidras debugger tool can be a bit confusing the first time you use it. Here I will go through how to do it first with a local binary and next time remotely to qemu emulating a raspberry pi. I am using version 11.0 of Ghidra.

chatGPT interpetration of the Ghidra debug tool

We reuse and revisit the old example code from here, https://olof-astrand.medium.com/a-story-about-elfs-dwarfs-and-dragons-6de2a1df42ad

When we compile with the -fanalyzer this is the output from g++ 12.2.1, this is an improvement from the last version but it does not catch all stack errors in this program.

g++ -fanalyzer test.c
test.c: In function 'void test(int)':
test.c:25:5: warning: 'free' of 'ptr' which points to memory on the stack [CWE-590] [-Wanalyzer-free-of-non-heap]
25 | free(ptr);
| ~~~~^~~~~
'void test(int)': events 1-4
|
| 18 | int buf[10];
| | ^~~
| | |
| | (1) region created on stack here
| 19 | int *ptr;
| 20 | if (n < 10)
| | ~~
| | |
| | (2) following 'true' branch (when 'n <= 9')...
| 21 | ptr = buf;
| | ~~~~~~~~~
| | |
| | (3) ...to here
|......
| 25 | free(ptr);
| | ~~~~~~~~~
| | |
| | (4) call to 'free' here
|
Rirht click in project

After starting the ghidra debug tool, Select Debugger -> Debug a.out

Now a new dialog opens,

Launch dialog

Finally, make sure to click break on first instruction.

Our main function does not use an argument but later we will modify the program to call the test() function, then this dialog is where we enter arguments to our main function.

Now we can start stepping through our code.

Stepping through the code

Notice how the compiler inserts stack checking with the in_FS_Offset and local_10 variable. Stepping is done one instruction at the time so setting breakpoints and hitting continue is a good idea. You can see the output from our program in the interpreter. You can also enter commands directly to gdb, so this is a useful window.

Interpreter tab

After continuing we get a stack check fail. This is due to the stack checking.

Program received signal SIGSEGV, Segmentation fault.

0x00007ffff7f80ab7 in __stack_chk_fail () from /lib/ld-musl-x86_64.so.1
Program received signal SIGSEGV, Segmentation fault.

We can compile a.out again but now with the -fno-stack-protector option.

g++ -fanalyzer -fno-stack-clash-protection -fno-stack-protector test.c

https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

Breakpoints

Right click to insert breakpoints.

Right click to insert breakpoint

Breakpoint inserted.

Breakpoint

This time we have no stack checking and the program can run until completion.

(gdb) 

Continuing.
my_int=4
i = 0
i = 1
i = 2
i = 3
[Inferior 1 (process 2314) exited normally]
[Inferior 1 (process 2314) exited normally]

Modify the sourcecode

Now we modify the main function to call test()

int main(int argc,char **argv)
{
my_type t1 = {
.my_int = 4
};
test(argc);
one_arg(10);
two_args(2,&t1);

return 0;
}

So if we run like this, it works

gdb — args ./a.out 1 2 3 4 5 6 7 8 9 10 11

But with less than 10 arguments it will fail due to the free to a pointer to the local stack. There is also a possibility to open the time/trace window.

Debug dropdown,

Dont close trace on termination

Session traces

Traces can be quite useful, and let you to step backwards. If you hoover over local variables they will be displayed even when you are using a recordered trace. Also you can resume control at a specific tracepoint.

Continue running in emulator
Stepping in emulator

In the emulated environmet you can also step backwards.

Stepping backwards

Conclusion

This should be enough information to get you started debugging executables locally. In the next article we will look at how to connect to a qemu instance running a gdbserver.

Here you can read some more about using the ghidra debug tool, https://wrongbaud.github.io/posts/ghidra-debugger/

--

--