Living Off the Land at Scale: Hunting Attackers Who Blend Into Your Own Tools

17 March 2026 | 6 min read | justruss.tech

The 2025 CrowdStrike Threat Hunting Report found that 81% of interactive intrusions used no malware at all. Attackers use tools already present on every Windows system, PowerShell, WMI, PsExec, net.exe, nltest.exe, certutil.exe, to move through the environment. The challenge for defenders is that these tools run thousands of times per day for completely legitimate reasons. Finding the malicious use requires understanding context, sequence, and baseline rather than just looking for the tool name.

This technique is called Living off the Land (LOTL) and it is the primary reason that signature-based detection fails against sophisticated operators.

Why LOTL is so hard to detect

When an attacker runs nltest.exe /domain_trusts to enumerate trust relationships, the event log shows nltest.exe with the /domain_trusts argument. When a legitimate domain administrator runs the same command to troubleshoot a trust issue, the event log shows exactly the same thing. The bytes, the process name, the arguments, identical. Detection requires answering: who ran it, from what machine, at what time, in what sequence, and is that combination normal for this environment?

// Common LOTL reconnaissance sequence (attacker perspective for detection context)
// Phase 1: Domain enumeration
nltest.exe /domain_trusts // enumerate domain trusts
net group "Domain Admins" /domain // list domain admin members
net group "Enterprise Admins" /domain // list enterprise admin members
whoami /all // current privileges
ipconfig /all // network configuration

// Phase 2: Host enumeration
net view /domain // list machines in domain
arp -a // local ARP cache (nearby hosts)
Get-ADComputer -Filter * -Properties * // full AD computer enumeration

// Phase 3: Credential access preparation
query user // find logged-in users
tasklist /v // look for AV/EDR processes
reg query HKLM\SOFTWARE\Microsoft\... // enumerate installed software

// Each of these individually is normal
// ALL of them from the same account in a 10-minute window is an attacker

Detection approach 1: command sequence correlation

// Splunk: find accounts running multiple recon commands in a short window
index=wineventlog EventCode=4688 earliest=-2h
| eval recon_command=case(
 match(Process_Command_Line, "(?i)nltest.*domain_trusts"), "domain_trust_enum",
 match(Process_Command_Line, "(?i)net.*group.*domain.admin"), "domain_admin_enum",
 match(Process_Command_Line, "(?i)net.*group.*enterprise.admin"), "enterprise_admin_enum",
 match(Process_Command_Line, "(?i)whoami.*/all"), "whoami_privs",
 match(Process_Command_Line, "(?i)net\s+view"), "network_enum",
 match(Process_Command_Line, "(?i)arp\s+-a"), "arp_enum",
 match(Process_Command_Line, "(?i)Get-ADComputer"), "ad_computer_enum",
 match(Process_Command_Line, "(?i)Get-ADUser.*Filter.*\*"), "ad_user_enum",
 match(Process_Command_Line, "(?i)tasklist.*\/v"), "process_list",
 true(), null()
)
| where isnotnull(recon_command)
| bin _time span=10m
| stats
 dc(recon_command) AS distinct_recon_types,
 values(recon_command) AS commands_used,
 values(ComputerName) AS source_hosts
 by _time, Account_Name
| where distinct_recon_types >= 3
| sort -distinct_recon_types

Detection approach 2: unusual LOLBin usage patterns

// certutil.exe is used by attackers to download files
// Legitimate use: certificate management, no external URLs
index=sysmon EventCode=1 earliest=-24h
 Image="*\\certutil.exe"
| eval is_suspicious=if(
 match(CommandLine, "(?i)(-urlcache|-decode|-encode).*http"),
 "HIGH",
 if(match(CommandLine, "(?i)-decode"), "MEDIUM", "LOW")
)
| where is_suspicious != "LOW"
| table _time, ComputerName, Account_Name, CommandLine, is_suspicious
// mshta.exe should never load external URLs in a healthy environment
index=sysmon EventCode=1
 Image="*\\mshta.exe"
 CommandLine="*http*"
| table _time, ComputerName, Account_Name, CommandLine

// regsvr32.exe used with /s /u /i for COM scriptlet execution
// The "squiblydoo" technique uses external URLs
index=sysmon EventCode=1
 Image="*\\regsvr32.exe"
 (CommandLine="*scrobj*" OR CommandLine="*http*")
