Catalyst

AlexCTF est un CTF en ligne proposé par le MSP TechClub qui s’est déroulé en février 2017. Catalyst est la 3ième épreuve de la catégorie Reverse.

Sommaire

  • Reconnaissance
  • Faire sauter la restriction
  • Trouver le username
  • Trouver password

Reconnaissance

Dans un premier temps on remarque, avec la commande file, que le fichier est un binaire compilé en 64 bits pour linux.

$ file catalyst
catalyst: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3c4646c45da147f57cfa3fe0b9f1022d84fbe85f, stripped

On observe un chargement presque interminable

$ ./catalyst

Mais que fait ce chargement ?

C’est parti désassemblons le binaire ;)

$ gdb catalyst
gdb-peda$ disas main
No symbol table is loaded.  Use the "file" command.

Pas de symbole, méthode B trouver l’entrypoint :

gdb-peda$ info file
Symbols from "catalyst".
Local exec file:
`catalyst`, file type elf64-x86-64.
Entry point: 0x400780
[...]
0x0000000000400570 - 0x0000000000400690 is .rela.plt
0x0000000000400690 - 0x00000000004006a7 is .init
0x00000000004006b0 - 0x0000000000400780 is .plt
0x0000000000400780 - 0x0000000000401032 is .text
0x0000000000401034 - 0x000000000040103d is .fini
[...]

Maintenant on peut désassembler de l’entrypoint jusqu’à la fin du code, soit de 0x400780 à 0x401032.

gdb-peda$ disas 0x400780,0x401032
Dump of assembler code from 0x400780 to 0x401032:
xor ebp,ebp
mov  r9,rdx
pop  rsi
mov  rdx,rsp
and  rsp,0xfffffffffffffff0
push rax
push rsp
mov  r8,0x401030
mov  rcx,0x400fc0
mov  rdi,0x400d93
call QWORD PTR [rip+0x201846]        # 0x601ff0
...

Le programme fait un premier call, c’est l’appel à libc_start_main comme dans tout programme compilé avec gcc. En 64 bits les paramètres sont passés par les registres. Le premier paramètre de cette fonction est l’adresse de la fonction main, placé dans le registre EDI.

gdb-peda$ disas 0x400d93,0x401032
Dump of assembler code from 0x400d93 to 0x401032:
push   rbp
mov    rbp,rsp
sub    rsp,0x20
mov    edi,0x3e8
call   0x400720 <malloc@plt>
mov    QWORD PTR [rbp-0x10],rax
mov    edi,0x3e8
call   0x400720 <malloc@plt>
mov    QWORD PTR [rbp-0x18\],rax
mov    edi,0x0
call   0x400710 <time@plt>
mov    edi,eax
call   0x400700 <srand@plt>
mov    edi,0x401088
[...]
mov    edi,0x401890
call   0x4006d0 <puts@plt>
mov    edi,0x4018b0
mov    eax,0x0
call   0x4006f0 <printf@plt>

On remarque 2 appel à malloc qui alloue chacun 1000 octets, les adresses sont respectivement stockée à [ebp-0x10] et [ebp-0x18].

Ensuite le programme initialise le générateur de nombre aléatoire (srand) avec le temps actuel (time).

La fonction puts est appelée plusieurs fois pour dessiner “ALEXCTF” en couleur, puis printf affiche la chaine situé à 0x4018b0, “Loading”.

         call   0x400730 <fflush@plt>
         mov    DWORD PTR [rbp-0x4],0x0
