Driver to Hide Processes and Files
List of Contents
- Introduction
- Is this article dangerous?
- Legal reason to hide processes and files
- Projects used
- Project implementation
- Project structure
- NT function call scheme and hook
- Function index in SST
- Change the results returned by the original function
- Process hiding
- File hiding
- GUI application
- Communication with HideDriver
- Format of communication
- HideDriver IOCTLs
- How to build this solution
- Additional information about the build
- Supported Windows version and testing
- Bibliography
- Useful Links
- Documentation and additional info
- History
Introduction
We are glad to introduce our project - "The Hide Driver project".
The main idea of this work is to create a driver for hiding of selected processes and files.
The task: The processes selected by the user should be invisible for such applications as the Task Manager, Process Explorer, and others. In addition, they should not be available for such Windows API functions as EnumProcesses()
,OpenProcess()
, EnumProcessModules()
, and other Process APIs. The files selected by the user should be invisible for such file managers as Windows Explorer, Far, Total Commander, etc. In addition, they should not be available for such Windows API functions as FindFile()
, OpenFile()
and other File API functions.
This article is written as the result of one education project implemented during ApriorIT students courses.
During the project implementation we add some features that were not mentioned in the initial task:
- Correct work on the multiple-processor systems
- Support of the wildcards in the names of files, processes, users
- Filtering of the access by the name of the process that requested for access
- Filtering of the access by the user name
A lot of time was spent to create the Universal Subsystem of Interceptions where the implementation of the given interception is a kind of plug-in. Due to the limited terms we implemented only two such plug-ins with expanded functionality.
More interesting features can be added using SST hooking but they were not implemented in this project frame:
- Hiding of registry keys
- Hiding of the list of services and drivers
- Hiding of opened handles (file, process, etc.)
- Storing of the parameters in the registry or *.xml file
Is This Article Dangerous?
It's up to you! One may think that the described technique is good for virus creation. But it surely was not the aim of our article. Here are some thoughts we follow:
- To install the driver you must have Administrator right.
- This is a popular technique of files and process hiding. So all antiviruses and security products know how to bypass it.
- When you try to install driver antivirus can block this or ask you to make a choice.
- All popular antiviruses use some techniques to see the hidden files. Techniques are different - for example it can be direct call to filesystem driver or using of the system internal structure.
- All popular antiviruses use some techniques to see hidden processes. For example hook of
KiSwapContext
function.KiSwapContext
is called when the time quantum of the thread ends. Time quantum is in milliseconds andKiSwapContext
is called very frequently so you can't hide anything from it. - Antiviruses filter all calls to file system and if you try to load a virus, they will see it and it doesn't matter if this file is hidden or not. Usually antivirus uses filter drivers located under the filesystem. In this case, when you try to read something from the disk antivirus checks content using image signatures and if there is a virus block it calls and shows the warning.
- If you just try to search by following keywords: antirootkit, detecting hidden process, etc. you will find hundreds of pages with hundreds of various programs.
Legal Reason to Hide Processes and Files
The main and obvious application of such technology is enterprise security and management systems. You cannot rely fully on the permissions as some users (like most of you here - developers) should have more than usual access permissions on their PC. Many security systems take the approach of using such kind of techniques to make sure that only authorized person(s) can turn this system off. It helps to prevent IP theft and data leakage in many companies.
Another application of the described technique is software licensing and copy protection frameworks.
Project Implementation
The task described in the Introduction was resolved by using one of the general ways - Hooking SSDT. A lot of information about this technology can be found in the articles mentioned in the Useful Links section.
Projects Used
I would like to thank the people who developed the following projects - they made the implementation of this project easier:
Project Structure
Directory Structure
.\bin - folder with binary files
.\lib - folder with library files
.\obj - folder with object files
.\src - folder with source files
|
|-> .\Common - Files that are shared between projects.
|-> .\STLPort - Directory with STLPort 4.6 ported for
using in windows drivers.
|-> .\drvCppLib - Kernel Library to develop driver in C++.
|-> .\drvCppLibTest - Kernel Driver to test drvCppLib.
|-> .\drvUtils - Kernel Library with utils for kernel mode projects.
|-> .\HideDriver - Kernel Driver installed by Gui App. Performs main work.
|-> .\HideDriverGui - Win32 Application used to run driver and communicate with it.
|-> .\Utils - Win32 Library with utils for user mode projects.
|-> .\UtilsPortable - Directory with headers for user mode and kernel mode projects.
|-> .\UtilsPortableUnitTest - Win32 Application with unit test for UtilsPortable.
Project Structure
NT Function Call Scheme and Hook
The scheme below describes the normal call cycle of functions FindFirstFile()
, FindNextFile()
used to enumerate files and folders.
The next scheme describes the situation when HideDriver
is installed.
So let's make a conclusion after all of this.
When hook is installed, you can:
- Call original function or not
- Change the results returned by the original function
- Even if original function returned successful status, you can return an error
Function Index in SST
Let's look at the structures described in NT function call scheme.
[Code from the file src\HideDriver\ServiceTableDef.h]
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable;
PDWORD CounterTable;
ULONG ServiceLimit;
PBYTE ArgumentTable;
}
SYSTEM_SERVICE_TABLE ,
* PSYSTEM_SERVICE_TABLE ,
* * PPSYSTEM_SERVICE_TABLE ;
typedef struct _SERVICE_DESCRIPTOR_TABLE {
SYSTEM_SERVICE_TABLE ntoskrnl; //SST for ntoskrnl.exe
SYSTEM_SERVICE_TABLE win32k; //SST for win32k.sys
SYSTEM_SERVICE_TABLE unused1;
SYSTEM_SERVICE_TABLE unused2;
}
SERVICE_DESCRIPTOR_TABLE ,
* PSERVICE_DESCRIPTOR_TABLE,
* * PPSERVICE_DESCRIPTOR_TABLE ;
Where PNTPROC
is defined as:
typedef PVOID* PNTPROC;
So ServiceTable
is a simple array of pointers to NtXXX functions.
Question is: how to find out what is the index of function NtQueryDirectoryFile
in the ServiceTable
array?
First Approach (wrong)
Driver stores many tables with all indexes of functions for all versions of Windows.
If we use these tables, we have to:
- store all indexes for all versions of Windows and service packs for each function
- update index tables for each new OS version or service pack
For some similar tasks such as offset in EPROCESS
structure, there is no other way of finding the index in the structure.
But for SST, another way exists.
Second Approach (right)
To retrieve the index for all versions of Windows, we need to find some place in the system where it is always used.
Lets disable function ZwQueryDirectoryFile
in WinDBG
by executing u command:
As you can see, the underlined number is the index of ZwQueryDirectoryFile
function in the ServiceTable
array.
Disabling other ZwXXX functions, you can see that all of these functions look the same and start with: mov eax
,SST_Index
.
The code below retrieves this index:
[Code from file src\HideDriver\HookFactory.cpp]
SSTHook CreateSSTHook(IN const PVOID pNewFuncPtr,IN PUNICODE_STRING function_name)
{
...
PVOID pTrueFuncPtr_ZW=MmGetSystemRoutineAddress(function_name);
if(pTrueFuncPtr_ZW == NULL)
throw std::exception(__FUNCTION__"Can't get function address");
// Skip command byte, move to index byte
ULONG mFuncID = *(PULONG)((PUCHAR) pTrueFuncPtr_ZW + 1);
...
}
Change the Results Returned by the Original Function
After hook is installed and we receive NtQueryDirectorFile
calls, we need to cut information about a file from theList
to hide it.
Let's look at NtQueryDirectoryFile
and NtQuerySystemInformation
function definitions:
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
NTSTATUS NtQueryDirectoryFile(
HANDLE FileHandle,
HANDLE Event OPTIONAL,
PIO_APC_ROUTINE ApcRoutine OPTIONAL,
PVOID ApcContext OPTIONAL,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
BOOLEAN ReturnSingleEntry,
PUNICODE_STRING FileName OPTIONAL,
BOOLEAN RestartScan
);
Here is one of the possible results returned by this function:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
WCHAR FileName[1];
} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;
// SystemProcessesAndThreadsInformation
typedef struct _SYSTEM_PROCESSES_INFORMATION {
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG SessionId;
ULONG Reserved2;
VM_COUNTERS VmCounters;
#if (VER_PRODUCTBUILD >= 2195)
IO_COUNTERS IoCounters;
#endif // (VER_PRODUCTBUILD >= 2195)
SYSTEM_THREADS_INFORMATION Threads[1];
} SYSTEM_PROCESSES_INFORMATION, *PSYSTEM_PROCESSES_INFORMATION;
These two functions returned information that look very similar.
That's why we created several utility functions which operate with these two structures. This utility is showed in the code below:
[Code from file src\UtilsPortable\ListUtils.h]
#pragma once
#include "boost/function.hpp"
#include "boost/bind.hpp"
namespace utils
{
struct NtList
{
size_t NextEntryOffset;
};
#define LastEntryOffset 0
inline bool IsEntryLast(NtList* pList)
{
return (pList->NextEntryOffset == LastEntryOffset);
}
////// FOR EACH SECTION ///
inline NtList*
GetNextEntryPointer(NtList* list)
{
if(list->NextEntryOffset == 0)
throw std::exception("No more entries inside list");
return (NtList*)((char*)list + list->NextEntryOffset);
}
template<class Visitor>
inline void
ListForEach( NtList* list, Visitor visitor )
{
while( true )
{
visitor(list);
if( IsEntryLast(list) )
break;
list = GetNextEntryPointer(list);
}
}
///// ENTRY COUNT SECTION /
inline void
CountEntryVisitor(NtList* list,size_t* entryCount)
{
*entryCount += 1;
}
inline size_t
GetEntryCount(NtList* list)
{
size_t count = 0;
ListForEach(list,boost::bind(&CountEntryVisitor,_1,&count));
return count;
}
////// LIST SIZE SECTION /
inline void
SizeEntryVisitor(NtList* list,size_t* size)
{
size += list->NextEntryOffset;
}
inline size_t
GetListSize(NtList* list)
{
size_t size = 0;
ListForEach(list,boost::bind(&SizeEntryVisitor,_1,&size));
return size;
}
///// CUT SECTION
inline void
CutNextEntryByFakeOffset( NtList* list )
{
NtList* pNextEntry = GetNextEntryPointer(list);
if( IsEntryLast(pNextEntry) )
list->NextEntryOffset = LastEntryOffset;
else
list->NextEntryOffset = list->NextEntryOffset + pNextEntry->NextEntryOffset;
}
template<class Checker>
inline void
CutFromListByFakeOffset_IfImpl( NtList* list,
Checker checker )
{
if( IsEntryLast(list) )
return; // Last entry already dispatched
while(true)
{
NtList* pNextEntry = GetNextEntryPointer(list);
if( !checker(pNextEntry) )
break;
CutNextEntryByFakeOffset(list);
if( IsEntryLast(list) )
break;
}
}
template<class Checker>
inline void
CutFromListByFakeOffset_If( NtList* list
, Checker checker )
{
ListForEach( list, boost::bind(&CutFromListByFakeOffset_IfImpl<Checker>,_1,
boost::ref(checker) ) );
}
}//namespace utils
Process Hiding
To hide processes, we need to cut the information about them from the list returned byNtQuerySystemInformation()
.
It's very easy using utils
described above.
[Code from file src\UtilsPortable\IProcessChecker.h]
struct NtQuerySysInfoParams
{
SYSTEM_INFORMATION_CLASS SystemInformationClass;
PVOID SystemInformation;
ULONG SystemInformationLength;
PULONG ReturnLength;
};
struct IProcessChecker
{
virtual bool
CheckProcess( wchar_t* imageName
, size_t nameSize )=0;
};
[Code from file src\UtilsPortable\ProcessHideAlgorithm.h]
inline bool
CheckProcEntry( utils::NtList* list,
const NtQuerySysInfoParams& params,
IProcessChecker* checker )
{
SYSTEM_PROCESSES_INFORMATION* info =
(SYSTEM_PROCESSES_INFORMATION*)list;
return checker->CheckProcess(info->ProcessName.Buffer,
info->ProcessName.Length/2);
}
inline NTSTATUS
HideProcessImpl( const NtQuerySysInfoParams& params,
IProcessChecker* checker )
{
utils::NtList* pList =
(utils::NtList*)params.SystemInformation;
// First entry always exist
// because first entry is idle process
utils::CutFromListByFakeOffset_If(pList,
boost::bind(&CheckProcEntry,_1,params,checker));
return STATUS_SUCCESS;
}
File Hiding
To hide a file, we need to cut the information about it from the list returned by NtQueryDirectoryFile()
.
Hiding algorithm starts from NewNtQueryDirectoryFile()
function which receives calls every time someone callsNtQueryDirectoryFile()
function in the system.
NewNtQueryDirectoryFile()
calls original function and processes returned data by passing it to the HideFile()
function from UtilsPortable\HideAlgorithm.h.
[Code from file src\HookFile.cpp]
NTSTATUS NewNtQueryDirectoryFile(IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan)
{
using namespace HideAlgorithm;
NtQueryDirParams params = {FileHandle,Event,ApcRoutine,ApcContext,
IoStatusBlock,FileInformation,FileInformationLength,
FileInformationClass,ReturnSingleEntry,FileName,RestartScan};
NTSTATUS status = OriginalHandlerWrapper(params);
if( !NT_SUCCESS(status) )
return status;
params.RestartScan = FALSE; // Search must be continued
// Save original handler to request more data in future
HideParams hideParams = {params,gFileChecker,&OriginalHandlerWrapper};
switch(FileInformationClass)
{
case FileDirectoryInformation:
return HideFile<FILE_DIRECTORY_INFORMATION>(hideParams);
case FileFullDirectoryInformation:
return HideFile<FILE_FULL_DIRECTORY_INFORMATION>(hideParams);
case FileBothDirectoryInformation:
return HideFile<FILE_BOTH_DIRECTORY_INFORMATION>(hideParams);
case FileNamesInformation:
return HideFile<FILE_NAMES_INFORMATION>(hideParams);
case FileIdBothDirectoryInformation: // Used by Vista and later explorer
return HideFile<FILE_ID_BOTH_DIR_INFORMATION>(hideParams);
case FileIdFullDirectoryInformation: // Used by Vista and later explorer
return HideFile<FILE_ID_FULL_DIR_INFORMATION>(hideParams);
default:return status;
}
}
When we know what type of information is requested, we can cut the process from the list using ListUtils
fromUtilsPortable
.
The first step is to establish correspondence between callers and algorithm.
It is done by specifying virtual function and parameters in the code below.
[Code from file src\UtilsPortable\IFileChecker.h]
struct NtQueryDirParams
{
HANDLE FileHandle;
HANDLE Event;
PIO_APC_ROUTINE ApcRoutine;
PVOID ApcContext;
PIO_STATUS_BLOCK IoStatusBlock;
PVOID FileInformation;
ULONG FileInformationLength;
FILE_INFORMATION_CLASS FileInformationClass;
BOOLEAN ReturnSingleEntry;
PUNICODE_STRING FileName;
BOOLEAN RestartScan;
};
struct IFileChecker
{
virtual bool
CheckFile( wchar_t* fileName
, size_t nameSize
, const NtQueryDirParams& params )=0;
};
Original handler needs to request more data if request is finished with the flag ReturnSingleEntry == TRUE
.
[Code from file src\UtilsPortable\FileHideAlgorithm.h]
typedef NTSTATUS(*OriginalHandlerWrapperPtr)(const NtQueryDirParams& params);
struct HideParams
{
const NtQueryDirParams& callParams;
IFileChecker* checker;
OriginalHandlerWrapperPtr wrapperPtr;
};
template<class InfoType>
inline bool
CheckFileEntry( utils::NtList* list,
const HideParams& params )
{
InfoType* info = (InfoType*)list;
return params.checker->CheckFile(info->FileName,
info->FileNameLength/2,
params.callParams);
}
The next step is to protect top level functions from exceptions that can arise during the hiding of processes.
Unhandled exception will cause the system crash.
template<class InfoType>
inline NTSTATUS
HideFile( const HideParams& params )
{
try
{
return HideFileImpl<InfoType>(params);
}
catch(const std::exception& ex)
{
#ifdef KdPrint // For use in user mode environment
KdPrint( (__FUNCTION__" std::exception: %s\n",ex.what()) );
#endif
}
return STATUS_SUCCESS;
}
After this, we need to process the first entry separately from the next entries.
We should do it because we need to shift the buffer to hide the first entry but to hide the next entries, we need just to set fake offset.
template<class InfoType>
inline NTSTATUS
HideFileImpl( const HideParams& params )
{
NTSTATUS status = FirstEntryProcessor<InfoType>(params);
if( !NT_SUCCESS(status) )
return status;
status = NextEntryProcessor<InfoType>(params);
if( !NT_SUCCESS(status) )
return status;
return STATUS_SUCCESS;
}
In the code below, you can see the algorithm of hiding of the first entry from the list returned byNtQueryDirectoryFile()
.
template<class InfoType>
inline NTSTATUS
FirstEntryProcessor( const HideParams& params )
{
utils::NtList* pList =
(utils::NtList*)params.callParams.FileInformation;
utils::NtList* pCurEntry = pList;
while(true)
{
if(!CheckFileEntry<InfoType>(pCurEntry,params))
{
if(pList == pCurEntry)
break; // Nothing to hide
ShiftBuffer(pList,pCurEntry);
break; // First entry hiding complete
}
// This entry needs to be hidden
if( utils::IsEntryLast(pCurEntry) == false )
{
// Move to next entry to check.
// This is needed to shift buffer only once.
pCurEntry = utils::GetNextEntryPointer(pCurEntry);
}
else
{
// Reached last entry
// This mean that all data needs to be hidden
// Try to request more data
NTSTATUS status = params.wrapperPtr(params.callParams);
if( !NT_SUCCESS(status) )
return status;
// Move to begin and resume checking
pCurEntry = pList;
}
}
return STATUS_SUCCESS;
}
Algorithm of hiding the next entries is the same as the algorithm of process hiding.
template<class InfoType>
inline NTSTATUS
NextEntryProcessor( const HideParams& params )
{
utils::NtList* pList =
(utils::NtList*)params.callParams.FileInformation;
if( utils::IsEntryLast(pList) )
return STATUS_SUCCESS;
utils::CutFromListByFakeOffset_If(pList,
boost::bind(&CheckFileEntry<InfoType>,_1,params));
return STATUS_SUCCESS;
}
GUI Application
The GUI application enables a user to choose processes or/and files for hiding in an easy way. You can find an example of such application in the attached file. It was developed using MFC. It should be also mentioned that you can create your own GUI application following the rules in the Format of communication section.
Communication with HideDriver
IOCTLs and the DeviceIoCotrol()
routine should be used for communication between the user-mode application and driver.
You can find additional information about such communication implementations in the article: "Driver Development Part 2: Introduction to Implementing IOCTLs".
Wrapper class DriverWork
was developed for communication with drivers. This class wraps DeviceIoControl()
function and other service functions.
Here is an example of using of DriverWork
class to send IOCTL to HideDriver
:
[Code from file src\HideDriverGUI\FileForm.cpp]
void FileForm::OnMenuAdd()
{
...
try
{
utils::DriverWork::Exchange(
_T("\\\\.\\HideDriver"),
IOCTL_ADD_FILE_HIDE_RULE,
str, // Input string
size, // Size of input string
(PWCHAR)ret_data, // Output string
sizeof(ret_data), // Size of buffer for output string
&BytesReturned);
}
catch(const std::exception& ex)
{
::MessageBoxA(this->GetSafeHwnd(),ex.what(),"Error",MB_ICONERROR|MB_OK);
return;
}
...
}
Below you can see the code of DriverWork::Exchange()
function:
[Code from file src\Utils\DriverWork.cpp]
void DriverWork::Exchange(LPCTSTR driverName,
unsigned long ioctlCode,
PWCHAR pInStr,
DWORD inStr_size,
PWCHAR pOutStr,
DWORD outStr_size,
PDWORD bytesReturned)
{
HANDLE hHandle =
CreateFile( driverName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );
if(hHandle == INVALID_HANDLE_VALUE)
throw std::runtime_error("Can't get handle to driver: " + GetLastErrorStr());
HandleGuard guard1(hHandle);
if( !DeviceIoControl(hHandle,
ioctlCode,
pInStr, inStr_size, // Input
pOutStr, outStr_size,// Output
bytesReturned,
NULL) )
{
throw std::runtime_error("Driver communication error: " + GetLastErrorStr());
}
}
Format of Communication
All input strings (parameters) are UNICODE strings. If an error occurs, driver returns ASCII string with the error description.
All HideDriver
IOCTLs (except CLEAR IOCTLs) work with HIDE_RULE
. HIDE_RULE
supports the following options:
- Hide from the list of processes, it means that only selected processes should not see this process or file
- Hide from the list of users, it means that only selected users should not see this process or file
These options can be used together.
Format of HIDE_RULE string
must be:process(file)_name_to_hide;access_user_name;access_process_name
.
Where:
process(file)_name_to_hide
- process name (file path) to hideaccess_user_name
- name of user, who should not see this process(file)access_process_name
- name of process, which should not see this process(file)
To insert several processes or user names, separate them by ',' character.
Example:
process_name_to_hide;user_name1,user_name2;process_name1,process_name2
All names support wildcards:
"*" - Matches all characters
"?" - Matches any single character
Some Examples
*;*;* - Hide all processes from all users and processes. Just for fun.
System;*;* - Hide process system from all users and processes.
er*;*;* - Hide all processes whose name starts with "er" characters from everybody.
System;*;Rob - Hide process "System
" from User: Rob
HideDriver IOCTLs
All IOCTLs that can processHideDriver
are located in file
Ioctl.h.
[Code from file src\Common\Ioctl.h]
/*-----------------------------------------------------------------------*/
/* Process hide IOCTLs */
#define IOCTL_ADD_PROCESS_HIDE_RULE CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to add process hide rule to hide list.
Input string must be HIDE_RULE.
*/
#define IOCTL_DEL_PROCESS_HIDE_RULE CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to delete process hide rule from hide list.
Input string must be HIDE_RULE.
*/
#define IOCTL_CLEAR_PROCESS_HIDE_RULES CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to clear process hide list.
Input string should be empty.
*/
#define IOCTL_QUERY_PROCESS_HIDE_RULES CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to get process hide rule list.
Input string should be empty.
Format of output string:
HIDE_RULEs separated by '\n' character
and ends with '\0' character.
Example:
*;*;*\nSystem;*;*\0 - two hide rules (*;*;*) and (System;*;*)
*/
/*-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/
/* File hide IOCTLs */
#define IOCTL_ADD_FILE_HIDE_RULE CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to add file hide rule to hide list.
Input string must be HIDE_RULE.
*/
#define IOCTL_DEL_FILE_HIDE_RULE CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to delete file hide rule from hide list.
Input string must be HIDE_RULE.
*/
#define IOCTL_CLEAR_FILE_HIDE_RULES CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x903, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to clear file hide list.
Input string should be empty.
*/
#define IOCTL_QUERY_FILE_HIDE_RULES CTL_CODE( \
FILE_DEVICE_UNKNOWN, 0x904, METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
This IOCTL used to get file hide rule list.
Input string should be empty.
Format of output string same as in IOCTL_QUERY_PROCESS_HIDE_RULES.
*/
/*-----------------------------------------------------------------------*/
How to Build this Solution
- Install Windows Driver Developer Kit 2003
http://www.microsoft.com/whdc/devtools/ddk/default.mspx - Set global environment variable "
BASEDIR
" to path of installed DDK.
Computer -> Properties -> Advanced -> Environment variables ->System Variables -> New
Like this:BASEDIR
-> c:\winddk\3790
(You have to restart your computer after this.) - Download and install boost (tested with 1.38 version)
http://www.boost.org/users/download/ - Set global environment variable "
BOOST
" to path of installed boost.
If you choose Visual Studio 2003, then you can simply open HideDriver_vs7.sln and build all.
If you choose Visual Studio 2005 or 2008, you will need files from Visual Studio 2003 to do it. Copy files located here:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src\intel\st_lib\ (But surely this path depends on the Visual Studio install path) to the project folder: HideDriver_source\src\drvCppLib\lib_copy\.
After this, you can use the file "HideDriver_vs8.sln" in Visual Studio 2005 and file "HideDriver_vs9.sln" in Visual Studio 2008.
Additional Information About the Build
You will also need the file from $VCInstllPath\crt\src\intel\st_lib\ - Single Threaded C++ Run-Time is located in this directory. Any other version of run-time library can't be used for the driver building.
This method was originally discovered by Gary Nebbett in the article: "Enabling C++ exceptions and RTTI in kernel-mode code". It's like nobody has invented something better since that time.
Supported Windows Version and Testing
All tests were performed with Driver Verifier Enabled with all options ON except low resource simulation.
- Windows 2000, SP4
- Windows XP, SP3
- Windows 2003 Server, R2
- Windows Vista, SP0,SP1
- Windows 2008 Server
- Windows 7 Beta, Build 7000
All versions are x86; x64 windows version is not supported because of PatchGuard.
Bibliography
- Mark Russinovich, David Solomon. Microsoft Windows Internals ((Fourth Edition) ed.)
- Greg Hoglund, Jamie Butler. Rootkits: Subverting the Windows Kernel
- Gary Nebbett. Windows NT/2000 Native API Reference
- Sven B. Schreiber. Undocumented Windows 2000 Secrets - A Programmer's Cookbook
Useful Links
We recommend the article "Driver Development" by Toby Opferman:
It would also be useful to look through other articles (Part 2, Part 3, Part 4, Part 5, Part 6,Part 7).
Also, a lot of knowledge that helped us to develop this project was obtained here:
- Hooking Windows NT System Services
- http://www.osronline.com/
- http://www.rootkit.com/
- http://msdn.microsoft.com/
- http://www.boost.org/
For Russian speaking readers, we recommend these sources:
- http://wasm.ru/
- http://wasm.ru/article.php?article=apihook_3
- http://wasm.ru/article.php?article=hidingnt
Additional Info
Additional information can be found at the Apriorit Education page.
History
21/01/2009 (by Ivan Romanenko & Sergey Popenko)
- Initial version of this article
12/02/2009 (by Ivan Romanenko)
- Added possibility to build solution in VS2003, VS2005, VS2008
- Added the topic "How to build this solution"
29/04/2009 (by Ivan Romanenko)
- Project
- Bugfix
- Wrong hiding of the first file in the directory
- Wrong synchronization that locks PC
- Allocation memory on height IRQL
- Bug with build using DDK2003
- Bug in filtering by user
- Removed using
SingleMode
and changedHookMng
- New
- Using Wildmat instead of own wild cards implementation
- Unit tests
- Using BOOST and STLPort
- Changes in project structure, utils and tests are added
- Bugfix
- Article
- Bugfix
- Wrong language in Ioctl.h
- Fixed spelling error at code comments
- New
- 11 new topics added
- Bugfix
13/08/2009 (by Ivan Romanenko)
- Project
- Bugfix
- Fixed: synchronization bug in
CReadWriteSection
class - Fixed: wrong name converting for network shares
- Added: heuristics for offset receiving. Absolute offsets are not used any more, except for case when
DriverVerifier
substitutesntoskrnl
code under Vista.
- Fixed: synchronization bug in
- Bugfix