Saturday, 2 June 2018

Adding a Command Line to PowerShell's Process Listing

My NtObjectManager PowerShell module has plenty of useful functions and cmdlets, but it's not really designed for general use-cases such as system administration. I was reminded of this when I got a reply to a tweet I made announcing a new version of the module.

While the Get-NtProcess cmdlet does have a CommandLine property it's not really a good idea to use it just for that. Each process object returned from the cmdlet is an instance of the NtProcess class which maintains an open handle. While the garbage collector should eventually kick in and clean up for you it's still bad practice to leave open handles lying around.

Wouldn't it be useful if you could get the command line of a process without requiring a large third party module? As pointed out in the tweet you can use WMI but it's uglier than calling Get-Process. It also has a small flaw, it doesn't show the command lines for elevated processes on the same desktop which Get-NtProcess can do (at least on Windows 8 and above).

So I decided to investigate how I might add the command line to the existing Get-Process cmdlet in the simplest way possible. To do so I expose some functionality in PowerShell which I'm guessing few realise exists, or for that matter need to use. First let's see what type of object Get-Process is returning. We can call GetType on the returned objects and find that out.

PS C:\> $ps = Get-Process
PS C:\> $ps[0].GetType() | select Fullname

FullName
--------
System.Diagnostics.Process

We can see that it's just returning the list of normal framework Process objects. Nothing too surprising there, however one thing is interesting, the object has more properties available than the Process class in the framework. A good example is the Path property which returns the full path to the main executable:

PS C:\> $ps[0] | select Path

Path
----
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

Where does that come from? Using the Get-Member cmdlet gives us a clue.

PS C:\> $ps[0] | Get-Member Path

   TypeName: System.Diagnostics.Process

Name MemberType     Definition
---- ----------     ----------
Path ScriptProperty System.Object Path {get=$this.Mainmodule.FileName;}

Very interesting, it seems to a script property, after some digging it turns out this script is added in something called a Type Extension file which has been around since the early days of PowerShell. It allows you to add arbitrary properties and methods to existing types. The extensions for the Process class are in $PSHome\types.ps1xml, a snippet is shown below.

<Type>
 <Name>System.Diagnostics.Process</Name>
 <Members>
  <ScriptProperty>
   <Name>Path</Name>
    <GetScriptBlock>$this.Mainmodule.FileName</GetScriptBlock>
   </ScriptProperty>
...

Of course what I should have done first is just checked Lee Holmes blog where he wrote a description of these Type Extension files a mere 12 years ago! Anyway you can also get the help for this feature using running Get-Help about_Types.

This sounds ideal, we can add our create out own Type Extension file and add a scripted property to pull out the command line. We just need to write it, the easiest solution would be to use my NtApiDotNet .NET library, but if you're using that you might as well just use the NtObjectManager module to begin with as the library is what the module is built on. Therefore, we'll need to re-implement everything in C# using Add-Type then just invoke that to get the command line when necessary. This is not too hard, I just based it on the code in my library.

If you copy the gist into a file with a ps1xml extension you can then add the extension to the current session using the Update-TypeData cmdlet and passing the path to the file. If you want this to persist you can add the call to your profile, or save the session and reload it.

PS C:\> Update-TypeData .\command_line_type.ps1xml
PS C:\> Get-Process explorer | select CommandLine

CommandLine
-----------
C:\WINDOWS\Explorer.EXE

I've tried to make it as compact as possible, for example I don't enable SeDebugPrivilege which would be useful for administrators as it'd allow you to read the command line from almost any process on the system. You could add that feature if you like. One thing I had to do is also call OpenProcess on the PID, which is odd as the Process class actually has a SafeHandle property which returns a native handle for the process. Unfortunately the framework opens this handle with PROCESS_ALL_ACCESS rights, not the limited PROCESS_QUERY_LIMITED_INFORMATION access we require. This means that elevated processes can not be opened, removing the advantage this approach gives us.

