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
|