How to hear the Bloodhound barking

Pepe Pérez Sifre
MercadonaIT
Published in
13 min readJan 13, 2022
Photo by Pepe Pérez Sifre. Detection capabilities at Mercadona

How we are going to achieve it

Bloodhound is a data analysis tool, which is used either by cybercriminals or the Red Team to discover different paths that can be used in order to escalate privileges or move laterally, or alternatively it can be used by the Blue Team to detect these paths and patch them. In this post we will look at the different ways in which this tactic can be detected [1], as well as the particular techniques used [2] [3] [4]

Firstly, we all need to understand how this tool works in order to be able to draw up proper detection rules. Bloodhound uses graph theory to represent “paths” to different “nodes” using the data that is stored in a database, in particular the Neo4j [5], therefore meaning that it does not actually execute any attacks itself. Said data needs to be ingested in some way, and that is the point at where the attack occurs and the point where we can detect the bad guys. In this post we will try to explain how this data collection is performed and then how it can be detected.

BloodHound interface example

Information gathering

Anyone who is familiar with Bloodhound will immediately think of one tool: SharpHound[6], or the python version, bloodhound.py [7] when talking about information gathering and collection.

Both of these tools collect the same information, and they do so in more or less the same way, so we are going to try to abstract the way they work under the hood, so that we can create a common detection system for both cases.

After undertaking executions in a laboratory environment and performing code analysis for both tools, we determined that we can divide the collection process into two parts to make it easier: data collection and session collection.

In the data collection phase, LDAP queries are carried out to the domain controllers, in order to collect all of the information that is available about the directory. This information will subsequently be used for drawing the graph using objects to depict the relationship between them. This will be performed over the LDAP or LDAPS protocol:

Data collection.

In the session collection phase, session information will be collected from all of the computers joined to the domain. This will be done via RPC over SMB:

Session collection

Once all of the data has been collected it will be parsed and stored in the Neo4j database for it to be queried.

The next image includes a quick overview of how Sharphound works, and in this case the collection is also divided in two depending on the protocol that is being used, LDAP for the Active Directory data or SMB for sessions. It is important to mention that unless we specify a collection method, the default will be used, which executes both phases.

Sharphound overview [8]

First phase. Data collection

The information stored in the Active Directory database is retrieved using LDAP queries, Therefore our first objective will be to find out what these queries are by using both the source code and the generated network traffic.

If we wish to retrieve all of the domain information we could expect thousands of requests via LDAP, however, that will not be the case, as if we filter our network traffic capture file by LDAP requests performed, only a few queries appear. “For example, the query shown in the image below will obtain all computer accounts that are not Administratively Disabled:

LDAP query from Wireshark

On the other hand, by analyzing the source code it is possible to find the query seen before

LDAP query from the SharpHound.exe source code

Then, if we collect all the LDAP requests and compare them with the source code, the conclusion is that all the requests can be summarized in the next one:

(|(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913)(primarygroupid=*))(&(sAMAccountType=805306369)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))(|(samAccountType=805306368)(samAccountType=805306369)(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913)(objectClass=domain)(&(objectcategory=groupPolicyContainer)(flags=*))(objectcategory=organizationalUnit))(objectclass=domain)(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913)(samaccounttype=805306368)(samaccounttype=805306369)(objectclass=domain)(objectclass=organizationalUnit)(&(objectcategory=groupPolicyContainer)(flags=*)))(|(&(&(objectcategory=groupPolicyContainer)(flags=*))(name=*)(gpcfilesyspath=*))(objectcategory=organizationalUnit)(objectClass=domain))(&(samaccounttype=805306368)(serviceprincipalname=*)))

The content that is collected by each of these queries is mapped in this table:

LDAP queries and their collection capabilities

Next, and in order to detect this phase, two different detection approaches will be analyzed: tool level and AD decoys:

Tool level

This option is the easiest to implement, however it is also the least effective. This approach will try to detect the use of the collection tools on the endpoints. At a higher level, it can be used to detect the tool itself and its arguments as Florian has shown in this Sigma Rule[9]. In this case, it detects the signature CollectionMethod All.

BloodHound.py execution using Docker

At a lower level, instead of detecting the tool itself, it is possible to detect the queries that are executed. To do so, network detection systems could be used to detect the LDAP requests. The downside of this approach is the use of LDAPS where these signatures will not match. Otherwise, we could use any EDR solution where Advanced Hunting queries can be performed. For example, by performing the following query, it is possible to detect the LDAP request that searches all the accounts which have a SPN (ex. Microsoft EDR):

let timeframe = 7d;
let ldap_filter = dynamic(["(serviceprincipalname=*)"]);
DeviceEvents
| where Timestamp >= ago(timeframe)
| where ActionType == 'LdapSearch'
| extend LDAP = parse_json(AdditionalFields)
| extend AttributeList = LDAP.AttributeList
| extend SearchFilter = LDAP.SearchFilter
| extend DistinguishName = LDAP.DistinguishedName
| where SearchFilter has_any (ldap_filter)
| project Timestamp, DeviceName, SearchFilter, DistinguishName, AttributeList

