Skip to content

tomstryhn/PowerShell-InMemory-Execution

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 

Repository files navigation

PowerShell-InMemory-Execution

You might have heard "InMemory Execution", mentioned at some point if you work with CyberSecurity or IT in general, if not, it doesn't matter, I will try and get you up to date on the term, at least in regards to PowerShell and InMemory Execution of scripts.

Educational material, for defenders and lab use only.

This repository exists to help IT and security practitioners understand how InMemory Execution works, so they can detect and defend against it. The techniques are widely documented, used by real adversaries, and covered by every major EDR/AV product. Nothing in this material is novel, but reading about something and trying it are different things.

  • Only run the sample code on systems you own, or have explicit written permission to test against.
  • Prefer an isolated lab (ie. a VM snapshot you can revert) over any machine connected to corporate or production infrastructure.
  • Do not use these techniques against systems, networks, or users you are not authorised to assess. Unauthorised use is illegal in most jurisdictions regardless of intent.

The author accepts no responsibility for misuse. Read the Detection section, the point of understanding the offence is improving the defence.

Content

Script execution explained

Detection

Final thoughts

Script execution explained

To get the big picture let's start with the basics:

Normal PowerShell script execution

The normal way of creating and running a PowerShell script, is to use some sort of code editor, some uses the PowerShell ISE, Notepad++, personally I use the VS Code. Basically you write a snippet of code, and saves the file with the '.ps1' extension, this is the default PowerShell script file extension. For this demonstration you can write the following in your preffered editor:

Write-Host '####################'
Write-Host '### Hello World! ###'
Write-Host '####################'

Save the file as 'HelloWorld.ps1'

Now you are able to run this code from a PowerShell session, of course depending on the Execution Policy, whole different talk, but assuming you wrote some code that actually works, and the Execution Policy on your computer lets you run the script, you should be able to run the newly created script by simply opening PowerShell on your computer, and navigating to the file location, and typing the name of the file like this:

PS C:\> .\HelloWorld.ps1

####################
### Hello World! ###
####################

This is a very basic example, to get the idea of how a normal script execution works. So what did you do? You SAVED a PowerShell scriptfile on your harddrive and ran it in a PowerShell session. The big word here is SAVED, why? Well most Anti-Virus and Anti-Malware programs, even older or cheap ones, will almost certainly scan any new files on your harddrive, especially scriptfiles. Some Administrators actually blocks all script-files outside predefined known paths, which in some cases actually can prevent you from saving the file with the '.ps1' extension. This makes the job of hackers a little bit more difficult, but they found away to get around this issue:

InMemory Execution

Basically, what InMemory Execution means, is that the code will only exist in the memory of the session, where it will be executed. But how can you get the script onto the computer without saving it to disk, you might ask? Variables is the quick and easy answer, you get your code loaded into a variable, which only exists in the memory, and then you Invoke the code into your session.

Sample time:

$remoteURL = 'https://raw.githubusercontent.com/tomstryhn/PowerShell-InMemory-Execution/main/codesamples/VeryFriendlyCode.ps1'       
$remoteCode = (Invoke-WebRequest -Uri $remoteURL -UseBasicParsing).Content  
Invoke-Expression -Command $remoteCode

Now remember to check the remote code you are about to run, you can do this by copying the url in the $remoteURL, and opening in a new tab in your browser, to be sure what you are about to execute. When you have validated that it's not something dangerous, you can copy the above code into a new PowerShell session, you should see something like what you generated in the sample from earlier.

But now it's red. MAGIC

What happened? Well on a lowlevel explanation line-by-line, first we created a variable $remoteURL and put in the URL for the code we want to execute, without saving it onto the disk. Then by using a builtin feature of PowerShell, called Invoke-WebRequest, which simply acts as a browser and fetches all the information from the $remoteURL. We then select the content, since that's all we need, by using the (In..RL).Content. Now we actually have the code we want to execute in 'memory', so now all we need to do is execute it, this is done by using the Invoke-Expression, which runs the code, and returns the result. All this is done by using a variable, so we skipped the part where we had to save the script onto our harddrive, hence InMemory Execution.

Detection

"Never touches disk" is catchy, but it is not the same as "leaves no trace". InMemory Execution of PowerShell lights up multiple signals that defenders can, and should, be collecting. If you administer or defend a Windows estate, these are the places to look.

PowerShell logging

