Obfuscating PowerShell is a real art, and there are many ways to encode scripts in weird ways. Luckily, most of them work in the same way: 1. Decoding some string and 2. Executing that string as another stage in the script.
Often this is a task of finding the part that executes the code, removing it, and instead printing the code so you can analyze it further.
I will explain this process with an example. This is taken from the NahamConCTF 2023 - IR challenge, which provided the following PowerShell script:
It starts off with a lot of special characters that are supposed to evaluate into something:
A common way to make a little sense of this is to format it, like adding newlines after ; semicolons. In this case, however, there are semicolons used all over the place, not just as statement enders. To do this more cleanly we'll use the PowerShell-Beautifier script to parse and format these statements, which will then allow us to separate a ${;} from a ; as only the second has a space after it.
First, it defines a few variables with the ${} syntax, and after, it uses those variables in a giant string. The first few variables are some primitives, and the last 3 variables seem to be more complicated but still short. We could statically try to reason with this, but a much simpler way would be to just let PowerShell evaluate it for us. Let's run the first few lines making sure nothing can trigger a payload on our investigating machine:
Here we find a very important string: iex which means Invoke-Expression. This will take a string, and execute it as PowerShell code, which is very common for these obfuscators. We need to be careful to remove this part to make sure our code is not actually run, only the string is evaluated for us.
All the way at the end of the script we find:
...${]}${|}|${;}" | &${;};
We will remove this ${;} now that we know it means to evaluate and run the code, and instead replace it with a Write-Output command which simply prints it to the console:
...${]}${|}|${;}" | Write-Output
Running this safe script now prints the next stage of the script, obfuscated in a different way:
It uses a very similar scheme, building out a script and then evaluating it with iex, literally this time. In a very similar fashion to last time, we'll simply remove the trigger of the payload and only print it using Write-Output:
...
[CHar]86+[CHar]32+[CHar]59 | Write-Output
When we now execute the safe script, we find another stage:
Pretty clearly we can read the string concatenation in $ehyGknDcqxFwCYJz5vfot4T8 is another Invoke-Expression. This is assigned to a New-Alias as PwN. Later we see this alias used on another string, which would be executed as code. So instead, we again replace this with a Write-Console to find what it does:
In this challenge, the flag was found here. But in other cases, you might want to understand the encryptFiles function now to find how files are encrypted, and how they can be decrypted.
For another example that digs more into understanding the payload, see this walkthrough of another piece of obfuscated PowerShell malware.