In a recent assessment our team found itself in a somewhat new situation that resulted in a useful tool we wanted to share with the community. The assessment started with us gaining initial access into a customer’s network. This particular customer had invested significant time and effort into securing their enterprise. The majority of users in the customer’s domain required smart card authentication, credential caching was disabled (no Mimikatz’s sekurlsa::logonPasswords), and there was significant host-based logging (Powershell, Sysmon, HIPS).
After some careful execution, we managed to pivot to a high value server. This server was valuable to us because high privileged administrators across the network used this server to perform administrative tasks. We identified a lesser known N-day vulnerability on the server and coded up a native exploit that got us SYSTEM privileges.
With SYSTEM we gained the ability to impersonate any user that was currently logged-in to the server by either injecting into a user process or merely stealing their user token. While this technique is effective in escalating privileges on the domain and moving laterally, it requires finding active sessions to impersonate. Often this involves sitting and waiting for users to login that have sufficient privileges, and when they log out, you lose the ability to use their accounts. The ideal solution is to find domain user accounts that use credentials instead of smart card authentication.
You may be asking yourself, if you already have the equivalent of domain admin via token impersonation, why are you concerned with getting passwords? There are a few answers to this question. First, having credentials for a high value user account can preserve access to a network for much longer and enable it on demand. Second, sometimes lateral movement is limited to services such as RDP which we currently don’t know of a way to leverage RDP using a stolen user token. Lastly, using credentials of an existing highly privileged user account is far less suspicious than creating a new user or escalating an existing one.
Luckily for us, we found that the customer’s Active Directory setup utilized several high privileged service accounts on various servers across the domain. These domain service accounts used credentials to authenticate in order to operate. Windows stores an encrypted version of the credentials for the domain service accounts for each service in the registry at HKLM:\Security\Policy\Secrets. There are several public implementations for decrypting the credentials from the registry with the most popular being the Mimikatz lsadump::secrets module.
Our problem then became finding which computers had services that run under the context of a domain service account and either manually running mimikatz on each system, or collecting the system and security registry hives and performing this step offline. While not a technically challenging process, we are lazy hackers and would prefer to automate as much of the process as we can. The following is a screenshot of mimikatz dumping the credentials of a service account.
With the assumption that our tool will be running under the context of a user with Administrator privileges on our target computers, we can leverage the Win32 API to query the services on our targets remotely. We can parse the service start name to determine if the running context is something other than normal system level accounts. If we get a hit, we can use the remote registry API to save the system and security registry hives. The hives can then be parsed by mimikatz to decrypt the stored service account credentials. One of the advantages of developing the tool in C++ is we can easily integrate it as a module into any existing native C2 frameworks.
Implementing serviceFu was fairly straight forward. We pulled the lsadump::secrets code from the mimikatz source and integrated it directly into the project. The goal was to only bring in the bare minimum necessary for parsing the registry hives and decrypting the passwords, mostly because we didn’t want to risk any unwanted AV detections that mimikatz tends to fire. It is a mixture of C and C++, created in Visual Studio 2012 and statically compiled so it can be copied and run wherever. It supports inputting targets by hostname or IP, and IP addresses can be in many formats like comma separated lists, CIDR notation, and ‘-‘ separated ranges. Other arguments allow you to specify whether you want to perform some or all of the functions, in case you want to skip registry save or mimikatz.
While we recognize the tool is only useful in certain situations, when those situations arise it is invaluable. Being able to scan a whole domain for clear-text domain service credentials remotely is quite useful. Let us know what you think and feel free to contribute to the Github repo.