-
Notifications
You must be signed in to change notification settings - Fork 1
x86 assembly on a 64 bit Linux Part 3
Analyze the x86 asm generated by TCC when compiling a C file and how to create executables with assembly language.
Requirements
- Basic linux usage and terminal, scripting
- Configuring, Compiling, linking C programs
Out of the box, most recent linux versions come with a gdb x86_64 version installed by default.
x86_64 version of gdb will help you to debug 32-bit and 64-bit executables. Inspecting the file attributes for the executable gdb installed in your linux machine you can confirm that gdb is actually a 64-bit executable.
$which -a gdb
$file /usr/bin/gdb
If you really need to customize gdb to be 32-bit on your 64 bit OS, you can still use the source code and some tricky config options to get the job done.
-
Download GDB 9.2
-
Make sure to have installed the multilib from Part 1 of this tutorial
-
Run the ./configure command in the terminal using the options shown. We instruct the configure to use for c, c++ and linking the 32 bit version (by far the most reliable form of building 32-bit application in a 64-bit OS)
$./configure --build=i386-pc-linux-gnu "CFLAGS=-m32" "CXXFLAGS=-m32" "LDFLAGS=-m32"
-
make
After you completed the installation your customized gdb file, confirm by invoking the gdb file or using the file command
"i386-pc-linux-gnu" and the ELF 32 bit file confirming the 32-bit version.
Instead of repeating configuration commands inside gdb to help you during a debug session, one can use a configuration file to change the default behavior of gdb at start up.
$gdb -x config hello
By invoking gdb with the option -x, gdb uses a file config that contains a sequence of commands to be executed previous to open the gdb session.
Most typically you would want to
- inspect CPU registers
- make sure you are using the right CPU architecture to debug on a 32-bit executable
- Set the disassembly flavor as an intel syntax (unless you enjoy self-inflicting pain practices)
- Set a break point in main. Conveniently any C program would contain a main function where we can add a break point when starting to debug the executable
Each line in the file, represents one gdb configuration command
set archi i386
set disassembly-flavor intel
display/10i $eip
display/x $eax
display/x $ebx
display/x $ecx
display/x $edx
display/x $edi
display/x $esi
display/x $ebp
display/16xw $esp
break main
Notice you can still execute the gdb commands, inside the gdb enviroment, but the debug environment will be lost once you end the gdb session.
Debugging a C program with symbols With a very simple C program hello
#include <stdio.h>
int sub()
{
return 2+2;
}
int main()
{
int b = sub();
printf("Hello GDB\n");
return 1;
}
Compile with symbols
$ tcc -m32 -g -o hello hello.c
Start a gdb session using the config file
$gdb -x config hello
Notice that gdb already set a break point in main, that gets listed, thanks to the config file that pre-set it
Inside the gdb, execute the command 'r' to begin the program
(gdb) r
The program stopped running at the main function, as we instructed and references to known symbols (i.e. the sub function) are included. The disassembly syntax is obviously an intel one, and the rest of the CPU registers are displayed in hexadecimal.
To continue the program execute the command 'c' and that will complete the hello
(gdb) c
Debugging a C program without symbols Compiling the same C program but without symbols
$ tcc -m32 -o hello hello.c
And restart a new gdb session with the same config file
No debugging symbols found in the hello program, therefore any other function than the entry point wont be listed anymore! But we can still debug the program
Consider that we want to debug the sub, but we can't add a break point using the function name because there are no symbols. We can still use the address by inspecting the value where the first call line is listed and add a break point to that particular address.
(gdb)b *0x8048255
(gdb)c
If you continue execution now you will be inside the ?? function, which in reality is the sub()
In a more complex program where you have no idea how the C source file looks like, you have to guess and apply reverse engineering. Cumbersome but doable, some software exploits were found using this kind of method.
Remember that, when a program was executed from the bash and crashed, you can recover the crash dump by adding the crash data to gdb.
Consider this crash was reported right after invoking an program
Use the core, that contains the last dumped crash info and start a gdb session with that information
$gdb -x config hello core
Hope you like this small tutorial. Thanks for reading and following!