SysmonEnte but not Sysmon End

Introduction

SysmonEnte is a brand new POC of attack against Sysmon, it aims at injecting into Sysmon process to tamper with the events generated, making those unreliable for detection. The tool has been developed by people working @codewhitesec and is described in details in a blog post SysmonEnte Blog Post.

When I see a new attack on Sysmon, I am always very excited about it and I can’t wait to see how the guys did it. I have to confess, after reading the article I felt that the people who worked on this one were very creative and that a lot of work has been put to develop SysmonEnte. After the first read I did feel that this is done for Sysmon and there is nothing we can do to detect this kind of attack. Then I realised, that some weird events might be generated at some point of the attack and I had the intuition I could try to build some detection rules based on those.

In this blog post I will describe my experiments and methodology to attempt at building generic rules to detect SysmonEnte.

Experiment Description

The full experiment description, all the steps to reproduce it as well as the resulting files are available in a GitHub repository. So I will only make here a description of the methodology I usually follow to conduct that kind of experimentation.

  1. install Sysmon in a VM with a configuration to log all events
  2. spin a program to collect all the events locally (to avoid complex setup)
  3. run the attack (it works also with a malware or anything you want to build detection rules for)
  4. revert my VM to a clean state (before attack/malware ran)
  5. collect a bunch of events, during several hours to create a baseline
  6. start to analyse and compare both the traces (with and without the attack running)

The fifth step is not always mandatory, it actually needs to be done only once for a given setup. If we always make our experiments on the same VM with the same setup and same Sysmon configuration, we can fairly assume that the baseline trace would not change a lot, at least not in a significant way to affect our experiments.

Again during this baseline building step, it makes sense to capture events from boot. The main reason for this, is that we want a very accurate baseline, because that is what we will use to test and validate our detection(s) rule(s).If we miss some events from boot, which might trigger the rule, the SOC guys would probably become crazy after the rule is deployed in production and the machines start to reboot. The boot sequence is indeed very specific and it is not rare to see some events only during this sequence. All this to say that the baselining step is crucial if we want to build quality rules.

Analysis

Reading the original SysmonEnt blog post, we really have the feeling the authors thought about every single event that could make the attack detected. However, there is one little sentence that triggered in my mind and started me doing this work. This sentence is this one: “This can be easily circumvented by stealing a token from a System process. We steal the token from an elevated svchost process running as System by only using a PROCESS_QUERY_LIMITED_INFORMATION mask, where we do not need the SE_DEBUG privilege which is often used in detection rules.”. They are talking here about stealing a token from a process running as NT AUTHORITY\System, and this is actually generating a sysmon ProcessAccess event. One might assume this event is common or irrelevant, but I had the intuition it is not. The only way to verify challenge this intuition is to catch the event disect it and look at our baseline to search for similar events.

Here you can see the particular event the privilege escalation step of the attack is generating. We clearly see that it is a process access with PROCESS_QUERY_LIMITED_INFORMATION mask (i.e. 0x1000) event from entenloader.exe to a svchost process running as System

  "Event": {
    "EventData": {
      "CallTrace": "C:\\Windows\\SYSTEM32\\ntdll.dll+a43d4|\\\\vboxsvr\\Sysmon-VS-SysmonEnte\\entenloade
r.exe+3ab9|UNKNOWN(00000000000000C8)",
      "GrantedAccess": "0x1000",
      "RuleName": "-",
      "SourceImage": "\\\\vboxsvr\\Sysmon-VS-SysmonEnte\\entenloader.exe",
      "SourceProcessGUID": "{0bd59c11-58d2-6318-1a03-000000000e00}",
      "SourceProcessId": "5496",
      "SourceThreadId": "7444",
      "SourceUser": "WINDEV2204EVAL\\User",
      "TargetImage": "C:\\Windows\\system32\\svchost.exe",
      "TargetProcessGUID": "{0bd59c11-c4ed-6318-0d00-000000000e00}",
      "TargetProcessId": "796",
      "TargetUser": "NT AUTHORITY\\SYSTEM",
      "UtcTime": "2022-09-07 08:39:46.434"
    },
    [...]
  }

There are two things we can observe in this event. Lets start with the easiest, we see that UNKNOWN code location in the CallTrace field. This keyword is used by Sysmon when it cannot locate the call site of the Windows API opening the target process. Under some circumstances it is phishy because it means the call is done dynamically (shellcode, JIT …). By experience, if you do not known what the SourceImage binary is (which would be the case here if we would not be the initiator of the attack), you can consider this kind of event very suspicious.

Secondly, we clearly see that SourceUser field is a normal user and TargetUser is SYSTEM, which is not something that should happen frequently. It is not supposed to happen too often since a process running as a normal user is not allowed to open a SYSTEM process, even with PROCESS_QUERY_LIMITED_INFORMATION privileges. An additional take from this event, is that we can infer that entenloader.exe is running with elevated privileges, otherwise we would never see this event. Indeed Sysmon logs a ProcessAccess event when the access attempt is successfull. If you are not convinced, you can make a little exercise where you try to open a process running as SYSTEM from both processes running as a normal and privileged user. If you do not have time for doing this, do not worry, I did it for you and you can see an illustration of what I said in the image below.

procprot

Ok, so now we have found an event to work on, and it seems to be the only one, we have to confirm our guess (i.e. that our event should not be generated by too many processes). Why is it important to make sure the event we want to build a rule for is not common with too many other processes:

  1. the more processes we have to whitelist, the more chances we have to forget/miss processes in our whitelist and create false positives
  2. the more we whitelist, the more room an attacker has to bypass the detection rule (injection in whitelisted process, file hijacking …)
  3. complex detection rules are not easy to build, validate and maintain properly

