Logo Blog perso d'Ozwald

Déplombage à l'ancienne

Par Oz le - Sécurité
Hack Reverse Engineering

Tout ceux qui ont connu la grande époque des sharewares se souviennent, peut-être avec nostalgie, des bidouilles à faire dans un éditeur héxadécimal pour déverrouiller (illégalement, c'était mal !) telle ou telle fonction. C'est en repensant à cette époque lointaine que je me suis dit qu'à présent j'était très certainement capable de comprendre ce qu'il y avait derrière le fait de changer quelques valeurs dans un éditeur hexa...voire même que je serai capable de reproduire le phénomène. Aussitôt pensé, aussitôt fait (ça m'a pris moins de 10mn, donc n'ayez pas peur : tout le monde arrivera à suivre ^^ !)

Slalom - Creative Common by "Mikael Miettinen" on Flickr

Première chose à faire pour reproduire les conditions de l'époque : trouver un programme cible à "déplomber". Pour le bien de la démonstration j'en réalise un moi-même qui se comportera donc de la façon que je souhaite et dont je connaitrai les rouages internes. Hop je dégaine vim :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    if (0==strcmp("secretpassword",argv[1])){
        printf("HACK SUCCESSFUL\n");
    }
    printf("Au revoir\n");
    return EXIT_SUCCESS;
}

Le concept de ce micro bout de code est simple : on invoque le programme avec un argument, si jamais cet argument est bien "secretpassword" le programme nous affiche "HACK SUCCESSFUL" (c'est la fonction que nous cherchons à "déplomber"), si l'argument fourni n'est pas le bon le programme passe directement à "Au revoir" (et si on ne fourni pas d'argument il segfault...c'est juste un programme d'exemple !). Bref, on compile ($gcc hackme.c) puis on lance gdb ($gdb a.out). Une fois le soft chargé dans gdb, on désassemble le bloc d'instruction principal ((gdb)disass /r main) :

0x08048461 <main+13>:    53     push   %ebx
0x08048462 <main+14>:    51     push   %ecx
0x08048463 <main+15>:    83 ec 10       sub    $0x10,%esp
0x08048466 <main+18>:    89 cb  mov    %ecx,%ebx
0x08048468 <main+20>:    8b 43 04       mov    0x4(%ebx),%eax
0x0804846b <main+23>:    83 c0 04       add    $0x4,%eax
0x0804846e <main+26>:    8b 00  mov    (%eax),%eax
0x08048470 <main+28>:    89 44 24 04    mov    %eax,0x4(%esp)
0x08048474 <main+32>:    c7 04 24 90 85 04 08   movl   $0x8048590,(%esp)
0x0804847b <main+39>:    e8 c8 fe ff ff call   0x8048348 <printf@plt>
0x08048480 <main+44>:    8b 43 04       mov    0x4(%ebx),%eax
0x08048483 <main+47>:    83 c0 04       add    $0x4,%eax
0x08048486 <main+50>:    8b 00  mov    (%eax),%eax
0x08048488 <main+52>:    89 44 24 04    mov    %eax,0x4(%esp)
0x0804848c <main+56>:    c7 04 24 95 85 04 08   movl   $0x8048595,(%esp)
0x08048493 <main+63>:    e8 d0 fe ff ff call   0x8048368 <strcmp@plt>
0x08048498 <main+68>:    85 c0  test   %eax,%eax
0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>
0x0804849c <main+72>:    c7 04 24 a4 85 04 08   movl   $0x80485a4,(%esp)
0x080484a3 <main+79>:    e8 b0 fe ff ff call   0x8048358 <puts@plt>
0x080484a8 <main+84>:    c7 04 24 b6 85 04 08   movl   $0x80485b6,(%esp)
0x080484af <main+91>:    e8 a4 fe ff ff call   0x8048358 <puts@plt>
0x080484b4 <main+96>:    b8 00 00 00 00 mov    $0x0,%eax
0x080484b9 <main+101>:   83 c4 10       add    $0x10,%esp
0x080484bc <main+104>:   59     pop    %ecx
0x080484bd <main+105>:   5b     pop    %ebx
0x080484be <main+106>:   5d     pop    %ebp
0x080484bf <main+107>:   8d 61 fc       lea    -0x4(%ecx),%esp
0x080484c2 <main+110>:   c3     ret

