name spyprime rt equ 0dh ; return lf equ 0ah ; linefeed eof equ 1ah ; end of file ok_seg equ 1 ; flag for segment decode check ok_off equ 2 ; flag for offset decode check max_size equ 4095 ; maximum size for SPY' search region beep_delay equ 4 ; number of clock ticks between beeps timer_count equ 597 ; frequency of beep = 1,193,180 / timer_count ; These constants are used by the newint16h routine in this, the SPY' program. ; They are not needed for the SPY program. ckey1 equ 20cdh ; value of first key (a "backwords" int 20) ckey2 equ 1234h ; value of second key ckey3 equ 5678h ; value of third key code segment assume cs:code, ds:code org 0 key1 dw ? ; offset of int 20h instruction ; used to verify a PSP location org 100h begin: jmp start ; jump to program start location ;------------------------------------------------------------------------------- ; This copyright message appears when SPY'.COM is "typed" to the screen. ;------------------------------------------------------------------------------- db 8,8,8,' ' copyright db rt,lf db "SPY' Copyright (C) 1987 by Charles Lazo III, v1.0" db eof,8,' ',rt,lf,'$' key2 dw ckey2 ; keys used to determine prior load of SPY' key3 dw ckey3 oldint8 dd ? ; double word storage for old int 8 vector oldint16h dd ? ; double word storage for old int 16 vector beeps dw 0 ; number of beeps (notices) to send to user delay dw 1 ; number of clock ticks 'til next beep beep_on db 0 ; beep on/off status spy_size dw ? ; number of bytes to spy on resident db 0 ; indicates prior load of SPY' (0 = none) spy_location label dword spy_off dw ? ; storage for value of offset for SPYing spy_seg dw ? ; storage for value of segment for SPYing ;------------------------------------------------------------------------------- ; Comparisons of the spied region and the spy buffer (set up initially as a copy ; of the spied region) are made in this addition to the clock interrupt. ; Changes to the spied region are counted and the spy buffer is updated. The ; speaker is turned on and off here, but frequency is set during program ; initialization. ;------------------------------------------------------------------------------- newint8 proc near push ax ; save interrupted program's registers push bx push cx push si push di push ds push es mov ax,cs ; our data lies here mov ds,ax lea si,spy_buffer ; point to our buffer les di,spy_location ; point to spied region mov cx,spy_size ; compare whole region cld ; forward! cmp_more: repz cmpsb ; compare until no match or cx = 0 jz cmp_done ; if zero, then cx = 0 and we're done inc beeps ; account for a change mov al,es:[di-1] ; change accounted; update spy_buffer mov [si-1],al or cx,cx ; set zero flag by cx (avoid inf loop) jmp short cmp_more ; continue 'til done cmp_done: cmp beep_on,0 ; is the beep on? jz do_beep? ; no, shall we do a beep? dec beep_on ; yes, turn it off in al,97 ; get speaker control bits and al,0fch ; set them off out 97,al ; turn off speaker jmp short exit ; job done; get out do_beep?: cmp beeps,0 ; are there beeps to be done? jz delay1 ; no, get out dec delay ; reduce delay 'til next beep jnz exit ; not zero, then exit in al,97 ; get speaker control bits or al,3 ; set them on out 97,al ; turn on speaker inc beep_on ; signal beep is on mov delay,beep_delay; reinitialize delay counter dec beeps ; one less beep to do jmp short exit ; leave now delay1: mov delay,1 ; don't wait for first beep of a series exit: pop es ; restore registers pop ds pop di pop si pop cx pop bx pop ax jmp dword ptr cs:oldint8 ; continue with interrupt newint8 endp ;------------------------------------------------------------------------------- ; Keyboard interrupt, int 16h, is issued with function number ah = 77h in the ; set_es routine. If there has been no prior load of SPY', then the interrupt ; returns without changing anything. However, if SPY' has been run previously, ; then this routine has been chained into the interrupt 16h handler and the ; issue of "keyboard" function number 77h causes the returned value of es to be ; what cs was for the original load of SPY'. ;------------------------------------------------------------------------------- newint16h proc near cmp ah,77h ; our cue? jne not_us ; nah, not us push ax ; save this mov ax,ckey1 ; have we a match for the first key? cmp ax,cs:key1 je test_2nd ; yes, test the 2nd key jmp short onward ; no previous load found test_2nd: mov ax,ckey2 ; have we a match for the second key? cmp ax,cs:key2 je test_3rd ; yes, test the 3rd key jmp short onward ; no previous load found test_3rd: mov ax,ckey3 ; have we a match for the third key? cmp ax,cs:key3 jne onward ; no match => no previous load pop ax ; unstack saved value mov ax,cs ; pass this cs in es mov es,ax mov ax,ckey1 ; pass these to inform set_es mov bx,ckey2 iret onward: pop ax ; recover this not_us: jmp dword ptr cs:oldint16h ; continue with interrupt newint16h endp ;------------------------------------------------------------------------------- ; Here the region spied upon is copied to an area of the TSR that remains in ; memory after termination, the spy_buffer. The es register is equal either to ; cs if SPY' has not yet been loaded or to the value of cs when SPY' was loaded ; previously (accomplished by a call to the set_es routine). ;------------------------------------------------------------------------------- copy_spied: lea di,spy_buffer ; destination of copy mov si,spy_off ; the offset is source of copy mov cx,spy_size ; number of bytes to copy mov ax,spy_seg ; load segment of SPY' region to ds push ds ; SOD (save our data) mov ds,ax cld ; forward copy rep movsb ; copy the SPY' region to spy_buffer pop ds ; RestoreOD cmp resident,0 ; is SPY' currently resident in memory? je tsr ; no, make it so mov ax,4c00h ; yes, end with error code 0 int 21h ;------------------------------------------------------------------------------- ; SPY' has not yet been loaded into memory, so we do it now. First the 8253-5 ; chip that generates the frequency of the beeps is initialized with the value ; given by the constant timer_count. Then the new interrupt 8 routine is ; spliced in and finally the constant max_size is used to reserve enough memory ; for the largest permissible spy buffer. ;------------------------------------------------------------------------------- tsr: mov delay,beep_delay; initialize delay counter ; set 8253-5 programmable timer to chosen frequency mov al,182 ; byte to initialize 8253 timer out 67,al ; tell timer next two bytes are count mov ax,timer_count ; get timer count out 66,al ; output low byte mov al,ah out 66,al ; output high byte mov ax,3508h ; get the interrupt 8 (clock) vector int 21h ; with DOS function 35h call ; Retain offset and segment of interrupt 8 vector: mov word ptr cs:oldint8,bx mov word ptr cs:oldint8+2,es lea dx,newint8 ; place offset in dx mov ax,2508h ; set its pointer into interrupt table int 21h ; with DOS function 25h call mov ax,3516h ; get the int 16h (keyboard) vector int 21h ; with DOS function 35h call ; Retain offset and segment of interrupt 16h vector: mov word ptr cs:oldint16h,bx mov word ptr cs:oldint16h+2,es lea dx,newint16h ; place offset in dx mov ax,2516h ; set its pointer into interrupt table int 21h ; with DOS function 25h call lea dx,spy_buffer ; where SPY' buffer begins mov cx,max_size ; add the maximum size of the buffer: add dx,cx mov cl,4 ; compute number of paragraphs to save: shr dx,cl ; bytes to paragraphs in dx inc dx ; insure sufficient size for buffer mov ax,3100h ; terminate but stay resident code = 0 int 21h ;------------------------------------------------------------------------------- ; The following is initialization code and data that is overwritten by the data ; copied to the spy buffer when this buffer is initialized with a copy of the ; spied region. ;------------------------------------------------------------------------------- spy_buffer: ; this is where SPY' will store a copy of the SPY' region ; to later check for any changes to the region syntax db rt,lf,"SPY' syntax: SPY' xxxx:yyyy L zzz",rt,lf,rt,lf db 'Where xxxx, and yyyy are hexadecimal numbers up to ' db 'four digits in length and',rt,lf db "zzz is a one to three digit hexadecimal number. SPY' " db 'will monitor the segment-',rt,lf db 'offset region xxxx:yyyy for a length of zzz bytes and ' db 'report to the user with a',rt,lf db 'number of beeps equal to the number of bytes changed ' db 'in that region if and when',rt,lf db 'any are changed.',rt,lf,'$' progress db 0 ; flags for progress of command line conversion ;------------------------------------------------------------------------------- ; This routine will convert an ASCII hex digit in al (with either upper or lower ; case alpha hex) into a 1-of-16 binary value in al. ;------------------------------------------------------------------------------- make_binary proc near ; hex ASCII digit in al to binary in al cmp al,'0' ; less than "0", then not hex digit jb not_digit cmp al,'f' ; greater than "f", then not hex digit ja not_digit cmp al,'9' ; test for numeric digit ja not_numeric ; not a numeric digit sub al,'0' ; convert to binary ret not_numeric: cmp al,'F' ; over "F"? ja low_case ; yes, test for lower case cmp al,'A' ; less than "A"? jb not_digit ; yes, not hex digit sub al,37h ; convert to binary ret low_case: cmp al,'a' ; less than "a"? jb not_digit ; yes, not hex digit sub al,57h ; convert to binary ret not_digit: stc ; set carry flag if not hex digit ret make_binary endp ;------------------------------------------------------------------------------- ; This routine is called to allow the command line parse routines to ignore any ; space and tab characters on the command line that do not lie in hex numbers. ; If the return character at the end of the command line is encountered, then ; the carry flag is set; otherwise it is reset. ;------------------------------------------------------------------------------- skip proc near ; skip over spaces and tabs; signal ; a RETURN with the carry flag more_skip: cmp al,' ' ; is it a space? je another ; if so, get another cmp al,9 ; is it a tab? je another ; if so, get another cmp al,rt ; is it a RETURN? je a_rt ; yes, set carry and return clc ; not one of these three so return ret ; with carry clear another: lodsb ; get another character jmp short more_skip ; and try again a_rt: stc ; return with carry set ret skip endp ;------------------------------------------------------------------------------- ; Here we parse the command line, which by the conditions of proper syntax has ; the three hexadecimal values for segment, offset and byte size for the region ; which is to be spied. The routine between cnv_more: and cnv_error: is run ; once each to decode segment, offset and size which are stored respectively in ; the variables spy_seg, spy_off, and spy_size unless a syntax error is found. ; The variable progress is used to keep track of which of these three numbers ; has just been decoded. A semicolon can be used to preface comments at the end ; of the command line. ;------------------------------------------------------------------------------- convert proc near ; convert hex ASCII to binary in ax mov cx,16 ; for hex to binary conversion cnv_more: xor bx,bx ; accumulate result here xor ah,ah ; need this zero too dec si ; move back to last character cycle: lodsb ; get possible ASCII hex digit call make_binary ; if ASCII hex, then make binary in al jc test_seg ; if carry set, then char not hex digit xchg ax,bx ; accumulation in ax, last digit in bx mul cx ; bump to next power or dx,dx ; test for overflow jnz cnv_error ; result can't be larger than fff0h add ax,bx ; add in most recent digit xchg ax,bx ; accumulation in bx, ah = 0 jmp short cycle ; continue conversion cnv_error: pop ax ; remove return location (of call) jmp stax ; display syntax and end test_seg: test progress,ok_seg ; have we decoded the segment value? jnz test_off ; yes, test offset call skip ; skip over spaces and tabs jc cnv_error ; carry flag set if RETURN was found cmp al,':' ; syntax says must have colon here jne cnv_error lodsb ; get another call skip ; skip over spaces and tabs jc cnv_error ; carry flag set if RETURN was found mov spy_seg,bx ; store value of segment for SPYing or progress,ok_seg ; segment value is now decoded jmp short cnv_more ; continue to convert offset and size test_off: test progress,ok_off ; have we decoded the offset value? jnz test_size ; yes, test size call skip ; skip over spaces and tabs jc cnv_error ; carry flag set if RETURN was found and al,0dfh ; convert "L" or "l" to "L" cmp al,'L' ; syntax says must have "l" or "L" here jne cnv_error lodsb ; get another call skip ; skip over spaces and tabs jc cnv_error ; carry flag set if return was found mov spy_off,bx ; store value of offset for SPYing or progress,ok_off ; offset value is now decoded jmp short cnv_more ; continue to convert size test_size: cmp bx,max_size ; size must not be over maximum ja cnv_error call skip ; skip over spaces and tabs jnc comment ; shall allow comments at cmd line end cnv_finished: mov spy_size,bx ; store away size ret comment: cmp al,';' ; test for comment symbol jne cnv_error ; not comment symbol, then error jmp short cnv_finished ; all done convert endp ;------------------------------------------------------------------------------- ; This is the routine that contains the code that this program was designed to ; demonstrate. It serves to detect if SPY' has been loaded previously as a TSR. ; If it has, then the code segment at the time of the previous load will be put ; into es; if not, then es will return with the current value of cs. The method ; used attempts to find the set of key values key1, key2, and key3 in the memory ; above the current PSP. key1 is word cd20h which is the int 20h instruction ; found at offset 0 of every PSP. If key1 matches, then key2 is tried and if it ; matches key3 is tried. A triple match gives fair confidence that SPY' has ; been run as a TSR once before. It is even possible to gain further confidence ; or detect the presence of a different version of the same program by comparing ; a string in the returned es to a string in cs, say the copyright notice. Note ; that the keys are compared in the newint16h routine that handles the function ; 77h if SPY' has been previously loaded. ;------------------------------------------------------------------------------- set_es proc near ; es becomes cs of original SPY' load mov ah,77h ; function to find previous load address int 16h cmp ax,ckey1 ; if ax = ckey1 jne not_loaded cmp bx,ckey2 ; and bx = ckey2 jne not_loaded mov resident,1 ; then there's been a prior load ret not_loaded: mov ax,cs ; else, return with es = cs mov es,ax ret set_es endp ;------------------------------------------------------------------------------- ; Execution starts here. The command line is decoded, new parameters are passed ; to the prior load if it exists, and the beeps variable is set to zero for a ; fresh start. Execution is then passed to copy_spied: and other termination ; code that can not be overwritten by the copy process. ;------------------------------------------------------------------------------- start: lea dx,copyright ; display copyright mov ah,9 int 21h mov bx,80h ; get command line byte count mov al,[bx] cmp al,1 ja parse ; if command given, parse it stax: lea dx,syntax ; display command syntax mov ah,9 int 21h mov ax,4c01h ; and exit with error code 1 int 21h parse: mov si,82h ; address first usable character lodsb call skip ; skip over spaces and tabs jc stax ; carry is set if RETURN was found call convert ; convert and store hex values call set_es ; set es to spy_buffer segment mov ax,spy_seg ; set SPY' region to newly given values mov es:spy_seg,ax ; (this isn't necessary for first mov ax,spy_off ; time through, but it doesn't mov es:spy_off,ax ; hurt either.) mov ax,spy_size mov es:spy_size,ax xor ax,ax ; initialize beeps count mov es:beeps,ax jmp copy_spied ; copy that which is to be spied code ends end begin