65.9K
CodeProject 正在变化。 阅读更多。
Home

隐藏进程和文件的驱动程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (144投票s)

2009年1月21日

CPOL

12分钟阅读

viewsIcon

721981

downloadIcon

28688

在本文中,我们描述了我们创建的用于隐藏系统中进程和文件的驱动程序。

目录

  1. 引言
  2. 本文是否危险?
  3. 隐藏进程和文件的法律依据
  4. 使用的项目
  5. 项目实现
  6. 项目结构
  7. NT 函数调用方案和挂钩
  8. SST 中的函数索引
  9. 更改原始函数返回的结果
  10. 进程隐藏
  11. 文件隐藏
  12. GUI 应用程序
  13. 与 HideDriver 的通信
  14. 通信格式
  15. HideDriver IOCTL
  16. 如何构建此解决方案
  17. 关于构建的附加信息
  18. 支持的 Windows 版本和测试
  19. 参考文献
  20. 有用链接
  21. 文档和附加信息
  22. 历史

引言

我们很高兴向大家介绍我们的项目——“隐藏驱动程序项目”。

这项工作的主要思想是创建一个用于隐藏选定进程和文件的驱动程序

任务:用户选择的进程对于任务管理器、进程查看器等应用程序应该是不可见的。此外,它们对于诸如 EnumProcesses()OpenProcess()EnumProcessModules() 和其他进程 API 等 Windows API 函数应该是不可用的。用户选择的文件对于 Windows Explorer、Far、Total Commander 等文件管理器应该是不可见的。此外,它们对于诸如 FindFile()OpenFile() 和其他文件 API 函数等 Windows API 函数也应该是不可用的。

本文是我们在 ApriorIT 学生课程期间实施的一项教育项目的成果。

在项目实施过程中,我们添加了一些最初任务中未提及的功能。

  • 在多处理器系统上正确工作
  • 支持文件名、进程名、用户名中的通配符
  • 按请求访问的进程名过滤访问
  • 按用户名过滤访问

花费了大量时间来创建**通用拦截子系统**,其中给定拦截的实现是一种插件。由于时间有限,我们只实现了两个具有扩展功能的插件。

使用 SST 挂钩可以添加更多有趣的功能,但在此项目框架中并未实现。

  • 隐藏注册表项
  • 隐藏服务和驱动程序列表
  • 隐藏打开的句柄(文件、进程等)
  • 将参数存储在注册表或 *.xml 文件中

这篇文章危险吗?

这取决于你!有些人可能会认为这里描述的技术适合用于病毒创建。但这肯定不是我们文章的目的。以下是我们遵循的一些想法:

  1. 要安装驱动程序,您必须拥有管理员权限。
  2. 这是文件和进程隐藏的一种常用技术。因此,所有杀毒软件和安全产品都知道如何绕过它。
  3. 当您尝试安装驱动程序时,杀毒软件可能会阻止此操作或要求您做出选择。
  4. 所有流行的杀毒软件都使用某些技术来查看隐藏的文件。技术各不相同——例如,它可以是直接调用文件系统驱动程序,或使用系统内部结构。
  5. 所有流行的杀毒软件都使用一些技术来查看隐藏的进程。例如,挂钩 KiSwapContext 函数。KiSwapContext 在线程的时间片用完时调用。时间片以毫秒为单位,KiSwapContext 调用非常频繁,因此您无法隐藏任何东西。
  6. 杀毒软件会过滤所有对文件系统的调用,如果您尝试加载病毒,它们会看到它,而不管该文件是否隐藏。通常,杀毒软件使用位于文件系统下方的过滤器驱动程序。在这种情况下,当您尝试从磁盘读取内容时,杀毒软件会使用图像签名检查内容,如果存在病毒,则会阻止并显示警告。
  7. 如果您只需搜索关键词,如:antirootkit、detecting hidden process 等,您会找到数百页的各种程序。

隐藏进程和文件的法律依据

