Terminating a process as soon as it starts, from KM

Hi guys,

I want to terminate a specific process as fast as possible after it has started. The reason I want this is simple: It doesn’t seem to be a trivial thing, so I want to know how to deal with the problems involved and I think this is a good way for me to learn.

I want to base the decision to terminate on the path of the executable, so I register a process and an image notification routine. In the process notification routine I add the PID in a small temporary list which I look up in the image notification routine.
If there is a match I remove the PID from the tmp list and I have the FullImageName (I’m testing on an XP machine, so I can’t use the ‘Ex’ variant of the process notification routine).

Now in the image notification routine I am able to open the process, but I can not terminate it because while I’m in the image notification routine the thread is holding a lock and terminating the process in the notification routine wouldn’t free this lock as I’m in the context of the being-terminated process.

So I create a workitem (IoQueueWorkItem) and hand over the process handle. The workitem terminates the process and closes the process handle.

This solution seems to work, but I’d like to know from you guys is if my reasoning so far is correct, and if there is a better way.
Creating work items is kind of a hassle, storing the IO_WORKITEM somewhere… and what if another process is created that I also want to terminate before the first IO_WORKITEM is completed? Right now the WORK_ITEM_ROUTINE sets the PIO_WORKITEM to NULL, and before allocating a new WORK_ITEM I check if it is NULL.
Should I create a small list of PIO_WORK_ITEM’s to deal with this, so that there always is a free PIO_WORKITEM to allocate?

I thought about using DPC’s, but ZwTerminateProcess needs to be called at PASSIVE_LEVEL.

Might you be able to have a user-space app (service, whatever) that you notify from your driver to appropriately womp the process?

----- Original Message -----
From: “chris schade”
To: “Windows System Software Devs Interest List”
Sent: Thursday, August 26, 2010 11:07:55 AM
Subject: [ntdev] Terminating a process as soon as it starts, from KM

Hi guys,

I want to terminate a specific process as fast as possible after it has started. The reason I want this is simple: It doesn’t seem to be a trivial thing, so I want to know how to deal with the problems involved and I think this is a good way for me to learn.

I want to base the decision to terminate on the path of the executable, so I register a process and an image notification routine. In the process notification routine I add the PID in a small temporary list which I look up in the image notification routine.
If there is a match I remove the PID from the tmp list and I have the FullImageName (I’m testing on an XP machine, so I can’t use the ‘Ex’ variant of the process notification routine).

Now in the image notification routine I am able to open the process, but I can not terminate it because while I’m in the image notification routine the thread is holding a lock and terminating the process in the notification routine wouldn’t free this lock as I’m in the context of the being-terminated process.

So I create a workitem (IoQueueWorkItem) and hand over the process handle. The workitem terminates the process and closes the process handle.

This solution seems to work, but I’d like to know from you guys is if my reasoning so far is correct, and if there is a better way.
Creating work items is kind of a hassle, storing the IO_WORKITEM somewhere… and what if another process is created that I also want to terminate before the first IO_WORKITEM is completed? Right now the WORK_ITEM_ROUTINE sets the PIO_WORKITEM to NULL, and before allocating a new WORK_ITEM I check if it is NULL.
Should I create a small list of PIO_WORK_ITEM’s to deal with this, so that there always is a free PIO_WORKITEM to allocate?

I thought about using DPC’s, but ZwTerminateProcess needs to be called at PASSIVE_LEVEL.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

> I can not terminate it because while I’m in the image notification routine the thread is holding

a lock and terminating the process in the notification routine wouldn’t free this lock as I’m in
the context of the being-terminated process.

FYI, a thread cannot get terminated until a return to the UM is made - otherwise you would end up with unreleased locks, corrupt memory, etc. Therefore, by the time of termination the lock will be released anyway…

Anton Bassov

Antov,

I get a bugcheck when I call ZwTerminateProcess from the image notification routine(KERNEL_APC_PENDING_DURING_EXIT 0x20).
The ZwTerminateProcess documatation states that when calling ZwTerminateProcess on the current process, the call will not return and the stack will not unwind.

I think that contradicts your statment, unless I read it wrong in which case I apologize :).

John,

Yes I could do this in usermode, but my point of doing this in KM is pure self-education…
So the goal of this self-given homework is to learn how to deal with situations where you can’t call a function ‘in the context of the function’s target process’.

Well part of the problem is that there is no guarantee that the create
process callback will be running at a time that the process handle is
valid, so your ZwTerminateProcess is not guaranteed to work.

Don Burn (MVP, Windows DKD)
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

xxxxx@gmail.com” wrote in message
news:xxxxx@ntdev:

> Antov,
>
> I get a bugcheck when I call ZwTerminateProcess from the image notification routine(KERNEL_APC_PENDING_DURING_EXIT 0x20).
> The ZwTerminateProcess documatation states that when calling ZwTerminateProcess on the current process, the call will not return and the stack will not unwind.
>
> I think that contradicts your statment, unless I read it wrong in which case I apologize :).
>
> John,
>
> Yes I could do this in usermode, but my point of doing this in KM is pure self-education…
> So the goal of this self-given homework is to learn how to deal with situations where you can’t call a function ‘in the context of the function’s target process’.

But I’m in the image notification routine(I only use the process notification routine to add the PID to a list that I look up in the image notification routine), I thought at that time the process handle is guaranteed to be valid.

If it’s not guaranteed, can I loop until the handle is valid?

Antov,

I guess what you mean is that if ZwTerminateProcess is called from another context than the being-terminated process it has to return to UM, so the notification routine lock is freed, even if the notification routine hasnt finished yet at the time my IO_WORKITEM is running.
Which is absolutely true… I have another call in the notification routine, which is returning ‘STATUS_THREAD_IS_TERMINATING’ so.

