CrackMe® Practices for Newbies
Project 9: CrackMe 2 by Cronos

A how to tutorial
Friday, 09-Apr-99 19:55:47

    Greetings fellow newbies,

    After some hesitation and a greet deal of procrastination, I am finally ready to share with you some of what I learned from the current project

    Target: Cronos2.com
    Protectio: Username - serial number combination
    URL: http://skyscraper.fortunecity.com/nexus/650/com/cronos2.zip
    Programer: Cronos
    Aouther: Joseph

    Tools, used and not used
    SoftIce: I found it useless. I was not able to make it load the target..
    W23dasm: Was only good for creating a less than half decent source listing.
    HexWorkShop: Used to dump the code .
    DosBasic: Used to write some routines to do some tasks I needed.
    IDA: Created a source listing after some massaging.
    TD5DOS: This is the only one I could use to debug the target.


    Initial screen an messages displayed by the program
    Once the program is ran it opens a DOS window and displays the following:
    CronosSystems (C) Cronos
    Authorised Access Only

    Usernam:

    After a username is entered it displays another line :

    Access Code:

    After the Username and Access Code are entered one of two messages is displayed:
    Access Denied if the combination was wrong or ,
    Access Granted if the combination was correct.

    Hidden text and messages in the program
    Hoping to locate the text for the messages mention above, I examined the hex dump of the program but there was not recognizable text shown. That is interesting. Where did these messages come from then? There are no other files associated with this program that might contain the text messages. The mystery deepens, but the answer is not that hard to find. Some sort of encoding must have been used to hide the plain text. A few steps through the program with the debugger revealed the hidden mystery shows its face. Fetch a byte from memory multiply it by 1FH and take the left byte of the result or the remainder of dividing by 256. The byte fetched from memory is stored in bx register and ax register contains the value 1FH a mul dx instruction multiplies these two registers and stores the result in ax. After the multiplication only the byte in al is stored back in memory. The encoding was done by multiplying the ASCII number of each character by DFH and the remainder of divide by 256 was take. So code = asc(char) * DFH MOD 2^8. It looks like I am getting ahead of my self so let us go back to the program and do some analysis.

    General description and division of the program
    The small size of the program offers an excellent opportunity to study almost every byte in it, thus let me divide the program to its main components. The program is only 915 bytes long and it may be divided into three sections:
    Code: the code section is about 323 bytes. Even those are not completely code since there are about 28 pairs of data bytes scattered thorough the code. I will come to this later
    Text data: There are about 87 bytes of data in the program starting at 296h and at 405h.
    Other data: The rest of the program, which is more than one half is mainly jump tables except for a few bytes used for other purposes.

    How dos the program work?
    This program is different from the usual programs most of us are familiar with: It has no calls and no return so one is always working on the highest level of the program. It uses a short segment of code starting at 110h as the main part and from it jumps to various routines that do very small tasks. All routines end by a jmp 110 instruction: Here is that main section:

    110 mov bx, [si] ;si points to an address in the
    112 83 C6 02 add si, 2 ;jump table
    115 8B D3 mov dx, bx
    117 83 C2 02 add dx, 2
    11A FF 27 jmp word ptr [bx]


    Important routines in the program:
    Interrupts: There are four interrupt calls, here they are:

    1E9 5A pop dx
    1EA B4 02 mov ah, 2 ;DOS - DISPLAY OUTPUT
    1EC CD 21 int 21h ;to send ; DL = character
    1EE E9 1F FF jmp 110 ;to standard output

    1F3 B4 08 mov ah, 8 ;DOS - KEYBOARD INPUT
    1F5 CD 21 int 21h ;NO ECHO
    F7 B4 00 mov ah, 0 ;Return: AL = character
    1F9 50 push ax
    1FA E9 13 FF jmp 110

    1FF B8 4C 00 mov ax, 4Ch
    202 CD 21 int 21h ;DOS - PROGRAM TERMINATION

    2F7 B8 03 00 mov ax, 3
    2FA CD 10 int 10h ;VIDEO - SET VIDEO MODE
    2FC E9 11 FE jmp 110 ;AL = mode

    Math routines: There is an add routine, a subtract routine, and a multiply routine. These do operation on two registers namely ax and dx, here they are:
    Add routine

    1B7 58 pop ax
    1B8 5A pop dx
    1B9 03 C2 add ax, dx
    1BB 50 push ax
    1BC E9 51 FF jmp 110

    Subtract routine
    1C1 5A pop dx
    1C2 58 pop ax
    1C3 2B C2 sub ax, dx
    1C5 50 push ax
    1C6 E9 47 FF jmp 110

    Multiply routine
    1CB 58 pop ax
    1CC 5A pop dx
    1CD F7 E2 mul dx
    1CF 50 push ax
    1D0 E9 3D FF jmp 110

    There are other add and sub routines but they add 2 or subtract 2 from a given register therefore I did mot consider them with the above.

    Memory manipulation routines. There are two of those of special interest for us: Fetch one byte from memory and put a byte in memory.

    Fetch a byte routine
    183 5B pop bx ;bx points to a memory location
    184 0F B6 07 movzx ax, byte ptr [bx]
    187 50 push ax
    188 EB 86 jmp 110

    Put a byte in memory
    018C 58 pop ax ;al contains the byte to be stored
    018D 5B pop bx
    018E 88 07 mov [bx], al
    0190 E9 7D FF jmp 110


    Compare an jump routines:
    Since these operations occupy a special place in the heart of every newbie reverser, I thought I will include them here. There are three of them. The first one makes, at least, three different things: 1- Checks if the character entered by the user is a back space, 2- check if the username and the access code are of equal length, and finally it dose the comparisons between the calculated code and a hard coded value. So don't attempt to the jump in this one. The second one checks for the length of the username and the access code making sure that none will exceed 256 bytes. An the third one checks if the character entered was a carriage return 0Dh. Here are the three routines:

    2C1 58 pop ax
    2C2 5B pop bx
    2C3 3B C3 cmp ax, bx
    2C5 74 05 jz 2CC
    02C7 6A 00 push 0
    2C9 E9 44 FE jmp 110
    2CC 6A 01 push 1
    2CE E9 3F FE jmp 110

    2D3 58 pop ax
    2D4 5B pop bx
    2D5 3B C3 cmp ax, bx
    2D7 72 05 jb 2DE
    2D9 6A 00 push 0
    2DB E9 32 FE jmp 110
    2DE 6A 01 push 1
    2E0 E9 2D FE jmp 110

    2E5 58 pop ax
    2E6 5B pop bx
    2E7 3B C3 cmp ax, bx
    2E9 75 05 jnz 2F0
    2EB 6A 00 push 0
    2ED E9 20 FE jmp 110
    2F0 6A 01 push 1
    2F2 E9 1B FE jmp 110


    A reversing method
    To revers the target one needs to find out what happens to the username and the access code after they have been entered and one needs a good place to stop the program at to follow this process. As listed above, there is an interrupt routine which gets a character from the keyboard thus providing a good place to stop the program. This routine starts at address 1F3:

    1F3 B4 08 mov ah, 8 ;DOS - KEYBOARD INPUT
    1F5 CD 21 int 21h ;NO ECHO
    F7 B4 00 mov ah, 0 ;Return: AL = character
    1F9 50 push ax
    1FA E9 13 FF jmp 110

    It is a good idea to place the break at the end rather than the beginning of the routine so one will be able to step through the code without going into the interrupt routine itself. With a bpx place at 1FA the program will stop there after the user enters the first character of the username. Once the program stops start single stepping through the code and immediately you will land at 110. Once there notice how the registers is, bx, and dx are used to determine the next routine to be executed. Keep stepping through and eventually you will get to the multiplication routine starting at 1CB.


    1CB 58 pop ax
    1CC 5A pop dx
    1CD F7 E2 mul dx
    1CF 50 push ax
    1D0 E9 3D FF jmp 110

    Put an other bpx the top of this routine, you will need it later. Here the character from the username will be multiplied by 2Bh. Your journey through the program will lead you to location 18C where your encoded character will be stored in memory in location starting at 494h.

    018C 58 pop ax ;al contains the byte to be stored
    018D 5B pop bx
    018E 88 07 mov [bx], al
    0190 E9 7D FF jmp 110

    Son after that you will be entering the second character of the username. From now on just run the program which will keep breaking at the locations where bpxs were place. Don't disable them. After you have entered enough characters, 5 or 6, hit the carriage return to start entering the access code. It a good idea to follow the journey of the first character of the access code in the same manner as done with the first one of the username. One difference will be the memory location where the encoded characters of the access code are stored. In this case the location starts at 594. The real fun starts after you the last character of the access code is entered and the carriage return is hit.
    After you hit the carriage return for the second and last time the program will break after returning from the interrupt call. From her on you have one of two choices, either single step and watch what is happening are run. In either case you will come to the multiplication routine where you have place the second bpx.

    From there on single step until you com to the addition routine at 1B7

    1B7 58 pop ax
    1B8 5A pop dx
    1B9 03 C2 add ax, dx
    1BB 50 push ax
    1BC E9 51 FF jmp 110

    Place another bpx at the top of this routine also. You could continue single stepping through the code, but to save some time run the program and it will break at the addition routine for a second time where it will add the number from the username to whatever has been calculated so far which will be 93h at this point. From here on single step again until you reach the top of the subtraction routine at 1C1

    1C1 5A pop dx
    1C2 58 pop ax
    1C3 2B C2 sub ax, dx
    1C5 50 push ax
    1C6 E9 47 FF jmp 110

    Here the number fetched from the access code will be subtracted from the accumulated number.
    From here on just run the program by hitting F5, X , or whatever. The program, however will continue to break at the top of each of the multiplication, addition, and subtraction routines. It is wise to just single step the few instructions in each routine to in order to watch what is happening.
    After all the characters in the username and the access code, which should be of an equal length, are processed you will end with a 16bit number which for convenience we will name as X.
    If you continue watching the multiplication and addition routines, soon you will notice that a hard coded number = 5BFh will be multiplied by X. The result of the multiplication will soon be added to another hard coded number = 79DFh. The result of this addition eventually will be compared with a third hard coded number = 7777h. If both numbers are equal you will get the "Access Granted" message, but if not you will get the "Access Denied" message. This last comparison takes place here:

    2C1 58 pop ax
    2C2 5B pop bx
    2C3 3B C3 cmp ax, bx
    2C5 74 05 jz 2CC
    02C7 6A 00 push 0
    2C9 E9 44 FE jmp 110
    2CC 6A 01 push 1
    2CE E9 3F FE jmp 110


    The number needed for a solution and how it is generated:

    In order to succeed in getting the "Access Granted" message the number calculated from the username-access code combination must be 5868h. To generale this number, the program uses a complicated algorithm which may be summarized by the following equation:
    X = X*3 + 93H + NameCode(n) - AuthCode(n)
    With X = 0 at the start and the calculation repeated n times an being the length of each of the username and the access code. Here is a more detailed description of the above equation and a programmatic way of how it works:
    Definitions:
    Length = the number of bytes in each of the username and authorization number.
    n = 1 to length.
    NameCode(n) = the code for the respective byte in the name.=Ascii*2Bh
    AuthCode(n ) = the code of the respective byte in the authorization number = Ascii*2Bh
    X = result of calculation as shown next = 0 at the start
    For n = 1 to length
    X = X*3 + 93H + NameCode(n) - AuthCode(n)
    next n
    There is no easy way to calculate the needed number, but a determined reverser will find many was to do it.

    Patching the program
    Although the jump instruction following the compare je can't be changed since that routine is used to compare other values, there is an easy albeit not very obvious way to patch the target and get the "Access Granted" every time.
    After comparing the lengths of the username and access code, the program either continues with the rest of calculation if the two are equal, or goes on no display the "Access Denied" message if they are not equal. But for some reason, lucky for us, it compares the 7777 with 0000 in order to jump to the bad location. This is a good loop hole to exploit. By changing the 7777 to 0000 and enter a username different in length from the access code the "Access Granted" message will be displayed every time we enter a username longer or shorter than the access code. Try it.
    Use any hex editor such as HexWorkShop and change the 7777 to 0000. You will find 7777 at location 382 when using a hex editor and at location 482 when you are using a debugged such as SoftIce.

    Enjoy,

    Joseph

    Joseph


Message thread:

Joseph's Thread (Question to Cronos) (30-Mar-99 23:45:59)

Back to main board