PicoCTF 2018 - Reverse
Sommaire
- Assembly_0
- Assembly_1
- Assembly_2
- Assembly_3
Assembly_0:
Ce premier challenge de la catégorie nous présente une suite d’instructions assembleur:
L’unique fonction présentée va en effet prendre deux arguments, comme l’indique la consigne. Il sera nécessaire de déterminer la valeur retournée par cette fonction pour valider le challenge.
Les deux premières instructions servent à initialiser la stack:
Le premier argument est passé dans le registre EAX:
[ebp+0x8] identifie en effet le premier argument passé dans la fonction, puisque la stack se présente sous la forme suivante:
Ensuite, le second argument est passé dans le registre EBX:
Pour finir, la valeur de EBX est passé dans le registre EAX, et le registre EBX est détruit:
EAX contient donc le second argument du programme, à savoir ‘0xb0’ dans notre cas. Cette valeur hexadécimal permet ainsi de valider ce premier challenge.
Assembly_1:
Toujours dans le même principe, le second challenge est fournis avec un programme assembleur, plus conséquent cette fois-ci:
Le programme est appelé avec comme unique argument ‘0x255’.
La première fonction initie une comparaison entre l’argument du programme et la valeur hexadécimal ‘0xea’. Si l’argument est plus grand que cette valeur (instruction ‘jg’), alors le flux d’exécution est redirigé vers la fonction ‘part_a’:
Le saut est donc pris.
La fonction ‘part_a’ va elle aussi comparer l’argument du programme avec la valeur ‘0x6’. Mais cette fois-ci, le saut est pris si l’argument n’est pas égal à ‘0x6’ (instruction ‘jne’):
Le saut est bien évidement pris.
La fonction ’part_c’ se contente d’ajouter ‘0x3’ à la valeur actuelle du registre EAX:
La valeur retournée par le programme est donc ‘0x258’, ce qui permet de valider le second challenge de la catégorie ‘Reverse’.
Assembly_2:
Cet exercice aura pour but d’introduire le concepte des boucles en assembleur. Le programme est appelé avec les arguments ‘0x4’ et ‘0x2d’. Le code étudié est le suivant:
Dans un premier temps, le second argument est placé dans le registre EAX. Une variable locale est ensuite définie avec le contenu de eax. Les variables locales sont identifiées par des valeurs négatives sous EBP (i.e: [ebp-4], [ebp-8], etc …):
Le même processus est répété avec le premier argument, avant d’appeler la fonction ‘part_b’:
La fonction ‘part_b’ se comporte comme une boucle while. Tant que l’argument n°1 est inférieure à ‘0x1d89’, la fonction ‘part_a’ est appelée:
La fonction ‘part_a’ va incrémenter la variable locale n°1 de ‘0x1’, avant d’ajouter ‘0x64’ à l’argument 1:
Enfin, la valeur finale du premier argument est retournée. Afin de ne pas avoir à calculer à la mains les nombreuses boucles du programme, l’écriture d’un script Python est nécessaire. Le script est lui suivant:
Celui-ci retourne la valeur finale du registre eax, à savoir ‘0x79’, permettant par la même occasion de valider le challenge:
Assembly_3:
Pour ce prochain niveau, les choses se compliquent, puisque l’accent est mis sur la manipulation de données au sein des registres. Le code assembleur est le suivant:
Et le programme est appelé avec les arguments ‘0xfac0f685’, ‘0xe0911505’ et ‘0xaee1f319’.
Point important à noter, les registres ‘AX’, ‘AH’ et ‘AL’ sont spéciaux. En effet, ceux-ci correspondent à différentes parties du registre ‘EAX’.
- AX correspond aux 16 plus petits bits de EAX.
- AL correspond au plus petit byte de EAX.
- AH correspond au 8 plus hauts bits de AX (i.e: eax = 0X12345678. ax = 0x5678, al = 0x78 et ah = 0x56)
Pour commencer, la valeur ‘0x27’ est chargée dans EAX:
L’instruction suivante va initialiser à 0 le registre AL (le xor d’un élément avec lui même est toujours égale à 0). Le registre EAX passe donc par la même occasion à 0:
Ensuite, le contenu du pointeur sur [ebp+b] est placé dans le registre AH:
Comme vu plus haut, les arguments passés au programme sont accessibles dans la stack via des adresses positives qui sont supérieurs à [ebp+4] (réservé pour l’adresse de retours du programme / de la fonction en cours). En suivant cette logique, l’argument ‘0xfac0f685’ devrait être accessible en [ebp+8], ‘0xe0911505’ en [ebp+c] et ‘0xaee1f319’ en [ebp+10].
Mais à quoi correspond [ebp+b] dans notre cas ?
Les arguments placés dans la stack sont en little-indian. Par exemple, ‘0x12345678’ et en fait stocké sous la forme ‘0x78563412’:
L’espace mémoire lié au premier argument est donc le suivant:
Ainsi, le registre AH contient désormais la valeur 0xfa. AH étant la partie haute du registre EAX, ce dernier devient ‘0xfa00’.
Le programme va ensuite effectuer un shift vers la gauche des 16 premiers bits (représentation binaire) de la valeur contenu dans AX:
Un shift consiste à décaler, vers la gauche ou vers la droite, les bits uns-à-uns. Voici par exemple, la représentation d’un shift vers la gauche:
Cette image illustre un unique shift. Un shift par ‘0x10’, ou par 16 en base 10, revient à effectuer 16 fois de suite cette opération.
Appliquons désormais cette opération à nos registres !
Précédemment, AX était à la même valeur que EAX, à savoir ‘0xfa00’. La représentation binaire du contenu du registre AX est:
Un premier shift vers la gauche transformerai le chiffre binaire de la façon suivante:
Un second shift consécutif donnerai donc:
Au bout de 5 shifts supplémentaires, la valeur binaire devient alors nulle. Un shift par 16 de la valeur initial du registre AX retourne donc la valeur ‘0x0’:
Si le registre AX = 0x0, EAX devient lui aussi = à 0x0.
L’instruction suivante va soustraire la valeur contenue dans [ebp+c] à la valeur contenu dans AL:
[ebp+c] contient ‘0x05’ et AL contient ‘0x0’. L’opération est donc ‘0x0 - 0x5’. Soit ‘0xfb’ (attention, c’est un nombre négatif, la représentation exacte est ici ‘0xffffffffb’). EAX contient désormais ‘0xfb’.
La prochaine instruction est la suivante:
La valeur contenu à [ebp+f] est ajoutée au contenu du registre AH. [ebp+f] contient ‘0xe0’ et AH est nulle. Donc AH = ‘0x0 + 0xe0’, AH = ‘0xe0’. AH étant les 8 hauts bits de EAX, le registre EAX devient désormais ‘0xe0fb’.
Pour finir, un xor est fait entre [ebp+12] et AUX. [ebp+12] = ‘0xaee1’ (attention au little-indan, c’est ici un WORD) et AX = ‘0xe0fb’.
Le xor de ces deux valeurs retourne ‘0x4e1a’. Cette opération clos la suite d’instructions du programme, et permet de valider le challenge.