-=  HCU STRAINER 1999 : CHALLENGE 1  =-
  	                   Terminate 5.0
					   
                                       by Spath (09/98).


Goals: 1. Extensively analyze and explain Terminate's protection scheme.
       2. Create a 16 bit assembly key generator for it.
       3. Design a technique to assure that your generated key will be
          valid in any further version of terminate.

Tools: PCWATCH (I love this tool)
       SuperTracer 2.00
       SoftIce 3.0 
       TASM 5.0 

               

AT FIRST LOOK
I) THE ALGORITHM
II) REVERSING THE ALGORITHM
III) OTHER CHECKS AND VERSION COMPATIBILITY
IV) THE KEY GENERATOR
FINAL NOTES



AT FIRST LOOK 
  Playing a little bit with PCWATCH, I find out that TERMINATE try to open 
a file called TERMINAT.KEY through INT 21 (3D/3F) ; when I create a dummy 
key file, 162h bytes are read 11 times and I get the message "TERMINAT.KEY 
was damaged, please reinstall". By the way, some interrupt vectors are 
redefined (including 00h and 3Fh), which can crash simplest debuggers. 


PART ONE : THE ALGORITHM 
  I fire SIce 3.0 through its DOS loader : as expected, the breakpoint on
INT21/3F stops the execution at the first read attempt in TERMINAT.KEY. 
Note that the number of bytes read is saved (but all files larger then
162h*11 bytes will work).

:0C22   INT  21          ; read 162h bytes
        POP  DX
        POP  DS
        JB   0C2E        ; if a problem occured, save error code
        CMP  AX,CX       ; 162h bytes read ? 
        JZ   0C31        ; yes : go on     
        MOV  AX,DX       ; no : save number of bytes read
:0C2E   MOV  [24E4],AX   ; flag failure (DS:[24E4] = 0 if ok) 
:0C31   POP  BP
        RETF 0004

and later, DS:[24E4] is saved into DS:[17D6] to be checked in step 2
of the decryption algorithm. 

Then, I put a BPR breakpoint on the DS:DX memory location and I land 
in the algorithm. Before showing the code, I will give some explanations : 
this algorithm is based on 2 layers of encryption, so that there are 2 
separate checks (of course, if the first fails, the second one is not 
performed). As I explained before, the datas are loaded from TERMINAT.KEY
in blocks of 162h bytes, then stored in a internal buffer before being
processed. 
  
Two cryptographic functions are used (I call them F1 and F2) ; 



F1(buffer, magic_word1, magic_word2)

   This function only modify two magic words (and not the buffer). It uses 

   basic byte transformations (XOR, SHL, ADD, SHR, RCR) and an array of

   constant datas. It is used once on the 4th block (from byte 5Bh to byte

   15Dh), and once on the complete file (for each block, from byte 0 to 

   byte 15Dh).



F2(buffer, parameter1, parameter2, parameter3)

   This function modify the bytes of the buffer from address 5Bh to address 

   161h and also its two first parameters. Therefore, the values you choose 

   for parameter1 and parameter2 are just initial values, that are only used

   for byte 5Bh. This function is used 3 times in the algorithm, each call 

   being embeded in the previous one, so that we have   

   buffer = F2(F2(F2(buffer))). Note that only the 4th block's decryption 

   requires F2 and that the third parameter is always constant (100h).





Here's a description of the whole decryption algorithm : 



0) the two magic words have initial values of (FFFFh,FFFFh).

1) compute several tests on the file (see part 3).

2) calculate F1 all over the file, and compare the resulting words

   with the values at offset 10*162h+15Eh, 10*162h+160h (the magic words

   of the last block). If the result is incorrect, the next tests are not 

   performed.

3) compute xoring with FFh on bytes 5Bh to 161h of block 4.

4) compute block4 = F2(block4, 0007h, 0000h, 0100h)

5) compute block4 = F2(block4, 325ch, 0000h, 0100h)

6) compute block4 = F2(block4, 0904h, 33eeh, 0100h)

7) with initials values of (FFFFh,FFFFh), compute F1 on bytes 5Bh to 15Dh

   of block4. If the magic words are equal to bytes 15Eh and 160h of 

   block4, the decryption worked.

8) compute several tests on the file (see part 3).



For detailed explanations, here's the code of the F1 function:



:56B   INC  WORD PTR[17D2]          ; increment buffer index

       MOV  DI,[BP+06]

       LES  DI,SS:[DI+FEFA]

       MOV  AX,ES

       PUSH AX

       MOV  DI,[17D2]               ; load index pointer

       POP  ES

       MOV  AL,ES:[DI]              ; load byte from buffer

       PUSH AX                      ; push byte read

       MOV  DI,[BP+06]

       PUSH WORD PTR SS:[DI+FEF8]   ; push magic byte 2

       PUSH WORD PTR SS:[DI+FEF6]   ; push magic byte 1

       POP  BX                      ; BX = magic byte 1

       POP  DX                      ; DX = magic byte 2

       POP  CX			    ; CX = byte read

       PUSH DX                      ; push magic byte 2

       PUSH BX                      ; push magic byte 1

:595   XOR  BX,CX		    	

       XOR  BH,BH		    ; clear upper byte	

       SHL  BX,1

       SHL  BX,1

       ADD  BX,[E320]		    ; add constant (2014h)	

       MOV  AX,[BX]                 ; get value from data segment

       MOV  CX,[BX+02]

       POP  BX                      ; BX = magic byte 1

       POP  DX                      ; DX = magic byte 1

       PUSH CX                      ; save CX

       MOV  CX,08                   ; initialise loop

:5AC   SHR  DX,1                    ; play with magic bytes

       RCR  BX,1

       LOOP 5AC                     ; play again                   

       AND  DX,0FF

       POP  CX                      ; restore CX 

       XOR  AX,BX

       MOV  BX,CX

       XOR  DX,BX

       MOV  DI,[BP+06]

       MOV  SS:[DI+FEF6],AX         ; save magic byte 1

       MOV  SS:[DI+FEF8],DX         ; save magic byte 2

       MOV  AX,[17D2]               ; test buffer index

       CMP  AX,[BP-04]              ; 15Eh bytes processed ?

       JNZ  056B                    ; no : continue



And here is the code of the F2 function : the 3 parameters are 

respectively stored at DS:24e6, DS:24e8, SS:BP+06. The 2 first ones 

are modified at each call, the third one is always equal to 100h.



:0A3C  INC WORD PTR [17D2]      ; increment buffer index

       MOV  AX,0100h            ; set third parameter

       PUSH AX

       CALL xxxx:2053           ; calculate new parameters

       MOV  DX,AX                   

       MOV  DI,[BP+06]

       LES  DI,[17D2]           ; index of the next byte to process

       POP  ES

       MOV  AL,ES:[DI]          ; get byte from buffer

       XOR  AH,AH

       XOR  AX,DX               ; calculate new value for this byte

       MOV  DL,AL

       MOV  DI,[BP+06]

       LES  DI,SS:[DI+FEFA]

       MOV  AX,ES

       PUSH AX

       MOV  DI,[17D2]           ; index of the processed byte

       POP  ES

       MOV  ES:[DI],DL          ; put new value in the buffer

       MOV  AX,[17D2]

       CMP  AX,[BP-04]          ; did we process 161h bytes ?

       JNZ  0A3C                ; no : continue



And here's the xxxx:2053 procedure, where the two parameters are 

modified :

:2053  PUSH BP

       MOV  BP,SP

       MOV  AX,[24E6]         ; get first parameter

       MOV  BX,[24E8]         ; get second parameter

       MOV  CX,AX

       MUL  WORD PTR [12BC]   ; DS:[12BC] = 8405h

       SHL  CX,1

       SHL  CX,1

       SHL  CX,1

       ADD  CH,CL

       ADD  DX,CX

       ADD  DX,BX

       SHL  BX,1

       SHL  BX,1

       ADD  DX,BX

       ADD  DH,BL

       MOV  CL,05

       SHL  BX,CL

       ADD  DH,BL

       ADD  AX,0001

       ADC  DX,00

       MOV  [24E6],AX        ; modify first parameter

       MOV  [24E8],DX        ; modify second parameter

       XOR  AX,AX

       MOV  BX,[BP+06]       ; get third parameter

       OR   BX,BX            ; avoid division by 0

       JZ   2097	     

       XCHG AX,DX

       DIV  BX

       XCHG AX,DX

:2097  POP  BP               ; AX=0, DX=(second parameter /100h) 

       RET  0002

    

With the F1 function, we obtain two magic words which depend on every

byte of the key file. These words are checked versus words number 15Fh

and 160h of the last read block.



Here's the code where the result of the F1 function is checked :



