PicoCTF 2018 - Reverse

Sommaire

  • Assembly_0
  • Assembly_1
  • Assembly_2
  • Assembly_3

Assembly_0:

image-left

Ce premier challenge de la catégorie nous présente une suite d’instructions assembleur:

image-left

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:

image-left

Le premier argument est passé dans le registre EAX:

image-left

[ebp+0x8] identifie en effet le premier argument passé dans la fonction, puisque la stack se présente sous la forme suivante:

image-left

Ensuite, le second argument est passé dans le registre EBX:

image-left

Pour finir, la valeur de EBX est passé dans le registre EAX, et le registre EBX est détruit:

image-left

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:

image-left

Toujours dans le même principe, le second challenge est fournis avec un programme assembleur, plus conséquent cette fois-ci:

image-left

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

image-left

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

image-left

Le saut est bien évidement pris.

La fonction ’part_c’ se contente d’ajouter ‘0x3’ à la valeur actuelle du registre EAX:

image-left

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:

image-left

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:

image-left

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

image-left

Le même processus est répété avec le premier argument, avant d’appeler la fonction ‘part_b’:

image-left

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:

image-left

La fonction ‘part_a’ va incrémenter la variable locale n°1 de ‘0x1’, avant d’ajouter ‘0x64’ à l’argument 1:

image-left

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:

image-left

Celui-ci retourne la valeur finale du registre eax, à savoir ‘0x79’, permettant par la même occasion de valider le challenge:

image-left

Assembly_3:

image-left

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:

image-left

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:

image-left

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:

image-left

Ensuite, le contenu du pointeur sur [ebp+b] est placé dans le registre AH:

image-left

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

image-left

L’espace mémoire lié au premier argument est donc le suivant:

image-left

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:

image-left

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:

image-left

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:

image-left

Un premier shift vers la gauche transformerai le chiffre binaire de la façon suivante:

image-left

Un second shift consécutif donnerai donc:

image-left

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

image-left

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:

image-left

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

image-left

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’.

image-left

Le xor de ces deux valeurs retourne ‘0x4e1a’. Cette opération clos la suite d’instructions du programme, et permet de valider le challenge.