Implemented Windows Version fingerprinting

This commit is contained in:
Jakob Friedl
2025-05-21 14:06:04 +02:00
parent c55a9f9443
commit 71336a6fa7
8 changed files with 161 additions and 26 deletions

View File

@@ -1,16 +1,6 @@
import winim, os, net
import winim, os, net, strformat, strutils, registry
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)]
import ./[types, utils]
# Hostname/Computername
proc getHostname*(): string =
@@ -31,6 +21,22 @@ proc getDomain*(): string =
GetComputerNameExW(ComputerNameDnsDomain, &buffer, &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
proc getProcessExe*(): string =
@@ -42,7 +48,8 @@ proc getProcessExe*(): string =
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()
# We replace trailing NULL bytes to prevent them from being sent as JSON data
return string($buffer).extractFilename().replace("\u0000", "")
finally:
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
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 =
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"