How to reverse our target creating a good Brute Force Cracker ! |
Not Assigned | |
by fravia+ | ||
fra_00xx 981008 aLoNg3x 0100 NA PC |
"Well, probably the essay can finish here, but we're reverser and not simple
byte changer, and so we must go ahead with this work !" As you will see, if you read the following, aLoNg3x goes to great length to explain how a part of the code of his target works... even if this has no importance whatsoever in order to deprotect it (infact Mirc's protection scheme could and should figure in the most stupid section, btw if you perform a simple search you'll find either 10870 pages (using the most simple search) or 61 pages (using a more refined search), with ready-made mirc's cracks for the lamers, so I think that this essay will be more useful for the protectors that for anyone wanting to steal software...) But "we're reverser and not simple byte changer" and hey presto! There is a decryption routine to investigate, and a "brute force cracker" probe to build. The sort of material that any reversers will be able to use on many other targets (not necessarily protection schemes, btw) and that any protectors should, by all means, duly consider when preparing new schemes... Enjoy! | |
So i've decided to try to work on this new feature... Ok. I've locked the proggie with the passwd "fabio" (hmmm quite nice this name :p). That's all... and i've tried to look around to find this "stupid" word written somewhere in a plain text format in the configuration files in the mIRC directory. D'oh :( Nothing of this.. and now ? Let's run the Regedit. and under the Key "HKCU\Software\mirc" we can find something of really "useful"... There is a lock key composed by a string ('\0' terminated) of numbers... in my case it's "1657786368,192" - Then let's replace it with an idiot "0" - Restart mIRC and we will see in the options that the proggie isn't locked anymore. Well, probably the essay can finish here, but we're reverser and not simple byte changer, and so we must go ahead with this work ! :P. Let's load our favourite Debugger (SoftICE of course) and... Please. wait a moment because i'm going to restart my PiCci' with sice :D [Three minutes later...] Ok... i'm back again with my dear old (and very slow) P100 =) First of all we must set again the lock code and set ON the checkBox "Ask for password on startup". Then terminate mIRC and press CTRL+D to enter in sice. We have set ON this checkbox in order to have a dialog box (waiting the password) when we load mirc32.exe. We can set a "bpx advapi32!RegQueryValueA" and run mIRC. The proggie will break after some other breakpoints in this position: :0049D677 E82C7C0300 Call RegQueryValueA This simple call read our good code 1657786368,192 from the windows register. (in order to understand that this is the right call you must take a look to the pushed arguments. :0049D67C 85C0 test eax, eax ; Success ??? :0049D67E 7551 jne 0049D6D1 ; NO Jump away... :0049D680 6A2C push 0000002C ; push ',' :0049D682 68E4504E00 push 004E50E4 :0049D687 E840B60200 call 004C8CCC ; Search ! After this call EAX points to the comma (',') in the string readed from the win register. :0049D68C 83C408 add esp, 00000008 :0049D68F 890424 mov dword ptr [esp], eax ; ESP->"comma" :0049D692 833C2400 cmp dword ptr [esp], 00000000 :0049D696 7439 je 0049D6D1 ; Error :( :0049D698 8B0C24 mov ecx, dword ptr [esp] :0049D69B C60100 mov byte ptr [ecx], 00 ; ','='\0' This mov instruction substitutes the comma (',') with a Zero ('\0') to create an useful asciiZ(ero terminated) string. :0049D69E FF0424 inc dword ptr [esp] ; next char.. :0049D6A1 8B0424 mov eax, dword ptr [esp] :0049D6A4 803800 cmp byte ptr [eax], 00 ; IS IT A '\0' ? :0049D6A7 740F je 0049D6B8 ; YES Jump away... :0049D6A9 8B1424 mov edx, dword ptr [esp] :0049D6AC 52 push edx :0049D6AD E876350300 call 004D0C28 This call puts in EAX the number after the "old" comma char. :0049D6B2 59 pop ecx :0049D6B3 A3B0E64D00 mov dword ptr [004DE6B0], eax :0049D6B8 6A0A push 0000000A :0049D6BA 8D4C2404 lea ecx, dword ptr [esp+04] :0049D6BE 51 push ecx :0049D6BF 68E4504E00 push 004E50E4 :0049D6C4 E86F4A0300 call 004D2138 This call puts in eax the number (of course in HEX format) before the "old" comma char. :0049D6C9 83C40C add esp, 0000000C :0049D6CC A3ACE64D00 mov dword ptr [004DE6AC], eax This instrucion puts in a 4 bytes variable in 004DE6AC the hex number. (it's built with the password that we've setted in the options dialog) Ok.. quite simple.. now we'll set a "BPR 004DE6AC 004DE6B0 R". Placing this breakpoint on range we'll see where our "cripted" locking code is readed from the proggie. and then: -> in 0049D787 -> it is compared with 0 (to check if mIRC is locked or no..) Now the proggie reaches the request of the UN-locking code.. if you enter a dummie code like 'cacca' you will break again in our target here: -> in 0049D541 -> it is compared with eax If you take a look in this piece of code you'll see this: :0049D535 6A20 push 00000020 :0049D537 68E4504E00 push 004E50E4 ; pointer to "cacca",0 :0049D53C E8BFE1F6FF call 0040B700 ; work on our unlocking code :0049D541 3B05ACE64D00 cmp eax, dword ptr [004DE6AC] ; check equ :0049D547 7512 jne 0049D55B ; naaahh bad code... :p This is really simple... :p - it seems to be like a common keygenerator routine.. Let's navigate inside the call to 0040B700 to see the "cripting" procedure: :0040B700 55 push ebp :0040B701 8BEC mov ebp, esp :0040B703 51 push ecx :0040B704 53 push ebx :0040B705 56 push esi :0040B706 8B750C mov esi, dword ptr [ebp+0C] :0040B709 BB185F4E00 mov ebx, 004E5F18 :0040B70E B91C5F4E00 mov ecx, 004E5F1C :0040B713 33C0 xor eax, eax :0040B715 8903 mov dword ptr [ebx], eax :0040B717 33D2 xor edx, edx :0040B719 8911 mov dword ptr [ecx], edx :0040B71B 8B4508 mov eax, dword ptr [ebp+08] :0040B71E A3205F4E00 mov dword ptr [004E5F20], eax :0040B723 EB1F jmp 0040B744 ==========================\ :0040B725 8B13 mov edx, dword ptr [ebx] <=====\ | :0040B727 81E2000000FF and edx, FF000000 | | :0040B72D C1EA18 shr edx, 18 | | :0040B730 8911 mov dword ptr [ecx], edx | | :0040B732 25FF000000 and eax, 000000FF | | :0040B737 0103 add dword ptr [ebx], eax | | :0040B739 0113 add dword ptr [ebx], edx | | :0040B73B C12308 shl dword ptr [ebx], 08 | | :0040B73E FF05205F4E00 inc dword ptr [004E5F20] | | :0040B744 8B15205F4E00 mov edx, dword ptr [004E5F20] <="=|=====/" :0040B74A 8A02 mov al, byte ptr [edx] | :0040B74C 84C0 test al, al | :0040B74E 75D5 jne 0040B725 ====================/ :0040B750 33D2 xor edx, edx :0040B752 8915245F4E00 mov dword ptr [004E5F24], edx :0040B758 EB11 jmp 0040B76B ==========================\ :0040B75A 8B03 mov eax, dword ptr [ebx] <=======\ | :0040B75C 83E001 and eax, 00000001 | | :0040B75F 8901 mov dword ptr [ecx], eax | | :0040B761 D12B shr dword ptr [ebx], 1 | | :0040B763 0103 add dword ptr [ebx], eax | | :0040B765 FF05245F4E00 inc dword ptr [004E5F24] | | :0040B76B BA20000000 mov edx, 00000020 <="=============|=====/" :0040B770 2BD6 sub edx, esi | :0040B772 3B15245F4E00 cmp edx, dword ptr [004E5F24] | :0040B778 7FE0 jg 0040B75A =====================/ :0040B77A 8975FC mov dword ptr [ebp-04], esi :0040B77D DB45FC fild dword ptr [ebp-04] :0040B780 83C4F8 add esp, FFFFFFF8 :0040B783 DD1C24 fstp qword ptr [esp] :0040B786 6800000040 push 40000000 :0040B78B 6A00 push 00000000 :0040B78D E85E510C00 call 004D08F0 :0040B792 83C410 add esp, 00000010 :0040B795 E8A24F0C00 call 004D073C :0040B79A 48 dec eax :0040B79B 2103 and dword ptr [ebx], eax :0040B79D 8B03 mov eax, dword ptr [ebx] ; Very important ! :0040B79F 5E pop esi ; infact EAX is :0040B7A0 5B pop ebx ; compared after :0040B7A1 59 pop ecx ; the RET. :0040B7A2 5D pop ebp :0040B7A3 C20800 ret 0008 If you look carefully in this function and debug into it sometimes, you will understand that in "dword ptr [ebx]" is stored the cripted unlocking inserted code.. and.. very IMPORTANT.. the condition of the JG is never verified.. and also the value pointed by EBX (that is the cripted unlocking code) doesn't change from the "JG" to the "MOV EAX, DWORD PTR [EBX]". Quite good... so we can ignore a big piece of asm code.. I've also ignored the instruction about the Stack... like the push & pop And i've changed these memory locations: DWORD PTR [EBX] ==> CodiceOK DWORD PTR [ECX] ==> Check DWORD PTR [EBP+08] == PassTrial Moreover I've inserted the First_Jmp & Second_Jmp labels. CRYPT_MIRG3X.ASM : _________________________________ _ _ _ mov CodiceOK, 0 mov Check, 0 mov eax, offset PassTrial mov Ofs_Pass, eax jmp Second_Jmp First_Jmp: mov edx, CodiceOK and edx, FF000000 shr edx, 18 mov Check, edx and eax, 000000FF add CodiceOK, eax add CodiceOK, edx shl CodiceOK, 08 inc Ofs_Pass Second_Jmp: mov edx, Ofs_Pass mov al, byte ptr [edx] test al, al jne First_Jmp mov eax, CodiceOK _________________________________ _ _ _ Oh Yeah.. a very little function, totally coded in pure Assembler :D Of course this code isn't optimized in a good way, but i'm very lame in ASM coding :P Now we need a routine to generate every kind of word.. ...from "a" ... to "zzzzzzzz" ... I've supposed to use words up to 8 CHAR and I've used chars from 'a' to 'z'. Let's make our first .... BRUTE FORCE CRACKER !!! Hmmhhh :( i don't code since a lot of time, and this work is quite hard for my poor brain.. However i've write down this UGLY routine in C: BRUTE_MIRG3X.C : _________________________________ _ _ _ #define MAX_CHAR 8 #define FIRSTCHAR 'a' #define LASTCHAR 'z' void main(void) { // BEGIN int number_of_char=1; int counter,toinc; char code[MAX_CHAR]=""; code[0]=FIRSTCHAR; do { // CheckUnLockCode... toinc=1; counter=0; while (code[counter]==LASTCHAR) { if (code[counter+1]=='\0') { ++number_of_char; code[counter+1]=FIRSTCHAR; code[counter]=FIRSTCHAR; // toinc=0; } else { code[counter]=FIRSTCHAR; counter++; } } if (toinc) ++code[counter]; } while (number_of_char<=max_char); } // END OF FILE _________________________________ _ _ _ I think that it works... it isn't a good code, but the most important thing is that it seems to work :p The last step is to create the complete ASM source code of our application. So we can use the file "crypt_mirg3x.asm" and must generate manually the bruteforcing routine in ASM, translating with our hands the C code. I've make this work, and I hope that it's correct :P MIRG3X.ASM : _________________________________ _ _ _ .386 ; CHOOSE YOUR PROCESSOR .model flat, stdcall ; GENERAL CONSTANTS NULL equ 0h ; CONSTANTS ABOUT THE READING OPERATIONS FROM THE REGISTRY KEY_QUERY_VALUE equ 1h HKEY_CURRENT_USER equ 80000001h ERROR_SUCCESS equ 0h REG_NONE equ 0h MAXLEN equ 40h COMMA equ ',' ; CONSTANTS ABOUT THE MESSAGE BOXES MB_OK equ 0h ; CONSTANTS ABOUT THE BRUTE-FORCING ROUTINE MAX_CHAR equ 8 FIRSTCHAR equ 'a' LASTCHAR equ 'z' ; USER32 extrn MessageBoxA :PROC ; KERNEL32 extrn ExitProcess :PROC ; ADVAPI32 extrn RegOpenKeyExA :PROC extrn RegQueryValueExA :PROC extrn RegCloseKey :PROC .data hRegister dd 0h KeyToOpen db "Software\mIRC\lock",0 KeyName db 0 ReadedKey db 40 dup(MAXLEN) SizeKey dd MAXLEN ; SPONSOR !!! :D Ring0txt db "This little program has been totally \ coded in Assembler by aLoNg3x \ (along3x@geocities.com) ... \ proudly member of RingZer0 \ http://ringzer0.cjb.net ...",0 Ring0cpt db "aLoNg3x - http://ringzer0.cjb.net :p",0 ; TEXT OF THE MESSAGES OpErr db "No key registry found !",0 InErr db "Invalid key in the register !",0 NoErr db "Your mIRC don't have any lock password !",0 UnErr db "Sorry.. I haven't found any \ valid unlocking password :_( !",0 Result db "The unlock password of your mIRC \ is: ", MAX_CHAR+1 dup(0) LenRes dd 37 ; THIS IS THE LENGHT OF "The unlock... " ; CAPTION OF THE MESSAGES DoneCpt db "Brute Force Cracked !",0 FailCpt db "Cracking failed... :_(",0 ; VARIABLEs USED BY THE BRUTEFORCER AND BY THE CRYPTER PassTrial db MAX_CHAR+1 dup(0) CodiceOK dd 0h Check dd 0h Ofs_Pass dd 0h Registro dd 0h .code _start: call MessageBoxA, NULL, offset Ring0txt, offset Ring0cpt, MB_OK ; THIS PIECE OF CODE READ FROM THE REGISTER THE LOCK KEY call RegOpenKeyExA, HKEY_CURRENT_USER, offset KeyToOpen, NULL, \ KEY_QUERY_VALUE, offset hRegister cmp eax, ERROR_SUCCESS jne Open_Error ; IF THE KEY CANNOT BE OPEN THEN THE PROGRAM JUMPS TO THE ERROR MESSAGE call RegQueryValueExA, hRegister, offset KeyName, NULL, REG_NONE, \ offset ReadedKey, offset SizeKey call RegCloseKey, hRegister ; THIS PIECE OF CODE IS USEFUL TO STORE IN EBX THE LENGHT OF THE NUMBER ; BEFORE THE ',' mov edi, offset ReadedKey xor ebx, ebx NextChar: cmp byte ptr [edi+ebx],COMMA je Substitute ; IF THE ACTUAL CHAR IS ',' THE PROGRAM INSERT A '\0' cmp byte ptr [edi+ebx], 0 je Invalidkey_Error ; IF THE PROG REACH THE ZERO TERMINATING CHAR THEN THERE IS AN ERROR IN THE KEY inc ebx jmp NextChar ; GO TO THE NEXT CHAR ; THIS PIECE OF CODE SUBSTITUTES THE ',' WITH THE ZERO ASCII CODE Substitute: mov byte ptr [edi+ebx],0 ; THIS PIECE OF CODE TRANSLATES THE NUMBER IN THE ASCIIZ STRING INTO AN HEX NUMBER IN EAX xor ecx,ecx inc ecx PrevChar: dec ebx xor edx,edx mov dl, byte ptr [edi+ebx] sub dl,30h mov esi,ecx imul esi,edx add eax,esi imul ecx,0Ah test ebx,ebx jne PrevChar ; IF THE PROG HASN'T REACHED THE FIRST FIGURE OF THE NUMBER THE THE ; PROG GO TO THE PREVIOUS CHAR cmp eax, 0 je Nokey_Error ; IF THE VALUE OF THE READED KEY IS 0 THEN THERE ISN'T A LOCK PASSWORD ; AND THE PROGRAM JUMPS TO THE ERROR MESSAGE mov Registro, eax ; ____________________________________________ _ _ _ ; / ; THIS ROUTINE MAKES A BRUTE FORCE CRACK COMPARING THE GENERATED CODE WITH THE ; VALUE OF "Registro" VARIABLE, READED FROM THE Windows Register xor esi, esi mov edi, offset PassTrial mov byte ptr [edi], FIRSTCHAR ; _______________ _ _ _ ; / ; THIS IS THE CORE OF THE CRYPTING ROUTINE Punkreas: mov CodiceOK, NULL mov Check, NULL mov eax, offset PassTrial mov Ofs_Pass, eax jmp Second_Jmp First_Jmp: mov edx, CodiceOK and edx, 0FF000000h shr edx, 18h mov Check, edx and eax, 000000FFh add CodiceOK, eax add CodiceOK, edx shl CodiceOK, 08 inc Ofs_Pass Second_Jmp: mov edx, Ofs_Pass mov al, byte ptr [edx] test al, al jne First_Jmp mov eax, CodiceOK ; \_______________ _ _ _ ; END OF THE CRYPTING ROUTINE. THE RESULT IS IN EAX cmp eax, Registro jne Continue jmp Show_The_Code Continue: xor ebx, ebx inc ebx xor eax, eax Pippo: cmp byte ptr [edi+eax], LASTCHAR jne PornoRiviste cmp byte ptr [edi+eax+1], 00h jne Shandon inc esi mov byte ptr [edi+eax+1], FIRSTCHAR mov byte ptr [edi+eax], FIRSTCHAR xor ebx, ebx jmp Pippo Shandon: mov byte ptr [edi+eax], FIRSTCHAR inc eax jmp Pippo PornoRiviste: test bl, bl je Fine inc byte ptr [edi+eax] Fine: cmp esi, MAX_CHAR jle Punkreas jmp Uncracked_Error ; \____________________________________________ _ _ _ ; END OF THE BRUTE-FORCING. IF THE PROGRAM HAS NOT FOUND ANY VALID KEY THEN THE ; PROGRAM SHOW AN ERROR MESSAGE ; _______________________ _ _ _ ; / ; THIS ROUTINE PLACES AT THE END OF THE "Result" MESSAGE THE CORRECT LOCK ; PASSWORD AND SHOW THE RESULT OF THE CRACK Show_The_Code: xor ebx, ebx dec ebx mov eax, offset Result add eax, LenRes OtherChars: inc ebx mov cl, byte ptr [edi+ebx] mov byte ptr [eax+ebx], cl cmp byte ptr [edi+ebx], 0h jne OtherChars ; IF THE CURRENT CHAR IS != '\0' THEN THERE ARE OTHER CHARS THAT MUST BE READED call MessageBoxA, NULL, offset Result, offset DoneCpt, MB_OK jmp End_App ; \_______________________ _ _ _ ; END OF THE ROUTINE SHOWING THE CORRECT UNLOCKING CODE ; ____________ _ _ _ ;/ ; ERROR MESSAGES Uncracked_Error: call MessageBoxA, NULL, offset UnErr, offset FailCpt, MB_OK jmp End_App Nokey_Error: call MessageBoxA, NULL, offset NoErr, offset FailCpt, MB_OK jmp End_App Open_Error: call MessageBoxA, NULL, offset OpErr, offset FailCpt, MB_OK jmp End_App Invalidkey_Error: call MessageBoxA, NULL, offset InErr, offset FailCpt, MB_OK jmp End_App ;\____________ _ _ _ ; END OF ERROR MESSAGES End_App: call ExitProcess, NULL end _start _________________________________ _ _ _ Welll DONE ! This is the complete code of this brute-force cracker :) I cannot explain every step of this code to keep quite small this tutorial, however if you don't understand it.. mail me. I've inserted some comments to help the reader. And I think that with a little knowledge of the assembler this essay can be readed in a easy way. (the names of the labels are referred to some italian punk groups >:) I've tested this executable on my Pentium 100 and it needs 43 seconds to crack the word of 1 2 3 4 and 5 letters. To do the same thing on the Pentium II 233 of a my friend it needs 9 seconds. Of course this code can be also optimized. We can also use this code with every other kind of bruteforce cracker, in fact we can change only the: 1) Crypting routing. 2) Reading Operations of the masked code. And.. you must remember that a crypted value can be the result of different inserted password. I.E. : I set the password "fabio" The bruteforce cracker find the password "nobaa" In fact these two values inserted in the cripting routine give the same result: "1657786368" Moreover you can see that the number after the comma is obtained using the options of the lock section. If you set ON only the "Ask on startup" checkbox the number after the comma is "1". If you set OFF that checkbox, the number after the ',' becomes a "0". Of course there are also a great number of other combinations. Ole' :-) I think that now mirc is totally reversed :P