home *** CD-ROM | disk | FTP | other *** search
- 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