wraith/
version.rs

1//! Windows version detection and release mapping
2
3use crate::arch::segment;
4use crate::error::{Result, WraithError};
5use core::cmp::Ordering;
6
7/// represents a specific Windows version
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct WindowsVersion {
10    pub major: u32,
11    pub minor: u32,
12    pub build: u32,
13}
14
15/// named Windows releases with known build numbers
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub enum WindowsRelease {
18    Windows7,       // 7601
19    Windows8,       // 9200
20    Windows81,      // 9600
21    Windows10_1507, // 10240
22    Windows10_1511, // 10586
23    Windows10_1607, // 14393
24    Windows10_1703, // 15063
25    Windows10_1709, // 16299
26    Windows10_1803, // 17134
27    Windows10_1809, // 17763
28    Windows10_1903, // 18362
29    Windows10_1909, // 18363
30    Windows10_2004, // 19041
31    Windows10_20H2, // 19042
32    Windows10_21H1, // 19043
33    Windows10_21H2, // 19044
34    Windows10_22H2, // 19045
35    Windows11_21H2, // 22000
36    Windows11_22H2, // 22621
37    Windows11_23H2, // 22631
38    Windows11_24H2, // 26100
39    Unknown,
40}
41
42impl WindowsVersion {
43    /// minimum supported version (Windows 7 SP1)
44    pub const MIN_SUPPORTED: Self = Self {
45        major: 6,
46        minor: 1,
47        build: 7601,
48    };
49
50    /// get current Windows version from PEB
51    ///
52    /// reads OSMajorVersion, OSMinorVersion, OSBuildNumber from PEB
53    pub fn current() -> Result<Self> {
54        // SAFETY: segment::get_peb returns valid PEB pointer for current process
55        let peb = unsafe { segment::get_peb() };
56        if peb.is_null() {
57            return Err(WraithError::InvalidPebAccess);
58        }
59
60        // offsets are consistent across all Windows versions for these fields
61        // x64: OSMajorVersion @ 0x118, OSMinorVersion @ 0x11C, OSBuildNumber @ 0x120
62        // x86: OSMajorVersion @ 0xA4, OSMinorVersion @ 0xA8, OSBuildNumber @ 0xAC
63
64        #[cfg(target_arch = "x86_64")]
65        let (major, minor, build) = unsafe {
66            let major = (peb.add(0x118) as *const u32).read_unaligned();
67            let minor = (peb.add(0x11C) as *const u32).read_unaligned();
68            let build = (peb.add(0x120) as *const u16).read_unaligned() as u32;
69            (major, minor, build)
70        };
71
72        #[cfg(target_arch = "x86")]
73        let (major, minor, build) = unsafe {
74            let major = (peb.add(0xA4) as *const u32).read_unaligned();
75            let minor = (peb.add(0xA8) as *const u32).read_unaligned();
76            let build = (peb.add(0xAC) as *const u16).read_unaligned() as u32;
77            (major, minor, build)
78        };
79
80        let version = Self { major, minor, build };
81
82        if version < Self::MIN_SUPPORTED {
83            return Err(WraithError::UnsupportedWindowsVersion {
84                major,
85                minor,
86                build,
87            });
88        }
89
90        Ok(version)
91    }
92
93    /// map version to named release
94    pub fn release(&self) -> WindowsRelease {
95        match (self.major, self.minor, self.build) {
96            (6, 1, _) => WindowsRelease::Windows7,
97            (6, 2, _) => WindowsRelease::Windows8,
98            (6, 3, _) => WindowsRelease::Windows81,
99            (10, 0, b) if b >= 26100 => WindowsRelease::Windows11_24H2,
100            (10, 0, b) if b >= 22631 => WindowsRelease::Windows11_23H2,
101            (10, 0, b) if b >= 22621 => WindowsRelease::Windows11_22H2,
102            (10, 0, b) if b >= 22000 => WindowsRelease::Windows11_21H2,
103            (10, 0, b) if b >= 19045 => WindowsRelease::Windows10_22H2,
104            (10, 0, b) if b >= 19044 => WindowsRelease::Windows10_21H2,
105            (10, 0, b) if b >= 19043 => WindowsRelease::Windows10_21H1,
106            (10, 0, b) if b >= 19042 => WindowsRelease::Windows10_20H2,
107            (10, 0, b) if b >= 19041 => WindowsRelease::Windows10_2004,
108            (10, 0, b) if b >= 18363 => WindowsRelease::Windows10_1909,
109            (10, 0, b) if b >= 18362 => WindowsRelease::Windows10_1903,
110            (10, 0, b) if b >= 17763 => WindowsRelease::Windows10_1809,
111            (10, 0, b) if b >= 17134 => WindowsRelease::Windows10_1803,
112            (10, 0, b) if b >= 16299 => WindowsRelease::Windows10_1709,
113            (10, 0, b) if b >= 15063 => WindowsRelease::Windows10_1703,
114            (10, 0, b) if b >= 14393 => WindowsRelease::Windows10_1607,
115            (10, 0, b) if b >= 10586 => WindowsRelease::Windows10_1511,
116            (10, 0, b) if b >= 10240 => WindowsRelease::Windows10_1507,
117            _ => WindowsRelease::Unknown,
118        }
119    }
120
121    /// check if current version is at least the given release
122    pub fn is_at_least(&self, release: WindowsRelease) -> bool {
123        self.release() >= release
124    }
125
126    /// check if this version has the LdrpHashTable (Windows 8+)
127    pub fn supports_hash_table(&self) -> bool {
128        self.is_at_least(WindowsRelease::Windows8)
129    }
130
131    /// check if this version has the LdrpModuleBaseAddressIndex (Win8+)
132    pub fn supports_base_address_index(&self) -> bool {
133        self.is_at_least(WindowsRelease::Windows8)
134    }
135
136    /// check if Windows 11 (different PEB layout in some areas)
137    pub fn is_windows_11(&self) -> bool {
138        self.build >= 22000
139    }
140}
141
142impl PartialOrd for WindowsVersion {
143    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
144        Some(self.cmp(other))
145    }
146}
147
148impl Ord for WindowsVersion {
149    fn cmp(&self, other: &Self) -> Ordering {
150        match self.major.cmp(&other.major) {
151            Ordering::Equal => match self.minor.cmp(&other.minor) {
152                Ordering::Equal => self.build.cmp(&other.build),
153                ord => ord,
154            },
155            ord => ord,
156        }
157    }
158}
159
160impl core::fmt::Display for WindowsVersion {
161    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162        write!(f, "{}.{}.{}", self.major, self.minor, self.build)
163    }
164}
165
166impl core::fmt::Display for WindowsRelease {
167    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
168        match self {
169            Self::Windows7 => write!(f, "Windows 7"),
170            Self::Windows8 => write!(f, "Windows 8"),
171            Self::Windows81 => write!(f, "Windows 8.1"),
172            Self::Windows10_1507 => write!(f, "Windows 10 1507"),
173            Self::Windows10_1511 => write!(f, "Windows 10 1511"),
174            Self::Windows10_1607 => write!(f, "Windows 10 1607"),
175            Self::Windows10_1703 => write!(f, "Windows 10 1703"),
176            Self::Windows10_1709 => write!(f, "Windows 10 1709"),
177            Self::Windows10_1803 => write!(f, "Windows 10 1803"),
178            Self::Windows10_1809 => write!(f, "Windows 10 1809"),
179            Self::Windows10_1903 => write!(f, "Windows 10 1903"),
180            Self::Windows10_1909 => write!(f, "Windows 10 1909"),
181            Self::Windows10_2004 => write!(f, "Windows 10 2004"),
182            Self::Windows10_20H2 => write!(f, "Windows 10 20H2"),
183            Self::Windows10_21H1 => write!(f, "Windows 10 21H1"),
184            Self::Windows10_21H2 => write!(f, "Windows 10 21H2"),
185            Self::Windows10_22H2 => write!(f, "Windows 10 22H2"),
186            Self::Windows11_21H2 => write!(f, "Windows 11 21H2"),
187            Self::Windows11_22H2 => write!(f, "Windows 11 22H2"),
188            Self::Windows11_23H2 => write!(f, "Windows 11 23H2"),
189            Self::Windows11_24H2 => write!(f, "Windows 11 24H2"),
190            Self::Unknown => write!(f, "Unknown"),
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_version_comparison() {
201        let win10 = WindowsVersion {
202            major: 10,
203            minor: 0,
204            build: 19041,
205        };
206        let win11 = WindowsVersion {
207            major: 10,
208            minor: 0,
209            build: 22000,
210        };
211        assert!(win10 < win11);
212    }
213
214    #[test]
215    fn test_release_mapping() {
216        let win11 = WindowsVersion {
217            major: 10,
218            minor: 0,
219            build: 22621,
220        };
221        assert_eq!(win11.release(), WindowsRelease::Windows11_22H2);
222    }
223}