Search This Blog

Friday, September 16, 2011

Using APCs to inject your DLL

A few blogs ago I discussed remote threads on Windows 7. This topic was targeted towards the goal of injecting one's DLL into processes.

There are several ways of injecting your code inside any other processes:

  • Via SetWindowsHookEx
  • By using the APP_Init key in the registry
  • With CreateRemoteThread & NtCreateThreadEx
  • You could also do it from a driver by replacing the main thread's entry point with your shell code.
Those techniques are increasingly difficult to put in practice. They do work but sometimes, it's not enough or it may not work on some OS. For instance CreateRemoteThread only works half of the time on Windows 7 because of the different sessions used by applications and services.

Hooking NtCreateThread in a driver and inject your DLL that way won't work on Windows 7 either since NtCreateThread is no longer used by NtCreateProcess. Not to mention that some of that stuff won't work on Windows 64. For instance, NtCreateThreadEx() doesn't use the same structure in 32bit and 64bit. It took me half a day to figure out the proper way of injecting a DLL on Win64.

That time could have been saved if I had used a proper/cleaner way of injecting my DLL.

Enters APC.

In this article you can see how APCs are used in Windows (2000, XP, 7) so I won't go over it again.

The basic idea is that in order to inject our DLL, we will use an APC and queue it for the process. Quite obviously, this has to be done in a driver.

When the target program starts, our driver can be notified via a callback (See PsSetCreateProcessNotifyRoutine) and it can also be notified whenever a module loads (see PsSetLoadImageNotifyRoutine ).

As the module loading callback is called, we can wait for NTDLL.DLL to be loaded since it is the first DLL that will be automatically loaded for every process on the system.

Another reason to wait for NTDLL to be loaded is because we can parse the PE headers and find out the user mode address for LdrLoadDLL. You could do a GetProcAddress(NULL, "LoadLibraryA") and pass it down to your driver but with ASLR this could potentially cause problems.

So, in the callback, we wait for the NTDLL to load and then we obtain the address of LdrLoadDLL.

Here's the code to find out the address of a function in a given DLL.

/**
  * This function is like GetProcAddress()
 * ImageBase is the address of the mapped DLL
  * ImageSize is the size of the DLL
  * FunctionName is the API we are looking for
 *
  * Before looking through the PE headers, we need to map the DLL in memory because
  * it may not be fully mapped. By creating a MDL, we can take care of this.
  */

