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

简单的 SST Unhooker

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (13投票s)

2010年3月17日

CPOL

4分钟阅读

viewsIcon

38615

downloadIcon

1259

本文介绍了一个简单的 unhooker,它可以恢复被未知 rootkit 钩住的原始 SST,这些 rootkit 会隐藏某些服务和进程。

目录

  1. SST:参考文献
  2. 算法
  3. 内存映射文件
  4. 实现
  5. 演示
  6. 如何构建
  7. 历史

1. SST:参考文献

本文是 Ivan Romananko 的文章“隐藏进程和文件的驱动程序”的逻辑延续。你可以在其中找到所有关于系统服务表 (SST) 及其钩取操作的必要信息

在本文中,我想介绍如何编写自己的 unhooker,它可以恢复被 Ivan 那样的驱动程序钩住的原始 SST。

2. 算法

我的目标是编写一个简单的驱动程序,用于 SST 钩取检测和移除。

这意味着我们的驱动程序不应使用各种 Zw 函数和 SST 表,因为我假设 SST 表已被未知 rootkit 损坏。

目前我不会考虑过滤驱动程序和函数代码拼接器,但也许以后会回来研究它们。

检测和移除钩取的最简单方法是将内存中的 SST 与 ntoskernel.exe 文件中的初始 SST 进行比较。

所以目标是:

  1. 在内存中找到 ntoskernel 模块
  2. 找到 ntoskernel 中存放 SST 的部分,并计算 SST 在该部分中的相对偏移量
  3. ntoskernel.exe 文件中找到该部分
  4. 计算文件中 SST 的实际地址
  5. 从文件中读取值并与 SST 进行比较

但在实现之前,我想提供一些额外的信息。

3. 内核模式下的内存映射文件

“内存映射文件是虚拟内存的一个段,它被赋予了与文件或类文件资源的部分直接的字节对字节关联。” (c) Wiki

是的,我们要解析 PE 文件,而内存映射文件对此任务非常有用。

而且,从内核模式下使用内存映射文件 API 非常简单,因为它与 Win32 API 非常相似。在内核模式驱动程序中,不应使用 CreateFileMappingMapViewOfSection 函数,而是应该访问

NTSTATUS
  ZwCreateSection(
    OUT PHANDLE  SectionHandle,
    IN ACCESS_MASK  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes OPTIONAL,
    IN PLARGE_INTEGER  MaximumSize OPTIONAL,
    IN ULONG  SectionPageProtection,
    IN ULONG  AllocationAttributes,
    IN HANDLE  FileHandle OPTIONAL
    );

NTSTATUS
  ZwMapViewOfSection(
    IN HANDLE  SectionHandle,
    IN HANDLE  ProcessHandle,
    IN OUT PVOID  *BaseAddress,
    IN ULONG_PTR  ZeroBits,
    IN SIZE_T  CommitSize,
    IN OUT PLARGE_INTEGER  SectionOffset  OPTIONAL,
    IN OUT PSIZE_T  ViewSize,
    IN SECTION_INHERIT  InheritDisposition,
    IN ULONG  AllocationType,
    IN ULONG  Win32Protect
    );

函数。

但是,如果我们使用这些函数,我们将违反不使用 SST 的规则。而且,对于反 rootkit 来说,使用极低级别的函数以期望对可能的 rootkit 隐形是很好的。

有鉴于此,我们可以使用内存管理器 (Mm) 的未公开函数,当然是以我们自己的风险。

NTSTATUS
MmCreateSection (
    OUT PVOID *SectionObject,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN PLARGE_INTEGER MaximumSize,
    IN ULONG SectionPageProtection,
    IN ULONG AllocationAttributes,
    IN HANDLE FileHandle OPTIONAL,
    IN PFILE_OBJECT File OPTIONAL
    );

NTSTATUS
MmMapViewOfSection(
    IN PVOID SectionToMap,
    IN PEPROCESS Process,
    IN OUT PVOID *CapturedBase,
    IN ULONG_PTR ZeroBits,
    IN SIZE_T CommitSize,
    IN OUT PLARGE_INTEGER SectionOffset,
    IN OUT PSIZE_T CapturedViewSize,
    IN SECTION_INHERIT InheritDisposition,
    IN ULONG AllocationType,
    IN ULONG Protect
    );

