A note on evidence
To keep the confidence level clear, the article uses three labels:
- Sandbox-confirmed: we saw it directly in the Windows or Linux ANY.RUN run.
- Documentation-confirmed: the vendor or operating-system documentation supports it, but our sandbox did not reproduce it.
- Not verified in testing: it is plausible or expected, but the telemetry available to us could not prove it.
Research highlights
- Explains direct and relayed mesh connections and why both weaken domain- and IP-based detection.
- Describes how Tailscale implements DNS and routing on Windows and Linux.
- Documents two sandbox detonations of version 1.102.2: Windows 10 and Ubuntu 22.04.2.
- Identifies registry, file, firewall, netfilter, service, network and control-plane artefacts.
- Provides syntax-validated KQL for Microsoft Defender for Endpoint and Microsoft Sentinel.
- Provides YARA rules for identifying Tailscale and Headscale artefacts in binaries and exported text.
- Distinguishes legitimate software presence from unauthorised or malicious use.
Background: what mesh means
At its simplest, a mesh VPN gives every enrolled node a stable virtual address and lets nodes talk directly where policy allows. That is different from a traditional hub-and-spoke VPN, where traffic usually passes through a central gateway.
Most mesh products split the job into two parts:
- Control plane: handles authentication, node registration, policy, key exchange and peer discovery.
- Data plane: carries the encrypted traffic, either directly between peers or through a relay.
Tailscale hosts the control plane for its customers, while Headscale offers a self-hosted, API-compatible alternative. On the endpoint, however, the client behaviour is much the same. That means most of the host artefacts covered here apply to both. The main difference is on the network: a Headscale-managed client talks to the operator’s server rather than Tailscale’s hosted control plane.
Direct and relayed connections
In practice, both nodes are often behind NAT. The coordination service helps them try to establish a direct UDP path. If that does not work — because of symmetric NAT, restrictive egress rules or similar network constraints — the client falls back to relaying the already encrypted traffic through vendor infrastructure. Tailscale calls these relay servers DERP servers.
The payload stays end-to-end encrypted either way. From a network sensor’s point of view, the only thing that changes is who the client appears to be talking to: the peer itself or a legitimate relay.
Why reputation-based detection struggles
Tailscale’s MagicDNS uses the fixed resolver address 100.100.100.100 and device names beneath .ts.net. Tailscale and compatible Headscale deployments assign node addresses from the RFC 6598 carrier-grade NAT range 100.64.0.0/10.
That causes two practical problems for detection:
- The domains and infrastructure are legitimate and shared by benign customers.
- Routing is selective: only mesh destinations use the overlay, while unrelated traffic continues through the normal interface.
The better approach is to look for the behaviour needed to make the mesh work: installation, DNS-policy changes, virtual interfaces, routing and firewall changes, control-plane traffic, NAT discovery and, crucially, whether the deployment was actually authorised.
DNS and conditional routing
Windows
Local NRPT rules
Documentation-confirmed. Tailscale primarily implements split DNS through a local Name Resolution Policy Table (NRPT) rule. A standalone client writes local rules beneath:
HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\{<rule-GUID>}
Relevant values include a DNS server of 100.100.100.100 and a namespace containing .ts.net.
The Group Policy-delivered NRPT path is different:
HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig
That distinction is worth keeping. The second path is for centrally managed policy, so its presence does not prove that a standalone mesh client created the rule.
Useful telemetry includes:
- Sysmon Event ID 13 for registry value changes.
- Windows Security Event ID 4657 when object-access auditing and a suitable SACL are configured.
- PowerShell activity involving
Add-DnsClientNrptRule or Set-DnsClientNrptRule.
- Directory Service Event ID 5136 for changes to a Group Policy object on a domain controller.
Not verified in testing. The Windows sandbox reported no registry events at all. We therefore cannot say whether the local NRPT path was written during the run.
Hosts-file behaviour
Sandbox-confirmed. tailscaled.exe wrote to:
C:\Windows\System32\drivers\etc\hosts
The client used the familiar atomic-replacement pattern: write a temporary file, preserve the old copy, then rename the new file into place. The important point is that this was not just a documented fallback — it happened during our standalone test. What the telemetry could not tell us was whether an NRPT rule was created at the same time.
Linux
Linux has no direct equivalent of NRPT, so Tailscale works with whichever DNS-management layer the host already uses.
systemd-resolved
Sandbox-confirmed. On Ubuntu 22.04.2, Tailscale worked with the host’s existing DNS setup and left /etc/resolv.conf alone. In this mode, 100.100.100.100 is associated with tailscale0, along with the tailnet’s routing suffix.
Useful evidence includes:
resolvectl status output for tailscale0.
systemd-resolved journal activity correlated with tailscaled startup.
- D-Bus activity involving
org.freedesktop.resolve1, where such auditing is available.
resolv.conf fallback
Documentation-confirmed; not verified in testing. When no supported DNS manager is available and DNS acceptance is enabled, Tailscale can replace /etc/resolv.conf and preserve the previous file as:
/etc/resolv.pre-tailscale-backup.conf
That backup filename is a useful and fairly specific artefact. Our Ubuntu host had systemd-resolved, so there was no reason for the client to take this fallback path.
Sandbox findings
Windows 10 Standalone Host
The Windows test used tailscale-setup-1.102.2.exe, signed by Tailscale Inc.
Installation and binaries
Sandbox-confirmed. Current-version files were installed beneath:
C:\Program Files\Tailscale\
Confirmed binaries included:
tailscale.exe
tailscaled.exe
tailscale-ipn.exe
wintun.dll
Older deployments may use a Tailscale IPN subdirectory, so fleet hunting should account for both paths.
Driver installation
Sandbox-confirmed. The installer staged the open-source WinTun driver through drvinst.exe before copying it into the driver store. A sandbox will often flag this as ‘dropping a system driver’. That is expected for this type of VPN client and does not, on its own, make the installation malicious.
Persistence
Sandbox-confirmed. The installer created the all-users Startup shortcut:
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\Tailscale.lnk
This relaunches the GUI component at logon. It is separate from the Tailscale Windows service, which maintains the tunnel.
Firewall changes
Sandbox-confirmed. tailscaled.exe launched netsh.exe to add and remove inbound firewall rules. Confirmed rule-name patterns included:
Tailscale-In
Tailscale-Process
These rule names are easy to hunt and relatively high confidence. They show that Tailscale is or was present; they do not tell you why it was installed.
DNS cache activity
Sandbox-confirmed. tailscaled.exe repeatedly launched ipconfig.exe with /flushdns and /registerdns. A burst of these commands beneath a service process is useful supporting evidence when it appears alongside installation, registry, hosts-file or network events.
NAT discovery
Sandbox-confirmed. The client generated a rapid fan-out of UDP connections to port 3478 across many unrelated external addresses. This is consistent with Tailscale’s network-check process, which characterises NAT behaviour and selects an appropriate relay path.
The pattern matters more than any single destination: one process contacting many different UDP/3478 endpoints in a short window.
Control plane and enrolment
Sandbox-confirmed. The unattended service channel used:
controlplane.tailscale.com/ts2021
The server returned HTTP 101 to upgrade into Tailscale’s authenticated Noise-protocol channel.
Interactive enrolment opened the system browser and proceeded through login.tailscale.com and an identity-provider flow. That browser trail gives defenders a straightforward way to distinguish interactive enrolment from unattended deployment with an authentication key.
Interpreting the sandbox verdict
The Windows run received a malicious verdict based largely on generic behaviours such as driver installation, service execution, autorun creation and hosts-file modification. Those behaviours are worth detecting, but they do not answer the most important question: was the software authorised? That requires context from software inventory, asset records and change management.
Ubunto 22.04.2 Standalone Host
The official installation script installed the same client version, 1.102.2. The sandbox reported no overall threat despite flagging some firewall-related behaviours. The contrast with the Windows verdict is a useful reminder: generic sandbox scores are not a substitute for knowing what is approved in your own environment.
Package installation
Sandbox-confirmed. The installer followed a standard APT workflow:
- Added the official Tailscale package repository and signing key.
- Wrote
/etc/apt/sources.list.d/tailscale.list.
- Installed
tailscale and tailscale-archive-keyring packages.
- Enabled and restarted
tailscaled.service through the normal package post-installation process.
The full package-manager chain is still useful during forensic review, but it is standard installation behaviour when the deployment is approved.
Runtime command and paths
Sandbox-confirmed. The service started as:
/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=41641
That gives us three solid host artefacts: the state file, the Unix control socket and the default UDP listening port.
Interface and netfilter behaviour
Sandbox-confirmed. Tailscale created tailscale0 and maintained three named chains:
ts-input
ts-forward
ts-postrouting
The rules included traffic acceptance for tailscale0, UDP port 41641 handling, packet marking, masquerading and anti-spoof controls for 100.64.0.0/10.
The chain names are much more useful than a generic ‘firewall changed’ event. They can be hunted in process telemetry, audit logs and captured configurations.
Control plane and logging
Sandbox-confirmed. The Linux client contacted controlplane.tailscale.com for its authenticated control channel and used Tailscale logging infrastructure. A Headscale-controlled node would instead contact the operator’s control-plane address, so endpoint artefacts may remain stable while network destinations change.
Detection artefact summary
Windows
- Local NRPT path:
HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig
- GPO NRPT path:
HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig
- MagicDNS resolver:
100.100.100.100
- Node range:
100.64.0.0/10
- DNS suffix:
.ts.net
- Install path:
C:\Program Files\Tailscale\
- Legacy install path:
C:\Program Files\Tailscale IPN\
- Service name:
Tailscale
- Driver artefacts:
wintun.sys, wintun.inf, wintun.cat
- Startup artefact:
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\Tailscale.lnk
- Firewall names:
Tailscale-In, Tailscale-Process
Linux
- Service:
tailscaled.service
- Binary:
/usr/sbin/tailscaled
- State:
/var/lib/tailscale/tailscaled.state
- Socket:
/run/tailscale/tailscaled.sock
- Interface:
tailscale0
- UDP listen port:
41641
- Chains:
ts-input, ts-forward, ts-postrouting
- APT artefacts:
/usr/share/keyrings/tailscale-archive-keyring.gpg, /etc/apt/sources.list.d/tailscale.list
- DNS fallback artefact:
/etc/resolv.pre-tailscale-backup.conf — documentation-confirmed but not reproduced in testing.
Network
- Hosted control plane:
controlplane.tailscale.com
- Interactive enrolment:
login.tailscale.com
- Administration:
console.tailscale.com
- Logging:
log.tailscale.com, log.tailscale.io
- NAT discovery: multi-destination UDP/3478 fan-out
Threat hunting with KQL
These queries target Microsoft Defender for Endpoint advanced hunting and Microsoft Sentinel. Validate table availability, retention and telemetry coverage in your environment. Tune thresholds against an approved installation before treating results as anomalous.
1. Local NRPT rule creation
let Lookback = 30d;
DeviceRegistryEvents
| where Timestamp > ago(Lookback)
| where ActionType == "RegistryValueSet"
| where RegistryKey has @"SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig"
| where RegistryValueName in ("GenericDNSServers", "Name")
or RegistryValueData has ".ts.net"
or RegistryValueData has "100.100.100.100"
| project Timestamp, DeviceName, RegistryKey, RegistryValueName,
RegistryValueData, InitiatingProcessFileName,
InitiatingProcessAccountName, InitiatingProcessFolderPath
| order by Timestamp desc
2. Hosts-file writes
let Lookback = 30d;
DeviceFileEvents
| where Timestamp > ago(Lookback)
| where FolderPath endswith @"System32\drivers\etc"
| where FileName =~ "hosts"
| where ActionType in ("FileModified", "FileCreated")
| summarize WriteCount = count(), FirstWrite = min(Timestamp),
LastWrite = max(Timestamp),
Writers = make_set(InitiatingProcessFileName, 10)
by DeviceName
| where WriteCount >= 3
| order by WriteCount desc
3. Tailscale and Headscale process activity
let Lookback = 30d;
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where FileName in~ ("tailscale.exe", "tailscaled.exe", "tailscale-ipn.exe", "headscale.exe")
or ProcessCommandLine has_any ("tailscale up", "tailscale login", "headscale nodes", "--config headscale")
| project Timestamp, DeviceName, AccountName, FileName, FolderPath,
ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
4. Mesh addressing and hosted infrastructure
let Lookback = 30d;
DeviceNetworkEvents
| where Timestamp > ago(Lookback)
| extend Host = tostring(parse_url(RemoteUrl).Host)
| where ipv4_is_in_range(RemoteIP, "100.64.0.0/10")
or Host endswith ".ts.net"
or Host has_any ("controlplane.tailscale.com", "login.tailscale.com",
"console.tailscale.com", "log.tailscale.com", "log.tailscale.io")
| project Timestamp, DeviceName, InitiatingProcessFileName,
InitiatingProcessAccountName, RemoteIP, RemoteUrl, RemotePort
| order by Timestamp desc
5. Firewall-rule creation
let Lookback = 30d;
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where FileName =~ "netsh.exe"
| where ProcessCommandLine has "advfirewall firewall"
and ProcessCommandLine has_any ("Tailscale-In", "Tailscale-Process")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine,
InitiatingProcessFileName
| order by Timestamp desc
6. UDP/3478 fan-out
let Lookback = 7d;
DeviceNetworkEvents
| where Timestamp > ago(Lookback)
| where RemotePort == 3478
| summarize DistinctRemoteIPs = dcount(RemoteIP),
FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
by DeviceName, InitiatingProcessFileName, bin(Timestamp, 5m)
| where DistinctRemoteIPs >= 15
| order by DistinctRemoteIPs desc
7. Linux-specific activity
let Lookback = 30d;
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where DeviceOSPlatform == "Linux"
| where FileName in~ ("iptables", "ip6tables", "xtables-nft-multi", "nft")
| where ProcessCommandLine has_any ("ts-input", "ts-forward", "ts-postrouting")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine,
InitiatingProcessFileName
| order by Timestamp desc
8. Shadow-IT correlation
let Lookback = 30d;
let KnownMeshHosts = dynamic([]); // Populate from the CMDB or approved software inventory.
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where FileName in~ ("tailscale.exe", "tailscaled.exe", "headscale.exe", "tailscaled", "tailscale")
| distinct DeviceName
| where DeviceName !in (KnownMeshHosts)
In practice, the strongest finding is rarely a single event. It is the combination of client execution, DNS or firewall changes, mesh-related traffic and no matching approval record.
For Linux systems without Defender coverage, equivalent controls include audit rules for /etc/resolv.conf, service and package monitoring, and execution auditing for iptables, ip6tables, nft or xtables-nft-multi commands containing the confirmed Tailscale chain names.
Threat hunting with YARA
These rules tell you that Tailscale or Headscale artefacts are present; they do not tell you that the activity is malicious. Check matches against the software allow-list or CMDB before escalating them.
import "pe"
rule Tailscale_Windows_Client_Binary
{
meta:
description = "Identifies likely Tailscale Windows client binaries"
category = "policy-compliance"
severity = "informational"
strings:
$path1 = "Tailscale\\tailscale.exe" nocase
$path2 = "Tailscale\\tailscaled.exe" nocase
$path3 = "Tailscale\\tailscale-ipn.exe" nocase
$path4 = "Tailscale IPN\\tailscale.exe" nocase
$svc1 = "Tailscale IPN Service" wide ascii
$svc2 = "Tailscale" wide ascii fullword
$ctrl1 = "login.tailscale.com" nocase
$ctrl2 = "controlplane.tailscale.com" nocase
$dns = "100.100.100.100" ascii
$suffix = ".ts.net" nocase
$magicdns = "MagicDNS" nocase
condition:
uint16(0) == 0x5A4D and
(any of ($path*) or 2 of ($svc*, $ctrl*, $dns, $suffix, $magicdns))
}
rule Tailscale_Windows_Firewall_And_Persistence_Artifact
{
meta:
description = "Matches Tailscale firewall names and Startup shortcut text"
category = "host-artifact"
severity = "informational"
strings:
$fwrule1 = "Tailscale-In" ascii wide
$fwrule2 = "Tailscale-Process" ascii wide
$startup = "StartUp\\Tailscale.lnk" nocase ascii wide
condition:
any of them
}
rule Tailscale_NRPT_Registry_Artifact
{
meta:
description = "Matches text exports containing Tailscale-related NRPT artefacts"
category = "host-artifact"
severity = "informational"
strings:
$local = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig" nocase
$gpo = "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig" nocase
$genericdns = "GenericDNSServers" ascii wide
$tsip = "100.100.100.100" ascii wide
condition:
($local and 1 of ($genericdns, $tsip)) or ($gpo and $genericdns)
}
rule Headscale_Server_Artifact
{
meta:
description = "Identifies likely Headscale server binaries and configuration artefacts"
category = "policy-compliance"
severity = "informational"
strings:
$bin = "headscale" fullword nocase
$server = "server_url:" ascii
$noise = "noise:" ascii
$derp = "derp:" ascii
$db = "headscale.db" nocase
$module = "github.com/juanfont/headscale" ascii
condition:
$module or ($bin and 2 of ($server, $noise, $derp, $db))
}
rule Tailscale_Linux_Artifact
{
meta:
description = "Matches confirmed Linux process, netfilter and package artefacts"
category = "host-artifact"
severity = "informational"
strings:
$chain1 = "ts-input" ascii fullword
$chain2 = "ts-forward" ascii fullword
$chain3 = "ts-postrouting" ascii fullword
$state = "tailscaled.state" ascii
$socket = "tailscale/tailscaled.sock" ascii
$port = "--port=41641" ascii
$repo = "/etc/apt/sources.list.d/tailscale.list" ascii
condition:
2 of them
}
rule Tailscale_Linux_DNS_Config_Artifact
{
meta:
description = "Matches Linux DNS-integration artefacts in text or log exports"
category = "host-artifact"
severity = "informational"
strings:
$iface = "tailscale0" ascii
$resolved = "org.freedesktop.resolve1" ascii
$backup = "resolv.pre-tailscale-backup.conf" nocase ascii
$tsip = "100.100.100.100" ascii
condition:
$backup or ($iface and $resolved) or ($iface and $tsip)
}
Testing status
The original ruleset compiled successfully with YARA 4.5.0 and matched representative positive fixtures for Windows, Linux, NRPT, firewall, persistence and hosts-file artefacts. Negative fixtures did not match.
That gives confidence in the syntax and basic rule logic, but it is not the same as proving coverage across every release and configuration. Test the rules against approved software in your own environment before deploying them.
Methodology and limitations
Two sandbox runs were used:
- Tailscale 1.102.2 on standalone Windows 10.
- Tailscale 1.102.2 on standalone Ubuntu 22.04.2.
The runs captured installation, process, file, firewall or netfilter, and network behaviour. Two important gaps remain:
- Windows NRPT: registry telemetry was empty, so the run could not confirm whether the local NRPT key was written.
- Linux DNS fallback:
systemd-resolved was available, so the direct /etc/resolv.conf replacement path was not exercised.
The next useful tests are therefore clear: repeat the Windows run with registry collection verified beforehand, and run the Linux installer on a minimal distribution without a supported DNS manager.
MITRE ATT&CK considerations
Potentially relevant techniques include:
- T1572 — Protocol Tunnelling: encrypted traffic traverses the mesh overlay.
- T1090.002 — External Proxy: relays can carry traffic when direct peer connectivity fails.
- T1133 — External Remote Services: an enrolled node can provide persistent remote access.
- T1112 — Modify Registry: local NRPT changes modify Windows DNS policy.
These mappings need some restraint. MagicDNS helps peers find one another, but that does not automatically make DNS the C2 protocol. In the same way, a legitimate client changing DNS or firewall settings should not be labelled as defence impairment unless there is evidence that the change was intended to weaken security controls.
Conclusion
Mesh VPNs are difficult to detect as C2 precisely because the software itself is not malicious. It is signed, widely used and designed to provide reliable remote access. The same features that make it useful to administrators can make it useful to an intruder. That is why domains, IP addresses and reputation feeds are not enough; defenders need to recognise the host and network changes that make the overlay work.
The best opportunities are the practical ones: DNS-policy changes, hosts-file or resolver updates, mesh-specific interfaces and firewall chains, control-plane traffic, NAT-discovery bursts and signs that the software arrived without approval. Put those signals together with inventory and change data, and it becomes possible to find both hosted Tailscale and self-hosted Headscale deployments without treating every legitimate installation as an incident.