Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C

Simple SST Unhooker

,
Rate me:
Please Sign up or sign in to vote.
4.92/5 (13 votes)
17 Mar 2010CPOL4 min read 37.4K   1.2K   26   7
This article includes a description of a simple unhooker that restores original SST hooked by unknown rootkits, which hide some services and processes.

Table of Contents

  1. SST: References
  2. Algorithm
  3. Memory Mapped Files
  4. Implementation
  5. Demonstration
  6. How to Build
  7. History

1. SST: References

This article is a logical continuation of the article "Driver to Hide Processes and Files" by Ivan Romananko. You can find all the necessary information about System Service Table (SST) and its hooking in it.

In this article, I would like to present how to write your own unhooker that will restore original SST hooked by drivers like Ivan's one.

2. Algorithm

My goal is to write a simple driver for SST hooking detection and removing purposes.

This means that our driver should not use various Zw-functions and SST table because I suppose that SST table is corrupted by unknown rootkits.

I do not care about filter drivers and function code splicers for now, but maybe I will come back to them in future.

The simplest way to detect and remove hooks is to compare SST that is placed in memory with the initial SST from ntoskernel.exe file.

So the goal is:

  1. to find ntoskernel module in memory
  2. to find the section of ntoskernel where SST is placed and to calculate relative offset of SST in the section
  3. to find this section in the ntoskernel.exe file
  4. to calculate the real address of SST in the file
  5. to read values from the file and to compare them with SST

But before the implementation, I would like to present some additional information.

3. Memory Mapped Files in Kernel Mode

"A memory-mapped file is a segment of virtual memory which has been assigned a direct byte-for-byte correlation with some portion of a file or file-like resource". (c) Wiki

Yeah, we want to parse the PE file and memory mapped files are very useful for this task.

And it is easy enough to use mapped files API from the kernel mode, because it is very similar to Win32 API. Instead of CreateFileMapping and MapViewOfSection functions in kernel mode driver should access:

C++
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

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

functions.

But if we use these functions, we will break our own rule not to use SST. Also, it is good for antirootkit to use extremely low level functions in the hope of being invisible to the possible rootkits.

With regard to this, we can use undocumented functions of Memory Manager (Mm), of course at our own risk:

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

This example demonstrates an alternative approach to the usage of mapped files through MmCreateSection/MmMapViewOfSection functions.

The presented approach is pretty good because it doesn’t utilize Zw* functions and even handles at all, but it has one restriction. If you start this sample from DriverEntry, it will work fine, but if you start it from the IRP_MJ_DEVICE_CONTROL handler, you will see that MmCreateSection function fails with STATUS_ACCESS_DENIED. Why?

The answer is: Zw* functions do one good thing - they set previous mode to KernelMode and this allows to utilize kernel mode pointers and handles as parameters for them (for more information, see Nt vs. Zw - Clearing Confusion On The Native API article).

So, the presented above function can be called only from DriverEntry or from the system thread.

4. Algorithm Implementation

I designed the following structure to save all ntoskernel parsing results:

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

And I implemented the chosen algorithm as follows:

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

And here is the function that returns real value of SST:

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

After that, it is quite simple to implement main functionality:

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

This tiny cycle completely removes all SST hooks and brings SST to its initial state.

6. Demonstration

For testing purposes, I developed a simple console utility named unhooker.exe. This utility can be started without parameters; in this case, it shows information about its abilities:

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

This sample demonstrates how to use utility to detect and erase hooks:

sst-unhook/screen.png

Have fun!

6. How to Build

Build steps are the same as in the “Hide Driver” article. They are:

  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

And set it like this: BASEDIR -> c:\winddk\3790
(You have to restart your computer after this.)

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

7. History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

Written By
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionx64? Pin
xiaxingqiao15-Oct-10 18:57
xiaxingqiao15-Oct-10 18:57 
Generalx64 Pin
xiaxingqiao15-Oct-10 17:05
xiaxingqiao15-Oct-10 17:05 
GeneralRe: x64 Pin
Victor A. Milokum18-Oct-10 5:21
Victor A. Milokum18-Oct-10 5:21 
I think you can port this to x64, but you need to solve some problems, and first of them will be obtaing KeServiceDescriptorTable address; because of KeServiceDescriptorTable variable is not exported starting with Vista x64. There are some approaches to do this, see "Windows Vista 64bits and unexported kernel symbols" for example
QuestionMemory mapped files in kernel mode? Why, what is it good for? Pin
Tage Lejon17-Mar-10 11:05
Tage Lejon17-Mar-10 11:05 
AnswerRe: Memory mapped files in kernel mode? Why, what is it good for? Pin
Garth J Lancaster17-Mar-10 11:48
professionalGarth J Lancaster17-Mar-10 11:48 
GeneralRe: Memory mapped files in kernel mode? Why, what is it good for? Pin
Victor A. Milokum18-Mar-10 2:31
Victor A. Milokum18-Mar-10 2:31 
AnswerRe: Memory mapped files in kernel mode? Why, what is it good for? Pin
Victor A. Milokum18-Mar-10 2:00
Victor A. Milokum18-Mar-10 2:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.