Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Home NTDEV

More Info on Driver Writing and Debugging


The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.


Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/


Before Posting...

Please check out the Community Guidelines in the Announcements and Administration Category.

Removing PROCESS_TERMINATE flag causes hang

Ged_MurphyGed_Murphy Member - All Emails Posts: 116
This is more of a point to raise rather than question, as I couldn't find
any previous discussion about it in the archives so thought it'd be good to
document it.
I noticed that when removing the PROCESS_TERMINATE flag on some requests, I
was occasionally seeing the process hang before it had chance to properly
startup.

A bit of background, ObRegisterCallbacks allows you to register for process
handle request callbacks, which gives us an OB_PRE_CREATE_HANDLE_INFORMATION
structure with the handle information. We can then modify the access mask
before the handle is created, resulting in a handle with reduced rights. The
docs state the following :

"An ACCESS_MASK value that specifies the access rights to grant for the
handle. By default, this member equals OriginalDesiredAccess, but the
ObjectPreCallback routine can modify this value to restrict the access that
is granted."
and
"If the access right is listed as a modifiable flag, the access right can be
removed."
https://msdn.microsoft.com/en-us/library/windows/hardware/ff558725(v=vs.85).
aspx

Removing the PROCESS_TERMINATE flags works well in most scenarios, however
this hang was occurring which seemed to be linked to process elevation.

Here's what's happening:
- Parent process calls CreateProcess to start a child.
- CreateProcessInternalW starts to setup the process object and then
requests a handle to it with PROCESS_ALL_ACCESS
- The child process being created is something we want to stop being
terminated (for some users), so the PROCESS_TERMINATE right is removed and
the process continues its setup.
- The first thread is created in the child process, it does some init
and enters a suspended state.
- CreateProcessInternalW realises that the child process needs
elevating, so it calls TerminateProcess to cleanup the suspended process
that it created.
- TerminateProcess fails because it doesn't have the rights. However
Windows doesn't check the return code and just calls WaitForSingleObject
using the process handle
- The child process remains suspended and never terminates, the handle
is never signalled and parent thread waits indefinitely, so we get a hang in
the parent thread and a zombie process.

There's no way of knowing that this handle request is coming from Windows as
part of the CreateProcess routine (without deciphering the callstack...) so
we can't put in any special case code for this scenario. There's therefore
no easy way to avoid this lockup without putting in more fine grain control
into the rules.

I'm just pointing this out at the docs point to this being a valid thing to
do, however CreateProcessInternal isn't capable of handling this scenario
and just assumes that it will always have the right to terminate. The docs
are missing this piece of information, which breaks this design.

I know some people will be thinking that you should never remove this flag,
and I tend to agree, however enterprise products do occasionally have to do
things like this when large customers request it, and this is one of those
cases. It's a shame because the design is really quite nice otherwise, and
this issue might force people down the detour route instead, which I'm
personally very much against.

Ged.

