Intercepting Logon Credentials by Hooking msv1_0!SpAcceptCredentials
Hooking, Credential Stealing
This lab was inspired by @_xpn_ and his great post https://blog.xpnsec.com/exploring-mimikatz-part-2/ - definitely go read it if you haven't.
In this lab I am going to write a simple DLL that, when injected into lsass.exe, will install a hook for msv1_0.SpAcceptCredentials
routine, intercept logon credentials and write them out to disk.
The purpose of this lab was for me to play around with:
API hooking + intercepting logon credentials
Programatically searching process memory space for byte patterns
Ghidra / WinDBG
Not an OPSEC safe technique. Can be flagged for at least the following:
LSASS loading unusual DLLs
WriteProcessMemory
API usage
Overview
Below is a high level overview of the lab and technique implementation:
LSASS has the
MSV1_0.DLL
Authentication Package module loaded in its memory spaceMSV1_0.dll is responsible for handling interactive logons
SpAcceptCredentials
inside MSV1_0.dll is called by the system when a user successfully authenticates interactively (i.e logon types 2, 10)SpAcceptCredentials
is passed clear text credentialsIf we can hook the
SpAcceptCredentials
, we can intercept those credentialsSpAcceptCredentials
is not an exported function in the MSV1_0.dll, so we cannot useGetProcAddress
to find its location in lsass process memoryIn order to find
SpAcceptCredentials
in memory, we will need to:signature it
scan lsass.exe memory space (actually, for simplicity, just the range of
msv1_0.baseOfImage - msv1_0.sizeOfImage
) for that signature
Once
SpAcceptCredentials
signature is found, we will hook it by redirecting the originalSpAcceptCredentials
to our rogue functionhookedSpAccecptedCredentials
hookedSpAccecptedCredentials
, once called, will:Intercept the logon credentials and write them out to disk
Unhook
SpAcceptCredentials
, so that the originalSpAcceptCredentials
can be called later, so that a user can successfully authenticate and get its logon session created without crashing lsass.exeReinstall the hook
hookedSpAccecptedCredentials
by starting a new thread that will execute with a delay of a couple of seconds. Delay is there to allow for the originalSpAcceptCredentials
to finish executing before it gets patched again, otherwise we would end up in a never ending cycle whereSpAcceptCredentials
would be jump tohookedSpAccecptedCredentials
andhookedSpAccecptedCredentials
would callSpAcceptCredentials
as required in the step 4Call the original
SpAcceptCredentials
with intercepted credentials so that the system can complete the user authentication / logon session creation successfully
Loading msv1_0 Debugging Symbols
First of, let's see if we can hit the breakpoint on msv1_0!SpAcceptCredentials
. For this, let's jump WinDBG and sort load the symbols for msv1_0 module if they are missing.
Let's find the EPROCESS
structure for the lsass.exe:
We can now switch the WinDBG to lsass.exe process's context:
Listing modules loaded by lsass with command lm
shows that we do not have symbols for msv1_0.dll loaded:
...although the module itself is loaded:
Let's load the missing symbols:
We can confirm the symbols are now loaded:
Let's now set a breakpoint for msv1_0!SpAcceptCredentials
:
Finally, let's see if we can hit the breakpoint by trying to authenticate for a new logon session with a runas
command:
While we are at it, let's take a look at the start of the msv1_0!SpAcceptCredentials
routine before we patch it later - we will be replacing the first 12 bytes (mov rax + 8 byte address to hookedSpAccecptedCredentials routine + jmp rax) of this routine with a jump to our hookedSpAccecptedCredentials
routine, that will be intercepting any new credentials passed to it:
Inspecting SpAcceptCredentials
Arguments
SpAcceptCredentials
ArgumentsOnce the breakpoint is hit, we can inspect what arguments the SpAcceptCredentials
was called with.
Considering that we know the following:
On x64, Win APIs use a
fastcall
calling convention - the first 4 function arguments are passed via registersPrototype of the
SpAcceptCredentials
- it accepts 4 argumentsMembers of the
structure. We are interested in the following:PSECPKG_PRIMARY_CREDPassword - contains a plaintext password
Domain name
DownLevelName - user name
...we can now inspect the values and structures passed as shown below:
Note how we can identify the username spotless
, domain name - WS02
(my local machine name in this case) and the password in plaintext 123456
.
Additionally, below shows that the value contained in the register r8
holds a new logon session id that was created as part of a successful authentication via runas
command:
Signaturing SpAcceptCredentials
SpAcceptCredentials
As mentioned earlier, the SpAcceptCredentials
is not exported in the msv1_0
DLL, so we cannot use Windows APIs to resolve its address in memory, therefore we need to find it ourselves by scanning the lsass process memory space.
In order to do it, we need to find a sequence of bytes in the SpAcceptCredentials
routine, that uniquely identifies it. Per mimikatz's source code, we can use the following bytes for our signature:
My msv1_0.dll is from x64 Windows 10, 1809
If we check the msv1_0.dll
in Ghidra, we indeed find our signature - 16 bytes into the SpAcceptCredentials
function start:
We can also confirm the bytes are present when SpAcceptCredentials
breakpoint is hit, as expected:
We will pass this signature later to our memory hunting routine GetPatternMemoryAddress(..., signature, ...)
in our DLL, that will be injected into the lsass where it will identify the memory address of SpAcceptCredentials
routine inside the lsass.exe process:
HUH - Hooking: Under the Hood
Before we start looking under the hood of lsass.exe, there are a couple of other things to note.
Our compiled and injected DLL will immediately call installSpAccecptedCredentialsHook
once lsass.exe loads our malicious DLL with LoadLibrary
:
installSpAccecptedCredentialsHook
will:
wait for 5 seconds before proceeding - as explained earlier - this allows the original
SpAccecptedCredentials
to be called and finish its execution, before it gets patched againfind
SpAccecptedCredentials
memory address based on the signature discussed earlier - lines 85-86 in the below screenshotread and store the first 12 bytes of
SpAccecptedCredentials
in memory - these bytes will be used to restore the function to its original state / unpatch it - line 89overwrite the first 12 bytes of
SpAccecptedCredentials
with a jump to our rogue functionhookedSpAccecptedCredentials
that will intercept any new user logon credentials - line 92-95
Assuming we've compiled the DLL, let's inject it into lsass. I will simply inject it with Process Hacker:
Let's now have a quick look inside the lsass.exe via WinDBG when msv1_0!SpAcceptCredentials
is called.
If we break into lsass, we will see that our module memssp-dll.dll
is now loaded - line 23:
If we disassemble msv1_0!SpAcceptCredentials
, we will notice that the first few bytes of the routine are now different, compared to those we saw earlier before the DLL injection - this confirms the hook was installed:
The first instructions of the hooked function now are:
These instructions came from the below code in our DLL.
mov rax
instruction, where rax is the address of our hookedSpAccecptedCredentials
:
and jmp rax
:
Now, if we remember that our malicious module's memssp-dll.dll
base address was 7FF9CB391000h
and its size was 5e2cbfd1
, it means that our module is mapped in the range [7FF9CB391000h, 7FF9CB391000+5e2cbfd1]
=> [0x7FF9CB391000, 0x00007ffa`2965cfd1]
:
This means that 7FF9CB391000h
as seen in the first instruction of the hooked SpAcceptCredentials
routine, is part of our malicious module since it falls in the range [0x7FF9CB391000, 0x00007ffa`2965cfd1]
:
Moving forward - note that after the trampoline to our rogue function, I've set the breakpoint on instruction rbx, r9
at 7ff9b6955344
:
If we hit the breakpoint msv1_0!SpAcceptCredentials
and and continue running, we immediately hit that second breakpoint at 7ff9b6955344
, however, note that our trampoline mov rax, jmp rax
is now gone:
This is because hookedSpAccecptedCredentials
(previously stored in rax) unhooked SpAccecptedCredentials
by writing back 12 original bytes of SpAccecptedCredentials
before it was hooked, to the start of SpAccecptedCredentials
(orange) and redirected the code back to the start of SpAccecptedCredentials
(lime), so that a new user logon session can be created:
Highlighted in blue is the code that actually intercepts the credentials and writes them to disk. Code in white is responsible for re-hooking the SpAccecptedCredentials
in a new delayed thread, so that the originalSpAcceptCredentials
can finish executing without crashing the system.
Demo
Below shows how user spotless
on a machine WS02
authenticates successfully and its credentials are written to c:\temp\credentials.txt
:
Note that msv1_0 exports a function LsaApLogonUserEx2
that we could have hooked to intercept credentials since it is also passed a structure PSECPKG_PRIMARY_CRED
when a user attempts to authenticate. This lab, however, was focused on the exercise of finding the required function address by scanning the target process memory rather than resolving it via Windows APIs:
SymFromName
It's possible to resolve the SpAcceptCredentials
function address if we have access to debugging symbols like so:
Code
References
Last updated