6

I'm investigating the security measurements used by an application running in Windows 10. The application is started by my local non-admin Windows user and does not try to elevate to administrator during startup. Despite this, trying to inspect the process using Process Explorer or inject a DLL into the process fails.

Process explorer, when being started by the same user that started the other application, just shows "Access denied" for this process. I can't see the image path, what user is running it, etc. If I start it as administrator I do have access and it says that the process is running as my regular user.

Similarly, when trying to inject a DLL into this processes it only works if I run the injector as administrator. If I run it as my regular user the call to OpenProcess fails with error code 5 (Access Denied).

Strangely enough, if I start the application via a debugger (Ollydbg in this case), it does not get "hidden"; I can inspect it using Process Explorer and inject DLLs into it as my regular user.

What kind of security measurement is this and how does it work?

1 Answer 1

7

First, some background.

We're going to be digging into the guts of the Windows security model a bit here.

Processes are securable objects (much like files, registry keys, devices, shared memory sections, named pipes, etc.). That means they have a Security Descriptor, which contains an ACL (Access Control List) and other details like their Owner. In the case of processes, the security descriptor can be set by the parent process when calling a CreateProcess-family function, but usually nobody bothers and it's just set to some default. The default it allows the owner (that is, the user it runs as) and the SYSTEM user to have full control, and everybody else gets basically no privileges. You can see this in the Process Explorer tool from Sysinternals; select a process, go to Properties, go to the Security tab, and click on Permissions (at the bottom).

This ACL is checked when a process calls a function in the OpenProcess family. Much like CreateFile or RegOpenKey, these functions require the caller to specify the permissions that they want to have on the target process. The OS then checks those permissions vs. the ACL of the target process to see whether the security token (which identifies the owning "user" or "account" or similar, plus Groups and any special Security Identifiers "SIDs") that the calling process runs as are granted (and not also denied) the requested permissions on the callee process. This is exactly the same as when a process tries to open a file or similar, except instead of checking a file's ACL, it's checking a process' ACL.

You can read more about process security in Windows at Process Security and Access Rights


The (probable) answer to your question

So how does a process prevent itself from being opened by other processes (as is required by Process Explorer to get the process details, or by a DLL-injecting process trying to get access to the target process' memory)? Simply call SetSecurityInfo, passing a HANDLE to itself as the first parameter (there's actually a special HANDLE reserved to indicate "the current process, from the perspective of the caller"; it always has full access and doesn't require a call to OpenProcess first; this value is returned by GetCurrentProcess). In the other parameters to SetSecurityInfo, specify that you are setting the DACL (discretionary ACL, the kind that controls who has permissions to access the object in various ways), and pass an empty DACL (one that grants no privileges at all), or a similarly limited DACL. (Note: do not pass NULL for the DACL; that removes the DACL and allows everybody unrestricted access.) You can do this very early on in the main method of your program, such that anybody who tries to open this program's process object will get Access Denied.


Wow, is it really that simple?

Eh, not really. There's a bunch of stuff going on here that you need to consider if you want to do this yourself, or that is relevant to you if you want to bypass it.

  • It's possible to open an object (such as a process) without specifying any requested permissions at all. In that case, so long as the object is at least visible to the caller, there are no permissions to check against the target's DACL, so the call succeeds and you get back a HANDLE or similar. Of course, it has no granted permissions, which is mostly useless.
  • ACLs can be overridden. Obviously, anybody who has been granted the relevant access can modify a process' ACL, but also, any process running as the owner of the process can always override the ACL even if the owner isn't actually permitted by the DACL to modify anything. This doesn't happen automatically, though, which is why other processes running as the same user will still get Access Denied errors when they try to open the process normally; they first must open it without requesting any privileges, and then overwrite the ACL using "permissionless" HANDLE.
  • With sufficiently high kernel privileges (a concept equivalent to Linux capabilities) - specifically, SeTakeOwnershipPrivilege (in practice, this mostly only is assigned to processes running as Administrators or SYSTEM) - a process can override the owner of an object (such as a process). This doesn't change who the process is running as, but it does change who has the "set the ACL regardless of privileges" permission from the bullet point above. This is also not automatic; a process must first open the target with no privileges specified, then specifically request to take ownership, then request to overwrite the ACL. It can then re-open the target with whatever privileges it granted itself. This of course means it can take over anything, and then give itself whatever privileges, so of course it's tantamount to complete ownership of the OS.
  • Kernel privileges can do a lot! There are also a few that let you just ignore specific aspects of the ACL, rather than overwriting it (although overwriting it could be viewed as ignoring the WRITE_DAC permission). For files specifically, there's the SeRestorePrivilege, which allows (over)writing files without regard to their DACL, but I don't think it works on processes. However, SeDebugPrivilege does work on processes, and does what it says in the name; a holder of that privilege can control processes in the way a debugger needs to, including reading and writing process memory and suspending or resuming threads, regardless of the process' DACL. Yes, this also de facto means complete control of the operating system. Anyhow, this privilege is why processes running as Administrator are able to access the process details.
  • There's a window of vulnerability here. While a parent process could always create the child process with such a "nobody can access this" DACL, if the child process wants to set its own DACL, it can't do that until the process starts executing. However, that creates a (short) window at process initialization when an external process could open a HANDLE with meaningful access to the child process because during that time, the child process still has the default security descriptor. This is especially relevant if the child process was created suspended, but could happen even without that if the "attacker" process is responds immediately when a new process starts (I believe you can register an event handler for whenever a process starts).
  • There's another vulnerability here: the parent process. A parent process always receives a HANDLE to the child process (it's in the lpProcessInformation out-parameter) and that HANDLE has full privileges. Even if the parent never set the child's security descriptor, and even if the child modifies its DACL before the parent ever does anything with the returned HANDLEs, the parent can still use the child process' HANDLE to do whatever the parent wants. The permissions a HANDLE grants its holder are set when the HANDLE is created, and are only checked against the target's security descriptor (and caller's security token) at creation time. After that, so long as the target exists and the HANDLE is never closed, it can be used regardless of any changes to the target's security descriptor.
1
  • 1
    Thank you for this very detailed answer. I have confirmed that the application I'm investigating does indeed update the DACL, via SetKernelObjectSecurity (which I think can do the same thing as SetSecurityInfo).
    – simon
    Commented Sep 2 at 12:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .