Geospace

Une journée de rétro-ingénierie

Si on est sur un public qui a déjà fait un de reverse ou qui, du moins, connaît les enjeux du bas niveau et n’a pas peur d’un listing assembleur, on peut se passer d’une intro et aller tout de suite à la démo.

Autrement, il est super important de rassurer le public afin d’éviter tout “blocage”. Beaucoup de débutants restent perplexes devant l’assembleur et ne savent pas quoi faire. Il faut alors forcer l’étudiant à traduire ligne par ligne le code vers du C. On peut alors lui montrer qu’une fois qu’on a identifié tel ou tel pattern et fait le lien avec du C, il est très facile de le repérer à nouveau et l’exercice devient de plus en plus facile.

Il faut aussi insister sur le fait que l’assembleur peut s’apprendre sur le tas mais qu’il est important de poser des questions aux assistants ou faire des recherches Google. Pour ceux qui veulent en savoir plus, j’aime bien Le langage assembleur de Olivier CAUET. Attention, peut être un peu cher pour ce que c’est mais c’est cool de l’avoir dans la collection.

Parmi les exercices, il y a des binaires compilés avec clang pour le 32 bits. Il faut donc prévenir les étudiants qu’ils doivent installer les librairies partagées 32 bits. Les noms des paquets varient selon les distributions. Pour Fedora, ça devrait suffire :

sudo dnf install glibc.i686

Les lignes de compilation sont données en commentaire dans le code source des exercices.

Attention : Au niveau de la correction, certains exercices peuvent être résolus facilement avec ltrace. Les étudiants doivent tout de même répondre à toutes les questions. On peut considérer que si, dans leurs explications, il n’y a pas au moins autant d’informations que dans cette correction, ce n’est pas bon.

Exercice 00

On installe GDB, peu importe la distribution Linux il devrait être dans les dépots. Pour Peda, on suit simplement le GitHub.

Une fois GDB installé, on part sur une petite démonstration. On peut faire la démo sur l’exercice 01. Il faut leur montrer :

Exercice 01

Sources du binaire ici…

Ce binaire est un programme affichant “Hello, World !”. Mais le programme exécute aussi du code inutile avant le binaire. Il faut pousser les étudiants à vraiment regarder le listing. C’est sur cet exercice qu’il faut être le plus présent, donner des indices et aider. Il ne faut pas les dégoûter mais au contraire attiser la curiosité.

Pas de difficulté particulière autrement. Dès que quelques étudiants ont terminé, on corrige ensemble. Comme ça, on remet une couche sur GDB et on en profite pour présenter les notions suivantes :

Si besoin de révision, bon article avec des schémas et tout.

Exercice 02

Sources du binaire ici…

Le flag peut être trouvé avec ltrace :

ltrace ./bin02 aled

