Phishing with Macros and Powershell

Written on:May 22, 2015
Add One

Over the past 6 months, it seems we’ve been experiencing a resurgence of macro-based malware, possibly because it’s such a simple and proven means of delivering a phishing payload to large organizations. If you’re performing a penetration test against an organization and you have reason to believe untrusted macro execution is enabled, they can also be a good means to test user awareness and gain a foothold via social engineering.

Regardless of their popularity, quite often these macro-based exploits fail for a number of reasons. If you’re the target, hopefully it’s because you’ve recognized that untrusted macros are dangerous and have taken the steps to prevent their execution in your environment. If not, you might be finding that reliance on network and desktop security products for protection can be a bit of a gamble.

Sometimes you get lucky and these exploits fail due to simple coding errors. Take for example the following heavily obfuscated example I came across recently:


In order for this obfuscated macro to work, the above variables are parsed and the values extracted using the InStr() function to generate the destination URL from which to download the malicious executable payload. In the portion of the macro highlighted below, the value used as parameter string2 is derived from the Chr() function. Unfortunately, the value passed to that function  (joHNknVFXmrvEA) is incorrectly calculated, the resulting call to InStr() returns a null, and the macro exploit fails.


A little tweak to the code and we can see where the macro was intending to download the malicious payload from:


Of course relying on poorly written macros is not a good security control and if for some reason you allow untrusted macros to run, you are at the mercy of your other security protections. A while back, my coworker and I analyzed another macro-based Word exploit that we saw in the wild and the approach the author used was simple and pretty typical – download and run a malicious executable on the victim’s workstations, sans any real obfuscation.

Here’s an abridged view of the macro:

It’s easy to see what’s happening in the above code…the macro attempts to download a file called 33.exe (containing some exploit code), save it to the user’s temp directory, and execute it.

While really basic in concept, phishing attempts like these can be effective if you allow untrusted macro execution (either via prompt or automatically). With this example, if macros were enabled, the code would execute and the Word document would immediately close. However, if the target didn’t have macros enabled, the Word document would remain open and provided detailed instructions (with screenshots) on how to enable macros as a last ditch effort to trick the recipient.

Aside from macros not being enabled (we will assume they are if you’re intentionally using a macro-based exploit), the problem with the above approach if you’re using them in a penetration test is that it can easily be detected for a couple of reasons.

First, although the document itself does not contain the executable payload, the macro signature is enough to trip most exchange-based AV (stripping the attachment before the target user receives the email). That was easy enough for us to fix in the above example…we simply broke up the macro code into smaller functions and moved some of the hard-coded string values to local variables.

Even after bypassing any Exchange-based AV and successfully delivering the attachment to the target, you still have to deal with AV detection for the downloaded executable. In many large organizations this means not only bypassing client-side AV once it’s downloaded, which is relatively easy (see here for more), but also firewall and web proxy AV, which could prevent the download altogether. Sure it can be done, but if you’re using a macro-based exploit in a penetration test, why try and tackle AV bypass if you don’t have to?

Instead I figured why not remove the executable entirely and harness the power of Powershell? For our demo we went with a simple Meterpreter reverse TCP shell, generated with the handy Unicorn tool (by Dave Kennedy at TrustedSec).

Rather than wrestle with VBS and it quirky string length limits, we can embed the Powershell script right into the Document properties of the Word file (in this case, the Author field) and just reference the value via a local function variable in our macro.


We don’t want the Powershell window to display for the end user at all, hence the -nologo, -win hidden, etc.

Now, it’s just a matter of updating the macro to run the powershell:

Simple, right?

This works fine for Windows clients but we couldn’t forget about our Mac users so just for fun we decided to implement a simple python reverse shell for anyone running MS Word on a Mac.

Here’s the combined code:

You can see that this is not at all a sophisticated piece of code and there is no effort to detect the recipient’s OS. Instead, we first attempt the Windows shell function and if that doesn’t work we revert to the Mac function. If the latter fails, the exploit simply fails quietly and the user is none-the-wiser.

