Subscribe to receive all latest blog updates

This article includes description of simple unhooker that restores original System Service Table hooked by unknown rootkits, which hide some services and processes.

Contents:

1. Rootkit detection algorithm
2. Memory-mapped files in kernel mode
3. Implementation of the antirootkit algorithm
4. SST unhooker demonstration
5. Anti-rootkit building

Written by:
Victor Milokum,
Leader of Network Security Team


1. Rootkit detection algorithm

Let's develop a simple driver to detect and delete SST hooks. Therefore, our solution should not use Zw-functions and SST, as it is supposed that System Service Table is corrupted by malware.

In this article, I am not going to pay attention to filter driver and function code splicers. However, I will probably do it in future.

Comparing current SST with the initial one located in ntoskernel.exe is the easiest way for hooks detecting and removing.

We should perform the following steps:

  1. Find ntoskernel module in memory.
  2. Find ntoskernel section, where SST is located, and calculate relative SST offset in section.
  3. Find ntoskernel section in ntoskernel.exe.
  4. Calculate real SST address in file.
  5. Read values from file and compare them with SST.

Before implementation, we’ll consider memory-mapped files in kernel mode.

2. Memory-mapped files in kernel mode

According to Wikipedia, memory-mapped file is a virtual memory segment that has been assigned a direct byte-for-byte correlation with some portion of a file or file-like resource.

We are going to use memory-mapped files in kernel mode, as they are quite useful for parsing PE file. In addition, their API is really easy-to-use, as it is rather similar to Win32 API.

Instead of CreateFileMapping and MapViewOfSection functions in kernel mode, driver will access the following functions:

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

and

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

Unfortunately, using these functions will break our rule about not using SST. In addition, it is good for antirootikit to use extremely low-level functions in order to be invisible to possible rootkits. Thus, we can use undocumented Memory Manager (Mm) functions (at our own risk, of course):

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

Example above shows alternative approach of using mapped files in the MmCreateSection / MmMapViewOfSection functions.

Introduced solution is pretty good, as it doesn’t use or even handle Zw* functions. Unfortunately, there is one restriction. If you start demonstrated example from DriverEntry, it will work fine, but if you start it from the IRP_MJ_DEVICE_CONTROL handler, the MmCreateSection function will fail with STATUS_ACCESS_DENIED.

Why it happens?

The answer is simple. Zw* functions set previous mode to KernelMode, which allows to use kernel mode pointers and handlers as parameters (learn more details at Nt vs. Zw - Clearing Confusion On The Native API article).

Thus, only DriverEntry or the system thread can call considered function.

3. Implementation of the antirootkit algorithm

The following structure saves all ntoskernel parsing results:

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

Antirootkit algorithm implementation:

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

The following function returns real SST value:

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

Main functionality implementation:

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

Cycle above completely deletes all SST hooks and brings SST to its virgin state.

4. SST unhooker demonstration

We have considered steps for developing a simple console utility named “unhooker.exe” for testing purposes. We can start it with no parameters; in this case, it will show information about its abilities:

  1. 1. The “stat” command shows statistics about SST hooking.
  2. 2. The “unhook” command cleans up SST.

The following example demonstrates how to use utility to detect and delete hooks:

Have fun!

5. Anti-rootkit building

Steps for unhooker building are just as ones, described in the “Hide Driver” article:

  1. Install Windows Driver Developer Kit 2003 http://www.microsoft.com/whdc/devtools/ddk/default.mspx
  2. Set global environment variable "BASEDIR" to path of installed DDK. Go here: Computer -> Properties -> Advanced -> Environment variables ->System Variables -> New
  3. And set it like this: BASEDIR -> c:\winddk\3790
    (You have to restart your computer after this.)

  4. If you choose Visual Studio 2003, then you can simply open UnhookerMain.sln and build it all.

Download:

release.zip (58 KB)

src.zip (37 KB)