Added build log to client UI.
This commit is contained in:
@@ -4,5 +4,5 @@
|
||||
--opt:size
|
||||
--passL:"-s" # Strip symbols, such as sensitive function names
|
||||
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
|
||||
-d:MODULES="66"
|
||||
-d:MODULES="12"
|
||||
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"
|
||||
@@ -2,6 +2,7 @@ import whisky
|
||||
import tables, strutils, strformat, json, parsetoml, base64, os # native_dialogs
|
||||
import ./utils/[appImGui, globals]
|
||||
import ./views/[dockspace, sessions, listeners, eventlog, console]
|
||||
import ./views/modals/generatePayload
|
||||
import ../common/[types, utils, crypto]
|
||||
import ./websocket
|
||||
|
||||
@@ -118,6 +119,10 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
except IOError:
|
||||
discard
|
||||
|
||||
# Close and reset the payload generation modal window when the payload was received
|
||||
listenersTable.generatePayloadModal.resetModalValues()
|
||||
igClosePopupToLevel(0, false)
|
||||
|
||||
of CLIENT_CONSOLE_ITEM:
|
||||
let agentId = event.data["agentId"].getStr()
|
||||
consoles[agentId].addItem(
|
||||
@@ -133,6 +138,13 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
event.timestamp
|
||||
)
|
||||
|
||||
of CLIENT_BUILDLOG_ITEM:
|
||||
listenersTable.generatePayloadModal.addBuildlogItem(
|
||||
cast[LogType](event.data["logType"].getInt()),
|
||||
event.data["message"].getStr(),
|
||||
event.timestamp
|
||||
)
|
||||
|
||||
else: discard
|
||||
|
||||
# Draw/update UI components/views
|
||||
|
||||
@@ -264,7 +264,7 @@ proc draw*(component: ConsoleComponent, connection: WsConnection) =
|
||||
var labelSize: ImVec2
|
||||
igCalcTextSize(addr labelSize, ICON_FA_MAGNIFYING_GLASS, nil, false, 0.0f)
|
||||
|
||||
let searchBoxWidth: float32 = 200.0f
|
||||
let searchBoxWidth: float32 = 400.0f
|
||||
igSameLine(0.0f, availableSize.x - (labelSize.x + textSpacing) - searchBoxWidth)
|
||||
|
||||
# Show tooltip when hovering the search icon
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import strutils, sequtils
|
||||
import strutils, sequtils, times
|
||||
import imguin/[cimgui, glfw_opengl, simple]
|
||||
import ../../utils/[appImGui, colors]
|
||||
import ../../../common/[types, profile, utils]
|
||||
@@ -13,6 +13,8 @@ type
|
||||
spoofStack: bool
|
||||
sleepMaskTechniques: seq[string]
|
||||
moduleSelection: DualListSelectionComponent[Module]
|
||||
buildLog: ConsoleItems
|
||||
|
||||
|
||||
proc AgentModal*(): AgentModalComponent =
|
||||
result = new AgentModalComponent
|
||||
@@ -36,12 +38,42 @@ proc AgentModal*(): AgentModalComponent =
|
||||
|
||||
result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc)
|
||||
|
||||
proc resetModalValues(component: AgentModalComponent) =
|
||||
result.buildlog = new ConsoleItems
|
||||
result.buildLog.items = @[]
|
||||
|
||||
proc resetModalValues*(component: AgentModalComponent) =
|
||||
component.listener = 0
|
||||
component.sleepDelay = 5
|
||||
component.sleepMask = 0
|
||||
component.spoofStack = false
|
||||
component.moduleSelection.reset()
|
||||
component.buildLog.items = @[]
|
||||
|
||||
proc addBuildlogItem*(component: AgentModalComponent, itemType: LogType, data: string, timestamp: int64 = now().toTime().toUnix()) =
|
||||
for line in data.split("\n"):
|
||||
component.buildLog.items.add(ConsoleItem(
|
||||
timestamp: timestamp,
|
||||
itemType: itemType,
|
||||
text: line
|
||||
))
|
||||
|
||||
proc print(component: AgentModalComponent, item: ConsoleItem) =
|
||||
case item.itemType:
|
||||
of LOG_INFO, LOG_INFO_SHORT:
|
||||
igTextColored(CONSOLE_INFO, $item.itemType)
|
||||
of LOG_ERROR, LOG_ERROR_SHORT:
|
||||
igTextColored(CONSOLE_ERROR, $item.itemType)
|
||||
of LOG_SUCCESS, LOG_SUCCESS_SHORT:
|
||||
igTextColored(CONSOLE_SUCCESS, $item.itemType)
|
||||
of LOG_WARNING, LOG_WARNING_SHORT:
|
||||
igTextColored(CONSOLE_WARNING, $item.itemType)
|
||||
of LOG_COMMAND:
|
||||
igTextColored(CONSOLE_COMMAND, $item.itemType)
|
||||
of LOG_OUTPUT:
|
||||
igTextColored(vec4(0.0f, 0.0f, 0.0f, 0.0f), $item.itemType)
|
||||
|
||||
igSameLine(0.0f, 0.0f)
|
||||
igTextUnformatted(item.text.cstring, nil)
|
||||
|
||||
proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBuildInformation =
|
||||
|
||||
@@ -109,6 +141,38 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
|
||||
igSeparator()
|
||||
igDummy(vec2(0.0f, 10.0f))
|
||||
|
||||
igText("Build log: ")
|
||||
try:
|
||||
# Set styles of the eventlog window
|
||||
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
|
||||
igPushStyleColor_Vec4(ImGui_Col_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
|
||||
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
|
||||
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
|
||||
|
||||
let buildLogHeight = 250.0f
|
||||
let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32
|
||||
if igBeginChild_Str("##Log", vec2(-1.0f, buildLogHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
|
||||
# Display eventlog items
|
||||
for item in component.buildLog.items:
|
||||
component.print(item)
|
||||
|
||||
# Auto-scroll to bottom
|
||||
if igGetScrollY() >= igGetScrollMaxY():
|
||||
igSetScrollHereY(1.0f)
|
||||
|
||||
except IndexDefect:
|
||||
# CTRL+A crashes when no items are in the eventlog
|
||||
discard
|
||||
|
||||
finally:
|
||||
igPopStyleColor(3)
|
||||
igPopStyleVar(1)
|
||||
igEndChild()
|
||||
|
||||
igDummy(vec2(0.0f, 10.0f))
|
||||
igSeparator()
|
||||
igDummy(vec2(0.0f, 10.0f))
|
||||
|
||||
# Enable "Build" button if at least one module has been selected
|
||||
igBeginDisabled(component.moduleSelection.items[1].len() == 0)
|
||||
|
||||
@@ -127,9 +191,6 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
|
||||
modules: modules
|
||||
)
|
||||
|
||||
component.resetModalValues()
|
||||
igCloseCurrentPopup()
|
||||
|
||||
igEndDisabled()
|
||||
igSameLine(0.0f, textSpacing)
|
||||
|
||||
|
||||
@@ -83,16 +83,16 @@ type
|
||||
CONFIG_PROFILE = 5'u8
|
||||
|
||||
LogType* {.size: sizeof(uint8).} = enum
|
||||
LOG_INFO = " [INFO] "
|
||||
LOG_ERROR = " [FAIL] "
|
||||
LOG_SUCCESS = " [DONE] "
|
||||
LOG_WARNING = " [WARN] "
|
||||
LOG_COMMAND = " [>>>>] "
|
||||
LOG_INFO = "[INFO] "
|
||||
LOG_ERROR = "[FAIL] "
|
||||
LOG_SUCCESS = "[DONE] "
|
||||
LOG_WARNING = "[WARN] "
|
||||
LOG_COMMAND = "[>>>>] "
|
||||
LOG_OUTPUT = ""
|
||||
LOG_INFO_SHORT = " [*] "
|
||||
LOG_ERROR_SHORT = " [-] "
|
||||
LOG_SUCCESS_SHORT = " [+] "
|
||||
LOG_WARNING_SHORT = " [!] "
|
||||
LOG_INFO_SHORT = "[*] "
|
||||
LOG_ERROR_SHORT = "[-] "
|
||||
LOG_SUCCESS_SHORT = "[+] "
|
||||
LOG_WARNING_SHORT = "[!] "
|
||||
|
||||
SleepObfuscationTechnique* = enum
|
||||
NONE = 0'u8
|
||||
@@ -264,7 +264,8 @@ type
|
||||
CLIENT_AGENT_PAYLOAD = 104'u8 # Return agent payload binary
|
||||
CLIENT_CONSOLE_ITEM = 105'u8 # Add entry to a agent's console
|
||||
CLIENT_EVENTLOG_ITEM = 106'u8 # Add entry to the eventlog
|
||||
CLIENT_LOOT = 107'u8 # Download file or screenshot to the operator desktop
|
||||
CLIENT_BUILDLOG_ITEM = 107'u8 # Add entry to the build log
|
||||
CLIENT_LOOT = 108'u8 # Download file or screenshot to the operator desktop
|
||||
|
||||
Event* = object
|
||||
eventType*: EventType
|
||||
|
||||
@@ -4,6 +4,7 @@ import ../globals
|
||||
import ../core/logger
|
||||
import ../db/database
|
||||
import ../../common/[types, utils, serialize, crypto]
|
||||
import ../websocket
|
||||
|
||||
const PLACEHOLDER = "PLACEHOLDER"
|
||||
|
||||
@@ -49,6 +50,8 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepT
|
||||
wipeKey(aesKey)
|
||||
|
||||
cq.info("Profile configuration serialized.")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, "Profile configuration serialized.")
|
||||
|
||||
return encMaterial & encData
|
||||
|
||||
proc replaceAfterPrefix(content, prefix, value: string): string =
|
||||
@@ -79,9 +82,11 @@ proc compile(cq: Conquest, placeholderLength: int, modules: uint32): string =
|
||||
writeFile(configFile, config)
|
||||
|
||||
cq.info(fmt"Placeholder created ({placeholder.len()} bytes).")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, fmt"Placeholder created ({placeholder.len()} bytes).")
|
||||
|
||||
# Build agent by executing the ./build.sh script on the system.
|
||||
cq.info("Compiling agent.")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, "Compiling agent...")
|
||||
|
||||
try:
|
||||
# Using the startProcess function from the 'osproc' module, it is possible to retrieve the output as it is received, line-by-line instead of all at once
|
||||
@@ -97,9 +102,13 @@ proc compile(cq: Conquest, placeholderLength: int, modules: uint32): string =
|
||||
# Check if the build succeeded or not
|
||||
if exitCode == 0:
|
||||
cq.info("Agent payload generated successfully.")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, "Agent payload generated successfully.")
|
||||
|
||||
return exeFile
|
||||
else:
|
||||
cq.error("Build script exited with code ", $exitCode)
|
||||
cq.client.sendBuildlogItem(LOG_ERROR_SHORT, "Build script exited with code " & $exitCode)
|
||||
|
||||
return ""
|
||||
|
||||
except CatchableError as err:
|
||||
@@ -109,6 +118,7 @@ proc compile(cq: Conquest, placeholderLength: int, modules: uint32): string =
|
||||
proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): seq[byte] =
|
||||
|
||||
cq.info("Patching profile configuration into agent.")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, "Patching profile configuration into agent.")
|
||||
|
||||
try:
|
||||
var exeBytes = readFile(unpatchedExePath)
|
||||
@@ -119,6 +129,7 @@ proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): se
|
||||
raise newException(CatchableError, "Placeholder not found.")
|
||||
|
||||
cq.info(fmt"Placeholder found at offset 0x{placeholderPos:08X}.")
|
||||
cq.client.sendBuildlogItem(LOG_INFO_SHORT, fmt"Placeholder found at offset 0x{placeholderPos:08X}.")
|
||||
|
||||
# Patch placeholder bytes
|
||||
for i, c in Bytes.toString(configuration):
|
||||
@@ -127,10 +138,12 @@ proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): se
|
||||
writeFile(unpatchedExePath, exeBytes)
|
||||
|
||||
cq.success(fmt"Agent payload patched successfully: {unpatchedExePath}.")
|
||||
cq.client.sendBuildlogItem(LOG_SUCCESS_SHORT, fmt"Agent payload patched successfully: {unpatchedExePath}.")
|
||||
return string.toBytes(exeBytes)
|
||||
|
||||
except CatchableError as err:
|
||||
cq.error("An error occurred: ", err.msg)
|
||||
cq.client.sendBuildlogItem(LOG_ERROR_SHORT, "An error occurred: " & err.msg)
|
||||
|
||||
return @[]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import terminal, parsetoml, json, math, base64
|
||||
import terminal, parsetoml, json, math, base64, times
|
||||
import strutils, strformat, system, tables
|
||||
|
||||
import ./core/[listener, builder]
|
||||
@@ -101,11 +101,27 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
|
||||
# Set the client instance to nil again to prevent debug error messages
|
||||
cq.client = nil
|
||||
|
||||
var lastCtrlCTime = fromUnix(0)
|
||||
var ctrlC = 0
|
||||
|
||||
proc handleCtrlC() {.noconv.} =
|
||||
let now = getTime()
|
||||
if now - lastCtrlCTime > initDuration(seconds = 2):
|
||||
ctrlC = 0
|
||||
|
||||
inc ctrlC
|
||||
lastCtrlCTime = now
|
||||
|
||||
if ctrlC == 1:
|
||||
echo "\nPress Ctrl+C again to exit."
|
||||
else:
|
||||
echo "\nExiting."
|
||||
quit(0)
|
||||
|
||||
proc startServer*(profilePath: string) =
|
||||
|
||||
# Ensure that the conquest root directory was passed as a compile-time define
|
||||
when not defined(CONQUEST_ROOT):
|
||||
quit(0)
|
||||
# Handle team server exit
|
||||
setControlCHook(handleCtrlC)
|
||||
|
||||
header()
|
||||
|
||||
|
||||
@@ -116,3 +116,15 @@ proc sendConsoleItem*(client: WsConnection, agentId: string, logType: LogType, m
|
||||
)
|
||||
if client != nil:
|
||||
client.ws.sendEvent(event, client.sessionKey)
|
||||
|
||||
proc sendBuildlogItem*(client: WsConnection, logType: LogType, message: string) =
|
||||
let event = Event(
|
||||
eventType: CLIENT_BUILDLOG_ITEM,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
data: %*{
|
||||
"logType": cast[uint8](logType),
|
||||
"message": message
|
||||
}
|
||||
)
|
||||
if client != nil:
|
||||
client.ws.sendEvent(event, client.sessionKey)
|
||||
|
||||
Reference in New Issue
Block a user