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.

18/07/2010

Write up RMLL crackme level 1

Ce billet aurait dû sortir il y a une semaine mais le week-end dernier se déroulait le smpCTF auquel j'ai participeéau sein de l'équipe BiG-DadDy ( arrivée 12ème ) ou avec dad nous nous sommes occupés surtout des épreuves p0wnMe, bref maintenant j'ai le temps donc je le consacre à faire ce write up.

Du 6 au 11 Juillet 2010, à Bordeaux se déroulait les Rencontres Mondiales du Logiciel Libre. Je fais actuellement un stage dans la boite XXXX et mon maître de stage se rendait sur place ( je n'ai pas eu la chance de l'accompagner :( ). Bref, dès son premier jour d'arrivée il m'envoie un e-mail avec ce lien.

Quoi des challenges de sécurité !?! Je me précipite sur les crackme.

Première approche :

w4kfu@vm-debian:~/RMLL$ ./mars0 
serial : 34534343423432403245-450-435-043-50435
Nice try ! But ... no ...

Allez hop on sort gdb :

w4kfu@vm-debian:~/RMLL$ gdb ./mars0 
...
(gdb) disas main
....
0x0804855b <main+9>:	        movl   $0xc49c6547,0x1e(%esp)
0x08048563 <main+17>:		movl   $0xee987f5e,0x22(%esp)
0x0804856b <main+25>:		movl   $0x4cced336,0x26(%esp)
0x08048573 <main+33>:		movl   $0xee7c11e4,0x2a(%esp)
.....
0x080485a9 <main+87>:	call   0x80483e4 <strlen@plt>
0x080485ae <main+92>:	cmp    $0x10,%eax
....
0x080485d2 <main+128>:	call   0x80484f4 <chiffre>
....
0x080485ee <main+156>:	call   0x80483b4 <memcmp@plt>
0x080485f3 <main+161>:	test   %eax,%eax
0x080485f5 <main+163>:	jne    0x804860a <main+184>
....

Déjà ici on peut commencer à comprendre un peu le fonctionnement de notre crackme :

Posons un breakpoint sur le memcmp() :

(gdb) b* main+156
Breakpoint 1 at 0x80485ee
(gdb) r
Starting program: /home/w4kfu/RMLL/mars0 
serial : 1234567890123456
Breakpoint 1, 0x080485ee in main ()
(gdb) x/4x $esp
0xbfffecc0:	0xbfffecef	0xbfffecde	0x00000010	0x080483a0
(gdb) x/4x 0xbfffecef
0xbfffecef:	0xb5dd0032	0x85f83d00	0x3ca1842f	0xca5e21a6  <<< Notre SERIAL
(gdb) x/4x 0xbfffecde
0xbfffecde:	0xc49c6547	0xee987f5e	0x4cced336	0xee7c11e4  <<< Ce a quoi il est comparee

Regardons maitenant du côté de la fonction chiffre :

(gdb) disas chiffre
0x080484f5 <chiffre+1>:	mov    %esp,%ebp
0x080484f7 <chiffre+3>:	sub    $0x20,%esp
0x080484fa <chiffre+6>:	movl   $0x82e83303,-0x15(%ebp)
0x08048501 <chiffre+13>:       movl   $0xbac50639,-0x11(%ebp)
0x08048508 <chiffre+20>:       movl   $0x19abd6e,-0xd(%ebp)
0x0804850f <chiffre+27>:       movl   $0x8f1d6099,-0x9(%ebp)
0x08048516 <chiffre+34>:       movb   $0x0,-0x5(%ebp)
0x0804851a <chiffre+38>:       movl   $0x0,-0x4(%ebp)
0x08048521 <chiffre+45>:       jmp    0x804854a <chiffre+86>
0x08048523 <chiffre+47>:       mov    -0x4(%ebp),%eax
0x08048526 <chiffre+50>:       add    0x8(%ebp),%eax
0x08048529 <chiffre+53>:       mov    -0x4(%ebp),%edx
0x0804852c <chiffre+56>:       add    0x8(%ebp),%edx
0x0804852f <chiffre+59>:       movzbl (%edx),%ecx
0x08048532 <chiffre+62>:       mov    -0x4(%ebp),%edx
0x08048535 <chiffre+65>:       lea    (%ecx,%edx,1),%edx
0x08048538 <chiffre+68>:       mov    %edx,%ecx
0x0804853a <chiffre+70>:       mov    -0x4(%ebp),%edx
0x0804853d <chiffre+73>:       movzbl -0x15(%ebp,%edx,1),%edx
0x08048542 <chiffre+78>:       xor    %ecx,%edx
0x08048544 <chiffre+80>:       mov    %dl,(%eax)
0x08048546 <chiffre+82>:       addl   $0x1,-0x4(%ebp)
0x0804854a <chiffre+86>:       cmpl   $0xf,-0x4(%ebp)
0x0804854e <chiffre+90>:       jle    0x8048523 <chiffre+47>
0x08048550 <chiffre+92>:       leave  
0x08048551 <chiffre+93>:       ret    

Le disas ci-dessus peut être traduit de la sorte en C :

#include <stdio.h>

char *chiffre(char *cherche, char *sec);

int 
main(void)
{
      int i;
      char *mot_chercher = "1234567890123456";
      char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f";
      char *mot_crypt;

      mot_crypt = chiffre(mot_chercher,second_word);
      for ( i = 0 ; i < 16 ; i++)
      	       printf("%x ",mot_crypt[i]);
      return 0;
}

char *
chiffre(char *cherche, char *sec)
{
      int i = 0;
      static char found[16];

      for ( i = 0; i < 16; i++)
      	       found[i] = (cherche[i] + i)^ sec[i];
      return found;
}

On teste pour voir si on retrouve bien le même résultat :

w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
32 0 dd b5 0 3d f8 85 2f 84 a1 3c a6 21 5e ca

Notre code est correct, bien maintenant il faut retrouver le serial valide, ce n'est pas très compliqué car nous savons à quoi il doit être égal. Nous n'avons qu'à faire l'opération inverse :

#include <stdio.h>

char *chiffre(char *cherche, char *sec);

int
main(void)
{
      char *mot_chercher = "\x47\x65\x9c\xc4\x5e\x7f\x98\xee\x36\xd3\xce\x4c\xe4\x11\x7c\xee"; 
      char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f";

      printf("Le serial est  %s\n", chiffre(mot_chercher,second_word));
}

char *
chiffre(char *cherche, char *sec)
{
      int i = 0;
      static char found[16];

      for ( i = 0; i < 16; i++)
      	       found[i] = (cherche[i] ^ sec[i])-i;
      return found;
}

On exécute tout ça :

w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
Le serial est  DUrCctWMPeJBqdSR

Et on teste notre serial :

w4kfu@vm-debian:~/RMLL$ ./mars0
serial : DUrCctWMPeJBqdSR
Good job !
Use your serial as password for the next level !

Pages : 1