This is also the reason WMI doesn't return all command lines. The WMI host process impersonates the caller when querying the command line for a process. WMI then uses an old method of extracting the command line by reading the command line directly from memory (using a technique similar to this StackOverflow post). As this requires PROCESS_VM_READ access this fails with elevated processes. Perhaps they should move to the NtQueryInformationProcess approach on modern versions of Windows ;-)

PS C:\> start -verb runas notepad test.txt
PS C:\> Get-WmiObject Win32_process | ? Name -eq "notepad.exe" | select CommandLine

CommandLine
-----------

PS C:\> Get-Process notepad | select CommandLine

CommandLine
-----------
"C:\WINDOWS\system32\notepad.exe" test.txt

Hope you find this information useful, there's loads of useful functionality in PowerShell which can make your life much easier. You just have to find it first :-)

Sunday, 5 November 2017

Named Pipe Secure Prefixes

When writing named pipe servers on Windows it’s imperative is do so securely. One common problem you’ll encounter is named pipe squatting, where a low privileged application creates a named pipe server either before the real server does or as a new instance of an existing server. This could lead to information disclosure if the server is being used to aggregate private data from a number of clients as well as elevation of privilege if the conditions are right.

There’s some programming strategies to try and eliminate named pipe squatting including passing the FILE_FLAG_FIRST_PIPE_INSTANCE flag to CreatedNamedPipe which will cause an error if the pipe already exists as well as appropriate configuration of the pipe’s security descriptor. A recent addition to Windows is named pipe secure prefixes, which make it easier to develop a named pipe server which isn’t vulnerable to squatting. Unfortunately I can’t find any official documentation on these prefixes or how you use them. Prefixes seem to be mentioned briefly in Windows Internals, but it doesn’t go into any detail about what they are or why they work. So this blog is an effort to remedy that lack of documentation.

First let’s start with how named pipes are named. Named pipes are exposed by the Named Pipe File System (NPFS) driver, which creates the \Device\NamedPipe device object. When you create a new named pipe instance you call CreateNamedPipe (really NtCreateNamedPipeFile under the hood) with the full path to the pipe to create. It’s typically to see this in the form \\.\pipe\PipeName where PipeName is the name you want to assign. At the native API level this path is converted to \??\pipe\PipeName; \??\pipe is a symbolic link which ultimately resolves this path to \Device\NamedPipe\PipeName.

Even though \Device\NamedPipe is a file system, NPFS doesn’t support directories other than the root. If you list the contents of the named pipe root directory you’ll notice that some of the names pipes have backslashes in the name, however NPFS just treats them as names as shown below.

named_pipes.PNG

So we’d assume we can’t have directories, but if we look at the function which handles IRP_MJ_CREATE dispatch in the NPFS driver we find something interesting:

NTSTATUS NpFsdCreate(PDEVICE_OBJECT device, PIRP irp) {
 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
 BOOLEAN directoryfile = stack->Parameters.Create.Options
                                & FILE_DIRECTORY_FILE;
 DWORD disposition = stack->Parameters.Create.Options >> 24;
 NTSTATUS status;
 // Other stuff...

 if (directoryfile) { ← Check for creating a directory
   if (disposition == FILE_OPEN) {
     status = NpOpenNamedPipePrefix(...);
   } else if (disposition == FILE_CREATE
           || disposition == FILE_OPEN_IF) {
     status = NpCreateNamedPipePrefix(...);
   }
 }

 // Even more stuff...
}

In the code we can see that the flag for creating a directory file is checked. If a directory file is requested then the driver tries to create or open a named pipe prefix. Let’s try and create one of these prefixes:

create_prefix.PNG

Well darn, it say it requires a privilege. Let’s dig into NpCreateNamedPipePrefix to see which privilege we’re missing.

