                        How to crack TEST2 by LordByte

                                   by Crook
                               27 January 1998

Introduction

     TEST2  is  the  second TEST in the series of CrackMes issued by LordByte.
The  higher  number  is  the harder CrackMe is. As you can expect TEST2 is not
really  difficult. If you feel it's too easy for you just go ahead and try the
TEST7.

Where you can get it

     You  can  get  it  from http://crackme.home.ml.org together with zillions
(well, nearly ;) other ones.

Cracking

     At  first  I  must  say  you have to know assembler before trying to read
this.  I'll present a complete disassembly of this little piece of code but to
fully  understand  what  I'm talking about you must know what those magic MOVs
and XORs are.

Let's go

     (Below  is  the  full source code of TEST2, produced by IDA - Interactive
DisAssembler.  If  you want to crack more & more progs you should get it. More
details  can  be  found  on great FraVia's site: http://fravia.org and the IDA
itself can be found almost everywhere on the Web).

------------------------------------------------------------------------------
                jmp     short $+2
                int     3               ; Trap to Debugger
------------------------------------------------------------------------------
     This  is  just  a  typical  start of LordByte's program ;) The first jump
which  is  effectively  useless.  The next instruction is to stop the debugger
execution  at  this point. It's very useful to us because the original COM was
pcaked  using  PKLITE and we are able to find entrypoint without unpacking the
program.

------------------------------------------------------------------------------
                mov     ax, 900h
                mov     dx, offset intro_str
                int     21h             ; DOS - PRINT STRING
                                        ; DS:DX -> string terminated by "$"
                mov     ax, 0A00h
                mov     dx, offset password
                int     21h             ; DOS - BUFFERED KEYBOARD INPUT
                                        ; DS:DX -> buffer
------------------------------------------------------------------------------
     This two DOS calls just print the intro message and get the password from
the user. Nothing complicated yet.

------------------------------------------------------------------------------
                mov     ax, 351Ch
                int     21h             ; DOS - 2+ - GET INTERRUPT VECTOR
                                        ; AL = interrupt number
                                        ; Return: ES:BX = value of interrupt vector
                mov     si, offset old_ofs
                mov     [si], bx
                mov     bx, es
                mov     [si+2], bx
                mov     si, offset interrupt
                mov     dx, si
                mov     ax, 251Ch
                int     21h             ; DOS - SET INTERRUPT VECTOR
                                        ; AL = interrupt number
                                        ; DS:DX = new vector to be used for specified interrupt
                jmp     short next
------------------------------------------------------------------------------
     Now  we're  saving vectors of 1ch interrupt and setting it to our vector.
This  interrupt  is  called  (normally) 18.2 times a second allowing you to do
things 'in background'. It's used for some (lame ;) antidebugging later.

------------------------------------------------------------------------------
terminate:
                pop     bp
                add     sp, 8
                mov     ax, 4C00h
                int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
                                        ; AL = exit code
------------------------------------------------------------------------------
     This  proc  just  ends  the  execution of this program. It's not executed
after  playing with vectors because there is a 'jmp short next' jump to 'next'
label below.

------------------------------------------------------------------------------
next:
                mov     si, offset password+2
                mov     ax, 6F25h

xor_loop:
                xor     ax, [si]
                cmp     byte ptr [si+1], 0
                jz      anti_loop
                cmp     byte ptr [si+1], 0Dh
                jz      anti_loop
                inc     si
                jmp     short xor_loop
------------------------------------------------------------------------------
     Yo! This is the 'juicy' part of the prog. Let's look closer what is going