NTSTATUS
MmUnmapViewOfSection(
    IN PEPROCESS Process,
    IN PVOID BaseAddress
     );

NTSTATUS drv_MapAllFileEx(HANDLE hFile OPTIONAL,
                           drv_MappedFile * pMappedFile,
                           LARGE_INTEGER * pFileSize,
                           ULONG Protect)
{
    NTSTATUS status = STATUS_SUCCESS;
    PVOID section = 0;
    PCHAR pData=0;
    LARGE_INTEGER offset;

    offset.QuadPart = 0;

    // check zero results
    if (!pFileSize->QuadPart)
        goto calc_exit;

    status = MmCreateSection (&section,
                              SECTION_MAP_READ,
                              0, // OBJECT ATTRIBUTES
                              pFileSize, // MAXIMUM SIZE
                              Protect,
                              0x8000000,
                              hFile,
                              0
                              );

    if (status!= STATUS_SUCCESS)
         goto calc_exit;

    status = MmMapViewOfSection(section,
                                PsGetCurrentProcess(),
                                (PVOID*)&pData,
                                0,
                                0,
                                &offset,
                                &pFileSize->LowPart,
                                ViewUnmap,
                                0,
                                Protect);

    if (status!= STATUS_SUCCESS)
        goto calc_exit;

calc_exit:

    if (NT_SUCCESS(status))
    {
        pMappedFile->fileSize.QuadPart = pFileSize->QuadPart;
        pMappedFile->pData = pData;
        pMappedFile->section = section;
    }
    else
    {
        if (pData)
            MmUnmapViewOfSection(PsGetCurrentProcess(),
                                pData);

        if (section)
        {
            ObMakeTemporaryObject(section);
            ObDereferenceObject(section);
        }
    }
    return status;
}

此示例演示了通过 MmCreateSection/MmMapViewOfSection 函数使用内存映射文件的替代方法。

所提出的方法相当不错,因为它不使用 Zw* 函数,甚至不使用句柄,但有一个限制。如果你从 DriverEntry 启动此示例,它会正常工作,但如果你从 IRP_MJ_DEVICE_CONTROL 处理程序启动它,你会发现 MmCreateSection 函数会因 STATUS_ACCESS_DENIED 而失败。为什么?

答案是:Zw* 函数有一个好处,那就是它们将 Previous Mode 设置为 KernelMode,这允许将内核模式指针和句柄用作它们的参数(有关更多信息,请参阅Nt vs. Zw - Clearing Confusion On The Native API article)。

因此,上面介绍的函数只能从 DriverEntry 或系统线程调用。

4. 算法实现

我设计了以下结构来保存所有 ntoskernel 解析结果。

#define IMAGE_SIZEOF_SHORT_NAME              8
typedef struct _Drv_VirginityContext
{
    drv_MappedFile m_mapped;
    HANDLE m_hFile;
    UCHAR  m_SectionName[IMAGE_SIZEOF_SHORT_NAME+1];
    ULONG  m_sstOffsetInSection;
    char * m_mappedSST;
    ULONG m_imageBase;
    char * m_pSectionStart;
    char * m_pMappedSectionStart;
    char * m_pLoadedNtAddress;
}Drv_VirginityContext;

我按照以下方式实现了选定的算法。