:906   MOV  AX,SS:[DI+FEF6]       ; load magic byte 1

       MOV  DX,SS:[DI+FEF8]	  ; load magic byte 2

       LES  DI,SS:[DI+FEFA]    

       CMP  DX,ES:[DI+160]        ; magic byte 2 = byte 160h ,

       JNZ  092A                  ; no : bad boy

       CMP  AX,ES:[DI+015E]       ; magic byte 1 = byte 15Eh ,

       JNZ  092A		  ; no : bad boy

       CMP  WORD PTR [17D6],00    ; no problem reading the file ,

       JZ   092D                  ; ok : go on

:092A  JMP  22A7                  ; bad boys go to hell

:092D  MOV  DI,[BP+06]





PART TWO : REVERSING THE ALGORITHM

  So the question is : can we reverse the decryption algorithm ?

Yes, if we can reverse F1 and F2 :



- for the F1 function, there's no problem at all, since the result

  of the hash is stored at the end of the hashed bytes. Therefore, 

  we can directly use the F1 function to calculate the magic words, 

  then we simply write them at the end of the block.  



- for the F2 function, we will use two major flaws : first, the 

  calculation of the two parameters is completely independant from

  the contents of the buffer, i.e. the F2 function is of the form :

  

  for (i=5Bh, i<161H, i++)

     {

      parameter1 = F3(parameter1, parameter2)

      parameter2 = F4(parameter1, parameter2)

      buffer[i] =  F5(buffer[i], parameter1, parameter2)

     }



  Moreover, the F5 function is of the form :

  F5(buffer[i]) = buffer[i] XOR F6(parameter1, parameter2)

  

  As a result of these two weaknesses, if we get the two correct

  initial parameters F2 is its own inverse !



  Therefore our encryption algorithm will be :



0) fill all the buffers with random bytes.

1) with initials values of (FFFFh,FFFFh), compute F1 on bytes 5Bh to 15Dh

   of lock4. Write the result at bytes 15Eh and 160h.

2) handle special tests (see part 3).

2) compute block4 = F2(block4, 0904h, 33eeh, 0100h).

3) compute block4 = F2(block4, 325ch, 0000h, 0100h).

4) compute block4 = F2(block4, 0007h, 0000h, 0100h).

5) compute xoring with FFh on bytes 5Bh to 161h of block 4.

6) with initials values of (FFFFh,FFFFh), calculate F1 all over the file 

   and write the resulting words at offset 15Eh and 160h of the last buffer.







PART THREE : OTHER CHECKS AND VERSION COMPATIBILITY 

  Apart from the algorithm, four rows of tests are performed on the file, 

as described below :



1)    

    Buffer number          Test                      Result

         1            Buffer[0B] = 46h ('F') AND    Runtime error103

                      Buffer[1B] = 2Fh ('/') AND    (File not open)	

		      Buffer[14] = 2Eh 	



         2            Buffer[1] = Buffer[2] AND       add 0329 

                      Buffer[1] = Buffer[3] AND      to MagicWord1  	

		      Buffer[1] = Buffer[4] AND

		      Buffer[1] = Buffer[5] 

		      	

	 5	            see 2)                     see 2)  



	11 (a)	      Buffer[12C] = Buffer[12D] and    add 0192 

		      Buffer[12C] = Buffer[12E] and   to MagicWord1

		      Buffer[12C] = Buffer[12F] and

		      Buffer[12C] = Buffer[130] 			



        11 (b)	      Buffer[15A] = DC and	     Runtime error103	       

		      Buffer[15B] = 64 and	     (File not open)	

		      Buffer[15C] = D9 and

		      Buffer[15D] = E9			



   As a result of checks on buffer 2 and buffer 11 (a), a file filled with 

f.i. all '0', encrypted with the correct algorithm would not work. This 

means that in correct keyfiles, these parts are filled with usefull (at 

least not constant) datas. This basic detection of false keyfiles is useless

since I fill the keyfile with random values. 

   To avoid the tests on blocks 1 and 11 (b), I could have add specific 

tests in my key-generator, but generating these particular values is very 

unlikely : even if it happens, you only need to re-run the key-generator. 

Note that all these tests were already used in version 4, but the tested 

values were slightly different.



2) 

   This test use a lot of code, so I tried to explain it in a "mathematical 

way" to make it more understandable... I hope I succeeded. So this test use 

bytes 15Eh and 160h of buffers 4 and 5. Some functions are involved, which 

take these two bytes as input and return three bytes (which i will call B1, 

B2 and B3). 



Here is what happens :



- calculate  B2 = F7(Buffer5[15E])

	   (B1,B3) = F8(Buffer5[160])



- after decryption of buffer 4, calculate 

	     B2' = F9(F7(Buffer4[15E]))

	   (B1',B3') = F8(Buffer4[160])

	

- if B1=B1' and B2=B2' and B3=B3' then the result of the test

  is succesfull. 



Since the bytes of buffer 4 are the result of al lot of work, it 

is much easier to find the correct bytes of buffer 5 to pass this 

test. The condition on B1 and B3 are easy to fulfil : we only 

need to have Buffer5[160] = Buffer4[160]. The other condition seems

more complicated, since we need to compensate the effects of F9 on

Buffer4[15E] (further refered to as MW1).  



Here's some code "of.class" tppabs="http://fravia.org/99solu/of.class" F9 :



:141F	SHR   DX,1      ; SI = F7(MW1), BX=A0h=(2^4)*0Ah

	RCR   BX,1      ; BX = BX / 2

	RCR   AX,1

	DEC   CL

	JNZ   141F      ; loop

 	...

 	ADC   BX,SI 	; B2' = F7(MW1) + BX



   So at the end, we have F9(F7(MW1)) = F7(MW1) + 2^(4-CL)*0Ah : in 

other words the action of F9 is to add n*0Ah (a multiple of ten in 

decimal). Let's try to compensate this effects with F7, which use 

this code :



	NEG   DX	; invert magic word 2

        NEG   AX	; invert magic word 1

        MOV   BX,AX	

	...

:1667	DEC   AL	; BX = NOT(magic word 1)

	ADD   BX,BX     ; calculate B2 = 2*B2

	ADC   DX,DX	

	JNS   1667      ; loop until a negative number is obtained

	

  We see that if we substract n to magic word 1, B2 will increase by 

(2^k)*n, where k is the number of times we execute the loop. Thanks 

to the author, k=4-CL, so that we have (at last) :



F9(x)   = x + (2^k)*0Ah      

F7(x-n) = F7(x) + (2^k)*n	



=>  F9(F7(Buffer4[15E])) = F7(Buffer4[15E]) + (2^k)*0Ah

		         = F7(Buffer4[15E] - 0Ah) 	



  So to pass the test, a key-generator can set :

Buffer5[15E] = Buffer4[15E] - 0Ah     and 

Buffer5[160] = Buffer4[160].





3)

   Then, some other checks are performed on the decrypted (after 3*F2) fourth 

buffer to detect some people (or companies) names, like "Peter Thomsen",

"Joshua Schultz" or "Velibor Cagalj / Zeit Systeme Gmbh" (sic). These 

chains are seeked at offsets 7Ah and E0h of the fourth buffer, the first 

byte containing the size of the chain to compare. Here's the code :



:1070	LODSB			; load size of the chain in the buffer

	MOV   AH,ES:[DI]	; load size of the model chain

	INC   DI

	MOV   CL,AL

	CMP   CL,AH		; compare sizes

	JBE   107D		; buffer size <= model size : ok

	MOV   CL,AH		; otherwise compare model size

:107D   OR    CL,CL

	JZ    1087

	XOR   CH,CH

	REPZ  CMPSB		; compare chains

	JNZ   1089

	CMP   AL,AH		; if chains are equal, set Z flag

:1089   MOV   DS,DX

	RET	



If one of these names if found, Terminate makes an awful "beeeeeep" 

before displaying the main screen ; of course, you are not registered

(maybe these guys didn't pay their key ?). This test also existed in 

version 4, but the list of names was slightly different (and shorter).



4)

At last, several other tests are done on this buffer, which can cause a

funny message to appear, asking you to delete your illegal keyfile :



Buffer[158]=FA98 & Buffer[156]=12FD

Buffer[74]=4638  & Buffer[72]=2391

Buffer[150]=FE6F & Buffer[14E]=DF92

Buffer[150]=C740 & Buffer[14E]=AE99

Buffer[150]=0000 & Buffer[14E]=B37B (*)

Buffer[150]=C740 & Buffer[14E]=AE66



(*) This test has been added from version 4 to version 5 : apparently, the

Terminate 4.0 keyfile generator from sULIVIAN [UCF] always fail it. I

suppose the author analyzed the key-generator, found a flaw in the PRNG

and added this specific test.





How to design a technique to assure that my key will be valid in any