Comments

  • Alex_GrigAlex_Grig Member Posts: 3,238
    If you want processes created by an user process be non-killable by the user, YOU'RE DOING THINGS WRONG.

    The proper way to create a process non-killable by an user (even though it's running under an user's token) is to have a service create a process with user's token by calling CreateProcessAsUser, and set the appropriate ACL on it.
  • Ged_MurphyGed_Murphy Member - All Emails Posts: 116
    Thanks for the caps, I probably would have missed the second half of your
    sentence otherwise....

    I'm not trying to do what you suggest, and your suggestion is completely the
    wrong solution for the problem I'm trying to solve.


    -----Original Message-----
    From: [email protected]
    [mailto:[email protected]] On Behalf Of
    [email protected]
    Sent: 09 October 2016 14:03
    To: Windows System Software Devs Interest List <[email protected]>
    Subject: RE:[ntdev] Removing PROCESS_TERMINATE flag causes hang

    If you want processes created by an user process be non-killable by the
    user, YOU'RE DOING THINGS WRONG.

    The proper way to create a process non-killable by an user (even though it's
    running under an user's token) is to have a service create a process with
    user's token by calling CreateProcessAsUser, and set the appropriate ACL on
    it.

    ---
    NTDEV is sponsored by OSR

    Visit the list online at:
    <http://www.osronline.com/showlists.cfm?list=ntdev&gt;

    MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
    drivers!
    Details at <http://www.osr.com/seminars&gt;

    To unsubscribe, visit the List Server section of OSR Online at
    <http://www.osronline.com/page.cfm?name=ListServer&gt;
  • Alex_GrigAlex_Grig Member Posts: 3,238
    To make a process truly unkillable, it's not enough to deny TERMINATE_PROCESS right.

    >for the problem I'm trying to solve

    I'm afraid "the problem you're trying to solve" is you're trying to find out how to implement a wrong solution for the feature the customers want.
  • Ged_MurphyGed_Murphy Member - All Emails Posts: 116
    > To make a process truly unkillable, it's not enough to deny
    TERMINATE_PROCESS right.

    Yep, I'm well aware of that. I've currently listed 12 very different
    'attack' methods, of which I'm sure there are more for anyone more
    imaginative than I am.

    > I'm afraid "the problem you're trying to solve" is you're trying to find
    out how to implement a wrong solution for the feature the customers want.

    To be fair, you don't know what problem I'm trying to solve, and I wasn't
    asking for any suggestions on solving anything

    The solution I listed previously is a commonly accepted solution to what is
    a slightly controversial topic. There's quite a few topics on ntdev pointing
    to the use of ObRegisterCallbacks to modify handles. For example this
    lengthy post:
    https://www.osronline.com/showThread.CFM?link=261303

    Microsoft themselves also provide sample code showing this exact solution to
    restrict PROCESS_TERMINATE access on a particular target process
    https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
    llback/driver
    If you run the sample using a target process that requires elevation, you'll
    see that it exhibits this exact problem and causes a hang in the parent
    thread and a zombie process.

    I'm just documenting the issues I found when researching, in the hope it
    might help someone else in the future. It's surely worth pointing out that
    following Microsoft's lead on this topic will cause problems. The WDK sample
    code should probably be removed, or CreateProcessInternalW should be 'fixed'
    to avoid the hangs.

    Ged.
  • Ken_JohnsonKen_Johnson Member - All Emails Posts: 1,559
    Well, what would you propose that CreateProcessAsUser is supposed to do if it discovers that it can't finish preparing the new process to run, but access to terminate it has been revoked silently (even though CreateProcessAsUser itself expects that it has obtained terminate access)? There are a number of reasons why this may happen, including (but not limited to) elevation required. It is not necessarily reasonable to account for them all up front, as some operations may need to be done on the new process once it is created (and which could fail due to e.g. low resources).


    This is a classic, fundamental problem with filters that decide to mess with access rights, or otherwise alter the "ground rules" from their norm: You can introduce failure modes that no program expects (or even worse, that there is just no reasonable way to handle at all), because the behavior of the system that programs may rightly expect to depend on has suddenly been changed out from under them.

    Filter capabilities like this are very much in a quite fragile "You break it, you buy it" sort of category if broadly used. There are a whole bunch of ways to break things in subtle cases (revoking access to terminate the process from the creator being one example). Those filtering mechanisms are best used sparingly and in very narrow circumstances, if at all.

    - S (Msft)

    -----Original Message-----
    From: [email protected] [mailto:[email protected]] On Behalf Of Ged Murphy
    Sent: Sunday, October 09, 2016 12:06 PM
    To: Windows System Software Devs Interest List <[email protected]>
    Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

    > To make a process truly unkillable, it's not enough to deny
    TERMINATE_PROCESS right.

    Yep, I'm well aware of that. I've currently listed 12 very different 'attack' methods, of which I'm sure there are more for anyone more imaginative than I am.

    > I'm afraid "the problem you're trying to solve" is you're trying to
    > find
    out how to implement a wrong solution for the feature the customers want.

    To be fair, you don't know what problem I'm trying to solve, and I wasn't asking for any suggestions on solving anything

    The solution I listed previously is a commonly accepted solution to what is a slightly controversial topic. There's quite a few topics on ntdev pointing to the use of ObRegisterCallbacks to modify handles. For example this lengthy post:
    https://www.osronline.com/showThread.CFM?link=261303

    Microsoft themselves also provide sample code showing this exact solution to restrict PROCESS_TERMINATE access on a particular target process https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
    llback/driver
    If you run the sample using a target process that requires elevation, you'll see that it exhibits this exact problem and causes a hang in the parent thread and a zombie process.

    I'm just documenting the issues I found when researching, in the hope it might help someone else in the future. It's surely worth pointing out that following Microsoft's lead on this topic will cause problems. The WDK sample code should probably be removed, or CreateProcessInternalW should be 'fixed'
    to avoid the hangs.

    Ged.


    ---
    NTDEV is sponsored by OSR

    Visit the list online at: <http://www.osronline.com/showlists.cfm?list=ntdev&gt;

    MONTHLY seminars on crash dump analysis, WDF, Windows internals and software drivers!
    Details at <http://www.osr.com/seminars&gt;

    To unsubscribe, visit the List Server section of OSR Online at <http://www.osronline.com/page.cfm?name=ListServer&gt;
  • Alex_GrigAlex_Grig Member Posts: 3,238
    The thing is, you're not the first here to pop up with the question "how do I make a process unkillable". You think you know how to make a process unkillable, but it's just a wrong approach to the problem. The right approach is to make sure the user's process doesn't have "write"-type rights to the protected process object, by means of DACL. This also prevents DLL injection and hook installation. One non-bullet-proof way is to have the CreateProcess caller supply security descriptor for the new process, with restricted access for the current user. This is non-bullet-proof because an object owner can always change the security descriptor of an object. If CreateProcess caller runs under user's account, then the process object owner is the user. To work around that, CreateProcess(AsUser) needs to be called by a privileged (service) account.
  • anton_bassovanton_bassov Member MODERATED Posts: 5,245
    >To be fair, you don't know what problem I'm trying to solve,

    Well, let me guess.....probably, just writing malware, right.This is the very first thing that gets into one's head when they encounter the requirements like "unkillable process" or "undeletable file", particularly if the former is backed up by the latter.


    Search this list for more info and see how posters who ask questions like that are generally treated here. You will find quite a few "funny" threads where the usual suspects shout the word "MALWARE" in capitals and request these posters to disclose the names of their companies, as well as of products that require the above mentioned features.


    You will see how some other posters are exclaiming "Microsoft has granted users the rights to terminate any process that happens to be running under their corresponding accounts. This has been granted to us by Microsoft!!!", saying the certain word that appears twice in the above quotation with the same reverence that deeply religious people mention the name of their deity/prophet/idol/software license/whatever happens to be an object of adoration in their particular religion.


    You will also find quite a few examples of truly yours ranting all over the place and arguing with everyone just about anything one may possibly imagine, as well as attacking MSFT and those whom he generally refers to as "MSFT Sycophant Club" (as a matter of a fact, it was the above mentioned club's "International Anthem" that got him on the moderation list last year).


    In other words, enjoy the read......


    Anton Bassov
  • Ged_MurphyGed_Murphy Member - All Emails Posts: 116
    [email protected] wrote:
    > Well, what would you propose that CreateProcessAsUser is supposed to do
    > if it discovers that it can't finish preparing the new process to run, but
    access
    > to terminate it has been revoked silently (even though CreateProcessAsUser
    > itself expects that it has obtained terminate access)?

    Hi Ken,
    Yeah it's a tricky one, as you say there's no one-stop solution. I guess a
    good start would be checking the return of TerminateProcess and not calling
    waiting on the process handle. At least we'd only have a suspended process
    and not a hung parent thread.
    However one idea could be, assuming the terminate error was ACCESS_DENIED,
    to hand off the terminate job to the system process. At least that would
    give the OS a chance to clean up in this scenario. Anyone blocking system
    from terminating processes would be clearly have a very bad design.

    The other option is to add a big note to the sample code, saying "this code
    will cause hangs", or preferably just remove it entirely from the WDK
    samples.

    Incidentally, the hang can be fixed by resuming the hung processes, which
    gives you a halfplemented processes, but one that is killable via the GUI.
    At least this resolves the hang and zombie.


    [email protected] wrote:
    > The thing is, you're not the first here to pop up with the question
    > "how do I make a process unkillable"

    I didn't pop up with that question. In fact, I didn't ask any question at
    all. You're assuming I have a problem, and you're assuming you have the
    correct fix. Neither of those assumptions are correct. For the record,
    you'll be pleased to know that if I were trying to do what you suggest, I
    would likely be using DACLs (and a mini-filter to block ownership changes).

    > This also prevents DLL injection and hook installation
    Yep, amongst other things. Interestingly, you can also prevent these things
    by stripping rights.
    Handle duplication, VM read/write, remote thread termination,
    getting/setting thread context, assigning/terminating job objects. They can
    all be avoided with stripping handle rights. I'm not saying it's the correct
    solution, and I'm not asking for help or ratification, I'm just saying it's
    possible and Microsoft provide documented APIs to help you do it :)

    [email protected] wrote:
    > > To be fair, you don't know what problem I'm trying to solve,
    > Well, let me guess.....probably, just writing malware, right.

    No!. If I was writing malware, I'd just make the process undetectable, not
    unkillable.

    > Search this list for more info and see how posters who ask
    > questions like that are generally treated here.
    I'm well aware, I've been reading this list (well, I'm normally on ntfsd)
    daily for the last 15 years.... In fact from back in the day when Ken used
    to work in open source :)


    Look, I haven't turned up asking any questions. I haven't requested hints or
    pointers on how to stop processes from being killed. I'm certainly not a
    malware writer, I write enterprise software for multi-billion dollar
    industries, and have done for a looong time.

    I just wrote a mail to point out an issue I found when doing some research.
    An issue that hadn't been documented before on this list, and an issue that
    Microsoft themselves exhibit in their own sample code. It's a bit of a sad
    day when you try to post something helpful to the list and get shot down for
    being a malware writer.

    Ged.


    -----Original Message-----
    From: [email protected]
    [mailto:[email protected]] On Behalf Of Skywing
    Sent: 09 October 2016 22:06
    To: Windows System Software Devs Interest List <[email protected]>
    Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

    Well, what would you propose that CreateProcessAsUser is supposed to do if
    it discovers that it can't finish preparing the new process to run, but
    access to terminate it has been revoked silently (even though
    CreateProcessAsUser itself expects that it has obtained terminate access)?
    There are a number of reasons why this may happen, including (but not
    limited to) elevation required. It is not necessarily reasonable to account
    for them all up front, as some operations may need to be done on the new
    process once it is created (and which could fail due to e.g. low resources).


    This is a classic, fundamental problem with filters that decide to mess with
    access rights, or otherwise alter the "ground rules" from their norm: You
    can introduce failure modes that no program expects (or even worse, that
    there is just no reasonable way to handle at all), because the behavior of
    the system that programs may rightly expect to depend on has suddenly been
    changed out from under them.

    Filter capabilities like this are very much in a quite fragile "You break
    it, you buy it" sort of category if broadly used. There are a whole bunch
    of ways to break things in subtle cases (revoking access to terminate the
    process from the creator being one example). Those filtering mechanisms are
    best used sparingly and in very narrow circumstances, if at all.

    - S (Msft)

    -----Original Message-----
    From: [email protected]
    [mailto:[email protected]] On Behalf Of Ged Murphy
    Sent: Sunday, October 09, 2016 12:06 PM
    To: Windows System Software Devs Interest List <[email protected]>
    Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

    > To make a process truly unkillable, it's not enough to deny
    TERMINATE_PROCESS right.

    Yep, I'm well aware of that. I've currently listed 12 very different
    'attack' methods, of which I'm sure there are more for anyone more
    imaginative than I am.

    > I'm afraid "the problem you're trying to solve" is you're trying to
    > find
    out how to implement a wrong solution for the feature the customers want.

    To be fair, you don't know what problem I'm trying to solve, and I wasn't
    asking for any suggestions on solving anything

    The solution I listed previously is a commonly accepted solution to what is
    a slightly controversial topic. There's quite a few topics on ntdev pointing
    to the use of ObRegisterCallbacks to modify handles. For example this
    lengthy post:
    https://www.osronline.com/showThread.CFM?link=261303

    Microsoft themselves also provide sample code showing this exact solution to
    restrict PROCESS_TERMINATE access on a particular target process
    https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
    llback/driver
    If you run the sample using a target process that requires elevation, you'll
    see that it exhibits this exact problem and causes a hang in the parent
    thread and a zombie process.

    I'm just documenting the issues I found when researching, in the hope it
    might help someone else in the future. It's surely worth pointing out that
    following Microsoft's lead on this topic will cause problems. The WDK sample
    code should probably be removed, or CreateProcessInternalW should be 'fixed'
    to avoid the hangs.

    Ged.


    ---
    NTDEV is sponsored by OSR

    Visit the list online at:
    <http://www.osronline.com/showlists.cfm?list=ntdev&gt;

    MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
    drivers!
    Details at <http://www.osr.com/seminars&gt;

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

    ---
    NTDEV is sponsored by OSR

    Visit the list online at:
    <http://www.osronline.com/showlists.cfm?list=ntdev&gt;

    MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
    drivers!
    Details at <http://www.osr.com/seminars&gt;

    To unsubscribe, visit the List Server section of OSR Online at
    <http://www.osronline.com/page.cfm?name=ListServer&gt;
  • Alex_GrigAlex_Grig Member Posts: 3,238
    >> This also prevents DLL injection and hook installation

    >Yep, amongst other things. Interestingly, you can also prevent these things
    >by stripping rights.

    That's what DACLs were designed to do. What you're trying to do is a kludge.
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    My guess is that TerminateProcess calls ZwTerminateProcess. The process handle is used to obtain a referenced pointer to the process object with ObReferenceObjectByHandle. But this latter call should fail with STATUS_ACCESS_DENIED if you removed the TERMINATE_PROCESS access right. So, TerminateProcess should return quickly with an error like ERROR_ACCESS_DENIED.
  • Ged_MurphyGed_Murphy Member - All Emails Posts: 116
    >>> This also prevents DLL injection and hook installation

    >> Yep, amongst other things. Interestingly, you can also prevent these
    things
    >> by stripping rights.

    > That's what DACLs were designed to do. What you're trying to do is a
    kludge.

    I'm not trying to do it.
    I give up....
  • Ged_MurphyGed_Murphy Member - All Emails Posts: 116
    Yeah, that's exactly what happens.

    I suppose the obvious solution is to update the WDK sample to exclude
    parents from having terminate rights removed to their children (it currently
    does for handles to its own process).
    The sample already monitors process creates / deletes, so it's not much
    extra work to keep a list of parent -> child relationships and update child
    entries if the parent processes closes (making the pid invalid).

    I've just tried this locally with the WDK sample and it works well.

    Ged.

    -----Original Message-----
    From: [email protected]
    [mailto:[email protected]] On Behalf Of
    [email protected]
    Sent: 10 October 2016 17:52
    To: Windows System Software Devs Interest List <[email protected]>
    Subject: RE:[ntdev] Removing PROCESS_TERMINATE flag causes hang

    My guess is that TerminateProcess calls ZwTerminateProcess. The process
    handle is used to obtain a referenced pointer to the process object with
    ObReferenceObjectByHandle. But this latter call should fail with
    STATUS_ACCESS_DENIED if you removed the TERMINATE_PROCESS access right. So,
    TerminateProcess should return quickly with an error like
    ERROR_ACCESS_DENIED.


    ---
    NTDEV is sponsored by OSR

    Visit the list online at:
    <http://www.osronline.com/showlists.cfm?list=ntdev&gt;

    MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
    drivers!
    Details at <http://www.osr.com/seminars&gt;

    To unsubscribe, visit the List Server section of OSR Online at
    <http://www.osronline.com/page.cfm?name=ListServer&gt;
  • OSR_Community_UserOSR_Community_User Member Posts: 110,217
    Don't be confused. CreateProcessInternalW runs in the context of the creator that may not be the parent process.

    I think you should use the protected process feature of the OS.
  • anton_bassovanton_bassov Member MODERATED Posts: 5,245
    > That's what DACLs were designed to do.


    I can easily foresee your usual "don't let users run as admins" mantra, but still,
    assuming that we are speaking about the admin account, can't other processes undo your
    "unbreakable protection" of a target process with SetEntriesInAcl() and SetSecurityInfo() calls?

    Anton Bassov
  • Alex_GrigAlex_Grig Member Posts: 3,238
    >can't other processes undo your "unbreakable protection" of a target process with SetEntriesInAcl() and SetSecurityInfo() calls?

    Only if DACLs let them do it. If it's an admin account, the IT doesn't know what they're doing.
  • anton_bassovanton_bassov Member MODERATED Posts: 5,245
    > If it's an admin account, the IT doesn't know what they're doing.

    As I can see, my statement about your usual "mantra" turned into some kind of a "self-fulfilling
    prophecy". In fact, it was obvious to me from the very beginning that it was about to happen anyway.

    In either case, this whole discussion may make sense only if we assume the admin account. Otherwise, all that one has to do here is to run the target process under the admin account, and the whole problem is going to get automatically reduced to a complete non-existence, at least as far as termination by a "regular" user is concerned....



    Anton Bassov
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Upcoming OSR Seminars
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead!
Developing Minifilters 24 May 2021 Live, Online
Writing WDF Drivers 14 June 2021 Live, Online
Internals & Software Drivers 27 September 2021 Live, Online
Kernel Debugging TBD 2021 Live, Online