Feb 20 2025 |
Don’t Touch That Object! Finding SACL Tripwires During Red Team Ops

During red team operations, stealth is a critical component. We spend a great deal of time ensuring our payloads will evade any endpoint detection and response (EDR) solution, our traffic is obfuscated and hard to trace, and our commands will interact with a system in a way that limits the number of possible detection opportunities based on our actions that could thwart our operation; however, even when tiptoeing around a client environment, we have likely all experienced a scenario where we happen to list the wrong directory, read the wrong file, or access the wrong registry key and set off an alert to the Security Operations Center (SOC) to get an investigation rolling. I am, of course, talking about that pesky system access control list (SACL) that made a simple Windows event to let the SOC know someone tried to access something they should not.
DACL vs. SACL
If you have spent some time in the field, you are likely familiar with DACLs and SACLs, but I will do a quick recap to refresh some minds and educate the rest. We will start with the securable object. From Microsoft’s documentation: “A securable object is an object that can have a security descriptor. All named Windows objects are securable. Some unnamed objects, such as process and thread objects, can have security descriptors too.” So, any securable object can have a security descriptor applied to it that can contain access control lists (ACLs). We are talking about files, registry keys, processes, pipes, services, etc. The ACLs within the security descriptor come in two flavors: discretionary access control lists (DACLs) and SACLs.
Most people are more familiar with the DACL, which determines whether a security principal attempting to access a securable object in question is allowed to do so based on allow or deny entries. This is done based on several factors in the access token, but in short, we can equate it to the doorman at a bar. A user attempts to access the bar and presents their ID to the doorman, then the doorman checks their ID and allows or denies them entry based on the information provided. SACLs, on the other hand, are more like a logbook. They are not determining access; they only log whether the security principal succeeded or failed to access the securable object. We can think of this as a scribe standing next to the doorman at the bar, writing down the names of every person who attempts to access the bar and whether the doorman allows or denies them.
We have seen the use of these technological trip flares increasing lately. While the increase in SACL usage is not bad, it does mean that we need to be even more careful about what we access in an environment. Honeypot accounts in Active Directory (AD) can catch the use of tools like BloodHound when it tries to read an AD object that no one was intended to read, the registry could be watched to see if an attacker tries to access local security authority (LSA) registry keys, or it could be as simple as a “password” file on a share set to let defenders know if someone tries to access some suspiciously sweet administrator credentials. As organizations and defenses mature, it is becoming more crucial for red teamers to know what we should not risk touching.
Enter SACL Scanner
To help with this, and learn C along the way, I created a simple C program called SACL_Scanner to aid fellow red teamers in identifying the configured trip flares so we can avoid them. Currently, it will scan for SACLs on three local Windows securable objects and AD: registry keys, services, files/directories, and AD objects. It is also compiled to run with execute_pe in most C2 frameworks since it is much more likely that a red teamer will be in that scenario rather than directly on a host running programs.
Before we get into the demos, we need to talk about the obvious barrier to what we are trying to achieve: privileges. We will need the SE_SECURITY_NAME privilege corresponding to the objects we are trying to read. This means that we must be at least an administrator or have the SE_SECURITY_NAME privilege assigned to our access token. It is likely that when you are really worried about SACLs on other users’ files, registry keys for the security account manager (SAM), or mucking with services on the local host, you are already an administrator; ergo, it should not be too much of an issue there. However, when trying to read the SACLs on AD objects, we run into a bit of a catch-22 in that we might want to know what objects we should not touch so we can execute an attack path in AD to elevate our access therein, but we need elevated permissions to read the SACLs on AD objects to know which objects we should not touch. Sadly, I do not have a solution for that as that is AD working correctly. Nevertheless, we can still obtain additional information once we have elevated our access, tread lightly, and limit our indicators of compromise (IOCs).
Additionally, something the tool is not going to do is let you know the status of whether auditing itself is enabled. SACLs are two parts that combine to make event logs for detection: the SACL itself on the securable object and the computer audit policy settings determine whether the logs themselves are generated. Both of these must be enabled for a SACL to provide any value. If the SACL is set on an object but auditing is not enabled, the SACL does not really matter. Conversely, if auditing is enabled but nothing has a SACL set, then auditing is not generating anything. One could argue this is only partially true as there are objects such as LSASS that have SACLs set by default, but we will not get into that list here as Microsoft does not make it readily available. In our case, to reiterate, we are only checking for SACLs themselves on securable objects here; not whether auditing is enabled.
For demo purposes, I am running a simple Windows environment with a single workstation and domain controller (DC). For my command and control (C2) framework, I am using the Mythic framework with a Merlin agent running on the workstation. In this case, the agent runs under the context of an elevated user to show the output. Also, forewarning, I will not be covering covert techniques themselves but rather a down-the-middle use case to focus on the tool and output itself.
Alright; now that the background, summary, and requirements are complete, let’s get into the simple demos. We will start with the registry. There is so much information available to us in the registry that we are almost guaranteed to interact with it in some way during an assessment, whether it is intentional or not. But where do we want to start our testing to see which important registry (sub)keys defenders might be watching? Thankfully, one of my defensive cohorts, Luke Paine, already made a sample list in his post of The Defender’s Guide — The Defender’s Guide to the Windows Registry. Included in his detailed coverage of registry SACLs, he provided a .csv file with a list of keys and the registry operation to watch: Highly Targeted Registry Keys.csv. Let’s start with a few items in this list to test our SACL setup in a lab.
The following few pictures show the setup of the audit policies and SACLs so we can conduct testing. If you are unfamiliar with setting this up, you can think of it as a little guide to setting SACLs in your environment. First, for our local host, we go into Local Security Policy, then Local Policies > Audit Policy, and ensure that Audit object access is enabled (Figure 1).