这项技术的主要且显而易见的应用程序是企业安全和管理系统。您不能完全依赖权限,因为某些用户(如在座的大多数开发者)应该对他们的 PC 拥有比普通用户更多的访问权限。许多安全系统采用此类技术来确保只有授权人员才能关闭此系统。这有助于防止许多公司的数据被盗和数据泄露。

所述技术的另一个应用是软件许可和防盗版框架。

项目实现

引言中描述的任务通过一种通用方法——挂钩 SSDT 来解决。有关此技术的更多信息可以在有用链接部分提到的文章中找到。

使用的项目

我想感谢开发以下项目的各位——他们使这个项目的实现变得更加容易。

项目结构

目录结构

   .\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.

项目结构

NT 函数调用方案和挂钩

下面的图表描述了用于枚举文件和文件夹的函数 FindFirstFile()FindNextFile() 的正常调用周期。

接下来的图表描述了安装 HideDriver 时的情况。

那么,让我们根据这一切得出结论。

安装挂钩后,您可以

  • 调用原始函数或不调用
  • 更改原始函数返回的结果
  • 即使原始函数返回成功状态,您也可以返回错误

SST 中的函数索引

让我们看看 NT 函数调用方案中描述的结构。

[来自文件 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 ;

其中 PNTPROC 定义为

typedef PVOID* PNTPROC;

因此,ServiceTable 是一个指向 NtXXX 函数的指针的简单数组。

问题是:**如何找出函数 NtQueryDirectoryFile ServiceTable 数组中的索引?**

第一种方法(错误)

驱动程序存储许多表格,其中包含所有 Windows 版本的函数索引。

如果我们使用这些表格,我们就必须

  • 为每个函数存储所有 Windows 版本和 service pack 的所有索引
  • 为每个新的操作系统版本或 service pack 更新索引表

对于一些类似的任务,例如 EPROCESS 结构中的偏移量,没有其他方法可以找到结构中的索引。

但是对于 SST,存在另一种方法。

第二种方法(正确)

要获取所有 Windows 版本的索引,我们需要在系统中找到一个它始终被使用的地方。

WinDBG 中,通过执行 u 命令禁用函数 ZwQueryDirectoryFile

如您所见,带下划线的数字是 ZwQueryDirectoryFile 函数在 ServiceTable 数组中的索引。

禁用其他 ZwXXX 函数,您会发现所有这些函数看起来都一样,并且以:mov eax, SST_Index. 开头。

下面的代码检索此索引。

[来自文件 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);

    ...
}

更改原始函数返回的结果

安装挂钩后,当我们收到 NtQueryDirectorFile 调用时,我们需要从 List 中删除有关文件的信息以隐藏它。

让我们看一下 NtQueryDirectoryFile NtQuerySystemInformation 函数的定义。

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
    );

这是此函数返回的一种可能结果。

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;

这两个函数返回的信息看起来非常相似。

因此,我们创建了几个操作这些结构的实用函数。此实用程序显示在下面的代码中。

[来自文件 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

进程隐藏

要隐藏进程,我们需要从 NtQuerySystemInformation() 返回的列表中删除有关它们的信息。

使用上面描述的 utils 非常容易。

[来自文件 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;
};

[来自文件 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;
}

文件隐藏

要隐藏文件,我们需要从 NtQueryDirectoryFile() 返回的列表中删除有关它的信息。

隐藏算法从 NewNtQueryDirectoryFile() 函数开始,该函数每次有人在系统中调用 NtQueryDirectoryFile() 函数时都会接收调用。

NewNtQueryDirectoryFile() 调用原始函数并通过将返回的数据传递给 UtilsPortable\HideAlgorithm.h 中的 HideFile() 函数来处理它。

[来自文件 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;
    }
}

当我们知道请求的是哪种类型的信息时,我们可以使用 UtilsPortable 中的 ListUtils 从列表中删除进程。

