CrackMe® Practices for Newbies ~ Moderated

Joseph's Thread: Program analysis and a possible solution
Tuesday, 02-Feb-99 05:49:07
    208.254.224.156 writes:

    I am not sure if this is going to be of helpful to anybody, but since I spent many hours on the project I thought it is a good idea to share with you my findings.

    Target: Crackme4.exe by THE_q/PC
    Found at:http//members.xoon/Ryanosis/files.html
    Protection: Missingkey file

    Tools: W32dsm98
    HexWorkshop32

    Number system used in this article:
    I will be using the hexadecimal number system and will suffix each number with an 'h' to indicate that it is a hex number and if I give the decimal equivalent I will suffix it with a d, thus the number 30 hex which equals 48 decimal will be written 30h and if an equivalent is given it will be between () such as (48d).

    When the program is run it displays a window containing some information about the author and a continue button. Pressing the continue button produces a second small window with 4 panels stating the following :
    Status:iNCORRECT fILE :-P and
    cRACKED by: iNCORRECT fILE :-P
    Obviously something has to be done to change the contents of the second and fourth panels, so let us start.

    Step 1: Looking for the missing file using the FileMonitor
    First let us try to find the missing file and determine its minimum length. The first thing comes to mind is to run Filemon and see if it will tell us anything about the missing file. Thus we open filemon and run our target, but wait a moment, what is going on? As soon as the program started running the filemon window was zapped. Yes EB, I am running Win95 filemon.

    Step 2: Searching for the missing file in the target itself using dead listing and stepping
    Using W32dasm a listing of the program was produced and stepping through the program with the debugger brought me to the location where the program tries to open the missing file and we see the following code:
    * Possible StringData Ref from Data Obj ->"CrkMe4.Dat"
    |
    :00401018 6828204000 push 00402028 ;Memory containing the
    filename CrkMe4.Dat

    * Reference To: KERNEL32.CreateFileA, Ord:0000h
    |
    :0040101D E8CB080000 Call 004018ED
    :00401022 83F8FF cmp eax, FFFFFFFF ;eax = -1 if file is not
    found
    :00401025 0F84BC000000 je 004010E7 ;Bad boy jump, go there
    If the file is not found.
    :0040102B A333204000 mov dword ptr [00402033], eax ;File handle
    is stored here
    :00401030 6A00 push 00000000
    :00401032 6837204000 push 00402037
    :00401037 6A30 push 00000030 ;Number of bytes to be read

    From the code above we learn that the missing file is named crkme4.dat and it should contain a minimum of 30h (48d) bytes but before we leave, let us find out why the filemon window was zapped.

    Step 3: Why was the FileMonitor screen zapped
    Further stepping and examination of the listing brings us to this bit of code:

    * Possible StringData Ref from Data Obj ->"Win95 File Monitor"
    |
    :004015D1 68DD224000 push 004022DD ;Win95 File Monitor is stored
    At this memory location.

    :004015D6 68D0224000 push 004022D0
    :004015DB E8E9020000 Call 004018C9 ;Check if Filemon is opened
    :004015E0 83F800 cmp eax, 00000000 ;If yes eax = 1
    :004015E3 7426 je 0040160B ;Jump if Filemon is not opened
    [Snip the nops and some other code]
    :
    * Reference To: KERNEL32.TerminateProcess, Ord:0000h
    |
    :00401606 E8CA020000 Call 004018D5 ;Zap the Win95 File Monitor
    Window.
    The snippet of code above conferms that the target program indeed looks for and closes the file monitor window if it is opened. Now this is said, let us continue with the important task--try to find more and crack the program if possible.

    Step 4: Creating the missing file
    Now we know the name and the size of the missing file, creating it is a trivial matter. As a matter of fact we don't need to create anything, any file will do as long as it is named 'crkme4.dat' and resides in the same directory with the program. We can rename 'crkme4.dat' any one of the files three other files that came in the zip file and were extracted to the same directory where the target is. So rename the file 'File_id.diz' to 'crkme4.dat' and you will have an acceptable data file with which the target program will have no problem. This is the easy way. But as crackers and in need to manipulate the data file, we better create it using a hex editor such as the HexWorkShop. So open the Hworks32 and press Ctrl N to open a new file then Ctrl Ins and in the little window click the hex button and enter 30 in the number of bytes box. This will create a file with 30h null '00' bytes. Now save this file to the same directory where the target is as 'crkme4.dat'. Now the missing file has been created, let us continue our exploration.

    Step 5: Exploring the program
    Now anytime we run the target program, it will look for and find the crkme4.dat file and read 30h bytes. This happens here:

    :0040104F E8A5080000 Call 004018F9
    :00401054 833D3720400030 cmp dword ptr [00402037], 00000030
    :0040105B B800000000 mov eax, 00000000
    :00401060 7240 jb 004010A2 ;This is a jump to the bad boy
    Jump.
    The call to 004018f9 reads the file and the cmp instruction and the conditional jump make sure that 30h bytes were read. The 30h bytes read are stored in a memory buffer starting at location 0040203b.
    :
    :00401066 A05B204000 mov al, byte ptr [0040205B]
    :0040106B A271204000 mov byte ptr [00402071], al
    The byte moved to al from location 40205b and the stored at 402071 is byte number 20h in 'crkme4.dat'. In other words, it is the first byte of the third line of 'crkme4.dat' file. This byte, depending on its value, plays a very important part in the calculation of the key for the target program. For reasons which will be obvious later, I will call this byte the minus byte and leave it now to return to it later.

    :00401070 BE3B204000 mov esi, 0040203B ;Now esi contains the
    data buffer address

    :00401075 6A04 push 00000004 ;This is a loop counter
    :00401077 56 push esi
    :00401078 56 push esi
    :00401079 E898000000 call 00401116 ;Manipulate the dat

    The code starting at 0040166 and ending at 00401078 is in preparation to do some heavy manipulation of the data contained in the 'crkme4.dat' file and now resides in memory starting at address 0020203b. This manipulation is done in the routine starting at 00401116. This routine will run through a number of loops. The n will equal the number pushed on the stack in the third instruction before the call. In this case the pushed number is 4 thus the routine will loop 4 times processing 4 bytes of data in each loop and extracting 3 bytes of code from each 4 bytes of data. Thus during the first call to 00401116 10h (16d) bytes of data are processed, those will be the first 10h (16d) bytes of data in the data file, that is bytes 00h to 0fh. From those 10h (16d) bytes of data a set of 0ch (12d) bytes of code are produced and are stored memory buffer where the original data was, that is at lactation 40203b. Let us call this codestring1.
    Many unpleasant things happen to the data bytes during this process, and the fact that the bytes are taken 4 at a time does not make any more palatable.
    First a number is subtracted from each byte. This number is the same one I talked about earlier and named it the minus byte. This was the byte number 20h in the data file. If this byte = 00h null, it has no effect on the data during the subtraction operation. Next each one of the four bytes in the group starting with the second, then third, then fourth, then first is split into two parts and these parts are shifted and ored to produce new bytes, then some bytes are recreated by combining two of the new bytes. These new yet are the components of the code and as was stated earlier, three bytes of code are produced from each group of 4 bytes of data. If the minus byte was = 00 and every one of the first 10h (16d) bytes of data was = 00, the 0ch (12d) bytes of code will be 00's other wise they will contain some value.
    The fact the author of the program, clever as he is, let it pass without checking for > 0 represents a serious flow in the program in my opinion. He could have added an instruction such as this: cmp al, 00 followed by je bad boy. Had he done this he would have guaranteed the generation of nun zero code for any segment of the data file left with 00h null values.
    The second call to 401116 will perform 5 loops and process the data starting at bye 10h through 23h in the data file which are located in memory starting at address 40204b. This will produce 0Eh (15d) bytes of code and will be stored in memory starting at address 40204b. We shall call this codesting2. Furthermore let us take the last 3 bytes of this codestring and name them codestring3. Codestring 3 will consist of three bytes of nulls 00 if data bytes 20h through 23h were all left = 00 other wise they will have some value.

    :0040107E BF3B204000 mov edi, 0040203B ;edi =start of buffer
    :00401083 83C70C add edi, 0000000C ;edi = address of byte 0ch
    ;of the old data.
    :00401086 32C0 xor al, al
    :00401088 B904000000 mov ecx, 00000004 ;This puts 4 byte of 0's
    :0040108D F3 repz ;in this memory location
    :0040108E AA stosb ;in preparation to the
    ;next step.
    :0040108F BE4B204000 mov esi, 0040204B ;Address of next data
    ;segment to be processed.
    :00401094 6A05 push 00000005 ;Number of loops
    :00401096 56 push esi
    :00401097 56 push esi
    :00401098 E879000000 call 00401116 ;Process the next segment of
    ;data: bytes 10h to 23h, total
    ;14h (20d) bytes.
    At this point and after the return from the second call to 00401116 we end up with 2 codestrings: Codestring1 consisting of 0ch (12d) bytes and codestring2 consisting of 0Eh (15d) bytes of code. To make things easier to explain, let us split codestring2 into 2 codestrings: Codestrin2 consisting of the first 0Ch (12d) bytes and codestring3 consisting of the last 3 bytes of codestring2. Now we are ready to go through the final stage of encoding which will take place during the call to 004011c1. The program starting at 004011c1 accomplishes the task at hand after a short initialization section, in 3 loops. in four steps: the first is a preparation step and envolves the following snippet of the program:

    :004011C1 33C0 xor eax, eax
    :004011C3 33DB xor ebx, ebx
    :004011C5 BE43204000 mov esi, 00402043 ;Address of the last 4
    ;bytes of codestring1
    :004011CA BF4B204000 mov edi, 0040204B ;Address of the first
    ;4 bytes of codestring2
    :004011CF 8B6F0C mov ebp, dword ptr [edi+0C] ;Address of
    ;codestring3. The move instruction moves
    ;codestring 3 + one extra byte to ebp
    :004011D2 81E5FFFFFF00 and ebp, 00FFFFFF ;Makes the extra byte 00.
    :004011D8 C1E508 shl ebp, 08 ;makes the lsb = 00
    :004011DB 81F555555555 xor ebp, 55555555 ;After this instruction
    ;ebp will be = 55555555 if and only if all
    ; the bytes in codestring3 were = 00
    :004011E1 B903000000 mov ecx, 00000003 ;Number of loops.

    Let us assume here that the minus byte was 00 and no data other than 00 was inserted into locations 00 to 0Fh and locations 21h to 23h of the data file thus we shall have a codestring1 consisting of all 0's and caodestring3 will also consist of all 0'. Also let us assume that an 8 byte number was used to fill locations 10h to 7h in the data file thus producing a codestring2 consisting 6 bytes of numbers and the other 6 are all 0's. Now let us find out what is going to happen: lodsd loads eax with the last for bytes of codestring1 = 00000000, add eax, ebp, this will make eax = 55555555, mov ebx dwrd ptr [edi] will load ebx with the first 4 bytes of codestring2, this will be an actual 4 byte number, mul ebx will multiply ebx with eax and the result will be placed in edx and eax; edx=the high order bytes of the answer and eax = the lower order bytes of the answer; mov eax, edx oops! Whatever was in eax is overwritten and lost. Finally stosd stores the 4 bytes in eax in the memory location contained in edx and edx is incremented by 4 to point to the next 4 bytes. Like wise the previous stosd decremented esi by 4 to point to the previous set of 4 bytes. The second loop will process the second two sets of 4 bytes taken from codestring1 and codestring2. This time the sets will be taken from the same relative position from both codestrings, that is bytes 4 to 8 from each string. The third and final loop will process the first 4 bytes from codestring1 and the last 4 bytes from codestring2. With the above description in mined we may be able to follow the following code with some ease.


    :004011E6 FD std ;Start of the loop
    :004011E7 AD lodsd
    :004011E8 03C5 add eax, ebp
    :004011EA 8B1F mov ebx, dword ptr [edi]
    :004011EC F7E3 mul ebx
    :004011EE 8BC2 mov eax, edx
    :004011F0 FC cld
    :004011F1 AB stosd
    :004011F2 E2F2 loop 004011E6 ;Loop until ecx = 0
    :004011F4 83C614 add esi, 00000014 ;Adjust esi to point to
    ;the beginning of the
    ;real key.
    :004011F7 83C708 add edi, 00000008
    :004011FA B903000000 mov ecx, 00000003 ; Number of loops
    :004011FF AD lodsd
    :00401200 8B1F mov ebx, dword ptr [edi] ;Edi points to fake
    ;key
    :00401202 83C704 add edi, 00000004
    :00401205 3BC3 cmp eax, ebx ;Compare real and fake keys
    :00401207 750C jne 00401215 ;Tough luck, go and prepare
    ;for bad boy jump.
    [Snip some Nops]
    :0040120D E2F0 loop 004011FF ;Do some more
    :0040120F B801000000 mov eax, 00000001 ;eax = 00000001 is good
    :00401214 C3 ret


    Step 6: Conclusions and answers
    Now we have finished the difficult part, let us draw some conclusions and answer some questions based on what we have found.
    A Is the program crackable? The answer is yes and in more than one way:
    1- Nop the jump instruction at 4010A5 or change it from 74 40 to 74 00.
    2- Fill every byte of the data file ctkme4.dat with 0's, this will produce a key of all 0's and since the last 8 bytes of the file are 0's, this will pass the compare real and fake check, but will not pass a check for the presence of a key at 4010AB. Thus change or nop the jump instruction at 004010B2 which follow the checking instruction.
    3-Fish the key generated by the program at 004011f7. Bpx 4011f7 and d esi and your real key is there.
    Wait a minute!! We still get garbage or nothing at all in the space after cRACKED by. Can't we have a solution with a real name in this box. Yes you can if you are willing to spend lots of time guessing and trying, but what you achieve will not be repeatable thus will not be considered a general solution. This leads us to the main conclusion:
    There is no universal method to find a set of data bytes which will generale a given name as the real key. To be able to do something like that the process must be reversible and since the program uses some none reversible math the process becomes un-reversable. The xor operation is reversable, but the and or operations are no, the program uses all three. The program also uses multiplication and multiplication is reversible provided the result remains intact, but in our case part of the result, the lower order 4 bytes were over written and thus lost for ever. Based on these facts, the conclusion is: It is impossible to create a keygen procedure which will satisfy every case.

    Step 7: A possible solution
    Based on what has been said above, can we find a way around this problem? Yes we can and here is how I did it:

    First let us look at theses two instructions: The first at 00401037 and it is a push 00000030. The 30h is the number of bytes the program is going to read from the data file. The other instruction is at 00404B. At is another push but this time the number pushed 0040204B is the address of the real key in memory and what ever is there is going to show up in the panel after cRACKED by. A third instruction of some interest is the ones starting at 00401054, Here the program checks if 30h bytes or more were read from the data file. It does not seem to mind if more that 30h bytes were read. After the program reads the data from the data file it places it in memory starting at location 40203b. There seem tp be enough of empty space here for us to play with. In creating the key the program uses and changes bytes of this memory starting with 40203b and ending with 402071.
    Now let us open the crkme4.dat file and add to it another line of 10h (16d) bytes. Starting at the 7th position of this new line let us write a name of up to 9 characters, leaving the last byte = 00, so our data file will look like this:
    0000 0000 0000 0000 0000 0000 0000 0000
    2115 4934 7483 F110 0000 0000 0000 0000
    0000 1565 7120 5425 0520 0000 0000 0000
    0000 0000 0000 004A 4F53 4550 4800 0000 .......JOSEPH...
    Makes no deference what you use to fill some of the locations in the first 3 lines.
    Now we run the program and bpx at 004011F7 and d esi and fish your real key. Make sure you copy the bytes in their proper order. The best way to do this db esi in Soft ice or check the byte button in W32dsm. Go back to your crkme4.dat and insert the real key in the proper place. Open the program again but before running it change the following to locations: change 00401038 from 30 to 40 and change location 004010cd from 4B to 72. The first change will cause the program to read 40h byte instead of 30h bytes, and the second change will push the address 00402072--the address of the name we placed on the 4th line of crkme4.dat file. Now run the program and if you copied the real key correctly you will see the name you entered displayed in the proper place. If you want a longer name yo may add a fifth line to your data file and change the 30 to 50 instead of 40.
    An acceptable solution? May be, but it is a solution nevertheless and it is better than no solution at all.

    :00401037 6A30 push 00000030

    :004010CC 684B204000 push 0040204B

    :00401054 833D3720400030 cmp dword ptr [00402037], 00000030
    :0040105B B800000000 mov eax, 00000000
    :00401060 7240 jb 004010A2

    Finally, the following is a list of location of bad boy jumps and the reason for each:

    :00401022 83F8FF cmp eax, FFFFFFFF
    :00401025 0F84BC000000 je 004010E7 ;No dat file was found


    :00401060 7240 jb 004010A2 ;< 30h bytes are read from
    ;the data file; eax = 00

    :004010A2 83F800 cmp eax, 00000000
    :004010A5 7440 je 004010E7

    :004010AB 803D4B20400000 cmp byte ptr [0040204B], 00 ;Here a check
    ;is made to make sure that non zero code
    ;was genarated, jump if no code.
    :004010B2 7433 je 004010E7

    :00401207 750C jne 00401215
    : 00401215 33C0 xor eax, eax
    :00401217 C3 ret ;This returns to 4010A2

    Best regards,

    Joseph

    Joseph


Message thread:

Joseph's Thread: Program analysis and a possible solution (Joseph) (02-Feb-99 05:49:07)

Back to main board


Message subject:

Name: (optional)

Email address: (optional)

Type your message here:




Back to main board

Copyright © InsideTheWeb, Inc. 1997-1999
All rights reserved.