NTSTATUS NpCreateNamedPipePrefix(...) {  
 // Blah blah...
 if (SeSinglePrivilegeCheck(SeExports->SeTcbPrivilege, UserMode)) {
   // Continue...
 } else {
   return STATUS_PRIVILEGE_NOT_HELD;
 }
}

So that’s awkward, TCB privilege is only granted to SYSTEM users, not even to administrators. While as an administrator it’s not that hard to get a SYSTEM token the same can’t be said of LocalService or NetworkService accounts. At least let’s check if impersonating SYSTEM with TCB privilege will work to create a prefix:

create_prefix_working.PNG

We now have a new prefix called Badgers, so let’s try and create a new named pipe as a normal user under that prefix and see if it does anything interesting.

create_pipe_access_denied.PNG

We get STATUS_ACCESS_DENIED returned. This it turns out is because the driver will find the largest prefix that’s been registered and check if the caller has access to the prefix’s security descriptor. What’s the security descriptor for the Badgers prefix?
prefix_sd.PNG

Seems it’s just SYSTEM and Administrators group with access, which makes sense based on the original caller being SYSTEM. Therefore, if we supply a more permissive security descriptor then it should allow a normal user to create the pipe.

create_with_dacl.PNG

Another question, how can we get rid of an existing prefix? You just need to close all handles to the prefix and it will go away automatically.

As said it’s a pain that you need TCB privilege to create new secure prefixes especially for non-administrator service accounts. Has the system created any prefixes already? There’s nothing obvious in NPFS. After a bit of investigation the Session Manager process (SMSS) creates a number of known prefixes in the function SmpCreateProtectedPrefixes. For example SMSS creates the following prefixes:

\ProtectedPrefix\Administrators
\ProtectedPrefix\LocalService
\ProtectedPrefix\NetWorkService

Each of these prefixes have a DACL based on their name, e.g. LocalService has a DACL which only allows the LocalService user to create named pipes under that prefix.
prefix_sd-2.PNG

It’s worth noting that the owner for the prefixes is the Administrators group which means an administrator could open the prefixes and rewrite the DACL, if you really wanted to screw with the OS :-)

Anyway, if you’re writing a new named pipe server and you want to make it more difficult for named pipe squatting then adding an appropriate secure prefix will prevent other users, especially low privileged users from creating a new pipe with the same name. If someone knows where this is documented please let me know as I think it’s a useful security feature which few know about.

Sunday, 8 October 2017

Bypassing SACL Auditing on LSASS

Windows NT has supported the ability to audit resource access from day one. Any audit event ends up in the Security event log. To enable auditing an administrator needs to configure which types of resource access they want to audit in the Local or Group security policy, including whether to audit success and failure. Each resource to audit then needs to have a System Access Control List (SACL) applied which determines what types of access will be audited. The ACL can also specify a principal which limits the audit to specific groups.


My interest was piqued in this subject when I saw a tweet pointing out a change in Windows 10 which introduced a SACL for the LSASS process. The tweet contains a screenshot from a page describing changes in Windows 10 RTM. The implication is this addition of a SACL was to detect the use of tools such as Mimikatz which need to open the LSASS process. But does it work for that specific goal?


Let’s take apart this SACL for LSASS, what it means from an auditing perspective and then go into why this isn’t a great mechanism to discover Mimikatz or similar programs trying to access the memory of LSASS.


Let’s start by setting up a test system so we can verify the SACL is present, then enable auditing to check that we get auditing events when opening LSASS. I updated one of my Windows 10 1703 VMs, then installed the NtObjectManager PowerShell module.
lsass_open_annotated.png


A few things to note here, you must request the ACCESS_SYSTEM_SECURITY access right when opening the process otherwise you can’t access the SACL. You must also explicitly request the SACL when access the process’ security descriptor. We can see the SACL as an SDDL string, which matches with the SDDL string from the tweet/Microsoft web page. The SDDL representation isn’t a great way of understanding a SACL ACE, so I also expand it out in the middle. The expanded form tells us the ACE is an Audit ACE as expected, that the principal user is the Everyone group, the audit is enabled for both success and failure events and that the mask is set to 0x10.


