Windows Developer's Journal



 

 

 
WDJ
WDJ Current Issue
Download WDJ Code
Development Tools
WDJ Favorite Links
Job Opps
WDJ Authors Wanted
WDJ on CD-ROM
Contact WDJ
Marketing Opportunites
Vendors
Customer Service
Search
Subscribe
------------------
WDJ Archive
----------------
Dinkumware
Seagate
Click Here!
Click Here!
Feature Article Extras (Figures, Listing)

Tracing NT Kernel-Mode Calls

By Dmitri Leman
 
Figure 1: Extracts from pehelper.cpp

Excerpts from PeHelper.cpp

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

   FUNCTION: bGetModuleFileName

   PURPOSE:  Get module name by the base address and PE headed
      
   PARAMETERS:                   .
                                  
   void              * p_pBaseAddress   - base address
   PIMAGE_NT_HEADERS   p_pNTHeader      - PE header
   char              * p_pszReturnName  - return module name
   int                 p_iSizeName      - size of the buffer

   RETURN VALUE:
      bool          true on success, false on failure
-----------------------------------------------------------------*/
bool bGetModuleFileName
(
    void              * p_pBaseAddress,
    PVOID               p_pNTHeader,
    char              * p_pszReturnName,
    int                 p_iSizeName
)
{
    *p_pszReturnName = 0;

    if (!MmIsAddressValid(p_pNTHeader))
        return false;

    ULONG l_dwExpTableRVAOffset = 
        ((PIMAGE_NT_HEADERS)p_pNTHeader)->OptionalHeader.
        DataDirectory[0].VirtualAddress;
    if(!l_dwExpTableRVAOffset)
        return false;

    PIMAGE_EXPORT_DIRECTORY l_pExportDir = 
        (PIMAGE_EXPORT_DIRECTORY)
        (((char*)p_pBaseAddress) + l_dwExpTableRVAOffset);
    
    if (!MmIsAddressValid(l_pExportDir))
        return false;

    ULONG l_dwNameRVA = l_pExportDir->Name;
    if(!l_dwNameRVA)
        return false;

    char * p_pszName = ((char*)p_pBaseAddress) + l_dwNameRVA;
    if (!MmIsAddressValid(p_pszName))
        return false;

    strncpy(p_pszReturnName, p_pszName, p_iSizeName);

    return true;
}//bool bGetModuleFileName

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

   FUNCTION: pdwGetSlotInExportTable

   PURPOSE: Look for the exported function and return the address
    of the slot in the module export table correspondent to
    this function. The value in this slot is the address of this
    function.
      
   PARAMETERS:                   .

    void *  p_pBaseAddress          - module base address
    const char *  p_pszFunctionName - name of the function
    void *  p_pNTHeader             - location of NT header for
        this module. It can be obtained from p_pBaseAddress,
        but should be provided by the called for efficiency.

   RETURN VALUE:
      ULONG * - pointer to the slot or NULL
-----------------------------------------------------------------*/
ULONG * pdwGetSlotInExportTable
(
    void *  p_pBaseAddress,
    const char *  p_pszFunctionName,
    void *  p_pNTHeader
)
{
    PIMAGE_SECTION_HEADER l_ExpSectHeader = 
        pGetSectionHeader(".edata", 
        (PIMAGE_NT_HEADERS)p_pNTHeader);

    if ( !l_ExpSectHeader )
        return NULL;

    PIMAGE_EXPORT_DIRECTORY l_pExpDir = (PIMAGE_EXPORT_DIRECTORY)
        (ULONG(p_pBaseAddress) +l_ExpSectHeader->PointerToRawData);

    PULONG l_pdwFunctions = (PULONG)((ULONG)
        l_pExpDir->AddressOfFunctions + ULONG(p_pBaseAddress));
    PCSTR *l_ppszName = (PCSTR*)((ULONG)
        l_pExpDir->AddressOfNames + ULONG(p_pBaseAddress));
    WORD * l_pwOrdinals = (WORD*)((ULONG)
        l_pExpDir->AddressOfNameOrdinals + ULONG(p_pBaseAddress));

    int l_iNumNames = l_pExpDir->NumberOfNames;
    for (int i=0; i < l_iNumNames; i++ )
    {
        if(!_stricmp(p_pszFunctionName, 
            (*l_ppszName + ULONG(p_pBaseAddress))))
        {
            WORD l_wOrdinal = *l_pwOrdinals;
            return l_pdwFunctions + l_wOrdinal;
        }//if(!stricmp(p_pszFunctionName, *l_ppszName))

        l_ppszName++;
        l_pwOrdinals++;
    }//for (int i=0; i < l_iNumNames; i++ )
    return NULL;
}//ULONG * pdwGetSlotInExportTable

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

   FUNCTION: pGetProcAddress

   PURPOSE: The same functionality as Win32 GetProcAddress,
            but works for NT kernel mode
      
   PARAMETERS:                   .

    void *  p_pBaseAddress          - module base address
    const char *  p_pszFunctionName - name of the function
    void *  p_pNTHeader             - location of NT header for
        this module. If it is NULL, the location will be
        obtained from p_pBaseAddress, but the caller may
        pass earlier obtained value for efficiency.

   RETURN VALUE:
      void * - pointer to the exported function