---------jmp    0x400ea5
|   ---->call   0x400770 <rand@plt>
|  |     mov    esi,eax
|  |     mov    edx,DWORD PTR [rbp-0x4]
|  |     mov    eax,edx
|  |     add    eax,eax
|  |     add    eax,edx
|  |     lea    ecx,[rax+0x1]
|  |     mov    eax,esi
|  |     cdq
|  |     idiv   ecx
|  |     mov    eax,edx
|  |     mov    edi,eax
|  |     call   0x400760 <sleep@plt>
|  |     mov    edi,0x2e
|  |     call   0x4006c0 <putchar@plt>
|  |     mov    rax,QWORD PTR [rip+0x20122f]        # 0x6020c8 <stdout>
|  |     mov    rdi,rax
|  |     call   0x400730 <fflush@plt>
|  |     add    DWORD PTR [rbp-0x4],0x1
---|---> cmp    DWORD PTR [rbp-0x4],0x1d
   |---- jle    0x400e67

Voici ci-dessus la boucle de chargement. A chaque tour de boucle le programme tire un nombre aléatoire qu’il divise par une certaine valeur. Le résultat est passé à la fonction sleep et un point (0x2e) est affiché à chaque tour de boucle. Les opérations sont aux moins répétées 29 fois (0x1d). Le programme nous ralentis dans notre travail.

Faire sauter la restriction

Nous allons donc “patcher” l’instruction cmp DWORD[rbp-0x4],0x1d par cmp DWORD[rbp-0x4],0x00 afin de diminuer l’attente.

Ouvrons le binaire avec hexedit et changeons les octets de l’instruction à l’adresse 0x400ea5

$ objdump -h catalyst

catalyst:     format de fichier elf64-x86-64

Sections :
Idx Nom           Taille    VMA               LMA               Fich off  Algn
...
 12 .text         000008b2  0000000000400780  0000000000400780  00000780  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

L’offset de l’instruction dans le fichier se calcul de la manière suivante : 0x780 + (0x400ea5 - 0x400780) = 0xea5 Et voici les octets qui compose l’instruction, modifier le 1D en 00.

Trouver le username

Quelques pas plus loin dans le code,

mov    edi,0x4018b8 ; "Username: "
mov    eax,0x0
call   0x4006f0 printf
mov    rax,QWORD PTR [rbp-0x10]
mov    rsi,rax
mov    edi,0x4018c3 ; "%s"
mov    eax,0x0
call   0x400740 __isoc99_scanf
mov    edi,0x4018c6 ; "Password: "
mov    eax,0x0
call   0x4006f0 printf
mov    rax,QWORD PTR [rbp-0x18]
mov    rsi,rax
mov    edi,0x4018c3 ; "%s"
mov    eax,0x0
call   0x400740 __isoc99_scanf
mov    edi,0x4018d1 ; "Loggin in"
mov    eax,0x0
call   0x4006f0 printf
[...]

Le code ci-dessus gère la saisie du nom d’utilisateur et celle du mot de passe. On retrouve nos deux buffers alloués précédemment [rbp-0x10] et [rbp-0x18] qui sont en paramètre des appels à scanf. [rbp-0x10] fait référence au buffer qui contiendras le nom d’utilisateur et [rbp-0x18] au mot de passe. Ensuite on tombe sur la même boucle de chargement qu’il faudra patcher à nouveau.

Le plus intéressant se trouve à la fin de la fonction main:

mov    rax,QWORD PTR [rbp-0x10]
mov    rdi,rax
call   0x400c9a
mov    rax,QWORD PTR [rbp-0x10]
mov    rdi,rax
call   0x400cdd
mov    rax,QWORD PTR [rbp-0x10]
mov    rdi,rax
call   0x4008f7
mov    rdx,QWORD PTR [rbp-0x18]
mov    rax,QWORD PTR [rbp-0x10]
mov    rsi,rdx
mov    rdi,rax
call   0x400977
mov    rdx,QWORD PTR [rbp-0x18]
mov    rax,QWORD PTR [rbp-0x10]
mov    rsi,rdx
mov    rdi,rax
call   0x400876
mov    eax,0x0
leave  
ret    

De nombreux appels prennent en paramètre le mot de passe, le nom d’utilisateur ou les deux. La première fonction (0x400c9a) semble contrôler la taille mais rien d’intéressant concernant le nom d’utilisateur. Cependant dans la fonction suivante (0x400cdd), les instructions cmp nous mettent la puce à l’oreille ;).