__libc_start_main(0x80484b0, 2, 0xffb48f84, 0x8048570 <unfinished ...>
strcmp("aled", "quoi_de_neuf_docteur")                                                               = -1
printf("Non, c'est pas bon...\n"Non, c'est pas bon...
)                                                                    = 22
+++ exited (status 1) +++

Ou bien avec strings :

strings bin02

[...]
I want a password as argument.
quoi_de_neuf_docteur
C'est bingo !
Non, c'est pas bon...
[...]

Peu importe comment l’étudiant trouve, on lui montre l’autre méthode.

Exercice 03

Sources du binaire ici…

Prologue :

0x080484f0 <+0>:	push   ebp
0x080484f1 <+1>:	mov    ebp,esp
0x080484f3 <+3>:	push   ebx
0x080484f4 <+4>:	sub    esp,0x34

On a donc 0x34, soit 52 octets réservés.

La boucle :

0x08048582 <+146>:	call   0x80483d0 <strlen@plt>
0x08048587 <+151>:	mov    DWORD PTR [ebp-0x18],eax   # loc18 = strlen(flag)
0x0804858a <+154>:	mov    DWORD PTR [ebp-0x1c],0x0   # loc1c = 0, notre compteur
0x08048591 <+161>:	mov    eax,DWORD PTR [ebp-0x1c]
0x08048594 <+164>:	cmp    eax,DWORD PTR [ebp-0x18]   # if (loc1c >= loc18)
0x08048597 <+167>:	jae    0x80485dc <main+236>       # fin de la boucle
0x0804859d <+173>:	mov    eax,DWORD PTR [ebp-0x1c]
0x080485a0 <+176>:	mov    ecx,DWORD PTR [ebp-0x14]   # loc14 est le pointeur vers flag
0x080485a3 <+179>:	movsx  eax,BYTE PTR [ecx+eax*1]   # eax = flag[loc1c]
0x080485a7 <+183>:	cmp    eax,0x5f                   # if (flag[loc1c] != '_')
0x080485ac <+188>:	jne    0x80485b7 <main+199>       # on continue
0x080485b2 <+194>:	jmp    0x80485cc <main+220>       # sinon on saute le bloc
0x080485b7 <+199>:	mov    eax,DWORD PTR [ebp-0x1c]
0x080485ba <+202>:	mov    ecx,DWORD PTR [ebp-0x14]
0x080485bd <+205>:	movsx  edx,BYTE PTR [ecx+eax*1]   # edx = flag[loc1c]
0x080485c1 <+209>:	sub    edx,0x20                   # flag[loc1c] -= 20
0x080485c7 <+215>:	mov    bl,dl                      # on passe en majuscule
0x080485c9 <+217>:	mov    BYTE PTR [ecx+eax*1],bl
0x080485cc <+220>:	mov    eax,DWORD PTR [ebp-0x1c]
0x080485cf <+223>:	add    eax,0x1                    # loc1c++
0x080485d4 <+228>:	mov    DWORD PTR [ebp-0x1c],eax
0x080485d7 <+231>:	jmp    0x8048591 <main+161>       # et on boucle

Nous avons donc les variables locales suivantes :

L’adresse du flag est :

Breakpoint 1, 0x080485a0 in main ()
gdb-peda$ x/x $ebp-0x14
0xffffd714:	0x0804a008
gdb-peda$ x/s 0x0804a008
0x804a008:	"avec_effet_de_serre"

On a EBP-0x14 qui vaut 0xffffd714. A cette adresse se trouve le pointeur vers notre flag (avant parcours ici) : 0x0804a008.

Ce pointeur est par exemple déréférencé en main+179 :

0x80485a3 <main+179>:	movsx  eax,BYTE PTR [ecx+eax*1]

On ajoute EAX (loc1c) à ECX (loc14, ou encore EBP-0x14) puis on déréférence pour récupérer le caractère dans EAX.

Le mot de passe est AVEC_EFFET_DE_SERRE.

Exercice 04

Sources du binaire ici…

+132 à +137 :

0x08048554 <+132>:	mov    eax,DWORD PTR [ebp-0xc]
0x08048557 <+135>:	mov    eax,DWORD PTR [eax]
0x08048559 <+137>:	mov    DWORD PTR [ebp-0x10],eax
0x0804855c <+140>:	mov    eax,DWORD PTR [eax]
0x0804855e <+142>:	mov    DWORD PTR [ebp-0x14],eax

On a EBP-0xC qui est déréférencé. Cela correspond à récupérer argv[1], le pointeur ayant déjà été incrémenté en +70 :

0x08048513 <+67>:	mov    eax,DWORD PTR [ebp-0xc]
0x08048516 <+70>:	add    eax,0x4
0x0804851b <+75>:	mov    DWORD PTR [ebp-0xc],eax

Attention, si on sent que l’étudiant est perdu parce qu’il n’a pas vu le ++argv, on peut lui dire. C’est quand même pas mal frustrant… :p

Le pointeur est sauvegardé pour un usage futur. On va ensuite le déréférencer encore une fois sur 4 octets. Cela revient à un *((uint32_t *)(pointeur)). Ces 4 octets sont finalement stockés dans une variable locale.

+152 à +158 :

0x08048568 <+152>:	mov    eax,DWORD PTR [ebp-0x10]
0x0804856b <+155>:	mov    eax,DWORD PTR [eax+0x4]
0x0804856e <+158>:	mov    DWORD PTR [ebp-0x1c],eax

On a EBP-0x10 qui est récupéré : c’est le pointeur que nous venons de stocker, soit argv[1]. Ce pointeur est incrémenté de 4 puis déréférencé à nouveau sur 4 octets. C’est notre équivalent de *((uint32_t *)(pointeur) + 1). On récupère ainsi les 4 octets suivants. On peut donc voir argv[1] non pas comme une chaîne de caractères mais comme un uint64_t que nous traitons en deux uint32_t.

La fonction soap prend deux valeurs non signées sur 4 octets en paramètre. Ces deux valeurs sont parcourues octets par octets. Les octets sont XORés deux à deux. Le résultat de ce XOR est retourné. La destination du résultat n’est pas importante si l’étudiant ne l’évoque pas.

On a beaucoup des déréférenciations, le xor, une boucle, rien de particulier.

0x08048460 <+0>:	push   ebp
0x08048461 <+1>:	mov    ebp,esp
0x08048463 <+3>:	push   ebx
0x08048464 <+4>:	push   esi
0x08048465 <+5>:	sub    esp,0x14                 # 14 octets de local
0x08048468 <+8>:	mov    eax,DWORD PTR [ebp+0xc]  # argument1
0x0804846b <+11>:	mov    ecx,DWORD PTR [ebp+0x8]  # argument2
0x0804846e <+14>:	lea    edx,[ebp-0x10]           # pointeur vers argument2
0x08048471 <+17>:	lea    esi,[ebp-0xc]            # pointeur vers argument1
0x08048474 <+20>:	mov    DWORD PTR [ebp-0xc],ecx  # copie de l'argument 2
0x08048477 <+23>:	mov    DWORD PTR [ebp-0x10],eax # copie de l'argument 1
0x0804847a <+26>:	mov    DWORD PTR [ebp-0x14],esi # pointeur vers argument 1
0x0804847d <+29>:	mov    DWORD PTR [ebp-0x18],edx # pointeur vers argument 2
0x08048480 <+32>:	mov    DWORD PTR [ebp-0x1c],0x0 # compteur
0x08048487 <+39>:	cmp    DWORD PTR [ebp-0x1c],0x4 # si >= 4
0x0804848e <+46>:	jae    0x80484c1 <soap+97>      # fin de la boucle
0x08048494 <+52>:	mov    eax,DWORD PTR [ebp-0x18] # argument 2
0x08048497 <+55>:	mov    ecx,DWORD PTR [ebp-0x1c] # compteur
0x0804849a <+58>:	movzx  eax,BYTE PTR [eax+ecx*1] # argument2[compteur]
0x0804849e <+62>:	mov    ecx,DWORD PTR [ebp-0x14] # argument 1
0x080484a1 <+65>:	mov    edx,DWORD PTR [ebp-0x1c] # compteur
0x080484a4 <+68>:	movzx  esi,BYTE PTR [ecx+edx*1] # argument1[compteur]
0x080484a8 <+72>:	xor    esi,eax                  # le xor entre les deux caractères
0x080484aa <+74>:	mov    eax,esi                  # résultat
0x080484ac <+76>:	mov    bl,al                    # partie basse
0x080484ae <+78>:	mov    BYTE PTR [ecx+edx*1],bl  # argument1[compteur] = résultat
0x080484b1 <+81>:	mov    eax,DWORD PTR [ebp-0x1c] # compteur
0x080484b4 <+84>:	add    eax,0x1                  # ++compteur
0x080484b9 <+89>:	mov    DWORD PTR [ebp-0x1c],eax
0x080484bc <+92>:	jmp    0x8048487 <soap+39>      # et on boucle
0x080484c1 <+97>:	mov    eax,DWORD PTR [ebp-0xc]  # résultat en valeur de retour
0x080484c4 <+100>:	add    esp,0x14
0x080484c7 <+103>:	pop    esi
0x080484c8 <+104>:	pop    ebx
0x080484c9 <+105>:	pop    ebp
0x080484ca <+106>:	ret

Les arguments sont poussés sur la stack avant l’instruction call :

0x080485a2 <+210>:	mov    DWORD PTR [esp],eax
0x080485a5 <+213>:	mov    DWORD PTR [esp+0x4],ecx
0x080485a9 <+217>:	call   0x8048460 <soap>

La valeur de retour se trouve dans EAX après l’appel :

0x080485a9 <+217>:	call   0x8048460 <soap>
0x080485ae <+222>:	mov    ecx,DWORD PTR [ebp-0x2c]
0x080485b1 <+225>:	mov    DWORD PTR [ecx+0x4],eax

Le pointeur finit sauvegardé dans l’espace local.

Le préfixe ds signifie data segment. Ce pointeur fait référence à une variable globale initialisée. On peut en profiter pour parler un peu des segments.

Le XOR est une opération symétrique. Il suffit donc de B X C pour retrouver A.

Pour le mot de passe, on se récupère les octets de la variable globale :

gdb-peda$ x/s 0x80498f8
0x80498f8 <belier>:	"\026\207\004\b"
gdb-peda$ x/s * 0x80498f8
0x8048716:	"1337W00T"

Puis les octets en dur dans le binaire qui sont passés à soap :

0x08048561 <+145>:	mov    DWORD PTR [ebp-0x18],0x76667c7d
[...]
0x08048571 <+161>:	mov    DWORD PTR [ebp-0x20],0x670c7519

On en fait un uint64_t : 0x670c751976667c7d. Attention à l’ordre des deux uint32_t dans le uint64_t. Il n’y a pas de problème de boutisme mais le “deuxième” doit être mis en premier. Ce qui nous fait 8 octets des deux côtés, on peut XORer :

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

int main(void) {
  uint64_t ok = 0x670c751976667c7d;
  const char *oklm = "1337W00T";

  uint8_t *zer = (uint8_t *)&ok;
  char r[9] = {0};

  for (size_t i = 0; i < 8; ++i)
    r[i] = zer[i] ^ oklm[i];

  printf("Flag: %s !\n", r);
  return EXIT_SUCCESS;
}

// clang solve.c -o solve
// ./solve
// Flag: LOUANE<3 !

Le mot de passe demandé est donc LOUANE<3.

Exercice 05

Sources du binaire ici…

On est sur un binaire strippé… On va se retrouver l’adresse de la fonction main en regardant quel argument est passé à la routine __libc_start_main@plt.

On récupère le point d’entrée du programme :

gdb-peda$ info file
Symbols from "/challenges/ex05/bin05".
Local exec file:
	`/challenges/ex05/bin05', file type elf32-i386.
	Entry point: 0x8048360
	0x08048134 - 0x08048147 is .interp
	0x08048148 - 0x08048168 is .note.ABI-tag
        [...]

Il faut bien expliquer aux étudiants la différence entre point d’entrée et fonction main. On peut explorer les instructions à partir de là :

gdb-peda$ x/50i 0x8048360
   [...]
   0x8048375:	push   ecx
   0x8048376:	push   esi
   0x8048377:	push   0x804845b
   0x804837c:	call   0x8048350 <__libc_start_main@plt>
   0x8048381:	hlt
   0x8048382:	xchg   ax,ax
   [...]

On a donc 0x804845b. C’est l’adresse de notre fonction main :

gdb-peda$ x/20i 0x804845b
   0x804845b:	lea    ecx,[esp+0x4]
   0x804845f:	and    esp,0xfffffff0
   0x8048462:	push   DWORD PTR [ecx-0x4]
   0x8048465:	push   ebp
   0x8048466:	mov    ebp,esp
   0x8048468:	push   ebx
   0x8048469:	push   ecx
   0x804846a:	mov    ebx,ecx
   0x804846c:	cmp    DWORD PTR [ebx],0x1

On retombe sur un prologue de fonction, on voit un check sur le nombre d’aguments… On est au bon endroit !

Un peu plus bas dans le listing du main :

0x80484aa:	mov    edx,DWORD PTR ds:0x804982c
0x80484b0:	add    edx,0x25
0x80484b3:	sub    esp,0x8
0x80484b6:	push   eax
0x80484b7:	push   edx
0x80484b8:	call   0x8048310 <strcmp@plt>
0x80484bd:	add    esp,0x10

On a un strcmp entre EAX (entrée utilisateur) et un autre pointeur (ds:0x804982c + 0x25). On va voir ce qui se passe :

gdb-peda$ x/s * 0x804982c
0x8048590:	"c'est pas le quartier qui me quitte, c'est moi j'quitte le quartier"
gdb-peda$ x/s 0x8048590
0x8048590:	"c'est pas le quartier qui me quitte, c'est moi j'quitte le quartier"
gdb-peda$ x/s (0x8048590 + 0x25)
0x80485b5:	"c'est moi j'quitte le quartier"
gdb-peda$

Le mot de passe demandé est donc c'est moi j'quitte le quartier !

Exercice 06

Sources du binaire ici…

On est sur du bytecode Python :

file bin06

bin06: python 2.7 byte-compiled

On décompile le code avec uncompyle :

uncompyle6 bin06.pyc

# uncompyle6 version 3.2.5
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.4.2 (default, Sep 25 2018, 22:02:39)
# [GCC 4.9.2]
# Embedded file name: ./bin06.py
# Compiled at: 2019-01-02 16:26:15
import sys
FLAG = 8405166

def main(argc, argv):
    if argc < 2:
        sys.stderr.write('Il me faut un argument...\n')
        return -1
    userInput = argv[1]
    if str(FLAG) == userInput:
        sys.stdout.write('Bravo !\n')
        return 0
    sys.stdout.write("Ce n'est pas bon...\n")
    return 1


if __name__ == '__main__':
    ret = main(len(sys.argv), sys.argv)
    sys.exit(ret)
# okay decompiling bin06.pyc

On a pas mal d’autres modules qui peuvent fonctionner : unpyclib, decompyle… C’est l’étudiant qui choisit mais il faut décourager l’utilisation de GDB et le pousser à trouver ce qui répond à son problème.

Le mot de passe est donc 8405166.

Exercice 07

On est sur un binaire écrit en Go :

strings bin07 | grep "runtime.go"

*runtime.gobuf
runtime.gogetenv
runtime.gomcache
runtime.gosweepdone
runtime.gosweepone
[...]

GDB est plus ou moins capable de travailler avec ce genre de binaire… Commençons par regarder les fonctions du module main :

gdb-peda$ set loggin on
Copying output to gdb.txt.
gdb-peda$ info functions
[...]

Le logging est utile quand la sortie de GDB est aussi fat…

cat gdb.txt | grep main
File /home/oursin/go/src/re/main.go:
void main.main(void);
void main.test(struct string, struct string);
void runtime.main(void);
void runtime.main.func1(void);
void runtime.main.func2(bool *);
void main.init(void);

La fonction test semble intéressante, on peut poser un point d’arrêt dessus :

gdb-peda$ b main.test
Breakpoint 1 at 0x4883f0: file /home/oursin/go/src/re/main.go, line 10.
gdb-peda$ r
Starting program: /challenges/ex07/bin07
AAAAAAAAAAAAAAAAAAAA
[----------------------------------registers-----------------------------------]
RAX: 0x4bd326 ("RmFpdGVzRHVHb0JhbmRlRGVCYXRhcmRzSIGFPE: floating-point exceptionSIGTTOU: background write to ttybufio: invalid use of UnreadBytebufio: invalid use of UnreadRunebufio: tried to fill full bufferend outs"...)
RBX: 0x1c
RCX: 0x1c
RDX: 0x1c
RSI: 0xc00008c000 ("QUFBQUFBQUFBQUFBQUFBQUFBQUE=")
RDI: 0xc000080cb8 ("QUFBQUFBQUFBQUFBQUFBQUFBQUE=")
RBP: 0xc000080f88 --> 0xc000080f90 --> 0x428887 (<runtime.main+519>:	mov    eax,DWORD PTR [rip+0x1467bf]        # 0x56f04c <runtime.runningPanicDefers>)
RSP: 0xc000080c48 --> 0x488b79 (<main.main+1561>:	mov    rbp,QWORD PTR [rsp+0x338])
RIP: 0x4883f0 (<main.test>:	mov    rcx,QWORD PTR fs:0xfffffffffffffff8)
R8 : 0x4
R9 : 0x0
R10: 0x34 ('4')
R11: 0x1
R12: 0x40 ('@')
R13: 0x40 ('@')
R14: 0x4
R15: 0x100
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4883ed:	int3
   0x4883ee:	int3
   0x4883ef:	int3
=> 0x4883f0 <main.test>:	mov    rcx,QWORD PTR fs:0xfffffffffffffff8
   0x4883f9 <main.test+9>:	lea    rax,[rsp-0x18]
   0x4883fe <main.test+14>:	cmp    rax,QWORD PTR [rcx+0x10]
   0x488402 <main.test+18>:	jbe    0x488553 <main.test+355>
   0x488408 <main.test+24>:	sub    rsp,0x98
[------------------------------------stack-------------------------------------]
0000| 0xc000080c48 --> 0x488b79 (<main.main+1561>:	mov    rbp,QWORD PTR [rsp+0x338])
0008| 0xc000080c50 --> 0xc000080cb8 ("QUFBQUFBQUFBQUFBQUFBQUFBQUE=")
0016| 0xc000080c58 --> 0x1c
0024| 0xc000080c60 --> 0x4bd326 ("RmFpdGVzRHVHb0JhbmRlRGVCYXRhcmRzSIGFPE: floating-point exceptionSIGTTOU: background write to ttybufio: invalid use of UnreadBytebufio: invalid use of UnreadRunebufio: tried to fill full bufferend outs"...)
0032| 0xc000080c68 --> 0x20 (' ')
0040| 0xc000080c70 --> 0xc000080cb8 ("QUFBQUFBQUFBQUFBQUFBQUFBQUE=")
0048| 0xc000080c78 --> 0x1c
0056| 0xc000080c80 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, main.test (flag=..., input=...) at /home/oursin/go/src/re/main.go:10
10	/home/oursin/go/src/re/main.go: No such file or directory.

Il en faut pas plus… On repère la chaîne RmFpdGVzRHVHb0JhbmRlRGVCYXRhcmRz sur la pile. Et on récupère le flag :

echo -n "RmFpdGVzRHVHb0JhbmRlRGVCYXRhcmRz" | base64 --decode
FaitesDuGoBandeDeBatards

Exercice 08

Le binaire de cet exercice utilise plusieurs protections anti-debug :

De part la construction du binaire, on ne va pas aller chercher __libc_start_main@plt nous même pour repérer la fonction main comme on l’a fait précédemment. On va plutôt utiliser la technique du backtrace qui est plus “automatique”. On commence par ltrace le binaire pour espérer retrouver une fonction connue :

ltrace ./siglol
getenv("COLUMNS")                                                                                      = nil
signal(SIGSEGV, 0x55cab5675199)                                                                        = 0
getenv("LINES")                                                                                        = nil
[...]
ptrace(0, 0, 1, 0)                                                                                     = -1
puts("ARRETEZ DE ME SUIVRE"ARRETEZ DE ME SUIVRE
)                                                                           = 21

Mis à part le code XORé, toutes les protections sont alors exposées. Le premier appel à getenv se situe avant toute autre protection anti-debug donc pas de pression :

gdb-peda$ b * getenv
Breakpoint 1 at 0x1030
gdb-peda$ r
[...]
Breakpoint 1, __GI_getenv (name=0x555555556084 "COLUMNS") at getenv.c:35
35	getenv.c: No such file or directory.
gdb-peda$ bt
#0  __GI_getenv (name=0x555555556084 "COLUMNS") at getenv.c:35
#1  0x0000555555555419 in ?? ()
#2  0x00007ffff7a52b45 in __libc_start_main (main=0x5555555553fd, argc=0x1, argv=0x7fffffffe6f8, init=<optimized out>, fini=<optimized out>,
    rtld_fini=<optimized out>, stack_end=0x7fffffffe6e8) at libc-start.c:287
#3  0x00005555555550ce in ?? ()

La trace nous révèle l’appel à __libc_start_main. Son premier paramètre est l’adresse de main : 0x5555555553fd. On peut poser un point d’arrêt dessus et désamorcer toutes les protections que l’on connaît alors :

gdb-peda$ b * 0x5555555553fd
Breakpoint 2 at 0x5555555553fd
gdb-peda$ unset env LINES               # bypass getenv()
gdb-peda$ unset env COLUMNS             # bypass getenv()
gdb-peda$ handle SIGSEGV pass noprint   # bypass SIGSEGV
gdb-peda$ unptrace                      # bypass ptrace()
Breakpoint 3 at 0x555555555070
'ptrace' deactivated
[...]
gdb-peda$ r
[...]
Breakpoint 2, 0x00005555555553fd in ?? ()
gdb-peda$ x/20i $rip
=> 0x5555555553fd:	push   rbp
   0x5555555553fe:	mov    rbp,rsp
   0x555555555401:	sub    rsp,0x10
   0x555555555405:	mov    QWORD PTR [rbp-0x8],0x0
   0x55555555540d:	lea    rdi,[rip+0xc70]        # 0x555555556084
   0x555555555414:	call   0x555555555030 <getenv@plt>

A partir de là, on peut commencer à se balader dans le code, regarder les routines…

La fonction qui nous intéresse est la dernière à être appelée par main :

0x555555555499:	call   0x5555555552d7
0x55555555549e:	leave
0x55555555549f:	ret

On regarde :

gdb-peda$ x/70i 0x5555555552d7

[...]
0x555555555346:	mov    eax,DWORD PTR [rbp-0x4]          # the counter
0x555555555349:	lea    rdx,[rip+0x2d90]                 # XORed code
0x555555555350:	movzx  eax,BYTE PTR [rax+rdx*1]         # xored[counter]
0x555555555354:	mov    edx,eax                          # to EDX
0x555555555356:	movzx  eax,BYTE PTR [rip+0x2dfa]        # global variable (shoud be 0 at this point)
0x55555555535d:	xor    eax,edx                          # XOR operation
0x55555555535f:	mov    ecx,eax                          # saved XORed byte
0x555555555361:	mov    eax,DWORD PTR [rbp-0x4]          # get counter back
0x555555555364:	lea    rdx,[rip+0x2d75]                 # where to write result
0x55555555536b:	mov    BYTE PTR [rax+rdx*1],cl          # write result
0x55555555536e:	add    DWORD PTR [rbp-0x4],0x1          # increment counter
0x555555555372:	cmp    DWORD PTR [rbp-0x4],0x74         # should we end ?
0x555555555376:	jbe    0x555555555346                   # loop
[...]
0x555555555381:	mov    eax,DWORD PTR [rbp-0x8]          # another counter
0x555555555384:	lea    rdx,[rip+0x2d55]                 # XORed code
0x55555555538b:	movzx  eax,BYTE PTR [rax+rdx*1]         # xored[counter]
0x55555555538f:	mov    edx,DWORD PTR [rbp-0x8]          # counter
0x555555555392:	sub    edx,0x6e                         # kind of key
0x555555555395:	xor    eax,edx                          # xor operation
0x555555555397:	mov    ecx,eax                          # save result
0x555555555399:	mov    eax,DWORD PTR [rbp-0x8]          # get counter back
0x55555555539c:	lea    rdx,[rip+0x2d3d]                 # XORed code
0x5555555553a3:	mov    BYTE PTR [rax+rdx*1],cl          # write result
0x5555555553a6:	add    DWORD PTR [rbp-0x8],0x1          # increment counter
0x5555555553aa:	cmp    DWORD PTR [rbp-0x8],0x74         # should we end ?
0x5555555553ae:	jbe    0x555555555381                   # loop
[...]
0x5555555553ce:	mov    rdi,rax
0x5555555553d1:	call   rdx                              # call clean code
0x5555555553d3:	test   eax,eax

On a deux boucles qui viennent XOR notre bout de code chiffré. La première peut être ignorée étant donné que la variable globale utilisée comme clé est en fait la valeur de retour de getenv(). Etant donné que nous avons unset LINES et COLUMNS, sa valeur devrait être 0, ce qui ne modifie pas le code.

La deuxième boucle déchiffre effectivement le code. On peut aller voir un peu ce qui se passe :

gdb-peda$ b * 0x5555555553d1
Breakpoint 4 at 0x5555555553d1
gdb-peda$ c
Continuing.





Rentre une string frer:
ABCDEFGHI

Breakpoint 4, 0x00005555555553d1 in ?? ()
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffe1e0 ("ABCDEFGHI")
RBX: 0x0
RCX: 0xff
RDX: 0x5555555580e0 --> 0x8d48c03148db3148
RSI: 0x7fffffffe1e0 ("ABCDEFGHI")
RDI: 0x7fffffffe1e0 ("ABCDEFGHI")
RBP: 0x7fffffffe5f0 --> 0x7fffffffe610 --> 0x0
RSP: 0x7fffffffe1e0 ("ABCDEFGHI")
RIP: 0x5555555553d1 (call   rdx)
[...]
[-------------------------------------code-------------------------------------]
   0x5555555553c0:	lea    rdx,[rip+0x2d19]        # 0x5555555580e0
   0x5555555553c7:	lea    rax,[rbp-0x410]
   0x5555555553ce:	mov    rdi,rax
=> 0x5555555553d1:	call   rdx
[...]

On remarque que notre entrée est passée en paramètre. On peut imaginer qu’on se rapproche de la routine de vérification. On s’affiche le code déchiffré :

gdb-peda$ n
0x00005555555580e0 in ?? ()
gdb-peda$ x/50i $rip
0x5555555580e0:	xor    rbx,rbx
0x5555555580e3:	xor    rax,rax
0x5555555580e6:	lea    rax,[rip+0xfffffffffffffff9]
0x5555555580ed:	add    rax,0xe
0x5555555580f1:	jmp    rax
0x5555555580f3:	adc    dh,BYTE PTR [rbx-0x776b7ce]      # who cares up to there
0x5555555580f9:	jmp    0x555555558109                   # start of the loop
0x5555555580fe:	xor    cl,bl                            # XOR operation
0x555555558100:	add    bl,0x3                           # add 0x03 to key
0x555555558103:	mov    BYTE PTR [rax],cl                # write result
0x555555558105:	add    rax,0x1                          # ++userInput (pointer)
0x555555558109:	mov    cl,BYTE PTR [rax]                # *userInput
0x55555555810b:	test   cl,cl                            # should we end ?
0x55555555810d:	jne    0x5555555580fe                   # loop
0x555555558113:	sub    rdx,0x60
0x555555558117:	xor    rbx,rbx
0x55555555811a:	mov    rax,rsi
0x55555555811d:	mov    bl,0x48                          # length of xoredFlag (loop condition)
0x55555555811f:	mov    cl,BYTE PTR [rdx]                # *xoredFlag
0x555555558121:	cmp    BYTE PTR [rax],cl                # if *xored(userInput) != xoredFlag
0x555555558123:	jne    0x555555558140                   # jump to badboy
0x555555558129:	sub    bl,0x1                           # decrement loop condition
0x55555555812c:	add    rax,0x1                          # ++xored(userInput)
0x555555558130:	add    rdx,0x1                          # ++xoredFlag (pointer)
0x555555558134:	test   bl,bl                            # should we end ?
0x555555558136:	jne    0x55555555811f                   # loop
0x55555555813c:	xor    rax,rax                          # goodboy
0x55555555813f:	ret
0x555555558140:	mov    eax,0x1                          # badboy
0x555555558145:	ret

On va se mettre au niveau du XOR pour choper la valeur de départ de la clé :

gdb-peda$
[...]
RBX: 0x32 ('2')
[...]
[-------------------------------------code-------------------------------------]
   0x5555555580f3:	adc    dh,BYTE PTR [rbx-0x776b7ce]
   0x5555555580f9:	jmp    0x555555558109
   0x5555555580fe:	xor    cl,bl
=> 0x555555558100:	add    bl,0x3
   0x555555558103:	mov    BYTE PTR [rax],cl
   0x555555558105:	add    rax,0x1
   0x555555558109:	mov    cl,BYTE PTR [rax]
   0x55555555810b:	test   cl,cl
[------------------------------------stack-------------------------------------]
[...]

La clé commence à 0x32 puis est incrémentée de 0x03 à chaque fois. Il nous manque plus que les octets XORés :

gdb-peda$ b * 0x55555555811d
Breakpoint 5 at 0x55555555811d
gdb-peda$ c
Continuing.
[...]
RDX: 0x555555558080 --> 0x182129455c595954
[...]
   0x555555558113:	sub    rdx,0x60
   0x555555558117:	xor    rbx,rbx
   0x55555555811a:	mov    rax,rsi
=> 0x55555555811d:	mov    bl,0x48
   0x55555555811f:	mov    cl,BYTE PTR [rdx]
   0x555555558121:	cmp    BYTE PTR [rax],cl
   0x555555558123:	jne    0x555555558140
   0x555555558129:	sub    bl,0x1
[...]
gdb-peda$ p/d 0x48
$7 = 72
gdb-peda$ x/72c $rdx
0x555555558080:	0x54	0x59	0x59	0x5c	0x45	0x29	0x21	0x18
0x555555558088:	0x28	0x28	0x3e	0xc	0x35	0x38	0x3	0x3c
0x555555558090:	0x3d	0x0	0x1b	0x1f	0x31	0x4	0x1a	0x28
0x555555558098:	0x18	0x12	0xee	0xdc	0xe1	0xfb	0xe3	0xfc
0x5555555580a0:	0xcd	0xf6	0xf0	0xfa	0xf2	0xcd	0xfb	0xc3
0x5555555580a8:	0xcf	0xf2	0xd2	0xd2	0xc4	0xdb	0xc9	0xe0
0x5555555580b0:	0xa3	0xb3	0xad	0xa8	0x91	0xa4	0xba	0x88
0x5555555580b8:	0xbc	0xb1	0x81	0x84	0xb9	0x88	0xb3	0x9d
0x5555555580c0:	0x93	0x99	0x94	0x94	0x90	0x66	0x61	0x7a
gdb-peda$

Et on récupère le flag :

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


const uint8_t xored[] = {
  0x54, 0x59, 0x59, 0x5c, 0x45, 0x29, 0x21, 0x18,
  0x28, 0x28, 0x3e, 0x0c, 0x35, 0x38, 0x03, 0x3c,
  0x3d, 0x00, 0x1b, 0x1f, 0x31, 0x04, 0x1a, 0x28,
  0x18, 0x12, 0xee, 0xdc, 0xe1, 0xfb, 0xe3, 0xfc,
  0xcd, 0xf6, 0xf0, 0xfa, 0xf2, 0xcd, 0xfb, 0xc3,
  0xcf, 0xf2, 0xd2, 0xd2, 0xc4, 0xdb, 0xc9, 0xe0,
  0xa3, 0xb3, 0xad, 0xa8, 0x91, 0xa4, 0xba, 0x88,
  0xbc, 0xb1, 0x81, 0x84, 0xb9, 0x88, 0xb3, 0x9d,
  0x93, 0x99, 0x94, 0x94, 0x90, 0x66, 0x61, 0x7a
};

int main(void) {
  uint8_t clean[sizeof(xored) + 1] = {0};

  uint8_t key = 0x32;
  for (size_t i = 0; i < sizeof(xored); ++i) {
    clean[i] = xored[i] ^ key;
    key += 0x3;
  }

  printf("Flag is : [%s]\n", clean);

  return EXIT_SUCCESS;
}

// clang solve.c -o solve
// $ ./solve
// Flag is : [flag{he_ben_ca_c_est_un_bon_gros_chall_de_barbu_avec_un_flag_a_rallonge}]

C’est tout bon !

Exercice 09

On insiste pour avoir un retour ! Et on indique aux étudiants que si ils veulent continuer ils peuvent nous envoyer des questions par mail !