Windows has three builtin PowerShell logging features, and together they defeat most of the practical value of running scripts "from memory":

  • Script Block Logging (event ID 4104, channel Microsoft-Windows-PowerShell/Operational). When enabled, PowerShell records the full, decoded text of every script block it compiles, regardless of whether the code came from a file, a variable, Invoke-Expression, -EncodedCommand, or a remoting session. This is the single most valuable PowerShell telemetry source, it sees through base64 encoding and obfuscation wrappers, because PowerShell has to decode the script before running it.
  • Module Logging (event ID 4103). Logs pipeline execution details (commands, parameters, output) for configured modules.
  • Transcription. Writes a readable transcript of every PowerShell session to a protected folder. Useful for incident response, less useful as a realtime detection signal.

All three are enabled via Group Policy (Computer Configuration, Administrative Templates, Windows Components, Windows PowerShell), and should be forwarded to your SIEM. A quick way to verify Script Block Logging is actually catching InMemory Execution:

    PS C:\> Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 20 |
                Where-Object Id -eq 4104 |
                Select-Object TimeCreated, @{n='ScriptBlockText';e={$_.Properties[2].Value}}

After running the Invoke-Expression sample earlier in this README, you should see the fetched script body appear verbatim in a 4104 event, disk or no disk.

AMSI, the InMemory scanning hook

The Antimalware Scan Interface (AMSI) is a Windows API that lets AV/EDR products scan content after it has been decoded in memory, but before it is executed. PowerShell is an AMSI provider, so when you call Invoke-Expression on a string, that string is submitted to AMSI, which hands it to Microsoft Defender (or any third-party AV that implements the AMSI provider interface) for inspection.

This is why many "just download and IEX" payloads get blocked even though they never touch disk, AMSI sees the fully reconstructed script right before it runs. When a block happens you will typically see:

  • PowerShell raising a "This script contains malicious content and has been blocked by your antivirus software" error.
  • A Defender event in the Microsoft-Windows-Windows Defender/Operational log (event IDs 1116/1117).

AMSI bypass techniques exist, and are a regular cat-and-mouse target for offensive and defensive research. Keep Defender definitions current, keep AMSI enabled, and alert on AMSI-provider tampering.

Network and process telemetry

Because the InMemory pattern nearly always involves fetching the payload over the network, defenders get a second bite at the apple from non-PowerShell telemetry:

  • Proxy, DNS, and firewall logs will show Invoke-WebRequest and DownloadString calls reaching out to paste sites, raw GitHub URLs, or other first-stage infrastructure. Hunt for PowerShell user-agents (the default is along the lines of Mozilla/5.0 (Windows NT ...) WindowsPowerShell/5.1.*) talking to rare or newly registered domains.
  • EDR and Sysmon process telemetry, in particular Sysmon event ID 1 (process create) and event ID 3 (network connect from powershell.exe), catches the invocation context (command line, parent process, integrity level). Classic red flags:
    • powershell.exe spawned from Office, Outlook, wmiprvse.exe, mshta.exe, rundll32.exe, or a browser.
    • -EncodedCommand or -enc with a long base64 blob.
    • IEX, iex, Invoke-Expression together with Net.WebClient, DownloadString, or Invoke-WebRequest on the same line.
    • PowerShell making outbound HTTP(S) connections to IPs or domains not on an approved list.

None of these signals is proof of compromise on its own, but together they give defenders high-confidence detection, even when the script itself "only lives in memory".

Hardening that reduces the attack surface

Detection is half the picture, reducing what an attacker can run in the first place is the other half:

  • Constrained Language Mode (CLM) limits PowerShell to a safe subset of the language that cannot call arbitrary .NET APIs. Combined with an application-control policy, this blocks most weaponised InMemory payloads outright.
  • Windows Defender Application Control (WDAC) or AppLocker enforcement in Allow-mode is what triggers CLM for unsigned or untrusted scripts.
  • Execution Policy (RemoteSigned, AllSigned) is not a security boundary, it is trivially bypassed and Microsoft documents it as such. Do not rely on it.
  • Disable PowerShell v2 (Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2). PowerShell v2 predates AMSI and Script Block Logging, and is a well-known downgrade target.

Final thoughts

Now keep in mind that this example was done with a simple and safe script, you could verify before executing, but by nesting functions and scripts inside a 'container' function or scriptfile, hackers are able to execute malicious code and tools like BloodHound for reconnaissance in victims environments. More and more, of the AntiVirus, AntiMalware and Defender software, are able to detect these attempts, some might be blocked based of the content of the variable, since some of the software are monitoring the memory aswell. The securitysoftware will recognize known 'signatures' of these malicious 'packages' when they are attempted executed, some already when they are attempted fetched, but the hackers are also getting better and better at cloaking their scripts, by rewriting them or rearrange the code, so the signatures changes.

So it's more less a cat and mouse game.