Le challenge Logmein est une épreuve proposée par l’équipe RIT Competitive Cybersecurity Club (RC3) lors du CTF du 20 Novembre 2016.
Logmein est un binaire linux à reverse, commençons ;)
Sommaire
- Reconnaissance du binaire
- Désassemblage
- Récupération du flag
Reconnaissance du binaire
La commande file va nous permettre de reconnaître le type de fichier
$ file logmein
logmein: 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]=c8f7fb137d9be24a19eb4f10efc29f7a421578a7, stripped
Nous sommes en face d’un exécutable 64 bits pour Linux, essayons de l’exécuter ;)
$ ./logmein
Welcome to the RC3 secure password guesser.
To continue, you must enter the correct password.
Enter your guess: password
Incorrect password!
Le but du challenge est de retrouver le mot de passe pour s’authentifier. Nous allons utiliser gdb pour examiner le binaire.
$ gdb logmein
Essayons de désassembler la fonction main du programme.
Désassemblage
(gdb) disas main
No symbol table is loaded. Use the "file" command
Pas de chance le binaire n’a pas été compilé avec les symboles, essayons
(gdb) info file
file type elf64-x86-64.
Entry point: 0x400530
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x00000000004002b4 is .gnu.hash
0x00000000004002b8 - 0x0000000000400360 is .dynsym
0x0000000000400360 - 0x00000000004003c4 is .dynstr
0x00000000004003c4 - 0x00000000004003d2 is .gnu.version
0x00000000004003d8 - 0x0000000000400408 is .gnu.version_r
0x0000000000400408 - 0x0000000000400420 is .rela.dyn
0x0000000000400420 - 0x0000000000400498 is .rela.plt
0x0000000000400498 - 0x00000000004004b2 is .init
0x00000000004004c0 - 0x0000000000400520 is .plt
0x0000000000400520 - 0x0000000000400528 is .plt.got
0x0000000000400530 - 0x0000000000400892 is .text
0x0000000000400894 - 0x000000000040089d is .fini
0x00000000004008a0 - 0x0000000000400993 is .rodata
0x0000000000400994 - 0x00000000004009d8 is .eh_frame_hdr
0x00000000004009d8 - 0x0000000000400b0c is .eh_frame
0x0000000000600e10 - 0x0000000000600e18 is .init_array
0x0000000000600e18 - 0x0000000000600e20 is .fini_array
0x0000000000600e20 - 0x0000000000600e28 is .jcr
0x0000000000600e28 - 0x0000000000600ff8 is .dynamic
0x0000000000600ff8 - 0x0000000000601000 is .got
0x0000000000601000 - 0x0000000000601040 is .got.plt
0x0000000000601040 - 0x0000000000601050 is .data
0x0000000000601050 - 0x0000000000601058 is .bss
Cette commande nous permet d’afficher le point d’entrée et les différentes sections du binaire. La section .text contient le code du programme.
La commande disas nous permet de désassembler le code machine entre deux adresses.
(gdb) disas 0x400530,0x400892
xor ebp,ebp
mov r9,rdx
pop rsi
mov rdx,rsp
and rsp,0xfffffffffffffff0
push rax
push rsp
mov r8,0x400890
mov rcx,0x400820
mov rdi,0x400630
call 0x4004f0 <__libc_start_main@plt>
On peut remarquer un appel à la fonction __libc_start_main dont le prototype est décrit ci-dessous :
int __libc_start_main(
int *(main) (int, char * *, char * *),
int argc,
char * * ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (* stack_end)
);
Cette fonction permet d’initialiser le processus et lance la fonction main avec les bons arguments. En assembleur 64 bits les arguments de fonction sont transmis par les registres. RDI contiendra le premier argument, RSI le deuxième, ensuite RDX, RCX, R8, R9, XMM0–7. A partir de cela on peut trouver l’adresse de la fonction main 0x400630 transmis par RDI.
(gdb) disas 0x400630,0x400892
push rbp
mov rbp,rsp
sub rsp,0x90
movabs rdi,0x4008d8
mov DWORD PTR [rbp-0x4],0x0
mov rax,QWORD PTR ds:0x4008b0 ; charge dans rax le QWORD pointé par ds:0x4008b0
mov QWORD PTR [rbp-0x20],rax ; place à l'adresse rbp-0x20 le contenu rax
mov rax,QWORD PTR ds:0x4008b8 ; 8 octets plus loin
mov QWORD PTR [rbp-0x18],rax ; 8 octets plus loin
mov cx,WORD PTR ds:0x4008c0
mov WORD PTR [rbp-0x10],cx
mov rax,QWORD PTR ds:0x4008d0
mov QWORD PTR [rbp-0x28],rax
mov DWORD PTR [rbp-0x2c],0x7
mov al,0x0
call 0x4004e0 <printf@plt>
Examinons les différentes zones mémoires,
(gdb) x/s 0x4008d0
0x4008d0: "harambe"
(gdb) x/s 0x4008c0
0x4008c0: "6"
(gdb) x/s 0x4008b8
0x4008b8: "L*.?+6/46"
(gdb) x/s 0x4008b0
0x4008b0: ":\"AL_RT^L*.?+6/46"
(gdb) x/s 0x4008d8
0x4008d8: "Welcome to the RC3 secure password guesser.\n"
Gardons à l’esprit que nous avons une chaîne certainement chiffrée en 0x4008b0 et une certainement la clé de chiffrement en 0x4008d0. De plus la taille de la clé (7) se trouve en rbp-0x2c. En continuant un peu plus loin nous obtenons
movabs rdi,0x40094b ; arg1 de scanf "%32s"
lea rsi,[rbp-0x50] ; arg2 de scanf soit l'adresse du buffer qui va réceptionner ce que l'on entre , buff
mov DWORD PTR [rbp-0x64],eax
mov al,0x0
call 0x400500 <__isoc99_scanf@plt>
lea rdi,[rbp-0x20] ; mais dit donc je connais cette zone mémoire ;) (voir au dessus mov QWORD PTR [rbp-0x20],rax), c'est l'adresse de la chaîne chiffrée
lea rsi,[rbp-0x50] ; buff
mov QWORD PTR [rbp-0x70],rdi
mov rdi,rsi ; arg1 de strlen, buff
mov DWORD PTR [rbp-0x74],eax
call 0x4004d0 <strlen@plt> ; strlen(buff)
mov rdi,QWORD PTR [rbp-0x70]
mov QWORD PTR [rbp-0x80],rax
call 0x4004d0 <strlen@plt> ; strlen(cipher)
mov rsi,QWORD PTR [rbp-0x80]
cmp rsi,rax
jae 0x400700 ; strlen(buff) >= strlen(cipher)
call 0x4007c0 ; bad password et exit
Après cette petite analyse on remarque que le programme compare la chaîne entrée avec la chaîne chiffrée ce qui nous donne une information sur la taille du mot de passe, celui-ci fait 17 caractères (taille de la chaîne chiffrée) ou plus (jae jump if above or equal).
En continuant un peu plus loin on tombe sur une boucle à partir de l’adresse 0x400707, et là ça se corse un petit peu.
mov DWORD PTR [rbp-0x54],0x0 ; i = 0
condition_boucle:
lea rdi,[rbp-0x50] ; buff
movsxd rax,DWORD PTR [rbp-0x54] ; chargement de i dans rax
mov QWORD PTR [rbp-0x88],rax ; compteur1 = i = 0
call 0x4004d0 <strlen@plt> ; strlen(buff)
mov rdi,QWORD PTR [rbp-0x88] ; chargement du compteur dans rdi
cmp rdi,rax
jae 0x4007ac ; si compteur1 >= strlen(buff) call 0x4007f0 = printf("bad password") et exit
lea rdi,[rbp-0x20] ; adresse de la chaine chiffrée
movsxd rax,DWORD PTR [rbp-0x54] ; chargement de i dans rax
mov QWORD PTR [rbp-0x90],rax ; compteur2 = i = 0
call 0x4004d0 <strlen@plt> ; strlen(cipher)
mov rdi,QWORD PTR [rbp-0x90] ; compteur2
cmp rdi,rax
jb 0x400754 ; si compteur2 < strlen(cipher)
call 0x4007c0 ; = bad password et exit
interne:
movsxd rax,DWORD PTR [rbp-0x54] ; charger compteur1 dans rax
mov cl,BYTE PTR [rbp+rax*1-0x20] ; cipher[compteur1]
mov BYTE PTR [rbp-0x55],cl ; caractere_1 = cipher[compteur1]
mov eax,DWORD PTR [rbp-0x54] ; chargement de i dans eax
cdq
idiv DWORD PTR [rbp-0x2c] ; eax % [rbp-0x2c] % 7 eax modulo 7 (taille de "harambe")
movsxd rsi,edx
mov cl,BYTE PTR [rbp+rsi*1-0x28] ; rsi est utilisé comme index, à [rbp-0x28] nous avons "harambe"
mov BYTE PTR [rbp-0x56],cl ; caractere_2 = key[compteur]
movsx edx,BYTE PTR [rbp-0x55] ; caractere_1
movsx edi,BYTE PTR [rbp-0x56] ; caractere_2
xor edx,edi ; caractere_1 XOR caractere_2
mov cl,dl
mov BYTE PTR [rbp-0x57],cl ; c = caractere_1 XOR caractere_2
movsxd rsi,DWORD PTR [rbp-0x54] ; chargement de i dans rsi
movsx edx,BYTE PTR [rbp+rsi*1-0x50] ; rbp-0x50 chaine entrée (buff) , mychar = buff[i]
movsx edi,BYTE PTR [rbp-0x57] ; c
cmp edx,edi
je 0x400799 ; si c == mycar
call 0x4007c0 ; bad password and et exit
continue:
jmp 0x40079e
mov eax,DWORD PTR [rbp-0x54] ; i
add eax,0x1 ; i+1
mov DWORD PTR [rbp-0x54],eax ; i = i + 1
jmp 0x400707
En conclusion cette boucle déchiffre les caractères de la chaîne chiffrée et les compare avec ceux qui composent notre chaîne.
Récupération du flag
Maintenant réalisons le script python qui va nous faire le boulot :
key="harambe"
cipher=":\"AL_RT^L*.?+6/46"
decode=""
for i in range(0,len(cipher)):
decode+= chr( ord(cipher[i]) ^ ord(key[i%len(key)]) )
print(decode)
Et voilà on peut valider le challenge avec le flag trouvé ;P
$ ./logmein
Welcome to the RC3 secure password guesser.
To continue, you must enter the correct password.
Enter your guess: RC3-2016-XORISGUD
You entered the correct password!
Great job!