Hy, I am continuing to play with W2k device drivers, and this time I've achieved a
result I've desired for a long time: to hook an interrupt under w2k.
I was using as a guideline a good free sample driver from www.beyondlogic.org but
it is written in C. Anyway it works and I hope to translate it in assembly because
it shows the "legal" way for interrupt hooking under w2k: IoConnectInterrupt() and
The driver contains, among others, those 2 functions: the "ISR" and the DPC.
One may suppose that the ISR, whose goal is only to request the DPC, is a true ISR but
it is not the case. It doesn't terminate with an IRET and the address that
IoConnectInterrupt puts in the IDT is another one. So I decided to go for a
manual entry of my ISR's address into the IDT and wrote a driver in pure assembly.
The success is partial, I've been able to install my (short) ISR, it is correctly executed when I
generate the interrupt but....here is the hell!! I doesn't succeed in returning from my ISR!
I am completely lost into the labyrinth of the Kernel code.... :)
If I put an IRET instruction at the end of the routine it causes a page fault. Why?
The selector is 0008, system code. I only changed the four bytes of the vector with
the address of my ring0 routine...I doesn't touched the selector which should be the
same of my driver because the ISR is executed well.
So the question is? How can I return??

Posted on 2003-12-03 14:40:51 by fooCoder
#define doesn't don't

Pardon...but I am very tired :)
Posted on 2003-12-03 14:44:09 by fooCoder
Hi fooCoder

That's a nice example showing how to hook hardware interrupts via IoConnectInterrupt, thanks. For software interrupts it seems there's still no 'official' way to do that other than modifying the IDT directly I suppose.

From the sounds of it you went the SIDT route to point to your new interrupt handler, then I assume you generated that interrupt to test it out. There is one issue I'm aware of that might be the source of your trouble, depending on how you generated the 'fake' interrupt. This may have no bearing on your hardware int, but it's worth keeping in mind anyway. From my notes,

An interrupt can be generated by:

1. Inserting directly into code, i.e. as "INT 6", or 06CDh

In this case, an "INT x" will push as a return address a pointer to the address *after* the INT

2. Create a condition which creates the desired interrupt
i.e. "lea eax, eax" (C08Dh) generates an "Invalid Opcode" error INT 6

In this case, the return address points to the line which *caused* the error, so ESP in the Interrupt handling routine must be adjusted by 2 bytes (or however many bytes the faulting instruction is)

add dword ptr , 2

OK, this is one issue that might be giving you what sounds like a stack problem. Note also the use of the 32 bit operand IRETD and not the 16 bit IRET. If I remember correctly this caused me problems and IRETD was necessary. Actually, I just resurrected my old Win98 code and got it running OK under a Win2K driver, then I changed the IRETD to an IRET and got a nice BSOD, so... ;-)

There is also this disclaimer in Undocument Windows NT:

You can apply the same technique for hooking software interrupts to hook hardware interrupts or exceptions although you should use the documented loConnectlnterrupt() function to hook hardware interrupts. You have to write an interrupt handler keeping in mind the type of interrupt it is hooking into because the stack frame might differ in various situations. The new interrupt handler must be written in Assembly language because of the restrictions imposed by 32-bit compilers.

I don't quite understand the last line considering the C code example you mentioned, but the text clearly warns about being careful with the stack frame depending on the type of interrupt. If you're having problems with a specific interrupt I could throw it in my code and see if it works.

Posted on 2003-12-04 01:20:01 by Kayaker
Hy Kayaker,

thank you for your precious advice, I've experienced with INT D9 before, to test how to
write correctly into the IDT and then I've turned on INT vector 37 which is IRQ7 - LPT1.
I'll try to test the different behaviour of different interrupt types to solve those
stack problems. I've "discovered" that one might use particular care when using ESI register
in driver routines, like DriverUnload()...so I push it before and pop after the ESI code.

After writing my post I found one possible solution to the IRETD problem: I looked at
the 37h vector before I installed my ISR. It simply pointed to the following code:

push 00000037; vector number
jmp FixedAddress; Fixed address into the kernel

