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

Le fond de la trousse à outil de papy

Par Oz le - Sécurité
File Carving Outil Reverse Engineering Réseau

J'avais initié il y a un moment une série de billets sur les outils que j'aime bien et que j'utilise en sécurité et/ou bidouillages puis j'y ai repensé cet après midi en me disant que, depuis cet éclaireur, je n'avais pas donné suite...Le temps est donc venu de parler de deux ou trois autres outils bien pratiques !

Tools - Creative Common by 'mtneer_man' on Flickr

L'outil central de ce post sera tcpflow :) ! Cette superbe commande sert à reconstituer le contenu brut des sessions TCP capturées (en live ou dans un fichier pcap). Il prend donc en argument une interface réseau (ou un fichier pcap) puis il génère, pour chaque session TCP, un fichier qui contient les données brutes qui ont transitées dans le flux TCP en question (en bref il supprime les en-têtes IP, il ordonne les paquets TCP, supprime les en-têtes TCP, et sauvegarde bout à bout le contenu complet de la session ainsi allégé). Les fichiers générés suivent une convention de nommage intuitive de type "IP1 PORT1 IP2 PORT 2" et vous pouvez donc assez facilement retrouver celui que vous voulez. A quoi ça sert me direz vous ? A tout ce que vous pouvez imaginer, mais comme je suis beau joueur (et bavard) je vous donne quelques exemples :

Un scénario d'utilisation simple consisterai à sniffer une session FTP et à vouloir récupérer les fichiers qui ont été téléchargées. Grâce à tcpflow c'est simple comme "bonjour" ! Vous lancez tcpflow et il va vous créer un fichier par session TCP sniffée. Vous allez donc avoir un fichier qui contient les commandes et réponses qui ont transitées par la session TCP de controle FTP (à destination du port 21 du serveur), et un fichier pour chaque autre session TCP. Si vous connaissez un peu le protocole ftp vous savez que les autres sessions TCP contiennent précisément chacune le contenu d'un fichier téléchargé, et que donc le fichier généré par tcpflow est l'image exacte du fichier qui a été téléchargé :) ! Job's done.

Second scénario d'utilisation simple : vous sniffez du traffic HTTP. Premier réflexe pour "gratter" des trucs intéressants : urlsnarf, voir carrément dsniff. C'est pas mal, mais si j'ai envie de récupérer les images, voir les animations flash1 comment pourrais-je faire pour extraire simplement ces fichiers de la bouillie informe d'un pcap qui contient pèle mèle les en-têtes IP et TCP et ne garantie en rien l'ordre de lecture des paquets ? Aussi compliqué que ça puisse paraitre c'est en fait presque immédiat avec tcpflow :) ! En lançant tcpflow vous obtiendrez un fichier par session TCP, si vous êtes en HTTP/1.0 vous obtenez donc un fichier dump par fichier ayant réellement transité en HTTP. Seul problème : ces fichiers contiennent les en-têtes HTTP...et si par malheurs vous êtes en HTTP/1.1 c'est encore pire : un seul fichier de session généré par tcpflow peut contenir plusieurs fichiers téléchargés en HTTP les uns à la suite des autres, avec des en-têtes HTTP intercalés dans tout ça...Donc on s'est bien débarassé des en-têtes IP, on s'est bien débarassé des en-têtes TCP, on a bien réordonné les paquets avant d'en coller le contenu bout à bout, mais il nous reste les en-têtes HTTP. C'est là qu'un petit effort de mémoire devient utile pour trouver une bidouille sale mais qui marche : foremost :) ! Pour rappel foremost extrait des fichiers à partir de suite d'octets bruts en se basant sur du pattern matching de header et de footer, foremost permet donc bien d'extraire les fichiers à partir des dump générés par tcpflow en ne faisant pas attention aux headers HTTP (testé et approuvé) ! Alors, bien sur, c'est un petit peu "sale" comme méthode, et vous raterez pas mal de fichiers (typiquement ceux qui transitent compressé puisque foremost ne sait pas les reconnaitre), mais ça a au moins l'avantage de marcher super vite pour récupérer la majeure partie de ce qui a transité :) ! De plus cette méthode est adaptable, en remplaçant foremost par un gentil petit photorec par exemple, qui a le bon gout de récupérer plus de type de fichiers que foremost (un jour il faudra vraiment que je le teste quand même...).

Dernier scénario où tcpflow peut s'avérer salutaire : si vous avez un pentest à faire sur une application client-serveur et que vous réussissez à récupérer une trace réseau. Dans ce cas là pensez à faire un petit tcpflow, ça vous permettra d'analyser tranquillement les informations qui transitent entre le client et le serveur sans avoir à vous encombrer des couches réseau basses. A la base c'est même pour ça que tcpflow a été écrit il me semble : pour faciliter la rétro-ingénierie des protocoles obscurs.

Bref vous l'aurez compris : tcpflow est un outil très pratique, et vu qu'en plus il est très léger et rapide ça serait vraiment dommage de s'en priver.

  1. pour éventuellement en extraire la bande son, la convertir en mp3, et la sauvegarder conformément à l'exception aux droits d'auteurs et droits voisins au nom de la copie privé

« Page 2 / 2