PVOID GetProcAddress(PVOID ImageBase, DWORD ImageSize, const char* FunctionName)
{    PVOID pFunc = NULL;
    PIMAGE_DOS_HEADER DosHeader = NULL;
    PIMAGE_NT_HEADERS NtHeader = NULL;
    PIMAGE_EXPORT_DIRECTORY pIed = NULL;
    PIMAGE_DATA_DIRECTORY ExportDataDir;
    PIMAGE_EXPORT_DIRECTORY ExportDirectory;
    PVOID LoadAddress = NULL;
    PULONG FunctionRvaArray;
    PUSHORT OrdinalsArray;
    PULONG NamesArray;
    ULONG Index;
    PMDL vMem = NULL;

    __try {
        vMem = IoAllocateMdl(ImageBase, ImageSize, FALSE, FALSE, NULL);
        if (vMem != NULL)
        {
            ULONG ByteCount = 0;
            LoadAddress =  MmGetMdlVirtualAddress(vMem);
            DosHeader = (PIMAGE_DOS_HEADER) LoadAddress;
            ByteCount = MmGetMdlByteCount(vMem);

            MmProbeAndLockPages(vMem, UserMode,  IoReadAccess);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        if (vMem != NULL)
            IoFreeMdl(vMem);

        DbgPrint("Unable to read memory");
        return NULL;
    }

    //
    // Peek into PE image to obtain exports.
    //
    NtHeader = ( PIMAGE_NT_HEADERS ) PtrFromRva( DosHeader, DosHeader->e_lfanew );
    if( IMAGE_NT_SIGNATURE != NtHeader->Signature )
    {
        //
        // Unrecognized image format.
        //
        return NULL;
    }

    ExportDataDir = &NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    ExportDirectory = ( PIMAGE_EXPORT_DIRECTORY ) PtrFromRva(LoadAddress, ExportDataDir->VirtualAddress);

    if ( ExportDirectory->AddressOfNames == 0 ||
         ExportDirectory->AddressOfFunctions == 0 ||
         ExportDirectory->AddressOfNameOrdinals == 0 )
    {
        //
        // This module does not have any exports.
        //
        return NULL;
    }

    FunctionRvaArray = ( PULONG ) PtrFromRva(LoadAddress, ExportDirectory->AddressOfFunctions);
    OrdinalsArray = ( PUSHORT ) PtrFromRva(LoadAddress, ExportDirectory->AddressOfNameOrdinals);
    NamesArray = ( PULONG) PtrFromRva(LoadAddress, ExportDirectory->AddressOfNames);

    for ( Index = 0; Index < ExportDirectory->NumberOfNames; Index++ )
    {
        //
        // Get corresponding export ordinal.
        //
        USHORT Ordinal = ( USHORT ) OrdinalsArray[ Index ] + ( USHORT ) ExportDirectory->Base;

        //
        // Get corresponding function RVA.
        //
        ULONG FuncRva = FunctionRvaArray[ Ordinal - ExportDirectory->Base ];

        if ( FuncRva >= ExportDataDir->VirtualAddress && 
             FuncRva < ExportDataDir->VirtualAddress + ExportDataDir->Size )
        {
            //
            // It is a forwarder.
            //
        }
        else
        {
            //
            // It is an export.
            //
            ULONG FunctionNamePointer = (ULONG) LoadAddress + NamesArray[Index];
            const char* pszName = (const char*) FunctionNamePointer;
            if (strcmp(pszName, FunctionName) == 0)
            {
                pFunc = (PVOID) ((ULONG) LoadAddress + FuncRva);
                break;
            }
        }
    }

    __try {
        if (vMem != NULL)
        {
            MmUnlockPages(vMem);                            
            IoFreeMdl(vMem);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        DbgPrint("Unable to read memory");
    }

    return pFunc;
}

By now, your callback knows that NTDLL has been loaded and with the help of the code above, we obtained the address for LdrLoadDLL.

Notice in the function above that the begining consists of mapping the DLL into memory. The DLL is loaded but may or may not be fully mapped in memory. Failure to do it will result into not finding the API we want since the PE headers are not in memory yet.

Now, we need to prepare for the APC.
First, we need a dummy Kernel APC routine since it is required by the queuing call.

/**
  * This is dummy Kernel APC Routine
  *
  */
VOID KernelApcRoutine (
    IN PKAPC Apc,
    IN PKNORMAL_ROUTINE *NormalRoutine,
    IN PVOID *NormalContext,
    IN PVOID *SystemArgument1,
    IN PVOID *SystemArgument2)
{
    UNREFERENCED_PARAMETER( SystemArgument1 );
    UNREFERENCED_PARAMETER( SystemArgument2 );

    DbgPrint("User APC is being delivered - Apc: %p\n", Apc);
    if (PsIsThreadTerminating( PsGetCurrentThread() ))
    {
        *NormalRoutine = NULL;
    }

    ExFreePoolWithTag(Apc, DIRECT_KERNEL_ALLOC_TAG);
}


That code will be called when the APC is processed and it will delete the memory allocated for the APC object.

Next, we need a function to create the APC and queue it. First our new function needs to allocate some memory in the target process. Since the callback is called in the context of the target, we can simply use NtCurrentProcess() to specify what process the memory will be allocated into.

...
    ZwAllocateVirtualMemory( NtCurrentProcess(),
                                               &context,
                                               0,
                                               &contextSize,
                                               MEM_COMMIT, PAGE_READWRITE);

...

The {context} is a structure that you will define. You can store the address of LdrLoadDLL inside of it as well as the name of the DLL you want to inject.

Then you need to allocate memory for the APC object, which you can do using:

    ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), 'tag');

Now, you must initialize the APC, which you do with the following call:

...
     KeInitializeApc(apc, KeGetCurrentThread(),
                              OriginalApcEnvironment,
                              (PKERNEL_ROUTINE) KernelApcRoutine,
                              NULL,
                              InjectionShellCode,
                              UserMode, context);
...

The context is the one that we just allocated and which will be passed as a parameter to the user mode routine.

For the actual routine, you can go two ways. You can create some shell code in assembly and insert the opcodes inside some array of memory, or you can create a function in your own code. If you write a function inside your source code, you will have to make sure that it does not call any windows APIs.

The only thing your function should do is to create a UNICODE string manually (meaning, no call to RtlUnicodeStringInit() ).