Okay, let’s configure auditing for this event. I enabled Object Auditing in the system’s local security policy (for example run gpedit.msc) as shown:


audit_policy.PNG


You don’t need to reboot to change the auditing configuration, so just reopen the LSASS process as we did earlier in PowerShell, we should then see an audit event generated in the security event log as shown:


access_event_annotated.png


We can see that the event contains the target process (LSASS) and the source process (PowerShell) is logged. So how can we bypass this? Well let’s look back at what the SACL ACE means. The process the kernel goes through to determine whether to generate an audit event based on a SACL isn’t that much different from how the DACL is used in an access check. The kernel tries to find an ACE with a principal which is in the current token’s groups and the mask represents one or more access rights which the opened handle has been granted. So looking back at the SACL ACE we can conclude that the audit event will be generated if the current token has the Everyone group and the handle has been granted access 0x10. What’s 0x10 when applied to a process? We can find out using the Get-NtAccessMask cmdlet.


PS C:\> Get-NtAccessMask -AccessMask 0x10 -ToSpecificAccess Process
VmRead


This shows that the access represents PROCESS_VM_READ, which makes sense. If you’re trying to block a process scraping the contents of LSASS the handle needs that access right to call ReadProcessMemory.


The first thought for bypassing this is can you remove the Everyone group from your token and then open the process, at which point the audit rule shouldn’t match? Turns out not easily, for a start the only easy way of removing a group from a token is to convert it into a Deny Only group using CreateRestrictedToken. However, the kernel treats Deny Only groups as enabled for the purposes of auditing access checks. You can craft a new token without the group if you have SeCreateTokenPrivilege but it turns out that based on testing that the Everyone group is special and it doesn’t matter what groups you have in your token it will still match for auditing.


So what about the access mask instead? If you don’t request PROCESS_VM_READ then the audit event isn’t triggered. Of course we actually want that access right to do the memory scraping, so how could we get around this? One way is you could open the process for ACCESS_SYSTEM_SECURITY then modify the SACL to remove the audit entry. Of course changing a SACL generates an audit event, though a different event ID to the object access so if you’re not capturing those events you might miss it. But it turns out there’s at least one easier way, abusing handle duplication.


As I explained in a P0 blog post the DuplicateHandle system call has an interesting behaviour when using the pseudo current process handle, which has the value -1. Specifically if you try and duplicate the pseudo handle from another process you get back a full access handle to the source process. Therefore, to bypass this we can open LSASS with PROCESS_DUP_HANDLE access, duplicate the pseudo handle and get PROCESS_VM_READ access handle. You might assume that this would still end up in the audit log but it won’t. The handle duplication doesn’t result in an access check so the auditing functions never run. Try it yourself to prove that it does indeed work.


dup_process.PNG


Of course this is just the easy way of bypassing the auditing. You could easily inject arbitrary code and threads into the process and also not hit the audit entry. This makes the audit SACL pretty useless as malicious code can easily circumvent it. As ever, if you’ve got administrator level code running on your machine you’re going to have a bad time.

So what’s the takeaway from this? One thing is you probably shouldn’t rely on the configured SACL to detect malicious code trying to exploit the memory in LSASS. The SACL is very weak, and it’s trivial to circumvent. Using something like Sysmon should do a better job (though I’ve not personally tried it) or enabling Credential Guard should stop the malicious code opening LSASS in the first place.

UPDATE: I screwed up by description of Credential Guard. CG is using Virtual Secure Mode to isolate the passwords and hashes in LSASS from people scraping the information but it doesn't actually prevent you opening the LSASS process. You can also enable LSASS as a PPL which will block access but I wouldn't trust PPL security.