| table _time, ComputerName, Account_Name, CommandLine

Detection approach 3: parent-child process context

// WMI spawning network tools is a classic lateral movement pattern
// WmiPrvSE.exe should not spawn cmd.exe with net.exe commands
index=sysmon EventCode=1 earliest=-24h
| eval ParentName=lower(mvindex(split(ParentImage, "\\"), -1))
| eval ChildName=lower(mvindex(split(Image, "\\"), -1))
| where ParentName="wmiprvse.exe" AND
 ChildName IN ("cmd.exe","powershell.exe","net.exe","nltest.exe",
 "ping.exe","whoami.exe","ipconfig.exe")
| table _time, ComputerName, Account_Name, ParentImage, Image, CommandLine
// PsExec lateral movement signature
// PsExec creates a specific named pipe and service
index=sysmon (EventCode=17 OR EventCode=18) earliest=-24h
 PipeName="\PSEXESVC"
| table _time, ComputerName, Image, PipeName

// Also watch for the PSEXESVC service being created
index=wineventlog EventCode=7045
 Service_Name="PSEXESVC"
| table _time, ComputerName, Service_File_Name

Detection approach 4: BloodHound-style enumeration patterns

// BloodHound/SharpHound generates specific LDAP query patterns
// It queries for ALL users, ALL computers, ALL groups, ALL trusts in rapid succession
// This is visible as a burst of LDAP activity in network logs

// Zeek ldap.log (if you have LDAP logging enabled)
cat /opt/zeek/logs/current/*.log | grep -i "ldap" | \
 python3 -c "
import sys, json
from collections import defaultdict
queries = defaultdict(list)
for line in sys.stdin:
 try:
 rec = json.loads(line)
 if 'base_object' in rec:
 queries[rec.get('id.orig_h', '')].append(rec.get('filter', ''))
 except:
 pass
for src, q_list in queries.items():
 if len(q_list) > 20:
 print(f'{src}: {len(q_list)} LDAP queries in window')
 for q in q_list[:5]:
 print(f' {q}')
"
// Windows Event ID 4662: LDAP queries against sensitive AD attributes
// BloodHound queries for specific properties that generate 4662 events
index=wineventlog EventCode=4662 earliest=-1h
| rex field=_raw "Properties:\s+(?P<props>[^\r\n]+)"
| eval bloodhound_indicator=if(
 // SID History, AdminCount, servicePrincipalName - all BloodHound targets
 match(props, "bf967aba|bf967a9c|f3a64788|028ebdfa"),
 "potential_bloodhound", null()
)
| where isnotnull(bloodhound_indicator)
| bin _time span=5m
| stats count AS ldap_ops, dc(ObjectName) AS unique_objects
 by _time, SubjectUserName, IpAddress
| where ldap_ops > 50
| sort -ldap_ops

Building a LOTL baseline for your environment

// The most important thing you can do: build a 30-day baseline
// of every LOLBin execution in your environment

// Step 1: collect all process creation events for 30 days
// Step 2: for each LOLBin, document:
// - which accounts run it
// - from which machines
// - at what hours
// - with what parent processes
// - with what argument patterns

// Step 3: any deviation from baseline is an anomaly worth investigating

python3 << EOF
# Example: analyse your Sysmon CSV export for LOLBin patterns
import csv
from collections import defaultdict

lolbins = ["certutil.exe","mshta.exe","regsvr32.exe","wscript.exe",
 "cscript.exe","msiexec.exe","rundll32.exe","nltest.exe",
 "net.exe","net1.exe","whoami.exe","ipconfig.exe"]

patterns = defaultdict(list)

with open("sysmon_proc_create.csv") as f:
 for row in csv.DictReader(f):
 exe = row.get("Image","").lower().split("\\")[-1]
 if exe in lolbins:
 key = (exe, row.get("Account_Name",""), row.get("ParentImage",""))
 patterns[key].append(row.get("CommandLine",""))

print("LOLBin patterns in your environment (baseline):")
for (exe, account, parent), cmds in sorted(patterns.items(), key=lambda x: -len(x[1])):
 print(f"\n{exe} | Account: {account} | Parent: {parent.split('\\\\')[-1]}")
 print(f" Occurrences: {len(cmds)}")
 print(f" Sample commands: {cmds[0][:100]}")
EOF