Both of these rules are pretty easy to implement, however, the overall downside of these types of detections is that they are based on the tool, meaning that they are tightly tied to the machine where the execution is performed, and usually cybercriminals are aware of this. As such, they perform executions from an un-monitored machine, or even from memory like we did to evade the AV layer of detection, making these detections useless, and therefore a completely different approach will be analyzed in the next section.

AD decoys

AD decoys are based on the Deception principle, and the Deception concept is rather simple: to trick the attacker into interacting with resources that have been created solely with that objective, either for detection purposes or to disrupt the attack process. In this case, AD decoys are AD objects like users or computers or even groups that are created by the security team in order to notify whenever they have been enumerated because any kind of reconnaissance over the entire Active directory also includes these decoy accounts.

For these decoys to work, the Windows Security Logs will be used. Each Windows system has nine audit policy categories and 50 policy subcategories, which can be enabled or disabled. The policy that we will use is the Directory Service Access [10] which makes low-level auditing available for all types of objects in AD. This policy is similar to the Object Access policy, however, this one logs events that are related to objects outside the AD, such as files or registry keys, whereas the Directory Service Access logs events that are related to AD objects, such as users or computers.

The subcategory that we are interested in is “Directory Service Access”, and the event ID we will use is 4662 [11](An operation was performed on an object). Activating this audit requires a two-step procedure. Firstly, we must enable the Active Directory Service object access policy on the system, and secondly, we must select the specific objects and define the types of access that we wish to monitor. The steps that must be followed to achieve this are:

First, we can check if this policy is enabled in our environment:

auditpol /get /category:"DS Access"

If it is not enabled, we can enable it with:

auditpol /set /subcategory:"Directory Service Access" /Success:Enable

Next, the decoys objects must be configured as follows:

AD decoys configuration

With this policy, that everyone always performs, for example, a read operation over this object, an event log with id 4662 will be generated. Finally, to check if everything is configured properly, we can execute a Ldapsearch query of the object as follows from the Docker image:

ldapsearch -Y GSSAPI -b "DC=EXAMPLE,DC=COM" -H ldap://DCSERVER:389 "(SamAccountName=HONEYACCOUNT)"

This event is generated on the DC side:

Event ID 4662

For the detection rule, we will have to pay attention to the Object Name field. We will not see the readable name of the audited object, instead, we will see its GUID, so to create the detection rule, we have to look for event ID 4662 and Object Name=GUID AD decoy. Now every time Bloodhound.py or Sharphound are executed this event will occur and an alert can be triggered.

This detection is quite good, but it depends on the size or complexity of the organization where it is used. In this case all the tests were performed in a non-productive environment, where the results were perfect. Every time BloodHound was executed an alert was triggered and only two false-positives were observed during a one month period, and these were well identified and exempted, however, once it was moved to the production environment the number of false-positives was uncontrollable, meaning that this detection can be very accurate if the company is not too big and all of the processes are more or less under control, however, the opposite is true if the Active Directory is very large. Therefore, the traces left over from the second phase of the reconnaissance will be used to complement this first detection

Second phase. Session collection

Once all the data has been recollected using LDAP, these tools utilize the list of computers obtained to recollect user sessions. In summary, what this phase does is that instead of doing pings to check if a computer is alive, it checks if port 445 is open, and if it is available it will collect sessions from the computer using for example, in the SharpHound agent, the win32api function NetSessionEnum for user sessions, the NetWkstaUserEnum for user sessions (privileged mode), and the NetLocalGroupGetMembers for local group members. All these are performed via RPC over SMB.

To identify possible detection methods, we will execute this API function manually (NetSessionEnum) so that we can understand what kind of information is obtained, and how it can be used by the cybercriminal.

As we can see in the Microsoft Documentation[12], in order for the NetSessionEnum function to be executed it requires at least level 10 privileges, that is to say domain authenticated privileges (we can check it out on the log event shown before, where access mask equals 0x10). It will query a computer each time it is called, and it will return the IP for where the session originated from and a username.

If we execute the net session command on a server, if nobody has accessed it yet, the response will be empty, however, if we establish an SMB session from a computer joined domain, with, for example, the command net view \\Server-Name\ , if we then execute the net session command on the server again, that session will be shown as follows:

Net session execution on the server

For the moment everything makes sense, we can access a share of a server and inside the server it is possible to check all the sessions, however, the information leak occurs if we execute Get-NetSession from PowerView[13], from a remote machine and we also obtain that information:

Get-NetSession execution from a PC

