Modifying Empire payloads to avoid detection

Intro

For today’s post and the first post of a new website, I thought I’d discuss the C2 (Command and Control) framework Empire. The original PowerShell Empire project was discontinued, but several awesome people at BC Security developed a new version created mostly in Python 3. However, it can use several different agents, including pure-PowerShell for Windows. I’m also going to be using the GUI BC Security created for their version of Empire called Starkiller just to make some demonstrations easier, but everything can be done from the command-line if needed.

I’m not going to cover how to setup Empire because that’s pretty straightforward following the instructions on their Github. I thought it would be more useful to go over some of the default settings that should always be changed for any real engagement and features that don’t always work out of the box due to up-to-date anti-virus signatures. For these tests I’m going to be using Windows Defender as the chosen AV because it’s free, but the general suggestions below should be effective against other products as well.

Now, someone may say, “Why Empire? Cobalt Strike is the most used C2 out there, you should cover that.” First off, most of the information I’m going to go over isn’t necessarily specific to Empire, I’m just using it to demonstrate why using the defaults is generally a bad idea in any tool. Secondly, Cobalt Strike is expensive and Empire is free.

With that out of the way, let’s get started and…

Default Empire settings and common IoCs

On Kali Linux, the easiest way to run Empire is to install it with apt and start it with the command powershell-empire server. This starts up the application, loads plugins and shows that the API and SocketIO server is started up successfully.

Starkiller is similarly available through apt on Kali and can be started with the starkiller command. On first launch you’ll be greeted with the login screen below defaulting to connecting to port 1337 on localhost, assuming you’re running the server on the same machine. The default credentials for Empire are ’empireadmin’ and ‘password123’.

Once logged in, starkiller opens to the Listeners screen by default. From here on I’m going to focus on basic usage, but for a specific attack vector, so I won’t be going into detail on anything else. However, all of the information is either available on their Github or a linked wiki from there.

The attack vector I want to focus on is using Empire in conjunction with a malicious Office document to take over a machine. In order to make this work, we’ll need to do a few things first.

  1. Start a listener in Empire using our desired configuration
  2. Choose and generate a stager in Empire that will provide the payload to use in a in Word document.
  3. Put the payload into a macro in the Word document
  4. Send the document to the victim
    • This part will be staged and I’ll just move the document to the target machine, but the end result would be the same.

Creating a listener

From the Listeners page, we just click Create and are taken to a new screen to choose the type of listener to use. In this case I’m going to choose ‘http’, but there are a variety that can be used for different situations.

This leads to the listener configuration screen with quite a few more options. This is also the first place I want to point out some settings that should definitely be changed if you’re planning to use Empire in a real engagement and don’t want to be caught immediately.

All of the settings seen above are defaults for the http listener. The hostname and port will always need to be changed to match the server you’re running Empire on and listener name should be changed to anything you want that makes it easier to recognize what it’s for. Other items like “DefaultProfile” and “Headers”, which controls what the HTTP server looks like, should always be modified. As Empire is open-source, most modern AV/EDR vendors will have extensive IoCs (Indicators of compromise) for the default settings and behavior of the tool. As an example, if I google one of the default paths set in DefaultProfile, it’s pretty obvious what it’s associated with.

With this in mind, I generally change the following settings for a listener:

  • Host
    • Server running Empire
  • Port
    • Port to listen on
  • DefaultProfile
    • Any random URL paths and a common user agent
  • Headers
    • Any common web server
  • Launcher
    • This could be modified to launch PowerShell in a different way, but I’m going to change this manually later.
  • StagingKey
    • Any random 32 character string
  • CertPath
    • This would be set to a certificate if you want to use HTTPS, but I won’t be in this case.
  • Cookie
    • Any random string. Using a common cookie name like “PHPSESSID” could also work if your server headers match.
  • Proxy, ProxyCreds, UserAgent
    • I usually set these to none to begin with, but they can be set as needed.

Once finished and started, we can see it is listening on the Listeners panel.

Creating a stager

With the listener running, now we need to generate a payload that will connect back to it. We can do that by navigating to the Stagers tab and clicking Create. This again presents a list of choices for the type of stager to use ranging from Windows to OSX to platform independent. For this example, I’m going to choose the “windows/macro” option to match my chosen attack vector.

Once selected, we’re given a new screen to configure how the stager’s payload is generated.