第一步是建立调用者和算法之间的对应关系。

这是通过在下面的代码中指定虚拟函数和参数来完成的。

[来自文件 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;
};

如果请求以 ReturnSingleEntry == TRUE 标志完成,则原始处理程序需要请求更多数据。

[来自文件 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);
}

下一步是保护顶级函数免受隐藏进程期间可能发生的异常的影响。

未处理的异常将导致系统崩溃。

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;
}

在此之后,我们需要单独处理第一个条目和后续条目。

我们应该这样做,因为我们需要移动缓冲区来隐藏第一个条目,但要隐藏后续条目,我们只需设置一个假的偏移量。

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;
}

在下面的代码中,您可以看到隐藏 NtQueryDirectoryFile() 返回列表中的第一个条目的算法。

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;
}

隐藏后续条目的算法与隐藏进程的算法相同。

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 应用程序

GUI 应用程序使用户能够以简单的方式选择要隐藏的进程或/和文件。您可以在附件文件中找到此类应用程序的示例。它是使用 MFC 开发的。还应该提到的是,您可以按照通信格式部分中的规则创建自己的 GUI 应用程序。

与 HideDriver 的通信

IOCTLs 和 DeviceIoCotrol() 例程应用于用户模式应用程序和驱动程序之间的通信。

您可以在文章“驱动程序开发第二部分:IOCTL 实现简介”中找到有关此类通信实现的更多信息。

开发了包装器类 DriverWork 用于与驱动程序通信。此类包装了 DeviceIoControl() 函数和其他服务函数。

以下是使用 DriverWork 类向 HideDriver 发送 IOCTL 的示例。

[来自文件 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;
    }
...
}

下面是 DriverWork::Exchange() 函数的代码。

[来自文件 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());
    }
}

通信格式

所有输入字符串(参数)都是 UNICODE 字符串。如果发生错误,驱动程序将返回一个带有错误描述的 ASCII 字符串。

所有 HideDriver IOCTL(除了 CLEAR IOCTLs)都使用 HIDE_RULEHIDE_RULE 支持以下选项:

  • 从进程列表中隐藏,这意味着只有选定的进程不应看到此进程或文件。
  • 从用户列表中隐藏,这意味着只有选定的用户不应看到此进程或文件。

这些选项可以一起使用。

HIDE_RULE 字符串 的格式必须是:process(file)_name_to_hide;access_user_name;access_process_name

其中

  • process(file)_name_to_hide - 要隐藏的进程名(文件路径)。
  • access_user_name - 不应看到此进程(文件)的用户名。
  • access_process_name - 不应看到此进程(文件)的进程名。

要插入多个进程或用户名,请用“,”字符分隔它们。

示例

process_name_to_hide;user_name1,user_name2;process_name1,process_name2

所有名称都支持通配符。

"*" - 匹配所有字符。

"?" - 匹配任何单个字符。

一些示例

*;*;* - 从所有用户和进程中隐藏所有进程。仅供娱乐。

System;*;* - 从所有用户和进程中隐藏“System”进程。

er*;*;* - 从所有人那里隐藏所有以“er”字符开头的进程。

System;*;Rob - 从用户 Rob 那里隐藏“System”进程。

HideDriver IOCTL

所有可以处理 HideDriver 的 IOCTL 都位于文件 Ioctl.h 中。

