Annonce

Réduire
Aucune annonce.

Reversing tutorials - level 3 - Analyse (semi-)statique de binaire

Réduire
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Tutoriel Reversing tutorials - level 3 - Analyse (semi-)statique de binaire

    Avant propos

    A consulter :

    Pour ce tutoriel, un peu de pratique avant tout. On ne va pas découvrir de nouveaux aspects du reversing mais utiliser ce que l'on connaît déjà (plus ou moins) et on va s'intéresser a un aspect particulier : l'analyse statique de binaire.

    Avant toute chose, je vous conseille de mettre en place une VM quand vous faites du reversing, il est pratique par le biais de snapshots de maintenir un système saint pour faire tous les tests que l'on souhaite.

    Analyse

    Le contexte :
    Un binaire nous est donné, celui-ci est à reverser

    Dans un premier temps, on va procéder à une identification du fichier.

    Code:
    $ ll
    total 8.0K
    -rwxr-xr-x 1 death death 7.4K Aug  7 19:36 reverseme*
    
    $ file reverseme
    reverseme: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=cd634133eb48748f8ce861ad28f3be5591e31ba7, not stripped
    C'est je pense la première chose à faire. Vérifier de quel type de fichier il s'agit.
    Ici on s'attaque un à binaire classique. On remarque que celui-ci n'est pas "stripped" donc il dispose encore de ses symboles. Ce qui facilite le debug s'il doit y en avoir.

    Passons à l'analyse. Pour cela on va utiliser gdb, même si objdump est un bon désassembleur, gdb ajoute des éléments en plus qui facilitent légèrement le travail. On utilisera gdb non pas pour faire du debug mais juste pour désassembler et retrouver certains éléments plus simples à recueillir lorsque le binaire est en mémoire.

    Code:
    $ gdb reverseme
    Reading symbols from /home/death/tuto/binary/reverseme...(no debugging symbols found)...done.
    (gdb) disass main
    Dump of assembler code for function main:
        0x00000000004006c8 <+0>:    push rbp
        0x00000000004006c9 <+1>:    mov rbp,rsp
        0x00000000004006cc <+4>:    sub rsp,0x10
        0x00000000004006d0 <+8>:    mov DWORD PTR [rbp-0x4],edi
        0x00000000004006d3 <+11>:   mov QWORD PTR [rbp-0x10],rsi
        0x00000000004006d7 <+15>:   cmp DWORD PTR [rbp-0x4],0x2
        0x00000000004006db <+19>:   jne    0x400720 <main+88>
        0x00000000004006dd <+21>:   mov rax,QWORD PTR [rbp-0x10]
        0x00000000004006e1 <+25>:   add rax,0x8
        0x00000000004006e5 <+29>:   mov rax,QWORD PTR [rax]
        0x00000000004006e8 <+32>:   mov rdi,rax
        0x00000000004006eb <+35>:   call    0x40061c <check_password>
        0x00000000004006f0 <+40>:   test eax,eax
        0x00000000004006f2 <+42>:   je    0x400700 <main+56>
        0x00000000004006f4 <+44>:   mov edi,0x400800
        0x00000000004006f9 <+49>:   call    0x4004d0 <[email protected]>
        0x00000000004006fe <+54>:   jmp    0x400740 <main+120>
        0x0000000000400700 <+56>:   mov rax,QWORD PTR [rip+0x200481] #    0x600b88 <[email protected]@GLIBC_2.2.5>
        0x0000000000400707 <+63>:   mov rcx,rax
        0x000000000040070a <+66>:   mov edx,0xc
        0x000000000040070f <+71>:   mov esi,0x1
        0x0000000000400714 <+76>:   mov edi,0x40080c
        0x0000000000400719 <+81>:   call    0x400520 <[email protected]>
        0x000000000040071e <+86>:   jmp    0x400740 <main+120>
        0x0000000000400720 <+88>:   mov rax,QWORD PTR [rbp-0x10]
        0x0000000000400724 <+92>:   mov rdx,QWORD PTR [rax]
        0x0000000000400727 <+95>:   mov rax,QWORD PTR [rip+0x20045a] #    0x600b88 <[email protected]@GLIBC_2.2.5>
        0x000000000040072e <+102>:  mov esi,0x400819
        0x0000000000400733 <+107>:  mov rdi,rax
        0x0000000000400736 <+110>:  mov eax,0x0
        0x000000000040073b <+115>:  call    0x400500 <[email protected]>
        0x0000000000400740 <+120>:  mov eax,0x0
        0x0000000000400745 <+125>:  leave
        0x0000000000400746 <+126>:  ret
     End of assembler dump.
      (gdb)
    Comme vous le constatez, gdb nous ajoute des décalages <+N> par rapport au début de la fonction ce que ne fait pas objdump.

    Première chose, un bref coup d'œil sur les appels de fonction à partir de la fonction « main ». Étant donné que le binaire contient toujours les symboles, on peut commencer le reversing à partir de la fonction main.
    On y trouve des appels aux fonctions de la libc fprintf, puts, strlen ... celles-ci sont facilement repérables car on retrouve le symbole @plt en fin de leur nom. On y voit aussi un appel à la fonction « check_password » qui est locale cette fois.

    A priori main et « check_password » sont les 2 fonctions dont nous devrions nous intéresser.

    C'est parti, étape 1 : commenter le code pour éviter d'avoir à réinterpréter dans notre petit cerveau chaque ligne à chaque lecture du code.

    Pour la fonction main :

    Code:
        0x00000000004006c8 <+0>:    push	rbp				# prologue
        0x00000000004006c9 <+1>:    mov	rbp,rsp				# ...
        0x00000000004006cc <+4>:    sub	rsp,0x10			# réservation d'un espace sur la pile
        0x00000000004006d0 <+8>:    mov	DWORD PTR [rbp-0x4],edi		# on place argc sur la pile
        0x00000000004006d3 <+11>:   mov	QWORD PTR [rbp-0x10],rsi	# on place argv sur la pile
        0x00000000004006d7 <+15>:   cmp	DWORD PTR [rbp-0x4],0x2		# on compare argv avec la valeur 2
        0x00000000004006db <+19>:   jne	0x400720 <main+88>		# jump si argc diffèrent de 2
        0x00000000004006dd <+21>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x00000000004006e1 <+25>:   add	rax,0x8   	  		# on ajoute 8 à rax (décalage d'une adresse donc d'une case mémoire comme argv est un char**)
        0x00000000004006e5 <+29>:   mov	rax,QWORD PTR [rax]		# déréférencement de rax pour récupérer l'argument passe au binaire
        0x00000000004006e8 <+32>:   mov	rdi,rax   	  		# on place l'argument dans rdi
        0x00000000004006eb <+35>:   call	0x40061c <check_password>	# on appelle la fonction check_password
        0x00000000004006f0 <+40>:   test	eax,eax				# on test la valeur de retour de la fonction
        0x00000000004006f2 <+42>:   je	0x400700 <main+56>		# jump si elle est égale a zéro
        0x00000000004006f4 <+44>:   mov	edi,0x400800			# on place 0x400800 dans edi
        0x00000000004006f9 <+49>:   call	0x4004d0 <[email protected]>		# appel de puts avec 0x400800 en paramètre
        0x00000000004006fe <+54>:   jmp	0x400740 <main+120>		# on jump
        0x0000000000400700 <+56>:   mov	rax,QWORD PTR [rip+0x200481]	# on place stderr (fd d'erreur défini par la libc) dans rax
        0x0000000000400707 <+63>:   mov	rcx,rax   	  		# on place rax dans rcx
        0x000000000040070a <+66>:   mov	edx,0xc				# on place la valeur 12 dans edx
        0x000000000040070f <+71>:   mov	esi,0x1				# on place la valeur 1 dans esi
        0x0000000000400714 <+76>:   mov	edi,0x40080c			# on place 0x40080c dans edi
        0x0000000000400719 <+81>:   call	0x400520 <[email protected]>		# appel de fwrite
        0x000000000040071e <+86>:   jmp	0x400740 <main+120>		# on jump
        0x0000000000400720 <+88>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x0000000000400724 <+92>:   mov	rdx,QWORD PTR [rax]		# on déréférence puis place dans rdx, on obtient donc le chemin de lancement du binaire
        0x0000000000400727 <+95>:   mov	rax,QWORD PTR [rip+0x20045a]	# on place stderr dans rax
        0x000000000040072e <+102>:  mov	esi,0x400819  			# on place 0x400819 dans esi
        0x0000000000400733 <+107>:  mov	rdi,rax				# on place rax dans rdi
        0x0000000000400736 <+110>:  mov	eax,0x0				# on mets la valeur 0 dans eax
        0x000000000040073b <+115>:  call    0x400500 <[email protected]>		# appel de fprintf
        0x0000000000400740 <+120>:  mov	eax,0x0	 			# on mets 0 dans eax (valeur de retour du programme)
        0x0000000000400745 <+125>:  leave					# epilogue
        0x0000000000400746 <+126>:  ret					# ...
    Bien quelques réglages s'imposent. Pour y voir plus clair, on va reconstituer un système labélisé. C'est à dire définir des labels pour les sauts et appels relatifs (on en a pas ici).
    Cela va nous aider à y voir plus clair en séparant la fonction par blocs de code. On précisera ainsi où vont les sauts plutôt qu'une adresse.

    Code:
    _main:
        0x00000000004006c8 <+0>:    push	rbp				# prologue
        0x00000000004006c9 <+1>:    mov	rbp,rsp				# ...
    
    _label_1:
        0x00000000004006cc <+4>:    sub	rsp,0x10			# réservation d'un espace sur la pile
        0x00000000004006d0 <+8>:    mov	DWORD PTR [rbp-0x4],edi		# on place argc sur la pile
        0x00000000004006d3 <+11>:   mov	QWORD PTR [rbp-0x10],rsi	# on place argv sur la pile
        0x00000000004006d7 <+15>:   cmp	DWORD PTR [rbp-0x4],0x2		# on compare argv avec la valeur 2
        0x00000000004006db <+19>:   jne	0x400720 <main+88>		# jump si argc différent de 2  -> _label_5
    
    _label_2:
        0x00000000004006dd <+21>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x00000000004006e1 <+25>:   add	rax,0x8   	  		# on ajoute 8 a rax (décalage d'une adresse donc d'une case mémoire comme argv est un char**)
        0x00000000004006e5 <+29>:   mov	rax,QWORD PTR [rax]		# déréférencement de rax pour récupérer l'argument passe au binaire
        0x00000000004006e8 <+32>:   mov	rdi,rax   	  		# on place l'argument dans rdi
        0x00000000004006eb <+35>:   call	0x40061c <check_password>	# on appelle la fonction check_password
        0x00000000004006f0 <+40>:   test	eax,eax				# on test la valeur de retour de la fonction
        0x00000000004006f2 <+42>:   je	0x400700 <main+56>		# jump si elle est égale a zéro -> _label_4
    
    _label_3:
        0x00000000004006f4 <+44>:   mov	edi,0x400800			# on place 0x400800 dans edi
        0x00000000004006f9 <+49>:   call	0x4004d0 <[email protected]>		# appel de puts avec 0x400800 en paramètre
        0x00000000004006fe <+54>:   jmp	0x400740 <main+120>		# on jump -> _label_6
    
    _label_4:
        0x0000000000400700 <+56>:   mov	rax,QWORD PTR [rip+0x200481]	# on place stderr (fd d'erreur défini par la libc) dans rax
        0x0000000000400707 <+63>:   mov	rcx,rax   	  		# on place rax dans rcx
        0x000000000040070a <+66>:   mov	edx,0xc				# on place la valeur 12 dans edx
        0x000000000040070f <+71>:   mov	esi,0x1				# on place la valeur 1 dans esi
        0x0000000000400714 <+76>:   mov	edi,0x40080c			# on place 0x40080c dans edi
        0x0000000000400719 <+81>:   call	0x400520 <[email protected]>		# appel de fwrite
        0x000000000040071e <+86>:   jmp	0x400740 <main+120>		# on jump -> _label_6
    
    _label_5:
        0x0000000000400720 <+88>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x0000000000400724 <+92>:   mov	rdx,QWORD PTR [rax]		# on déréférence puis place dans rdx, on obtient donc le chemin de lancement du binaire
        0x0000000000400727 <+95>:   mov	rax,QWORD PTR [rip+0x20045a]	# on place stderr dans rax
        0x000000000040072e <+102>:  mov	esi,0x400819  			# on place 0x400819 dans esi
        0x0000000000400733 <+107>:  mov	rdi,rax				# on place rax dans rdi
        0x0000000000400736 <+110>:  mov	eax,0x0				# on mets la valeur 0 dans eax
        0x000000000040073b <+115>:  call    0x400500 <[email protected]>		# appel de fprintf
    
    _label_6:
        0x0000000000400740 <+120>:  mov	eax,0x0	 			# on mets 0 dans eax (valeur de retour du programme)
    
        0x0000000000400745 <+125>:  leave					# epilogue
        0x0000000000400746 <+126>:  ret					# ...
    Un peu mieux ?
    Ensuite, on va chercher les chaines de caractères pour les ajouter en commentaire lors de leur utilisation.
    Comme le binaire est en mémoire (car lance avec gdb) on va pouvoir afficher ces chaines directement depuis gdb.

    Code:
    (gdb) x/s 0x400800
    0x400800: "Well done !"
    (gdb) x/s 0x40080c
    0x40080c: "Try again !\n"
    (gdb) x/s 0x400819
    0x400819: "USAGE %s <password>\n"
    (gdb)
    On peut les ajouter en commentaire.

    Code:
    _main:
        0x00000000004006c8 <+0>:    push	rbp				# prologue
        0x00000000004006c9 <+1>:    mov	rbp,rsp				# ...
    
    _label_1:
        0x00000000004006cc <+4>:    sub	rsp,0x10			# réservation d'un espace sur la pile
        0x00000000004006d0 <+8>:    mov	DWORD PTR [rbp-0x4],edi		# on place argc sur la pile
        0x00000000004006d3 <+11>:   mov	QWORD PTR [rbp-0x10],rsi	# on place argv sur la pile
        0x00000000004006d7 <+15>:   cmp	DWORD PTR [rbp-0x4],0x2		# on compare argv avec la valeur 2
        0x00000000004006db <+19>:   jne	0x400720 <main+88>		# jump si argc différent de 2  -> _label_5
    
    _label_2:
        0x00000000004006dd <+21>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x00000000004006e1 <+25>:   add	rax,0x8   	  		# on ajoute 8 a rax (décalage d'une adresse donc d'une case mémoire comme argv est un char**)
        0x00000000004006e5 <+29>:   mov	rax,QWORD PTR [rax]		# déréférencement de rax pour récupérer l'argument passe au binaire
        0x00000000004006e8 <+32>:   mov	rdi,rax   	  		# on place l'argument dans rdi
        0x00000000004006eb <+35>:   call	0x40061c <check_password>	# on appelle la fonction check_password		: check_password(argv[1])
        0x00000000004006f0 <+40>:   test	eax,eax				# on test la valeur de retour de la fonction
        0x00000000004006f2 <+42>:   je	0x400700 <main+56>		# jump si elle est égale a zéro -> _label_4
    
    _label_3:
        0x00000000004006f4 <+44>:   mov	edi,0x400800			# on place 0x400800 dans edi
        0x00000000004006f9 <+49>:   call	0x4004d0 <[email protected]>		# appel de puts avec 0x400800 en parametre	: puts("Well done !\n")
        0x00000000004006fe <+54>:   jmp	0x400740 <main+120>		# on jump -> _label_6
    
    _label_4:
        0x0000000000400700 <+56>:   mov	rax,QWORD PTR [rip+0x200481]	# on place stderr (fd d'erreur défini par la libc) dans rax
        0x0000000000400707 <+63>:   mov	rcx,rax   	  		# on place rax dans rcx
        0x000000000040070a <+66>:   mov	edx,0xc				# on place la valeur 12 dans edx
        0x000000000040070f <+71>:   mov	esi,0x1				# on place la valeur 1 dans esi
        0x0000000000400714 <+76>:   mov	edi,0x40080c			# on place 0x40080c dans edi
        0x0000000000400719 <+81>:   call	0x400520 <[email protected]>		# appel de fwrite				: fwrite("Try again !\n", 1, 12, stderr)
        0x000000000040071e <+86>:   jmp	0x400740 <main+120>		# on jump -> _label_6
    
    _label_5:
        0x0000000000400720 <+88>:   mov	rax,QWORD PTR [rbp-0x10]	# on place argv dans rax
        0x0000000000400724 <+92>:   mov	rdx,QWORD PTR [rax]		# on déréférence puis place dans rdx, on obtient donc le chemin de lancement du binaire
        0x0000000000400727 <+95>:   mov	rax,QWORD PTR [rip+0x20045a]	# on place stderr dans rax
        0x000000000040072e <+102>:  mov	esi,0x400819  			# on place 0x400819 dans esi
        0x0000000000400733 <+107>:  mov	rdi,rax				# on place rax dans rdi
        0x0000000000400736 <+110>:  mov	eax,0x0				# on mets la valeur 0 dans eax
        0x000000000040073b <+115>:  call    0x400500 <[email protected]>		# appel de fprintf				: fprintf(stderr, "USAGE %s <password>\n", argv[0])
    
    _label_6:
        0x0000000000400740 <+120>:  mov	eax,0x0	 			# on mets 0 dans eax (valeur de retour du programme)
    
        0x0000000000400745 <+125>:  leave					# epilogue
        0x0000000000400746 <+126>:  ret					# ...
    Ce n’est pas un peu plus compréhensible à la lecture ?
    Ainsi on peut en déduire que la fonction « check_password » doit retourner une valeur différente de zéro pour nous afficher le message "Well done !".

    Faisons de même pour « check_password » :

    Code:
    _check_password:
        0x000000000040061c <+0>:	push	rbp				# prologue
        0x000000000040061d <+1>:	mov	rbp,rsp				# ...
        0x0000000000400620 <+4>:	push	rbx				# ...
    
    _label_1:
        0x0000000000400621 <+5>:	sub	rsp,0x28			# réservation de mémoire sur la pile
        0x0000000000400625 <+9>:	mov	QWORD PTR [rbp-0x28],rdi	# on place le paramètre sur la pile -> var_1
        0x0000000000400629 <+13>:	mov	QWORD PTR [rbp-0x20],0x4007f4	# on place l'adresse 0x4007f4 sur la pile -> var_2
        0x0000000000400631 <+21>:	mov	DWORD PTR [rbp-0x14],0x0	# on place 0 sur la pile -> var_3
        0x0000000000400638 <+28>:	jmp    	0x400682 <check_password+102>	# jump jump !
    
    _label_2:
        0x000000000040063a <+30>:	mov	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x000000000040063d <+33>:	movsxd 	rdx,eax	      			# on place eax dans rdx soit var_3
        0x0000000000400640 <+36>:	mov 	rax,QWORD PTR [rbp-0x28]	# on place var_1 dans rax
        0x0000000000400644 <+40>:	add 	rax,rdx	      			# ajout var_3 a var_1 -> incrémentation du pointeur de var_3
        0x0000000000400647 <+43>:	movzx 	eax,BYTE PTR [rax]		# déréférencement de rax
        0x000000000040064a <+46>:	mov 	edx,eax	     			# on place eax dans edx
        0x000000000040064c <+48>:	xor 	edx,0x2a			# xor de edx avec 42
        0x000000000040064f <+51>:	mov 	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x0000000000400652 <+54>:	movsxd 	rcx,eax	      			# on place eax dans rcx
        0x0000000000400655 <+57>:	mov 	rax,QWORD PTR [rbp-0x20]	# on place var_2 dans rax
        0x0000000000400659 <+61>:	add 	rax,rcx	      			# ajout de var_3 a var_2
        0x000000000040065c <+64>:	movzx 	eax,BYTE PTR [rax]		# déréférencement de rax
        0x000000000040065f <+67>:	cmp 	dl,al	     			# comparaison de dl et al ( (*(var_1 + var_3) ^ 42) et *(var_2 + var_3))
        0x0000000000400661 <+69>:	jne    	0x400677 <check_password+91>	# jump si different -> _label_4
    
    _label_3:
        0x0000000000400663 <+71>:	mov 	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x0000000000400666 <+74>:	movsxd 	rdx,eax	      			# on place eax dans rdx
        0x0000000000400669 <+77>:	mov 	rax,QWORD PTR [rbp-0x20]	# on place var_2 dans rax
        0x000000000040066d <+81>:	add 	rax,rdx	      			# on ajoute var_3 a var_2
        0x0000000000400670 <+84>:	movzx 	eax,BYTE PTR [rax]		# déréférencement de rax
        0x0000000000400673 <+87>:	test 	al,al	     			# test la valeur
        0x0000000000400675 <+89>:	jne    	0x40067e <check_password+98>	# jump si différent de 0 -> _label_5
    
    _label_4:
        0x0000000000400677 <+91>:	mov 	eax,0x0				# on place 0 dans eax
        0x000000000040067c <+96>:	jmp    	0x4006c1 <check_password+165>	# jump -> _label_7
    
    _label_5:
        0x000000000040067e <+98>:	add 	DWORD PTR [rbp-0x14],0x1	# incrémentation de var_3
        0x0000000000400682 <+102>:	mov 	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x0000000000400685 <+105>:	movsxd 	rbx,eax	      			# on place eax dans rbx
        0x0000000000400688 <+108>:	mov 	rax,QWORD PTR [rbp-0x28]	# on place var_1 dans rax
        0x000000000040068c <+112>:	mov 	rdi,rax	      			# on place rax dans rdi
        0x000000000040068f <+115>:	call    0x4004e0 <[email protected]>		# appel de strlen            : strlen(var_1)
        0x0000000000400694 <+120>:	cmp 	rbx,rax				# on compare la taille retournée a var_3
        0x0000000000400697 <+123>:	jb    	0x40063a <check_password+30>	# jump si inferieur -> _label_2
    
    _label_6:
        0x0000000000400699 <+125>:	mov 	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x000000000040069c <+128>:	movsxd 	rdx,eax				# on place eax dans rdx
        0x000000000040069f <+131>:	mov 	rax,QWORD PTR [rbp-0x28]	# on place var_1 dans rax
        0x00000000004006a3 <+135>:	add 	rax,rdx	      			# ajout de var_3 a var_1
        0x00000000004006a6 <+138>:	movzx 	edx,BYTE PTR [rax]		# déréférencement de rax
        0x00000000004006a9 <+141>:	mov 	eax,DWORD PTR [rbp-0x14]	# on place var_3 dans eax
        0x00000000004006ac <+144>:	movsxd 	rcx,eax	      			# on place eax dans rcx
        0x00000000004006af <+147>:	mov 	rax,QWORD PTR [rbp-0x20]	# on place var_2 dans rax
        0x00000000004006b3 <+151>:	add 	rax,rcx	      			# ajout de var_3 a var_2
        0x00000000004006b6 <+154>:	movzx 	eax,BYTE PTR [rax]		# déréférencement de rax
        0x00000000004006b9 <+157>:	cmp 	dl, al	     			# comparaison de dl et al (*(var_1 + var_3) et *(var_2 + var_3))
        0x00000000004006bb <+159>:	sete 	al				# si égale, alors mets a zéro
        0x00000000004006be <+162>:	movzx 	eax,al				# place al dans eax
    
    _label_7:
        0x00000000004006c1 <+165>:	add 	rsp,0x28			# libère l'espace de la pile
    
    _label_8:
        0x00000000004006c5 <+169>:	pop 	rbx				# epilogue
        0x00000000004006c6 <+170>:	pop 	rbp				# ...
        0x00000000004006c7 <+171>:	ret   					# ...
    Une dernière étape qui parfois est bien utile, la retranscription dans un langage de plus haut niveau (style C). Ainsi on pourra lire plus facilement le code et comprendre ce qu'il fait.

    Pour la fonction « main », on aurait donc quelque chose comme:

    Code:
    int main(int argc, char *argv[])
    {
      if (argc == 2)
        {
          if (check_password(argv[1]))
          	puts("Well done !\n");
          else
    	fwrite("Try again !\n", 1, 12, stderr);
        }
      else
        fprintf(stderr, "USAGE %s <password>\n", argv[0]);
      return (0);
    }
    Et pour « check_password » :

    Code:
    int check_password(char * var_1)
    {
      char * var_2 = "\x7e\x42\x4f\x7a\x4b\x59\x59\x5d\x45\x58\x4e";
      int var_3;
    
      for (var_3 = 0; var_3 < strlen(var_1); var_3++)
        {      
          if ((var_1[var_3] ^ 42) != var_2[var_3] || var_2[var_3] == 0)
    	return (0);
        }
    
      return (var_1[var_3] == var_2[var_3]);
    }
    On a pu déduire var_2 grâce a gdb. En effet, on a vu que cette adresse est incrémentée et qu'elle se finie par la valeur 0. Avec gdb on affiche donc son contenu :

    Code:
    (gdb) x/32x 0x4007f4
    0x4007f4:       0x7a4f427e      0x5d59594b      0x004e5845      0x6c6c6557
    0x400804:       0x6e6f6420      0x00212065      0x20797254      0x69616761
    0x400814:       0x0a21206e      0x41535500      0x25204547      0x703c2073
    Ici le 0 est situé au 12e bit on récupère donc :
    Code:
    0x7a4f427e      0x5d59594b      0x004e5845
    Qui une fois passe en big-endian pour pouvoir l'utiliser dans un code c sous forme de char* :

    Code:
    \x7e\x42\x4f\x7a\x4b\x59\x59\x5d\x45\x58\x4e\x00
    On a tout ce qu'il faut maintenant, il ne reste plus qu'à lire. Ici on voit que notre paramètre subit un xor et est comparé à une chaine de bits.

    Voir : http://en.wikipedia.org/wiki/Exclusive_or

    Lorsque l'on fait un xor sur une valeur, on récupère une nouvelle valeur. Si on xor cette nouvelle valeur, on retrouve alors l'originale.

    Ainsi il nous suffit de faire un xor sur cette chaine pour récupérer le contenu originale et donc trouver ce que le binaire attend pour comparer. Ici la valeur du xor est de 42, on utilisera donc cette même valeur.

    Let's go.

    Code:
    [email protected]:~$ python
    Python 2.7.5+ (default, Jun  2 2013, 13:26:34)
    >>> s = "\x7e\x42\x4f\x7a\x4b\x59\x59\x5d\x45\x58\x4e"
    >>> result = ""
    >>> for i in s:
    ...     result += chr(ord(i) ^ 42)
    ...
    >>> print result
    ThePassword
    >>>
    Bon ce n’était pas si complique si ?

    Essayons quand même pour être certain :
    Code:
    $ ./reverseme ThePassword
    Well done !
    $
    Bon comme vous l'avez constaté c'est assez long et fastidieux comme travail et là on à même pas 20 lignes de code en C alors imaginez un programme entier de plusieurs MB. Ce serait juste impossible. Voilà pourquoi ce type d'analyse est à privilégier pour des portions de code qui nécessitent réellement un besoin de compréhension absolu.

    Souvent on utilisera cette méthode pour reverser un algorithme spécifique pour ensuite pouvoir l'implémenter dans un code tel que des algorithmes de chiffrements ...


    Next time : analyse de binaire mais avec d'autres outils cette fois.

  • #2
    Salut,
    Toujours pas de level 4?

    Commentaire


    • #3
      Bonjour,

      Death était très peu disponible ces temps-ci (voyage en Chine), le level 4 devrait arriver dans quelques temps


      Suivre Hackademics: Twitter, Google+, Facebook.

      Commentaire

      Chargement...
      X