Implemented initial version of logging system. Log formatting and content needs to be reworked.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,11 +3,14 @@
|
||||
*.db
|
||||
*.key
|
||||
!data/*/.gitkeep
|
||||
# Ignore binaries
|
||||
# Ignore binaries
|
||||
bin/*
|
||||
!bin/.gitkeep
|
||||
*.exe
|
||||
|
||||
# Ignore log files
|
||||
*.log
|
||||
|
||||
.vscode/
|
||||
|
||||
# Nim
|
||||
|
||||
@@ -165,7 +165,7 @@ type
|
||||
port*: int
|
||||
protocol*: Protocol
|
||||
|
||||
# Server context structures
|
||||
# Context structures
|
||||
type
|
||||
KeyPair* = object
|
||||
privateKey*: Key
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import terminal, strformat, strutils, sequtils, tables, times, system
|
||||
|
||||
import ../core/logger
|
||||
import ../[utils, globals]
|
||||
import ../db/database
|
||||
import ../protocol/packer
|
||||
@@ -26,6 +27,10 @@ proc register*(registrationData: seq[byte]): bool =
|
||||
cq.writeLine(fgRed, styleBright, fmt"[-] Failed to insert agent {agent.agentId} into database.", "\n")
|
||||
return false
|
||||
|
||||
if not cq.makeAgentLogDirectory(agent.agentId):
|
||||
cq.writeLine(fgRed, styleBright, "[-] Failed to create log")
|
||||
return false
|
||||
|
||||
cq.agents[agent.agentId] = agent
|
||||
|
||||
let date = agent.firstCheckin.format("dd-MM-yyyy HH:mm:ss")
|
||||
@@ -77,23 +82,23 @@ proc handleResult*(resultData: seq[byte]) =
|
||||
listenerId = Uuid.toString(taskResult.listenerId)
|
||||
|
||||
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")
|
||||
cq.info(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")
|
||||
|
||||
case cast[StatusType](taskResult.status):
|
||||
of STATUS_COMPLETED:
|
||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {taskId} completed.")
|
||||
cq.success(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {taskId} completed.")
|
||||
of STATUS_FAILED:
|
||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {taskId} failed.")
|
||||
cq.error(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {taskId} failed.")
|
||||
of STATUS_IN_PROGRESS:
|
||||
discard
|
||||
|
||||
case cast[ResultType](taskResult.resultType):
|
||||
of RESULT_STRING:
|
||||
if int(taskResult.length) > 0:
|
||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, "Output:")
|
||||
cq.info(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, "Output:")
|
||||
# Split result string on newline to keep formatting
|
||||
for line in Bytes.toString(taskResult.data).split("\n"):
|
||||
cq.writeLine(line)
|
||||
cq.output(line)
|
||||
|
||||
of RESULT_BINARY:
|
||||
# Write binary data to a file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import terminal, strformat, strutils, tables, times, system, parsetoml
|
||||
|
||||
import ./task
|
||||
import ./[task, logger]
|
||||
import ../utils
|
||||
import ../db/database
|
||||
import ../../common/types
|
||||
@@ -118,6 +118,8 @@ proc agentInteract*(cq: Conquest, name: string) =
|
||||
cq.setStatusBar(@[("[mode]", "interact"), ("[username]", fmt"{agent.username}"), ("[hostname]", fmt"{agent.hostname}"), ("[ip]", fmt"{agent.ip}"), ("[domain]", fmt"{agent.domain}")])
|
||||
cq.writeLine(fgYellow, styleBright, "[+] ", resetStyle, fmt"Started interacting with agent ", fgYellow, styleBright, agent.agentId, resetStyle, ". Type 'help' to list available commands.\n")
|
||||
cq.interactAgent = agent
|
||||
|
||||
cq.log(fmt"Started interacting with agent {agent.agentId}.")
|
||||
|
||||
while command.replace(" ", "") != "back":
|
||||
command = cq.readLine()
|
||||
|
||||
@@ -40,58 +40,58 @@ proc listenerList*(cq: Conquest) =
|
||||
proc listenerStart*(cq: Conquest, host: string, portStr: string) =
|
||||
|
||||
# Validate arguments
|
||||
if not validatePort(portStr):
|
||||
cq.writeLine(fgRed, styleBright, fmt"[-] Invalid port number: {portStr}")
|
||||
return
|
||||
|
||||
let port = portStr.parseInt
|
||||
|
||||
# Create new listener
|
||||
let
|
||||
name: string = generateUUID()
|
||||
listenerSettings = newSettings(
|
||||
appName = name,
|
||||
debug = false,
|
||||
address = "", # For some reason, the program crashes when the ip parameter is passed to the newSettings function
|
||||
port = Port(port) # As a result, I will hardcode the listener to be served on all interfaces (0.0.0.0) by default
|
||||
) # TODO: fix this issue and start the listener on the address passed as the HOST parameter
|
||||
|
||||
var listener = newApp(settings = listenerSettings)
|
||||
|
||||
# Define API endpoints based on C2 profile
|
||||
# GET requests
|
||||
for endpoint in cq.profile.getArray("http-get.endpoints"):
|
||||
listener.addRoute(endpoint.getStringValue(), routes.httpGet)
|
||||
|
||||
# POST requests
|
||||
var postMethods: seq[HttpMethod]
|
||||
for reqMethod in cq.profile.getArray("http-post.request-methods"):
|
||||
postMethods.add(parseEnum[HttpMethod](reqMethod.getStringValue()))
|
||||
|
||||
# Default method is POST
|
||||
if postMethods.len == 0:
|
||||
postMethods = @[HttpPost]
|
||||
|
||||
for endpoint in cq.profile.getArray("http-post.endpoints"):
|
||||
listener.addRoute(endpoint.getStringValue(), routes.httpPost, postMethods)
|
||||
|
||||
listener.registerErrorHandler(Http404, routes.error404)
|
||||
|
||||
# Store listener in database
|
||||
var listenerInstance = Listener(
|
||||
listenerId: name,
|
||||
address: host,
|
||||
port: port,
|
||||
protocol: HTTP
|
||||
)
|
||||
if not cq.dbStoreListener(listenerInstance):
|
||||
return
|
||||
|
||||
# Start serving
|
||||
try:
|
||||
if not validatePort(portStr):
|
||||
raise newException(CatchableError,fmt"[-] Invalid port number: {portStr}")
|
||||
|
||||
let port = portStr.parseInt
|
||||
|
||||
# Create new listener
|
||||
let
|
||||
name: string = generateUUID()
|
||||
listenerSettings = newSettings(
|
||||
appName = name,
|
||||
debug = false,
|
||||
address = "", # For some reason, the program crashes when the ip parameter is passed to the newSettings function
|
||||
port = Port(port) # As a result, I will hardcode the listener to be served on all interfaces (0.0.0.0) by default
|
||||
) # TODO: fix this issue and start the listener on the address passed as the HOST parameter
|
||||
|
||||
var listener = newApp(settings = listenerSettings)
|
||||
|
||||
# Define API endpoints based on C2 profile
|
||||
# GET requests
|
||||
for endpoint in cq.profile.getArray("http-get.endpoints"):
|
||||
listener.addRoute(endpoint.getStringValue(), routes.httpGet)
|
||||
|
||||
# POST requests
|
||||
var postMethods: seq[HttpMethod]
|
||||
for reqMethod in cq.profile.getArray("http-post.request-methods"):
|
||||
postMethods.add(parseEnum[HttpMethod](reqMethod.getStringValue()))
|
||||
|
||||
# Default method is POST
|
||||
if postMethods.len == 0:
|
||||
postMethods = @[HttpPost]
|
||||
|
||||
for endpoint in cq.profile.getArray("http-post.endpoints"):
|
||||
listener.addRoute(endpoint.getStringValue(), routes.httpPost, postMethods)
|
||||
|
||||
listener.registerErrorHandler(Http404, routes.error404)
|
||||
|
||||
# Store listener in database
|
||||
var listenerInstance = Listener(
|
||||
listenerId: name,
|
||||
address: host,
|
||||
port: port,
|
||||
protocol: HTTP
|
||||
)
|
||||
if not cq.dbStoreListener(listenerInstance):
|
||||
raise newException(CatchableError, "Failed to store listener in database.")
|
||||
|
||||
# Start serving
|
||||
discard listener.runAsync()
|
||||
cq.add(listenerInstance)
|
||||
cq.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on {host}:{portStr}.")
|
||||
|
||||
except CatchableError as err:
|
||||
cq.writeLine(fgRed, styleBright, "[-] Failed to start listener: ", err.msg)
|
||||
|
||||
|
||||
57
src/server/core/logger.nim
Normal file
57
src/server/core/logger.nim
Normal file
@@ -0,0 +1,57 @@
|
||||
import terminal, times, strformat, strutils
|
||||
import std/[dirs, paths]
|
||||
import ../../common/[types, profile]
|
||||
|
||||
proc makeAgentLogDirectory*(cq: Conquest, agentId: string): bool =
|
||||
try:
|
||||
let cqDir = cq.profile.getString("conquest_directory")
|
||||
createDir(cast[Path](fmt"{cqDir}/data/logs/{agentId}"))
|
||||
return true
|
||||
except OSError:
|
||||
return false
|
||||
|
||||
proc log*(cq: Conquest, logEntry: string) =
|
||||
if cq.interactAgent == nil:
|
||||
return
|
||||
|
||||
let
|
||||
date = now().format("dd-MM-yyyy")
|
||||
timestamp = now().format("dd-MM-yyyy HH:mm:ss")
|
||||
cqDir = cq.profile.getString("conquest_directory")
|
||||
agentLogPath = fmt"{cqDir}/data/logs/{cq.interactAgent.agentId}/{date}.log"
|
||||
|
||||
# Write log entry to file
|
||||
let file = open(agentLogPath, fmAppend)
|
||||
file.writeLine(fmt"[{timestamp}] {logEntry}")
|
||||
file.flushFile()
|
||||
|
||||
proc extractStrings(args: string): string =
|
||||
if not args.startsWith("("):
|
||||
return args
|
||||
|
||||
# Remove styling arguments, such as fgRed, styleBright, resetStyle, etc. by extracting only arguments that are quoted
|
||||
var message: string
|
||||
for str in args[1..^2].split(", "):
|
||||
if str.startsWith("\""):
|
||||
message &= str
|
||||
return message.replace("\"", "")
|
||||
|
||||
template info*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, args)
|
||||
cq.log("[*] " & extractStrings($(args)))
|
||||
|
||||
template error*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, args)
|
||||
cq.log("[-] " & extractStrings($(args)))
|
||||
|
||||
template warn*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.writeLine(fgYellow, "[!] ", resetStyle, args)
|
||||
cq.log("[!] " & extractStrings($(args)))
|
||||
|
||||
template success*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.writeLine(fgGreen, "[+] ", resetStyle, args)
|
||||
cq.log("[+] " & extractStrings($(args)))
|
||||
|
||||
template output*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.writeLine(args)
|
||||
cq.log("[>] " & extractStrings($(args)))
|
||||
@@ -1,7 +1,7 @@
|
||||
import prompt, terminal, argparse, parsetoml
|
||||
import strutils, strformat, times, system, tables
|
||||
|
||||
import ./[agent, listener, builder]
|
||||
import ./[agent, listener, builder, logger]
|
||||
import ../[globals, utils]
|
||||
import ../db/database
|
||||
import ../../common/[types, utils, crypto, profile]
|
||||
@@ -150,8 +150,8 @@ proc startServer*(profilePath: string) =
|
||||
try:
|
||||
# Load and parse profile
|
||||
let profile = parseFile(profilePath)
|
||||
styledEcho(fgGreen, styleBright, "[+] Using profile \"", profile.getString("name"), "\" (", profilePath ,").")
|
||||
styledEcho(fgGreen, styleBright, "[+] ", profile.getString("private_key_file"), ": Private key found.")
|
||||
styledEcho(fgBlack, styleBright, "[*] ", "Using profile \"", profile.getString("name"), "\" (", profilePath ,").")
|
||||
styledEcho(fgBlack, styleBright, "[*] ", "Using private key \"", profile.getString("private_key_file"), "\".")
|
||||
|
||||
# Initialize framework context
|
||||
cq = Conquest.init(profile)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import times, strformat, terminal, tables, sequtils, strutils
|
||||
|
||||
import ./logger
|
||||
import ../utils
|
||||
import ../protocol/parser
|
||||
import ../../modules/manager
|
||||
@@ -56,6 +57,7 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
|
||||
|
||||
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
||||
cq.writeLine(fgBlue, styleBright, fmt"[{date}] ", fgYellow, fmt"[{cq.interactAgent.agentId}] ", resetStyle, styleBright, input)
|
||||
cq.log(fmt"Agent command received: {input}")
|
||||
|
||||
# Convert user input into sequence of string arguments
|
||||
let parsedArgs = parseInput(input)
|
||||
@@ -78,8 +80,8 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
|
||||
|
||||
# Add task to queue
|
||||
cq.interactAgent.tasks.add(task)
|
||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"Tasked agent to {command.description.toLowerAscii()}")
|
||||
cq.info(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"Tasked agent to {command.description.toLowerAscii()}")
|
||||
|
||||
except CatchableError:
|
||||
cq.writeLine(fgRed, styleBright, fmt"[-] {getCurrentExceptionMsg()}" & "\n")
|
||||
cq.error(getCurrentExceptionMsg() & "\n")
|
||||
return
|
||||
@@ -41,7 +41,7 @@ proc dbInit*(cq: Conquest) =
|
||||
|
||||
""")
|
||||
|
||||
cq.writeLine(fgGreen, styleBright, "[+] ", cq.dbPath, ": Database created.")
|
||||
cq.writeLine(fgBlack, styleBright, "[*] Using new database: \"", cq.dbPath, "\".\n")
|
||||
conquestDb.close()
|
||||
except SqliteError as err:
|
||||
cq.writeLine(fgGreen, styleBright, "[+] ", cq.dbPath, ": Database file found.")
|
||||
cq.writeLine(fgBlack, styleBright, "[*] Using existing database: \"", cq.dbPath, "\".\n")
|
||||
|
||||
@@ -2,12 +2,7 @@ import strutils, terminal, tables, sequtils, times, strformat, prompt
|
||||
import std/wordwrap
|
||||
|
||||
import ../common/types
|
||||
|
||||
# Utility functions
|
||||
proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] =
|
||||
# TODO: Verify that address is in correct, expected format
|
||||
let octets = ip.split('.')
|
||||
return (parseInt(octets[0]), parseInt(octets[1]), parseInt(octets[2]), parseInt(octets[3]))
|
||||
import core/logger
|
||||
|
||||
proc validatePort*(portStr: string): bool =
|
||||
try:
|
||||
@@ -19,17 +14,18 @@ proc validatePort*(portStr: string): bool =
|
||||
# Function templates and overwrites
|
||||
template writeLine*(cq: Conquest, args: varargs[untyped]) =
|
||||
cq.prompt.writeLine(args)
|
||||
|
||||
proc readLine*(cq: Conquest): string =
|
||||
return cq.prompt.readLine()
|
||||
template setIndicator*(cq: Conquest, indicator: string) =
|
||||
proc setIndicator*(cq: Conquest, indicator: string) =
|
||||
cq.prompt.setIndicator(indicator)
|
||||
template showPrompt*(cq: Conquest) =
|
||||
proc showPrompt*(cq: Conquest) =
|
||||
cq.prompt.showPrompt()
|
||||
template hidePrompt*(cq: Conquest) =
|
||||
proc hidePrompt*(cq: Conquest) =
|
||||
cq.prompt.hidePrompt()
|
||||
template setStatusBar*(cq: Conquest, statusBar: seq[StatusBarItem]) =
|
||||
proc setStatusBar*(cq: Conquest, statusBar: seq[StatusBarItem]) =
|
||||
cq.prompt.setStatusBar(statusBar)
|
||||
template clear*(cq: Conquest) =
|
||||
proc clear*(cq: Conquest) =
|
||||
cq.prompt.clear()
|
||||
|
||||
# Overwrite withOutput function to handle function arguments
|
||||
|
||||
Reference in New Issue
Block a user