18/07/2010

Write up RMLL crackme level 2

C'est parti pour le level suivant du RMLL.

Première approche :

w4kfu@vm-debian:~/RMLL$ ./mars1 
serial : 1234556065906954904560950654
Bad serial :/

On sort gdb pour voir tout ca :

w4kfu@vm-debian:~/RMLL$ gdb ./mars1 
(gdb) disas main
....
0x0804888c <main+9>:	        movl   $0xd6eddb56,0x1e(%esp)
0x08048894 <main+17>:		movl   $0x8086fb8b,0x22(%esp)
0x0804889c <main+25>:		movl   $0x9e0847d1,0x26(%esp)
0x080488a4 <main+33>:		movl   $0x638ce745,0x2a(%esp)
0x080488ac <main+41>:		movb   $0x0,0x2e(%esp)
...
0x080488f2 <main+111>:	call   0x804842c <ptrace@plt>
...
0x080489cd <main+330>:	call   0x804844c <strlen@plt>
0x080489d2 <main+335>:	cmp    $0x10,%eax
...
0x08048a76 <main+499>:	call   0x8048554 <chiffre>
...
0x08048ac5 <main+578>:	call   0x804840c <memcmp@plt>
....

On retrouve les même symptômes que le premier challenge, à la différence du ptrace et du code complètement offusqué. Si on s'amuse à disas la fonction chiffre c'est pas beau à voir.

On va devoir nettoyer tout ça car on peut y voir des trucs qui vont bien nous ennuyer :

0x0804857e <chiffre+42>:   rdtsc  
0x0804859e <chiffre+74>:   (bad)  
0x080485a0 <chiffre+76>:   leave  
0x080485a3 <chiffre+79>:   sub    %ecx,%eax
0x080485a5 <chiffre+81>:   cmp    $0x3000,%eax
0x080485aa <chiffre+86>:   ja     0x80485aa <chiffre+86>

J'ai donc écrit le code suivant pour enlever toutes ces cochonneries :

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

void look_pattern(int fd);
void replace_pattern(int fd, off_t offset, int nb);

int
main(void)
{
      int fd;

      if ( (fd = open("./copy_mars1", O_RDWR) ) == -1 )
          perror("open()");
      look_pattern(fd);
      close(fd);
      return 0;
}

void
look_pattern(int fd)
{
      off_t offset = 0x554;
      off_t offset_end = 0x87b;
      char pattern[7];

      while ( offset < offset_end)
      {
      	   if (read(fd, pattern, 7) <= 0)
                perror("read()");
          if ( pattern[0] == 0x77 )
               replace_pattern(fd, offset, 2);
	   else if ( pattern[0] == 0xc9 )
	       replace_pattern(fd, offset, 1);
	   else if ( pattern[0] == 0xf1 )
	       replace_pattern(fd, offset, 1);
	   else if (!strncmp(pattern, "\xdb\xe6", 2))
	       replace_pattern(fd, offset, 2);
	   else if (!strncmp(pattern, "\x0f\x31", 2))
	       replace_pattern(fd, offset, 2);
	   else if(!memcmp(pattern, (void*)"\x2b\xc1\x3d\x00", 4))
	       replace_pattern(fd, offset, 9);
	   offset++;
	   lseek(fd, offset, SEEK_SET);
      }
}

void
replace_pattern(int fd, off_t offset, int nb)
{
      int i = 0;

      lseek(fd, offset,SEEK_SET);
      for ( i = 0 ; i < nb ; i++) 
          if ( write(fd, "\x90", 1) == -1)
	      perror("write()");
}

Il faut aussi s'occuper du ptrace, voici le code du wrapper de ptrace :

int ptrace(int a, int b, int c, int d)
{
   return 0;
}

Pour le compiler et l'utiliser au sein de gdb :

>gcc -shared my_ptrace.c -o ptrace.so
(gdb) set environment LD_PRELOAD ./ptrace.so

Mais malheureusement, ça n'a pas suffit, ca m'a nettoyé un peu le disas.. Alors j'ai décidé de m'écrire un script gdb pour tracer TOUT ce que fait la fonction chiffre :

w4kfu@vm-debian:~$ cat .gdbinit
define test
   x/i $eip
   ni
   if ( $eip != 0x08048a7b)
   	  test

