In this article I’m describing yet another way to bypass AV detection/blocking access to LSASS process for credential dumping.
PROLOGUE
This story is sort of a continuation of my previous article. Someone messaged me on Slack asking for an additional feature in the code of my pypykatz tool. In this case the request was about making pypykatz able to parse the live LSASS process with the process handle already acquired by some other -neferious- means. Let’s see what it means and why the user was probably interested in this feature, which was added a day after the request.
NAME AND SHAME
This time I wanted to test my improvements on McAfee MVISION Endpoint Security but I failed. After spending an hour of setup and activating all lsass protection mechanism the product has to offer, it turned out that even the unmodified script was able to access lsass and dump all credentials from the computer.
HOW IT WAS
Before this commit pypykatz was able to extract the credentials from LSASS in the following ways:
- Open the LSASS process and parse the VAS (command:
live lsa
) - Parse minidump files of an LSASS process offline (command:
lsa minidump
) - Use Rekall to get credentials stored in full memory dumps and hibernation files (command:
lsa rekall
) - Use Volatility3 to get credentials stored in full memory dumps and hibernation files
- Act as a plugin for MemProcFs to get credentials stored in full memory dumps.
Sidenote: If you try to get some work actually done with windows memory analysis use MemProcFs. The other two supported 3rd party projects are <insert profanity here>
QUICK NOTE ON OPENPROCESS
The important feature for our current story is the first one (Open the LSASS process and parse the VAS) but how does it work? In a nutshell the following steps are done before the actual credential parsing part begins:
- Acquire debug privilege.
- Find the LSASS process’ PID.
- Open the LSASS process by invoking the OpenProcess method.
- Initialize a virtual reader and pass the process handle to it.
- Begin parsing the VAS using this reader and extract credentials.
Some day I might do a full writeup on how this whole thing works in detail, but not today. For the time being lets focus on the OpenProcess method. Invoking it will give us a handle to the opened process, in our case lsass.exe. Think of it as a number that is used in subsequent API calls to identify the target process we want to perform an operation on. For example one can later call the ReadProcessMemory function using this handle to tell the operating system something along the lines of “I’d like to read the memory of another process, specifically this one that I opened earlier”.
Cool! Now that we have an understanding of what OpenProcess is, and what it lets us achieve, we can kinda see why some AV products get triggered when they detect OpenProcess invoked with the PID of the lsass.exe process.
REQUESTED FEATURE
Looking at the previous section we can understand that the request was to create an interface in pypykatz that bypasses steps 1–3 and initializes the virtual reader with a handle that is somehow already available to the running pypykatz process. Makes one wonder why this was needed? Is it possible to get a handle on the lsass process in a different manner than what was described in the previous section?
THE OTHER WAY
As we established, OpenProcess on LSASS directly is a big no-no for some AVs. Yet, we kinda need to have an open handle to get them credz.
When one searches on the internet for enumerating and working with foreign handles, most results will be discussions of file handles (eg. why can’t I delete this file, how can I tell which process uses it still?), but that should not discourage you from reading them through because the steps laid out in those articles are almost the same for operating with process handles. This gem from 2012 has pretty much everything we need!
Let me break down my thought process with the following questions:
If we (our pypykatz process) can’t get a handle directly, is there any other process that can? maybe. this depends on the system you are on, but more on this later.
Can we identify all running processes that have a suitable handle? Yes! Obtaining the SYSTEM_HANDLE_INFORMATION
struct by invoking the method NtQuerySystemInformation with the SystemInformationClass
parameter set to SystemHandleInformation(16)
will yield all open handles in all running processes! Too bad that the documentation is following Microsoft’s usual doc standard. But hey, smarter people already figured it out so we can be lazy!
Also, using NtQueryObject we can distinguish the different object types which helps us to pre-filter the thousands of handles for process handles specifically.
Can we use the handle which is open in another process in our process? Yes! We can use NtDuplicateObject to get a copy of a specific handle in a foreign process to magically appear in our process. In case you are wondering why the documentation doesn’t link to an official MSDN page, then it is still not too late to get away from anything involving windows api programming and get a respectable job either as a street artist or a hitman because it is only going to get downhill from here.
Ok, sounds straightforward with only a few function calls. Now there is only one problem: there is no direct way in python to access all this *. Since pypykatz is implemented in python I had to create all the ctypes specs for all methods and their structures. Okay it took like 2 hours but still… anyhow.
HOW TO OBTAIN A HANDLE TO LSASS WITHOUT OPENING LSASS
This is a summary of how pypykatz does it.
- Get debug privileges.
- NtQuerySystemInformation will yield all handles opened for all processes. This also includes the PID information of the process for each handle. After this, for each PID/handle:
- OpenProcess with
PROCESS_DUP_HANDLE
privilege. This allows us to duplicate the handle. - NtDuplicateObject will get a copy of the handle of the remote process to our process. Recommended to pass at least
PROCESS_VM_READ
for DesiredAccess. - NtQueryObject will tell us if this handle is a Process handle or something else. (there are a lot of types, and OMG GUESS WHAT IT’S NOT DOCUMENTED)
- If it’s a process handle, QueryFullProcessImageName invoked with the handle will show the process executable path. If it’s
lsass.exe
then we have found a good match and can begin parsing.
Code can be found here
HOW IT IS NOW
Two things have changed since the request.
- pypykatz now supports parsing running lsass process via external process handle using the go_live_phandle method.
- pypykatz now ships with the function to automatically search for open handles to lsass process in other processes and use them for parsing. This can be invoked either by calling the go_handledup method or from the command line by using the
pypykatz live lsa --method handledup
command.
DOES IT REALLY EVADE AV?
Yes. Consider the following cases:
If the AV doesn’t allow OpenProcess to lsass.exe at all, then your only hope is that some other application has a handle already open and you can perform OpenProcess on that application. This should not happen normally, BUT we are talking about a system that has AV installed. Funsies here
If the AV allows OpenProcess on lsass.exe but they are really keen on putting the “ANTI-MIMIKATZ” label on their products then they just hook OpenProcess and check if the DesiredAccess value matches the one mimikatz uses and block the call based on that. The script I present in this article uses a different flag therefore the check is bypassed. The results are the same in the end :)