Introduction
Hi, this is my first article, and English is not my natural language. So first, please excuse my terrible language. This article is about hooking software/hardware interrupts and will also show you, how to retrieve device resource information from the registry. I ASSUME A LITTLE BIT OF DRIVER CODING KNOWLEDGE FOR THIS ARTICLE!
Background/How this works
Ok, now I'll explain, how interrupt hooking works. When an interrupt (software or hardware) occurs, the cpu uses the idt (interrupt descriptor table) to read the necessary information about how to handle that interrupt. You can get the table's memory address like this:
#pragma pack(1) // 2 works, too
typedef struct tagIDT
{
WORD wLimit;
DWORD dwBase;
} IDT, *PIDT;
#pragma pack()
VOID
LoadIDT(
OUT PIDT pIdt )
{
__asm
{
MOV EAX, [pIdt]
SIDT [EAX]
}
}
The command SIDT saves the idt information, which consists of the idt base address and the size(limit) of the table, to the specified memory address.
Now you can read single descriptors from the table. Here is their structure:
#pragma pack(1)
typedef struct tagINT_VECTOR
{
WORD wLowOffset;
WORD wSelector;
BYTE bAccess;
BYTE wUnused;
WORD wHighOffset;
} INT_VECTOR, *PINT_VECTOR;
#pragma pack()
Here are the functions to load/save a descriptor:
VOID
LoadINTVector(
IN PIDT pIdt,
IN UCHAR iVector,
OUT PINT_VECTOR pVector )
{
__try
{
DWORD dwBase = pIdt->dwBase + iVector * sizeof(INT_VECTOR);
memcpy( pVector, (const void *)dwBase, sizeof(INT_VECTOR) );
}
__except( 1 )
{
DPRINT( "LoadINTVector: Exception\n" );
}
DPRINT( "LoadINTVector: Vector 0x%.2X successfully dumped\n",
iVector );
}
VOID
SaveINTVector(
IN PIDT pIdt,
IN UCHAR iVector,
IN PINT_VECTOR pVector )
{
__try
{
DWORD dwBase = pIdt->dwBase + iVector * sizeof(INT_VECTOR);
__asm{ PUSHFD };
__asm{ CLI };
memcpy( (void *)dwBase, pVector, sizeof(INT_VECTOR) );
__asm{ POPFD };
}
__except( 1 )
{
DPRINT( "SaveINTVector: Exception\n" );
}
DPRINT( "SaveINTVector: Vector 0x%.2X successfully set\n", iVector );
}
Build-in ioctl codes:
IOCTL_HOOK_INT
Hooks only one specified interrupt.
- Parameters:
IN UCHAR iVector
IOCTL_UNHOOK_INT
Unhooks only one specified interrupt.
- Parameters:
IN UCHAR iVector
IOCTL_HOOK_ALL_INT
Hooks all useful interrupts.
IOCTL_DUMP_IDT
Dumps the whole IDT table.
- Parameters:
OUT IDT_ENTRY Array[256]
IOCTL_GET_INT_COUNTS
Retrieves the interrupt call counts.
- Parameters:
OUT __int64 Array[256]
IOCTL_GET_START_TIME
Retrieves the time when IOCTL_HOOK_ALL_INT
was called (PerformanceCounter).
- Parameters: OUT __int64 Time
To call these ioctl codes, open the driver like this:
DWORD dwOut;
HANDLE hDriver = CreateFile( <a>\\\\.\\InterruptHook</a>,
GENERIC_READ |GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
DeviceIoControl( hDriver, IOCTL_X, inbuffer, inbufferlen,
outbuffer, outbufferlen, &dwRet, NULL );
Retrieving device information from the registry
Ok, this is quite easy. Have a look at the registry at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum! There you'll find several subkeys like "ACPI" or "STORAGE". These subkeys have further subkeys, which are kinda classes, I don't know exactly what they do. They have subkeys, too, representing device drivers! Their subkey "Control" may include a value called "AllocConfig" (type REG_RESOURCE_LIST
). REG_RESOURCE_LIST
is not documented by microsoft!
It's a structure defined in ntddk.h:
typedef struct _CM_RESOURCE_LIST {
ULONG Count;
CM_FULL_RESOURCE_DESCRIPTOR List[1];
} CM_RESOURCE_LIST, *PCM_RESOURCE_LIST;
typedef struct _CM_FULL_RESOURCE_DESCRIPTOR {
INTERFACE_TYPE InterfaceType;
ULONG BusNumber;
CM_PARTIAL_RESOURCE_LIST PartialResourceList;
} CM_FULL_RESOURCE_DESCRIPTOR, *PCM_FULL_RESOURCE_DESCRIPTOR;
typedef struct _CM_PARTIAL_RESOURCE_LIST {
USHORT Version;
USHORT Revision;
ULONG Count;
CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1];
} CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;
typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {
UCHAR Type;
UCHAR ShareDisposition;
USHORT Flags;
union {
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Generic;
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Port;
struct {
ULONG Level;
ULONG Vector;
ULONG Affinity;
} Interrupt;
struct {
PHYSICAL_ADDRESS Start;
ULONG Length;
} Memory;
struct {
ULONG Channel;
ULONG Port;
ULONG Reserved1;
} Dma;
struct {
ULONG Data[3];
} DevicePrivate;
struct {
ULONG Start;
ULONG Length;
ULONG Reserved;
} BusNumber;
struct {
ULONG DataSize;
ULONG Reserved1;
ULONG Reserved2;
} DeviceSpecificData;
} u;
} CM_PARTIAL_RESOURCE_DESCRIPTOR, *PCM_PARTIAL_RESOURCE_DESCRIPTOR;
You can find a documentation of these structures here.
About the GUI
The GUI just refreshes the interrupt call count every 50ms and displays it. CPS means CALLS PER SECOND! Open a context menu when selecting an IRQ entry from the list!
Info on compiling
- The GUI can be compiled by Visual Studio 6.0 or higher.
- For the driver, you'll need the Windows NT(or higher) DDK(order it from Microsoft).
To compile it, use the DDK build environment, change to the source directory and type "build -cZ". If you don't have the DDK, use the compiled driver from the binary zip file. Here a little comment about the Visual Studio 6.0 C++ compiler/preprocessor:
When compiling my software, the compiler seems to interpret the CTL_CODE
macro wrong, so I had to use the hexadecimal values instead of the macro.
Known Issues
- My floppy disk doesn't work anymore after hooking the interrupts (reboot solves the prob)
- Spy++ ends in bluescreen when hooking a windows procedure (reboot...)
- Please send me feedback about other issues!!!
DISCLAIMER
USE THIS SOFTWARE ON YOUR OWN RISK!!! I DON'T KNOW WHAT HAPPENS TO OTHER COMPUTERS! NO WARRANTY FOR POSSIBLE DAMAGES!!!
History
- July 31th, 2003 - Posted this article on CodeProject