static NTSTATUS ResolveSST(Drv_VirginityContext * pContext, 
                           SYSTEM_MODULE * pNtOsInfo)
{
    PIMAGE_SECTION_HEADER pSection = 0;
    PIMAGE_SECTION_HEADER pMappedSection = 0;
    NTSTATUS status = 0;
    PNTPROC pStartSST = KeServiceDescriptorTable->ntoskrnl.ServiceTable;
    char * pSectionStart = 0;
    char * pMappedSectionStart = 0;
    // Drv_ResolveSectionAddress function detects 
    // to which section pStartSST belongs 
    // pSection will contain the section of ntoskernel.exe that contains SST

    pContext->m_pLoadedNtAddress = (char*)pNtOsInfo->pAddress;
    status = Drv_ResolveSectionAddress(pNtOsInfo->pAddress, pStartSST, &pSection);
    if (!NT_SUCCESS(status))
        goto clean;

    // save section name to context
    memcpy(pContext->m_SectionName, pSection->Name, IMAGE_SIZEOF_SHORT_NAME);

    // calculate m_sstOffsetInSection - offset of SST in section
    pSectionStart = (char *)pNtOsInfo->pAddress + pSection->VirtualAddress;
    pContext->m_sstOffsetInSection = (char*)pStartSST - pSectionStart;

    // find section in mapped file - on disk!
    status = Drv_FindSection(pContext->m_mapped.pData, 
                             pSection->Name, 
                             &pMappedSection);
    if (!NT_SUCCESS(status))
        goto clean;

    pMappedSectionStart = (char *)pContext->m_mapped.pData + 
                           pMappedSection->PointerToRawData;

    pContext->m_mappedSST = pMappedSectionStart + pContext->m_sstOffsetInSection;

    
    {   // don´t forget to save ImageBase
        PIMAGE_DOS_HEADER dosHeader  = 
                      (PIMAGE_DOS_HEADER)pContext->m_mapped.pData;
        PIMAGE_NT_HEADERS pNTHeader = 
                      (PIMAGE_NT_HEADERS)((char*)dosHeader + dosHeader->e_lfanew);
        pContext->m_imageBase = pNTHeader->OptionalHeader.ImageBase;
    }

    pContext->m_pSectionStart = pSectionStart;
    pContext->m_pMappedSectionStart = pMappedSectionStart;
clean:
    return status;
}

这是返回 SST 实际值的函数。

void Drv_GetRealSSTValue(Drv_VirginityContext * pContext, long index, void ** ppValue)
{
    char * pSST = pContext->m_mappedSST;
    ULONG * pValue = ((ULONG *) pSST) + index;
    // now pValue points to the mapped SST entry
    // but entry contains offset from the beginning of ntoskernel file, 
    // so correct it
    *ppValue = (void*)(*pValue + (ULONG)pContext->m_pLoadedNtAddress – 
                       pContext->m_imageBase);
}

之后,实现主要功能就相当简单了。

virtual NTSTATUS ExecuteReal()
{
    CAutoVirginity initer;
    NT_CHECK(initer.Init(&m_virginityContext));

    // now we are ready to scan :)
    for(int i = 0, sstSize = Drv_GetSizeOfNtosSST();
        i < sstSize;
        ++i)
    {
        void ** pCurrentHandler = Drv_GetNtosSSTEntry(i);

        void * pRealHandler = 0;
        Drv_GetRealSSTValue(&m_virginityContext, i, &pRealHandler);
        if (pRealHandler != *pCurrentHandler)
        {
            // oops, we found the difference!
            // unhook this entry
            Drv_HookSST(pCurrentHandler, pRealHandler);
        }
    }
    return NT_OK;
}

这个小循环完全移除了所有 SST 钩取,并将 SST 恢复到其初始状态。

6. 演示

为了测试,我开发了一个名为 unhooker.exe 的简单控制台实用程序。此实用程序可以不带参数启动;在这种情况下,它会显示有关其功能的信息。

  1. stat”命令显示 SST 钩取统计信息。
  2. unhook”命令清除 SST。

此示例演示了如何使用该实用程序来检测和清除钩取。

sst-unhook/screen.png

玩得开心!

6. 如何构建

构建步骤与“隐藏驱动程序”文章相同。它们是:

  1. 安装 Windows Driver Developer Kit 2003 - http://www.microsoft.com/whdc/devtools/ddk/default.mspx
  2. 将全局环境变量“BASEDIR”设置为已安装 DDK 的路径。转到:计算机 -> 属性 -> 高级 -> 环境变量 -> 系统变量 -> 新建

并将其设置为:BASEDIR -> c:\winddk\3790
(之后您需要重新启动计算机)。

如果您选择 Visual Studio 2003,则可以简单地打开 UnhookerMain.sln 并生成所有内容。

7. 历史

  • 2010年3月17日:初始发布
  • 这篇文章在 Apriorit 网站上 链接
© . All rights reserved.