I noticed that other vectors use the same technique, they push their number and jumps
to a fixed routine.
So, I changed my faulting IRET with the same code! And It works!!!
Once my ISR has completed its work, it calls the original code...
But note that FixedAddress changes between different subversions of win2k! So I need to
rebuild my driver for every machine because the jump is fixed.
A better way should be to jump at the old vector address directly. I implemented this
solution too because I save the original vector into four bytes and restore it when


b1 db 0
b2 db 0
b3 db 0
b4 db 0


Note that I try to write the original vector in little endian form into the 4 bytes and
then I read it in esi. Esi should contain the right address and jump to it.
mov edi, offset b1
mov esi,
jmp esi; jmp to the original vector saved in b1,b2,b3,b4

It works fine but I am uncertain because the byte values I expect to read from the old vector,
and that I substitute with the corresponding ones of my ISR, are different from what I expect.
Anyway my ISR installs itself correctly and restore the old vector correctly after DriverUnload.
The jump works fine...is it a coincidence??

Now I've another annoying problem....but it is ridiculous in respect of the previous...my driver
code doesn't succeed in unmasking the IRQ 7! I use this code (in DriverDispatch where I do all but
I tried in DriverEntry too):

mov dx, 00021H
in al, dx; usually 0E0h
shl al, 1
shr al, 1
out dx, al
;Enable IRQ generation on LPT1
mov edx, 0037Ah
in al, dx
or al, 010H
out dx, al
This code works well in DOS mode but not into my driver...it doesn't unmask the IRQ.
When I try this code from the debugger it works well, in particular OUT 60h on port 21h
unmasks the IRQ, but when I exit the debugger IRQ7 is masked again so my ISR is ignored.
How could I test my driver then? I ran the C code sample which contains "exactly" the same

mov edx,37Ah
in al,dx
or al,10h
out dx,al

Once I've ran the C driver the IRQ7 remains unmasked forever and this allows me to use my
ISR....is it possible that the C-DPC, which remains installed, periodically re-unmask the IRQ?

I don't know...what I can say is that when the IRQ7 is unmasked my ISR works.

A few words about the "equipment" I use: A primitive homemade cable link connects DATA BIT 0 with the -ACK PIN.
3 leds are normally attached LPT1. I use another driver to send values directly to the parallel port.
Certain values cause the leds to turn on. But if I send a 1, the signal re-enters via the -ACK pin and
causes an IRQ to happen. Then my ISR starts and I notify this because it turns on a particular led.
Then I manually send a 0 to the port and turn off all the leds.
If I set bit 1 again...the nice game continues :)

I don't know if someone can be interested in viewing all the source code, but when I'll be finished
I could post it.

Posted on 2003-12-04 04:19:52 by fooCoder

I am trying all sort of combinations...but I don't succeed in unmasking IRQ7.

This is the code and it should work:

mov dx, 0x0021
in al, dx
shl al, 1
shr al, 1
out dx, al; al = 60h

The most frustrating thing is that, when I debug my driver, I can also write to the ports directly by the debugger...and in this case the IRQ is unmasked until I remain into the Debugger itself. When I exit and then reenter the Debugger IRQ7 is masked again!

So it is the system who is periodically masking the IRQs....(or the DBG when it finishes)....

I don't know if it is a matter of code privilege....I can access other ports without problems.
Is there a system map of the IRQ status somewhere in memory?

Summarizing, it seems that this code, executed from a ring 0 driver, is simply ignored while the same Out from a Debugger causes the desired unmasking.

Can anyone help me?

Posted on 2003-12-05 06:06:41 by fooCoder

I'm not really familiar with unmasking IRQ's, but is it possible that since (if) you are jumping to the original interrupt handler from your ISR, instead of IRETD to whereever you generated the hardware exception, then all you are doing is passing control back to Windows which is by default remasking the IRQ? In reality this may be what one might often do, chain back to the previous ISR after you are done handling that particular interrupt, but in certain cases you may not want to do that.

If you yourself are generating the exception/interrupt in some manner, and there is little likelihood of it coming from an external source, then I don't know that there would be much reason to return control to the original handler. Normally you would check this in your ISR, you could insert a unique sequence of bytes just before the faulting instruction and test for them as an offset from ESP once inside your ISR, then you either jmp cs: or IRETD. You may need this controlling step for an effective hook/filter.

Posted on 2003-12-05 10:54:37 by Kayaker
Hy Kayaker,

