· Cracking NetTerm 4.2.7 ·

© 1998 by iNCuBuS++ / MiB


Software: NetTerm 4.2.7
Tools: Softice (any versions for Windows), an ASM compiler & pen and paper

'ello folks!!

I heard a friend of mine say "This is one evil s.o.b. ..." while he was trying to crack this proggie, and I heard others too, saying it's 'kinda hard' , so I decided to destroy it's fame of being difficult to crack.


Let's go straight to the works...

- Choose 'Register NetTerm...' option from Help menu
- Enter something as your name and the registration number, and before you hit 'OK' button, CTRL-D to SoftICE...
- Let's define a simple macro that will display parameters on the stack when a breakpoint occurs. We'll call it 'PARAMS':

macro PARAMS = "dd ss:esp+4"

Now we can execute the macro simply by typing its name like any other command. But we defined it for another purpose... Put a:

bpx GetDlgItemTextA DO "PARAMS"

return to the NetTerm and hit 'OK'. The ICE will pop up immediately at the beginning of the GetDlgItemTextA function and our little macro will dump the function's parameters on stack in the data window when the break occurs. Now, remember GetDlgItemTextA's API definition: the pointer to the buffer that receives the text is the third parameter on stack and therefore the third DWORD on the stack (it's the fourth DWORD actually, but we skipped the first one - the return address; thus ESP+4). Note here that we could make a more usefull macro that could immediately dump the contents of the buffer in the data window:

macro GETTEXT = "db (ss:esp+4)->8"
So, the breakpoint would look like this:
bpx GetDlgItemTextA DO "GETTEXT"
What have we done here? We took esp+4 (the address of the first parameter on stack) as the base address of the parameter array and using C-like syntax, we reffered to the contents of the third DWORD in the array (which starts at offset 8). This third dword happens to be the address of the buffer that receives the text from the edit box, so the above command 'db' will display the contents of the memory that begins at that address.

Ok...so, at the first break you should get the address of the NAME buffer. Exit the function with F11 and the buffer will be filled with entered name. Put a bpr on the name string and resume the program. It will break again at another GetDlgItemTextA. This one reads in the registration number. When you exit the function with F11, the buffer will fill up with the reg# you entered. Put a bpr on that string as well and resume the program again. Smack! You land in the middle of KERNEL32!lstrcmp function that is called from here:


:4644ED 680C804700   push 47800C		;ptr.to "33413953"
:4644F2 8B4510       mov eax, [ebp+10]		;ptr.to the entered reg#
:4644F5 50           push eax
:4644F6 FF151C924700 call [KERNEL32!lstrcmp]	;compare two strings
:4644FC 85C0         test eax, eax		;do they match?
:4644FE 0F8507000000 jnz 46450B			;jump if not!

Hmmm....it can't be that easy...Let's try it! Type 33413953 as the registration number and hit 'OK'. Nope! That's not it. That was too easy anyway. "Back to the drawing board..." as Wile E. Coyote would say ;]]] So, let's do all of the above steps again, but this time proceed after lstrcmp. We'll land in the middle of KERNEL32!lstrlen function that is called from here:

:464764 8B450C       mov eax,[ebp+0C]		;ptr. to the entered reg#
:464767 50           push eax
:464768 FF1528924700 call [KERNEL32!lstrlen]	;get length of the reg# string
:44476E 8945F4       mov [ebp-0C],eax		;store length

OK, let's go further. The next break occurs just a few instructions later:

:4647A2 8B45EC     mov eax, dword ptr [ebp-14]	;let's assume this is kind of an offset
:4647A5 8B4D0C     mov ecx, dword ptr [ebp+0C]	;ptr.to entered reg#
:4647A8 8A0408     mov al, byte ptr [eax+ecx]	;get a char. from reg# string
:4647AB 50         push eax			;push it as the 2nd parameter
:4647AC 8B4508     mov eax, dword ptr [ebp+08]	;ptr.to a table (this is interesting ;)
:4647AF 50         push eax			;push it as the 1st parameter
:4647B0 E851000000 call 464806			;let's call this a translation routine
:4647B5 8845FC     mov byte ptr [ebp-04], al	;store translated digit
:4647B8 8B45EC     mov eax, dword ptr [ebp-14]	;get offset
:4647BB 8B4D0C     mov ecx, dword ptr [ebp+0C]	;get ptr. to entered reg#
:4647BE 8A440801   mov al, byte ptr [eax+ecx+01];get the next char.
:4647C2 50         push eax			;push it as the 2nd parameter
:4647C3 8B4508     mov eax, dword ptr [ebp+08]	;get a ptr. to the translation table
:4647C6 50         push eax			;push it as the 1st parameter
:4647C7 E83A000000 call 464806			;translate character
:4647CC 8845F8     mov byte ptr [ebp-08], al	;store translated digit
:4647CF 33C0       xor eax, eax			;clear work register
:4647D1 8A45FC     mov al, byte ptr [ebp-04]	;get 1st converted digit
:4647D4 C1E004     shl eax, 04			;move it to high nibble of AL
:4647D7 33C9       xor ecx, ecx			;clear work register
:4647D9 8A4DF8     mov cl, byte ptr [ebp-08]	;get 2nd converted digit
:4647DC 0BC1       or eax, ecx			;combine it with the 1st digit into a number
:4647DE 8945F0     mov dword ptr [ebp-10], eax	;store fully translated number
:4647E1 8A45F0     mov al, byte ptr [ebp-10]	;get translated number
:4647E4 8B4DE8     mov ecx, dword ptr [ebp-18]	;get the buffer offset
:4647E7 8B5510     mov edx, dword ptr [ebp+10]	;get the ptr.to the buffer
:4647EA 880411     mov byte ptr [ecx+edx], al	;store the translated number into the buffer
:4647ED FF45E8     inc [ebp-18]			;insrease buffer offset (and a loop counter)
:4647F0 E996FFFFFF jmp 46478B			;back to the looptop

The translation table looks like this:

:44E9A8 61 31 32 33 34 35 36 37 38 39 63 66 69 6B 75 71     a123456789cfikuq
:44E9B8 33 41 39 53                                         3A9S

Now...Hey! Wait a minute! What's that in the table? That 33 41 39 53 looks just like...Yes, that's the reg# we tried the first time, remember ? Only now it is written as an integer array, not as ASCII string. This, of course, doesn't have to mean anything. Still, I sense something fishy..
Ok, let's go back to the above code. What this routine basically does is - it takes a chr from the entered reg#, converts it to a hex digit using the translation table ( 'a' = 0, '1' = 01h, '2' = 02h, .... '9' = 09h, 'c' = 0Ah, 'f' = 0Bh, ... 'q' = 0Fh) and stores it as the high nibble (a half byte - 4 bits) of a byte; then, it takes the next chr, converts it in the same way and stores it as the low nibble of a byte. High and low nibbles are then combined into a single byte and that byte is stored in the buffer. Offset into the buffer is then increased by 1 and the offset into the reg# string by 2. This process repeats until entire reg# has been converted. Thus, the 1st&2nd chr of the reg# form the first converted byte, the 3rd&4th form the second byte and so on...

The return parameter from this routine is the length of the array of converted bytes.

Next, we land here:


:46456B E8EB010000   call 46475B			;WE CAME FROM HERE
:464570 8D85FCFCFFFF lea eax, dword ptr [ebp-0304]	;ptr. to the array of converted bytes
:464576 50           push eax				;push it as the 2nd parameter
:464577 8D85FCFDFFFF lea eax, dword ptr [ebp-0204]	;ptr. to destination buffer
:46457D 50           push eax				;push it as the 1st parameter
:46457E FF15E091011C Call dword ptr [KERNEL32!lstrcpyA]	;copy the array
:464584 8D85FCFCFFFF lea eax, dword ptr [ebp-0304]	;ptr.to the array
:46458A 50           push eax
:46458B 8D85FCFDFFFF lea eax, dword ptr [ebp-0204]	;ptr.to the array copy
:464591 50           push eax
:464592 8B450C       mov eax, dword ptr [ebp+0C]	;ptr.to the name string
:464595 50           push eax
:464596 8B4508       mov eax, dword ptr [ebp+08]	;array length
:464599 50           push eax
:46459A E839000000   call 4645D8			;THE KEYGEN ROUTINE
:46459F 8D8500FFFFFF lea eax, dword ptr [ebp-0100]	ptr.to string "3A9S" ...remember
:4645A5 50           push eax				;what that is ?
:4645A6 8D85FCFCFFFF lea eax, dword ptr [ebp-0304]	;ptr.to the generated string
:4645AC 50           push eax
:4645AD FF151C92011C Call dword ptr [KERNEL32!lstrcmpA]	;compare strings
:4645B3 85C0         test eax, eax			;do they match?
:4645B5 0F850F000000 jne 4B45CA				;if not, beggar off !
Ok, so, there we are... If we experimet with different combinations of the name and reg#, we'll see that "3A9S" string always remains the same while the other one is always different. So, if we think about it and look at the parameters passed to the keygen routine, we can conclude that the entered name and reg# are combined together to form some string (or byte array, call it whatever you like). This array must match our magic string/array "3A9S" (33 41 39 53). Now it's clear what that number we encountered before was for....

Ok, if we know what's going on, can we regg it ? Hmmm...look tough - we would have to analyse the keygen algorythm in order to reverse the keygen process: if a combination of the name and the correct reg# produces "3A9S" string, then we must make the algorythm that would produce the correct reg# by combining the name and the magic string. Before we start tracing through the keygen code, we might consider what I like to call The Empirical Method, briefly described as "Try it and see if it works" method - a handy alternative for those who are not yet able to use zen ;]]] What does the above process look to you? My immediate thought was that it looks like a big xor operation, and we know that xor operations are reversibile:

				A XOR B = C , and then
				C XOR B = A
What am I trying to say here? Well, if we pass the name and the correct reg# to the keygen routine to produce the magic string, then perhaps we could pass the name and the magic string to the same routine to produce the correct registration number. Now, the easiest way to enter the magic string instead of the reg# is to type it in AS the reg# in the 'Register NetTerm' dialog box. The magic array 33h 41h 39h 53h , is translated to the ASCII using the same translation table as before. So we get "33413953" string,enter it as our reg#, hit 'OK' , but then we just get 'Invalid key' message. Breakpoints set around the keygen routine did not fire. Why? ....Remember that first lstrcmp ? Well, that's it. Programmers anticipated that someone will try this, so they placed a countermeasure, which is stupid btw, considering the fact that we can edit the reg# buffer directly in ICE. So, let's go back to the code immediately after the translation routine, specifficaly to :464570. There's LEA that loads the ptr.to the translated reg# into EAX, so we can take that address here and edit its contents:
				eb [array_addr] 33 41 39 53
That should do it... We'll trace quickly over the calls until we get to the code after the keygen routine. Now let's take a look at our generated array. It requires translation to ASCII string using the translation table. Now that we translated it , we can enter it as the reg#. Disable the breakpoints, enter the reg#, hit 'OK' and.....and......... .... .... and YES! It works! Now it's clear that the same algo is used for both generating the reg# and verifying it (this means we can use this very routine in our key generator). For those who didn't understand how we translated the 4-byte array into the 8-chr string, here's the example: I took my handle - iNCuBuS++ and from keygen routine got the array 9Ah,16h,3Ch,E0h. Look at the translation table:

HEX DIGIT    0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
ASCII CHR   'a'  '1'  '2'  '3'  '4'  '5'  '6'  '7'  '8'  '9'  'c'  'f'  'i'  'k'  'u'  'q'

So, the translation goes:

ARRAY		        9A             16             3C             E0
		       /  \           /  \           /  \           /  \
HEX DIGITS	    09h    0Ah     01h    06h     03h    0Ch     0Eh    00h
		     |      |       |      |       |      |       |      |
ASCII CHRS	    '9'    'c'     '1'    '6'     '3'    'i'     'u'    'a'

And there we have it. Registration number for me is 9c163iua . Simple, isn't it?

In order to make a key generator for this proggie, we now must analyse the keygen algo and implement it in our code. We'll have to decide whether we'll just rip the entire keygen routine and put it in our key generator or we'll reimplement it. This decision depends on the keygen code itself. If it requires too much source editing or the keygen branches into many subroutines, maybe it would be better to reimplement the algorythm. In our case we'll reimplement the algo, although we could've just ripped it, as you'll see yourself. To the keygen algo ! We'll skip less important parts and go straight to the main keygen loop. Grab a pencil and a piece of paper and write down every step of the algorythm.


:464635 FF45EC         inc [ebp-14]                     ;Increase offset into the array.
:464638 8B4508         mov eax, dword ptr [ebp+08]	;Get the length of the array.
:46463B 3945EC         cmp dword ptr [ebp-14], eax	;Did offset reach the end of array?
:46463E 0F8D41000000   jnl 464685			;If it did, we're done, so jump!
:464644 C745E800000000 mov [ebp-18], 00000000		;Clear inner loop's counter.
:46464B E903000000     jmp 464653			;Jump to the beginning of the loop.
:464650 FF45E8         inc [ebp-18]			;Increase inner loop's counter.
:464653 8B45E8         mov eax, dword ptr [ebp-18]	;Get the current counter value.
:464656 3945F0         cmp dword ptr [ebp-10], eax	;Did the counter reach the namelen ?
:464659 0F8E21000000   jle 464680			;If it did, the loop is over.
:46465F 8B45F4         mov eax, dword ptr [ebp-0C]	;Get the name buffer offset (I guess
:464662 50             push eax				;it is always 0).
:464663 8B45F8         mov eax, dword ptr [ebp-08]	;Get length of the array.
:464666 50             push eax
:464667 8B45FC         mov eax, dword ptr [ebp-04]	;Get ptr.to the name buffer.
:46466A 50             push eax
:46466B 8B45EC         mov eax, dword ptr [ebp-14]	;Get the offset into the array.
:46466E 8D8405E8FDFFFF lea eax, dword ptr [ebp+eax-0218];Get ptr.to the current byte
:464675 50             push eax				;of the array.
:464676 E822000000     call 46469D			;Do the math stuff with the byte.
:46467B E9D0FFFFFF     jmp 464650			;Go back to the inner loop's top.
:464680 E9B0FFFFFF     jmp 464635			;Go back to the outer loop's top.
:464685 8D85E8FDFFFF   lea eax, dword ptr [ebp-0218]
:46468B 50             push eax
:46468C 8B4514         mov eax, dword ptr [ebp+14]
:46468F 50             push eax
:464690 FF15E091011C   Call dword ptr [KERNEL32!lstrcpyA]
:464696 5F             pop edi
:464697 5E             pop esi
:464698 5B             pop ebx
:464699 C9             leave
:46469A C21000         ret 0010

Now, let's see what's actually happening with entered reg#:

:46469D 55             push ebp			
:46469E 8BEC           mov ebp, esp
:4646A0 53             push ebx
:4646A1 56             push esi
:4646A2 57             push edi
:4646A3 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:4646A6 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646A9 0FBE0408       movsx eax, byte ptr [eax+ecx]	;Get a byte from the name buffer.
:4646AD 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646B0 0FBE09         movsx ecx, byte ptr [ecx]	;Get the 1st byte from the name buff.
:4646B3 0FAF4D14       imul ecx, dword ptr [ebp+14]	;1st byte * name buff offset.
:4646B7 33C1           xor eax, ecx			;Taken_byte XOR (1st byte*nb_offset).
:4646B9 8B4D08         mov ecx, dword ptr [ebp+08]	;Get ptr. to the array.
:4646BC 3201           xor al, byte ptr [ecx]		;Previous_result XOR byte_from_array.
:4646BE 8B4D08         mov ecx, dword ptr [ebp+08]	;Get ptr. to the array.
:4646C1 8801           mov byte ptr [ecx], al		;Replace byte_from_array with the result.
:4646C3 8B4510         mov eax, dword ptr [ebp+10]	;Get the array length.
:4646C6 48             dec eax				;Decrease it.
:4646C7 3B4514         cmp eax, dword ptr [ebp+14]	;Did it reach the name buffer offset?
:4646CA 0F8F20000000   jg 4646F0			;If it is greater, skip next code.
:4646D0 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:4646D3 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646D6 0FBE0408       movsx eax, byte ptr [eax+ecx]	;Get a byte from the name buffer.
:4646DA 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646DD 0FBE09         movsx ecx, byte ptr [ecx]	;Get the 1st byte from the name buff.
:4646E0 03C1           add eax, ecx			;Add 2 bytes.
:4646E2 8B4D14         mov ecx, dword ptr [ebp+14]	;Get the name buffer offset.
:4646E5 8B550C         mov edx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646E8 880411         mov byte ptr [ecx+edx], al	;Store the result of adding.
:4646EB E920000000     jmp 464710			
:4646F0 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:4646F3 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:4646F6 0FBE440801     movsx eax, byte ptr [eax+ecx+01] ;Get the next byte from the name buff.
:4646FB 8B4D14         mov ecx, dword ptr [ebp+14]	;Get the name buffer offset.
:4646FE 8B550C         mov edx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:464701 0FBE0C11       movsx ecx, byte ptr [ecx+edx]	;Get the current byte from the name buff.
:464705 03C1           add eax, ecx			;Add 2 bytes.
:464707 8B4D14         mov ecx, dword ptr [ebp+14]	;Get the name buffer offset.
:46470A 8B550C         mov edx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:46470D 880411         mov byte ptr [ecx+edx], al	;Replace the curr.byte with the result.
:464710 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:464713 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:464716 0FBE0408       movsx eax, byte ptr [eax+ecx]	;Get the result.
:46471A 85C0           test eax, eax			;Is it 0?
:46471C 0F8514000000   jne 464736			;If not, don't adjust.
:464722 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:464725 8B4D0C         mov ecx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:464728 0FBE0408       movsx eax, byte ptr [eax+ecx]	;Get the result.
:46472C 40             inc eax				;Adjust the result.
:46472D 8B4D14         mov ecx, dword ptr [ebp+14]	;Get the name buffer offset.
:464730 8B550C         mov edx, dword ptr [ebp+0C]	;Get ptr. to the name buffer.
:464733 880411         mov byte ptr [ecx+edx], al	;Store adjusted result.
:464736 FF4514         inc [ebp+14]			;Increase the name buffer offset.
:464739 8B4510         mov eax, dword ptr [ebp+10]	;Get the array length.
:46473C 394514         cmp dword ptr [ebp+14], eax	;Does name buff offset exceed the length?
:46473F 0F8C07000000   jl 46474C			;If not, skip.
:464745 C7451400000000 mov [ebp+14], 00000000		;Clear the name buff offset.
:46474C 8B4514         mov eax, dword ptr [ebp+14]	;Get the name buffer offset.
:46474F E900000000     jmp 464754			;STUPID!
:464754 5F             pop edi				
:464755 5E             pop esi				
:464756 5B             pop ebx				
:464757 C9             leave				
:464758 C21000         ret 0010				

So, what do we have here? We have 2 nested loops doing the whole thing. The outer loop's purpose is to pass the current byte of the array to the inner loop for processing. Once the inner loop has finished, the outer loop moves to the next byte of the array. The outer loop ends when all the bytes from the array have been processed. The inner loop's role is to take the current byte, pass it to the subroutine that does the math stuff with it and pass the resulting byte to the next iteration. The number of iterations equals the length of the name. The byte processing is done directly in the array.
And now to the math stuff (a piece of cake). I may be wrong, but my guess is that the math subroutine was designed for something other than NetTerm, since it has not been used in the way it was designed for. Take a look - it is capable of doing a bit more complex stuff with the array than it does here. I tried many different names of different length, but the name buffer offset was always 0. So, some parts of the math algo always get skipped. This is convenient for us, since we'll have less work reimplementing the algo in our key generator.

Here's the complete algo:


  .---------------------- WHILE ARRAY_OFFSET <= ARRAY_LENGTH DO
  |
  |                     	LOOP_COUNTER = 0
  |
  |   	  .-------------- WHILE LOOP_COUNTER < NAME_LENGTH DO
  |   	  |
OUTER	INNER      		CURRENT_ARRAY_BYTE = 1ST_BYTE_FROM_NAME XOR CURRENT_ARRAY_BYTE
LOOP	LOOP         		1ST_BYTE_FROM_NAME = 1ST_BYTE_FROM_NAME + 2ND_BYTE_FROM_NAME
  |	  |           		LOOP_COUNTER = LOOP_COUNTER + 1
  |	  |
  |	  `-------------- END INNER_LOOP
  |
  |				ARRAY_OFFSET = ARRAY_OFFSET + 1;
  |
  `---------------------- END OUTER_LOOP

It's easy as that... Again, I have to stress out that this algo layout is valid only if we assume that name buffer offset is always 0 (as I think it is).

And now...the keygenerator ! You should write it yourself, of course, but I give the full source code of my keygen as an example (Otherwise, I might sue you for stealing my code ;)))) It is written for NASM and won't assemble with other assemblers.


