Improved Windows version fingerprinting and fixed console window not being focused on double-click.

This commit is contained in:
Jakob Friedl
2025-11-04 13:53:54 +01:00
parent 315b7fe50a
commit f3ddc49729
3 changed files with 92 additions and 84 deletions

View File

@@ -1,4 +1,4 @@
import winim, os, net, strutils, registry, zippy import winim, os, net, strutils, registry, zippy, strformat
import ../../common/[types, serialize, sequence, crypto, utils] import ../../common/[types, serialize, sequence, crypto, utils]
import ../../modules/manager import ../../modules/manager
@@ -69,16 +69,9 @@ 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 fingerprinting
type
ProductType = enum
UNKNOWN = 0
WORKSTATION = 1
DC = 2
SERVER = 3
# API Structs # API Structs
type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<windows.h>").} = object type
OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<windows.h>").} = object
dwOSVersionInfoSize: ULONG dwOSVersionInfoSize: ULONG
dwMajorVersion: ULONG dwMajorVersion: ULONG
dwMinorVersion: ULONG dwMinorVersion: ULONG
@@ -91,68 +84,79 @@ type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<
wProductType: UCHAR wProductType: UCHAR
wReserved: UCHAR wReserved: UCHAR
# Windows Version fingerprinting
ProductType {.size: sizeof(uint8).} = enum
UNKNOWN = "Unknown"
WORKSTATION = "Workstation"
DC = "Domain Controller"
SERVER = "Server"
WindowsVersion = object
major: DWORD
minor: DWORD
buildMin: DWORD # Minimum build number (0 = any)
buildMax: DWORD # Maximum build number (0 = any)
productType: ProductType
name: string
const VERSIONS = [
# Windows 11 / Server 2022+
# WindowsVersion(major: 10, minor: 0, buildMin: 22631, buildMax: 0, productType: WORKSTATION, name: protect("Windows 11 23H2")),
# WindowsVersion(major: 10, minor: 0, buildMin: 22621, buildMax: 22630, productType: WORKSTATION, name: protect("Windows 11 22H2")),
WindowsVersion(major: 10, minor: 0, buildMin: 22000, buildMax: 0, productType: WORKSTATION, name: protect("Windows 11")),
WindowsVersion(major: 10, minor: 0, buildMin: 26100, buildMax: 0, productType: SERVER, name: protect("Windows Server 2025")),
WindowsVersion(major: 10, minor: 0, buildMin: 20348, buildMax: 26099, productType: SERVER, name: protect("Windows Server 2022")),
# Windows 10 / Server 2016-2019
WindowsVersion(major: 10, minor: 0, buildMin: 19041, buildMax: 19045, productType: WORKSTATION, name: protect("Windows 10 2004/20H2/21H1/21H2/22H2")),
WindowsVersion(major: 10, minor: 0, buildMin: 17763, buildMax: 19040, productType: WORKSTATION, name: protect("Windows 10 1809+")),
WindowsVersion(major: 10, minor: 0, buildMin: 10240, buildMax: 17762, productType: WORKSTATION, name: protect("Windows 10")),
WindowsVersion(major: 10, minor: 0, buildMin: 17763, buildMax: 17763, productType: SERVER, name: protect("Windows Server 2019")),
WindowsVersion(major: 10, minor: 0, buildMin: 14393, buildMax: 14393, productType: SERVER, name: protect("Windows Server 2016")),
WindowsVersion(major: 10, minor: 0, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server (Unknown Build)")),
# Windows 8.x / Server 2012
WindowsVersion(major: 6, minor: 3, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows 8.1")),
WindowsVersion(major: 6, minor: 3, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server 2012 R2")),
WindowsVersion(major: 6, minor: 2, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows 8")),
WindowsVersion(major: 6, minor: 2, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server 2012")),
# Windows 7 / Server 2008 R2
WindowsVersion(major: 6, minor: 1, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows 7")),
WindowsVersion(major: 6, minor: 1, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server 2008 R2")),
# Windows Vista / Server 2008
WindowsVersion(major: 6, minor: 0, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows Vista")),
WindowsVersion(major: 6, minor: 0, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server 2008")),
# Windows XP / Server 2003
WindowsVersion(major: 5, minor: 2, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows XP x64 Edition")),
WindowsVersion(major: 5, minor: 2, buildMin: 0, buildMax: 0, productType: SERVER, name: protect("Windows Server 2003")),
WindowsVersion(major: 5, minor: 1, buildMin: 0, buildMax: 0, productType: WORKSTATION, name: protect("Windows XP")),
]
proc matchesVersion(version: WindowsVersion, info: OSVersionInfoExW, productType: ProductType): bool =
if info.dwMajorVersion != version.major or info.dwMinorVersion != version.minor:
return false
if productType != version.productType:
return false
if version.buildMin > 0 and info.dwBuildNumber < version.buildMin:
return false
if version.buildMax > 0 and info.dwBuildNumber > version.buildMax:
return false
return true
proc getWindowsVersion(info: OSVersionInfoExW, productType: ProductType): string = proc getWindowsVersion(info: OSVersionInfoExW, productType: ProductType): string =
let for version in VERSIONS:
major = info.dwMajorVersion if version.matchesVersion(info, if productType == DC: SERVER else: productType): # Process domain controllers as servers, otherwise they show up as unknown
minor = info.dwMinorVersion if productType == DC:
build = info.dwBuildNumber return version.name & protect(" (Domain Controller)")
spMajor = info.wServicePackMajor else:
return version.name
if major == 10 and minor == 0: # Unknown windows version, return as much information as possible
if productType == WORKSTATION: return fmt"Windows {$int(info.dwMajorVersion)}.{$int(info.dwMinorVersion)} {$productType} (Build: {$int(info.dwBuildNumber)})"
if build >= 22000:
return protect("Windows 11")
else:
return protect("Windows 10")
else:
case build:
of 20348:
return protect("Windows Server 2022")
of 17763:
return protect("Windows Server 2019")
of 14393:
return protect("Windows Server 2016")
else:
return protect("Windows Server 10.x (Build: ") & $build & protect(")")
elif major == 6:
case minor:
of 3:
if productType == WORKSTATION:
return protect("Windows 8.1")
else:
return protect("Windows Server 2012 R2")
of 2:
if productType == WORKSTATION:
return protect("Windows 8")
else:
return protect("Windows Server 2012")
of 1:
if productType == WORKSTATION:
return protect("Windows 7")
else:
return protect("Windows Server 2008 R2")
of 0:
if productType == WORKSTATION:
return protect("Windows Vista")
else:
return protect("Windows Server 2008")
else:
discard
elif major == 5:
if minor == 2:
if productType == WORKSTATION:
return protect("Windows XP x64 Edition")
else:
return protect("Windows Server 2003")
elif minor == 1:
return protect("Windows XP")
else:
discard
return protect("Unknown Windows Version")
proc getProductType(): ProductType = proc getProductType(): ProductType =
# The product key is retrieved from the registry # The product key is retrieved from the registry

View File

@@ -201,6 +201,10 @@ proc main(ip: string = "localhost", port: int = 37573) =
console.draw(connection) console.draw(connection)
newConsoleTable[agentId] = console newConsoleTable[agentId] = console
if sessionsTable.focusedConsole.len() > 0:
igSetWindowFocus_Str(sessionsTable.focusedConsole.cstring)
sessionsTable.focusedConsole = ""
# Update the consoles table with only those sessions that have not been closed yet # Update the consoles table with only those sessions that have not been closed yet
# This is done to ensure that closed console windows can be opened again # This is done to ensure that closed console windows can be opened again
consoles = newConsoleTable consoles = newConsoleTable

View File

@@ -15,6 +15,7 @@ type
agentImpersonation*: Table[string, string] agentImpersonation*: Table[string, string]
selection: ptr ImGuiSelectionBasicStorage selection: ptr ImGuiSelectionBasicStorage
consoles: ptr Table[string, ConsoleComponent] consoles: ptr Table[string, ConsoleComponent]
focusedConsole*: string
proc SessionsTable*(title: string, consoles: ptr Table[string, ConsoleComponent]): SessionsTableComponent = proc SessionsTable*(title: string, consoles: ptr Table[string, ConsoleComponent]): SessionsTableComponent =
result = new SessionsTableComponent result = new SessionsTableComponent
@@ -23,6 +24,7 @@ proc SessionsTable*(title: string, consoles: ptr Table[string, ConsoleComponent]
result.agentActivity = initTable[string, int64]() result.agentActivity = initTable[string, int64]()
result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
result.consoles = consoles result.consoles = consoles
result.focusedConsole = ""
proc cmp(x, y: UIAgent): int = proc cmp(x, y: UIAgent): int =
return cmp(x.firstCheckin, y.firstCheckin) return cmp(x.firstCheckin, y.firstCheckin)
@@ -39,9 +41,7 @@ proc interact(component: SessionsTableComponent) =
if not component.consoles[].hasKey(agent.agentId): if not component.consoles[].hasKey(agent.agentId):
component.consoles[][agent.agentId] = Console(agent) component.consoles[][agent.agentId] = Console(agent)
# Focus the existing console window component.focusedConsole = fmt"[{agent.agentId}] {agent.username}@{agent.hostname}"
else:
igSetWindowFocus_Str(fmt"[{agent.agentId}] {agent.username}@{agent.hostname}".cstring)
component.selection.ImGuiSelectionBasicStorage_Clear() component.selection.ImGuiSelectionBasicStorage_Clear()