Once the APC is initialized, you can queue it using:

...
     KeInsertQueueApc( apc, NULL, NULL, 0);
...

Here's how LdrLoadDLL is called:

...
     pfnLdrLoadDll(NULL,           // No name
                             0,                
                             &pDLL,         // full path here as a unicode string
                             &handle);
...

Basically, what will happen is the following:

  1. Process is created
  2. Module Load Callback is called
  3. Callback check if the module is NTDLL.DLL
  4. Callback retrieve the address of LdrLoadDll() from NTDLL.DLL
  5. Allocate the APC user mode routine context
  6. Allocate memory in the target process to hold the shellcode
  7. Allocate memory for the APC
  8. Initialize the APC (user mode routine points to the shell code)
  9. Queue the APC
  10. The rest of the modules get loaded and your APC gets processed
  11. The user mode routine, loads the DLL using LdrLoadDll.
  12. Main thread is created
  13. Process starts
Hopefully, all this should get you going.

Happy coding!

Wednesday, August 24, 2011

Laguna Seca Day

It was a bit of a surprise but the folks from HOD managed to get a date at Laguna Seca. I received the email about 3 weeks ago and a few hours later, I had bought my spot. Good thing I didn't wait since they pretty much sold out that date within 48 hours.

There were a bunch of people coming from the Apple Auto Club but I haven't met any. I just noticed that one dude who has a blue Ferrari with the Apple logo on the doors.

Once again, I took my sister and since it was her last day in California (before flying back to France) it became somewhat of a farewell gift.

We loaded up the Lotus on the newly painted trailer - I wanted to put some bed liner on it to make it more resistant to bad weather and bird poop - and we put the tools, helmets and my gloves in the Bee. We went to bed fairly early on Sunday night since we had to leave by 6:30 on Monday.

Monday the 23rd, 6:30AM: We basically left by 6:27AM and went down highway 1 towards Monterey. The good thing about Laguna Seca is that it's less than one hour away from home, which is very appreciable.

We got to the track by 7:30AM and proceeded to unload all the stuff after looking for a place to park. There had been an event over the week-end and there were a ton of 18 wheelers in the paddocks. After finding a decent looking spot, I parked the Bee, unloaded the Lotus and we went for a recon mission. The goal of our mission was to spot the bathrooms and also the gift shop.

9:00AM: Finally, it's time for the C group to get on the track. For some odd reason, our group is always the group that goes first and dries the track for the others, grrrrr....

The track was cold and so were my tires so, I did that session in slow motion. I think that I got lapped during my last lap by the guy you went out first. Unless, he went in the hot pits to wait for the track to be less congested. According to the dude in the Cayman parked next to me, people were slooooow. He told me that perhaps he should follow me. I answered that perhaps he shouldn't because I always start last. I usually pass a few people during a session but once that's done, the track is super clear in front of me. I can't catch the faster guys or guys that are as fast or a tad slower than me since the gap can be of several seconds between cars when we start out.

The second session was a lot better and I was feeling a bit more confident. The car felt pretty good and the tires warmed up a bit quicker too. My sister was impressed by the Corkscrew, which is understandable since it's a very unique turn.

The sun finally came up around 11AM and that was a welcome event since we were kind of cold in the paddocks. Our 3rd session was right before lunch, around noon. At that point the sky was all blue and the track was nice and warm. My lap times seemed a lot faster than in the morning although I wouldn't be able to tell since I don't have any lap timer.

I was feeling pretty good in turn 4 and I could feel that the car was telling me that it had a ton of grip as I was going across the apex. I also started to push harder in the straight/turn 1 and I would keep on the gas until a few yards of turn 2, which is a double apex turn (first time I tried to take that turn that way but I liked it).

I had to let an Atom by coming off turn 11 and my sister was amazed by the fact that the little car would let me in its dust going up the start/finish line. For a couple laps, I would catch up by turn 2 and be on the Atom's ass until T11 and finally, the driver let me pass her going up between T5 and T6. Never saw her again :)

In retrospect, I think that Sears Point is still better suited for the Lotus than Laguna Seca is but Laguna is still way better than Thunderhill, which benefits high HP cars.