on there. Moving 6f25h to AX and then XORing it by every char in the password.
But  we  must  be careful here. Why SI's initial value is 'offset password+2'?
Because  the  first  char at 'offset password' is how many chars can be put in
the  buffer  (put there before DOS call. It's the buffer length incremented by
one,  because  of 0Dh char on the end which also must fit into the buffer. DOS
checks this value and gives a beep when you're trying to enter more chars then
it's  stated  here.  In this program maximum password length is 11 = 0Ch - 1),
and the second byte at 'offset password+1' is how many chars user actually has
entered.  So  we have in AX value of 6f25h and let's assume we enetered 'test'
as a password. Let's have a look at the memory:

offset password: 0C 04 74 65 73 74 0D 00 ....

     Notice that 74 65 74 73 = 'test'. SI points to 'offset password+2', which
is  74  65 73 74. Now the AX get XORed (if you don't know this function go and
read  about  in  some  book, some paper on the Web or something, otherwise you
won't  understand  how the crack is being done). But with what? First guess is
7465,  but it's wrong. It gets XORed by 6574h. As you can see the order of the
bytes is reversed. That's how it is on x86 processors. Don't ask me why, go to
local  Intel  office  ;).  Then  after  INCrementing SI it's XORed by, yes you
guessed it this time, 7365h.

     This process ends when the 0Dh is reached, what is the mark of the end of
the  string  appended  to  the  string  by  DOS.  All right, ebough about this
snippet, let's go further.

------------------------------------------------------------------------------
anti_loop:
                cmp     cs:anti_debug, 0
                jnz     anti_loop
------------------------------------------------------------------------------
     This  to  lines  do a simple thing: try to stop an unexpirienced cracker.
Remember  what  the  program  did  when it was playing vectors? It also set an
interrupt  1ch  (which  is  called,  as  you  can remember, approx. 18 times a
second)  to  'interrupt'  procedure.  It's presented below, but in fact it's a
liitle but farther in the code.

------------------------------------------------------------------------------
interrupt:
                dec     cs:anti_debug
                iret
------------------------------------------------------------------------------
     It  simply  decreases the anti_debug variable. Now you can imagine what's
going on. In the debugger the interrupts are disabled, so the loop never ends.
But  when  the prog is not traced the interrupts are enabled, so anti_debug is
being decreased so it must reach 0 sometime, and the loop would end then.

------------------------------------------------------------------------------
                mov     si, offset old_ofs
                mov     dx, [si]
                push    ax
                mov     ax, [si+2]
                mov     ds, ax
                mov     ax, 251Ch
                int     21h             ; DOS - SET INTERRUPT VECTOR
                                        ; AL = interrupt number
                                        ; DS:DX = new vector to be used for specified interrupt
------------------------------------------------------------------------------
     After  the  anti  debugging  trick has ended the vectors are reset to the
original values and the value calcuted using xor_loop is pushed to the stack.

------------------------------------------------------------------------------
                push    cs
                pop     ds
                add     ax, 409h
                shl     ax, 3
                sub     ax, 4A3Eh
                xchg    ax, dx
                mov     ax, 300h
                mov     bx, 0FFFh
                mov     cx, 90h

mess_loop:
                add     ax, bx
                xchg    ax, bx
                loop    mess_loop
                push    bx
                push    ax
------------------------------------------------------------------------------
     mess_loop  does  nothing  but computing in AX c440h and in BX c69fh. This
values  are constant on every computer, under any debugger everytime. It could
be replaced with simple MOV AX,C440h MOV BX,C69Fh.

------------------------------------------------------------------------------
                mov     ax, 3503h
                int     21h             ; DOS - 2+ - GET INTERRUPT VECTOR
                                        ; AL = interrupt number
                                        ; Return: ES:BX = value of interrupt vector
                mov     ax, bx
                ror     ax, 4
                xor     ax, 7343h
                push    ax
------------------------------------------------------------------------------
     This  one  gets  the  03h  interrupt  vector (it is used in debuggers for
breakpointing).  It's  not used later so we can forget about it. It tries just
to mess up our understanding of the program.

------------------------------------------------------------------------------
                push    bp
                mov     bp, sp
                mov     bx, [bp+4]
                xor     bx, 0B816h
                xor     [bp+8], bx
                jnz     write_wrong
                mov     ax, 900h
                mov     dx, offset right_pass
                int     21h             ; DOS - PRINT STRING
                                        ; DS:DX -> string terminated by "$"
                jmp     terminate
------------------------------------------------------------------------------
     At  last is the end. What is done here? The BX is loaded with [BP+4]. WTF
is that? Look at this memory location under the debugger. It's C440h (computed
earlier).  BX  gets  XORed  with B816h so finally BX is 7C56h. [BP+8] is XORed
with  BX and if it's zero it's the right password! If it's not the password is
wrong.

------------------------------------------------------------------------------
write_wrong:
                mov     dx, offset wrong_pass
                mov     ax, 900h
                int     21h             ; DOS - PRINT STRING
                                        ; DS:DX -> string terminated by "$"
                jmp     terminate
start           endp
------------------------------------------------------------------------------

Cracking the schema

     What  do we know about the right password? The value computed by xor_loop
equals  to  7C56h. (Only n XOR n = 0). Now, let's think what the xor_loop does
with  password.  Assume  password  is  11  (0bh)  chars  long. Let's write the
password as 'abcdefghijk' plus 0dh char. How AX is XORed with these values?
AX  =      6F25h
XORed by:   b a
            c b
            d c
            e d
            f e
            g f
            h g
            i h
            j i
            k j
           0d k
           =====
Should be: 7C56h

Let's  notice  the  following  about the XOR: having any given value (x) and a
second value (y) there is just one value which fulfills the equation:
       x xor w = y
And we also know the value of w, which is:
       w = x xor y
If you don't know why, study so mathemathics.
Now  back  to  our  table. Basing on the fact I described above it's enough to
compute  'k'  and 'a' value having 'bcdefghij' given by the user! Writing such
a program is not hard and in cracker's jargon it's called keymaker.

6Fh xor (b xor c xor d xor e xor f xor g xor h xor i xor j) xor k xor 0dh = 7Ch
25h xor a xor (b xor c xor d xor e xor f xor g xor h xor i xor j) xor k = 56h

Let's assume

n = b xor c xor d xor e xor f xor g xor h xor i xor j

then

6Fh xor n xor k xor 0dh = 7Ch
25h xor a xor n xor k = 56h

after some calculations you get:

k = 1Eh xor n
a = 73h xor n xor k

because k = 1Eh xor n

a = 73h xor n xor 1Eh xor n
a = 73h xor 1Eh
a = 6Dh = 'm' !

What you see above is a proof that the first char is CONSTANT! Having given 9
chars that would be put inside the password we know the first char is 'm' and
we can compute the last! Here is the program which does the very thing:

program Test2KeyMaker;

var
  S : string;
  n : Byte;
  a, k : Byte;
  i : Byte;

begin
  Writeln('Simple keymaker for TEST2 by LordByte');
  Writeln('Written 27.1.1998 by Crook');
  repeat
    Write('Enter 9 char string: ');
    Readln(S);
  until Length(S) = 9;
  n := 0;
  for i := 1 to 9 do
    n := n xor Ord(S[I]);
  k := $1e xor n;
  a := $6d;
  Writeln('The password is: ', Chr(a), S, Chr(k));
end.

Ending

     So  that's  it. You must admit that wasn't so difficult. Take a next TEST
in a row a try to crack it. You surely will learn something.

                                                                         Crook