You may also notice that unlike the first example where the author chose to close the document, we felt that that was too much of an indicator to the user that something was amiss so we made sure to allow the user to close it on their own (allowing you to add meaningful content and further legitimize the attachment). Since the shell is executed via a separate Powershell (or python) process, it is not dependent upon the Word document remaining open anyway.

Unlike the original version that has the added detection risk of downloading and running a malicious executable, this approach is self-contained and more likely to bypass AV detection. That said, it’s not without its flaws. First, if you truly want to target Mac users, you have to consider that you’re more likely to get a macro prompt which could tip off the intended target:


Second, using a standard Metasploit payload can still trigger other security protections such as client- or network-based IPS, so additional modification may be required depending on your target test environment. If you’re targeting users with Administrator privileges and you are familiar with the target test environment you might also include some macro instructions to disable the workstation AV client which we also tested with success.

This is by no means a perfect approach, but if you’re set on using an MS Office macro-based phishing exploit for your next penetration test, you might consider an embedded Powershell script as your initial payload delivery to avoid the hassles of AV bypass.

A special thanks to my colleague Andy (@Blackjack988) for helping me create and test this PoC.

Until next time,


16 Comments add one

  1. James says:

    Hi Mike,

    Thanks for the informative article. Really interesting.

    Just a quick question: did you by any chance put the reverse shell into word > view >macro and created Auto_Open? or was you simply insert the r shell code into author word document tag? im new to client side attack, well student on security course trying to learn on how you did it really! what about Mail gateway anti virus or locally installed softwares and URL filtering protection? do they mitigate this kind of attac? much appreciate if you can elaborate.

    • Mike Czumak says:

      The reverse shell was stored in the Author metadata tag and simply referenced from the VB function. By taking this approach and avoiding a download of an external executable payload, we were able to defeat multiple levels of email, network, and client-side protections.

  2. Ctg says:

    Mike, great article and really liking your different spin on this. Would it be possible to explain how the code works and what type of multi-handler one would you have to create to receive the incoming shell?


    • Mike Czumak says:

      Thanks. The macro simply assigns the PowerShell script to a variable and creates a WScript.Shell object to call it. The PowerShell payload itself (created with the Unicorn tool) was pasted (along with the powershell command line options) into the Author property field of the Word document (Prepare–> Properties in Word 2007). For testing purposes I just used a windows/shell_reverse_tcp for the PowerShell payload and set up a corresponding handler on my remote “attacker” machine. Hope this clears things up.

      – Mike

  3. james says:

    Mike Thanks for your explanations.

    Brilliant as always .. welldone

  4. Sam says:

    Thanks for sharing this 🙂 .. would you please make the ‘malicious’ word file available for download as i’m not able to make it offline here 🙁

    • Mike Czumak says:

      Creation should be straightforward — simply create a macro-enabled .doc, copy and paste the code and insert the PS payload in the author field.

  5. B. Morg says:

    Kudos on a great piece. My only question is: are you modifying the original macro code to include the powershell code, or are you completely replacing the original code with the powershell code?

    • Mike Czumak says:

      Thanks. The macro code never includes the powershell script. The Powershell payload is simply inserted in the desired document properties field (in my example, the Author field) which is then accessed via the macro.

  6. metacom says:

    Great article thanks 🙂

  7. gaz says:

    Beautiful…. The simple solutions are always the best.

  8. vasco says:

    YOU KICK ASS 🙂 god job as always

  9. vasco says:

    Hi Mike

    I think i’m your biggest fan 🙂 i was working with your powershell macro solution and it has ben working very good until now. Av detect it and block it . Is this possible to make it fud again ? What if you just xor encode this part is this possible ?

    Sub Auto_Open()
    Call winshell
    End Sub`

    Sub AutoOpen()
    Call winshell
    End Sub

    Function winshell() As Object

    On Error Resume Next

    ‘get / execute powershell command from doc property
    Dim ps As String
    ps = ActiveDocument.BuiltInDocumentProperties(“Author”).Value
    Dim Obj As Object
    Set Obj = CreateObject(“WScript.Shell”)
    Obj.Run ps, 0

    Application.DisplayAlerts = False
    End Function

Leave a Comment

Your email address will not be published. Required fields are marked *