====== 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 ; il doit être >1 0x08048fa7 <+15>: mov DWORD PTR [esp],0x80c254d ; "Please enter password ?" 0x08048fae <+22>: call 0x8049990 0x08048fb3 <+27>: mov DWORD PTR [esp],0x0 ; push 0 0x08048fba <+34>: call 0x8049770 ; 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(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([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([ebp-0x48], [ebp-0x28]); 0x08048f60 <+190>: test eax,eax ; eax = 0 ? 0x08048f62 <+192>: je 0x8048f7b ; 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("The hash is %s\n",argv[1]); 0x08048f79 <+215>: jmp 0x8048f89 ; 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 ; printf("Wrong password !\n") 0x08048f89 <+231>: mov DWORD PTR [esp],0x1 0x08048f90 <+238>: call 0x8049770 ; 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 ; 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 ; ils sont égaux 0x08048cdd <+41>: mov eax,0x0 ; si non, eax <- 0 0x08048ce2 <+46>: jmp 0x8048cf3 ; 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 ; 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 : add eax,edx 0x8048ccb : movzx edx,BYTE PTR [eax] 0x8048cce : mov eax,DWORD PTR [ebp-0x4] 0x8048cd1 : mov ecx,DWORD PTR [ebp+0xc] 0x8048cd4 : add eax,ecx 0x8048cd6 : movzx eax,BYTE PTR [eax] 0x8048cd9 : cmp dl,al 0x8048cdb : je 0x8048ce4 -------------------------------------------------------------------------------- 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 : add eax,ecx 0x8048cd6 : movzx eax,BYTE PTR [eax] 0x8048cd9 : cmp dl,al 0x8048cdb : je 0x8048ce4 0x8048cdd : mov eax,0x0 0x8048ce2 : jmp 0x8048cf3 0x8048ce4 : add DWORD PTR [ebp-0x4],0x1 0x8048ce8 : 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** :-)