tutorial for the blaadme2 Qt crackme 2005, 0xf001 ---------------------------------------------------------------- Crackme: blaadme2 Author: blAAd! Language: C++, Qt Protection: stored serial hash Difficulty: rated 6/10 Source: http://www.crackmes.de Requirements: understaning assembly language, little maths Used Tools: lida, gdb, gcc TOC --- 0 ......... Intro 1 ......... Choosing the approach 2 ......... Let's debug 3 ......... The hash algorithm reversed 4 ......... Cracking the hash algorithm 4.1 ....... Understanding the algorithm 4.2 ....... Finding the weak point 5 ......... Serial generator in C 6 ......... Final words 0 Intro ------- Welcome to this crackme tutorial! This crackme is really interresting, and I would suggest really trying it before reading the tutorial :) I got stuck with this algorithm and thought it is irreversible in the first time, but ... The blaadme2 crackme is coded in C++ and utilizes the Qt library for GUI display. When starting, it simply shows a screen asking for a serial number. The input field for the serial accepts up to 15 characters. As we soon find out, the crackme uses no "anti tool" stuff, it does nothing against disassembling, debugging and getting strings out of it. 1 Choosing the approach ----------------------- At the beginning of course we know nothing about the serial number. There is a button which is called "Check It". When pressing it, it immediately says "No No No Try Again!". Of course :) So first I loaded the file into lida to get an impression of what to deal with: is there antidisassembling, are there symbol names, strings, ... First I open the strings window and look what whe have here. As we can see, we immediately find the "No No No ..." string: 0804AB4B - "YEAAHHH! U WIN " ; xref ( 0804A55F ) 0804AB6C - "No No No Try Again! " ; xref ( 0804A57E ) And we see a crossreference as well. So let's try the "backwards from string" approach: simply looking where the string gets accessed and working backwards from here to see which criterias must be met, that the flow of execution lands at this address where the string gets pushed: Looking at the code before the string 0804AB6C was pushed, we also see a cmp instruction: 0804A550 80 7D D7 01 cmp [ebp-29], 0x1 ; xref ( 0804A44D 0804A541 ) 0804A554 75 1F jnz Label_0804A575 (0804A575) ; (near + 0x1F) 0804A556 83 C4 F8 add esp, -0x8 0804A559 6A 00 push 0x0 0804A55B 6A 00 push 0x0 0804A55D 6A 01 push 0x1 0804A55F 68 4B AB 04 08 push "YEAAHHH! U WIN" (0804AB4B) 0804A564 68 5A AB 04 08 push "tOo Much blAAd! 2" (0804AB5A) 0804A569 6A 00 push 0x0 0804A56B E8 A8 F8 FF FF call information__11QMessageBoxP7QWidgetPCcT2iii (08049E18) ; (near - 0x758) 0804A570 83 C4 20 add esp, 0x20 0804A573 EB 1D jmp Label_0804A592 (0804A592) ; (near + 0x1D) Label_0804A575: 0804A575 83 C4 F8 add esp, -0x8 ; xref ( 0804A554 ) 0804A578 6A 00 push 0x0 0804A57A 6A 00 push 0x0 0804A57C 6A 01 push 0x1 0804A57E 68 6C AB 04 08 push "No No No Try Again!" (0804AB6C) 0804A583 68 5A AB 04 08 push "tOo Much blAAd! 2" (0804AB5A) 0804A588 6A 00 push 0x0 0804A58A E8 89 F8 FF FF call information__11QMessageBoxP7QWidgetPCcT2iii (08049E18) ; (near - 0x777) 0804A58F 83 C4 20 add esp, 0x20 It is easy to tell from this snippet (assuming no other checks / tricks are done) that this cmp checks for a good / bad serial, and displays the according messagebox. Now I am simply looking further "upwards", to see where [ebp-29] is initialized. We see many movs, xors, and use of local variables, which seems to be the serial calculation, and further above we find it at address: 0804A43C 0804A421 8B 43 74 mov eax, [ebx+74] 0804A424 50 push eax 0804A425 E8 8E F8 FF FF call text__C9QLineEdit (08049CB8) ; (near - 0x772) 0804A42A 83 C4 10 add esp, 0x10 0804A42D 89 C0 mov eax, eax 0804A42F 50 push eax 0804A430 8D 45 EC lea eax, [ebp-14] 0804A433 50 push eax 0804A434 E8 17 05 00 00 call Function___0804A950 (0804A950) ; (near + 0x517) 0804A439 83 C4 10 add esp, 0x10 0804A43C C6 45 D7 01 mov [ebp-29], 0x1 ; LOCAL FOR SERIAL_VALID 0804A440 C7 45 D8 00 00 00 00 mov [ebp-28], 0x0 ; COUNTER Label_0804A447: 0804A447 83 7D D8 0E cmp [ebp-28], 0xE ; xref ( 0804A546 ) 0804A44B 7E 05 jle Label_0804A452 (0804A452) ; (near + 0x5) <---+ 0804A44D E9 FE 00 00 00 jmp Label_0804A550 (0804A550) ; (near + 0xFE) | | CHECK_SERIAL: | 0804A452 BA 0E 00 00 00 mov edx, 0xE ; xref ( 0804A44B ) ----------------+ 0804A457 89 D0 mov eax, edx 0804A459 2B 45 D8 sub eax, [ebp-28] 0804A45C 8D 55 EC lea edx, [ebp-14] 0804A45F BE 0E 00 00 00 mov esi, 0xE 0804A464 89 F7 mov edi, esi 0804A466 2B 7D D8 sub edi, [ebp-28] Now we also see immediately before the initialization there is a call instruction executed. I assume those functions above setup the environment for checking: copy string from edit- box, etc... Quickly checking the called function we see it is a strcpy wrapper: Function___0804A950: 0804A950 55 push ebp ; xref ( 0804A434 ) 0804A951 89 E5 mov ebp, esp 0804A953 83 EC 10 sub esp, 0x10 0804A956 56 push esi 0804A957 53 push ebx 0804A958 8B 5D 08 mov ebx, [ebp+08] 0804A95B 8B 75 0C mov esi, [ebp+0C] 0804A95E 85 F6 test esi, esi 0804A960 74 11 jz Label_0804A973 (0804A973) ; (near + 0x11) 0804A962 83 C4 F8 add esp, -0x8 0804A965 56 push esi 0804A966 53 push ebx 0804A967 E8 5C F5 FF FF call strcpy (08049EC8) ; (near - 0xAA4) 0804A96C 83 C4 10 add esp, 0x10 0804A96F 89 C0 mov eax, eax 0804A971 EB 02 jmp Label_0804A975 (0804A975) ; (near + 0x2) Label_0804A973: 0804A973 31 C0 xor eax, eax ; xref ( 0804A960 ) Label_0804A975: 0804A975 89 C0 mov eax, eax ; xref ( 0804A971 ) 0804A977 EB 00 jmp Label_0804A979 (0804A979) ; (near + 0x0) Label_0804A979: 0804A979 8D 65 E8 lea esp, [ebp-18] ; xref ( 0804A977 ) 0804A97C 5B pop ebx 0804A97D 5E pop esi 0804A97E 89 EC mov esp, ebp 0804A980 5D pop ebp 0804A981 C3 ret 2 Let's debug ------------- Now it is allready time to debug, and see if we find our entered serial somewhere: I run the crackme in gdb and enter "12345678" into the editbox, and set a breakpoint after the strcpy function: # gdb blaadme2 gdb> bp 0x0804A439 gdb> run _______________________________________________________________________________ eax:BFFFF14C ebx:BFFFF47C ecx:B7F7EC4B edx:08080509 eflags:00200282 esi:0804AB4B edi:BFFFF14B esp:BFFFF0E8 ebp:BFFFF160 eip:0804A439 cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t S z a p c [007B:BFFFF0E8]---------------------------------------------------------[stack] BFFFF118 : B9 53 0E 40 8C 7D 1D 40 - 40 F1 FF BF FB 40 0E 40 .S.@.}.@@....@.@ BFFFF108 : 8C F1 FF BF 28 F1 FF BF - E6 11 43 40 0C 00 00 00 ....(.....C@.... BFFFF0F8 : 8C 7D 1D 40 FC A3 04 08 - FC A3 04 08 0C 00 00 00 .}.@............ BFFFF0E8 : 4C F1 FF BF 00 05 08 08 - 0C 00 00 00 18 D2 3F 40 L.............?@ [007B:0804AB4B]---------------------------------------------------------[ data] 0804AB4B : 59 45 41 41 48 48 48 21 - 20 55 20 57 49 4E 00 74 YEAAHHH! U WIN.t 0804AB5B : 4F 6F 20 4D 75 63 68 20 - 62 6C 41 41 64 21 20 32 Oo Much blAAd! 2 [0073:0804A439]---------------------------------------------------------[ code] 0x804a439 : add $0x10,%esp 0x804a43c : movb $0x1,0xffffffd7(%ebp) 0x804a440 : movl $0x0,0xffffffd8(%ebp) 0x804a447 : cmpl $0xe,0xffffffd8(%ebp) 0x804a44b : jle 0x804a452 0x804a44d : jmp 0x804a550 ------------------------------------------------------------------------------ Breakpoint 1, 0x0804a439 in Controllo::decodifica () Now by checking the eax register: gdb> dd 0xBFFFF14C [007B:BFFFF14C]---------------------------------------------------------[ data] BFFFF14C : 31 32 33 34 35 36 37 38 - 00 03 08 08 8C F1 FF BF 12345678........ we see a pointer to our serial string is stored in eax! Well, what follows then is first a long list of instructions, using many local variables and mov/sub/xor instructions, our Hash Algorithm. I show the output allready with my notes: 3 The hash algorithm reversed ----------------------------- I go through it line by line, for each new local variable I give a name, check if ebp gets changed, and comment what is going on. A little knowloedge og how variables and stack frames are handled in C could be an advantage: 0804A439 83 C4 10 add esp, 0x10 0804A43C C6 45 D7 01 mov [ebp-29], 0x1 ; LOCAL FOR SERIAL_VALID 0804A440 C7 45 D8 00 00 00 00 mov [ebp-28], 0x0 ; COUNTER Label_0804A447: 0804A447 83 7D D8 0E cmp [ebp-28], 0xE ; xref ( 0804A546 ) 0804A44B 7E 05 jle Label_0804A452 (0804A452) ; (near + 0x5) <---+ 0804A44D E9 FE 00 00 00 jmp Label_0804A550 (0804A550) ; (near + 0xFE) | | CHECK_SERIAL: | 0804A452 BA 0E 00 00 00 mov edx, 0xE ; xref ( 0804A44B ) ----------------+ 0804A457 89 D0 mov eax, edx ; eax = edx = 0xe 0804A459 2B 45 D8 sub eax, [ebp-28] ; 0x0e - COUNTER 0xe-0=0xe 0804A45C 8D 55 EC lea edx, [ebp-14] ; edx->"1234567890abcde" 0804A45F BE 0E 00 00 00 mov esi, 0xE ; 0804A464 89 F7 mov edi, esi ; edi = esi = esi = 0xe 0804A466 2B 7D D8 sub edi, [ebp-28] ; 0xe - COUNTER 0xe-0=0x0e 0804A469 89 7D BC mov [ebp-44], edi ; VAR1 = 0xe - COUNTER x0e 0804A46C 8D 4D EC lea ecx, [ebp-14] ; ecx->"1234567890abcde" 0804A46F 89 4D B4 mov [ebp-4C], ecx ; STRING1 => "1234567890abcde" 0804A472 8D 75 EC lea esi, [ebp-14] ; 0804A475 89 75 B8 mov [ebp-48], esi ; STRING2 => "1234567890abcde" 0804A478 8B 7D D8 mov edi, [ebp-28] ; COUNTER 0804A47B 89 7D D0 mov [ebp-30], edi ; VAR2 = COUNTER 0 0804A47E 8B 75 B4 mov esi, [ebp-4C] ; esi->"1234567890abcde" 0804A481 8B 7D BC mov edi, [ebp-44] ; edi = VAR1 0xE 0804A484 8A 0C 37 mov cl, [edi+esi] ; cl = STRING[VAR1] ;'e' ;end of str 0804A487 8B 75 B8 mov esi, [ebp-48] ; STRING 0804A48A 8B 7D D0 mov edi, [ebp-30] ; VAR2 0 0804A48D 32 0C 37 xor cl, [edi+esi] ; cl = STRING[VAR1] ^ STRING[VAR2] ; cl = STRING[0xe ] ^ STRING[0 ] 0804A490 88 0C 10 mov [eax+edx], cl ; STRING[0xe]=STRING[0xe] ^ STRING[0] ; STRING[0xe-COUNTER]= STRING[0xe-COUNTER] ^ STRING[COUNTER] ; CORRECT! 0804A493 BA 0E 00 00 00 mov edx, 0xE ; 0804A498 89 D0 mov eax, edx ; 0804A49A 2B 45 D8 sub eax, [ebp-28] ; 0xe-COUNTER 0804A49D 8D 55 EC lea edx, [ebp-14] ; STRING 0804A4A0 BE 0E 00 00 00 mov esi, 0xE ; 0804A4A5 89 F1 mov ecx, esi ; 0804A4A7 2B 4D D8 sub ecx, [ebp-28] ; 0xe - COUNTER 0804A4AA 89 4D BC mov [ebp-44], ecx ; VAR1 = 0xe - COUNTER 0804A4AD 8D 75 EC lea esi, [ebp-14] ; STRING 0804A4B0 8B 7D D8 mov edi, [ebp-28] ; COUNTER 0804A4B3 89 7D CC mov [ebp-34], edi ; VAR3 = COUNTER 0804A4B6 8B 7D CC mov edi, [ebp-34] ; VAR3 0804A4B9 C1 E7 02 shl edi, 0x2 ; VAR3 * 4 0804A4BC 03 7D CC add edi, [ebp-34] ; VAR3 * 2 + COUNTER 0804A4BF 8D 0C 3F lea ecx, [edi+edi] ; ecx= (VAR3 * 4 + COUNTER) *2 ; = (COUNTER * 4 + COUNTER) *2 ; = 5*COUNTER*2 = 10 * COUNTER 0804A4C2 89 4D C8 mov [ebp-38], ecx ; VAR4 = 10*COUNTER 0804A4C5 8A 4D C8 mov cl, [ebp-38] ; VAR4 & 0x000000FF 0804A4C8 88 4D C4 mov [ebp-3C], cl ; VAR5 = VAR4 & x000000FF 0804A4CB 66 C7 45 C2 C8 FF mov [ebp-3E], 0xFFC8 ; VAR6 = 0xFFC8 0804A4D1 66 8B 7D C2 mov di, [ebp-3E] ; VAR6 0804A4D5 8A 4D C4 mov cl, [ebp-3C] ; VAR5 & 0x000000FF ; (10*COUNTER & 0x000000FF) & 0x000000FF ; 10*COUNTER & 0x000000FF 0804A4D8 29 CF sub edi, ecx ; VAR6 - (VAR5 & 0x000000FF) ; 0xFFC8 - (10*COUNTER & 0x000000FF) 0804A4DA 66 89 7D B8 mov [ebp-48], di ; VAR7 = 0xFFC8 - (3*COUNTER & 0x000000FF) 0804A4DE 8A 4D B8 mov cl, [ebp-48] ; VAR7 0804A4E1 8B 7D BC mov edi, [ebp-44] ; edi = 0xe - COUNTER 0804A4E4 32 0C 37 xor cl, [edi+esi] ; xor cl,STRING[0xe - COUNTER] ; (0xFFC8 - (10*COUNTER & 0x0000000F)) ^ STRING[0xe - COUNTER] 0804A4E7 88 0C 10 mov [eax+edx], cl ; STRING[0x0e - COUNTER] = (0xFFC8 - (10*COUNTER & 0x0000000F)) ^ STRING[0xe - COUNTER] 0804A4EA BA 0E 00 00 00 mov edx, 0xE ; 0xe 0804A4EF 89 D0 mov eax, edx ; 0xe 0804A4F1 2B 45 D8 sub eax, [ebp-28] ; 0xe - COUNTER 0804A4F4 8D 55 EC lea edx, [ebp-14] ; STRING 0804A4F7 BE 0E 00 00 00 mov esi, 0xE ; 0xe 0804A4FC 89 F1 mov ecx, esi ; 0xe 0804A4FE 2B 4D D8 sub ecx, [ebp-28] ; 0xe - COUNTER 0804A501 89 4D BC mov [ebp-44], ecx ; VAR1 = 0xe - COUNTER 0804A504 8D 75 EC lea esi, [ebp-14] ; STRING 0804A507 8A 4D D8 mov cl, [ebp-28] ; COUNTER & 0000000F 0804A50A FE C1 inc cl ; INC COUNTER 0804A50C 88 4D C1 mov [ebp-3F], cl ; VAR10 = COUNTER+1 ;;;;;;;;;;;;;;;;;;;;;;;;; 0804A50F 8A 4D C1 mov cl, [ebp-3F] ; VAR10 0804A512 8B 7D BC mov edi, [ebp-44] ; VAR1 0804A515 02 0C 37 add cl, [edi+esi] ; cl + STRING[0x0e - COUNTER] ; VAR10 + STRING[0x0e - COUNTER] ; COUNTER+1 + STRING[0x0e - COUNTER] 0804A518 88 0C 10 mov [eax+edx], cl ; STRING[0xe - COUNTER] = COUNTER+1 + STRING[0x0e - COUNTER] 0804A51B BA 0E 00 00 00 mov edx, 0xE ; 0xe 0804A520 89 D0 mov eax, edx ; 0xe 0804A522 2B 45 D8 sub eax, [ebp-28] ; eax = 0xe - COUNTER 0804A525 8D 55 DC lea edx, [ebp-24] ; STRING2??? 0804A528 BE 0E 00 00 00 mov esi, 0xE ; 0x0e 0804A52D 89 F1 mov ecx, esi ; 0x0e 0804A52F 2B 4D D8 sub ecx, [ebp-28] ; 0x0e - COUNTER 0804A532 8D 75 EC lea esi, [ebp-14] ; STRING 0804A535 8A 04 10 mov al, [eax+edx] ; STRING2[0x0e - COUNTER] 0804A538 3A 04 31 cmp al, [ecx+esi] ; STRING[0xe - COUNTER] 0804A53B 74 06 jz Label_0804A543 (0804A543) ; (near + 0x6) 0804A53D C6 45 D7 00 mov [ebp-29], 0x0 ; SERIAL_VALID = 0 0804A541 EB 0D jmp Label_0804A550 (0804A550) ; (near + 0xD) ; if( STRING2[0x0e - COUNTER] != STRING[0xe - COUNTER]) SERIAL_VALID = 0; Label_0804A543: 0804A543 FF 45 D8 inc [ebp-28] ; xref ( 0804A53B ) 0804A546 E9 FC FE FF FF jmp Label_0804A447 (0804A447) ; (near - 0x104) ; COUNTER ++; loop 0804A54B 90 nop 0804A54C 8D 74 26 00 lea esi, [esi] Label_0804A550: 0804A550 80 7D D7 01 cmp [ebp-29], 0x1 ; xref ( 0804A44D 0804A541 ) 0804A554 75 1F jnz Label_0804A575 (0804A575) ; (near + 0x1F) I did the whole comments by looking at the deadlisting, just knowing epb-14 holds our serial. While commenting everything got more and more clear. Especially the naming of the variables is very useful. So what we can see is that first the counter is set to zero, the valid_serial flag is set: 0804A43C C6 45 D7 01 mov [ebp-29], 0x1 ; LOCAL FOR SERIAL_VALID 0804A440 C7 45 D8 00 00 00 00 mov [ebp-28], 0x0 ; COUNTER Then the main loop is done by: 0804A447 83 7D D8 0E cmp [ebp-28], 0xE ; xref ( 0804A546 ) 0804A44B 7E 05 jle Label_0804A452 (0804A452 so 0xe bytes are processed. the loop body starts at CHECK_SERIAL: | 0804A452 where every single byte of the serial is hashed. It is then compared against the corresponding byte at the same offset in STRING2. Now without digging too much into this algorithm, what is STRING2 ??? It could be a hardcoded hashed (encrypted) serial. Well, lets look how it looks: So I am breaking at the instruction after the pointer to STRING2 was set up 0804A525 8D 55 DC lea edx, [ebp-24] ; STRING2??? and look at the edx register: _______________________________________________________________________________ eax:0000000E ebx:BFFFF47C ecx:0000009D edx:BFFFF13C eflags:00200302 esi:BFFFF14C edi:0000000E esp:BFFFF0F8 ebp:BFFFF160 eip:0804A528 cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I T s z a p c [007B:BFFFF0F8]---------------------------------------------------------[stack] BFFFF128 : 00 00 00 00 00 00 00 00 - 00 00 00 00 8C 7D 1D 01 .............}.. BFFFF118 : C8 FF FF BF 0E 00 00 00 - 40 01 C8 FF 00 40 0E 40 ........@....@.@ BFFFF108 : 8C F1 FF BF 28 F1 FF BF - E6 11 43 40 4C F1 FF BF ....(.....C@L... BFFFF0F8 : 8C 7D 1D 40 FC A3 04 08 - FC A3 04 08 0C 00 00 00 .}.@............ [007B:BFFFF14C]---------------------------------------------------------[ data] BFFFF14C : 31 32 33 34 35 36 37 38 - 39 30 61 62 63 64 9D 00 1234567890abcd.. BFFFF15C : 94 F1 FF BF 94 F1 FF BF - C8 33 0C 40 7C F4 FF BF .........3.@|... [0073:0804A528]---------------------------------------------------------[ code] 0x804a528 : mov $0xe,%esi 0x804a52d : mov %esi,%ecx 0x804a52f : sub 0xffffffd8(%ebp),%ecx 0x804a532 : lea 0xffffffec(%ebp),%esi 0x804a535 : mov (%eax,%edx,1),%al 0x804a538 : cmp (%ecx,%esi,1),%al ------------------------------------------------------------------------------ 0x0804a528 in Controllo::decodifica () gdb> dd 0xBFFFF13C [007B:BFFFF13C]---------------------------------------------------------[ data] BFFFF13C : CF DC DF CE 05 CC CB 8A - 8F 9A A9 AD BB B9 CE 08 ................ BFFFF14C : 31 32 33 34 35 36 37 38 - 39 30 61 62 63 64 9D 00 1234567890abcd.. Aha, this looks not very self explaining. So let's look if/where ebp-24 is accessed before. We find this by scrolling upwards in the disassembly at: 0804A408 8D 7D DC lea edi, [ebp-24] 0804A40B BE 3C AB 04 08 mov esi, 0x804AB3C 0804A410 FC cld 0804A411 B9 03 00 00 00 mov ecx, 0x3 0804A416 F3 A5 rep:movsd 0804A418 66 A5 movsd 0804A41A A4 movsb gdb> dd 0x804AB3C [007B:0804AB3C]---------------------------------------------------------[ data] 0804AB3C : CF DC DF CE 05 CC CB 8A - 8F 9A A9 AD BB B9 CE 59 ...............Y Voila! So this is our encrypted Serial! And we can see it is directly read out of the executables .rodata section :) So what we know by now is, that our serial runs byte for byte through some "math instruction", and is then checked against a hardcoded previously hashed serial. We know the hash of this serial, but need to know it's plain form. The checking is done "backwards" from the end to the start of the string. Also it is overlapping: previously calculated bytes are used for further calculation. But we will come to all this later. First we want to check if we produced the correct algorithm with our comments... And we need to reverse the algorhitm :) I copyied out my comments from the deadlisting. It results in those 3 lines: I) STRING[0xe - COUNTER] = STRING[0xe - COUNTER] ^ STRING[COUNTER]; II) STRING[0x0e - COUNTER] = (0xFFC8 - (10*COUNTER & 0x0000000F)) ^ STRING[0xe - COUNTER]; III) STRING[0xe - COUNTER] = COUNTER+1 + STRING[0x0e - COUNTER]; Those lines we can express as STRING[0xe - COUNTER] = COUNTER+1 + ( (0xFFC8 - (10*COUNTER & 0x000000FF)) ^ ( STRING[0xe - COUNTER] ^ STRING[COUNTER]) ); by substituting as shown below: now when we substitute I -> II, and II -> III we get: I -> II) II) S[0xe-C] = (0xFFC8 - (10*C & 0xff)) ^ S[0xe-C] ^-------------------- I IIa) S[0xe-C] = (0xFFC8 - (10*C & 0xff)) ^ ( S[0xe-C] ^ S[C] ) --------------------------------------------------------------- IIa -> III III) S[0xe-C] = C + 1 + S[0xe-C] ^------------------------------------ IIa IIIa) S[0xe-C] = C + 1 + (0xFFC8 - (10*C & 0xff)) ^ ( S[0xe-C] ^ S[C] ) ========================================================================= We also can see that if one byte (even the 1st) does not match the byte in the Hash we compare, the function exits and displays "No No No ...". What I am doing now is to enter any serial like "000000000000000", and let it run through the algo. Then I code my own little C program with the calculation as above and check if it outputs the same hash. OK, so I have entered "000000000000000" into the serial field and set a breakpoint at 0x0804a53b, where every single hashed byte is compared. Of course our serial will not match the precomputed hash, so we need to change the zeroflag for each loop. Then I display the data via dd command (ebp-14): (bp 0x0804a53b, cfz if needed, c, step through 16 times) "000000000000000" gives BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 30 30 C9 00 00000000000000.. BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 30 C0 C9 00 0000000000000... BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 B7 C0 C9 00 000000000000.... BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 AE B7 C0 C9 00 00000000000..... BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 A5 AE B7 C0 C9 00 0000000000...... BFFFF14C : 30 30 30 30 30 30 30 30 - 30 9C A5 AE B7 C0 C9 00 000000000....... BFFFF14C : 30 30 30 30 30 30 30 30 - 93 9C A5 AE B7 C0 C9 00 00000000........ BFFFF14C : 30 30 30 30 30 30 30 8A - 93 9C A5 AE B7 C0 C9 00 0000000......... BFFFF14C : 30 30 30 30 30 30 E4 8A - 93 9C A5 AE B7 C0 C9 00 000000.......... BFFFF14C : 30 30 30 30 30 CC E4 8A - 93 9C A5 AE B7 C0 C9 00 00000........... BFFFF14C : 30 30 30 30 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 0000............ BFFFF14C : 30 30 30 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 000............. BFFFF14C : 30 30 E4 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 00.............. BFFFF14C : 30 C4 E4 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 0............... BFFFF14C : D4 C4 E4 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 ................ So our hash of "000000000000000" is D4 C4 E4 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 So I write the little program which uses the same hash algo as we have reversed: char STRING[] = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', 0 }; int main(int c, char **a) { int COUNTER; for(COUNTER=0; COUNTER < 0xf; COUNTER++) STRING[0xe - COUNTER] = COUNTER+1 + ( (0xFFC8 - (10*COUNTER & 0x000000FF)) ^ ( STRING[0xe - COUNTER] ^ STRING[COUNTER]) ); printf("%s\n", STRING); } Now by running ./blaadeyou | hexdump -C 00000000 d4 c4 e4 d0 fc cc e4 8a 93 9c a5 ae b7 c0 c9 0a |ÔÄäÐüÌä...¥®·ÀÉ.| and gdb showed: BFFFF14C : D4 C4 E4 D0 FC CC E4 8A - 93 9C A5 AE B7 C0 C9 00 ................ we see the algorithm is correct. 4 Cracking the hash algorithm ----------------------------- Now we must find a string which when run through this algorithm produces the bytes: CF DC DF CE 05 CC CB 8A - 8F 9A A9 AD BB B9 CE as this is our stored hash value which is compared against. Brute forcing of 16 bytes would take aaaages and is not an option here! 4.1 Understanding the algorithm ------------------------------- In order to crack this hash, we need to understand it. Basically a loop is executed over the whole length of the serial (STRING). During this loop the STRING is modified. The modification goes from right to left (end of string to start of string). S[0xe-C] = C + 1 + (0xFFC8 - (10*C & 0xff)) ^ ( S[0xe-C] ^ S[C] ) The counter C starts by 0 and increases until 0xe. So the last byte of the string gets XORed with the first byte and some other "modifications" are done. Then this byte gets stored into the STRING again. When thinking a little bit: after the first 8 iterations, the LAST 8 bytes are all- ready modified. Now loop 9 modifies the byte 0xe-9 = 5. This byte is S[0xe-9] and is XORed with S[9]. Now byte 9 WAS ALLREADY modified by the first 8 iterations! And this modification used the UNMODIFIED byte of S[0xe-9]. Seeing our precomputed hash, we see only all MODIFIED bytes (of course). So if we start from end to the beginning of the string, and trying to somehow decrypt it, it is impossible. We do not know any plain bytes, which are necessary to decrypt using the algorithm. We just have the hash (CF DC DF CE 05 CC CB 8A - 8F 9A A9 AD BB B9 CE) I give an example using the known output of "000000000000000" At the beginning, the counter is 0 and all bytes in STRING are '0' (0x30). BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 30 30 30 00 00000000000000.. +-------------STRING--------------------------+ | | String[0xe - 0xe] STRING[0xe - 0] Neg offs: e d c b a 9 8 7 6 5 4 3 2 1 0 Offs: 0 1 2 3 4 5 6 7 8 9 a b c d e 0th iteration: S[0xe-0] = 0 + 1 + (0xC8 - (10*0 & 0xff)) ^ ( S[0xe-0] ^ S[0] ) S[0xe] = 1 + (0xc8 ) ^ ( S[0xe] ^ S[0] ) S[0xe] = 0xc9 ^ ( 0x30 ^ 0x30 ) // 0x30 XOR 0x30 == 0 S[0xe] = 0xc9 So the last byte gets overwritten with 0xC9: BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 30 30 C9 00 00000000000000.. 1st iteration: S[0xe-1] = 1 + 1 + (0xC8 - (10*1 & 0xff)) ^ ( S[0xe-1] ^ S[1] ) S[0xe-1] = 2 + (0xc8 - 10 ) ^ ( S[0xe-1] ^ S[1] ) BFFFF14C : 30 30 30 30 30 30 30 30 - 30 30 30 30 30 C0 C9 00 0000000000000... So the last but 1 byte gets overwritten by 0xc0. ... and so on. Now the problem is easily seen what happend at the very end? 0xeth iteration: S[0xe-0xe] = 0xe + 1 + (0xC8 - (10*1 & 0xff)) ^ ( S[0xe-0xe] ^ S[0xe] ) S[0] = 0xf + (0xc8 - 10*0xf ) ^ ( S[0] ^ S[0xe] ) ^-------------- THIS... byte S[0xe] was computed by our 0th iteration. Now the 0th byte gets XORed with this last byte. And this needed for calculation the 0th byte plain serial. After this 0xeth instruction, the 0th byte is then also modified and the plain bytes of the serial are not known anymore. So when having a hash, the first 7 bytes are dependent on the last 7 bytes and vice versa. This is why I initially thought this algorithm is irreversible. But looking more into detail, it is really easy: 4.2 Finding the weak point -------------------------- Actually the weak point is obviuos after having really understood the algo- rithm: What happens at the 7th loop? S[0xe-C] = C + 1 + (0xFFC8 - (10*C & 0xff)) ^ ( S[0xe-C] ^ S[C] ) S[0xe-7] = 7 + 1 + (C8 - 70) ^ ( S[0xe-7] ^ S[7] ) // 0xe - 7 = 7 S[7] = 7 + 1 + (C8 - 70) ^ ( S[7] ^ S[7] ) Woooooooow! We found a part of the loop, where the result is independent from any other part of the STRING! S[7] = 7 + 1 + (C8 - 70) ^ ( S[7] ^ S[7] ) S[7] = 8 + 130 ^ ( 0 ) So possibly the byte S[7] can be random, as it is always XORed against itself. What happens after the 7th loop? Also we know, that for the last 7 loops 8....0xe, there the previous computed last 7 bytes of the string are used for the xor. Now those last 7 bytes we have allready precalculated as it is our hash. So for the last 7 bytes of HASH we can assume (C from 8 to 0xe): HASH[0xe-C] = C + 1 + (0xFFC8 - (10*C & 0xff)) ^ ( SERIAL[0xe-C] ^ HASH[C] ) HASH[0xe-C] - (C + 1) = (0xFFC8 - (10*C & 0xff)) ^ ( SERIAL[0xe-C] ^ HASH[C] ) knowing the byte ranges (Cmax = 0xe, 10*14 =140) we can simplify HASH[0xe-C] - (C + 1) = (C8 - 10*C) ^ ( SERIAL[0xe-C] ^ HASH[C] ) (HASH[0xe-C] - (C + 1)) ^ (C8 - 10*C) = SERIAL[0xe-C] ^ HASH[C] (HASH[0xe-C] - (C + 1)) ^ (C8 - 10*C) ^ HASH[C] = SERIAL[0xe-C] =============================================================== This means we got the plain first 7 bytes of our serial! Now knowing the plain first 7 bytes of the serial, we know all we need for the last 7: (C from 0 to 7) HASH[0xe-C] = C + 1 + (0xFFC8 - (10*C & 0xff)) ^ ( SERIAL[0xe-C] ^ SERIAL[C] ) HASH[0xe-C] - (C + 1) = (0xFFC8 - (10*C & 0xff)) ^ ( SERIAL[0xe-C] ^ SERIAL[C] ) HASH[0xe-C] - (C + 1) = (C8 - 10*C) ^ ( SERIAL[0xe-C] ^ SERIAL[C] ) (HASH[0xe-C] - (C + 1)) ^ (C8 - 10*C) = SERIAL[0xe-C] ^ SERIAL[C] (HASH[0xe-C] - (C + 1)) ^ (C8 - 10*C) ^ SERIAL[C] = SERIAL[0xe-C] ================================================================= Voila! And what about byte 7? It's random! :) We will verify with our serial generator (see 5) ./blaadyou i: 08, b: 35 i: 09, b: 36 i: 0a, b: 37 i: 0b, b: 35 i: 0c, b: 39 i: 0d, b: 31 i: 0e, b: 32 i: 00, b: 37 i: 01, b: 38 i: 02, b: 35 i: 03, b: 36 i: 04, b: 33 i: 05, b: 34 i: 06, b: 31 i: 07, b: 2a STRING: 2195765*1436587 Testing ....OK! So our own test said the serial is valid... Now try it whith the blaadme2 crackme :) 5 Serial generator in C ----------------------- /* -- 0xf001's solution to blaadme2 crackme -- */ unsigned char Hash[] = { 0xCF, 0xDC, 0xDF, 0xCE, 0x05, 0xCC, 0xCB, 0x8A, 0x8F, 0x9A, 0xA9, 0xAD, 0xBB, 0xB9, 0xCE }; unsigned char STRING[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int check(char *c) { int r = 1; unsigned char mybuf[16]; int i,COUNTER; for(i=0;i<16;i++)mybuf[i]=c[i]; for(COUNTER=0; COUNTER < 0xf; COUNTER++) mybuf[0xe - COUNTER] = COUNTER+1 + ( (0xFFC8 - (10*COUNTER & 0x000000FF)) ^ ( mybuf[0xe - COUNTER] ^ mybuf[COUNTER]) ); for(i=0;i<0xf;i++)if(mybuf[i] != Hash[i])r=0; return r; } int main(int c, char **a) { int i; unsigned char b; for(i=8; i < 0xf; i++) { b = (Hash[0xe - i] - ( i + 1 )) ^ (0xC8 - 10 * i) ^ Hash[i]; printf("i: %02x, b: %02x\n", i, b); STRING[0xe - i ] = b; } STRING[7]='*'; for(i=0; i < 8; i++) { b = (Hash[0xe - i] - ( i + 1 )) ^ (0xC8 - 10 * i) ^ STRING[i]; printf("i: %02x, b: %02x\n", i, b); STRING[0xe - i ] = b; } printf("STRING: %s\n", STRING); printf("Testing ...."); ( check(STRING) ) ? printf("OK!\n") : printf("aaargh!\n"); } 6 Final words ------------- Finally we solved this algorithm! :) I hope you enjoyed this tutorial, there is not much linux specific in here again, but the crackme is definately biiiig fun! Thanks to blAAd! 0xf001