;************************************************************************
;*                                                                      *
;*         NETTERM KEY GENERATOR program by iNCuBuS++ [MiB] 1998        *
;*                                                                      *
;************************************************************************
;
                BITS 16
                ORG 0x100


start:          push    cs
                pop     ds              ; DS = CS
                mov     ax,0003h        ;video mode=text 80x25
                int     10h             ;SET VIDEO MODE

                mov     dx,logo
                call    print           ;PRINT STRING

request_input:  mov     dx,input
                call    print           ;REQUEST USER INPUT

                mov     dx,buffer       ;pointer to buffer
                mov     ah,0Ah          ;
                int     21h             ;BUFFERED INPUT

                mov     dx,eol
                call    print

                lea     si,[buffer+1]
                lodsb                   ;get length
                cmp     al, 2           ;string less than 2 chrs.?
                jge     keygen          ;if NOT, proceed with convert

                mov     dx,wrong
                call    print
                jmp     request_input

keygen:         lea     di, [initkey]           ;ptr. to the magic number
                mov     bx, di                  ;save ptr.
                movzx   bp, al                  ;save name length
                mov     dx,4                    ;set the outer loop counter

main_loop:      mov     cx, bp                  ;set the inner loop counter
kg_loop:        mov     al,[si]                 ;  get 1ST_BYTE_FROM_NAME
                xor     [di],al                 ;  CURRENT_ARRAY_BYTE XOR 1ST_BYTE_FROM_NAME
                mov     al,[si+1]               ;  get 2ND_BYTE_FROM_NAME
                add     [si],al                 ;  1ST_BYTE_FROM_NAME + 2ND_BYTE_FROM_NAME
                loop    kg_loop                 ; END INNER_LOOP

                inc     di                      ;next array byte
                dec     dx                      ;decrement outer loop's counter
                jnz     main_loop               ;END OUTER_LOOP

                lea     si, [conv_tbl]          ;ptr.to translation table
                lea     di, [buffer]            ;ptr.to the output buffer
                mov     cx, 4                   ;set the loop counter