All in all, this was a very good day. The only blemish is that my exhaust (which I had to revert to stock) got so hot that a small part of the rear shell started to bubble up, screwing up the paint in the process. It's a little difficult to see but I know it's there :(

Saturday, August 13, 2011

Round and round around Infineon Raceway (Sears Point)

Monday 8th.

Finally got to go to Sears Point. Last time I was there was in 2005 for the AFM races, in which I crashed during the Formula IV race, in turn 2. Obviously, I know the layout of the track but, the lines are quite different in a car and on a motorcycle.

I loaded up the truck on Sunday and we left (my sister and I) on Monday at about 5:25AM (yikes!).
As we arrived in Oakland, I missed the 580 West exit and I ended up being forced to take the bay bridge. I turned around on Treasure Island, which was a tad hairy since there is only one road and I had to make a U-turn in a very tight spot with the following setup:


Definitely interesting (not counting the wasted $14 for the extra toll).

We got to the track at 7:50 and I went directly to the registration which was in the garage #1. I had to get a number for some reason, which I assumed was because of the sound limit, and I picked #13. Since there was no more #13, I got 43 and April (a lady member of the staff there) who's an awesome lady by the way, cut the 4 to make it a 1 :)

It was foggy to begin the day but it was more like low clouds. No humidity, but the track was cold and so were my tires. The car fish tailed in turn 4 a little, although it was nothing too serious, my sister did tell me about it. Finally, around 11AM and right before my 3rd session, the sky cleared up and the weather was just glorious!

Coming off turn 11.
We went 3 more sessions (there are 4 groups, so you do 3 sessions in the morning and 2 in the afternoon in the C group) and we reached the end of our day at about 4:30PM.

It took me a while to figure out the turns and I made a little list of spots where I sucked:

  • Turn 4. I just can't hit it properly even though I know it's a late apex.
  • The end of the esses, where I slow down too much.
  • I'm a chicken in turn 9
  • And so am I in turn 10
Overall, it turned out to be an great day and I hope that I can go there again sometimes soon.

Monday, June 27, 2011

Installing a Fuel Surge Tank on an Elise

Well, it's a pain if, like me, you're not mechanically inclined. Although, installing in itself isn't really the problem, it's more about removing stuff that's tough.

The FST I chose is made by Radium (Radium is actually an element in the periodic table), and it looks like this:

Very pretty design.
Anyway, it comes with a bunch more stuff, likes hoses and wires for controlling the pump that's inside.

What does a FST do will you ask? In short, it's to prevent fuel starvation. The problem is that the fuel tank on the Elise/Exige as a crappy design in the sense that the fuel pickup is on the left hand side so, when turning left, hard, if the tank isn't 3/4 full, the fuel shifts on the right side and therefore, the fuel pump sucks air instead of gasoline, which as you might expect really isn't that good because air + air doesn't really detonate that well.

So, the FST basically acts as a 'proxy' so to speak. It contains a reserve of about 1Qt and when the stock fuel pump is sucking air, the FST can still provide gas to the engine for a short amount of time.

My troubles started at step 1...
I had to disconnect the stock fuel line so, I bought a tool at the auto shop. Problem is that I bought somewhat of a generic tool and of course, the Lotus won't accept that. Ended up going online and buy the proper tool:
Now, that worked a whole lot better. This tiny little thing is designed for Toyota and some other brand I can't recall. $13 and it works very well. Although, I had to unscrew the fuel rail because the line is too close to the engine block and the wider part of the tool was too big and would not fit. Ugh.

Step 2, I had to take out the filler hose. That took me a couple of hours.
I forgot to mention that I had to get the AC removed in order to be able to install the FST. I didn't use it and it's pretty crappy anyway, so I got rid of it and its 50 some lbs.

Back to the darn hose. Taking it out took a while because it was hard to push it down towards the tank, just to be able to bend it enough to remove it from the filler neck (where you insert the fuel nozzle). I lost a nut in the overflow hose too. Ugh (and it never came out, R.I.P).

Finally, I got it out and I had to cut about 1" off it to insert some gizmo that does something that escapes me. Putting it back in was a bit easier as I put a tiny bit of grease on the inside perimeter of the hose, so that it would slide onto the aluminum pipes easily. Then, I had to tighten the metal collars and that was no sinecure either. Ugh.

Step 3. Started in the morning around 11AM. I connected one hose that goes to the aforementioned gizmo. Wasn't too bad. Then I had to remove the under panels (diffuser and under tray) because that's the only way to connect the power cable that will feed the FST. Plugged the cable in the FST and re-routed it until the little black box, next to the ECU.

It was a pain to open the plastic box but I got it figured out and I connected the cable. I also screwed the fuse assembly and the other little box on the FST holder, which was simple enough.

I had then to remove the seats, the black plastic thing behind them and the foam. Annoying but it didn't take all that long and I took advantage of that to get rid of the grills that cover the speakers (they vibrate, which is irritating).

Connected the white jumpers and put the remaining connector through the little hole.
I had then to cut the fuel line, which was surprisingly easy and then connect it to the hose that I ran by the firewall, on the back of the car. When I pulled that hose, I also pulled the other hose that connects to the fuel rail, and then I connected them both.

I checked for loose stuff and didn't see any, so I primed the FST and started the car. Initially, everything looked fine but when I looked at the FST, one fitting was not tight enough and gas was leaking out. I tightened it a bit and when it seemed alright, I started the car again.

So far, nothing to report. I still have to put everything back (foam, plastic panel, seats, wheels, etc) but that's relatively easy. It will have taken me about 4-5 hours to do everything basically, and I work very slowly.

I'll run some more leak tests during the week and hopefully, I'll be good to go by next week-end!

Saturday, May 28, 2011

First 'Oops' at Thunderhill

Went to Thunderhill on Thursday the 26th.

I had a lot of things to put my mind on that day though, because I haven't gone to the track for a while and the last time, it was raining and I was unable to test stuff.

Since the last dry track day, I have added the following:

  • 2 way Nitron Suspensions (2 way means compression and rebound settings)
  • Canards (little air flow fins on the front of the car)
  • 5 elements diffuser
  • Race mounts
  • G-Pan (baffled oil pan)

Obviously, the most important additions were the suspensions, canards and race mounts as they all contribute to a different ride behavior.

I added a couple clicks to the compression and rebound damping as I had them set up half way for street driving. Being on the track also means that one can use a more aggressive setup.

That seemed to work pretty well and I don't have anything to complain about regarding the handling. With the new suspensions, the ride height is also lower (118mm/123mm compared to the stock settings that were at 130mm/140mm or so).

The motor mounts are a very nice addition as they allow for crispier shifts and they also help a bit with the handling and the motor doesn't move as much under load.

Anyway, the first session was interesting as my tires were a little too inflated and cold. The track was a little slick but that's always like this and the C group always goes first for some reason. In the first lap, some lady spun her Ferrari in Turn 14 because her 'pit crew' forgot to put the gas cap on and fuel spewed all over and onto her right side tires, sending her spinning.

Where it got very interesting was in my second session. I had adjusted my tire pressure a bit and it felt better. The track was warmer, which helped as well. I was trying to work on Turn 5 and Turn 9 because they are fairly similar, being both blind corners that go up and come down on the right side. Turn 5 is not much of a corner but most people steer too much on the left going up, thus leaving 3 or 4 feet worth of track on the right side.

In my 3rd or 4th lap (I can't quite remember which was it was), I came out of Turn 3 and was going into 4 at a pretty good speed. I usually turn in earlier than a lot of people, skim the curb on the inside and track out (turns out that I wasn't tracking out wide enough, which I worked on in my last session).

This time around though, I cut it a tad short and I ended up hitting the curb a bit too much while turning left. The curb is quite high in that spot and that unsettled the car. Pretty much at the apex, the car started over steering and I initially thought that perhaps I could save it and I counter steered. It corrected itself but kicked back and over steered on the left side a lot quicker. By then I realized that I lost it and went both feet in (brakes + clutch).

The car ended up spinning to almost 180 degree but not quite. As I stopped, the car stalled because I released the clutch without thinking about it, killing the engine. A couple of cars went by as I was trying to start the car again. It resisted a bit and finally cranked over. I backed up a couple feet, waited for another car to go by and turned around to finish my lap.

Of course, by Turn 11 I got black flagged, which means that I would have to pit in. I was expecting it anyway so, it wasn't much of a surprise. I stopped in the hot pits and the group leader came to talk to me. He asked what happened and I told him that I was a little too happy going into 4 and I bit the curb, which punted me and caused the spin.

He checked the car quickly, but since I didn't go off track, there was not mud or anything nasty. The car was fine and so was I. It's easy to get rattled but I found myself pretty calm, considering the circumstances. I was just happy that I kept it on the track and I knew that even though it was 100% my fault, it wasn't a horrible mistake but rather a bad decision of cutting it to close to the curb. Better stay OFF the curb really.

The rest of the day went pretty smoothly and the group leader came along in my last session. He was quite happy with Turn 5 and 9, which always cause me problems and gave me some advices regarding Turn 4 and 8. Turn 8 in particular as I would turn too late, sacrificing too much entry speed and therefore, killing my exit. By turning earlier (a few feet) I was able to come out at about 100mph, compared to the 90mph or so that I would see before. Good stuff.

I still need to work on Turn 1 because I feel that I could carry a lot more speed in there. I just chicken out on the entry and I come out going into the short straight a some 85mph. I know that I can exit the turn doing 100mph easily but for some reason, I haven't been able to do it for a while now.

Wednesday, March 30, 2011

Mini-Filter messaging

Recently I have been working on some OS monitoring and, as a mean to communicate with my user mode application, I ended up resorting to the Mini-filter Messaging.

What is it?
Well, when you write a mini-filter (like a file mini filter) Windows provides you with a set of API that can be used to send messages to an user mode application. The API are as follows:

On the driver side:
And on the user mode side:

With all of those, you should be able to make your own communication channel.

First, on the driver side, you will need to create the communication port with FltCreateCommunication port. You can use a default security descriptor to pass as a parameter because it's a little easier and it does not have to be super secure either.

To create the descriptor, just use something like this:

PSECURITY_DESCRIPTOR sd;
NTSTATUS status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);

