From 4e0eae77b8ab310591f3d73a840f0550b335a6d5 Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:14:38 +0200 Subject: [PATCH] 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. --- conquest.nimble | 3 +- src/client/main.nim | 19 ++++++- src/client/views/loot/downloads.nim | 52 +++++++++--------- src/client/views/loot/screenshots.nim | 60 ++++++++------------- src/common/types.nim | 13 +++-- src/server/api/handlers.nim | 37 ++++++++++--- src/server/core/websocket.nim | 9 ++++ src/server/db/database.nim | 20 +++++-- src/server/db/dbAgent.nim | 14 ++--- src/server/db/dbListener.nim | 10 ++-- src/server/db/dbLoot.nim | 77 +++++++++++++++++++++++++++ src/server/main.nim | 13 +++-- 12 files changed, 230 insertions(+), 97 deletions(-) create mode 100644 src/server/db/dbLoot.nim diff --git a/conquest.nimble b/conquest.nimble index ad5ab25..39d5c25 100644 --- a/conquest.nimble +++ b/conquest.nimble @@ -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" \ No newline at end of file +requires "native_dialogs >= 0.2.0" +requires "pixie >= 5.1.0" \ No newline at end of file diff --git a/src/client/main.nim b/src/client/main.nim index 9052503..2f241ab 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -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 diff --git a/src/client/views/loot/downloads.nim b/src/client/views/loot/downloads.nim index 9855153..08c3014 100644 --- a/src/client/views/loot/downloads.nim +++ b/src/client/views/loot/downloads.nim @@ -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") diff --git a/src/client/views/loot/screenshots.nim b/src/client/views/loot/screenshots.nim index 55b31b2..2337081 100644 --- a/src/client/views/loot/screenshots.nim +++ b/src/client/views/loot/screenshots.nim @@ -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() diff --git a/src/common/types.nim b/src/common/types.nim index e96916e..cb92564 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -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) diff --git a/src/server/api/handlers.nim b/src/server/api/handlers.nim index d529023..574c7c7 100644 --- a/src/server/api/handlers.nim +++ b/src/server/api/handlers.nim @@ -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() diff --git a/src/server/core/websocket.nim b/src/server/core/websocket.nim index f20327f..605d1a5 100644 --- a/src/server/core/websocket.nim +++ b/src/server/core/websocket.nim @@ -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) diff --git a/src/server/db/database.nim b/src/server/db/database.nim index c72d69b..6e802ae 100644 --- a/src/server/db/database.nim +++ b/src/server/db/database.nim @@ -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") diff --git a/src/server/db/dbAgent.nim b/src/server/db/dbAgent.nim index dee6712..f92b29d 100644 --- a/src/server/db/dbAgent.nim +++ b/src/server/db/dbAgent.nim @@ -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 diff --git a/src/server/db/dbListener.nim b/src/server/db/dbListener.nim index 414a587..3d0c35d 100644 --- a/src/server/db/dbListener.nim +++ b/src/server/db/dbListener.nim @@ -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() diff --git a/src/server/db/dbLoot.nim b/src/server/db/dbLoot.nim new file mode 100644 index 0000000..ee0973a --- /dev/null +++ b/src/server/db/dbLoot.nim @@ -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..