a good new! As you said IRETD works!!! :) Now I don't need to do those risky jumps or to re-pass control to the original handler. It is wonderful! :)

The "bad" new is that, I think so after further investigations, IRQ masking is completely managed somewhere into the core of W2K. It is not so surprisingly because the same IRQ can be shared between different drivers/devices.

When a driver call IoConnectInterrupt the system creates a particular object that is responsible for the managing of that particular IRQ. I think that the entry in the IDT points to that object, this to the "fake" ISR and that requests the DPC. In the C sample there aren't I/O operations directed to port 20/21 (PIC). It is W2K that take care of all this!

It seems that it is not the original handler (which I now avoid with IRETD) that is responsible for the masking.

So, unless I discover how to directly unmask (and keep unmasked) those IRQs, which I consider almost impossible, I can only try to directly translate the C sample and put my operations into the DPC "legally" :)

But, once unmasked, I could hook directly the vector...because it works. :)

Posted on 2003-12-05 11:59:25 by fooCoder
Why hook it directly and mess with masking yourself, if there's a 'legal' and well-defined method of doing it? Does it bring any advantages at all?
Posted on 2003-12-05 12:14:29 by f0dder
Hy Fodder,

There are two reasons:

[1] - The "legal" method is not well documented as it should be (in my personal opinion). I am going to translate the C driver in ASM because MS doesn't give alternatives. Their DDK is not intended for people who want to "understand" how to write a Device Driver. A lot of objects and functions are undocumented but you have to use them into your "C" code.
[2] - Exact cognition of what I am doing. It is related to the first reason and in general is the explanation of why I am using assembly to code.

Posted on 2003-12-06 08:29:23 by fooCoder
As long as you don't think direct hooking is the proper approach for release applications :)
Posted on 2003-12-06 08:33:53 by f0dder
Just acheive a result and move on. Personally I first save the original interrupt vector than over write it than restore it when done. The reason the IRETD isn't working is because the driver is in the same context as the interrupts and IRETD is an intersegment jump. So you are getting a fault.
Posted on 2003-12-07 11:30:46 by mrgone

Just to clarify, IRETD is used to return from an ISR whether there be a task switch or not, the difference lies in how it behaves. The fact that there may or may not be an intersegment jump (CPL=0 to CPL=3) should have no bearing on whether there's a fault. IRETD simply reverses what the processor does when calling an interrupt or exception. This is explained best by the Intel manual Vol 1 Chap. 6:

If the code segment for the handler procedure has the same privilege level as the currently
executing program or task, the handler procedure uses the current stack; if the handler executes at a more privileged level, the processor switches to the stack for the handler?s privilege level.


A return from an interrupt or exception handler is initiated with the IRET instruction. The IRET instruction is similar to the far RET instruction, except that it also restores the contents of the EFLAGS register for the interrupted procedure:

When executing a return from an interrupt or exception handler from the same privilege level as the interrupted procedure, the processor performs these actions:
1. Restores the CS and EIP registers to their values prior to the interrupt or exception.
2. Restores the EFLAGS register.
3. Increments the stack pointer appropriately
4. Resumes execution of the interrupted procedure.

When executing a return from an interrupt or exception handler from a different privilege level than the interrupted procedure, the processor performs these actions:
1. Performs a privilege check.
2. Restores the CS and EIP registers to their values prior to the interrupt or exception.
3. Restores the EFLAGS register.
4. Restores the SS and ESP registers to their values prior to the interrupt or exception,
resulting in a stack switch back to the stack of the interrupted procedure.
5. Resumes execution of the interrupted procedure.

As for doing things in the "legal" way, unless you're just doing a quick ring 0 hack for personal reasons, I would have to agree you shouldn't tie up the CPU in your ISR, or disable interrupts for extended periods of time with CLI/STI to prevent reentrancy issues, especially if you're doing any kind of I/O operations. This is the kind of thing DPC's seemed designed for, something I hope to learn more about, and the DDK kind of emphasizes with an inescapable logic:

Because ISRs must execute as quickly as possible, drivers must usually postpone the completion of servicing an interrupt until after the ISR returns. Therefore, the system provides support for deferred procedure calls (DPCs), which can be queued from ISRs and which are executed at a later time and a lower IRQL than the ISR.