push   rbp
mov    rbp,rsp
sub    rsp,0x30
mov    QWORD PTR [rbp-0x28],rdi ; username
mov    rax,QWORD PTR [rbp-0x28] 
mov    QWORD PTR [rbp-0x8],rax  
mov    rax,QWORD PTR [rbp-0x8]  ; username
mov    eax,DWORD PTR [rax]      ; username[0:4]
mov    eax,eax
mov    QWORD PTR [rbp-0x10],rax ; x = username[0:4] 
mov    rax,QWORD PTR [rbp-0x8]  ; username
add    rax,0x4                  ; username + 4 
mov    eax,DWORD PTR [rax]      ; username[4:8]
mov    eax,eax
mov    QWORD PTR [rbp-0x18],rax ; y = username[4:8]
mov    rax,QWORD PTR [rbp-0x8]  ; username
add    rax,0x8                  ; username + 8
mov    eax,DWORD PTR [rax]      ; username[8:12]
mov    eax,eax
mov    QWORD PTR [rbp-0x20],rax ; z = username[8:12]
mov    rax,QWORD PTR [rbp-0x10] ; x
sub    rax,QWORD PTR [rbp-0x18] ; x - y 
mov    rdx,rax                  ; (x - y)
mov    rax,QWORD PTR [rbp-0x20] ; z
add    rax,rdx                  ; z + (x - y)
cmp    rax,0x5c664b56           ; z + (x - y) == 0x5c664b56
jne    0x400d7c <dead>
mov    rdx,QWORD PTR [rbp-0x10] ; x
mov    rax,QWORD PTR [rbp-0x20] ; z
add    rdx,rax                  ; x + z
mov    rax,rdx                  ; (x + z)
add    rax,rax                  ; (x + z) * 2
add    rdx,rax                  ; (x + z) * 3
mov    rax,QWORD PTR [rbp-0x18] ; y
add    rdx,rax                  ; (x + z) * 3 + y
movabs rax,0x2e700c7b2          
cmp    rdx,rax                  ; (x + z) * 3 + y == 0x2e700c7b2
jne    0x400d7c <dead>               
mov    rax,QWORD PTR [rbp-0x18] ; y
imul   rax,QWORD PTR [rbp-0x20] ; y * z
mov    rdx,rax                  
movabs rax,0x32ac30689a6ad314   
cmp    rdx,rax                  ; y * z == 0x32ac30689a6ad314
je     0x400d90 <quit>
dead:
mov    edi,0x401069             ; "invalid username or password"
call   0x4006d0 <puts@plt>
mov    edi,0x0
call   0x400750 <exit@plt>
quit:
nop
leave  
ret 

Après analyse on se retrouve avec 3 équations à 3 inconnue à résoudre, z + (x - y) = 1550207830 (0x5c664b56) (x + z) * 3 + y = 12465522610 (0x2e700c7b2) y * z = 3651346623716053780 (0x32ac30689a6ad314) Un petit tour sur dcode et on trouve x=1635017059 —> 0x61746163 —> ‘atac’ y=1953724780 —> ‘tsyl’ z=1868915551 —> ‘oec_’ Soit en remettant dans le bon ordre : catalyst_ceo

Trouver le mot de passe

Maintenant on peut s’attaquer à la fonction qui gère le check du mot de passe (0x400977), celle-ci commence par une boucle qui test si les caractères saisis sont bien compris dans certains intervalles, mais le plus intéressant vient après :

