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.
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 :
- 16 octets qui sont placés à la suite dans la stack.
- Notre serial doit être d'une longueur de 16 ( 0x10 ) caractères.
- Une fonction chiffre.
- Le test se fait à partir d'un memcmp().
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