-----------------------------------------------------------------*/
void * pGetProcAddress
(
    void *  p_pBaseAddress,
    const char *  p_pszFunctionName,
    void *  p_pNTHeader
)
{
    if(!p_pNTHeader)
    {
        p_pNTHeader = pCheckModuleHeader(p_pBaseAddress);
        if(!p_pNTHeader)
            return NULL;
    }
    ULONG * l_pdwSlot = pdwGetSlotInExportTable(p_pBaseAddress, 
        p_pszFunctionName, p_pNTHeader);
    if(!l_pdwSlot)
        return NULL;
    return (void*)(*l_pdwSlot + ULONG(p_pBaseAddress));
}//void * pGetProcAddress

Return to article
 
Figure 2: Reading import descriptors

Excerpt from Intrcpt.cpp

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

   FUNCTION: Interceptor::iInterceptImportedFunctionsInModule

   PURPOSE:  Intercept functions imported by the specified
    module as we were instructed by the config file
      
   PARAMETERS:                   .
                                  
    PVOID p_pModuleBaseAddress  Base address of the module
    PCSTR p_pszFileName         Name of the file for this module  
    bool  p_bRestore            Undo the interception
    bool  p_bUseFile - If true -read the import info from the file.
                        NT drivers may have import descriptors in
                        "INIT" section, which is discardable.
                        Therefore, we will try to read it from file

   RETURN VALUE:
      int       Number of intercepted functions
                0 if no functions of interest were found
                -1 on error
