Table des matières

Crackme Bukkake

Overview

$ file crackme 
crackme: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=0xfe11128a5dd83a7776ad2f978b1ab64ba5b7ddba, not stripped

Pour une raison que je ne sais pas expliquer, il est impossible de l'ouvrir avec IDA. C'est parti pour du full gdb ;-) !

main function

gdb$ disas main
Dump of assembler code for function main:
   0x08048f98 <+0>:	push   ebp
   0x08048f99 <+1>:	mov    ebp,esp
   0x08048f9b <+3>:	and    esp,0xfffffff0
   0x08048f9e <+6>:	sub    esp,0x10
   0x08048fa1 <+9>:	cmp    DWORD PTR [ebp+0x8],0x1    ; check le nombre d'argument
   0x08048fa5 <+13>:	jg     0x8048fbf <main+39>        ; il doit être >1
   0x08048fa7 <+15>:	mov    DWORD PTR [esp],0x80c254d  ; "Please enter password ?"
   0x08048fae <+22>:	call   0x8049990 <puts>
   0x08048fb3 <+27>:	mov    DWORD PTR [esp],0x0        ; push 0
   0x08048fba <+34>:	call   0x8049770 <exit>           ; exit(0)
   0x08048fbf <+39>:	mov    eax,DWORD PTR [ebp+0xc]    ; continue ici si au moins un argument est donné
   0x08048fc2 <+42>:	add    eax,0x4
   0x08048fc5 <+45>:	mov    eax,DWORD PTR [eax]        ; eax contient notre premier argument (son adresse)
   0x08048fc7 <+47>:	mov    DWORD PTR [esp],eax        ; push eax
   0x08048fca <+50>:	call   0x8048ea2 <check>          ; check(argv[1]);
   0x08048fcf <+55>:	leave  
   0x08048fd0 <+56>:	ret    
End of assembler dump.

De façon basique, le programme regarde s'il a au moins un argument en entrée. Si ce n'est pas le cas, il retourne un message d'erreur. Dans le cas contraire, il appel la fonction check avec argv[1] en argument.

check function