The only option required to be changed here is the Listener setting, which needs to be set to the listener we started earlier. However, similar to the listener settings, there are some that are generally a good idea to modify from the defaults. As a demonstration, I’m going to generate the stager without modifying anything else to see how it does against Windows Defender.

Once submitted, the stager is created and we can choose to copy the payload to the clipboard. Other options generate files that can be downloaded, but it just depends on the type of stager being used.

Putting the payload in a macro

The payload it generates is a standard VBA Macro that can be put into an Office document and, in this case, uses Run() function from WScript.Shell to execute the payload.

On another machine, I created a Word document named “empire.doc” (.doc still executes macros) and created a macro using the payload generated by Empire. After I copied the file over to the machine, Defender immediately flagged it as malicious. The detection unfortunately doesn’t say too much about what it thinks is malicious.

From my testing “O97M/Sadoca” is generally related to something in Office documents that it thinks it malicious, but can’t specifically identify. The !ml at the end usually means the detection was found through machine learning rather than basic signature detections, which means it’s probably a combination of things that are seen as malicious when put together.

Learning how to bypass Defender isn’t the point of this post though, so moving on for now.

Using the built-in obfuscation

Empire also has the option to obfuscate the PowerShell commands used in generated payloads. It does this using the Invoke-Obfuscation Powershell module, which works well, but doesn’t necessarily offer an immediate bypass of any anti-virus. I created a new macro stager and this time turned on the option for obfuscation, using the default choice of “Token\All\1”. Token obfuscation is only one of the methods offered by this library, but we’ll see how the default option works first.

Copying this payload into a Word document shows the payload is noticeably longer, but still uses the same method of execution through WScript.Shell. Unfortunately, we’re met with the same detection as last time.

So it looks like the obfuscation didn’t make any difference. Let’s take a look at the commands that were embedded in the payload to get an idea of what Defender might be detecting. To do this I just extracted the Base64-encoded payload from the macro and decoded it using CyberChef to make it easy.

This gives a PowerShell one-liner, but adding a line break on every semi-colon splits the commands up nicely enough to make it more readable.

Now let’s compare that to the obfuscated version of the same payload.