Ce script affiche le code exécuté par mon processeur puis fait un next instruction. Tout ça jusqu'à arriver à l'eip 0x08048a7b ( quand on sort de la fonction chiffre ). Je pose donc un breakpoint, j'active les logs et je bourrine ma touche "ENTREE".

(gdb) set environment LD_PRELOAD ./ptrace.so
(gdb) b* chiffre
Breakpoint 1 at 0x8048554
(gdb) r
Starting program: /home/w4kfu/RMLL/copy_mars1 
serial : 1234567890123456

Breakpoint 1, 0x08048554 in chiffre ()
(gdb) set logging file mars1_disas
(gdb) set logging on
Copying output to mars1_disas.
(gdb) test
....
BOURRINAGE TOUCHE ENTREE \o/
....
(gdb) set logging off

Bon par contre y'a 500 trucs qui se répètent et des lignes useless, bon bah on sort notre couteau suisse :

sed "/(gdb)/d" mars1_disas > mars1_disas2
cat mars1_disas2 | sort | uniq > real_disas

On arrive au résultat suivant : mars1_disas. On retranscrit le tout en C :

#include <stdio.h>

int
main(void)
{
      unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93";
      unsigned char input[16] = "1234567890123456";
      unsigned int ebp_14;
      unsigned int ebp_c;
      unsigned int eax;
      unsigned int ecx;
      unsigned int edx;
      unsigned char wtf[2];

      ebp_14 = 0;
      while ( ebp_14 <= 0xf )
      {
          ebp_c = 0;
          while ( ebp_c <= ebp_14 )
          {
	      input[ebp_14] = in_fct[ebp_c] ^ input[ebp_14];
	      edx = input[ebp_14] << ( ebp_14 & 0x7 );   
	      wtf[0] = edx;
             eax = ebp_14 & 0x7;
	      ecx = 0x8 - eax;
	      edx = input[ebp_14] >> ecx;
	      wtf[1] = edx;
	      input[ebp_14] = wtf[1] | wtf[0];
	      ebp_c++;
	   }
	   printf("%x\n",input[ebp_14]);
	   ebp_14++;
      }
      return 0;
}

Dans le code ci-dessus, j'ai déclaré les noms de façon assez explicite histoire de pas me tromper dans ce que je faisais. Maitenant qu'on sait comment fonctionne la fonction chiffre, j'ai "bruteforcé" le tout étant donné que l'on a les 16 octets auxquels on doit arriver :

void
bruteforce(void)
{
      unsigned char a;
      unsigned char b;
      unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93";
      unsigned char input[17] = {0};
      unsigned char result[16] = "\x56\xdb\xed\xd6\x8b\xfb\x86\x80\xd1\x47\x08\x9e\x45\xe7\x8c\x63";
      unsigned int ebp_14;
      unsigned int ebp_c;
      unsigned int eax;
      unsigned int ecx;
      unsigned int edx;
      unsigned char wtf[2];

      ebp_14 = 0;
      while ( ebp_14 <= 0xf )
      {
      	    a = '\x00';
	    while ( b != result[ebp_14])
	    {
		a += 1;
		b = a;
		ebp_c = 0; 
		while ( ebp_c <= ebp_14 )
		{
		    b = in_fct[ebp_c] ^ b;
		    edx = b << ( ebp_14 & 0x7 );   
		    wtf[0] = edx;
		    eax = ebp_14 & 0x7;
		    ecx = 0x8 - eax;
		    edx = b >> ecx;
		    wtf[1] = edx;
		    b = wtf[1] | wtf[0];
		    ebp_c++;
	         }
	     }
	     input[ebp_14] = a;
	     ebp_14++;
       }
       printf(" input = %s\n", input);
}

On teste tout ça :

w4kfu@vm-debian:~/RMLL$ gcc -o reverse reverse.c
w4kfu@vm-debian:~/RMLL$ ./reverse
input = POGddyrbtjYIoXfv
w4kfu@vm-debian:~/RMLL$ ./mars1 
serial : POGddyrbtjYIoXfv
Good job !
Use your serial as password for the next level !

Voilà c'est fini! Je n'ai pas le level 3 :(, si quelqu'un passe sur le blog et l'a disponible je suis intéressé car une fois le level2 résolu la machine était down.