gdb$ disas check
Dump of assembler code for function check:
   0x08048ea2 <+0>:	push   ebp
   0x08048ea3 <+1>:	mov    ebp,esp
   0x08048ea5 <+3>:	push   ebx
   0x08048ea6 <+4>:	sub    esp,0x54
   0x08048ea9 <+7>:	call   0x8048bcc <__x86.get_pc_thunk.bx>
   0x08048eae <+12>:	add    ebx,0x9d1ae
   0x08048eb4 <+18>:	mov    BYTE PTR [ebp-0x28],0x3
   0x08048eb8 <+22>:	mov    BYTE PTR [ebp-0x27],0x23
   0x08048ebc <+26>:	mov    BYTE PTR [ebp-0x26],0x1f
   0x08048ec0 <+30>:	mov    BYTE PTR [ebp-0x25],0x23
   0x08048ec4 <+34>:	mov    BYTE PTR [ebp-0x24],0x5
   0x08048ec8 <+38>:	mov    BYTE PTR [ebp-0x23],0x3
   0x08048ecc <+42>:	mov    BYTE PTR [ebp-0x22],0x8
   0x08048ed0 <+46>:	mov    BYTE PTR [ebp-0x21],0x7
   0x08048ed4 <+50>:	mov    BYTE PTR [ebp-0x20],0x1f
   0x08048ed8 <+54>:	mov    BYTE PTR [ebp-0x1f],0x4
   0x08048edc <+58>:	mov    BYTE PTR [ebp-0x1e],0x5
   0x08048ee0 <+62>:	mov    BYTE PTR [ebp-0x1d],0x20
   0x08048ee4 <+66>:	mov    BYTE PTR [ebp-0x1c],0x21
   0x08048ee8 <+70>:	mov    BYTE PTR [ebp-0x1b],0x7
   0x08048eec <+74>:	mov    BYTE PTR [ebp-0x1a],0x1f
   0x08048ef0 <+78>:	mov    BYTE PTR [ebp-0x19],0x5
   0x08048ef4 <+82>:	mov    BYTE PTR [ebp-0x18],0x21
   0x08048ef8 <+86>:	mov    BYTE PTR [ebp-0x17],0x5
   0x08048efc <+90>:	mov    BYTE PTR [ebp-0x16],0x4
   0x08048f00 <+94>:	mov    BYTE PTR [ebp-0x15],0x1d
   0x08048f04 <+98>:	mov    BYTE PTR [ebp-0x14],0x22
   0x08048f08 <+102>:	mov    BYTE PTR [ebp-0x13],0x1e
   0x08048f0c <+106>:	mov    BYTE PTR [ebp-0x12],0x1f
   0x08048f10 <+110>:	mov    BYTE PTR [ebp-0x11],0x26
   0x08048f14 <+114>:	mov    BYTE PTR [ebp-0x10],0x25
   0x08048f18 <+118>:	mov    BYTE PTR [ebp-0xf],0x1f
   0x08048f1c <+122>:	mov    BYTE PTR [ebp-0xe],0x1e
   0x08048f20 <+126>:	mov    BYTE PTR [ebp-0xd],0x21
   0x08048f24 <+130>:	mov    BYTE PTR [ebp-0xc],0x24
   0x08048f28 <+134>:	mov    BYTE PTR [ebp-0xb],0x6
   0x08048f2c <+138>:	mov    BYTE PTR [ebp-0xa],0x24
   0x08048f30 <+142>:	mov    BYTE PTR [ebp-0x9],0x25
   0x08048f34 <+146>:	mov    DWORD PTR [esp+0x8],0x20
   0x08048f3c <+154>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048f3f <+157>:	mov    DWORD PTR [esp+0x4],eax     ; push notre serial passé en argument
   0x08048f43 <+161>:	lea    eax,[ebp-0x48]
   0x08048f46 <+164>:	mov    DWORD PTR [esp],eax         ; push l'adresse de ebp-0x48
   0x08048f49 <+167>:	call   0x8048cf5 <WhatisIt>        ; WhatisIt([ebp-0x48], argv[1]);
   0x08048f4e <+172>:	lea    eax,[ebp-0x28]
   0x08048f51 <+175>:	mov    DWORD PTR [esp+0x4],eax     ; push l'adresse de ebp-0x28
   0x08048f55 <+179>:	lea    eax,[ebp-0x48]
   0x08048f58 <+182>:	mov    DWORD PTR [esp],eax         ; psuh l'adresse de ebp-0x48
   0x08048f5b <+185>:	call   0x8048cb4 <ccc>             ; ccc([ebp-0x48], [ebp-0x28]);
   0x08048f60 <+190>:	test   eax,eax                     ; eax = 0 ?
   0x08048f62 <+192>:	je     0x8048f7b <check+217>       ; si oui, jmp en check+217
   0x08048f64 <+194>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048f67 <+197>:	mov    DWORD PTR [esp+0x4],eax     ; push argv[1]
   0x08048f6b <+201>:	lea    eax,[ebx-0x23b30]      
   0x08048f71 <+207>:	mov    DWORD PTR [esp],eax         ; push "The hash is %s\n"
   0x08048f74 <+210>:	call   0x8049960 <printf>          ; printf("The hash is %s\n",argv[1]);
   0x08048f79 <+215>:	jmp    0x8048f89 <check+231>       ; jmp après le "Wrong password"
   0x08048f7b <+217>:	lea    eax,[ebx-0x23b20]           ; jmp ici si ccc renvois 0
   0x08048f81 <+223>:	mov    DWORD PTR [esp],eax         
   0x08048f84 <+226>:	call   0x8049990 <puts>            ; printf("Wrong password !\n")
   0x08048f89 <+231>:	mov    DWORD PTR [esp],0x1 
   0x08048f90 <+238>:	call   0x8049770 <exit>            ; exit(1);
End of assembler dump.

Bon, on voit un certain nombre de mov [ebp-xx],0xyy, je décide de ne pas y prêter attention pour le moment. On remarque surtout l'appel à la fonction WhatisIt avec laquelle notre serial est passé en argument, ainsi que l'appel à ccc qui passe en argument quelque chose stocké en ebp-0x28 ainsi que ebp-0x48. Ceci parait louche, d'autant qu'il y a un certain nombre de mov sur l'ebp. La suite du programme dépend de ce que renvoie ccc dans eax. Si c'est un 0, le serial n'est pas le bon, si c'est autre chose, le mot de passe est le bon.