Apart from the obfuscated one looking extremely sus, these do the exact same thing. There are 3 main things happening here that we should focus on first. I’m going to use the unobfuscated version for reference since it’s more readable.

  1. The first 4 lines are the AMSI (Anti-Malware Scan Interface) and ETW (Event Tracing for Windows) bypass included by default. In this case it is using an AMSI bypass method identified by Matt Graeber here.
  2. Lines 6-15 are setting up the WebClient object it will use to make requests to the Empire HTTP server. Line 10 is a Base64-encoded string of the server name (hxxp://X.X.X.X) and line 11 is the endpoint I defined in the listener’s profile.
  3. Some other stuff happens on lines 16-24 using the stagingkey I set, but line 25 adds my defined cookie and 26 is where the actual request to download data from the server is made.

With this in mind, we should be able to modify the payload as long as it performs the same actions seen above. However, first I want to see if the AMSI bypass included by default actually works as many of the methods that have been made public through the linked repo above or https://amsi.fail now have signatures built to detect them before they can actually disable AMSI. To do that I just extracted the string below from the payload that is the actual bypass and tested it directly in PowerShell.

[REF].AsSEMbly.GEtTYPE('System.Management.Automation.Amsi'+'Utils');$ReF.GEtFieLD('amsiInitF'+'ailed','NonPublic,Static').SETVAlUE($nuLl,$trUe);

Unfortunately, it looks like the bypass itself is detected as malicious and even associated with an “AmsiTamper” signature. I’m not going to worry about the ETW bypass for now, but it was also seen as malicious on its own.

As another test, I removed both bypasses from the script and a few lines related to proxies as I don’t need them in this case. I then pasted the entire command back into a regular PowerShell window as a one-liner to see what happens. This was also detected, but this time was much more specific in that it was seen as a PowerShell Attack Tool.

That’s not unexpected as, if AMSI is enabled, pretty much any payload we try to run will eventually be flagged as malicious content if we don’t disable it first.

First attempt at custom obfuscation

I mentioned earlier that most of the publicly available AMSI bypasses have signatures that prevent them from working correctly, but that is not the case for all of them. I don’t want to make the process too easy for someone who may be looking to do something actually malicious so I’ll leave the step of identifying a working one up to you, but here’s proof that it exists. The string ‘amsiutils’ is a simple test for detecting if AMSI is enabled and it no longer triggers after the bypass is run.

After adding this working bypass to the minimized payload from earlier and running directly in PowerShell, it doesn’t seem to generate an alert and the command hangs, which is usually an indication that whatever connection it made is still open.

In fact, checking back in Empire shows that we have a new agent that has checked in from the victim machine.

We can confirm it is working correctly by giving it a task and seeing the result. In this case I just tasked it to run the command “whoami”.

At this point, we know the PowerShell command works with the replacement AMSI bypass, but does it work when put back into the Word macro? I used CyberChef again to convert my PowerShell one-liner back to UTF-16LE and then Base64-encode it for use in the macro.

I also used Python to format the encoded string again for the Word macro as seen below where the variable s is the encoded payload. As a note, the wrap() function needs to be imported with from textwrap import wrap.

However, I tested the encoded payload directly in PowerShell before moving forward and was met with a new detection, this time specifically for Empire.

This will be problematic as the same detection will be seen if the command is run from the Word macro. To get around this I just used the unencoded one-liner directly in the macro, which is not very stealthy, but neither is a giant block of base-64 encoded text, so whatever works. Unfortunately, this is still detected by Defender when the Word document is dropped to disk.

Further obfuscation and bypassing Defender

After some trial and error, Defender seems to be able to detect something is malicious in the macro even when doing further obfuscation on the commands being run. The are several other possible routes to go down next, though I won’t go into detail for now as they could be their own topics. One of which involves hosting the actual payload on a remote server and using a PowerShell download cradle in the macro to avoid any malicious content being present on disk. This would allow the payload to be loaded directly into memory by the macro, which should not be detected if AMSI is disabled successfully.

I’m not going to share my final macro as again I don’t want to make it too easy for potentially malicious people to have a way of getting maldocs past Defender, but the GIF below shows that it is possible. In this example the document waits for a set amount of time, retrieves the payload from a second server, and executes it. This results in a new agent callback in Empire. Sorry for the poor quality of the down-sized GIF.

Conclusion

The main purpose of this post was to demonstrate that most of the default payloads or stagers generated by C2 frameworks are likely to have well documented signatures in modern anti-virus tools. This may not be the case as much for lesser-known tools, but it’s still a good idea to customize your payloads when you intend to use something in a real operation. The same rules apply to general configuration of the team server being used to host/deliver the payloads as EDR tools or network-based detections may have signatures built for those defaults as well.

Thanks for reading if you stuck around this long and I hope this was useful!

Analyzing a memory dump for malicious activity with volatility

I’ve been wanting to do a forensics post for a while because I find it interesting, but haven’t gotten around to it until now. Volatility is a memory forensics framework written in Python that uses a collection of tools to extract artifacts from volatile memory (RAM) dumps. It’s an open-source tool available for any OS, but I used it in a CSI Linux VM because it comes pre-installed (though it needs to be updated) and I wanted to try out a new distro.

DISCLAIMER – I don’t claim that any of this is the best or even the right way of using volatility, it’s just how I do it.

To set up this example, I used a malicious Word document macro and the Empire post-exploitation/C2 framework to perform the tasks below on the machine:

  • Word document downloads and executes PowerShell script from remote server when macros are enabled
  • The above script calls back to an Empire listener, creating an agent that allows us to run commands from Empire
  • Migrated from initial powershell.exe process to svchost.exe process owned by the same user
  • Escalated privileges used the ‘getsystem’ module in Empire (similar to getsystem in Metasploit)
  • As SYSTEM, dumped hashes using Mimikatz module built into Empire
  • Created new scheduled task for persistence with an encoded powershell command (another Empire module)
  • Finally, used a module to pass-the-hash for the Administrator user in the domain to get an agent on the lab domain controller
  • Used Magnet Ram Capture to get a memory dump of the VM

I’m also planning on the next post showing the other side of this attack where I’ll walk through what I did in Empire and how it looked from the attacker’s perspective.

I should mention that I purposely left the Empire agents connected during the memory dump because killing them would completely close their connections/processes, preventing volatility from being able to identify them. The process information is still in memory and can be seen using strings on the direct memory capture, but the volatility modules won’t see anything associated with it. This isn’t necessarily realistic, but in an actual investigation we’d likely be working with full forensic image that would have all of this information anyway.

Now, let’s see how many of the activities we can find with volatility.

The first step is to use the ‘imageinfo’ module to determine which Operating System profile volatility should use. This is important because using the incorrect profile will either give an error or just not give any results because it’s using the wrong memory mapping. My copy of volatility is installed in the /opt directory and I use the -f flag to point to my memory dump file, in this case post-empire.raw.

/opt/volatility/vol.py -f post-empire.raw imageinfo

This command can take a few minutes to finish, but when it does it should provide the output below with a suggested profile to use for further commands. As we can see, volatility is suggesting the profile for ‘Win10x64_19041’.

At this point I create an alias for our main command as it won’t change and I don’t want to type the whole thing each time.

alias vol="/opt/volatility/vol.py -f IMAGE_NAME --profile=PROFILE_NAME

The result is that I can now just type ‘vol’ followed by the module I want to run and get the same result as when using the longer command. Below shows the output of “vol -h”, showing that the tool is running correctly.

Now that we have the right profile and the alias setup, let’s get started looking for interesting information. The first command I usually use it ‘pstree’ to get an idea of what processes are running on the machine and what the parent/child relationships are.

Several of these processes stick out. First, the instance of svchost.exe running under PID 4468 appears to have spawned a conhost.exe process, which is unusual.

Second, the powershell process under PID 3804 also spawned an instance of conhost.exe, along with a second powershell.exe (PID 2568).

This activity alone doesn’t make something malicious, but it does give us some information to note for later. Svchost.exe is a common process for malware to inject into in an attempt at appearing benign and powershell spawning another instance of powershell is odd.

  • PIDs to watch for:
    • svchost.exe (4468)
    • powershell.exe (3804)
    • powershell.exe (2568)
  • Timeframes to watch:
    • 2021-01-13 19:59:23-25 (powershell activity)
    • 2021-01-13 20:03:17 (svchost.exe spawning conhost.exe)

Before moving on to the next command, some of these modules can take a while to run and output a huge amount of information, so I suggest sending the output to a file for easy reference later.

vol pstree > pstree.txt

The next command I’ll use is ‘netscan’ which scans for active network connections or open sockets. I saved the output to a file and sorted it by the PID column to make it easier to read. We don’t get any IP addresses in the output (apart from this machine, .102, and the DC at .3). However, we do see the PowerShell and svchost PIDs we identified earlier we using a socket at some point.

vol netscan > netscan.txt

There are other svchost.exe PIDs in the output, but we haven’t seen anything pointing to suspicious behaviour from them yet and will come back to them if needed. Next, I moved on to the ‘malfind’ module to search for processes that may have hidden or injected code in them, both of which could indicate maliciousness.

vol malfind > malfind.txt

This particular command gives a lot of output, including the process name, PID, memory address, and even the hex/ascii at the designated memory address.

To filter out some of the extra information, I like to start by grepping for “Process” to only get the line with the process/PID. This output gives a few processes that may be false positives simply due to how memory works in Windows, but we see the same 3 PIDs once again.

Now I’d like to get more information about these 3 processes that keep popping up. I used the ‘cmdline’ module to see if the command line arguments for the processes provide any more context on what they may have been doing.

vol cmdline > cmdline.txt

This gives quite a bit of output, so with some extra filtering we can look for the specific svchost and powershell processes we want to see. The svchost process and one of the powershell PIDs doesn’t give anything useful, but the other reveals some interesting information.

That’s a suspicious PowerShell command if I’ve ever seen one. It appears to be downloading and executing a script from a remote machine called “run.ps1”. It also gives us an IP to add to our suspicious items to watch for.

  • Suspicious IPs
    • 192.168.50.164
  • Suspicious files
    • run.ps1

Without knowing what this PowerShell script is doing, I’m already pretty confident this process is malicious at this point. I can get more information about this specific process using the ‘psinfo’ module, which unfortunately is not included with volatility, but can be added easily enough.

This output doesn’t give much new as we already have the command line arguments and similar processes, but it does tell us the parent PID of PowerShell that likely started the chain. However, in this case all we get is the PID 5884 without a process associated with it, which probably means the original application was closed after the PowerShell command was run. Since I already know this started with a Word document, PID 5884 is likely WINWORD.exe, but I closed the application before doing the memory dump.

Now, I’m curious if the memory region associated with this PowerShell process has anymore useful information we can glean from it. Volatility has commands for both ‘procdump’ and ‘memdump’, but in this case we want the information in the process memory, not just the process itself. The command below shows me using the memdump command with the -p flag to specify the PID I want to target and -D to indicate where I want to save the dump file to.

From here, I ran strings against the dump file and piped the output to a second file to sort through next. This will make it easier to look through any ASCII strings identified in the dump without having to re-run strings multiple times.

To start with, I grepped through this output for various things from ‘svchost.exe’ to the IP address seen earlier, but the most promising hit came when searching for ‘powershell.exe’.

It may be a little hard to see, but the big base64 encoded powershell command means we’re on the right track. This particular information is only showing up in memory because I have PowerShell Script logging enabled on the Windows 10 host this activity was performed on and it was writing this to the log as the command was run. There’s another clue in this text about what may be going on as well. The “Invoke-UserImpersonation” function name seems pretty specific and might yield some useful Google results.

In this case, this function seems to be part of the PowerSploit post-exploitation framework. This kind of search can be useful in a lot of cases as open-source tools often use well-known function/file names and if a malicious actor uses those tools at some point it can provide more information on what they tried to do.

Next, I decoded the base64 command using CyberChef and got the result below. The resulting script is obviously still obfuscated, but some keywords indicate it is trying to make a web request. The yellow sections include keywords related to web requests (User agent, System.Net.WebClient, Header) and the green section reveals the target of the request.

Decoding the extra layer of base64 in the first green section gives us the address below at the same IP the run.ps1 script was downloaded from.

Another quick Google search for the ‘news.php’ resource included in the request reveals this script was likely generated by the PowerShell Empire framework. As the script is creating a web request to a PHP file, this is likely the command that connects the first agent back to the Empire C2.

This information doesn’t tell us everything that happened, but it definitely provides more context on the tools being used by the attacker and what else to potentially look for.

I won’t go through the details of how to dump memory again, but I did the same thing for the other two processes we’ve already identified (svchost.exe at 4468 and powershell.exe at 2568). Grepping for powershell and the IP didn’t give anything useful, but scrolling through the strings output of the second powershell.exe process shows the standard output of mimikatz, a tool used for dumping credentials (among other things).

So we know they dumped credentials at some point. Looking further and grepping for “Invoke-” as a common function name in tools such as PowerSploit and Empire. The screenshot shows the function “Invoke-PSInject” being called with a target process ID of 4468, confirming the attacker did inject into the svchost.exe process we’ve already identified.

The next useful piece of information is found in the strings of the dump for PID 4468. The image below shows the results of performing the same grep for “Invoke-” on this file and reveals another function named “Invoke-SMBExec” that is used to execute commands on a remote machine. This function requires a username, target machine, and domain, but can accept an NTLM hash in place of a password. It appears the attacker used the hash for the Administrator user retrieved from the earlier Mimikatz command to run the next encoded powershell command directly on the domain controller.

The decoded command appears to follow the same format as the previous, only reaching out to a different file (/admin/get.php) on the target server. A search for this file again gives results pointing to PowerShell Empire.

The last thing I’ll show in this post, as it’s getting a little long, is a way of finding potential persistence mechanisms through Scheduled Tasks in Windows. Individual tasks are stored as files by default at C:\Windows\System32\Tasks and contain XML information on what the task does. I used the module ‘filescan’ to find all files listed in the dump and then grepped for the directory above to narrow the results. The final results show 3 scheduled tasks, one that looks more than a little suspicious.

Volatility has a module to dump files based on the physical memory offset, but it doesn’t always work and didn’t in this case. For reference, the command would have been similar to below.

  • -Q for the physical memory address (as seen to the far left in the image above)
  • -n to keep the name of the file when dumped
  • -D for the directory to save dumped files to
vol dumpfiles -Q 0x0000c601c1d3e760 -n -D dumps/

I’m going to stop the analysis for now, but the information we’ve gathered would be enough to fuel further investigation by an Incident Response team. The only things we haven’t found are the commands used for escalating privileges to SYSTEM, though more searching through the dumped process memory may reveal it eventually.

Hopefully this gives some insight into the capabilities volatility has and how useful it can be in an incident response or forensics scenario. It’s important to note that several of the commands I found in the process memory were likely only visible because PowerShell Script logging was enabled on the machine, but it does demonstrate how useful it is in an investigation.

The next post will show the attacker’s perspective of these same activities and how I used Empire to perform each one.