dfir it!

responding to incidents with candied bacon

Down the Rabbit Hole With Packaged PowerShell Scripts

Several weeks ago, during one of the investigations, I needed to triage a few potentially malicious Windows executables. One of them caught my attention - a .NET binary located in a seemingly legitimate subdirectory under Program Files. At the same time the file was obfuscated (based on a quick look at FLOSS output) and according to VirusTotal it was detected as “potentially malicious” by several antivirus products. Well, I thought, even if the file turns out to be non-malicious, there must be a reason for it to be obfuscated. Oh boy, how little did I know…

(Re)discovery

I opened the file in dnSpy and immediately encountered first obstacle - code was obfuscated with SmartAssembly. Fortunately, de4dot did all the dirty work for me and within seconds I was left with a compact code consisting of several classes. I quickly located main part of the program and realized that I am likely dealing with some kind of loader – part of the code was responsible for reading, decrypting and parsing data from two RT_RCDATA resources.

After poking around a little bit more I found a method that was responsible for creating a new PowerShell runspace and executing PowerShell code retrieved from a previously decrypted resource. I was aware of the tools like p0wnedShell making use of exactly same method to “execute PowerShell code without running powershell.exe” so I thought that I am finally onto something.

At this stage, I just wanted to get my hands on a decrypted PowerShell code as fast as possible. Using CFF Explorer I exported RT_RCDATA resource content to a file. Then I copied C# code responsible for decryption from dnSpy window and pasted it to LINQPad. I also needed to make a few small adjustments to the original code to read the content of the resource from a file and pass it to decryption function.

LINQPad

The code worked well but what I got back was not exactly what I expected. It was still a PowerShell code but it did not look like Invoke-Mimikatz or other offensive module that I knew of. Instead, I was looking at a rather ordinary script written by someone to manage software and patch installation on the workstations. All that effort for nothing? What a disappointment!

One last thing I wanted to figure out was the reason why someone made an effort to package a simple PowerShell script in this way. Was it a custom made packer? Or maybe the file was generated by some tool that I was not aware of?

I took a bunch of unique strings from the analyzed binary and fired up my favorite search engine. I quickly realized that analyzed binary was likely generated using SAPIEN Script Packager - available in products like PowerShell Studio or PrimalScript. Following this path I also found out that I was not the only one to encounter scripts packaged this way:

That’s all sorted out then, my investigation was over – or maybe not?

Nothing is lost…

Judging by the fair amount of posts on the SAPIEN Forums their products seem to be quite popular among developers and administrators. Following my “failed” investigation I started wondering if they are also popular among malware creators? Let’s take a brief look at some of the SAPIEN Script Packager capabilities:

On top of that, the SAPIEN PrimalScript script packager supports many more “engines”, allowing users to package not only PowerShell scripts but also:

  • VBScript and JScript (via WScript/CScript)
  • HTA (via MSHTA)
  • CMD and Batch (via CMD)
  • Windows Script Host scripts

Game Plan

After learning about all these features I was almost sure that there must be some malicious PowerShell scripts packaged this way (spoiler: I was not wrong). I came up with this simple plan:

  1. Collect as many samples as possible
  2. Use ExeToPosh to extract scripts
  3. Analyze extracted scripts

First step went smoothly thanks to Malware Hunting capabilities offered by VirusTotal. I initially created a really basic YARA rule for Retrohunt which resulted in approximately 230 samples and additionally gave me steady influx of 1-3 samples per day when applied to newly uploaded files.

Initial YARA rule used to match executables generated by SAPIEN Script Packager
1
2
3
4
5
6
7
8
rule sapien
{
    strings:
        $sapien = "SAPIEN PowerShell" wide
        $posh = "PoshExe" ascii
    condition:
        uint16(0) == 0x5A4D and all of them
}

The second step was when things started going south. While ExeToPosh worked really well for some of the collected samples it failed to extract data from the rest of the files. I did not want to reverse SAPIEN’s products (the license explicitly prohibits it anyway) so I ended up analyzing 20 or so samples. After several long evenings I knew exactly what was wrong. The files were generated by a different versions of Script Packager - and while mechanics did not change much between versions it was the small things that made a difference.

