lundi 11 novembre 2013

un crackme

Je surfe pas mal sur crackmes.de en ce moment. J'en ai vu un intéressant. Il s'agit de synamics's Xrockm.

Méthode de résolution; tout d'abord, on essaye les classiques:
mitsurugi@mitsu:~$ ./crackme_x86
What is the password?
password
mitsurugi@mitsu:~$ strace -e trace=open ./crackme_x86
open("/etc/ld.so.cache", O_RDONLY)      = 3
open("/lib/libc.so.6", O_RDONLY)        = 3
--- SIGCHLD (Child exited) @ 0 (0) ---
mitsurugi@mitsu:~$ gdb -q ./crackme_x86
Reading symbols from /home/mitsurugi/crackme_x86...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/mitsurugi/crackme_x86

^C
^Z
Processus arrêté
mitsurugi@mitsu:~$


Ok, on a un programme qui demande un mot de passe, qui n'est pas traçable avec strace, et qui semble planter gdb. Il faut regarder de plus près:
mitsurugi@mitsu:~$ strings crackme_x86
(...)

srand
puts
time
stdin
fgets
malloc
system
ptrace
__libc_start_main
(...)

You WIN, congratulations...
killall -q gdb
killall -q strace
        Sem cheat, fedepe...
What is the password?
(...)

Ok, donc l'anti debug vu plus haut est seulement un killall. Et ça, sous linux, ça se contourne en 3s:
mitsurugi@mitsu:~$ cp /usr/bin/strace STRACE
mitsurugi@mitsu:~$ cp /usr/bin/gdb GDB     
mitsurugi@mitsu:~$

et voilà. Plus d'antidebug. Ceci dit, strace ne nous apprend pas grand chose:
mitsurugi@mitsu:~$ ./STRACE ./crackme_x86
execve("./crackme_x86", ["./crackme_x86"], [/* 27 vars */]) = 0
brk(0)                                  = 0x804b000
(...)

write(1, "What is the password?\n", 22What is the password?
) = 22
fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77d6000
read(0, password
"password\n", 1024)             = 9
exit_group(0)                           = ?
mitsurugi@mitsu:~$

Bon, c'est curieux, on donne le mot de passe, et bam, exit (???) sans autre opération. Il est temps de lancer gdb.
mitsurugi@mitsu:~$ ./GDB -q ./crackme_x86
Reading symbols from /home/mitsurugi/crackme_x86...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/mitsurugi/crackme_x86
        Sem cheat, fedepe...
What is the password?
^Z
Program received signal SIGTSTP, Stopped (user).
0xb7f36dde in __read_nocancel () from /lib/libc.so.6


0xb7f36dde, mmmh, on est pas dans le main. Voyons voir ou nous sommes, et quelles sont les instructions qui vont suivre:
(gdb) bt
#0  0xb7f36dde in __read_nocancel () from /lib/libc.so.6
#1  0xb7ed81a3 in _IO_new_file_underflow () from /lib/libc.so.6
#2  0xb7ed938b in _IO_default_uflow_internal () from /lib/libc.so.6
#3  0xb7ed917a in __uflow () from /lib/libc.so.6
#4  0xb7eccac4 in _IO_getline_info_internal () from /lib/libc.so.6
#5  0xb7ecca11 in _IO_getline_internal () from /lib/libc.so.6
#6  0xb7ecb91a in fgets () from /lib/libc.so.6
#7  0x080487c5 in main ()
(gdb) x/10i 0x080487c5
   0x80487c5 <main+424>:    movzbl 0x804a040,%eax
   0x80487cc <main+431>:    cmp    $0x6f,%al
   0x80487ce <main+433>:    je     0x80487d7 <main+442>
   0x80487d0 <main+435>:    movb   $0x61,0x804a03d
   0x80487d7 <main+442>:    mov    $0x0,%eax
   0x80487dc <main+447>:    leave 
   0x80487dd <main+448>:    ret   
   0x80487de:    nop
   0x80487df:    nop
   0x80487e0 <__libc_csu_init>:    push   %ebp
(gdb)

Ok, donc juste après le fgets, on fait un movzbl d'une adresse puis on compare si l'octet (AL) vaut 'o' (\x6F). On break ici, et on regarde ce qu'on a à cette adresse:
(gdb) b * 0x80487cc
Breakpoint 1 at 0x80487cc
(gdb) c
Continuing.

Program received signal SIGTSTP, Stopped (user).
0xb7f36dde in __read_nocancel () from /lib/libc.so.6
(gdb) c
Continuing.
password

Breakpoint 1, 0x080487cc in main ()
(gdb) x/s 0x804a040
0x804a040 <password+4>:     "wor"
(gdb)

Ok, on est à password+4, ce qui est une information très intéressante.  Le movzbl ne prend qu'un seul octet et met le reste à zéro. Ce qui revient à dire que le 5e octet du mot de passe vaut 'o'. Là, j'ai 'w' donc ça va échouer. Pas de problèmes pour éviter cela:
(gdb) set $eax=0x6f
Et on continue:
(gdb) x/10i $eip
=> 0x80487cc <main+431>:    cmp    $0x6f,%al
   0x80487ce <main+433>:    je     0x80487d7 <main+442>
   0x80487d0 <main+435>:    movb   $0x61,0x804a03d
   0x80487d7 <main+442>:    mov    $0x0,%eax
   0x80487dc <main+447>:    leave 
   0x80487dd <main+448>:    ret   
   0x80487de:    nop
   0x80487df:    nop
   0x80487e0 <__libc_csu_init>:    push   %ebp
   0x80487e1 <__libc_csu_init+1>:    push   %edi