convert:        movzx   bp, byte [bx]           ;get the byte from the array
                mov     dx, bp                  ;save it
                and     bp, 00F0h               ;keep high nibble
                and     dx, 000Fh               ;keep low nibble
                shr     bp, 4                   ;move high nibble to its place
                mov     al, [si+bp]             ;get the corresponding chr.from table
                stosb                           ;write it to the output buffer
                mov     bp, dx                  ;
                mov     al, [si+bp]             ;get the corresponding chr.from table
                stosb                           ;write it to the output buffer
                inc     bx                      ;next array byte
                loop    convert                 ;back to looptop

                mov     al,'$'                  ;mark the end of the string
                stosb

                mov     dx,output
                call    print                   ;PRINT STRING

                mov     dx,buffer
                call    print

                mov     dx,eol
                call    print

exit:           mov     ax,4c00h
                int     21h             ;Exit to DOS with exit code

;

print:          mov     ah,09
                int     21h             ;PRINT STRING
                ret

;
;

;

buffer:         db      32
                times   33 db   0


;
;

input:          db      "Enter your name: $"

output:         db      "Your registration number is: $"

wrong:          db      "ERROR! The name must be AT LEAST 2 characters !!!",0Dh,0Ah,0Dh,0Ah,"$"

conv_tbl:       db      "a123456789cfikuq"

initkey:        db      033h, 041h, 039h, 053h

;
;

logo:           db      '°ÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ°',0Dh,0Ah
                db      '°³          Net Term V4.2.7           ³°',0Dh,0Ah
                db      '°³      Cracked by iNCuBuS++ [MiB]    ³°',0Dh,0Ah
                db      '°ÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁ°',0Dh,0Ah
eol:            db      0Dh,0Ah,'$'

Back to Tutor page!Back to tutorial page.

Copyright © MiB 1998. All rights reversed.