push   rbp
mov    rbp,rsp
push   rbx
sub    rsp,0x38
mov    QWORD PTR [rbp-0x38],rdi ; nom d'utilisateur
mov    QWORD PTR [rbp-0x40],rsi ; mot de passe
mov    rax,QWORD PTR [rbp-0x38] 
mov    QWORD PTR [rbp-0x20],rax ; nom d'utilisateur
mov    rax,QWORD PTR [rbp-0x40] 
mov    QWORD PTR [rbp-0x28],rax ; mot de passe
mov    DWORD PTR [rbp-0x14],0x0
[... code de la boucle ...]
mov    rax,QWORD PTR [rbp-0x20] ; username
mov    edx,DWORD PTR [rax]      ; username[0:4]
mov    rax,QWORD PTR [rbp-0x20] ; username
add    rax,0x4                  ; username + 4
mov    eax,DWORD PTR [rax]      ; username[4:8]
add    edx,eax                  ; username[0:4] + username[4:8]
mov    rax,QWORD PTR [rbp-0x20] ; username
add    rax,0x8                  ; username + 8
mov    eax,DWORD PTR [rax]      ; username[8:12]
add    eax,edx                  ; username[0:4] + username[4:8] + username[8:12]
mov    edi,eax                 
call   0x400700 srand    ; srand(username[0:4] + username[4:8] + username[8:12])
mov    rax,QWORD PTR [rbp-0x28]       ; password
mov    ebx,DWORD PTR [rax]            ; password[0:4]
call   0x400770 rand  ; rand()
sub    ebx,eax                        ; password[0:4] - rand()
mov    eax,ebx                        
cmp    eax,0x55eb052a                 ; password[0:4] - rand() == 0x55eb052a
je     0x400a9b ----------Good------
mov    edi,0x401069                |
call   0x4006d0 puts               |
mov    edi,0x0                     |
call   0x400750 exit               |
mov    rax,QWORD PTR [rbp-0x28]    |
add    rax,0x4       <-------------  ; password + 4
mov    ebx,DWORD PTR [rax]           ; password[4:8]
call   0x400770  rand                ; rand()
sub    ebx,eax                        ; password[4:8] - rand()
mov    eax,ebx
cmp    eax,0xef76c39                  ; password[4:8] - rand() == 0xef76c39
je     0x400ac9 ----------Good------
mov    edi,0x401069                |
call   0x4006d0 puts               |
mov    edi,0x0                     |
call   0x400750 exit               |
mov    rax,QWORD PTR [rbp-0x28]    |
add    rax,0x8     <----------------  ; password + 8
[...]

La fonction srand va initialiser le générateur de nombre aléatoire avec la graine : x + y + z (valeurs trouvées précédemment), ensuite le programme tire un nombre aléatoire le soustrait avec une partie du mot de passe et compare le résultat avec 0x55eb052a dans le premier cas, 0xef76c39 dans le deuxième etc … Mais comme srand est toujours initialiser avec la même, les nombres tirés par rand sont toujours les mêmes : Le petit programme suivant va nous permettre de trouver la séquence de nombre et en conséquence le mot de passe.

#include 
#include 

int main(int argc,char** argv)
{
	srand((unsigned int)(1953724780 + 1635017059 + 1868915551));
	unsigned int r = rand();
	r+= 0x55eb052a;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0xef76c39;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0xcc1e2d64;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0xc7b6c6f5;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0x26941bfa;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0x260cf0f3;
	printf("%04.4x\n",r);
	
	r = rand();
	r+= 0x10d4caef;
	printf("%04.4x\n",r);

	r = rand();
	r+= 0xc666e824;
	printf("%04.4x\n",r);
	
	r=rand();
	r+= 0xfc89459c;
	printf("%04.4x\n",r);
	r=rand();
	r+= 0x2413073a;
	printf("%04.4x\n",r);

	return 0;
}

Il ne reste plus qu’a convertir les résultats :

56534c73 VSLs
76345170 v4Qp
4763334b Gc3K
38577957 8WyW
5a694136 ZiA6
77676768 wggh
6a42484c jBHL
4339786d C9xm
56707352 VpsR
6a676747 jggG

Ce qui nous donne sLSVpQ4vK3cGWyW86AiZhggwLHBjmx9CRspVGggj :

Et paf un flag :D