阅读:6314回复:0
找到一篇关于APC的文章,各位试一试(刚找到,我没试)
The issue of notifying user-mode thread from a kernel-mode driver, or calling a user-mode routine from same, is very often discussed here. The usual solutions offered are: 1) a dedicated thread sending an IRP to the driver which the driver leaves uncompleted and completes when it needs to communicate information to the thread; and 2) signaling an event which can be accessed in both user and kernel mode, with user-mode thread waiting on it. Both these approaches have their drawbacks; in particular, they need a dedicated thread waiting, and they're relatively slow - sometimes you _know_ you're in the right context, and you just need to tell something to the user-mode thread or pass some information to it as soon as possible.
I've tried to find other ways of doing the same, being driven mostly by curiosity, spirit of exploration and stubborness. I've found two other mechanisms by which it is possible to make a thread call some specific user-mode function: 1. User-mode APC (Asynchronous Procedure Call). The whole issue of APCs is quite undocumented in the DDK. Actually, more informatiion about APCs can be found in Win32 SDK help than in NT DDK help! That's because the whole mechanism of completion routine-based I/O routines (like ReadFileEx(), etc.) is quite transparently based on APC, and the SDK help says a few basic things about APCs. They're also discussed briefly in the classic Helen Custer's "Inside Windows NT". The main problem with this approach is that according to the NT design principles, a thread can receive a user-mode APC _only_ if it declares itself alertable: either by waiting on a synchronization object with alertable flag set on, or explicitly calling a certain function in ntdll.dll to check whether it should be alerted. Since we _don't_ want our thread to wait on anything (we may just as well use named event then) we're stuck. In the code I give below, I overcome this problem in an undocumented and somewhat dirty way; I'm still searching for more "clean" ways to do it. When a user-mode APC is passed to a thread in this way, its routine be called next time the thread runs in user-mode. If the thread already runs in user-mode, the routine won't be invoked immediately, however; it'll be called next time the thread _returns_ to user-mode from some kernel-mode service. Usually that happens almost immediately since an active thread is calling kernel-mode services all the time; in the worst case it'll happen after the next clock tick, when clock tick procedure returns to user-mode. The user-mode APC will not, however, interrupt any kernel-mode activity; i.e. if the thread is waiting on an object, it won't be woken up; when it wakes up by itself, however, it will receive the pending APC immediately. 2. KeUserModeCallback(). This is an undocumented function used by Win32 subsystem running in the kernel in NT 4.0 (win32k.sys). It's used when the subsystem either needs to know some information stored in user-mode (for example, in user32.dll's data), or needs to call a thread's window procedure (for example, when you move your mouse, the subsystem eventually receives notification of it in kernel mode, and it calls your window procedure with WM_MOUSEMOVE message using this function). A catch here is that this mechanism is predefined to call only some specified functions: one of parameters to KeUserModeCallback() is an index to a special table from which later in user mode an address to call is fetched. Still it's possible to exploit this mechanism to call, _very_ quickly (much quicker than the APC, named event or pending IRP mechanisms allow) an arbitrary routine of your code. The catch here is that you _must_ be in your thread's context for this to work; in this respect user-mode APC is better since you can freely send it to any thread in the system. Below is sample code that will send user-mode APC to the current thread, calling an arbitrary user-mode routine and passing it three arbitrary parameters. If you want to send an APC to your thread from arbitrary context, you should capture its KTHREAD pointer by calling KeGetCurrentThread() in _its_ context (say, when receiving a custom control request from it and being top-level), and use it in a call to KeInitializeApc later at any time. Unfortunately, it will only work on x86 architecture due to one machine-dependent line which modifies directly the KTHREAD structure. It should, however, work on free/checked builds and SMP/uniprocessor machines alike. It's taken right out of an article I'm completing which discusses in detail both kernel-mode and user-mode APCs; I'm still uncertain whether I should simply put it on the web or try to offer it to some paper magazines. If you're interested, please try out this code and report to me whether it works/ doesn't work for you and what happens if it doesn't. Note that I of course disclaim any responsibility; your system might very well crash/bugcheck, though I've tested this code for some time now and it's always been working fine for me. /* The APC structure is defined in ntddk.h */ /* this is KERNEL_ROUTINE for our APC; in particular, it gets called when the APC is being delivered. Usually one of predefined useful routines in the kernel are used for this purpose, but we can't use any of them as none of them are exported. */ void MyRoutine(struct _KAPC *Apc, PKNORMAL_ROUTINE norm_routine, void *context, void *SysArg1, void *SysArg2) { ExFreePool(Apc); return; } /* pointer to the APC we will create */ static struct _KAPC *apc; /* KeInitializeApc() and KeInsertQueueApc() are the two functions needed to send an APC; they're both exported but not prototyped in the DDK, so we prototype them here. */ void KeInitializeApc(struct _KAPC *Apc, PKTHREAD thread, unsigned char state_index, PKKERNEL_ROUTINE ker_routine, PKRUNDOWN_ROUTINE rd_routine, PKNORMAL_ROUTINE nor_routine, unsigned char mode, void *context); void KeInsertQueueApc(struct _KAPC *APC, void *SysArg1, void *SysArg2, unsigned char arg4); /* call this function when you need to send a user-mode APC to the current thread. addr must be linear address of your user-mode function to call: void MyApcRoutine(ULONG arg1, ULONG arg2, ULONG arg3); ... SendAddrToTheDriverUsingIoctl((ULONG)MyApcRoutine); you should send it to the driver using your custom IOCTL. arg1, arg2, arg3 are arbitrary ulong's which are passed to the function residing at addr; this function should be prototyped as receiving three parameters and returning void. */ void SendAPC(ULONG addr, ULONG arg1, ULONG arg2, ULONG arg3) { PKTHREAD thread=KeGetCurrentThread(); /* this is self-explanatory */ apc=ExAllocatePool(NonPagedPool, sizeof(struct _KAPC)); /* Initialize the user-mode APC */ KeInitializeApc(apc, thread, 0, (PKKERNEL_ROUTINE)&MyRoutine, 0, (PKNORMAL_ROUTINE)addr, 1, (PVOID)arg1); /* Insert it to the queue of the target thread */ KeInsertQueueApc(apc, (PVOID)arg2, (PVOID)arg3, 0); /* Mark the current thread as alertable to force it to deliver the APC on the next return to the user-mode. NOTE: severely undocumented code here! */ *((unsigned char *)thread+0x4a)=1; } 请各位大虾测试。 |
|
最新喜欢:flyfox |