Il ne nous reste plus qu'à regarder ce que font WhatisIt et ccc.

Il est probable que la fonction WhatisIt soit là pour chiffrer notre serial et calculer celui d'origine. Je décide d'essayer de résoudre le challenge en regardant uniquement la fonction ccc.

ccc function

gdb$ disas 0x8048cb4
Dump of assembler code for function ccc:
   0x08048cb4 <+0>:	push   ebp
   0x08048cb5 <+1>:	mov    ebp,esp
   0x08048cb7 <+3>:	sub    esp,0x10
   0x08048cba <+6>:	mov    DWORD PTR [ebp-0x4],0x0       ; ebp-0x4 <- 0 (initialisation)
   0x08048cc1 <+13>:	jmp    0x8048ce8 <ccc+52>            ; début d'une boucle
   0x08048cc3 <+15>:	mov    eax,DWORD PTR [ebp-0x4]       ; eax <- ebp-0x4 (compteur)
   0x08048cc6 <+18>:	mov    edx,DWORD PTR [ebp+0x8]       ; edx <- une chaine bizarre 
   0x08048cc9 <+21>:	add    eax,edx
   0x08048ccb <+23>:	movzx  edx,BYTE PTR [eax]            ; prend le char N°eax de la chaine bizarre
   0x08048cce <+26>:	mov    eax,DWORD PTR [ebp-0x4]       ; eax <- ebp-0x4 (compteur)
   0x08048cd1 <+29>:	mov    ecx,DWORD PTR [ebp+0xc]       ; ecx <- une seconde chaine bizarre
   0x08048cd4 <+32>:	add    eax,ecx
   0x08048cd6 <+34>:	movzx  eax,BYTE PTR [eax]            ; prend le char N°eax de la seconde chaine bizarre
   0x08048cd9 <+37>:	cmp    dl,al                         ; compare les deux chars
   0x08048cdb <+39>:	je     0x8048ce4 <ccc+48>            ; ils sont égaux
   0x08048cdd <+41>:	mov    eax,0x0                       ; si non, eax <- 0
   0x08048ce2 <+46>:	jmp    0x8048cf3 <ccc+63>            ; on sort de la fonction ! (return 0) <=> le serial n'est pas le bon
   0x08048ce4 <+48>:	add    DWORD PTR [ebp-0x4],0x1       ; si les deux chars sont égaux, on incrémente de 1 le compteur
   0x08048ce8 <+52>:	cmp    DWORD PTR [ebp-0x4],0x1f      ;
   0x08048cec <+56>:	jbe    0x8048cc3 <ccc+15>            ; la boucle tourne 32 fois (ça fait tilt?)
   0x08048cee <+58>:	mov    eax,0x1
   0x08048cf3 <+63>:	leave  
   0x08048cf4 <+64>:	ret    
End of assembler dump.

Bon, on remarque très facilement que c'est une boucle qui vérifie si deux chaines sont les mêmes. A partir de la première différence, on sort de la boucle. Il ne nous reste plus qu'à savoir à quoi correspondent les deux chaines. L'une doit probablement correspondre à notre serial et l'autre au serial à trouver. Elles ont certainement été calculées par la fonction WhatisIt que nous avons choisi de ne pas regarder.

Pour arriver à trouver quelle chaine correspond à quoi, il suffit de lancer le programme deux fois, avec deux serials différent et de regarder laquelle des deux valeurs a changé !

gdb$ b *0x08048cd9
Breakpoint 1 at 0x8048cd9
gdb$ r 1337
Breakpoint 1, 0x08048cd9 in ccc ()
gdb$ i r $eax
eax            0x3	0x3
gdb$ i r $edx
edx            0x1e	0x1e
gdb$ r BBBB
Breakpoint 1, 0x08048cd9 in ccc ()
gdb$ i r $eax
eax            0x3	0x3
gdb$ i r $edx
edx            0x4	0x4

ebp+0x8 correspond donc au serial que nous avons entré et ebp+0xc au serial que nous devons entrer.

On remarque aussi que la valeur de eax n'a pas changé dans les deux cas. On sait aussi qu'il faut que l'on trouve un hash de 32 caractères. Très vite, on suppose qu'il est juste question d'une substitution, il ne nous reste qu'à recréer la table. Rien de plus simple!

