PowerShell for Security
PowerShell pipes typed objects, not text, which makes it the sharpest way to read a Windows host and the reason attackers live inside it.
Builds on
What you will learn
- -Explain why a PowerShell pipeline carries objects, not text, and what that changes
- -Read a live host with Get-Process and Get-Service and filter on real properties
- -Say what execution policy does and does not stop
- -Explain why one tool is a first choice for administrators and for attackers
windows-permissions ended with a in hand: a process running as some account, at some integrity level, either an ordinary user or, if a check went the attacker's way, a local . A token only decides what an action is permitted to do. It does nothing on its own. Something has to run, read the machine, and act on what it finds. On any Windows box built in the last decade, that something is almost always .
PowerShell ships with Windows. It is signed by Microsoft, trusted out of the box, and it reaches the process list, the , the , and the without installing anything. Administrators automate with it: patching, user accounts, service configuration. Attackers reach for the same tool for the same reasons. One program, two intents. What makes it worth understanding, and what makes it dangerous, is a single feature: the pipeline carries objects instead of text.
A shell that passes objects, not text
A is the program that reads typed commands and runs them. bash, the common
Linux shell, has one currency: text. Every command prints characters, and every
command that reads another command's output reads characters. ps, which lists
running processes on Linux, prints a table of text. Pulling one column out of it
means slicing that text by position with a tool like awk or cut, and the
slice breaks the moment a name runs long or a column shifts.
PowerShell works on a different currency. A , the name for a built-in
PowerShell command, does not print text and hope. It returns objects. An object
is a bundle of named, typed fields, called properties, plus actions it can
perform, called methods. Get-Process returns one object per running process,
and each object carries the process id, the name, the CPU time, the memory in
use, and more, all as real fields rather than columns in a string.
$Get-ProcessThat table is a convenience. Each row is really a System.Diagnostics.Process
object, and Get-Process | Get-Member prints the object's type along with every
property and method it exposes. The distinction drives everything that follows.
The pipe character | sends the output of one command into the next. In bash the
thing crossing the pipe is text. In PowerShell it is the objects themselves,
intact, every property still attached. Three cmdlets do most of the filtering.
Where-Object keeps only the objects matching a condition. Select-Object picks
which properties to keep. Sort-Object orders them by a property. Inside a
Where-Object condition, $_ refers to the current object moving through the
pipeline.
$Get-Process | Where-Object { $_.Name -eq 'lsass' }windows-architecture named as the process that holds in memory.
Finding it is one filter on a real property, Name, not a search through
characters. The same filter written against ps output in bash would depend on
exactly where the name sits in the line, and would break when the layout moved.
A pipeline runs Get-Process | Where-Object { $_.Name -eq 'lsass' }. Why is this more reliable than doing the same filter on the text output of the Linux ps command?
Reading a live host
usually starts with the same question from both sides: what is
running? Get-Service answers it, and every row it returns is a service object.
$Get-Service | Where-Object Status -eq 'Running'windows-architecture explained that many services run as , the most powerful account on the machine. An administrator reads this list to confirm the print spooler came back after a reboot. An attacker reads the same list for a service that runs as SYSTEM whose executable sits in a folder they can write to, because overwriting that file means code running as SYSTEM. The command is identical. Only the reason for reading the output changes.
PowerShell reaches past processes and services. the-windows-registry covered
where configuration and live, and PowerShell mounts the registry as
if it were a drive, so HKLM: is a path like any folder.
$Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'Reading that key is a one-line enumeration step for a defender auditing which programs autostart, and the same step for an attacker hunting persistence someone else left behind, or scouting a place to add their own. The tool is neutral. The motive is not.
An administrator and an attacker both run Get-Service | Where-Object Status -eq 'Running' on the same host. What actually differs between the two runs?
Execution policy is not a security boundary
A common belief is that a setting called locks scripts out. It is worth taking apart, because it is one of the first a beginner points at and one of the first an attacker ignores.
$Get-ExecutionPolicyRestricted sounds like it blocks scripts. It blocks one narrow thing: running a
.ps1 script file through the normal path, for instance double-clicking a file
that arrived by email. It does nothing to commands typed at the prompt or piped
in, and it is sidestepped in seconds. powershell.exe -ExecutionPolicy Bypass -File payload.ps1 sets the aside for that one run. Get-Content payload.ps1 | Invoke-Expression reads the script as text and runs it through the
pipeline, never invoking it as a file at all. Microsoft's own documentation
describes execution policy as a safety feature that stops accidents, not a
security control.
Setting execution policy to Restricted keeps an ordinary user from double-clicking a hostile script by accident. It stops nothing that an operator with a live PowerShell prompt can do, because typed commands, piped input, and the Bypass flag all ignore it. Treat it as a guardrail, never as a wall.
Loud by design
Attackers favor PowerShell for a reason with a name: living off the land, meaning using software already installed on the target instead of uploading a binary that an antivirus engine might recognize and block. PowerShell is already there, already signed, already trusted. It can pull code from a and run it entirely in memory without writing a file to disk. It can reach a remote over WinRM, the Windows Remote Management service on 5985, and run commands there with valid credentials. All of that from a tool no scanner flags as , because it ships with the operating system.
The catch is that PowerShell is one of the most heavily watched programs on Windows. Since version 5, script block logging records the actual code that runs into the , and it records the decoded version even when the original was or otherwise obfuscated. AMSI, the Antimalware Scan Interface, hands script content to the installed antivirus engine for inspection before it executes. The same reach that makes PowerShell powerful is the reach defenders instrument the hardest.
Running enumeration or scripts against a machine without written to test it is unauthorized access, whatever the intent. Build a small lab or use a licensed range, and keep every command inside an agreed .
A host has execution policy set to Restricted, and an attacker already has an interactive PowerShell prompt on it. What does Restricted actually stop them from doing?
On a Windows host or a Windows VM, open PowerShell and run Get-Process on its
own. Read the columns.
Now run Get-Process | Where-Object { $_.WorkingSet -gt 100MB } | Sort-Object WorkingSet -Descending. It keeps only the processes using more than 100 MB of
memory and sorts the heaviest first. Notice that 100MB and WorkingSet are a
number and a property PowerShell understands, not text you had to line up by hand.
Then run Get-Service | Where-Object Status -eq 'Running' | Measure-Object and
read the Count. That is how many services are running right now. Ask the question
an attacker asks: which of them run as a high-privilege account, and could any of
their executables be overwritten? You are not attacking anything. You are
learning to read a host the way both sides do.
The commands in this lesson generate traces, and those traces land somewhere. Script block and process-creation records become specific entries in the Windows event log. windows-event-logs covers that system next: the channels, the event worth watching (4624, 4625, 4688, 4720), and -WinEvent, which is one more object pipeline pointed at exactly the activity PowerShell just produced.