Loot (downloads/screenshots) is now sent by the teamserver either on client-connection or when new loot is added. For images, smaller thumbnails are used to reduce size of network packets.
This commit is contained in:
@@ -29,4 +29,5 @@ requires "imguin >= 1.92.2.1"
|
||||
requires "zippy >= 0.10.16"
|
||||
requires "mummy >= 0.4.6"
|
||||
requires "whisky >= 0.1.3"
|
||||
requires "native_dialogs >= 0.2.0"
|
||||
requires "native_dialogs >= 0.2.0"
|
||||
requires "pixie >= 5.1.0"
|
||||
@@ -84,7 +84,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
connection.sendPublicKey(clientKeyPair.publicKey)
|
||||
|
||||
of CLIENT_PROFILE:
|
||||
profile = parsetoml.parseString(event.data["profile"].getStr())
|
||||
profile = parsetoml.parseString(event.data["profile"].getStr())
|
||||
|
||||
of CLIENT_LISTENER_ADD:
|
||||
let listener = event.data.to(UIListener)
|
||||
@@ -152,9 +152,22 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
event.data["message"].getStr(),
|
||||
event.timestamp
|
||||
)
|
||||
|
||||
of CLIENT_LOOT_ADD:
|
||||
let lootItem = event.data.to(LootItem)
|
||||
case lootItem.itemType:
|
||||
of DOWNLOAD:
|
||||
lootDownloads.items.add(lootItem)
|
||||
of SCREENSHOT:
|
||||
lootScreenshots.addItem(lootItem)
|
||||
|
||||
else: discard
|
||||
|
||||
of CLIENT_SYNC_LOOT:
|
||||
discard
|
||||
|
||||
else: discard
|
||||
|
||||
|
||||
# Draw/update UI components/views
|
||||
if showSessionsTable: sessionsTable.draw(addr showSessionsTable)
|
||||
if showListeners: listenersTable.draw(addr showListeners, connection)
|
||||
@@ -175,6 +188,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
# This is done to ensure that closed console windows can be opened again
|
||||
consoles = newConsoleTable
|
||||
|
||||
|
||||
igShowDemoWindow(nil)
|
||||
|
||||
# render
|
||||
@@ -183,5 +197,6 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
if not showConquest:
|
||||
app.handle.setWindowShouldClose(true)
|
||||
|
||||
|
||||
when isMainModule:
|
||||
import cligen; dispatch main
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import strformat, strutils, times
|
||||
import strformat, strutils, times, os
|
||||
import imguin/[cimgui, glfw_opengl, simple]
|
||||
import ../../utils/[appImGui, colors]
|
||||
import ../../../common/[types, utils]
|
||||
@@ -6,7 +6,7 @@ import ../../../common/[types, utils]
|
||||
type
|
||||
DownloadsComponent* = ref object of RootObj
|
||||
title: string
|
||||
items: seq[LootItem]
|
||||
items*: seq[LootItem]
|
||||
selectedIndex: int
|
||||
|
||||
|
||||
@@ -16,24 +16,6 @@ proc LootDownloads*(title: string): DownloadsComponent =
|
||||
result.items = @[]
|
||||
result.selectedIndex = -1
|
||||
|
||||
result.items.add(@[LootItem(
|
||||
agentId: "DEADBEEF",
|
||||
path: "C:\\Software\\Conquest\\README.md",
|
||||
timestamp: now().toTime().toUnix(),
|
||||
size: 1000,
|
||||
host: "WKS-1",
|
||||
data: string.toBytes("README.md\nPreview\nHello world.")
|
||||
),
|
||||
LootItem(
|
||||
agentId: "DEADBEEF",
|
||||
path: "C:\\Software\\Conquest\\README.md",
|
||||
timestamp: now().toTime().toUnix(),
|
||||
size: 1000,
|
||||
host: "WKS-1",
|
||||
data: string.toBytes("README.md\nPreview\nHello world.")
|
||||
)
|
||||
])
|
||||
|
||||
proc draw*(component: DownloadsComponent, showComponent: ptr bool) =
|
||||
igBegin(component.title, showComponent, 0)
|
||||
defer: igEnd()
|
||||
@@ -60,12 +42,14 @@ proc draw*(component: DownloadsComponent, showComponent: ptr bool) =
|
||||
ImGui_TableFlags_SizingStretchSame.int32
|
||||
)
|
||||
|
||||
let cols: int32 = 4
|
||||
let cols: int32 = 6
|
||||
if igBeginTable("##Items", cols, tableFlags, vec2(0.0f, 0.0f), 0.0f):
|
||||
igTableSetupColumn("ID", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("AgentID", ImGuiTableColumnFlags_DefaultHide.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Host", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Path", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Creation Date", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Size", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Host", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupScrollFreeze(0, 1)
|
||||
igTableHeadersRow()
|
||||
|
||||
@@ -75,19 +59,25 @@ proc draw*(component: DownloadsComponent, showComponent: ptr bool) =
|
||||
if igTableSetColumnIndex(0):
|
||||
igPushID_Int(i.int32)
|
||||
let isSelected = component.selectedIndex == i
|
||||
if igSelectable_Bool(item.path.cstring, isSelected, ImGuiSelectableFlags_SpanAllColumns.int32 or ImGuiSelectableFlags_AllowOverlap.int32, vec2(0, 0)):
|
||||
if igSelectable_Bool(item.lootId.cstring, isSelected, ImGuiSelectableFlags_SpanAllColumns.int32 or ImGuiSelectableFlags_AllowOverlap.int32, vec2(0, 0)):
|
||||
component.selectedIndex = i
|
||||
igPopID()
|
||||
|
||||
if igTableSetColumnIndex(1):
|
||||
igText(item.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss"))
|
||||
igText(item.agentId)
|
||||
|
||||
if igTableSetColumnIndex(2):
|
||||
igText(item.host.cstring)
|
||||
|
||||
if igTableSetColumnIndex(3):
|
||||
igText(item.path.extractFilename().replace("C_", "C:/").replace("_", "/"))
|
||||
|
||||
if igTableSetColumnIndex(4):
|
||||
igText(item.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss"))
|
||||
|
||||
if igTableSetColumnIndex(5):
|
||||
igText($item.size)
|
||||
|
||||
if igTableSetColumnIndex(3):
|
||||
igText(item.host.cstring)
|
||||
|
||||
igEndTable()
|
||||
|
||||
igEndChild()
|
||||
@@ -99,7 +89,13 @@ proc draw*(component: DownloadsComponent, showComponent: ptr bool) =
|
||||
if component.selectedIndex >= 0 and component.selectedIndex < component.items.len:
|
||||
let item = component.items[component.selectedIndex]
|
||||
|
||||
igText(item.path)
|
||||
igText(fmt("[{item.host}] "))
|
||||
igSameLine(0.0f, 0.0f)
|
||||
igText(item.path.extractFilename().replace("C_", "C:/").replace("_", "/"))
|
||||
|
||||
igSeparator()
|
||||
|
||||
igText(item.data)
|
||||
|
||||
else:
|
||||
igText("Select item to preview contents")
|
||||
|
||||
@@ -20,36 +20,18 @@ proc LootScreenshots*(title: string): ScreenshotsComponent =
|
||||
result.title = title
|
||||
result.items = @[]
|
||||
result.selectedIndex = -1
|
||||
|
||||
result.items.add(@[LootItem(
|
||||
agentId: "DEADBEEF",
|
||||
timestamp: now().toTime().toUnix(),
|
||||
size: 1000,
|
||||
path: "/mnt/c/Users/jakob/Documents/Projects/conquest/data/loot/570DCB57/screenshot_1757769346.bmp",
|
||||
host: "WKS-1",
|
||||
data: string.toBytes(readFile("/mnt/c/Users/jakob/Documents/Projects/conquest/data/loot/570DCB57/screenshot_1757769346.bmp"))
|
||||
),
|
||||
LootItem(
|
||||
agentId: "DEADBEEF",
|
||||
timestamp: now().toTime().toUnix(),
|
||||
path: "/mnt/c/Users/jakob/Documents/Projects/conquest/data/loot/C2468819/screenshot_1759238569.png",
|
||||
size: 1000,
|
||||
host: "WKS-1",
|
||||
data: string.toBytes(readFile("/mnt/c/Users/jakob/Documents/Projects/conquest/data/loot/C2468819/screenshot_1759238569.png"))
|
||||
)
|
||||
])
|
||||
|
||||
result.textures = initTable[string, ScreenshotTexture]()
|
||||
|
||||
for item in result.items:
|
||||
var textureId: GLuint
|
||||
let (width, height) = loadTextureFromBytes(item.data, textureId)
|
||||
|
||||
result.textures[item.path] = ScreenshotTexture(
|
||||
textureId: textureId,
|
||||
width: width,
|
||||
height: height
|
||||
)
|
||||
proc addItem*(component: ScreenshotsComponent, screenshot: LootItem) =
|
||||
component.items.add(screenshot)
|
||||
|
||||
var textureId: GLuint
|
||||
let (width, height) = loadTextureFromBytes(string.toBytes(screenshot.data), textureId)
|
||||
component.textures[screenshot.path] = ScreenshotTexture(
|
||||
textureId: textureId,
|
||||
width: width,
|
||||
height: height
|
||||
)
|
||||
|
||||
proc draw*(component: ScreenshotsComponent, showComponent: ptr bool) =
|
||||
igBegin(component.title, showComponent, 0)
|
||||
@@ -77,12 +59,13 @@ proc draw*(component: ScreenshotsComponent, showComponent: ptr bool) =
|
||||
ImGui_TableFlags_SizingStretchSame.int32
|
||||
)
|
||||
|
||||
let cols: int32 = 4
|
||||
let cols: int32 = 5
|
||||
if igBeginTable("##Items", cols, tableFlags, vec2(0.0f, 0.0f), 0.0f):
|
||||
igTableSetupColumn("Path", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Creation Date", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Size", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("ID", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("AgentID", ImGuiTableColumnFlags_DefaultHide.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Host", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("Creation Date", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupColumn("File Size", ImGuiTableColumnFlags_None.int32, 0.0f, 0)
|
||||
igTableSetupScrollFreeze(0, 1)
|
||||
igTableHeadersRow()
|
||||
|
||||
@@ -92,19 +75,22 @@ proc draw*(component: ScreenshotsComponent, showComponent: ptr bool) =
|
||||
if igTableSetColumnIndex(0):
|
||||
igPushID_Int(i.int32)
|
||||
let isSelected = component.selectedIndex == i
|
||||
if igSelectable_Bool(item.path.cstring, isSelected, ImGuiSelectableFlags_SpanAllColumns.int32 or ImGuiSelectableFlags_AllowOverlap.int32, vec2(0, 0)):
|
||||
if igSelectable_Bool(item.lootId.cstring, isSelected, ImGuiSelectableFlags_SpanAllColumns.int32 or ImGuiSelectableFlags_AllowOverlap.int32, vec2(0, 0)):
|
||||
component.selectedIndex = i
|
||||
igPopID()
|
||||
|
||||
if igTableSetColumnIndex(1):
|
||||
igText(item.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss"))
|
||||
igText(item.agentId)
|
||||
|
||||
if igTableSetColumnIndex(2):
|
||||
igText($item.size)
|
||||
|
||||
if igTableSetColumnIndex(3):
|
||||
igText(item.host.cstring)
|
||||
|
||||
if igTableSetColumnIndex(3):
|
||||
igText(item.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss"))
|
||||
|
||||
if igTableSetColumnIndex(4):
|
||||
igText($item.size)
|
||||
|
||||
igEndTable()
|
||||
|
||||
igEndChild()
|
||||
|
||||
@@ -248,13 +248,14 @@ type
|
||||
type
|
||||
EventType* = enum
|
||||
CLIENT_HEARTBEAT = 0'u8 # Basic checkin
|
||||
CLIENT_KEY_EXCHANGE = 200'u8
|
||||
CLIENT_KEY_EXCHANGE = 200'u8 # Unencrypted public key sent by both parties for key exchange
|
||||
|
||||
# Sent by client
|
||||
CLIENT_AGENT_BUILD = 1'u8 # Generate an agent binary for a specific listener
|
||||
CLIENT_AGENT_TASK = 2'u8 # Instruct TS to send queue a command for a specific agent
|
||||
CLIENT_LISTENER_START = 3'u8 # Start a listener on the TS
|
||||
CLIENT_LISTENER_STOP = 4'u8 # Stop a listener
|
||||
CLIENT_REQUEST_SYNC = 5'u8 # Request to download a file/screenshot to the client
|
||||
|
||||
# Sent by team server
|
||||
CLIENT_PROFILE = 100'u8 # Team server profile and configuration
|
||||
@@ -349,10 +350,16 @@ type
|
||||
spoofStack*: bool
|
||||
modules*: uint32
|
||||
|
||||
LootItemType* = enum
|
||||
DOWNLOAD = 0'u8
|
||||
SCREENSHOT = 1'u8
|
||||
|
||||
LootItem* = ref object
|
||||
itemType*: LootItemType
|
||||
lootId*: string
|
||||
agentId*: string
|
||||
host*: string
|
||||
path*: string
|
||||
timestamp*: int64
|
||||
size*: int
|
||||
host*: string
|
||||
data*: seq[byte]
|
||||
data*: string # Image bytes or file content (binary data prefixed with length)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import terminal, strformat, strutils, sequtils, tables, system, std/[dirs, paths]
|
||||
import terminal, strformat, strutils, sequtils, tables, os, times
|
||||
import std/[dirs, paths]
|
||||
|
||||
import ../globals
|
||||
import ../db/database
|
||||
@@ -118,20 +119,44 @@ proc handleResult*(resultData: seq[byte]) =
|
||||
|
||||
of RESULT_BINARY:
|
||||
# Write binary data to a file
|
||||
# A binary result packet consists of the filename and file contents, both prefixed with their respective lengths as a uint32 value, unless it is fragmented
|
||||
# A binary result packet consists of the filename and file contents, both prefixed with their respective lengths as a uint32 value
|
||||
var unpacker = Unpacker.init(Bytes.toString(taskResult.data))
|
||||
let
|
||||
fileName = unpacker.getDataWithLengthPrefix().replace("\\", "_").replace(":", "") # Replace path characters for better storage of downloaded files
|
||||
fileBytes = unpacker.getDataWithLengthPrefix()
|
||||
fileData = unpacker.getDataWithLengthPrefix()
|
||||
|
||||
# Create loot directory for the agent
|
||||
createDir(cast[Path](fmt"{CONQUEST_ROOT}/data/loot/{agentId}"))
|
||||
let downloadPath = fmt"{CONQUEST_ROOT}/data/loot/{agentId}/{fileName}"
|
||||
|
||||
writeFile(downloadPath, fileBytes)
|
||||
writeFile(downloadPath, fileData)
|
||||
|
||||
cq.success(fmt"File downloaded to {downloadPath} ({$fileBytes.len()} bytes).", "\n")
|
||||
cq.client.sendConsoleItem(agentId, LOG_SUCCESS, fmt"File downloaded to {downloadPath} ({$fileBytes.len()} bytes).")
|
||||
# Get file information
|
||||
let fileInfo = getFileInfo(downloadPath)
|
||||
var lootItem = LootItem(
|
||||
lootId: generateUuid(),
|
||||
itemType: parseEnum[LootItemType](($cast[CommandType](taskResult.command)).split("_")[1]), # CMD_DOWNLOAD -> DOWNLOAD, CMD_SCREENSHOT -> SCREENSHOT
|
||||
agentId: agentId,
|
||||
path: downloadPath,
|
||||
timestamp: fileInfo.creationTime.toUnix(),
|
||||
size: fileInfo.size,
|
||||
host: cq.agents[agentId].hostname
|
||||
)
|
||||
|
||||
if lootItem.itemType == SCREENSHOT:
|
||||
lootItem.data = createThumbnail(readFile(downloadPath)) # Create a smaller thumbnail version of the screenshot for better transportability
|
||||
elif lootItem.itemType == DOWNLOAD:
|
||||
lootItem.data = readFile(downloadPath) # Read downloaded file
|
||||
|
||||
# Store loot in database
|
||||
if not cq.dbStoreLoot(lootItem):
|
||||
raise newException(ValueError, fmt"Failed to store loot in database." & "\n")
|
||||
|
||||
# Send packet to client to display file/screenshot in the UI
|
||||
cq.client.sendLoot(lootItem)
|
||||
|
||||
cq.output(fmt"File downloaded to {downloadPath} ({$fileData.len()} bytes).", "\n")
|
||||
cq.client.sendConsoleItem(agentId, LOG_OUTPUT, fmt"File downloaded to {downloadPath} ({$fileData.len()} bytes).")
|
||||
|
||||
of RESULT_NO_OUTPUT:
|
||||
cq.output()
|
||||
|
||||
@@ -143,3 +143,12 @@ proc sendBuildlogItem*(client: WsConnection, logType: LogType, message: string)
|
||||
)
|
||||
if client != nil:
|
||||
client.ws.sendEvent(event, client.sessionKey)
|
||||
|
||||
proc sendLoot*(client: WsConnection, loot: LootItem) =
|
||||
let event = Event(
|
||||
eventType: CLIENT_LOOT_ADD,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
data: %loot
|
||||
)
|
||||
if client != nil:
|
||||
client.ws.sendEvent(event, client.sessionKey)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import system, terminal, tiny_sqlite
|
||||
|
||||
import ./[dbAgent, dbListener]
|
||||
import ./[dbAgent, dbListener, dbLoot]
|
||||
import ../core/logger
|
||||
import ../../common/types
|
||||
|
||||
# Export functions so that only ./db/database is required to be imported
|
||||
export dbAgent, dbListener
|
||||
export dbAgent, dbListener, dbLoot
|
||||
|
||||
proc dbInit*(cq: Conquest) =
|
||||
|
||||
@@ -15,15 +15,15 @@ proc dbInit*(cq: Conquest) =
|
||||
# Create tables
|
||||
conquestDb.execScript("""
|
||||
CREATE TABLE listeners (
|
||||
name TEXT PRIMARY KEY,
|
||||
listenerId TEXT PRIMARY KEY,
|
||||
address TEXT NOT NULL,
|
||||
port INTEGER NOT NULL UNIQUE,
|
||||
protocol TEXT NOT NULL CHECK (protocol IN ('http'))
|
||||
);
|
||||
|
||||
CREATE TABLE agents (
|
||||
name TEXT PRIMARY KEY,
|
||||
listener TEXT NOT NULL,
|
||||
agentId TEXT PRIMARY KEY,
|
||||
listenerId TEXT NOT NULL,
|
||||
process TEXT NOT NULL,
|
||||
pid INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
@@ -40,6 +40,16 @@ proc dbInit*(cq: Conquest) =
|
||||
sessionKey BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE loot (
|
||||
lootId TEXT PRIMARY KEY,
|
||||
itemType INTEGER NOT NULL,
|
||||
agentId TEXT NOT NULL,
|
||||
host TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
size INTEGER NOT NULL
|
||||
);
|
||||
|
||||
""")
|
||||
|
||||
cq.info("Using new database: \"", cq.dbPath, "\".\n")
|
||||
|
||||
@@ -15,7 +15,7 @@ proc dbStoreAgent*(cq: Conquest, agent: Agent): bool =
|
||||
let sessionKeyBlob = agent.sessionKey.toSeq()
|
||||
|
||||
conquestDb.exec("""
|
||||
INSERT INTO agents (name, listener, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, sleep, modules, firstCheckin, latestCheckin, sessionKey)
|
||||
INSERT INTO agents (agentId, listenerId, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, sleep, modules, firstCheckin, latestCheckin, sessionKey)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""", agent.agentId, agent.listenerId, agent.process, agent.pid, agent.username, agent.hostname, agent.domain, agent.ipInternal, agent.ipExternal, agent.os, agent.elevated, agent.sleep, agent.modules, agent.firstCheckin, agent.latestCheckin, sessionKeyBlob)
|
||||
|
||||
@@ -32,7 +32,7 @@ proc dbGetAllAgents*(cq: Conquest): seq[Agent] =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
for row in conquestDb.iterate("SELECT name, listener, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKey FROM agents;"):
|
||||
for row in conquestDb.iterate("SELECT agentId, listenerId, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKey FROM agents;"):
|
||||
let (agentId, listenerId, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKeyBlob) = row.unpack((string, string, int, string, int, string, string, string, string, string, string, bool, uint32, int64, int64, seq[byte]))
|
||||
|
||||
# Convert session key blob back to array
|
||||
@@ -77,7 +77,7 @@ proc dbGetAllAgentsByListener*(cq: Conquest, listenerName: string): seq[Agent] =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
for row in conquestDb.iterate("SELECT name, listener, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKey FROM agents WHERE listener = ?;", listenerName):
|
||||
for row in conquestDb.iterate("SELECT agentId, listenerId, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKey FROM agents WHERE listenerId = ?;", listenerName):
|
||||
let (agentId, listenerId, sleep, process, pid, username, hostname, domain, ipInternal, ipExternal, os, elevated, modules, firstCheckin, latestCheckin, sessionKeyBlob) = row.unpack((string, string, int, string, int, string, string, string, string, string, string, bool, uint32, int64, int64, seq[byte]))
|
||||
|
||||
# Convert session key blob back to array
|
||||
@@ -114,11 +114,11 @@ proc dbGetAllAgentsByListener*(cq: Conquest, listenerName: string): seq[Agent] =
|
||||
|
||||
return agents
|
||||
|
||||
proc dbDeleteAgentByName*(cq: Conquest, name: string): bool =
|
||||
proc dbDeleteAgentByName*(cq: Conquest, agentId: string): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("DELETE FROM agents WHERE name = ?", name)
|
||||
conquestDb.exec("DELETE FROM agents WHERE agentId = ?", agentId)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
@@ -131,7 +131,7 @@ proc dbAgentExists*(cq: Conquest, agentName: string): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
let res = conquestDb.one("SELECT 1 FROM agents WHERE name = ? LIMIT 1", agentName)
|
||||
let res = conquestDb.one("SELECT 1 FROM agents WHERE agentId = ? LIMIT 1", agentName)
|
||||
|
||||
conquestDb.close()
|
||||
|
||||
@@ -144,7 +144,7 @@ proc dbUpdateSleep*(cq: Conquest, agentName: string, delay: int): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("UPDATE agents SET sleep = ? WHERE name = ?", delay, agentName)
|
||||
conquestDb.exec("UPDATE agents SET sleep = ? WHERE agentId = ?", delay, agentName)
|
||||
|
||||
conquestDb.close()
|
||||
return true
|
||||
|
||||
@@ -19,7 +19,7 @@ proc dbStoreListener*(cq: Conquest, listener: Listener): bool =
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("""
|
||||
INSERT INTO listeners (name, address, port, protocol)
|
||||
INSERT INTO listeners (listenerId, address, port, protocol)
|
||||
VALUES (?, ?, ?, ?);
|
||||
""", listener.listenerId, listener.address, listener.port, $listener.protocol)
|
||||
|
||||
@@ -37,7 +37,7 @@ proc dbGetAllListeners*(cq: Conquest): seq[Listener] =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
for row in conquestDb.iterate("SELECT name, address, port, protocol FROM listeners;"):
|
||||
for row in conquestDb.iterate("SELECT listenerId, address, port, protocol FROM listeners;"):
|
||||
let (listenerId, address, port, protocol) = row.unpack((string, string, int, string))
|
||||
|
||||
let l = Listener(
|
||||
@@ -54,11 +54,11 @@ proc dbGetAllListeners*(cq: Conquest): seq[Listener] =
|
||||
|
||||
return listeners
|
||||
|
||||
proc dbDeleteListenerByName*(cq: Conquest, name: string): bool =
|
||||
proc dbDeleteListenerByName*(cq: Conquest, listenerId: string): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("DELETE FROM listeners WHERE name = ?", name)
|
||||
conquestDb.exec("DELETE FROM listeners WHERE listenerId = ?", listenerId)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
@@ -70,7 +70,7 @@ proc dbListenerExists*(cq: Conquest, listenerName: string): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
let res = conquestDb.one("SELECT 1 FROM listeners WHERE name = ? LIMIT 1", listenerName)
|
||||
let res = conquestDb.one("SELECT 1 FROM listeners WHERE listenerId = ? LIMIT 1", listenerName)
|
||||
|
||||
conquestDb.close()
|
||||
|
||||
|
||||
77
src/server/db/dbLoot.nim
Normal file
77
src/server/db/dbLoot.nim
Normal file
@@ -0,0 +1,77 @@
|
||||
import strutils, system, terminal, tiny_sqlite, pixie
|
||||
import stb_image/write as stbiw
|
||||
import ../core/logger
|
||||
import ../../common/[types, utils]
|
||||
|
||||
proc dbStoreLoot*(cq: Conquest, loot: LootItem): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("""
|
||||
INSERT INTO loot (lootId, itemType, agentId, host, path, timestamp, size)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
""", loot.lootId, int(loot.itemType), loot.agentId, loot.host, loot.path, loot.timestamp, loot.size)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
cq.error(getCurrentExceptionMsg())
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
proc createThumbnail*(data: string, maxWidth: int = 1024, quality: int = 90): string =
|
||||
let img: Image = decodeImage(data)
|
||||
|
||||
let aspectRatio = img.height.float / img.width.float
|
||||
let
|
||||
width = min(maxWidth, img.width)
|
||||
height = int(width.float * aspectRatio)
|
||||
|
||||
# Resize image
|
||||
let thumbnail = img.resize(width, height)
|
||||
|
||||
# Convert to JPEG image for smaller file size
|
||||
var rgbaData = newSeq[byte](width * height * 4)
|
||||
var i = 0
|
||||
for y in 0..<height:
|
||||
for x in 0..<width:
|
||||
let color = thumbnail[x, y]
|
||||
rgbaData[i] = color.r
|
||||
rgbaData[i + 1] = color.g
|
||||
rgbaData[i + 2] = color.b
|
||||
rgbaData[i + 3] = color.a
|
||||
i += 4
|
||||
|
||||
return Bytes.toString(stbiw.writeJPG(width, height, 4, rgbaData, quality))
|
||||
|
||||
proc dbGetLoot*(cq: Conquest): seq[LootItem] =
|
||||
var loot: seq[LootItem] = @[]
|
||||
|
||||
try:
|
||||
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||
|
||||
for row in conquestDb.iterate("SELECT lootId, itemType, agentId, host, path, timestamp, size FROM loot;"):
|
||||
let (lootId, itemType, agentId, host, path, timestamp, size) = row.unpack((string, int, string, string, string, int64, int))
|
||||
|
||||
var l = LootItem(
|
||||
lootId: lootId,
|
||||
itemType: cast[LootItemType](itemType),
|
||||
agentId: agentId,
|
||||
host: host,
|
||||
path: path,
|
||||
timestamp: timestamp,
|
||||
size: size
|
||||
)
|
||||
|
||||
if l.itemType == SCREENSHOT:
|
||||
l.data = createThumbnail(readFile(path)) # Create a smaller thumbnail version of the screenshot for better transportability
|
||||
elif l.itemType == DOWNLOAD:
|
||||
l.data = readFile(path) # Read downloaded file
|
||||
|
||||
loot.add(l)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
cq.error(getCurrentExceptionMsg())
|
||||
|
||||
return loot
|
||||
@@ -56,14 +56,21 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
|
||||
cq.client.sessionKey = deriveSessionKey(cq.keyPair, publicKey)
|
||||
|
||||
# Send relevant information to the client
|
||||
# - C2 profile
|
||||
# - agent sessions
|
||||
# - listeners
|
||||
# C2 profile
|
||||
cq.client.sendProfile(cq.profile)
|
||||
|
||||
# Listeners
|
||||
for id, listener in cq.listeners:
|
||||
cq.client.sendListener(listener)
|
||||
|
||||
# Agent sessions
|
||||
for id, agent in cq.agents:
|
||||
cq.client.sendAgent(agent)
|
||||
|
||||
# Downloads & Screenshots
|
||||
for lootItem in cq.dbGetLoot():
|
||||
cq.client.sendLoot(lootItem)
|
||||
|
||||
cq.client.sendEventlogItem(LOG_SUCCESS_SHORT, "Connected to Conquest team server.")
|
||||
|
||||
of CLIENT_AGENT_TASK:
|
||||
|
||||
Reference in New Issue
Block a user