further version of terminate ? 



  Well, if I look how the sULIVIAN's key-generator has been invalidated,

I only need to use a good PRNG to generate keyfiles with no constant 

bytes. Therefore the four rows of tests will be useless.



  But it's not so easy... for instance, what if some other bytes of 

the file have forbidden (or required) values : these values are avoided 

(or set) by the keymaker of the author, and not tested in version 5. But

in version 6, the author add a test for these bytes, so that only the 

keyfiles generated by his program are sure to pass the test. This means

in particular that without changing his key-generator, the author could 

create new versions of his software and invalidate previous key-generators. 



So here's the method I propose :



1) Find many original Terminate 5.0 keyfiles.

2) Decrypt them by using the algorithm used in Terminate 5.0 and described

   in part one : you can either trace in SoftIce and dump the buffers or 

   reuse pieces of code "from.class" tppabs="http://fravia.org/99solu/from.class" the key-generator.

3) You obtain the files that the author encrypted. In each file, you can 

   read in plain-text all the informations about the owner of the keyfile 

   (name, company, town,..) : replace all these informations by 00h.

4) Compare all these files : if you are lucky, they are identical. If not,

   try to figure out how the bytes that are different can be deduced 

   from the owner's informations (e.g. the first character before a string

   is its size).

5) From these files, create a "mask" (or in worst case, code a mask-maker)

   for your new key-generator : your program will overwrite only the bytes 

   containing informations about the owner, leaving the others bytes 

   unmodified.



This method will prevent your keyfile from current (and future) byte checks 

for forbidden/required values and may guarantee a longer life to your

keyfile. Unfortunately, I did not find any original keyfile so that I was

unable to test it...





PART FOUR : THE KEY GENERATOR 

  Here's the code of my key generator, roughly commented. I used a big

buffer to store and process all the 11*162h=3894 bytes and reused the code

of F1 and F2 from Terminate. The result is (without optimisation) a 9320 

bytes COM file. 



;-------------------------------------------------------;

;   TERMINATE 5.0 Key Generator  v 0.01                 ;

;                                                       ;

;       coded by Spath (09/98) for +HCU strainer 1999   ;

;       compile with tasm, link with tlink /t           ;             

;-------------------------------------------------------;

Code    Segment Byte Public

Assume  Cs:Code, Ds:Code, Es:Code

Org     100h

.386



Start:

        mov   ah,09

        mov   dx,offset IntroMsg

        int   21h                     ; show intro msg



        mov   ax,3d00h

        mov   dx, offset InFileName   ; open terminat.exe

        int   21h

        jc    ErrorReadingFile



        mov   cx,0001h                ; use constant pointer value

        mov   dx,0d984h

        mov   bx,ax

        mov   ax,4200h

        int   21h

        jc    ErrorSettingPointer     ; set pointer at the right position



        mov   ax,3f00h

        mov   cx,1100h

        mov   dx,offset TerminateDatas ; read Terminate datas       

        int   21h



        mov   ah,3Ch                   ; create keyfile

        xor   cx,cx

        mov   dx,offset OutFileName 

        INT   21h

        

FillAndAnalyseBuffer:

        mov   si,offset Buffer         ; fill the buffer

        mov   cx,0b1h*11      	       ; with random values	

FillBuffer:            

        call  Random

        mov   [si],ax

        inc   si

	inc   si

        loopne FillBuffer  



;------ FIRST LAYER OF ENCRYPTION : F1 & F2 over buffers 4 and 5 --------



        mov   MagicWord1, 0FFFFh

        mov   MagicWord2, 0FFFFh

        mov   si, (offset Buffer) + 3*162h + 5Bh  ; calculate Magic Words

        mov   F1Index,05Bh

        call  F1

        mov   si, (offset Buffer) + 3*162h  ; focus on buffer 4      

        mov   dx,MagicWord1                 ; write magic word 1

	mov   word ptr [si+015eh],dx

        mov   dx,MagicWord2                 ; write magic word 2

        mov   word ptr [si+0160h],dx

        mov   si, (offset Buffer) + 4*162h  ; focus on buffer 5	

        mov   dx,MagicWord1                 ; write magic word 1

        sub   dx,0Ah

        mov   word ptr [si+015eh],dx

        mov   dx,MagicWord2                 ; write magic word 2

        mov   word ptr [si+0160h],dx



        mov   FirstParameter,0904h	    ; first call to F2	

        mov   SecondParameter,33EEh

        call  F2

        mov   FirstParameter,325Ch	    ; second call to F2

        mov   SecondParameter,0000h

        call  F2

        mov   FirstParameter,0007h	    ; third call to F2

        mov   SecondParameter,0000h

        call  F2



        mov   BufferIndex,5Bh         

XORLoop:				  ; XOR loop 

        mov  si,(offset Buffer) + 3*162h 

        add  si,BufferIndex                                 

        mov  al,[si]             ; get byte from buffer

        xor  al,0FFh

        mov  [si],al             ; put new value in the buffer

        mov  ax,BufferIndex

        cmp  ax,161h             ; is it finished ?

        jz   SecondLayer

        inc  BufferIndex

        jmp  XORLoop             ; no : continue



;------ SECOND LAYER OF ENCRYPTION : F1 all over the file ---------------

SecondLayer:

        mov   MagicWord1, 0FFFFh

        mov   MagicWord2, 0FFFFh

        mov   si, offset Buffer         

Calculate:                              ; calculate magic words

        mov   F1Index,0                 ; process 15Dh bytes

	call  F1

        inc   byte ptr F1Count          ; one more buffer processed

        cmp   F1Count,0Bh               ; is it buffer 0Bh ?

        jz    ConcludeF1                ; yes : conclude

        add   si,05                     ; skip useless bytes

	jmp   Calculate



ConcludeF1:

        mov   si, (offset Buffer) + 10*162h

        mov   dx,MagicWord1

	mov   word ptr [si+015eh],dx	; write magic word 1

        mov   dx,MagicWord2

        mov   word ptr [si+0160h],dx	; write magic word 2



        mov   si,offset Buffer		; write to file

	mov   cx,162h*11

        call  WriteToFile



        mov   ah,09

        mov   dx,offset FileCreatedMsg

        int   21h                       ; show end msg

           

	jmp   ExitProgram



ErrorSettingPointer:			 ; error in setting pointer

        mov   ah,09			 

        mov   dx,offset ErrorPointerMsg

        int   21h			 ; show error msg

        jmp   ExitProgram



ErrorReadingFile:			 ; error in opening file

        mov   ah,09

        mov   dx,offset ErrorOpeningMsg

        int   21h                        ; show error msg



ExitProgram:

        int   20h                        ; exit



;----------- GENERAL PURPOSE PROCEDURES AND FUNCTIONS -------------------

WriteToFile   PROC  NEAR

        push  ax

        push  cx

        push  dx

        push  cx



OpenFile:

        mov   ax,3D82h                ; open the file

        mov   dx,offset OutFileName

        INT   21h

Write:

        mov   bx,ax

        xor   dx,dx

        xor   cx,cx                   ; set pointer at the end

        mov   ax,4202h                ; of the file

        INT   21h

        jc    end_pop_write_to_file

        mov   ah,40h

        pop   cx                      ; write in the file

        mov   dx,si

        INT   21h

        jc    end_write_to_file

        mov   ah,3Eh

        INT   21h                     ; close the file

        jmp   end_write_to_file



end_pop_write_to_file:

        pop   cx



end_write_to_file:

        pop   dx

        pop   cx

        pop   ax

        ret

WriteTofile ENDP





; very simple PRNG

; return a number in ax

Random  PROC NEAR

        push    cx 

        push    dx

        and     ah,0fh                    

        

        in      al,40h               ; get byte from timer

        mov     cx,ax

        mov     dx,RandSeed          ; get previous randseed

shake:

        mul     dx

        add     ax,4321h

        add     dx,7D96h

        loop    shake

        

	mov     RandSeed,ax	     ; save new seed

        pop     dx

        pop     cx

        ret

Random  ENDP



;----- CRYPTOGRAPHIC PROCEDURES AND FUNCTIONS --------------------

F1   PROC NEAR

label56B:

        mov  al,[si]                  ; load byte from buffer

        PUSH AX                       ; push byte read

        PUSH WORD PTR MagicWord2      ; push magic word 2

        PUSH WORD PTR MagicWord1      ; push magic word 1

        POP  BX                       ; BX = magic word 1

        POP  DX                       ; DX = magic word 2

        POP  CX                       ; CX = byte read

        PUSH DX                       ; push magic word 2

        PUSH BX                       ; push magic word 1