Bon...ça en raconte des choses tout ça...Heureusement que je connais le programme de base, et que je l'ai conçu exprès pour que la tache soit aisée, sinon je pense qu'avec mes compétences en assembleurs "un peu" rouillées je me serait amusé 5mn à trouver ce que je cherche. Ici aucun problème cependant : il n'y a qu'un seul embranchement conditionnel qu'on repère facilement :

0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>

Ce que l'on veut faire c'est donc supprimer cet embranchement conditionnel afin que le flux d'instruction poursuive son cours direct. Pour ce faire je vais utiliser une méthode à moi et honnêtement j'ignore complètement si c'est la méthode canonique ou pas, mais moi je l'aime bien.

D'abord j'utilise xxd1 pour récupérer un dump héxa de mon binaire (xxd a.out > dump.txt), ensuite je cherche dans ce dump l'enchainement 750c qui correspond à mon saut conditionnel (antépénultième bloc dans la ligne suivante) :

0000490: 8504 08e8 d0fe ffff 85c0 750c c704 24a4  ..........u...$.

Je remplace maintenant mon saut conditionnel par l'instruction préférée des pionniers des shellcodes, à savoir le bien aimé 0x90 qui correspond à NOP2. La ligne dans mon fichier dump devient donc :

0000490: 8504 08e8 d0fe ffff 85c0 9090 c704 24a4  ..........u...$.

On récupère un binaire à partir du dump héxa modifié (xxd -r dump.txt b.out). Et ensuite il suffit de lancer b.out avec n'importe quel argument pour bien passer par la fonction que l'on voulait déplomber et qui était auparavent "protégée" par mot de passe :

$ ./b.out HackYou
HACK SUCCESSFUL
Au revoir

Et voilà, le tour est joué. Il me reste encore quelques années/décénnies/vies de pratique avant d'espérer atteindre le niveau de compétence de certains gourous du désassembleur, mais en tout cas ça m'a bien amusé de constater le chemin parcouru depuis mes années collèges avec ce mini déplombage des familles... et ça m'a aussi donné envie de jouer plus souvent avec le couple gdb/xxd.


Dromi le 2010/02/26 21:52

Mon épouse me fait judicieusement remarquer qu'il serait plus propre de modifier l'adresse du saut. En effet, imagine que tu aies ton printf si la condition est non vérifiée... et hop le Oz ne sait plus craquer son programme !
Petite question au passage, est-ce que ça marche si au lieu de mettre des NOP tu effaces simplement les instructions de la pile ?
Moi je me contenterais juste de signaler qu'on peut appeler xxd directement depuis vim (sinon il ne serait pas livré avec...) c'est plus rapide !

Ozwald le 2010/03/04 11:21

Effectivement il serait plus propre de modifier l'adresse du saut, et c'est même bien souvent la seule solution pour détourner le programme de la façon souhaitée, mais :
1. J'ai justement conçu ce programme-ci pour que je n'ai besoin que d'un simple petit nop (et je l'avoue au milieu du texte ;) ) 2. Modifier un saut est plus compliqué qu'écrire un petit nop (il faut au moins re-calculer l'adresse du saut, et ne pas s'emmêler les pinceaux parmi la pelleté de sauts qui existent)

Je ne suis pas certain de comprendre ce que tu proposes avec "effacer les instructions de la pile". Tu veux supprimer l'instruction jump du fichier binaire ? Si c'est bien ça je déconseille parce que ça va décaler toutes les instructions suivantes et donc potentiellement foutre la grouille dans d'autres sauts ou éventuelles références obscures :-/

Très bonne remarque pour vim (et je rajoute juste la précision que pour l'utiliser les commandes idoines sont :%!xxd et :%!xxd -r )

  1. Inclu dans le package de "vim", c'est donc forcément un programme génial ;) !
  2. NOP signifiant : No OPeration. Cette instruction ne fait rien du tout comme son nom l'indique :)