gdb$ b *0x08048cc9
Breakpoint 1 at 0x8048cc9
gdb$ r 0123456789ABCDEF
--------------------------------------------------------------------------[regs]
  EAX: 0x00000000  EBX: 0x080E605C  ECX: 0xBFFFF6A4  EDX: 0xBFFFF6F0  o d I t S z A P C 
  ESI: 0x08049580  EDI: 0x08049620  EBP: 0xBFFFF6D8  ESP: 0xBFFFF6C8  EIP: 0x08048CC9
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x8048cc9 <ccc+21>:	add    eax,edx
   0x8048ccb <ccc+23>:	movzx  edx,BYTE PTR [eax]
   0x8048cce <ccc+26>:	mov    eax,DWORD PTR [ebp-0x4]
   0x8048cd1 <ccc+29>:	mov    ecx,DWORD PTR [ebp+0xc]
   0x8048cd4 <ccc+32>:	add    eax,ecx
   0x8048cd6 <ccc+34>:	movzx  eax,BYTE PTR [eax]
   0x8048cd9 <ccc+37>:	cmp    dl,al
   0x8048cdb <ccc+39>:	je     0x8048ce4 <ccc+48>
--------------------------------------------------------------------------------

Breakpoint 1, 0x08048cc9 in ccc ()
gdb$ x/s $edx
0xbffff6f0:	 "\035\036\037 !\"#$%&\003\004\005\006\a\b\025\n\a\016\016m\016\b"
gdb$ si 4
--------------------------------------------------------------------------[regs]
  EAX: 0x00000000  EBX: 0x080E605C  ECX: 0xBFFFF710  EDX: 0x0000001D  o d I t S z a P c 
  ESI: 0x08049580  EDI: 0x08049620  EBP: 0xBFFFF6D8  ESP: 0xBFFFF6C8  EIP: 0x08048CD4
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x8048cd4 <ccc+32>:	add    eax,ecx
   0x8048cd6 <ccc+34>:	movzx  eax,BYTE PTR [eax]
   0x8048cd9 <ccc+37>:	cmp    dl,al
   0x8048cdb <ccc+39>:	je     0x8048ce4 <ccc+48>
   0x8048cdd <ccc+41>:	mov    eax,0x0
   0x8048ce2 <ccc+46>:	jmp    0x8048cf3 <ccc+63>
   0x8048ce4 <ccc+48>:	add    DWORD PTR [ebp-0x4],0x1
   0x8048ce8 <ccc+52>:	cmp    DWORD PTR [ebp-0x4],0x1f
--------------------------------------------------------------------------------
0x08048cd4 in ccc ()
gdb$ x/s $ecx
0xbffff710:	 "\003#\037#\005\003\b\a\037\004\005 !\a\037\005!\005\004\035\"\036\037&%\037\036!$\006$%\200\225\004\b"

Note: il faut prendre uniquement les 16 premiers chars de la chaine ;-)

On récupère aussi au passage le vrai hash stocké dans ecx. (uniquement les 32 premiers chars sont à prendre en compte)

CHAR code (décimal)
0 29
1 30
2 31
3 32
4 33
5 34
6 35
7 36
8 37
9 38
A 3
B 4
C 5
D 6
E 7
F 8

On code un petit truc en python, et le tour est joué !

#!/usr/bin/env python
# encoding: utf-8
 
def main():
 
    table = {"29":"0",
            "30":"1",
            "31":"2",
            "32":"3",
            "33":"4",
            "34":"5",
            "35":"6",
            "36":"7",
            "37":"8",
            "38":"9",
            "3":"A",
            "4":"B",
            "5":"C",
            "6":"D",
            "7":"E",
            "8":"F"};
 
    serial = "\003#\037#\005\003\b\a\037\004\005 !\a\037\005!\005\004\035\"\036\037&%\037\036!$\006$%"
    decrypt = ""
    for char in serial:
        decrypt += table[str(ord(char))]
 
    print decrypt
 
if __name__ == '__main__':
	main()
$ ./crackme $(./bukkake.py)
The hash is A626CAFE2BC34E2C4CB0512982147D78

On valide avec A626CAFE2BC34E2C4CB0512982147D78 :-)