label595:

        XOR  BX,CX                       

        XOR  BH,BH                    ; clear upper byte  

        SHL  BX,1

        SHL  BX,1

        ADD  BX,offset TerminateDatas ; use loaded datas       

        MOV  AX,[BX]                  ; 

        MOV  CX,[BX+02]

        POP  BX                       ; BX = magic word 1

        POP  DX                       ; DX = magic word 1

        PUSH CX                       ; save CX

        MOV  CX,08                    ; initialise loop

label5AC:

        SHR  DX,1                     ; play with magic words

        RCR  BX,1

        LOOP label5AC                 ; same player play again                   

        AND  DX,0FFh

        POP  CX                       ; restore CX 

        XOR  AX,BX

        MOV  BX,CX

        XOR  DX,BX

        MOV  MagicWord1,AX           ; save magic word 1

        MOV  MagicWord2,DX           ; save magic word 2

        mov  ax,F1Index              ; test buffer index

        cmp  ax,15Dh                 ; 15Eh bytes processed ?

        jz   ExitF1                  ; yes : done

        inc  si

	inc  F1Index

        jmp  label56B                ; no : continue

ExitF1:

        ret

F1      ENDP





F2 PROC NEAR

       mov   BufferIndex,5Bh         

F2Start:

       CALL Proc2053            ; calculate new parameters

       mov  dx,ax

       mov  si,(offset Buffer) + 3*162h 

       add  si,BufferIndex		                   

       MOV  AL,[si]             ; get byte from buffer

       XOR  AH,AH

       XOR  AX,DX               ; calculate new value for this byte

       MOV  DL,AL

       MOV  [si],DL             ; put new value in the buffer

       MOV  AX,BufferIndex

       CMP  AX,161h             ; did we process 162h bytes ?

       JZ   OutF2

       INC  BufferIndex

       JMP  F2Start             ; no : continue

OutF2:

       RET

F2 ENDP



Proc2053   PROC NEAR

label2053:

       MOV  AX,FirstParameter         ; get first parameter

       MOV  BX,SecondParameter        ; get second parameter

       MOV  CX,AX

       MUL  Cst8405h                  ; DS:[12BC] = 8405

       SHL  CX,1                      ; play with parameters

       SHL  CX,1

       SHL  CX,1

       ADD  CH,CL

       ADD  DX,CX

       ADD  DX,BX

       SHL  BX,1

       SHL  BX,1

       ADD  DX,BX

       ADD  DH,BL

       MOV  CL,05

       SHL  BX,CL

       ADD  DH,BL

       ADD  AX,0001

       ADC  DX,00

       MOV  FirstParameter,AX        ; modify first parameter

       MOV  SecondParameter,DX       ; modify second parameter

       XOR  AX,AX

       MOV  BX,0100h               ; third parameter is always the same

       XCHG AX,DX

       DIV  BX

       XCHG AX,DX

label2097:

       RET  

Proc2053 ENDP        



;------------ DATAS ----------------------------------------------------

OutFileName db "terminat.key",0

InFileName db "terminat.exe",0

IntroMsg    db 13,10,'TERMINATE 5.0 Key Generator'

         db 13,10,'coded by Spath in 1998' 

	 db 13,10,'for the 1999 +HCU Strainer',13,10,'$'

ErrorOpeningMsg db 13,10,'Error: TERMINAT.EXE not found',13,10,'$'

ErrorPointerMsg db 13,10,'Error: incorrect pointer value',13,10,'$'

FileCreatedMsg db 13,10,'TERMINAT.KEY has been generated...',13,10,'$'

Buffer   db 162h*11 dup (0)

MagicWord1 dw 0FFFFh

MagicWord2 dw 0FFFFh

FirstParameter dw ?

SecondParameter dw ?

BufferIndex dw 0

RandSeed dw 5A9Ch

F1Count db 00h

F1Index dw 0

TerminateDatas db 1200h dup(0)

Cst8405h dw 8405h



code ends

End Start





FINAL NOTES 

  I really enjoyed working on Terminate, which was for me the most 

challenging part of the strainer. The author had good ideas to reject 

current (and future) false keys. Yet, the decryption scheme is weak, 

since F1 is useless and F2 its own inverse. As a result, the encryption 

and decryption algorithms are symetricals and not really complicated. 

Maybe he should have use real cryptography instead of multiple paranoid

checks.

						

						Spath. (09/98)



---------------------------------------------------------------------------