After analysing our system baseline trace, available here we notice that only one other process is accessing other SYSTEM processes from a normal user with access rights that can be used for privilege escalation. And … the culprit is … taskmgr.exe! No real surprise, this guy is continuously checking running processes. So, the detection rule should not be very complex to build.

NB: the detection rule below is encoded in the Gene’s rule format but it should be rather straightforward to translate into any other format

{
    "Name": "HeuristicPrivEsc",
    "Tags": [
        "PrivEsc",
        "Heuristics"
    ],
    "Meta": {
        "Events": {
            "Microsoft-Windows-Sysmon/Operational": [
                10
            ]
        },
        "Computers": [],
        "Criticality": 8,
        "MinSysmonVersion": "v13.34",
        "Author": "0xrawsec",
        "Schema": "2.0.0"
    },
    "Matches": [
        "$src_image_wl: SourceImage ~= '(?i:{{system}}(taskmgr)\\.exe)'",
        "$sync_access: GrantedAccess = '0x100000'",
        "$src_user: SourceUser ~= '(?i:NT AUTHORITY\\\\)'",
        "$tgt_user: TargetUser ~= '(?i:NT AUTHORITY\\\\)'"
    ],
    "Condition": "!$sync_access and (!$src_user and $tgt_user) and !$src_image_wl"
}

With all this we almost forgot the first thing we noticed, that UNKNOWN keyword in the CallTrace field of our event, so lets take a look at our baseline in order to see if we find other events like this. The bad news is that we do see other ProcessAccess events with a normal SourceUser, system TargetUser and UNKNOWN keyword in CallTrace. The good news however, is that all those events only have 0x100000 SYNCHRONIZE access flag (who is rather benign to the best of my knowledge). So it seems we can build a second rule based on that same event generated by SysmonEnte.

{
    "Name": "HeuristicSuspiciousAccess",
    "Tags": [
        "Heuristics"
    ],
    "Meta": {
        "Events": {
            "Microsoft-Windows-Sysmon/Operational": [
                10
            ]
        },
        "Computers": [],
        "Criticality": 8,
        "MinSysmonVersion": "v13.34",
        "Author": "0xrawsec",
        "Schema": "2.0.0"
    },
    "Matches": [
        "$unk_calltrace: CallTrace ~= 'UNKNOWN'",
        "$sync_access: GrantedAccess = '0x100000'",
        "$src_user: SourceUser ~= '(?i:NT AUTHORITY\\\\)'",
        "$tgt_user: TargetUser ~= '(?i:NT AUTHORITY\\\\)'"
    ],
    "Condition": "!$sync_access and (!$src_user and $tgt_user) and $unk_calltrace"
}

Validating those rules on our baseline brings no false positives and catches all the privilege escalation attempts on the SysmonEnte trace. So it seems we have pretty reliable (at least according to our baseline) rules to detect SysmonEnte. We notice that by doing this exercise, we have also crafted generic rules to detect privilege escalation attempts.

We have to keep in mind that those rules have been validated in a lab environment and cannot be considered as production ready. In order to be able to deploy them at a wider scale we would have to challenge them against a bigger events dataset. Once you identified false positives, it is up to you to judge whether you can adapt the rules and whether it makes sense to you to deploy them. All this strongly depends of your environment, your IT policies and IT hygene.

One final note on Sysmon and the version I used for those experiments! We are lucky to have SourceUser and TargetUser fields present in ProcessAccess events since v13.34, without them it would have been much more complicated to create reliable detection rules.

Conclusion

Hey, but you just explained how to detect privilege escalation, not all the fancy things SysmonEnte is doing ! Yes I know, but this is the only thing that we have (thanks to the guys @codewhitesec). Since the attack requires privilege escalation to SYSTEM, why not considering this as an IoC ? I can hear your voice saying: “Yeah but there are plenty of other ways to escalate to system !”. This is a fact, and I would add that it will always be possible, but some ways leave more traces in Sysmon than others and it is not the purpose of this blog post to cover them all. This is why it is crucial to make experiments and dig deeper in order to try to cover the maximum of them.

The other point I would like to attract reader’s attention on, is that running SysmonEnte attack successfully requires administrative/elevated privileges. This is a prerequisite for being able to elevate to NT AUTHORITY\SYSTEM, using the token stealing technique. It is usually a bit harder to elevate from a standard user to SYSTEM, because it requires breaking other kinds of boundaries. So in a decent corporate environment, with a minimum of security policies, the attack should not be straightforward to make without a first local privilege escalation bug exploitation. We also have to think about all the delivery chain of this attack, as well as an eventual persistence mechanism if we want to have a realistic picture of what running SysmonEnte like technique from an attacker point of view would look like. So maybe SysmonEnte is very hard to detect, but all the things coming before and/or after may be easier to detect. So even if some pieces of the puzzle are missing, you might still be able to see the picture.

You might wonder why it will always be possible to escalate from an administrative user to NT AUTHORITY\SYSTEM, it is because Microsoft (to the best of my knowledge) does not consider this as a boundary violation. However breaking from a standard user to SYSTEM is considered as a serious boundary violation. This is definitely in the skill set of some threat actors to use zero day LPE, so depending on your enemies you may be more at risk than others.

I would finally propose a fairly simple idea to try detecting modified Sysmon events. One could create a program to generate canary events for which you know exactly the content and create detection rules triggering when events content deviates from what is expected. I am also a strong supporter of one of the ideas proposed in SysmonEnte blog post, which is the possibility of running Sysmon as PPL. Making Sysmon running as PPL would indeed protect the process memory from being modified by other processes, but it would enable Sysmon to pull events from the ETW Microsoft-Windows-Threat-Intelligence provider. Being able to get those events into Sysmon would definitely raise the bar pretty high for attackers.