Déassembler un objet REL

Les objets REL, sont ici, les fichiers objets produits par gcc. Par exemple, si nous avons le fichier source fonctions.c suivant:

1
2
3
4
5
6
7
8
9
int inc(int x)
{
    return x++;
}

int add(int x, int y)
{
    return x+y;
}

Il contient deux fonctions inc qui retourne l'entier x fournit en paramètre incrémenté de 1 et add qui retroune la somme des deux paramètres fournis. Nous avons choisis, deux fonctions simples pour commencer.

Si l'on souhaite voir le code assembleur généré par gcc il faut d'abord compiler le fichier source ci-dessus avec la commande suivante:

$ gcc -c fonctions.c -Wall

Nous obtenons alors un fichier fonctions.o, et nous allons maintenant le désassembler avec une commande Linux, toujours présente sur une station de développement qui se respecte. Cette commande, c'est objdump et l'exemple ci-dessous vous permet de comprendre comment l'utiliser pour désassembler notre fichier .o:

$ objdump -d fonctions.o

Ci-dessous le résultat affiché par la commande:

fonctions.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <inc>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   8b 45 fc                mov    -0x4(%rbp),%eax
   a:   8d 50 01                lea    0x1(%rax),%edx
   d:   89 55 fc                mov    %edx,-0x4(%rbp)
  10:   5d                      pop    %rbp
  11:   c3                      retq   

0000000000000012 <add>:
  12:   55                      push   %rbp
  13:   48 89 e5                mov    %rsp,%rbp
  16:   89 7d fc                mov    %edi,-0x4(%rbp)
  19:   89 75 f8                mov    %esi,-0x8(%rbp)
  1c:   8b 55 fc                mov    -0x4(%rbp),%edx
  1f:   8b 45 f8                mov    -0x8(%rbp),%eax
  22:   01 d0                   add    %edx,%eax
  24:   5d                      pop    %rbp
  25:   c3                      retq   

Le code désassemblé est affiché, avec la syntaxe at&t utilisée par le compilateur gas (GNU Assembler). Or, il s'avère que nombre d'entre nous utilisent plus courremment la syntaxe intel... Et bien c'est possible, pour cela, la commande est très proche comme vous allez le voir ci-dessous:

$ objdump -d fonctions.o -Mintel

Ci-dessous le résultat affiché en mode intel:

fonctions.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <inc>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
   7:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
   a:   8d 50 01                lea    edx,[rax+0x1]
   d:   89 55 fc                mov    DWORD PTR [rbp-0x4],edx
  10:   5d                      pop    rbp
  11:   c3                      ret    

0000000000000012 <add>:
  12:   55                      push   rbp
  13:   48 89 e5                mov    rbp,rsp
  16:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  19:   89 75 f8                mov    DWORD PTR [rbp-0x8],esi
  1c:   8b 55 fc                mov    edx,DWORD PTR [rbp-0x4]
  1f:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  22:   01 d0                   add    eax,edx
  24:   5d                      pop    rbp
  25:   c3                      ret     

Nous allons maintenant, dans un but péddagogique tricher un peu. Et nous allons faire cela en demandant à gcc de générer le code assembleur qui a servi à construire notre fichier objet fonctions.o. Et ceci pour vous permettre de comprendre quelques mécanismes de compilation.

Recompilons le source fonctions.c mais cette fois-ci avec cette commande:

$ gcc -masm=intel -S fonctions.c 

Nous obtenons alors un fichier fonctions.s, qui est le source assembleur de notre fichier, généré par gcc:


        .file   "fonctions.c"
        .intel_syntax noprefix
        .text
        .globl  inc
        .type   inc, @function
inc:
.LFB0:
        .cfi_startproc
        push    rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        mov     rbp, rsp
        .cfi_def_cfa_register 6
        mov     DWORD PTR -4[rbp], edi
        mov     eax, DWORD PTR -4[rbp]
        lea     edx, 1[rax]
        mov     DWORD PTR -4[rbp], edx
        pop     rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   inc, .-inc
        .globl  add
        .type   add, @function
add:
.LFB1:
        .cfi_startproc
        push    rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        mov     rbp, rsp
        .cfi_def_cfa_register 6
        mov     DWORD PTR -4[rbp], edi
        mov     DWORD PTR -8[rbp], esi
        mov     edx, DWORD PTR -4[rbp]
        mov     eax, DWORD PTR -8[rbp]
        add     eax, edx
        pop     rbp
        .cfi_def_cfa 7, 8
        ret
        

On remarquera au passage que l'option -masm=intel permet d'obtenir la syntaxe intel au lieu de at&t.

On retrouve bien dans le code généré depuis le source ci-dessus, les mêmes instructions que lorsque nous avons désassemblé le binaire.

Voilà, vous avez déjà une première solution pour désassembler des bibliothèques binaires. Le faire avec un programme complet est identique, à par que le code est bien plus complexe que celui des fonctions de cet exemple.

Bien entendu on se rend compte qu'il est nécessaire de connaître le language d'assemblage pour comprendre quelquechose à tout cela.

Apprendre le language assembleur peut être intimident au début, mais sachez que cela n'est pas si difficile que cela en à l'air, et si vous voulez vraiment réaliser des programmes de qualité, c'est malheureusement incontournable.