Finding base64 encoded commands

Brass Contributor



I put together a query to look for base64-encoded strings on Command Lines where powershell has been executed. So I whipped up the following query:

| where TimeGenerated between (ago(1d) .. now())
| where EventID in ("4688","4104")
| where CommandLine contains "powershell" or CommandLine contains "pwsh"
| extend args = split(CommandLine, " ")
| mv-apply l=args to typeof(string) on ( where args matches regex @'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$' )

 It executes, so syntactically it works, but it is extremely inefficient.

I'm looking for EventIDs 4688 and 4104 in the Security Log, where we have powreshell or pwsh executed. I then use the split() function to parse up the CommandLine column into an array with each distinct element. We then check those elements (exhaustively) using an mv-apply against some regex for finding Base64 strings. 


Is there a better way to do this? Although this executes, Will my logic even work?



5 Replies

@JKatzmandu Have you looked at the "Process executed from binary hidden in Base64 encoded file" rule template to see how that does it?

@Gary Bushey 


It's pretty weak:

| where CommandLine contains "TVqQAAMAAAAEAAA"




Trying to create a rule based detection of Powershell attacks is between hard and impossible. On the one hand based64 is used in valid code and on the other hand evasion is pretty easy. Why should the code be on the command line int the first place. Want the really dire picture? Daniel Bohannon presenatation on PowerShell obfuscation (video, slides) is impresive. 


I think that Defender for End Points (a.k.a. Defender ATP), is the right choice for detecting and protecting from such threats. 

@Ofer_Shezaf We came up with a "hedge" which is pretty good for finding base64 on the command line. It's not exhaustive, but essentially looks for longer command lines with a decent length of a base64 string, and it also has to be a valid string (one that can be decoded by the base64_decode() function.) 


In practice this worked and did find some "shady" items. We actually found items that were double-encoded.


| where TimeGenerated between (ago(7d) .. now())
| where EventID in ("4688")
| where ParentProcessName contains @'\cmd.exe' or NewProcessName contains "powershell" or NewProcessName contains "pwsh"
| where string_size(CommandLine) >= 24        // get rid of CommandLine that is too short.
| extend Evil_Base64 = extract(@'\s+([A-Za-z0-9+/]{20}\S+$)',1,CommandLine )
| where Evil_Base64 != ""
| extend decoded_command = base64_decode_tostring(Evil_Base64)
| where decoded_command != ""
| project TimeGenerated, SubjectAccount, Account, Computer, CommandLine, ParentProcessName, NewProcessName, Evil_Base64, decoded_command


@JKatzmandu : Great to learn. Thanks!