Posted on 2003-12-07 22:03:32 by Kayaker
An intersegment jump means it changes segments and has nothing to do with privilege level. An IRETD instruction is a RET Far instruction meaning intersegment jump.
Posted on 2003-12-07 23:31:05 by mrgone
OK, maybe it's a matter of semantics then. You are correct that an IRET is an intersegment (far) jump in that it makes use of the saved CS register to determine which address, as a long pointer, to return to, even if there is no change in segments (or privilege level) per se, this is my understanding of the matter.

When you used the term 'context' and intersegment jump I thought you meant it to indicate a true segment/ring change (i.e cs: 08 -> 1B). Other than that I don't understand what you were getting at, why the fact that the ISR and interrupt may be in the same driver context has anything to do why IRETD might cause a fault. It doesn't matter, all I was trying to get across was that IRETD should work in either case without faulting.
Posted on 2003-12-08 01:28:09 by Kayaker
I beleive is 02. The Windows system uses it and the driver uses the same CS selector. There for it would not be an intersegment jump. It needs a different CS selector for IRETD. Atleast that's as far as I got with it. The system uses all the memory in one big 4gig flat memory space. Look at the descriptors. The data segment is selector 10h.

This is actually Win2K GDT at logical address 80036000h and the second 64 bit entry is the system CS segment descriptor as indicated by type feild "9B" and the base is 0 and upper limit is actually CFFFFh X 4k granularity bit set =3,489656832d. Almost the full 4 gig.

00000050 00 00 00 00 00 00 00 00-FF FF 00 00 00 9B CF 00 ................
00000060 FF FF 00 00 00 93 CF 00-FF FF 00 00 00 FB CF 00 ................
00000070 FF FF 00 00 00 F3 CF 00-AB 20 00 F0 22 8B 00 80 ......... .."...
00000080 01 00 00 F0 DF 93 C0 FF-FF 0F 00 00 00 F3 40 00 ..............@.
00000090 FF FF 00 04 00 F2 00 00-00 00 00 00 00 00 00 00 ................
000000A0 68 00 40 00 47 89 00 80-68 00 A8 00 47 89 00 80 h.@.G...h...G...
Posted on 2003-12-08 02:46:15 by mrgone

I am happy that my original thread has stimulated such interesting observations. :)

To Fodder : No, I'd be VERY careful before releasing and application with a direct interrupt hook! I am working on my pc alone, where I can experiment at my own risk. :)

My goal is now to translate this "famous" C example into asm. It seemed to me a relatively easy task but it is not so.

I've translated the DriverEntry function, with HalGetInterruptVector and IoConnectInterrupt, without having syntax problems...the driver is correctly compiled.

But I get a KeBugException (another page fault => KERNEL MODE EXCEPTION NOT HANDLED) at the IoCreateDevice function.

IN ULONG DeviceExtensionSize,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,

This is the disassembly of the working C driver:

000103A9: 8B 5D 08 mov ebx,dword ptr
000103AC: 8D 45 FC lea eax,
000103AF: 50 push eax
000103B0: 6A 01 push 1
000103B2: 6A 00 push 0
000103B4: 8D 45 F0 lea eax,
000103B7: 6A 22 push 22h
000103B9: 50 push eax
000103BA: 6A 10 push 10h
000103BC: 53 push ebx
000103BD: FF 15 84 04 01 00 call dword ptr [__imp__IoCreateDevice@28]
000103C3: 85 C0 test eax,eax

And this is my function (working in other cases) :

lea ecx, DeviceObject;
mov esi, DriverObject;
lea edi, DeviceNameUnicode

push ecx
push 1
push 0
push 022h; DeviceType FILE_DEVICE_PARALLEL_PORT is indeed = 016h ??
push edi
push 010h; DeviceExtensionSize
push esi
call IoCreateDevice

test eax, eax

This funtion works in other cases when DeviceExtensionSize=0 and DeviceType = 8010h.
Do you know if I have to change those values? I got them directly from the disassembled code (10h & 22h).

Posted on 2003-12-11 04:46:24 by fooCoder

You can block that BSOD by handing both debug exceptions:

1 #DB Debug Fault/TrapNo Any code or data reference or the INT 1 instruction.


3 #BP Breakpoint Trap No INT 3 instruction.
Posted on 2003-12-11 13:28:18 by mrgone