-----------------------------------------------------------------*/
int Interceptor::iInterceptImportedFunctionsInModule
(
    PVOID p_pModuleBaseAddress,
    PCSTR p_pszFileName,
    bool  p_bRestore,
    bool  p_bUseFile
)
{
    if(!p_pModuleBaseAddress || 
        ULONG(p_pModuleBaseAddress) < 0x80000000)
        return 0; // we don't work with user addresses
    PIMAGE_NT_HEADERS l_pNTHeader = (PIMAGE_NT_HEADERS)
        pCheckModuleHeader(p_pModuleBaseAddress);
    if(!l_pNTHeader)
    {
        MYTRACE(TF_Error, 
            "ERROR : cannot recognize module header %s at %x",
            p_pszFileName, p_pModuleBaseAddress);
        return -1;
    }

    IMAGE_IMPORT_DESCRIPTOR* l_pImportDesc; 

    ULONG l_dwImportsStartRVA = 
        l_pNTHeader->OptionalHeader.DataDirectory
        [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if ( !l_dwImportsStartRVA )
    {
        MYTRACE(TF_Error, 
            "ERROR : cannot find import table in module %x",
            p_pModuleBaseAddress);
        return -1;
    }

    l_pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)
        (ULONG(p_pModuleBaseAddress) + ULONG(l_dwImportsStartRVA));

    // Find the section (usually .idata) that contains the imports.
    PIMAGE_SECTION_HEADER l_pSection = 
       GetEnclosingSectionHeader(l_dwImportsStartRVA, l_pNTHeader);
    if ( !l_pSection )
    {
        MYTRACE(TF_Error, 
            "ERROR : cannot find a section containing the "
            "import table in module %x",
            p_pModuleBaseAddress);
        return -1;
    }

    ULONG l_dwImportFileOffset = 0;
    NTSTATUS l_Status;
    MyFileLocal l_File;
    if(p_bUseFile)
    {
        long l_lDelta = (long)(l_pSection->VirtualAddress - 
                              l_pSection->PointerToRawData);
        //This delta is the difference between the location of this
        //section in memory and in the file. In order to find the 
        //offset in the file of some object, which belongs to this 
        //section, we should subtract the delta from this 
        //object's RVA.

        l_dwImportFileOffset = l_dwImportsStartRVA - l_lDelta;

        //Some file names returned by ZwQuerySystemInformation
        //does not have any directory information. They seems to
        //always be in Winnt\system\drivers. Other have
        //the directory in the form "Winnt\System\". In both cases
        //we should make correction in the directory name to
        //be able to open the file.
        char l_szFilePath[2*_MAX_PATH];
        if(!strchr(p_pszFileName, '\\'))
        {
            //this file name has no directory. 
            //May be it is in Windows\System32\Drivers
            //Try to contruct a full path
            _snprintf(l_szFilePath, sizeof(l_szFilePath),
                "\\SystemRoot\\System32\\Drivers\\%s",
                p_pszFileName);
            p_pszFileName = l_szFilePath;
        }//if(!strchr(p_pszFileName, '\\'))
        else if(!_strnicmp(p_pszFileName, 
            g_Data.m_pParams->m_szWindowsDir+2,
            g_Data.m_pParams->m_iLengthWindowsDir-2))
        {
            //In this case the module name is in form 
            //"\WinNT\some_dir\module_name"
            //Try to contruct a full path
            _snprintf(l_szFilePath, sizeof(l_szFilePath),
                "\\SystemRoot\\%s",
                p_pszFileName + 
                g_Data.m_pParams->m_iLengthWindowsDir-1);

            p_pszFileName = l_szFilePath;
        }
                        
        l_Status = l_File.OpenFile(p_pszFileName,
            false,false, NULL);

        if(!NT_SUCCESS(l_Status))
        {
            return -1;
        }
    }//if(p_bUseFile)

    int l_iNumFunctions = 0;
    while ( 1 )
    {
        IMAGE_IMPORT_DESCRIPTOR l_ImportDescr;
        if(p_bUseFile)
        {
            //Read the next IMAGE_IMPORT_DESCRIPTOR structure
            //from the file
            LARGE_INTEGER l_liOffset;
            l_liOffset.QuadPart = l_dwImportFileOffset;

            l_Status = l_File.ReadWriteFile(false, 
                &l_ImportDescr, sizeof(l_ImportDescr),
                &l_liOffset, NULL);
            if(!NT_SUCCESS(l_Status))
            {
                break;
            }
            l_pImportDesc = &l_ImportDescr;
        }
        //If p_bUseFile == false, we already have l_pImportDesc
        //pointing to the location in memory, where the import
        //descriptor is, unless it is discarded.

        // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
        if ( (l_pImportDesc->TimeDateStamp==0 ) && 
             (l_pImportDesc->Name==0) )
        {
            break;
        }
        for (IMAGE_THUNK_DATA * l_pThunk = (IMAGE_THUNK_DATA*)
                (ULONG(p_pModuleBaseAddress) + 
                 ULONG(l_pImportDesc->FirstThunk));
            l_pThunk->u1.Function; l_pThunk++)
        {
            if(p_bRestore)
            {
                ASM_APIFunctionStub * l_pStub = 
                    IsItInterceptedFunction(l_pThunk->u1.Function);
                if(l_pStub)
                {
                    InterlockedExchange
                        ((long*)&l_pThunk->u1.Function, 
                        l_pStub->dwGetOriginalFunctionAddress());

                    MYTRACE(TF_Intercepted, 
                        "Import %s restored in %s",
                        l_pStub->m_dwFunctionNameOffset,
                        p_pszFileName);

                    l_iNumFunctions++;
                }
            }
            else
            {
                ASM_APIFunctionStub * l_pStub = 
                    g_Data.m_pConfigMgr->pFindStubByOrigAddress
                        ((void*)l_pThunk->u1.Function);

                if (l_pStub)
                {
                    ULONG l_dwHookAddress = 
                        (ULONG)&l_pStub->m_Code;

                    InterlockedExchange
                        ((long*)&l_pThunk->u1.Function, 
                        l_dwHookAddress);

                    MYTRACE(TF_Intercepted, 
                        "Import %s intercepted in %s",
                        l_pStub->m_dwFunctionNameOffset,
                        p_pszFileName);

                    l_iNumFunctions++;

                }//if(l_dwHookAddress)
            }
        }//for (l_pThunk...

        if(p_bUseFile)
            l_dwImportFileOffset += sizeof(l_ImportDescr);
        else
            l_pImportDesc++;
    }//while (1)
    l_File.CloseFile();
    return l_iNumFunctions;
}//int Interceptor::iInterceptImportedFunctionsInModule
Return to article
 
Figure 3: Partial configuration file

EXP:HAL.DLL:READ_PORT_BUFFER_UCHAR
DWORD
LPDATA OUT
DWORD

EXP:HAL.DLL:READ_PORT_BUFFER_ULONG
DWORD
LPDATA OUT
DWORD

EXP:HAL.DLL:READ_PORT_BUFFER_USHORT
DWORD
LPDATA OUT
DWORD