(gdb) nexti
0x080487ce in main ()
(gdb) nexti
0x080487d7 in main ()
(gdb) nexti
0x080487dc in main ()
(gdb) nexti
0x080487dd in main ()
(gdb) nexti
0xb7e82db6 in __libc_start_main () from /lib/libc.so.6
(gdb) nexti
0xb7e82db9 in __libc_start_main () from /lib/libc.so.6
(gdb) nexti

Program exited normally.

Et là, je suis ennuyé car le programme "exit normally" (??).

Il y a donc manifestement quelquechose qui se passe avant ou après l'entrée du mot passe qui vérifie quelque chose. Cela rejoint le strace qui exite directement après l'entrée du mot de passe. Là, j'ai regardé les fonctions avec objdump pour me donner une idée:
mitsurugi@mitsu:~$ objdump -d crackme_x86 | grep ">:"
080483b0 <_init>:
080483e0 <fgets@plt-0x10>:
080483f0 <fgets@plt>:
08048400 <time@plt>:
08048410 <malloc@plt>:
08048420 <puts@plt>:
08048430 <system@plt>:
08048440 <__gmon_start__@plt>:
08048450 <srand@plt>:
08048460 <__libc_start_main@plt>:
08048470 <rand@plt>:
08048480 <ptrace@plt>:
08048490 <_start>:
080484c0 <__do_global_dtors_aux>:
08048520 <frame_dummy>:
08048544 <_finit_>:
08048584 <_init_>:
0804861d <main>:
080487e0 <__libc_csu_init>:
08048850 <__libc_csu_fini>:
08048852 <__i686.get_pc_thunk.bx>:
08048860 <__do_global_ctors_aux>:
0804888c <_fini>:
mitsurugi@mitsu:~$

Il y a une fonction qui a un nom qui m'étonne: _finit_. Allons breaker sur elle et voir quand elle est appelée:
mitsurugi@mitsu:~$ ./GDB -q ./crackme_x86
Reading symbols from /home/mitsurugi/crackme_x86...(no debugging symbols found)...done.
(gdb) b * _finit_
Breakpoint 1 at 0x8048544
(gdb) r
Starting program: /home/mitsurugi/crackme_x86
        Sem cheat, fedepe...
What is the password?
ooooo

Breakpoint 1, 0x08048544 in _finit_ ()
(gdb)

Ok, ça c'est intéressant, elle est bien appelée après avoir entré le mot de passe.
(gdb) bt
#0  0x08048544 in _finit_ ()
#1  0xb7ff0674 in _dl_fini () from /lib/ld-linux.so.2
#2  0xb7e9bc5f in exit () from /lib/libc.so.6
#3  0xb7e82dbe in __libc_start_main () from /lib/libc.so.6
#4  0x080484b1 in _start ()

Et elle est bien appelée depuis exit(). Et la fonction est très simple à lire:
(gdb) x/19i $eip
=> 0x8048544 <_finit_>:    push   %ebp
   0x8048545 <_finit_+1>:    mov    %esp,%ebp
   0x8048547 <_finit_+3>:    sub    $0x18,%esp
   0x804854a <_finit_+6>:    movzbl 0x804a03c,%eax
   0x8048551 <_finit_+13>:    cmp    $0x68,%al
   0x8048553 <_finit_+15>:    jne    0x8048582 <_finit_+62>
   0x8048555 <_finit_+17>:    movzbl 0x804a03d,%eax
   0x804855c <_finit_+24>:    cmp    $0x65,%al
   0x804855e <_finit_+26>:    jne    0x8048582 <_finit_+62>
   0x8048560 <_finit_+28>:    movzbl 0x804a03e,%eax
   0x8048567 <_finit_+35>:    cmp    $0x6c,%al
   0x8048569 <_finit_+37>:    jne    0x8048582 <_finit_+62>
   0x804856b <_finit_+39>:    movzbl 0x804a03f,%eax
   0x8048572 <_finit_+46>:    cmp    $0x6c,%al
   0x8048574 <_finit_+48>:    jne    0x8048582 <_finit_+62>
   0x8048576 <_finit_+50>:    movl   $0x80488b0,(%esp)
   0x804857d <_finit_+57>:    call   0x8048420 <puts@plt>
   0x8048582 <_finit_+62>:    leave 
   0x8048583 <_finit_+63>:    ret   
(gdb)

La fonction va donc lire à 0x804a03c (password), puis à
0x804a03d (password+1) etc.. si l'octet vaut \x68, puis \x65, \x6c et \x6c, soit:

 mitsurugi@mitsu:~$ printf '\x68\x65\x6c\x6c\n'
hell
mitsurugi@mitsu:~$

Ok. Le 5e caractère doit être un 'o', les 4 premiers valent 'hell', la solution est donc tout mot commençant par 'hello' :
mitsurugi@mitsu:~$ ./crackme_x86
What is the password?
hello
You WIN, congratulations...
mitsurugi@mitsu:~$ ./crackme_x86
What is the password?
hellocoton
You WIN, congratulations...
mitsurugi@mitsu:~$


La chose que je ne m'explique pas, c'est comment la fonction _finit_ fait pour être appelée, alors qu'on ne la voit nulle part.

Mitsurugi

1 commentaire: