As I continue my journey into Cyber security the next big hurdle will be the Offensive Security's Certified Expert certification. OffSec claims that certification holders will be able to perform the following skills:
1. Identify hard-to-find vulnerabilities. 2. Conduct intelligent fuzz-testing. 3. Analyze, correct, modify, and port exploit code. 4. Hand-craft binaries to evade anti-virus software. 5. Demonstrate creative problem solving and lateral thinking.
In order to prepare for this certification's materials and exam I felt the need to strengthen my code debugging skills. It has been some time since I have used a debugger (most of my debugging is done with print statements, don't judge me) and wanted to do a deep dive into the most popular one of all, GDB.
According to the GNU website about GDB it explains that the GNU Project debugger, allows you to see what is going on `inside' another program while it executes -- or what another program was doing at the moment it crashed. In order to streamline my study progression I picked up the book from No Starch Press, The Art of Debugging with GDB, DDD, and Eclipse. I am not as interested in using the GUI version of GDB or with Eclipse, but might find it helpful to have the book as a reference later on. The hope is that this blog serves as a guide to those looking to get into debugging using GDB, but also as a review of the No Starch Press book.
Chapter 1 of the Art of Debugging begins with understanding the principles of debugging, which essentially boils down to why do we need debugging. Why is using a debugger better than using print statements throughout code. Which debugger should I use? Should I use a debugger with a graphical user interface (GUI)? Then once the introduction is finished it begins the basics of debugging. This is where we will start in this blog post.
The first thing I want to mention is the difference in debugging compiled code that has included the symbol table versus without the symbol table. More or less what this means is that when compiling and linking GCC will include a list of memory addresses corresponding to your program's variables and lines of code within the executable. It might not be apparent why this is important, but let me demonstrate.
As you can see from the screenshot above this is a simple C program I wrote to simply call the
main function and then call a
print_results function which will print
Hello, World!\n to the screen and end execution.
In the screenshot above you can see when loading the compiled test program into GDB it even warns the user that No debugging symbols were found. On the other hand, if we compile with the symbols table loaded as shown in the screenshot below GDB will be able to read them in and show us 1 to 1 what our code looks like.
The screenshot below is the compiled test program with the symbols table loaded, and when running the
list command will show us the contents of the program.
Not all program when debugging will have the symbol table included with it. In fact most of the time in security you will be working on a malware sample or consulting job where the developer will have removed it to make it harder to understand. This is an example of security by obscurity which does not equate to being secure. Those vulnerabilities will still exist in code, and can still be found with more work. Having said that, for this blog post all of the code written and debugged will be compiled with the symbol table to help understand the process of debugging.
As you saw briefly in the screenshots above I have already demonstrated how to load a program into GDB, but to be clear I will show specifically different ways. Nearly all Linux distributions will have GDB already installed, but if it is not please reference your package installer or manually install from source. For this blog post we will be using Kali Linux which already has the GDB binary installed and referenced in our path so to start the binary simply type
gdb on the command line.
The screenshot above shows GDB starting and delivering the banner to the user. At this time no compiled program has been loaded. You can load a program from the GDB command line by typing
file <program path/name>.
You can also attach a file from the Linux command line as shown in the screenshot below.
What if your program or application is already running? Or runs in a daemon mode, maybe you need to attach to a running process. First you will need to find the process ID within Linux, and then can attach by running
gdb -p <pid>.
Now that the program is loaded we can run the compiled code by simply typing
As you can see from the screenshot above the application exited normally, and printed out what we expected. However, there was not much debugging that happened there. Let's stop the program when it hits a certain point. This is called setting a
In the screenshot above I typed in
break main. This is reference to the function named main. If the symbol table was not loaded this would not work as GDB would not know what the main function is or where it is. We can also break at specific line numbers by typing
break 12 and achieve the same result. How did we know what line main was on though? By using the
list command we can view the code that is loaded into GDB (remember this is only possible with the symbol table).
Maybe your code is much larger than our simple program shown above. GDB also comes with a TUI or a Terminal User Interface. You can enable the TUI from the Linux command line with
gdb -tui or from within the GDB command line with
Moving back to the subject of breakpoints. We can also list out our current breakpoints from the GDB command line using the
info breakpoints command or if you are feeling lazy you can shorten it to
info b or simply
In order to remove breakpoints you can use the
clear command. You will need to specify the breakpoint ID as well.
I made some slight modifications to the C program to demonstrate other aspects of GDB. As you can see in the screenshot below I have added a for loop that will loop 10 times continually printing out the statement Hello, World. The thing to notice is that I added a integer variable known as
I will break on line 9 which should be at the point the program prints out the string Hello, World.
The screenshot above shows us that when running the program the application did indeed break at line 9 of the program
test.c which is the print statement. Now the reason why I did this was to demonstrate GDB's ability to show the value of variables. Using the
i's value is currently zero. This is because it was just initialized with the first loop of the for loop. The next command,
continue, is also a new command which tells the debugger to resume normal execution. After the execution with the continue command we see that GDB hits the breakpoint at line 9 again. This is because we set a normal breakpoint which will stop execution after every hit. There are other variants of breakpoints which will be discussed at a later time. For now, we print the value of
i again and see that it has incremented to hold a value of one. Perfect! We are on our way to reaching the end point of our for loop.
Since we briefly talked about the
continue command I will go on to discuss the different versions of moving the execution along. GDB offers several variants to help be more granular with the execution flow. As shown above, the continue command will restart the execution phase until another breakpoint is hit. However, if we are trying to debug a particular function or lines of code we might need to move more slowly through the code. This is where the two commands
next come into play. The step command will move one line at a time which is commonly referenced as stepping into a function, where as the next command will execute all necessary sub-lines until it reaches the next execution of the current program. Does that sound confusing to you? Well it is in a way. The application might make references to code that does not live inside your compiled code. For example calling the
printf statement will actually call code that lives inside the
stdio.h file. Therefore the step program will move into that
stdio.h header file and execute line by line until the Hello, World is printed on your screen where as the
next command will simply ignore all of that and will move you to the next iteration of the for loop. The two following screenshots show the difference in the
So far we have gone over the basics of the GDB command line and how to execute a compiled program, and why this is beneficial. We have discussed the basics of stepping through programs and looking at the variables as they change. Hopefully, time permitting, this will not be my last blog post on GDB. Who would have thought that I could cover an entire GDB tutorial in a single blog post. Especially when entire websites and books are dedicated to teaching the material. Before I end this current blog post, I would like to type a few words about the book The Art of Debugging.
As of writing this blog post I am roughly a third of the way through the book. So far the authors Norman Matloff and Peter Jay Salzman have done a great job of being clear and concise with the instructions. With many of the Cyber security books I read sometimes there will be slight errors in code blocks within the pages. However, Matloff, and Salzman did a great job of making this intentional so the reader could learn how to debug the mistakes. As the reader continues to turn pages of the book the mistakes slowly disappear as they learn to debug errors in code. This is not a programming book though, and some basic programming or programming logic is required to understand the direction the debugging is progressing through. I highly recommend reading this book in conjunction with another No Starch Press book, Hacking the Art of Exploitation. The GDB book will provide a great reference as to what is happening in the other book. Overall, I will continue to read through this book and will enjoy having it on my desk as a reference as I continue to navigate through this Cyber security field.
Hopefully this blog post will serve as a jumping off point for those looking for a quick tutorial into GDB. Maybe to be used as a reference guide when you forget those commands. I will also include a GDB cheat sheet under the Sources and Inspiration section. Until next time, keep stepping through code!
Sources and Inspiration: