x86 assembly on a 64 bit Linux Part 3 - itzjac/cpplearning GitHub Wiki

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

GDB - Compile from source

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

1603649113420

$file /usr/bin/gdb

1603649167301

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

1603649657757

"i386-pc-linux-gnu" and the ELF 32 bit file confirming the 32-bit version.

GDB Configuration

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

1603650788624

Inside the gdb, execute the command 'r' to begin the program

(gdb) r

1603651554545

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

1603651067225

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

1603651211798

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

1603651649591

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()

1603652150837

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

1603652341390

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!

⚠️ **GitHub.com Fallback** ⚠️