No, you can’t loop since the caller of the image notify is the function
that will finalize the handle. It is unfortunate that Microsoft did not
talk to the community when we wanted this feature, what they gave us is
not anywhere as usable as it should be.

Don Burn (MVP, Windows DKD)
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

xxxxx@gmail.com” wrote in message
news:xxxxx@ntdev:

> But I’m in the image notification routine(I only use the process notification routine to add the PID to a list that I look up in the image notification routine), I thought at that time the process handle is guaranteed to be valid.
>
> If it’s not guaranteed, can I loop until the handle is valid?
>
> Antov,
>
> I guess what you mean is that if ZwTerminateProcess is called from another context than the being-terminated process it has to return to UM, so the notification routine lock is freed, even if the notification routine hasnt finished yet at the time my IO_WORKITEM is running.
> Which is absolutely true… I have another call in the notification routine, which is returning ‘STATUS_THREAD_IS_TERMINATING’ so.

Ok, but I should be able to loop in the workitem shouldn’t I? Because the notification routine will queue it, and then return and finalize the handle. Although it is a bit hacky…

Right now it is working when I call ZwOpenProcess in the notification routine and hand it over to the workitem, but I guess that is pure luck.

If you were to implement something like this Don, how would you go about doing it?
Even if we do the TerminateProcess in usermode, we still need a guarantee that our notification routine finishes(and all of the other registered routines) so that the handle is finalized before usermode attempts to open/terminate the process.

Looping in the workitem should work. A nastier approach is to map the
image into kernel space, and modify it to crash on startup.

On all of these approaches be aware that there are a ton of ways to
spoof a pathname, this has been discussed extensively on the ntfsd
forum, and you should understand that relying on pathnames of files has
its own set of problems.

Don Burn (MVP, Windows DKD)
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

xxxxx@gmail.com” wrote in message
news:xxxxx@ntdev:

> Ok, but I should be able to loop in the workitem shouldn’t I? Because the notification routine will queue it, and then return and finalize the handle. Although it is a bit hacky…
>
> Right now it is working when I call ZwOpenProcess in the notification routine and hand it over to the workitem, but I guess that is pure luck.
>
> If you were to implement something like this Don, how would you go about doing it?
> Even if we do the TerminateProcess in usermode, we still need a guarantee that our notification routine finishes(and all of the other registered routines) so that the handle is finalized before usermode attempts to open/terminate the process.

Thank you Don for your expertise :wink:

If this would be a real project I wouldn’t rely on the pathname (I will read up on what you said about spoofing the pathname though).
I guess if this would be a real project I would store some kind of hash of the program’s executable that I want to terminate and compare that… but if you say the pathname can be spoofed I guess that wouldn’t work out either.

Ok you’ve gotten me interested now, I’m going to read up on what you said and extend the “homework” a little :wink: Thanks!

> I get a bugcheck when I call ZwTerminateProcess from the image notification >routine(KERNEL_APC_PENDING_DURING_EXIT 0x20).

Hold on - you are calling ZwTerminateProcess() from process-notify routine, right? When you create a process this routine runs in context of the parent process, rather than child one. Therefore, judging from
your error code you seem to be calling it upon termination, rather than creation - in this case it runs in context of a process being terminated, so that you are trying to terminate it for the second time. Are you actually checking Create parameter???

I guess what you mean is that if ZwTerminateProcess is called from another context than
the being-terminated process it has to return to UM,

Actually, I thought you were speaking about the same thing - after all, you were speaking about calling
ZwTerminateProcess() upon process creation, i.e. in context of a parent process…

Anton Bassov

I’m was calling it from the IMAGE notification routine… not the process notification routine.

I added:

if( hProcessID == psGetCurrentProcessID() )

which evaluated to true.

maybe the code will clarify:

VOID createProcessNotify(
IN HANDLE hParentID,
IN HANDLE hProcessID,
IN BOOLEAN bCreate )
{
PAGED_CODE();
if( bCreate )
TmpCreatedProcessAdd( hProcessID );
}

VOID loadImageNotify (
IN PUNICODE_STRING pusFullImageName,
IN HANDLE hProcessID,
IN PIMAGE_INFO ImageInfo )
{
PAGED_CODE();

if( TmpCreatedProcessFindAndRemove( hProcessID ) ) // The PID is a PID that was just created
{
if( umShouldProgramTerminate( pusFullImageName ) )
ProcessWaitTerminate ( hProcessID );
}
}

NTSTATUS ProcessWaitTerminate( HANDLE hProcessID )
{
CLIENT_ID clientId;
clientId.UniqueProcess = hProcessID;
clientId.UniqueThread = 0;
DebugTrace( DBG_TRACE_ALL, (DBG_PREFIX “ProcessWaitTerminate( %u )\n”, hProcessID ) );
// … skipped object attribute initialization
NTSTATUS ntStatus = ZwOpenProcess(
&hProcess,
PROCESS_ALL_ACCESS,
&objAttr,
&clientId );
if( ! NT_SUCCESS( ntStatus ) )
{
DebugTrace( DBG_TRACE_ERROR, (DBG_PREFIX “ZwOpenProcess Failed (STATUS: 0x%0x)\n”, ntStatus ) );
return ntStatus;
}

if( hProcessID == PsGetCurrentProcessId() )
{
// If I call ZwTerminateProcess here it will bugcheck with 0x20
// So instead I allocate a workitem here which calls ProcessTerminate.
} else
{
ProcessTerminate( NULL, (PVOID)hProcess );
}
}