Compatible, Resident
Windows 95 Viruses
by DV8/IRG
This article assumes the reader is familiar with articles in VLAD-6 by Quantum and Qark - it would take unnecessary room to cover that ground again. When I started investigating Windows 95, a shade under a year ago, I decided to make a list of matters I felt were important in any virus running under that pseudo-OS or needed further consideration. The basic list isn't long...
- Full compatability. - Stay in memory after program termination. - What about Ring-0/Ring-3 incompatabilities? - Insert itself into the '95 boot sequence. - Other
Full Compatability
Compatability is the single most important thing any virus needs. How
many old viruses can you name off the top of your head which failed to
funtion properly once an undocumented change was made to an undocumented
part of it's base operating system? This is what happened with Bizatch
and will continue to happen with any virus that does not implement full
OS compatability.
Keeping compatability some very important questions arise.
- How do I call functions?
- How do I get loaded?
- How do I monitor file activity?
- Compatable Function Calling.
Anyone who has read the VLAD-6 articles on Windows 95 will be
familiar with this problem.
Quantum's solution was to locate the internal KERNEL32.DLL entry
point and call KERNEL32 functions by ordinal. However, a
compatability problem arose for him. If a newer version of KERNEL32
was in use or if the ordinal of a function he used was changed his
code was rendered unusable, either causing a fault (which Windows
95 should handle) or trashing the run-time and necessitating a
reboot.
Unfortunately each time MS releases an update to Win95 this is
likely to happen.
How can increase our compatability then? Also, how can we call
functions in a different module (ie. not KERNEL32.DLL)?
The answer becomes apparent when a few facts about Win95 are
considered.
- There is no hardware protection of memory.
This means that any valid memory address can be read or
written with impunity.
- KERNEL32.DLL is always loaded at the same starting address.
KERNEL32.DLL, regardless of version (language, revision,
etc) is always loaded to the same starting address in
memory, as long as that module is for Windows 95 (ie.
the base load address is different under Windows NT).
- Loaded files are mapped into memory peicemeal.
When a DLL is loaded it is opened and copied into memory,
as a whole. Next certain references are resolved (like the
actual address of data items). Finally control is passed
to the initialisation portion of the DLL.
- DLL files are of the PE type format.
Part of the PE format is an export table. '95 uses this
to resolve what functions are in a module and where they
are, as well as how to call them. All documented Win95
functions are called by name using this convention.
- There are functions to call other functions.
The "GetModuleHandle" and "GetProcAddess" class of
functions allow the exported entry point for any function
in any module to be located, allowing compatable calling.
Hence the method I present in this issue is comprised of the
following steps.
- Locate KERNEL32.DLL's PE header in memory (starting at the
flat address BFF70000h).
- Use the PE header info to locate KERNEL32's export section.
- Locate the export info for "GetModuleHandleA" and
"GetProcAddress".
- Extract the correct entry point for those two functions.
- Use "GetModuleHandleA" and "GetProcAddress" to call any and
all needed exported functions from any valid w95 module.
Strengths of this approach.
- Robust and reliable.
- Allows calling of any ring 3 function normally available to a
PE program.
Inherant weaknesses of this approach.
- If MS should (*shudder*) move KERNEL32.DLL then this approach
would meet the same fate as Bizatch.
However this is extremely unlikely for a number of reasons,
not the least of which is the fact they'd have to change
absolute addresses in other modules :)
You will agree that this approach is much more desirable than
previously used approaches. However it is not perfect, since it
does still rely on a constant value.
Expect to see a 100compatable approach in the next version of
Mr Klunky, hopefully in IR-9 (if I get all the bugs ironed out).
- Compatable Residency.
Although using an undocumented method one could probably go
resident under w95, taking such an approach would be begging for
compatability problems.
So, how do we go resident under Windows 95?
Well, you'll just have to look in the next section (Residency),
won't you.
- Monitoring File Activity Compatably.
As I said about going resident, you could probably achieve this
using an undocumented approach - but probably shouldn't.
Once again, read the "Residency" section for answers to this
question.
Residency
Residency under Windows 95... and (as we now see) compatable residency
at that! but how?
First - what is a TSR virus, really?
Most simply stated it is a type of driver, not (not always anyway) a
hardware driver. It's more of a file driver.
So we need to look at device drivers under Windows 95.
- The VxD.
VxD stands for "Virtual something Driver" - remember highschool
maths? "x" is the unknown quantity ;] A VxD has the LE executable
format.
Any w95 device or activity (including AV software, CD ROMs, etc)
needs a way of operating at privelage level 0 (ring 0). This way
it can have priority to do anything it needs to. In the case of
a virus this means intercepting file activity.
If we somehow can load our virus (compatably) as a ring 0 (VxD)
process we can do vitually anything.
As soon as we are a ring 0 process we cannot directly use ring 3
(normal program type) functions. We have to do everything through
VxD calls. These are in fact int 20h instructions followed by an
ordinal identifying which VxD call we are making.
Luckily there are VxD calls that let us do just about anything
that can be done in ring 3 - and then some.
When a VxD is it's control dispatch is the key to it's execution.
This dispatcher has control passed to it when any of several
system events occur. To signify success it RETurns with the carry
flag clear, or set on failure. It should default to success for
unhandler events.
Events we are concerned with follow.
- SYS_DYNAMIC_DEVICE_INIT
So that we can do any setting up we need to when the VxD
is loaded.
This event only occurs if the device can be dynamically
loaded (ie. loaded at any point other than system start).
A virus needs this handler so it can go resident
immediately after an infected program is run.
- SYS_DYNAMIC_DEVICE_EXIT
So that we can deinitialise all necessary items when we are
manually unloaded.
This event only occurs if the device can be dynamically
unloaded (ie. unloaded at any point other than system
shutdown).
A virus only needs this at the debugging level. One would
not normally want their virus to be trivially unloadable.
- INIT_COMPLETE
So that we can initialise anything we need when the system
finishes booting.
A virus that inserted itself into the w95 boot sequence
will need this handler so that it can be properly installed
in memory at the end of system boot.
- SYSTEM_EXIT
So that we can deinitialise all necessary items when the
system shuts down.
A virus needs this handler so that it can let '95 shut down
without generating errors (and thus signalling the user that
something is wrong).
Once we are loaded into ring 0 we need to monitor file activity.
- Monitoring File Access.
A VxD call named "IFSMgr_InstallFileSystemApiHook" lets us insert
our own file handler into the File System.
A virus VxD simply uses this function to trap all file processing
and filter out the desired items.
The File System (FS) hook needs to follow C calling conventions to
remain compatable with the rest of sub-systems. Certain parameters
are passed to the FS hook on the stack and these are utilised to
determine whether a call should be processed by the VxD.
There are several traps and tricks one needs to take care of, most
are learnt by experience. I have included the most important two.
- ALL file processing calls go through the file handler,
including those made by the VxD itself.
The VxD must take care not to allow processing of it's own
calls (there is no "chaining" in the DOS sense) or this
sequence of processing will be infinite and lead to a machine
crash.
- Some VxD functions restore the stack, most do not. Be sure to
always restore the stack manually, but only when a function
does not do so itself.
When deinitialising the VxD calls "IFSMgr_RemoveFileSystemApiHook"
to remove it's File System hook.
Ring-0/Ring-3 Incompatabilities
I previously mentioned the incompatabilities between ring 0 and ring 3
processes. They simply are not (in normal circumstances) able to use
each other's code. For compatability reasons this is COMPLETELY ruled
out.
However, here we find ourselves with a situation where we NEED code
running at ring 3 (that is, attached to the host PE module) and code
running at ring 0 (the file monitoring VxD). How do we resolve (once
again in a compatable way) these needs and difficulties?
My approach was to have the code that is patched to the infected program
in the VxD file, as data. When the VxD infects a file it writes the PE
patch code to the correct position and appends a copy of the VxD to the
PE patch code.
In it's turn, when the infected file is run control is passed to the
PE patch code. This patch code creates the VxD file on disk and loads
into memory as a driver.
This allows two distinctly different ring levels of code to exist in a
single module and yet to interact in a compatable fashion.
Boot Sequence
Infecting the Windows 95 startup sequence is almost laughable when a
compatable approach has been used. Observing a few step will guarantee,
in a tasefully compatable fashion, that our virus will be loaded
whenever w95 starts.
- Create the VxD in a system directory.
There are two default directories where system DLLs, VxD, EXEs,
etc may be located, in a default installation of Windows 95 these
are...
C:\WINDOWS
C:\WINDOWS\SYSTEM
The "GetSystemDirectory" and "GetWindowsDirectory" ring 3 functions
return these directories.
The ring 0 "Get_Exec_Path" returns the fully qualified path to the
file "VMM32.VXD", for example "C:\WINDOWS\SYSTEM\VMM32.VXD" (ie. in
the system directory).
Ring 0 VxD call "Get_Config_Directory" returns the directory the
configuration files (eg "SYSTEM.INI") are stored in, for example
"C:\WINDOWS\" (ie. the windows directory).
Between the four of these functions the Windows or System
directories can be derived, either from ring 0 or ring 3.
- Create an entry in the registry.
Any registry key entry located in the
"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\VxD"
area of the registry can be made to run a VxD automatically on
system start.
This key should have the following entries within it.
- Start
A binary entry with the value 00.
- StaticVxD
A string entry with the filename of the VxD.
For example "MrKlunky.VxD".
The module "ADVAPI32.DLL" contains ring 3 functions for creating,
opening, adding, editing, deleting and closing registry keys and
entries.
The "RegCreateKey" class of functions allows keys to be opened or
created.
The "RegSetValue" class of fucntions allows entries to be created
and set.
The "RegCloseKey" function closes a handle to a registry key.
Other Concerns
There are a few of matters that concerned me or may concern the reader.
- Size.
Size is not really a concern on Windows 95 machines for several
reasons.
There is a 4GB continuous addressing space, with a minimum
actual RAM requirement of 8MB.
Similaraly hard drive space on machines running '95 tend to be in
the GB number range.
Available HDD space is never constant on a machine running '95,
there is so much swapping inevitably going on.
The average user rarely, if ever, sees the size of programs (s)he
is running, much less notices changes in those sizes!
- Viability.
How viable is this approach? Well, hopefully I have already answered
that ;]
Remember that the example that comes with this is article is not
really likely to survive "in the wild" and is purely for study and
experimentation.
- Loading multiple copies of the VxD.
I did discover something unsettling! Multiple copies of a VxD can be
loaded into memory if one of several criteria is not met.
The easy way (which I used) was Device ID numbers.
Each VxD must have either a unique Device ID, or a Device ID set to
Undefined_Device_ID.
If the Device ID is Undefined_Device_ID, multiple copies of the VxD
could conceivably be loaded simultaneously.
So, I "steal" a device ID which (I believe) is unused.
I have developed a method of preventing multiple-loads while using
Undefined_Device_ID, more on that next time.
Conclusion
This issue also includes an example of a virus employing the techniques
discussed here.
Shudder in awe at the might of Mr Klunky!
You should find the source and a debug script for MrKlunky.VxD (compiled
directly from the source) and for a util (Load-MrK.COM) in this issue.
The next version of Mr Klunky will incorporate the following...
- Absolutely NO absolute addresses.
- Uses Undefined_Device_ID without allowing multiple loads of the VxD.
- All new PE infection method.
- More things to make it viable "in the wild".
- Maybe a (non-destructive) activation routine.
Anyone who wants to yap with me about w95 techniques, approaches or
anything virus related (EXCEPT morality) contact me through any IR member.
Goodbye! Enjoy!
And remember - A Smurf a day keeps the doctor away!
-DV8/IRG