EXP:HAL.DLL:READ_PORT_UCHAR
DWORD

EXP:HAL.DLL:READ_PORT_ULONG
DWORD

EXP:HAL.DLL:READ_PORT_USHORT
DWORD

EXP:HAL.DLL:WRITE_PORT_BUFFER_UCHAR
DWORD
LPDATA
DWORD

EXP:HAL.DLL:WRITE_PORT_BUFFER_ULONG
DWORD
LPDATA
DWORD

EXP:HAL.DLL:WRITE_PORT_BUFFER_USHORT
DWORD
LPDATA
DWORD

EXP:HAL.DLL:WRITE_PORT_UCHAR
DWORD
BYTE

EXP:HAL.DLL:WRITE_PORT_ULONG
DWORD
DWORD

EXP:HAL.DLL:WRITE_PORT_USHORT
DWORD
WORD

EXP:HAL.DLL:HalBeginSystemInterrupt
DWORD
DWORD
Return to article
 
Figure 4: Sample tracer output

This is an excerpt from an output produced by a sample configuration file Hardware.cfg. It shows interupts and port access in responce to letters "WDJ" typed on a keyboard. 0x61 is the keyboard interrupt number. 0x11 and 0x91 are scan codes for the key 'W' down and up clicks, 0x20 and 0xA0 - 'D', 0x24 and 0xA4 - 'J'

{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 00000011.
{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 00000091.
{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 00000020.
{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 000000A0.
{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 00000024.
{ HalBeginSystemInterrupt(ULONG:01010006,ULONG:00000061)
} HalBeginSystemInterrupt = 00000001.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000064)
} READ_PORT_UCHAR = 00000015.
{ READ_PORT_UCHAR(ULONG:00000060)
} READ_PORT_UCHAR = 000000A4.
Return to article
 
Listing 1: stat.h — A simple timing class

/******************************************************************

Module : Stat.h - tools to measure overhead of the tracer Written 1998,1999 by Dmitri Leman Purpose: Purpose: NT kernel mode tracer. *****************************************************************/ #ifndef _STAT_H_ #define _STAT_H_ #pragma pack(push,4) //RDTSC instruction loads the current value of //the processor's timestamp counter into the EDX:EAX //OpCode 0x0f31 #define GetProcessorTickCount(p_i64ReturnCounter) __asm { \ __asm push eax \ __asm push edx \ __asm __emit 0fh \ __asm __emit 31h \ __asm mov DWORD PTR p_i64ReturnCounter, EAX \ __asm mov DWORD PTR p_i64ReturnCounter + 4, EDX \ __asm pop eax \ __asm pop edx \ } struct KeepStat { unsigned __int64 m_ui64TotalTimeIn; ULONG m_ulMinTimeIn; ULONG m_ulMaxTimeIn; ULONG m_ulNumberOfCalls; }; struct GetStat { GetStat(KeepStat & p_rKeep) : m_rKeep(p_rKeep) { unsigned __int64 l_ui64TimeStart; GetProcessorTickCount(l_ui64TimeStart); m_ui64TimeStart = l_ui64TimeStart; } void vRecord() { unsigned __int64 l_ui64TimeEnd; GetProcessorTickCount(l_ui64TimeEnd); ULONG l_ulTimeInside = //can keep up to few seconds (ULONG)(l_ui64TimeEnd - m_ui64TimeStart); ULONG l_dwPrev = InterlockedExchangeAdd( ((long*)&m_rKeep.m_ui64TotalTimeIn), l_ulTimeInside); if(l_dwPrev + l_ulTimeInside < l_dwPrev) {//Overflow. It should not happen too often. InterlockedIncrement (((long*)&m_rKeep.m_ui64TotalTimeIn)+1); } InterlockedIncrement((long*)&m_rKeep.m_ulNumberOfCalls); //The following operations are not thread safe. //But we cannot protect them by a critical section. //Thefefore, we may miss a data once in a while if(m_rKeep.m_ulMinTimeIn > l_ulTimeInside) m_rKeep.m_ulMinTimeIn = l_ulTimeInside; if(m_rKeep.m_ulMaxTimeIn < l_ulTimeInside) m_rKeep.m_ulMaxTimeIn = l_ulTimeInside; } unsigned __int64 m_ui64TimeStart; KeepStat & m_rKeep; };//struct GetStat extern KeepStat g_StatEntry, g_StatReturn; #pragma pack(pop) #endif //define _STAT_H_ // End of File

Return to article
 
Table 1: Allowable parameter types

Return to article
 
Table 2: Configuration file entries

Return to article
 


| Top | Search
CMP

© 2001 CMP Media Inc. All Rights Reserved. Privacy Policy