Then, pick a name for your port like (L\\MyCommunicationPort) and create the communication port.
There are some parameters passed to the API that are callback functions. You must use the pfnConnect and the pfnDisconnect callback (meaning that setting them to NULL would be useless) but you may leave pfnMessageNotify empty.

Once the user mode application will connect to the port, the pfnConnect function will be called and you will be able to start sending out messages.

The 'Connect' callback will provide you with a [inClientPort] that you must save (in a global variable for example) as you will need it to send messages.

Now, to send a message, that's easy, just use FltSendMessage().
By looking at the documentation, you will notice that it requires a PFLT_FILTER parameter that is your handle that came from the FltRegisterFilter() call that you made in your DriverEntry() function.

The next argument is the clientPort that you have saved earlier from the pfnConnect callback (you did, right?)

You need to provide a buffer to send, as well as its length. Personally, I keep the buffer size below 1KB because I don't know what the maximum size is and 1K is most of what I would need anyway.

You may provide a reply buffer as well, in case you want the user mode application to reply to your messages. I use it to make sure that the messages were delivered properly.

The timeout is a little tricky to figure out because the length is a multiple of 100 nanoseconds and it can be absolute or relative. So, if you want to set a 1 second timeout, you would need to enter a number such as this: -10000000. To make it easier I wrote a couple of macros:

#define K_NANOSECOND  10000000
#define K_MILLISECOND  10000

#define K_RELATIVE_WAIT_S(a)  -((LONGLONG) a * K_SECOND)
#define K_RELATIVE_WAIT_MS(a)  -((LONGLONG) a * K_MILLISECOND)

The code to use would look like: timeout.QuadPart = K_RELATIVE_WAIT_S(1) to wait for 1 second.

There is a very good example of the whole messaging system that comes with the DDK, which is under <drive:>\WINDDK\<release version>\src\filesys\minifilter\scanner\

What I should point out are a few little quirks that are not that trivial to figure out.
First, after you connect to the port, you will need to 'prime' the messaging by doing a few FilterGetMessage() calls. You will create a buffer per call and those buffers will be re-used during the life time of you application.

The buffer will be filled with data when GetQueuedCompletionStatus() returns in your thread(s). it will be followed by a FilterReplyMessage() and a FilterGetMessage().

One thing about the FilterReplyMessage() call though, you will pass your structure which must contain a FILTER_REPLY_HEADER structure. Before sending the reply, you must fill up the fields contained in there like this:

reply.ReplyHeader.Status = 0;
reply.ReplyHeader.MessageId = mystruct.fltHeader.MessageId;

the mystuct.fltHeader is the FILTER_MESSAGE_HEADER that must be included in the structure used to receive message like this:

typedef struct myStruct {
  FILTER_MESSAGE_HEADER fltHeader;
  MY_DATA  data;
} MY_STRUCT, *LPMY_STRUCT;

I would advise you to reply to the kernel messages in order to verify that the delivery went right. Otherwise, as I experienced it, some messages may get lost and some may get duplicated.

Happy messaging!