-= HCU STRAINER 1999 : CHALLENGE 2 =- Win32 byte patcher by Spath (08/98). Goal: 1. Create a Windows based 32 bit byte patcher for any target you wish, using any programming language. Tool: TASM 5.0 SoftIce 3.0 I) SOME EXPLANATIONS II) THE CODE PART ONE : SOME EXPLANATIONS My patcher is a simple "search and replace" one ; it loads pieces of the file in a internal buffer until it finds the byte sequence . Then the file pointer is set to this location it found and the new sequence is written to the file. This means in particular that the byte sequence must be unique, otherwise only the first occurence will be replaced. Why did I use SetFilePointer after I found the sequence ? Indeed, if the sequence is in the buffer I just loaded, the file pointer is already correctly set and I only need to re-write the full buffer. Yet, I thought that a problem would appear when the byte sequence start at the end of a buffer and continues at the begining of the next loaded one. For this task, I chose assembly because it is is my favourite language and because I wanted to play with TASM 5.0 (Thanks +Aesculapius !). As always, I also used SIce for debugging. Well, not much to say about this challenge, so here's... PART TWO : THE CODE ;-------------------------------------------------------; ; Simple Win32 Byte Patcher v 0.01 ; ; ; ; coded by Spath (08/98) for +HCU strainer 1999 ; ; compile: tasm32 -ml -m5 -q patcher ; ; link: tlink32 -Tpe -aa -x -c patcher ,,, import32 ; ;-------------------------------------------------------; .386p .model flat, stdCALL EXTRN ReadFile:PROC ; imported functions EXTRN WriteFile:PROC EXTRN CloseFile:PROC EXTRN CreateFileA:PROC EXTRN CloseHandle:PROC EXTRN MessageBoxA:PROC EXTRN SetFilePointer:PROC EXTRN ExitProcess:PROC INCLUDE WINDOWS.INC ; the famous one .DATA ;-- modify these datas to patch your target Filename db "virstop.exe",0 OldChain db 0fbh,81h,21h,0cdh,35h,0feh,0b8h,50h,0a7h,26h,10h NewChain db 00h,01h,02h,03h,04h,05h,06h,07h,08h,09h,10h ;-- internal constants FILE_ATTRIBUTE_NORMAL equ 080h OPEN_EXISTING equ 3 GENERIC_READ equ 80000000h GENERIC_WRITE equ 40000000h ;-- internal variables BufferSize equ 5000h Buffer db BufferSize DUP (?) ; here's the buffer FileHandle dd ? BytesRead dd ? CorrectBytes dd 0 ChainSize equ OFFSET NewChain - OFFSET OldChain ChainPos dd 0 ;-- strings cptPatcher db 'Patcher32',0 msgFileNotFound db 'Error: the file was not found...',0 msgPatchSuccesfull db 'The target has been succesfully patched !',0 msgSequenceNotFound db 'Error: the byte sequence was not found...',0 msgErrorWriting db 'Error: impossible to write to file...',0 .CODE START: push 0 push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push 0 push 0 push GENERIC_READ OR GENERIC_WRITE push OFFSET Filename call CreateFileA ; open the target file cmp eax,0FFFFFFFFh ; error ? jz ErrorOpeningFile ; yes : display error message mov FileHandle, eax ; no : save file handle mov esi,OFFSET OldChain ; start searching from the begining ReadLoop: push 0 push OFFSET BytesRead push OFFSET BufferSize push OFFSET Buffer push FileHandle call ReadFile ; fill the buffer mov edi,OFFSET Buffer ; start searching from the begining LoadByte: lodsb ; load a byte from OldChain cmp al,[edi] je GoodByte ; one byte in common found ? BadByte: mov esi, OFFSET OldChain ; no : search from the begining mov CorrectBytes,0 ; reset consecutive success counter jmp GoOn GoodByte: ; one correct byte was found inc CorrectBytes ; increment consecutive success counter cmp CorrectBytes,ChainSize ; did we find all the chain ? jz ChainFound ; yes : go to replace part GoOn: inc edi ; increment Buffer pointer cmp edi, OFFSET FileHandle ; is it the end of the buffer ? jne LoadByte ; no : check next byte cmp BytesRead,BufferSize ; yes : is it EOF ? jne SequenceNotFound ; yes : sequence not found add ChainPos, BufferSize ; adjust chain position jmp ReadLoop ; reload from file ChainFound: sub edi, ChainSize - 1 ; set edi at the begining of the chain add ChainPos, edi ; adjust chain position mov esi, OFFSET NewChain ; mov ecx, ChainSize repne movsb ; write new chain over older one sub edi, ChainSize ; adjust pointer sub ChainPos, offset Buffer push 0 ; set file pointer at ChainPos push 0 push ChainPos push FileHandle call SetFilePointer push 0 push offset BytesRead push ChainSize ; ChainSize bytes are written from push edi ; memory address edi into the file push FileHandle call WriteFile ; write to file cmp eax, 1 ; write access ok ? je Succesfull ; yes push 0 ; no : prepare "Error writing" message push offset cptPatcher push offset msgErrorWriting push 0 jmp Terminate Succesfull: push 0 ; prepare "Succesfull" message push offset cptPatcher push offset msgPatchSuccesfull push 0 jmp Terminate SequenceNotFound: push 0 ; prepare "Sequence not found" message push offset cptPatcher push offset msgSequenceNotFound push 0 jmp Terminate ErrorOpeningFile: push 0 ; prepare "File not found" message push offset cptPatcher push offset msgFileNotFound push 0 Terminate: call MessageBoxA ; display message box push 0 push FileHandle call CloseHandle ; close file call ExitProcess ; quit END START -------------------------------------------------------------------------- Greetings: _masta_, htak and Iczelion for their good Win32 ASM tutorials.
-= HCU STRAINER 1999 : CHALLENGE 3 =- BrainsBreaker v 2.1 (32 bits) by Spath (08/98). Goal: 1. Completely explain the protection scheme used by this program. Tools: SoftIce 3.01 (no need to upgrade, my old S3 card works fine) W32Dasm 8.9 (IDA not needed here) heXedit 4.3 (the fastest for search&patch) AT FIRST LOOK I) DEMO BOXES + SIDES LIMITATION II) TESTS OF INTEGRITY III) THE "DEMO" WORD ON THE PICTURES FINAL NOTES AT FIRST LOOK Well, this protection has at least one good point : the registration method is part of the registration secret. The limitations include demo boxes, a limition of the sides you can place and an awful "DEMO" word on the nicest puzzles. I removed them all. PART ONE : DEMO BOXES AND SIDES LIMITATION This part will not be very exciting, because it is just a reuse of well-known methods to remove all the annoying boxes. Since the strings of these boxes are not visible at first look, I just wait a little bit to let Bbrk32 load and decode them, and then I search in the data segment. For the first box ("Warning: the program is running in evaluation mode.You will be allowed to solve..."), the code is really straightforward : :00444D73 push 0000002C :00444D75 call 00420A08 ; see (1) :00444D7A mov ebx, eax :00444D7C test eax, eax ; should we show the box ? :00444D7E je 00444F88 ; no => go to 444F88 :00444D84 add esp, FFFFFFFC ; yes . . . :00444D94 call 00465533 ; display the box :00444D99 jmp 00444F88 ; box closed => 444F88 In the other case (the 3 first puzzles), the message displayed is "Brainsbreaker unregistered. Please see how to register." but the idea is the same : :00444EFE push 0000002C :00444F00 call 00420A08 ; see (1) :00444F05 mov [ebp-0088], eax :00444F0B test eax,eax ; should we show the box ? :00444F0D jz 00444F29 ; no Since before testing the result value, the prog always saves it somewhere else, I chose to make a 2 lines patch: Therefore I changed: 8BD8 mov ebx,eax into 33DB xor ebx,ebx 85C0 test eax,eax 33C0 xor eax,eax 898578FFFFFF mov [ebp-0088],eax into 33C0 xor eax,eax 85C0 test eax,eax 898578FFFFFF mov [ebp-0088],eax For the "Since now you will be allowed to lock x sides" boxes, the author used 2 counters, counter1 and counter2 respectively incremented by n and decremented by 2*n (1 Counter2 and you enter the "random display" zone (you also go there after 25 sides). In this zone, boxes are displayed depending on the clock for an average rate of. At last, if you have less then 8 sides to put, a box is displayed every time. Here's the code : :0044DCC7 movsx eax, word ptr [ebp+FFFFFE2E] ; read Counter1 :0044DCCE add eax, eax ; :0044DCD0 cmp eax, dword ptr [0048CA9D] ; is (2*Counter1) > Counter2 ? :0044DCD6 jl 0044DCE2 ; no : go to random test :0044DCD8 cmp word ptr [ebp+FFFFFE2E], 0019 ; is Counter1 > 19 ? :0044DCE0 jge 0044DCF4 ; yes : no box this time | :0044DCE2 call WINMM!TimeGetTime ; random test based on clock :0044DCE7 sub eax, dword ptr [0048CBFC] :0044DCED cmp eax, 00007530 ; :0044DCF2 jnb 0044DCFE ; no box this time :0044DCF4 cmp word ptr [ebp+FFFFFE2E], 0008 ; Counter1 > 8 ? :0044DCFC jg 0044DD68 ; yes : no box displayed :0044DCFE call WINMM!TimeGetTime ; no : prepare for the box :0044DD03 mov dword ptr [0048CBFC], eax :0044DD08 push 0000002C :0044DD0A call 00420A08 ; see (1) :0044DD0F mov dword ptr [ebp+FFFFFE10], eax :0044DD15 test eax, eax :0044DD17 je 0044DD5A ... ... :0044DD55 call 00465533 ; -= call the Message Box =- What is really funny is that counter1 is never decremented ! Each new value is calculated (with counter2) via a incremental loop which gets shorter and shorter (and which contains many crazy co-processor instructions) : :0044DC6E inc word ptr [ebp+FFFFFE2E] ; increment counter1 :0044DC75 add si, 0002 ; increment loop counter . . . :0044DCA7 movsx ecx, byte ptr [edx+54] :0044DCAB mov eax, dword ptr [ebp+FFFFFE24] ; this value is decreasing :0044DCB1 movsx edx, word ptr [eax+2*ecx+48] :0044DCB6 push edx :0044DCB7 call 0045D6E2 :0044DCBC movsx ecx, si :0044DCBF cmp eax, ecx :0044DCC1 jg 0044DBBA ; increment again Counter2 is incremented by this piece of code : :0045592E push 00000002 ; push increment :00455930 push 0048CA62 ; push base address (48CA9D - 3B) :00455935 call 0046E8A1 . . . :0046E8A4 mov eax, dword ptr [ebp+08] ; get base address :0046E8A7 mov edx, dword ptr [ebp+0C] ; get increment :0046E8AA add dword ptr [eax+3B], edx ; increment counter2 In spite of all these efforts, the crack was quite simple : I changed :0045592E 6A02 push 02 into 6A00 push 00 ; stop counter2 increment :0044DCD6 7C0A jl 44DCE2 into EB1C jmp 44DCF4 ; skip all the tests (1): Of course, I noticed that the real protection test seems to be in the push 2C/call 420A08 (which should return EAX=0) : the best crack would certainly have been to patch the 420A08 procedure, but this value seems not to be enough. Indeed, I tried to put a BPX 420a08 if @ss:(esp+4)==2c do "p ret ; r eax=0 ; g" but unfortunately, it crashed after a few calls ; so I decided to try to patch this way (if it had failed, I would have had to dig deeper into 420a08). PART TWO : TESTS OF INTEGRITY Ok, this works fine under SoftIce, but after having modified the bytes in Bbrk32.exe, I get a funny "Integrity check: Program seems to be altered from his original contents". So I put a breakpoint on MessageBoxA and find this piece of code : :0044358A push 00000000 :0044358C call 004410AF ; call integrity check procedure :00443591 cmp eax, 55443322 ; is it correct ? :00443596 je 0044360A ; yes, go on ... :00443598 push 00012010 ; display style :0044359D push dword ptr [00486B04] ; title of the window :004435A3 add esp, FFFFFFFC :004435A6 mov word ptr [esp], 00BE :004435AC call 0043A041 :004435B1 push eax ; text to display :004435B2 push 00000000 ; an orphaned window :004435B4 USER32!MessageBoxA ; display error box If I patch this location as follows :00443591 cmp eax, 55443322 into mov eax, 55443322 :00443596 je 0044360A jmp 0044360A and start again, I still receive the same infamous message... this paranoid author put more than one integrity test in his game. How can I find them all ? With the title parameter, stored in DS:00486B04, which is specific to this box (see (2)). Looking for the "push dword ptr [00486B0A]" instruction in the disassembled listing, I find 5 other locations : 41FD9F, 4447E9, 44C9E3, 453F77, 45D033. All these locations are patched as follows : :0041FD8A cmp dword ptr[00485880],00012345 into mov dword ptr[00485880],00012345 :0041FD94 je 0041FE3B into jmp 0041FE3B :004447E2 jnb 0044485F into jmp 0044485F :0044C9D8 jnb 0044CA63 into jmp 0044CA63 :00453F70 jnb 00453FED into jmp 00453FED :0045D02C jnb 0045D0A9 into jmp 0045D0A9 (2): This parameter is required, but it can be a/ read through indirect addressage : here I trust good old Borland C++ compiler for being consequent in its work. b/ mirrored somewhere else : here I trust the author's lack of imagination. PART THREE : THE "DEMO" WORD ON THE PICTURES For this part, I quickly found out that in every case the picture is displayed through a User32!UpdateWindow, which simply send a WM_PAINT message to the window. Since I am an absolute newbie in graphics coding, I needed a clue (4)... then I noticed that "DEMO" disappear when I click on the model picture. So I tried the following : 1) click on the picture and maintain the left button. 2) enter SIce and release the button. 3) "p ret" until I reach the Bbrk32 code Then I traced a little bit and... Bingo ! This is the graphical function I was looking for : GDI32!BitBlt. Therefore, I put a breakpoint on this function and write down all the calling adresses when I select a "clean" picture (f.i. the fish) from the main screen. Then I do the same thing with a "demo" picture (f.i. the cat). As expected, this last listing shows one more call to BitBlt ; here is a piece of the code that cause the "DEMO" word to appear (not only on the top left corner picture, but also on the small images and on the full-size one). :00428915 push 00AC0744 ; see (3) :0042891A movsx edx, word ptr [ebp-46] :0042891E push edx ; top left corner Y value (source) :0042891F movsx ecx, word ptr [ebp-48] :00428923 push ecx ; top left corner X value (source) :00428924 mov eax, dword ptr [ebp-40] :00428927 push [eax+04] ; source handle :0042892A movsx edx, word ptr [ebp-42] :0042892E push edx ; height of the picture :0042892F movsx ecx, word ptr [ebp-44] :00428933 push ecx ; length of the picture :00428934 push [ebp-3C] ; top left corner Y value (destination) :00428937 push [ebp-38] ; top left corner X value (destination) :0042893A push [esi+04] ; destination handle :0042893D Call GDI32!BitBlt ; display the bitmap My first idea was to change the push [edx+04] into push [esi+04], so that the bitmap is simply overwritten by itself. In fact, it removed the "DEMO" word, but the gray rectangle remained on the picture ; I therefore had to find which procedure called this piece of code. Back-tracing was not needed here, so with simple P RET and STACK commands I found these pieces of code: for the small pictures: :00462640 cmp dword ptr [ebp+FFFFFEC4], 00000000 ; write "DEMO" ? :00462647 je 004627C0 ; no : skip this part :0046264D mov eax, dword ptr [0048C918] ; yes for the top-left picture and the full-size one: :0045D317 push eax :0045D318 call 0046D872 :0045D31D test eax, eax ; write "DEMO" on the picture ? :0045D31F je 0045D427 ; no : skip this part :0045D325 mov edi, 004877AC ; yes And I change: :00462647 0F8473010000 je 004627C0 into jmp 004627C0 :0045D31D 85C0 test eax,eax into 33C0 xor eax,eax (3): This value is the colour combination parameter, as described in the wingdi.h file ("Ternary raster operations" paragraph) : CONST SRCCOPY : DWORD = 16_00CC0020; (* dest = source *) SRCPAINT : DWORD = 16_00EE0086; (* dest = source OR dest *) SRCAND : DWORD = 16_008800C6; (* dest = source AND dest *) ... (a total of 15 parameters are available) By the way, the 00AC0744 value is not described in it (??) (4): After all this work I realized that the "DEMO" string reference in W32dasm was immediately leading to this piece of code... :( FINAL NOTES Well, I enjoyed cracking this game, especially for part 3 (part 2 was quite disapointing, though). I learned a few things about graphical interface, which were very useful for the last challenge. Obviously, the author spent some time on the protection, which deserve respect, even if he chose complexity instead of precision. I think that my crack is a little bit heavy (12 patches)... I wish I have had more time to spend on the registration part. Here's the summary of my crack: offset old chain new chain Boxes: 4437A 8B D8 85 33 DB 33 44505 89 85 78 FF FF FF 85 C0 33 C0 89 85 78 FF FF FF Integrity tests: 1F394 0F 84 A1 E9 A2 00 42B91 3D 22 33 44 55 74 B8 22 33 44 55 EB 43DE2 73 EB 4BFD8 0F 83 85 E9 86 00 53570 73 EB 5C62C 73 EB "DEMO": 5C91D 85 33 61C47 0F 84 73 01 E9 74 01 00 Sides limitation: 54F2F 02 00 4D2D6 7C 0A EB 1C Spath. (08/98) ----------------------------------------------------------------------------- Greetings: - +Frog's Print, a master cracker and a nice person. - BeLZeBuTH, Ethan, Kellogs, CyberbobJr and all the guys on +FP's forums.
-= HCU STRAINER 1999 : ULTIMATE CHALLENGE =- BrainsBreaker v 2.1 (32 bits) by Spath (09/98). Goal: The objective of this challenge is to check that: 1. The participant understands the graphical part of demo-reversing. Tools: SoftIce 3.0 TASM 5.0 I) SOME EXPLANATIONS II) THE CODE PART ONE : SOME EXPLANATIONS The little star animation of BrainsBreaker is made of 14 bitmaps, each of these bitmaps being run-time calculated. Each bitmap is made of (at most) 3 parts : - a main white cross. - 1 to 3 ellipses, each one inside the previous one, to make a gradation of colours (the center is always white). - some pixels and little crosses around when the star is disappearing. The graphical functions involved are : - MoveToEx() and LineTo() to draw the crosses. - Ellipse() to draw the ellipses (no kidding). - CreatePen() and CreateSolidBrush() to choose the pens and brushes colors. The stars I create are slightly different (bigger,...) from BBrk32's : the main reason is that I understood this challenge as a programming one, since I did not find much to reverse-engineer. I therefore did not try to copy-paste the disassembled code of BBrk32, but instead tried to write my own. However, you can obtain almost the same stars if you change these parameters : DARK_COLOR : color of largest ellipse LIGHT_COLOR : color of middle ellipse SIDE_CROSS_NB : number of side crosses per bitmap ELLIPSE_GAP : distance between two ellipses SmallCrossSize : small crosses height & width CrossSize : main cross heights & widths EllipseSize : main ellipse heights and widths PART TWO : THE CODE ;-------------------------------------------------------; ; "Star Truc" v 0.01 ; ; ; ; coded by Spath (09/98) for +HCU strainer 1999 ; ; contains code from Henry S. Takeuchi (Htak) ; ; compile: tasm32 -ml -m5 -q startruc ; ; link: tlink32 -Tpe -aa -x -c startruc ,,, import32 ; ;-------------------------------------------------------; .386 .model flat,STDCALL MAX_BMP_HEIGHT equ 400 MAX_BMP_WIDTH equ 600 MF_END = 0080h ; end of menu template ; Define Win95 structures ; POINT STRUC ptX dd ? ptY dd ? POINT ENDS MSG STRUC msgWnd dd ? msgMessage dd ? msgWparam dd ? msgLparam dd ? msgTime dd ? msgPt POINT ? MSG ENDS PAINTSTRUCT STRUC psDC dd ? ; hdc psErase dd ? ; fErase psRect dd ? ; rcPaint ; the following reserved by Windows psRestore dd ? ; fRestore psIncUpdate dd ? ; fIncUpdate psRGB db 16 dup(?) ; rgbReserved PAINTSTRUCT ENDS WNDCLASS STRUC wcStyle dd ? ; style wcWndProc dd ? ; lpfnWndProc wcClsExtra dd ? ; cbClsExtra wcWndExtra dd ? ; cbWndExtra wcInstance dd ? ; hInstance wcIcon dd ? ; hIcon wcCursor dd ? ; hCursor wcBackgroundBrush dd ? ; hbrBackground wcMenuName dd ? ; lpszMenuName wcClassName dd ? ; lpszClassName WNDCLASS ENDS MF_SEPARATOR = 0800h MF_STRING = 0000h MF_POPUP = 0010h MF_END = 0080h ; end of menu template ; ; Window messages ; WM_CREATE = 0001h WM_DESTROY = 0002h WM_PAINT = 000Fh WM_TIMER = 0113h WM_COMMAND = 0111h WM_LBUTTONDOWN = 0201h ; ; Window styles ; WS_OVERLAPPED = 0 WS_CAPTION = 00C00000h WS_THICKFRAME = 00040000h WS_SYSMENU = 00080000h WS_MINIMIZEBOX = 00040000h WS_MAXIMIZEBOX = 00020000h WS_VISIBLE = 10000000h WS_OVERLAPPEDWINDOW = WS_OVERLAPPED or WS_CAPTION or WS_THICKFRAME or \ WS_SYSMENU or WS_MINIMIZEBOX or WS_MAXIMIZEBOX WS_EX_RIGHTSCROLLBAR = 0 ; scrollbar on right or bottom WS_EX_LEFT = 0 ; left alignment WS_EX_LTRREADING = 0 ; left-to-right reading SRCCPY = 00CC0020h ; define prototypes (since chal2 I read NetWalker code :) ) CreateSolidBrush PROCDESC WINAPI :DWORD CreatePen PROCDESC WINAPI :DWORD, :DWORD, :DWORD SelectObject PROCDESC WINAPI :DWORD, :DWORD Ellipse PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD, :DWORD MoveToEx PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD LineTo PROCDESC WINAPI :DWORD, :DWORD, :DWORD BitBlt PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, \ :DWORD, :DWORD, :DWORD, :DWORD MessageBoxA PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD FloodFill PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD Rectangle PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD, :DWORD PostQuitMessage PROCDESC WINAPI :DWORD DeleteObject PROCDESC WINAPI :DWORD DeleteDC PROCDESC WINAPI :DWORD ExitProcess PROCDESC WINAPI :DWORD GetDC PROCDESC WINAPI :DWORD ReleaseDC PROCDESC WINAPI :DWORD, :DWORD GetMessageA PROCDESC WINAPI :DWORD, :DWORD, :DWORD, :DWORD DispatchMessageA PROCDESC WINAPI :DWORD BeginPaint PROCDESC WINAPI :DWORD, :DWORD EndPaint PROCDESC WINAPI :DWORD, :DWORD LoadCursorA PROCDESC WINAPI :DWORD, :DWORD CreateCompatibleDC PROCDESC WINAPI :DWORD CreateCompatibleBitmap PROCDESC WINAPI :DWORD, :DWORD, :DWORD GetModuleHandleA PROCDESC WINAPI :DWORD RegisterClassA PROCDESC WINAPI :DWORD LoadMenuIndirectA PROCDESC WINAPI :DWORD ; this one is clearer this way extrn CreateWindowExA:proc ; no argument for these one extrn GetTickCount:proc extrn DefWindowProcA:proc .data ; local window ; msgbuffer MSG <> wc WNDCLASS <0,MainWndProc,0,0,0,0,0,2,0,szClassName> wndPaintStruct PAINTSTRUCT <> ; ; Handles ; appInst dd 0 ; hInstance of application module appMenu dd 0 ; hMenu of application window menu wndDC dd 0 ; hDC of window client area bmpDC dd 0 ; hDC of bitmap "canvas" bmpH dd 0 ; handle to bitmap bmpPrev dd 0 ; handle to original bitmap assigned to bmpDC bmpBrush1 dd 0 ; handle to first brush for bitmap DC wndColorBrush dd 0 ; handle to solid brush for window color hWhitePen dd 0 ; handles of pens hDarkPen dd 0 hLightPen dd 0 hBlackPen dd 0 hWhiteBrush dd 0 ; handles of brushes hDarkBrush dd 0 hLightBrush dd 0 ; ; Menu templates ; IDM_EXIT equ 101 IDM_HELP equ 901 IDM_ABOUT equ 902 appMenuTemplate dw 0 ; menu template version dw 0 ; offset from end of header to menu item list dw MF_STRING or MF_POPUP dw '&','F','i','l','e',0 dw MF_STRING or MF_END,IDM_EXIT dw 'E','&','x','i','t',0 dw MF_STRING or MF_POPUP or MF_END dw '&','H','e','l','p',0 dw MF_STRING,IDM_HELP dw '&','H','e','l','p','.','.','.',0 dw MF_SEPARATOR,0 dw 0 dw MF_STRING or MF_END,IDM_ABOUT dw '&','A','b','o','u','t','.','.','.',0 ; ; auxiliary window class information ; szClassName db 'Window03',0 ; miscellaneous string data appCaption db 'Star Truc v 0.01',0 helpCaption equ appCaption helpText db 'Just click in the window',0Dh,0Ah db 'to make stars...',0 aboutCaption db 'About Star Truc',0 aboutText db 'Coded by Spath for +HCU strainer 1999.',0Dh,0Ah db 'based on code by Henri S. Takeuchi.',0 ; ; graphical datas ; DARK_COLOR equ 00B94264h ; color of largest ellipse LIGHT_COLOR equ 00FFE010h ; color of middle ellipse SIDE_CROSS_NB equ 2 ; number of side crosses per bitmap ELLIPSE_GAP equ 2 ; distance between two ellipses PreviousPoint dd 0,0 ; previous X and Y value SmallCrossSize dd 4,4 ; small crosses height & width ; main cross height & width CrossSize dd 5,2,8,4,12,7,16,9,20,12,16,9,12,7,8,4,5,2,0,0 ; ellipse height & width values EllipseSize dd 0,0,4,2,8,5,12,8,16,10,12,8,10,5,4,2,0,0,0,0 Index dd 0 ; pointer for main cross and main ellipse sizes EllipseIndex dd 0 ; pointer for the two small ellipses sizes SideCounter dd 0 ; counter of little stars PosX dd 0 ; X value of the left-button click PosY dd 0 ; Y value of the left-button click .code ;-------------------------------------------------------------------------- ; This is where the program starts. _start: call GetModuleHandleA,0 ; get hmod (in eax) mov [appInst],eax ; HINSTANCE is the same as HMODULE in Win32 ; ; Complete the WNDCLASS structure. ; mov [wc.wcInstance],eax call LoadCursorA, 0, 32512 mov [wc.wcCursor],eax ; ; Create and display our window. ; call RegisterClassA, offset wc ; returns ATOM, 0 = error call LoadMenuIndirectA, offset appMenuTemplate mov [appMenu],eax ; ; Create colors ; call CreatePen, 0, 1, 0 ; create black tools mov [hBlackPen], eax xor eax, eax call CreateSolidBrush, eax mov [wndColorBrush],eax call CreatePen, 0, 1, 00FFFFFFh ; create white tools mov [hWhitePen], eax call CreateSolidBrush, 00FFFFFFh mov [hWhiteBrush], eax call CreatePen, 0, 1, DARK_COLOR ; create dark tools mov [hDarkPen], eax call CreateSolidBrush, DARK_COLOR mov [hDarkBrush], eax call CreatePen, 0, 1, LIGHT_COLOR ; create light tools mov [hLightPen], eax call CreateSolidBrush, LIGHT_COLOR mov [hLightBrush], eax ; ; Create window ; push large 0 ; lpParam push [appInst] ; hInstance push [appMenu] ; menu hmenu push large 0 ; parent hwnd push large 200 ; height push large 200 ; width push large 100 ; y push large 100 ; x push large (WS_OVERLAPPEDWINDOW or WS_VISIBLE) ; Style push offset appCaption ; Window text (caption) push offset szClassName ; Class name push large (WS_EX_LEFT or WS_EX_LTRREADING \ or WS_EX_RIGHTSCROLLBAR) ; extended style call CreateWindowExA ; ; Process messages, quit when WM_QUIT received. ; msg_loop: call GetMessageA, offset msgbuffer, 0, 0, 0 or eax,eax je end_loop ; WM_QUIT message call DispatchMessageA, offset msgbuffer jmp msg_loop ; ; Terminate program. ; end_loop: call ExitProcess, [msgbuffer.msgWparam] ;---------------------------------------------------------------- ; The window procedure...where messages for one class of windows ; are processed. ; MainWndProc: mov eax,[esp+8] ; message ID cmp eax,WM_PAINT ; from Windows je paint_client cmp eax,WM_COMMAND ; from menu, accelerator, or control je execute_command cmp eax,WM_LBUTTONDOWN ; mouse button has been pressed je LeftMouseDown cmp eax,WM_CREATE ; window created, about to show it je creating_window cmp eax,WM_DESTROY ; about to start window destruction je start_destroy jmp DefWindowProcA ; delegate other message processing ; ; Process WM_COMMAND. ; execute_command: mov eax,[esp+12] ; wParam and eax,large 0FFFFh ; command ID cmp eax,IDM_EXIT ; test exit command je exit_command cmp eax,IDM_HELP ; test help command je help_command cmp eax,IDM_ABOUT ; test about command je about_command xor eax,eax ; none of these ret 16 exit_command: ; exit command : quit call PostQuitMessage, 0 xor eax,eax ret 16 help_command: ; help command : display help box mov eax,[esp+4] call MessageBoxA, eax, offset helpText, offset helpCaption, 0 xor eax,eax ret 16 about_command: ; about command : display about box mov eax,[esp+4] call MessageBoxA, eax, offset aboutText, offset aboutCaption, 0 xor eax,eax ret 16 ; ; Process WM_PAINT. Some part of the client area needs to be (re)painted. ; paint_client: mov eax,[esp+4] ; hwnd call BeginPaint, eax, offset wndPaintStruct call BitBlt, eax, 0, 0, MAX_BMP_WIDTH, MAX_BMP_HEIGHT, \ [bmpDC], 0, 0, SRCCPY mov eax,[esp+4] ; hwnd call EndPaint, eax, offset wndPaintStruct xor eax,eax ret 16 ;- - - - - - - - - - - - - - - - - - - - - - - - - - - ; Process WM_LBUTTONDOWN. Left mouse button has been pressed. ; (here start the code which create stars) ; LeftMouseDown: mov esi, offset CrossSize ; start of main cross sizes array mov edi, offset EllipseSize ; start of big ellipse size array add esi, Index ; calculate new cross size add edi, Index ; calculate new ellipse size call BasicStep ; display the cross + 3 ellipses call SmallDelay ; wait a little bit call ClearScreen ; clear the screen mov esi, offset CrossSize add esi, Index cmp esi, (offset EllipseSize)-8 ; is it finished ? jge EndLeftMouseDown ; yes : exit add Index, 8 ; no : prepare next step jmp LeftMouseDown EndLeftMouseDown: call DisplayBitmap ; clear screen mov Index, 0 xor eax,eax ret 16 ; ------------ Graphical Procedures and functions --------------------- ; BasicStep procedure : each step of the animation is computed here ; BasicStep proc near call GetDC, dword ptr [esp+8] mov [wndDC],eax ; hDC for window ComputeEllipses: call SelectObject, [bmpDC], [hDarkBrush] call SelectObject, [bmpDC], [hDarkPen] mov EllipseIndex, 0 mov dx,[esp+22] ; HIWORD(lParam) = y mov eax,[esp+20] ; LOWORD(lParam) = x and edx,large 0FFFFh ; dx = y pos and eax,large 0FFFFh ; ax = x pos mov PosX, eax ; save origin mov PosY, edx call DrawEllipse ; draw dark ellipse cmp dword ptr [edi],4 ; can we draw more ellipses jle ComputeCross ; no call SelectObject, [bmpDC], [hLightBrush] call SelectObject, [bmpDC], [hDarkPen] mov EllipseIndex, ELLIPSE_GAP call DrawEllipse ; draw light ellipse call SelectObject, [bmpDC], [hWhiteBrush] call SelectObject, [bmpDC], [hWhitePen] mov EllipseIndex, 2*ELLIPSE_GAP call DrawEllipse ; draw white ellipse ComputeCross: call SelectObject, [bmpDC], [hWhitePen] call DrawCross ; draw the cross ComputeSideShow: cmp Index, 48 ; should we add sideshow jl DisplayAll ; not so soon call SideShow ; add sideshow DisplayAll: call DisplayBitmap ; display the bitmap call ReleaseDC, dword ptr [esp+8], [wndDC] ret BasicStep endp ; DrawCross procedure : draw a cross centered on (ax,dx) and of ; size 2*(esi+4, esi) ; DrawCross proc near pusha mov eax, PosX ; get origin mov edx, PosY mov dword ptr [PreviousPoint], eax mov dword ptr [PreviousPoint+4], edx sub eax,dword ptr [esi+4] Call MoveToEx, [bmpDC], eax, edx, 0 ; set cursor mov eax, dword ptr[PreviousPoint] add eax, dword ptr [esi+4] Call LineTo, [bmpDC], eax, [PreviousPoint+4] ; draw horiz. line mov edx, dword ptr [PreviousPoint+4] sub edx, dword ptr [esi] Call MoveToEx, [bmpDC], [PreviousPoint], edx, 0 ; set cursor mov edx, dword ptr[PreviousPoint+4] add edx, dword ptr [esi] Call LineTo, [bmpDC], [PreviousPoint], edx ; draw vert. line popa ret DrawCross endp ; DrawEllipse procedure : draw an ellipse centered on (ax,dx) ; and of size 2*(edi+4,edi) ; DrawEllipse proc near pusha mov eax, PosX ; get origin mov edx, PosY mov ebx, eax ; save x pos mov ecx, edx ; save y pos add edx, dword ptr [edi] add eax, dword ptr [edi+4] sub eax, EllipseIndex ; down right X value sub edx, EllipseIndex ; down right Y value sub ecx, dword ptr [edi] add ecx, EllipseIndex ; top left Y value sub ebx, dword ptr [edi+4] add ebx, EllipseIndex ; top left X value call Ellipse, [bmpDC], ebx, ecx, eax, edx popa ret DrawEllipse endp ; SideShow procedure : when the main star is disappearing, little ; stars must appear. ; SideShow proc near pusha mov eax, PosX ; get origin mov edx, PosY sub eax, dword ptr [CrossSize+36] ; set X origin of side show sub edx, dword ptr [CrossSize+32] ; set Y origin of side show mov PosY, edx ; save Y origin mov PosX, eax ; save X origin mov dword ptr [SideCounter], SIDE_CROSS_NB SideLoop: xor eax,eax ; choose color in al, 40h cmp al, 30h jl WhiteOne call SelectObject, [bmpDC], [hLightPen] ; light cross jmp GoOn WhiteOne: call SelectObject, [bmpDC], [hWhitePen] ; white cross GoOn: dec dword ptr [SideCounter] mov ebx, PosX ; get X origin mov edx, PosY ; get Y origin xor eax,eax in al,40h and al,17h add edx,eax ; add random value to Y in al,40h and al,0Fh add eax,ebx ; add random value to X mov PosX, eax mov PosY, edx mov esi, offset SmallCrossSize call DrawCross cmp [SideCounter], 0 jne SideLoop popa ret SideShow endp ; DisplayBitmap procedure : display bmpDC in wndDC ; DisplayBitmap proc near call BitBlt, [wndDC], 0, 0, MAX_BMP_WIDTH, MAX_BMP_HEIGHT, \ [bmpDC], 0, 0, SRCCPY ret DisplayBitmap endp ; ClearScreen procedure : just paint a big black rectangle ; ClearScreen proc near pusha call SelectObject, [bmpDC], [wndColorBrush] ; black pen call SelectObject, [bmpDC], [hBlackPen] ; black brush call Rectangle, [bmpDC], 0, 0, MAX_BMP_WIDTH-1, \ MAX_BMP_HEIGHT+25-1 popa ret ClearScreen endp ; SmallDelay procedure : just to wait a bit between 2 bitmaps ; SmallDelay proc near pusha call GetTickCount ; get initial value mov ebx, eax add ebx, 0020h ; calculate end value WaitLoop: call GetTickCount ; wait until we reach end value cmp eax, ebx jle WaitLoop popa ret SmallDelay endp ;----------------------------------------------------------- ; Process WM_CREATE. ; creating_window: call GetDC, dword ptr [esp+4+0] mov [wndDC],eax ; hDC for window call CreateCompatibleDC, eax ; create "canvas" DC mov [bmpDC],eax call CreateCompatibleBitmap, [wndDC], MAX_BMP_WIDTH, \ MAX_BMP_HEIGHT+25 ; create canvas mov [bmpH],eax call SelectObject, [bmpDC], eax ; bind canvas to DC mov [bmpPrev],eax ; save original bmp (canvas) call Rectangle, [bmpDC], 0, 0, MAX_BMP_WIDTH-1, \ MAX_BMP_HEIGHT+25-1 call CreateSolidBrush, 0 mov [wndColorBrush],eax call SelectObject, [bmpDC], eax ; replace brush mov [bmpBrush1],eax ; save original brush call FloodFill, [bmpDC], 0, 0, 0 ; clear background call FloodFill, [bmpDC], 1, 1, 0 call SelectObject, [bmpDC], [bmpBrush1] ; restore brush call ReleaseDC, dword ptr [esp+4], [wndDC] ; release DC xor eax,eax ret 16 ; Process WM_DESTROY. ; start_destroy: call SelectObject, [bmpDC], [bmpBrush1] ; restore original brush call DeleteObject, [wndColorBrush] ; delete our brushes call DeleteObject, [hWhiteBrush] call DeleteObject, [hLightBrush] call DeleteObject, [hDarkBrush] call DeleteObject, [hWhitePen] ; delete our pens call DeleteObject, [hBlackPen] call DeleteObject, [hLightPen] call DeleteObject, [hDarkPen] call SelectObject, [bmpDC], [bmpPrev] ; restore original bitmap call DeleteObject, eax ; delete our canvas call DeleteDC, [bmpDC] ; delete canvas DC call PostQuitMessage, 0 xor eax,eax ret 16 end _start ---------------------------------------------------------------------------- Greetings : - Henry S. Takeuchi (Htak) and NetWalker, for good usage of TASM - Earl Gray, who helped me to work so late.