Here is what I learned about collected executables generated by SAPIEN’s Script Packager:

  • Samples were both native PE executables and .NET managed assemblies
  • Older samples (based on compile timestamp) were not obfuscated. With few exceptions all .NET samples were obfuscated with SmartAssembly
  • Up to four RT_RCDATA binary resources were included in generated executables:

    • Resource ID 1 - “configuration”: stored a binary structure defining runtime settings for the script and the associated files. This resource seemed to be mandatory and was present in every analyzed sample. If “Alternate credentials” option was used credentials were stored in two separate fields: username as clear text string and password blob encrypted with the “configuration key”
    • Resource ID 2 - “library files”: stored a structure containing WSC/COM objects. The objects were sometimes compressed (ZIP) before being placed in the structure, finally the whole structure was encrypted with the “data key”. This resource seemed to be optional
    • Resource ID 3 - “data files”: stored arbitrary files needed by the script. Data in this resource was organized the same way as “library files”. This resource seemed to be optional
    • Resource ID 4 - “script”: stored packaged script. Script was encrypted with the “data key”. Some scripts were also compressed (ZIP) before being encrypted. This resource seemed to be mandatory and was present in every analyzed sample
  • Majority of analyzed samples implemented two decryption schemes: AES and what was internally called “simple decode”. AES, however, seems to be available only in the “high encryption pack” and I have not seen any sample making any use of it

  • Decryption key pairs were constant among samples and were included as clear text strings in generated executables. Older samples used following static key pair: foobar and hsdiafwiuera (“configuration key” and “data key” respectively). Newer samples (starting around release of PowerShell Studio 5.4.138) used the following static key pair: 073E77D0D536421AA25BF60B16746B88 and BC373ACA27924EBEA29D2A22E348ACB4
  • There were at least six different versions of configuration structures - starting with oldest and shortest (800 bytes) and ending with newest and longest (5236 bytes). Each new version introduced new fields or changes in the field format (e.g. character encoding)
  • Some fields present in the configuration structure were not always used by the loader at runtime. Taking this into account it is not always possible to tell if a given option is used without fully reverse engineering specific sample
  • Based on additional strings included in extracted scripts, collected samples were created by a range of products: starting with quite dated PrimalScript 2009 and ending with most recent PowerShell Studio 2018 5.5.150 and PrimalScript 2018 7.4.112

In order to facilitate all of the above options and to speed up the analysis of several hundred samples I decided to create my own tool to statically analyze and extract embedded data. You can find the script on Github. It works well for samples that I have collected but taking into account the variety of Script Packager versions and packaging options it may fail miserably in certain situations.

After running all collected samples through my script I ended up with a log file containing more than one million lines of scripts (mostly PowerShell) and metadata.

…Everything is forgotten (Unless you upload it to VirusTotal)

When I started going through extracted data I was a little bit baffled - I expected to find relatively large number of malicious scripts (after all I sourced all samples from VirusTotal). Out of 250 analyzed samples only approximately 20% turned out to be malicious:

  • 45 instances of fontdrvhost.ps1 - a PowerShell downloader and management script for cryptocurrency miners (e.g. Hybrid Analysis, VirusTotal, extracted script). Interestingly majority of samples contained configuration (e.g. proxy servers) for specific AD domains. All collected samples used msupdate[.]info for C2
  • Test PowerShell Empire stager TrashPayloadMVEC.ps1 (VirusTotal, extracted script)
  • Test PowerShell Empire stager LabTestHttp.ps1 (VirusTotal, extracted script)
  • Cobalt Strike Beacon loader amazon_64.ps1 - set to communicate with 144.208.127.168:443 (Censys). It was one of the few observed samples that made use of execution restrictions - in this case only SYSTEM user was allowed to run the script (VirusTotal, extracted script)
  • PowerShell loader for mimikatz GardeRat.ps1 (VirusTotal, extracted script)
  • Shellcode loader - likely Cobalt Strike stager set to communicate with 192.10.22.35:443 (VirusTotal, extracted script)

At the time of writing only packaged versions of fontdrvhost.ps1 had a decent amount of AV detections. Rest of the files listed above were ignored by majority of AV engines.

That would be it for malicious scripts. So what made remaining 80% of the extracted files - or rather what interesting data was included there? This part turned out to be a real treasure trove of this small research project. Let’s start with some statistics (based on approximately 210 non-malicious samples):

  • Only 4 of them were signed
  • Approximately 40 samples had more than five AV engine detections according to VirusTotal. As far as I can tell this is a threshold where VirusTotal will remove a sample on your request only if you make (false) detections disappear by contacting each AV vendor. This point may be important because:
  • 10 samples made use of the “Alternate credentials” where credentials for RunAs/Impersonate option are placed in the configuration resource. The list of collected credentials included such accounts as: administrator, fulladmin or sccm_services
  • Another 30 extracted scripts included one or more secrets, for example:

    • Numerous invocations of net user and Set-ADAccountPassword including clear text passwords
    • Several sets of credentials for FTP accounts
    • Several sets of credentials for SQL databases
    • Several sets of credentials for SCCM service accounts
    • Malwarebytes Premium license keys
    • Username and password for outlook.com account
    • TeamViewer API tokens
  • The same amount of scripts exposed potentially sensitive data such as internal hostnames, URLs or usernames

  • About 60 extracted scripts included a header exposing organization name and (user)name of script developer. List of companies included known network security vendors, health care, hospitality and entertainment providers
  • Plenty of extracted scripts fell into “IT management” category exposing how different organizations perform endpoint, user and email accounts management, software deployment or IT monitoring

Looking at above points it is clear that majority of these scripts were meant to remain internal to organizations in which they were developed. Unfortunately, the way how scripts are packaged does not seem to make things any better. I can see how a .NET binary obfuscated with SmartAssembly, containing IsDebuggerPresent string and decrypting data from its resource section during runtime can end up with a generic detections by multiple AV engines. Then it is just a short path to situation when someone uploads such ‘flagged’ binary to VirusTotal or one of the many online sandboxes.

It seems to make perfect sense to start monitoring networks and endpoints for presence of executables generated by SAPIEN Script Packager - both for malware detection and prevention of leakage of potentially sensitive data. It is worth noting that there are also other tools that can be used to package PowerShell scripts in a similar way: ISESteroids, Posh2Exe, PowerShell Pro Tools PSPack.exe.