Next, we can start setting up some SACLs. We will use the HKLMSYSTEMCurrentControlSetServices registry key referenced in the Defender’s Guide. For simplicity, open regedit.exe, browse to the key, right-click, and select Permissions (Figure 2).

Select “Advanced” in the security permissions window (Figure 3).

Next, select auditing. If you are unfamiliar with the basic setup of the advanced security window, the permissions tab will show and set DACLs, auditing handles SACLs, and effective access is, as it sounds, testing the access of the account you ask it. In my case, you will see that I have set a SACL to audit anyone in Authenticated Users accessing this registry key (Figure 4).

If we select the SACL, we can see the principal again; the type is set to success auditing, applies to this key and subkeys (inheritance set), and audits on Set Value and Create Subkey (Figure 5).

Now that we have our test SACLs set, we can pick a service to modify and ensure it works. In my case, I went with the OneSyncSvc and decided to change the ImagePath to set up some simple persistence (Figure 6).

Before we start the SACL testing, we open the Windows Event Viewer, navigate to Windows Logs > Security, and set a filter on Windows event ID (EID) 4663: “An attempt was made to access an object” (Figure 7). I just cleared them up to make sure we have a fresh list.

In our elevated Merlin agent, we use a simple run sc config command to modify the binPath of the service OneSyncSvc to instead point to a payload (Figure 8).

After the command, we can double-check that the service changed by refreshing our regedit and see that the ImagePath has changed for OneSyncSvc (Figure 9).

In Event Viewer, we now have a new EID 4663 (i.e., “An attempt was made to access an object”) stating that NT AUTHORITY/SYSTEM accessed the registry key HKLMSYSTEMCurrentControlSetServicesOneSyncSvc (Figure 10). This event is our expected result since we had the SACL set to audit any access and inherit it from Services, so we catch modifications on all services. Opening the event, we see that the requested access was Set key value, corresponding to our modification (Figure 11).


We can double-check things like this ahead of time to prevent tripping the SACL with this simple SACL_Scanner tool. It works with execute_pe (and Octoberfest7’s inline-execute-pe). Running this tool with the “-r” flag followed by the key we want to target will give us the desired information. It will scan the entire hive if we target a hive itself (HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER). We check whether an item has any SACLs, if it is a direct or inherited SACL, and the SACL info we desire to let us know what is being audited (Figure 12). Now, depending on how the SACL is set up, this is not full proof to check without being detected, but I will go into further details on this in the detection section.

Now, let’s check OneSyncSvc directly. It tells us there is a SACL applied to the key but does not give us details on it (Figure 13). This is intentional since it is an inherited SACL. It will display direct SACLs when requested, but inherited SACLs will only display when the verbose flag (-v) is added so we do not get too much information when scanning multiple items.

Rerunning the command with the “-v” flag gives us the complete information we want and some additional security identifier (SID) information (Figure 14).

Adding the “-opsec” flag allows us to run an additional check to determine if the SIDs within the SACL match any SIDs applied to our current access token. We can see that we have a detected match in this case, which lets us know that we should avoid modifying this registry key further, or we would trip the SACL (Figure 15). Note: a known limitation of this is that the tool compares the SIDs in the access token to the SACL, which means any nested groups that would not be added to the access token will not come back as detected since we are not unrolling groups here.

Now, let’s check the SAM SACL to see if dumping the SAM would get us burned. We can start by setting a SACL on HKLMSAM with the same steps. When we try to access a key under HKLMSAM, an event log lets us know someone has tried to access those keys (Figure 16).

