Finding Common Evasion TTPs in PowerShell Logs
TL;DR We tested out the MSBuildShell and PowerLess, two common TTPs to run PowerShell and PS1s without PowerShell.exe and then queried the…
Finding Common Evasion TTPs in PowerShell Logs

TL;DR We tested out the MSBuildShell and PowerLess, two common TTPs to run PowerShell and PS1s without PowerShell.exe and then queried the logs to see what it looked like.
This is essentially Part II in our mini series on Logging CLI Usage
Part I: PowerShell ScriptBlock & Transcript Logging
Part II: Logging common evasion TTPs
Part III: Whitelisting admins and flagging phishy CLI usage
Part IV: Transcript Logging WinRM Sessions
Background
In my previous howto I left whitelisting and checking to see if Transcript Logging logs ‘Living off the Land’ TTPs like using MSBuild instead of PowerShell for a future project. Whitelisting and then flagging discrepancies is IMHO an interesting tripwire as non IT folks won’t normally run PowerShell commands. Hence if Dave in HR suddenly starts running enumeration commands then it’s worth looking into as they probably got phished.
I also wanted to try out a couple different ‘Living off the Land’ methods of running PowerShell commands and PS1s without using PowerShell and see how to detect them in the logs. We went over them here.
Whitelisting
I simply made a quick & dirty whitelist from the members of the Domain Admins group, SYSTEM, Network Service, and Local Service. Get-Phishy then crawls the Transcript Logs and flags anyone who ran PowerShell commands and is not on that whitelist.
Function Get-Phishy {
Param (
[Parameter(Mandatory=$true, Position=0)]
[string]$LogPath
)
#Whitelist Domain Admins, SYSTEM, etc
$DA_Members = Get-ADGroupMember -Identity "Domain Admins" | Select-Object -ExpandProperty SamAccountName
$Whitelist = $DA_Members + "SYSTEM" + "NETWORK SERVICE" + "LOCAL SERVICE"
#Scan PowerShell Transcript Logs for discrepencies from the whitelist
Get-ChildItem -Path "$LogPath\*.txt" -Recurse | ForEach-Object {
$CurrentFile = $_
$FileContent = Get-Content $CurrentFile.FullName -TotalCount 10 # Only need the header
if ($FileContent) {
$RawUser = $FileContent[3] -replace 'Username: ', ''
$CleanUser = $RawUser.Split('\')[-1].Trim()
#Flag if the user is not in the Whitelist
if ($Whitelist -notcontains $CleanUser -and $CleanUser -notlike "*$") {
$RawTime = if ($FileContent[2]) { $FileContent[2] } else { "Unknown:Time" }
$HostLine = if ($FileContent[6]) { $FileContent[6] } else { "Unknown" }
[PSCustomObject]@{
Timestamp = ($RawTime.Split(':', 2)[1]).Trim()
User = $RawUser.Trim()
Computer = ($HostLine -replace 'Computer: ', '').Trim()
LogFile = $CurrentFile.Name
Alert = "Suspicious PowerShell usage"
}
}
}
}
}
# - - Execute on TestWECII - -
Write-Host "Example syntax to use this query: Get-Phishy -LogPath C:\PS_Logs | Format-Table -Autosize"
Write-Host "Use | Out-GridView to get a list that can be sorted"

But does it log Living off the Land?
MSBuildShell
I tested using MSBuildShell and this project to run a fully interactive PowerShell CLI without actually using PowerShell.exe, PowerShell_ISE, VS Code, etc. This is a common Red Team TTP to bypass PowerShell blocking.
One simply downloads the MSBuildShell.csproj from here and uses it via
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe C:\Temp\MSBuildShell-master\MSBuildShell-master\MSBuildShell.csproj

However in our testing whatever is typed into the MSBuildShell still ends up in the Transcript Logs.

The Transcript logs also let us know why the user saw that error about “this script contains malicious content and has been blocked by your antivirus software”. MSBuildShell contains this snippet
INvokE-expreSSIOn ( ( [ruNTiME.IntEROPSeRVICEs.mARSHAl]::PtRtOstRInGAuto( [rUNtIMe.iNTERoPSerVIceS.mArshAL]::SecurESTrIngtoBStR($('76492d1116743f0423413b16050a5345MgB8AHEATQBlADAANABWADQASABIADYAeABiAHkAbABNAGQAcQBHAHEAcgBFAFEAPQA9AHwAZgA1AGMAYgAzADIAYwBiAGQAYQAzADYAYwBhADIAMAA3ADkANQBmADUAZgA3AGQAOQA3ADgAYQAzAGYANAA2ADUANgAyADkAMQA4ADkAYwAwADIAYgAwADkAZABlADYAMAAzADQANQBkADIAZgAxAGQAZgBhADMAMQA3ADAAZABiADMANAA5ADAANwBjADUAOQA5AGMANgBiAGQAYwA1AGUAYwBlADUANgAwADkAYwAwADYAOAAyADEAOQBhADIAYgA2ADMAOQA2ADcAYgA0ADMAOQAxAGQAMwA3AGQANABkADIANAA0AGUAMgAwAGMAZgAwADQAZAAzAGEAZAA3ADgAYwA5ADgAOABjADIANABjAGUANAA0ADUAZQA5AGEAMQBhADUAOQA4ADAAZgAwAGYANgA5ADQAMQA1AGEANgA5ADMAZABiADAAYQBmADMAZgAxAGQANwA4AGIAMgA4ADMAOQBlAGUAOAA2ADQAZAA5AGEAMQBiADIAZABhAGEANgA5ADgAMQBlADcAZQA2ADIAZABiADIAYwBlADIAZABiAGMAZABiADAAYwBhADAAZAA5ADYAYQA1ADEANgBkADMANwA1ADcAZAAwADYAMwAwAGUANQA0ADEAMAA4ADEAMAA5ADIAMQA1ADgANABhADUAYwAzAGMANAAxAGUAZgAyADUAYgBlADkAZgAzADkAOAA5ADAAZQA5ADIAYQA2ADUAOQAzADEAOQA2ADUAZQAxADcAMAA3ADUANQBhAGEAMgA0AGIANgA1ADcANQAxADQANwAyADYANwAxADkANAA0ADYAMgA0ADkANgBlAGYAYQBjAGYANQBlADQAZABhADgANgA3ADEAZgBhAGUAOABlADUAMAA2ADcANAA5ADMAZAA0ADgAZQAwAGQAMAAyADcANAA2AGEANwAzADUAYQA5AGQAMgBkAGQAMwA0AGEANgA0AGQAOQA2ADkANwAwAGEAYwA1ADgANgAxADAAMAA2ADQAMQBjADEAZQBkADAAMQA2ADgAZgAyADMAMwAzADMAYwA5AGYAMQBmADIANwAwADUAZgA2ADUAMwAyADgAMwA0ADcAYgA4ADUAYgBmADMAMAA1ADQANABlAGYAZAAzAGMANwBmADUANgA3AGQAOQAzADEAMgA1ADUAZQAzADcAZgBjAGEANwA4AGIAYgA4ADIAOQBhAGUAOQAxADgANgA0ADcAMAAzAGIAZQBiADUAZAAwADUAOQAxADEANwA4ADkANQAwADIANwBiADgAZQA5AGYAMQAzADAAMQAyADEANQAxADcAMgBiADMAOAAzADcANwBlAGMAZABlAGYAYgBmADUAMwBlAGYAYwAyADkANwA3AGMANQBlADIAYwAyADEANAAxAGEANgBjAGQAMgA5ADIAMwAxADYAZQBmADAAMwAzADUAYwBkAGYANAA=' |conVERTto-sEcuREstRING -kEY (212..181)) ))))
If you ask CW6 Google WTH that does you get that it’s essentially an encrypted payload that’s decrypted, placed in memory, pulled from memory as a string, and run via Invoke-Expression. Basically it’s meant to evade anti-malware.
PowerLess
Using PowerLess is also logged, but only if you are looking at the logs holistically. You will miss it if you only check the Transcripts. This is because attackers can compile their own custom .exe and then run PS1s using it from cmd.exe. This TTP evades both Transcript and ScriptBlock logging in our testing. One doesn’t have to know how to write their own custom .exe to do this as there are many readily available on Google. I used PowerLess.
Simply
- Copy/paste the code from the site here
- Save it in VS Code
- Compile it
- Use the resulting PowerLess.exe to run PS1s without PowerShell
The code can be compiled from VS Code or by simply doing
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /reference:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\system.management.automation.dll /out:C:\Users\mishka\source\repos\PowerLess\powerless.exe C:\Users\mishka\source\repos\PowerLess\Program.cs
Once you have compiled PowerLess.exe one uses it from cmd.exe, Win + R, or other non PowerShell ways of running commands in Windows.
cd C:\Users\mishka\source\repos\PowerLess
PowerLess.exe PowerLess.ps1

This TTP shows up in Windows PowerShell Logs however. This does not log the contents of the PS1, however it clearly logs that someone used a custom *.exe to run said PS1 and the PS1’s filename. The mere fact this TTP was used to run a PS1 is incredibly suspicious and well worth digging into.
In Event Viewer it looks like the below.

However querying for this in Event Viewer is clunky and obviously doesn’t scale well at all. Hence I whipped up a quick & dirty query to flag it.
$LegitPowerShell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\PowerShell.exe"
Get-WinEvent -LogName "Windows PowerShell" | Where-Object {($_.Id -eq 600) -and ($_.Message -like "*WSMan*") -and ($_.Message -like "*HostApplication=*.exe*") -and ($_.Message -notmatch "$LegitPowerShell|powershell_ise.exe|sdiagnhost.exe")} | Select-Object -First 1 | Select-Object *

Note that I am not yet collecting logs from TestClient on the WEC, just the DCs, but the syntax to query for this from the WEC is the same. One just has to ensure that they are collecting Windows PowerShell Logs and not just the Security Log.
I’ll believe it when I see it
I did the above because quite frankly while I had my hunches and you can read theory all day … I prefer to FAFO in the home lab and see it for myself.

This leads me to a certain vendor and what they said about me on LinkedIn recently.
Apparently they were quite butt hurt that I had tried out an incredibly limited free trial they’d offered just over 3 years ago and found it quite lacking. They posted a rather odd rant on LinkedIn that
- Listed an incorrect job title for myself
- Made some truly bizarre and in our Googling disingenuous at best claims
- Seemed to insinuate that I am speaking on behalf of my employer [I’m not, and have never claimed to]
- Argued a quite technical and nuanced point based purely on semantics [and got those wrong according to Merriam-Webster]
- Rambled about how I hadn’t personally contacted them before mentioning their trial in passing [I didn’t contact Broadcom, Microsoft, SpectreOps, etc etc before writing about their products either. Why would I?]
- Most oddly of all, claimed he’s taking me to “Active Directory Security School”
I have been to “AD Security School” already. I have taken CRTP, the CRTP Renewal Exam, PJTP, done a TON of TryHackMe, done ranges like Slayer Labs, done SANS CTFs, passed MCSA shortly before it was retired forever by Microsoft, and perhaps most importantly spent a TON of time in the home lab FAFOing.
I don’t say all that to use the poor debate tactic known as a “False Appeal to Credentials”. I believe my point stands on it’s own here and I validated it for myself in the lab.
I’m happy to go to more free schools, but IMHO their school is far from ideal. It’s incredibly biased, disingenuous, self-serving, and most of all fundamentally flawed. It also doesn’t actually show how to do anything. It all boils down to a claim that auditing is too hard so just buy an expensive tool.
Poor debate tactics
Just off the top of my head, and based on what I have seen on LinkedIn:
- Name calling
- False appeal to credentials
- Strawmen
- Arguing semantics
- Being disingenuous
- Having a conflict of interest

If your argument lacks logic or facts just resort to name calling
The first one is hilarious. They called me a Tuk Tuk driver here, then along comes their best buddy in the comments.

Also one of the most incoherent, grammar error & typo ridden things I’d read that week
The post is rather problematic. For one Microsoft had changed the name to Entra ID over a year prior. For another he clearly didn’t do the research he claimed. Third his usage of all caps and referring to “keys to xyz” sounds suspeciously like the vendor himself, making me wonder if this “vCISO” is even a real person or just a fake LinkedIn account created by said vendor.
Regardless, these two can’t even get their name calling straight. Am I a “Tuk-Tuk Driver” or am I on the bus? If it’s the first one then how am I “licking windows”? Most Tuk Tuks don’t have windows and many don’t even have a windshield.
But hey, as someone who has spent a few years of their adult life working in the third and fourth world where motorcycles are used heavily I take “Tuk-Tuk Driver” as a compliment even though I know it wasn’t intended as one.
Hell I take the bus as a compliment as well. I used to watch college football back when “The Roy Bus” was all the rage.

Summary
I find the whole thing absolutely hilarious. That test I ran three years ago was just a YOLO I did in addition to testing with free tools that do a lot more than one thing. I didn’t name the trial product as I didn’t want that Medium howto to show up on Google as a review of it. It’s rather hard to do a review after all when the free trial only does one thing. I don’t like Broadcom, but I do have to give them praise for continuing to offer VMware Workstation and Player for free. I like Microsoft and of course they give away full featured free trials of everything. Linux is always free and this is why Linus Torvalds is one of my personal heroes.
A future project is to write some queries and/or functions that scan all the logs to catch non whitelisted users who run any commands in any CLI, even legacy cmd.exe. After all most non IT folks don’t know or care what a CLI is and if their account is running one they likely got phished.
I am a big fan of whitelists and use them whenever I can, for example our function for flagging non whitelisted entities who hold ‘Dangerous Rights’.
Well that is a future project and a future howto.
Funny enough while I was showing one of our ‘SIEM Guys’ what log to check to catch PowerLess we found … something running PowerShell to execute a Base64 command. We decoded it and were scratching our heads until we bounced the event off other logs and realized it was the Nessus scanner. Why Nessus is using Base64 is a mystery for another time, Google thinks it might be to avoid formatting issues RE an API, but it was good practice digging into an event and goes to show that you will get some false positives. JMHO though, I’ll take a false positive over a false negative any day.
Resources
MSBuildShell: https://github.com/cn33liz/msbuildshell
PowerLess: https://decoder.cloud/2017/11/02/we-dont-need-powershell-exe/
.NET Framework: https://learn.microsoft.com/en-us/dotnet/framework/
메타데이터
- post_id
- 3e68503564d8
- slug
- finding-common-evasion-ttps-in-powershell-logs-3e68503564d8
- url
- https://medium.com/@happycamper84/finding-common-evasion-ttps-in-powershell-logs-3e68503564d8
- canonical_url
- https://medium.com/@happycamper84/finding-common-evasion-ttps-in-powershell-logs-3e68503564d8
- author_url
- https://medium.com/@happycamper84
- status
- ok
- fetched_at
- 2026-08-18 10:40:08