Running Ghidra Debugger IN-VM
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.
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
|
After starting the ghidra debug tool, Select Debugger -> Debug a.out
Now a new dialog opens,
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.
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.
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.
Breakpoint inserted.
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,
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.
In the emulated environmet you can also step 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/