When checking HKEY_LOCAL_MACHINESAM with SACL_Scanner, we get the info we want to see, letting us know there is a SACL set and that we should avoid dumping the SAM (Figure 17).

I will run through some of the other flags pretty quickly as they should be understood pretty easily. Using the “-f” flag followed by a file on the host or share will similarly check the SACLs (Figure 18).

Next, we can use “-d” to instead feed it a directory to check the files therein. Notice that we have multiple files here and only one has SACLs applied (Figure 19). Sometimes we can judge based on names and settings. In our example below, we can see that there is a SACL directly applied to my tongue-in-cheek mock honeypot file to alert on reading the file; however, the server_PWs.txt file right above it has none. We can infer that I might not want to touch the more tempting file if I can avoid it.

If we throw the “-opsec” flag into the directory listing, we first check the directory itself to see if we should list files. If we trip a SACL while enumerating file SACLs, we will skip it (Figure 20). This check is nice with a targeted directory but even more important when we use just “-d” without a supplied directory, which will scan the entire C: drive.

Also, while I have not seen it much, we can scan services with “-s.” The flag by itself will check all services, or we can add a specific service to target (Figure 21).

Now, on to some AD checks. We start by ensuring the Audit Policy on the domain controller has “Audit directory service objects” enabled (Figure 22). We do not necessarily need it for our SACL checks, but it is good to include it if anyone needs the extra step to help turn on SACL eventing.

As I stated earlier in the post, the major blocker in reading the AD SACLs is simply having the privilege to read the SACLs objects. I know it’s a bit counterproductive and, sadly, this means that we cannot collect them like we do DACLs with a tool like SharpHound. As such, it will likely be of better use if you try to establish domain persistence versus finding an attack path to rise to the top. The flags should make sense based on what we covered. We add the “-a” flag to target AD followed by the “LDAP://{distinguished name}” of the object we want to target. Adding the “-opsec” flag will again check our SIDs to see if we would trip the SACL, and there is a hash map (the reason the file is a bit big) with the GUIDs mapped to user object class attributes. In our case below, we can see that there is a SACL applied to specterDA, which will trigger on writing to the msDS-KeyCredentialLink attribute (Figure 23). If we have not done shadow credentials so far during our assessment, we know we should not try to do that as our persistence on this DA account.

While the hash map covers those user object class attributes, we can still obtain the SACLs on other objects, like the data protection API (DPAPI) domain backup key. Running the scanner targeting that in my little test lab provides a sample output of no SACLs applied, so we should be fine in a SACL sense to backup that key and do what we need to from there (Figure 24).

Detections
When dealing with SACLs, there will be some fallibility in interacting with a securable object to get the information. The SACLs I commonly see on objects are auditing either modifications of an object or reading the data in a file or registry key. The event log this generates on a host is EID 4663 (i.e., “An attempt was made to access an object”). While this event is the primary log SACL_Scanner is designed to identify in the hope of avoiding generating it, you can use additional logs to detect the tool. For example, we can use EID 4656 (i.e., “A handle to an object was requested”) to get the precursor information to EID 4663. We will still need to obtain the handle to interact with it and read the permissions when reading the object information. We can add “Read Permissions” or “Read Attributes” checks to the SACL to identify SACL_Scanner obtaining the handle to read the SACLs (Figure 25). There are pros and cons to this, as with anything in security, as adding these checks to the SACL will create many more events as standard Windows programs like explorer.exe do their essential functions.

Similarly, in AD, we try to avoid writing the wrong properties by identifying them first, but other SACLs can detect our access attempts. By setting SACLs on reading object properties, specifically the Public-Information property set’s Object-Class attribute containing the NT-Security-Descriptor, we can detect SACL_Scanner looking at the SACLs on an AD object. We can see the GUIDs in an EID 4662 log, showing that we are reading those properties (Figure 26) and comparing them to the Microsoft documentation (Figure 27 and Figure 28). Auditing reading AD objects will generate an immense amount of traffic, so going that route will need to be severely tuned to gain any real value from it.



I am not a detection engineer, but to help with this I have made a basic Sigma rule that should help with detecting this tool and hopefully similar techniques.
That’s all for today. I hope this gives you a deeper understanding of how defenders are leveraging SACLs to detect unauthorized access attempts and how you can use SACL_Scanner to adapt your tradecraft accordingly. By being aware of these audit tripwires, you can fine-tune your enumeration, privilege escalation, and other techniques to remain stealthy.
Remember, effective red teaming isn’t just about bypassing defenses — it’s about continuously evolving alongside them. Stay proactive in researching new detection mechanisms, refining your OPSEC, and understanding the blue team’s perspective.
Until next time, stay sharp and tread carefully.
Don’t Touch That Object! Finding SACL Tripwires During Red Team Ops was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.