So if that information is accessible, what can a bad guy do with it? It helps to map where every user has a session on the domain, for example, the information shown above, lets us know that the user mvazquez requested a resource from DC-Company from the IP 192.168.1.20. This information confirms that mvazquez currently has an active session on 192.168.1.20. This information is important given that it indicates that the mvazquez’s credentials should be in Lsass memory on 192.168.1.20, and if that account has any type of privileges, we could attempt to extract their credentials in order to move laterally or vertically.

Now that we have seen what information has been gathered and how it can be used, it is time have a look at the detection capabilities. On the first level, we are going to have a look at all of the different tools that can be used to recollect that information and check how these can be detected. The tools include Sharphound and BloodHound.py, however, as mentioned previously we can also get all of that information from PowerView using Get-NetSession, or if we do a quick Google search other options like NetSess will come up, so if we only try to detect session collection using Sharphound and BloodHound.py, we will be leaving other recollection options behind. Also, as explained in phase one, tool detection is not the best option, so we will try to abstract the behaviors of all these tools.

If we keep diving deeper into the different layers of detection, we will obtain a table such as this one included below (If you want to know each of these I highly recommend you to check out this presentation [14] from the SpecterOps team.)

User Session Enumeration detection.

The overall idea behind this table is that we need to use a row at a layer where all recollection methods could be detected, that is to say, we could build a detection for each of these tools individually, but ultimately it will be impossible to create a detection for every single tool that could ever exist, so our main goal is to supplement the tool detections for non-tools, with a kind of technique based detection, like for example, modules being loaded, or API functions being called or RPC requests over the wire.

For example, if we try to detect this session collection at a Windows API Functions level, we could detect Sharphound, Get-NetSession, and NetSess, however we will not be able to detect BloodHound.py because if we review the SharpHound and Get-NetSession code, which are both publicly available on Github, we can see that they rely on NetSessionEnum..

NetSessionEnum usage on SharpHound

However, if we try to look for this code on the BloodHound.py script we will not find it:

NetSessionEnum search results on BloodHound.py

And this is because BloodHound.py directly calls the RCP procedure NetrSessionEnum, and in doing so it bypasses all of the operating system’s API functions.

hNetrSessionEnum usage on BloodHound.py

Therefore, the detections that we should rely on are the RPC Interfaces layer or the Network Protocol layer. On the Network Protocol, we should ask ourselves questions such as, does this technique require network connectivity? which port and protocol are used?, and above all, what differentiates this activity from other possibly benign activity? Taking into account the fact that the srvsvc named PIPE is added by Microsoft by default, we cannot just presume that if there is any request it must be bad. To analyze how this request is performed, we will use Wireshark again and execute each of the tools we have seen so far.

For SharpHound, Get-NetSession and NetSess, we see the NetSessEnum request:

NetSessEnum from Wireshark

So, we could expect the same results with bloodhound.py, however that is not the case as shown below:

BloodHound.py session recollection from Wireshark

That’s because bloodhound.py under the hood uses Impacket, and Impacket uses SMB3 which encrypts SMB traffic so it’s impossible to check if it calls NetSessionEnum or NetrSessionEnum as it should, then if at a payload packet level, we can’t detect anything, we will have to try it a higher level.

At the application layer, (for each session collection) in Wireshark, we firstly see the DNS request because we have the hostname from the LDAP queries but not the IP.

DNS Request from Wireshark

The 3-way handshake with the computer, the data exchange, and finally the end of the session. Until here, everything looks normal, but if we zoom out, something very interesting appears, hundreds of connections to port 445 from the same source Ip to different destination IPs in a short period of time.

Session recollection from the SIEM point of view

Therefore, if we characterize the requests that are made on our network, we could implement a pretty good detection that looks for the peak of requests to port 445 from the same source IP to a lot of different destination IPs.

Conclusions

We have seen two different ways in which the execution of the recollection tools used to inject the data into BloodHound can be detected, however, quite a good detection can be performed if we put it all together, that is to say, if we detect that a user from a specific source IP has enumerated a certain decoy object that we have created and that it immediately starts to connect to port 445 of almost all the endpoints we have, this would be a strong indicator that we are undergoing a reconnaissance.

In conclusion, depending on the size of the company, the accuracy we desire, and the FPs we are disposed to tolerate, different coverages will be obtained. The next table summarizes the best option for each case.

Relation between detection methods and company size

Finally, with both rules, can we be completely sure that a BloodHound reconnaissance will be detected every time it is executed? Of course, not. There are many bypasses for these or any other detection, such as collecting all of the data we need during different periods of time using the different modules provided by the tools themselves, making the rules unable to correlate. Therefore, detection also depends on how noisy the bad guys are.

Contributors:

  • Francisco Mateu Sanchez
    Incident Response Manager
  • Luis Miguel Agudo
    Red Team Lead
  • Mario Andrés
    Chief Information Security Officer

--

--