[来自文件 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.
*/

/*-----------------------------------------------------------------------*/

如何构建此解决方案

  1. 安装 Windows Driver Developer Kit 2003
    http://www.microsoft.com/whdc/devtools/ddk/default.mspx
  2. 将全局环境变量“BASEDIR”设置为已安装 DDK 的路径。
    计算机 -> 属性 -> 高级 -> 环境变量 -> 系统变量 -> 新建
    例如:BASEDIR -> c:\winddk\3790
    (之后您需要重新启动计算机。)
  3. 下载并安装 boost(在 1.38 版本上测试过)
    https://boost.ac.cn/users/download/
  4. 将全局环境变量“BOOST”设置为已安装 boost 的路径。

如果您选择 Visual Studio 2003,您可以直接打开 HideDriver_vs7.sln 并构建所有内容。

如果您选择 Visual Studio 2005 或 2008,您将需要 Visual Studio 2003 的文件来完成此操作。将此处的文件复制到项目文件夹:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src\intel\st_lib\(当然,此路径取决于 Visual Studio 的安装路径):HideDriver_source\src\drvCppLib\lib_copy\

之后,您可以在 Visual Studio 2005 中使用文件“HideDriver_vs8.sln”,在 Visual Studio 2008 中使用文件“HideDriver_vs9.sln”。

关于构建的附加信息

您还需要来自 $VCInstllPath\crt\src\intel\st_lib\ 的文件——单线程 C++ 运行时库位于该目录中。任何其他版本的运行时库都不能用于构建驱动程序。

此方法最初由 Gary Nebbett 在文章“在内核模式代码中启用 C++ 异常和 RTTI”中发现。似乎自那时以来没有人发明出更好的方法。

支持的 Windows 版本和测试

所有测试均在启用了 Driver Verifier 的情况下进行,**所有选项均开启**,除了低资源模拟。

  • Windows 2000, SP4
  • Windows XP, SP3
  • Windows 2003 Server, R2
  • Windows Vista, SP0, SP1
  • Windows 2008 Server
  • Windows 7 Beta, Build 7000

所有版本均为 x86;x64 Windows 版本不支持,因为有 PatchGuard

参考文献

  • Mark Russinovich, David Solomon. Microsoft Windows Internals ((第四版) 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

有用链接

我们推荐 Toby Opferman 的文章“Driver Development”

另外,查看其他文章(第二部分第三部分第四部分第五部分第六部分第七部分)也会很有用。

此外,我们开发此项目获得的许多知识都来自这里。

  1. 挂钩 Windows NT 系统服务
  2. http://www.osronline.com/
  3. http://www.rootkit.com/
  4. http://msdn.microsoft.com/
  5. https://boost.ac.cn/

    对于俄语读者,我们推荐这些来源:

  6. http://wasm.ru/
  7. http://wasm.ru/article.php?article=apihook_3
  8. http://wasm.ru/article.php?article=hidingnt

附加信息

有关附加信息,请访问 Apriorit 教育页面

历史

2009 年 1 月 21 日(作者:Ivan Romanenko & Sergey Popenko)

  • 本文的初始版本

2009 年 2 月 12 日(作者:Ivan Romanenko)

  • 添加了在 VS2003、VS2005、VS2008 中构建解决方案的可能性。
  • 添加了“如何构建此解决方案”主题。

2009 年 4 月 29 日(作者:Ivan Romanenko)

  • 项目
    • Bugfix
      • 隐藏目录中的第一个文件错误
      • 导致 PC 锁定的错误同步
      • 在高 IRQL 上分配内存
      • 使用 DDK2003 的构建错误
      • 按用户过滤的错误
      • 移除了 SingleMode 的使用并更改了 HookMng
    • 新建
      • 使用 Wildmat 代替自己的通配符实现。
      • 单元测试
      • 使用 BOOST 和 STLPort。
      • 项目结构变更,添加了 utils 和 tests。
  • 文章
    • Bugfix
      • Ioctl.h 中的语言错误。
      • 修复了代码注释中的拼写错误。
    • 新建
      • 11 个新主题已添加。

2009 年 8 月 13 日(作者:Ivan Romanenko)

  • 项目
    • Bug 修复
      • 修复:CReadWriteSection 类中的同步错误。
      • 修复:网络共享名称转换错误。
      • 添加:用于接收偏移量的启发式方法。绝对偏移量不再使用,除非在 DriverVerifier 在 Vista 下替换 ntoskrnl 代码的情况下。
© . All rights reserved.