mirror of
https://github.com/nikaiw/VMkatz
synced 2026-09-25 07:51:06 +00:00
Add pagefile.sys resolution for paged-out LSASS memory
When a VM directory contains both memory snapshots and disk images, open pagefile.sys from the VMDK/VDI/QCOW2 disk to resolve pages that Windows swapped out. Uses pre-built NTFS data run map with RefCell<Box<dyn DiskImage>> for interior mutability. - PTE pagefile detection (bits 0/10/11 + pagefile number/offset) - PageFileFault error variant in page table walker - PagefileReader: opens disk, extracts pagefile.sys data runs, binary-search page resolution - --disk CLI option for explicit disk image in single-file mode - Folder mode auto-discovers disk and opens pagefile automatically - Feature-gated behind "sam" (requires NTFS + disk image support)
This commit is contained in:
@@ -20,6 +20,9 @@ pub enum GovmemError {
|
||||
#[error("Page fault at 0x{0:x} (level: {1})")]
|
||||
PageFault(u64, &'static str),
|
||||
|
||||
#[error("Pagefile fault at 0x{0:x} (PTE: 0x{1:x})")]
|
||||
PageFileFault(u64, u64),
|
||||
|
||||
#[error("System process not found")]
|
||||
SystemProcessNotFound,
|
||||
|
||||
|
||||
+17
-2
@@ -18,14 +18,29 @@ pub struct LsassDlls {
|
||||
pub cloudap: Option<LoadedModule>,
|
||||
}
|
||||
|
||||
/// Pagefile reference type: wraps Option<&PagefileReader> when sam feature is enabled,
|
||||
/// or () when not. Allows a unified function signature across feature configurations.
|
||||
#[cfg(feature = "sam")]
|
||||
pub type PagefileRef<'a> = Option<&'a crate::paging::pagefile::PagefileReader>;
|
||||
#[cfg(not(feature = "sam"))]
|
||||
pub type PagefileRef<'a> = ();
|
||||
|
||||
/// Find LSASS and extract all credentials.
|
||||
/// When a pagefile reader is provided, paged-out memory is resolved from disk.
|
||||
pub fn extract_all_credentials<P: PhysicalMemory>(
|
||||
phys: &P,
|
||||
lsass: &Process,
|
||||
_kernel_dtb: u64,
|
||||
pagefile: PagefileRef<'_>,
|
||||
) -> Result<Vec<Credential>> {
|
||||
// Create virtual memory reader for LSASS
|
||||
let lsass_vmem = ProcessMemory::new(phys, lsass.dtb);
|
||||
// Create virtual memory reader for LSASS (with optional pagefile resolution)
|
||||
#[cfg(feature = "sam")]
|
||||
let lsass_vmem = ProcessMemory::with_pagefile(phys, lsass.dtb, pagefile);
|
||||
#[cfg(not(feature = "sam"))]
|
||||
let lsass_vmem = {
|
||||
let _ = pagefile;
|
||||
ProcessMemory::new(phys, lsass.dtb)
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"LSASS: PID={}, DTB=0x{:x}, PEB=0x{:x}",
|
||||
|
||||
+75
-5
@@ -8,6 +8,7 @@ use clap::Parser;
|
||||
|
||||
#[cfg(any(feature = "vmware", feature = "vbox"))]
|
||||
use vmkatz::lsass;
|
||||
use vmkatz::lsass::finder::PagefileRef;
|
||||
#[cfg(any(feature = "vmware", feature = "vbox"))]
|
||||
use vmkatz::lsass::types::Credential;
|
||||
#[cfg(any(feature = "vmware", feature = "vbox"))]
|
||||
@@ -36,6 +37,7 @@ use vmkatz::windows::process;
|
||||
after_help = "EXAMPLES:\n \
|
||||
vmkatz snapshot.vmsn Extract LSASS credentials\n \
|
||||
vmkatz --format ntlm snapshot.vmsn Output as NTLM hashes\n \
|
||||
vmkatz --disk disk.vmdk snapshot.vmsn Resolve paged-out creds from disk\n \
|
||||
vmkatz disk.vdi Extract SAM hashes + LSA secrets\n \
|
||||
vmkatz /path/to/vm/directory/ Auto-discover and process all files\n \
|
||||
vmkatz --list-processes snapshot.vmsn List running processes only\n \
|
||||
@@ -55,6 +57,11 @@ struct Args {
|
||||
#[arg(long, default_value_t = false)]
|
||||
sam: bool,
|
||||
|
||||
/// Disk image for pagefile.sys resolution (resolves paged-out memory from disk)
|
||||
#[cfg(feature = "sam")]
|
||||
#[arg(long, value_name = "DISK_IMAGE")]
|
||||
disk: Option<String>,
|
||||
|
||||
/// Output format
|
||||
#[arg(long, default_value = "text", value_name = "FORMAT", value_parser = ["text", "csv", "ntlm"])]
|
||||
format: String,
|
||||
@@ -101,7 +108,27 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
// LSASS credential extraction mode
|
||||
run_lsass(input_path, &args)
|
||||
#[cfg(feature = "sam")]
|
||||
{
|
||||
let pagefile_reader = args.disk.as_ref().and_then(|d| {
|
||||
match vmkatz::paging::pagefile::PagefileReader::open(Path::new(d)) {
|
||||
Ok(pf) => {
|
||||
println!(
|
||||
"[+] Pagefile: {:.1} MB",
|
||||
pf.pagefile_size() as f64 / (1024.0 * 1024.0),
|
||||
);
|
||||
Some(pf)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[!] Failed to open pagefile from {}: {}", d, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
return run_lsass(input_path, &args, pagefile_reader.as_ref());
|
||||
}
|
||||
#[cfg(not(feature = "sam"))]
|
||||
run_lsass(input_path, &args, Default::default())
|
||||
}
|
||||
|
||||
#[cfg(feature = "sam")]
|
||||
@@ -191,11 +218,39 @@ fn run_directory(dir: &Path, args: &Args) -> anyhow::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Try to open pagefile.sys from the first available disk image
|
||||
#[cfg(feature = "sam")]
|
||||
let pagefile_reader = if !discovery.lsass_files.is_empty() {
|
||||
discovery.disk_files.first().and_then(|d| {
|
||||
match vmkatz::paging::pagefile::PagefileReader::open(d) {
|
||||
Ok(pf) => {
|
||||
println!(
|
||||
"[+] Pagefile: {:.1} MB from {}",
|
||||
pf.pagefile_size() as f64 / (1024.0 * 1024.0),
|
||||
d.file_name().unwrap_or_default().to_string_lossy()
|
||||
);
|
||||
Some(pf)
|
||||
}
|
||||
Err(e) => {
|
||||
log::info!("No pagefile from disk: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "sam")]
|
||||
let pagefile: PagefileRef<'_> = pagefile_reader.as_ref();
|
||||
#[cfg(not(feature = "sam"))]
|
||||
let pagefile: PagefileRef<'_> = Default::default();
|
||||
|
||||
#[cfg(any(feature = "vmware", feature = "vbox"))]
|
||||
for file in &discovery.lsass_files {
|
||||
let name = file.file_name().unwrap_or_default().to_string_lossy();
|
||||
println!("\n[*] LSASS: {}", name);
|
||||
if let Err(e) = run_lsass(file, args) {
|
||||
if let Err(e) = run_lsass(file, args, pagefile) {
|
||||
eprintln!("[!] {}: {}", name, e);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +272,7 @@ fn run_directory(dir: &Path, args: &Args) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_lsass(input_path: &Path, args: &Args) -> anyhow::Result<()> {
|
||||
fn run_lsass(input_path: &Path, args: &Args, pagefile: PagefileRef<'_>) -> anyhow::Result<()> {
|
||||
let verbose = args.verbose || args.list_processes;
|
||||
let ext = input_path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
|
||||
@@ -238,10 +293,12 @@ fn run_lsass(input_path: &Path, args: &Args) -> anyhow::Result<()> {
|
||||
},
|
||||
args,
|
||||
verbose,
|
||||
pagefile,
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "vbox"))]
|
||||
{
|
||||
let _ = pagefile;
|
||||
anyhow::bail!("VirtualBox .sav support not enabled (compile with --features vbox)")
|
||||
}
|
||||
} else {
|
||||
@@ -272,10 +329,12 @@ fn run_lsass(input_path: &Path, args: &Args) -> anyhow::Result<()> {
|
||||
},
|
||||
args,
|
||||
verbose,
|
||||
pagefile,
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "vmware"))]
|
||||
{
|
||||
let _ = pagefile;
|
||||
anyhow::bail!("VMware .vmem/.vmsn support not enabled (compile with --features vmware)")
|
||||
}
|
||||
}
|
||||
@@ -286,6 +345,7 @@ fn run_with_layer<L: PhysicalMemory, F: FnOnce() -> anyhow::Result<L>>(
|
||||
make_layer: F,
|
||||
args: &Args,
|
||||
verbose: bool,
|
||||
pagefile: PagefileRef<'_>,
|
||||
) -> anyhow::Result<()> {
|
||||
let layer = make_layer()?;
|
||||
|
||||
@@ -325,8 +385,18 @@ fn run_with_layer<L: PhysicalMemory, F: FnOnce() -> anyhow::Result<L>>(
|
||||
}
|
||||
|
||||
// Extract credentials
|
||||
let credentials = lsass::finder::extract_all_credentials(&layer, lsass_proc, system.dtb)
|
||||
.context("Credential extraction failed")?;
|
||||
let credentials =
|
||||
lsass::finder::extract_all_credentials(&layer, lsass_proc, system.dtb, pagefile)
|
||||
.context("Credential extraction failed")?;
|
||||
|
||||
// Report pagefile resolution stats
|
||||
#[cfg(feature = "sam")]
|
||||
if let Some(pf) = pagefile {
|
||||
let resolved = pf.pages_resolved();
|
||||
if resolved > 0 {
|
||||
println!("[+] Pagefile: {} pages resolved from disk", resolved);
|
||||
}
|
||||
}
|
||||
|
||||
match args.format.as_str() {
|
||||
"csv" => print_csv(&credentials),
|
||||
|
||||
@@ -27,4 +27,24 @@ impl PageTableEntry {
|
||||
pub fn raw(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Windows pagefile PTE: not present (bit 0=0), not transition (bit 10=0),
|
||||
/// not prototype (bit 11=0), and non-zero (has pagefile info).
|
||||
/// Bits 1-4 = pagefile number, bits 32-63 = page offset in pagefile.
|
||||
pub fn is_pagefile(&self) -> bool {
|
||||
self.0 != 0
|
||||
&& (self.0 & 1) == 0
|
||||
&& (self.0 & (1 << 10)) == 0
|
||||
&& (self.0 & (1 << 11)) == 0
|
||||
}
|
||||
|
||||
/// Pagefile number from bits 1-4 (usually 0 for primary pagefile.sys).
|
||||
pub fn pagefile_number(&self) -> u8 {
|
||||
((self.0 >> 1) & 0xF) as u8
|
||||
}
|
||||
|
||||
/// Byte offset into pagefile from bits 32-63 (page index * 4096).
|
||||
pub fn pagefile_offset(&self) -> u64 {
|
||||
((self.0 >> 32) & 0xFFFF_FFFF) * 4096
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod entry;
|
||||
#[cfg(feature = "sam")]
|
||||
pub mod pagefile;
|
||||
pub mod translate;
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
use std::cell::RefCell;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
use ntfs::attribute_value::NtfsAttributeValue;
|
||||
|
||||
use crate::disk::{self, DiskImage};
|
||||
use crate::error::{GovmemError, Result};
|
||||
use crate::paging::entry::PageTableEntry;
|
||||
|
||||
/// Pre-built data run map entry mapping pagefile byte ranges to absolute disk positions.
|
||||
struct PagefileDataRun {
|
||||
file_offset: u64,
|
||||
disk_offset: u64,
|
||||
length: u64,
|
||||
}
|
||||
|
||||
/// Reads pages from pagefile.sys on a virtual disk image.
|
||||
///
|
||||
/// Pre-extracts NTFS data runs at construction time to avoid keeping ntfs crate
|
||||
/// types alive (which would create self-referential struct issues). Uses RefCell
|
||||
/// for interior mutability since read_virt(&self) is immutable but disk seeks need &mut.
|
||||
pub struct PagefileReader {
|
||||
disk: RefCell<Box<dyn DiskImage>>,
|
||||
data_runs: Vec<PagefileDataRun>,
|
||||
pagefile_size: u64,
|
||||
pages_resolved: std::cell::Cell<u64>,
|
||||
}
|
||||
|
||||
impl PagefileReader {
|
||||
/// Open pagefile.sys from a disk image, extracting its NTFS data runs.
|
||||
pub fn open(disk_path: &Path) -> Result<Self> {
|
||||
let mut disk = disk::open_disk(disk_path)?;
|
||||
let (data_runs, pagefile_size) = extract_pagefile_data_runs(&mut disk)?;
|
||||
|
||||
log::info!(
|
||||
"Pagefile: {:.1} MB, {} data runs",
|
||||
pagefile_size as f64 / (1024.0 * 1024.0),
|
||||
data_runs.len()
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
disk: RefCell::new(disk),
|
||||
data_runs,
|
||||
pagefile_size,
|
||||
pages_resolved: std::cell::Cell::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pagefile_size(&self) -> u64 {
|
||||
self.pagefile_size
|
||||
}
|
||||
|
||||
pub fn pages_resolved(&self) -> u64 {
|
||||
self.pages_resolved.get()
|
||||
}
|
||||
|
||||
/// Read a 4KB page from the pagefile at the given byte offset.
|
||||
pub fn read_page(&self, byte_offset: u64) -> Result<[u8; 4096]> {
|
||||
if byte_offset + 4096 > self.pagefile_size {
|
||||
return Err(GovmemError::DecryptionError(format!(
|
||||
"Pagefile offset 0x{:x} + 4096 exceeds size 0x{:x}",
|
||||
byte_offset, self.pagefile_size
|
||||
)));
|
||||
}
|
||||
|
||||
// Binary search for the data run containing this offset
|
||||
let idx = match self.data_runs.binary_search_by(|run| {
|
||||
if byte_offset < run.file_offset {
|
||||
std::cmp::Ordering::Greater
|
||||
} else if byte_offset >= run.file_offset + run.length {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}) {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
// Sparse region: return zeros
|
||||
return Ok([0u8; 4096]);
|
||||
}
|
||||
};
|
||||
|
||||
let run = &self.data_runs[idx];
|
||||
let run_offset = byte_offset - run.file_offset;
|
||||
let disk_pos = run.disk_offset + run_offset;
|
||||
|
||||
let mut disk = self.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(disk_pos))?;
|
||||
let mut buf = [0u8; 4096];
|
||||
disk.read_exact(&mut buf)?;
|
||||
|
||||
self.pages_resolved.set(self.pages_resolved.get() + 1);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Resolve a pagefile PTE: check if it points to pagefile #0 and read the page.
|
||||
pub fn resolve_pte(&self, raw_pte: u64) -> Option<[u8; 4096]> {
|
||||
let pte = PageTableEntry(raw_pte);
|
||||
if !pte.is_pagefile() || pte.pagefile_number() != 0 {
|
||||
return None;
|
||||
}
|
||||
self.read_page(pte.pagefile_offset()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract pagefile.sys data runs from the disk image.
|
||||
fn extract_pagefile_data_runs(
|
||||
disk: &mut Box<dyn DiskImage>,
|
||||
) -> Result<(Vec<PagefileDataRun>, u64)> {
|
||||
let partitions = crate::sam::find_ntfs_partitions(disk)?;
|
||||
|
||||
for &partition_offset in &partitions {
|
||||
match try_extract_from_partition(disk, partition_offset) {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(e) => {
|
||||
log::debug!("No pagefile at partition 0x{:x}: {}", partition_offset, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GovmemError::DecryptionError(
|
||||
"pagefile.sys not found on any NTFS partition".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Try to extract pagefile.sys data runs from a specific NTFS partition.
|
||||
fn try_extract_from_partition(
|
||||
disk: &mut Box<dyn DiskImage>,
|
||||
partition_offset: u64,
|
||||
) -> Result<(Vec<PagefileDataRun>, u64)> {
|
||||
let mut part_reader = crate::sam::PartitionReader::new(disk, partition_offset);
|
||||
|
||||
let ntfs = ntfs::Ntfs::new(&mut part_reader).map_err(|e| {
|
||||
GovmemError::DecryptionError(format!("NTFS parse error: {}", e))
|
||||
})?;
|
||||
|
||||
let root = ntfs.root_directory(&mut part_reader).map_err(|e| {
|
||||
GovmemError::DecryptionError(format!("NTFS root dir error: {}", e))
|
||||
})?;
|
||||
|
||||
let pagefile = crate::sam::find_entry(&ntfs, &root, &mut part_reader, "pagefile.sys")?;
|
||||
|
||||
let data_item = pagefile
|
||||
.data(&mut part_reader, "")
|
||||
.ok_or_else(|| {
|
||||
GovmemError::DecryptionError("pagefile.sys: no $DATA attribute".to_string())
|
||||
})?
|
||||
.map_err(|e| {
|
||||
GovmemError::DecryptionError(format!("pagefile.sys $DATA error: {}", e))
|
||||
})?;
|
||||
|
||||
let data_attr = data_item.to_attribute().map_err(|e| {
|
||||
GovmemError::DecryptionError(format!("pagefile.sys to_attribute error: {}", e))
|
||||
})?;
|
||||
|
||||
let data_value = data_attr.value(&mut part_reader).map_err(|e| {
|
||||
GovmemError::DecryptionError(format!("pagefile.sys value error: {}", e))
|
||||
})?;
|
||||
|
||||
let pagefile_size = data_value.len();
|
||||
|
||||
// Extract data runs from non-resident attribute
|
||||
match data_value {
|
||||
NtfsAttributeValue::NonResident(nr) => {
|
||||
let mut runs = Vec::new();
|
||||
let mut cumulative_offset = 0u64;
|
||||
|
||||
for run_result in nr.data_runs() {
|
||||
let run = run_result.map_err(|e| {
|
||||
GovmemError::DecryptionError(format!(
|
||||
"pagefile.sys data run error: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let allocated = run.allocated_size();
|
||||
|
||||
if let Some(pos) = run.data_position().value() {
|
||||
runs.push(PagefileDataRun {
|
||||
file_offset: cumulative_offset,
|
||||
disk_offset: partition_offset + pos.get(),
|
||||
length: allocated,
|
||||
});
|
||||
}
|
||||
|
||||
cumulative_offset += allocated;
|
||||
}
|
||||
|
||||
Ok((runs, pagefile_size))
|
||||
}
|
||||
_ => Err(GovmemError::DecryptionError(
|
||||
"pagefile.sys: $DATA is not non-resident (unexpected for a pagefile)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
+42
-2
@@ -58,6 +58,14 @@ impl<'a, P: PhysicalMemory> PageTableWalker<'a, P> {
|
||||
if pte.is_transition() {
|
||||
return Ok(pte.frame_addr() | (vaddr & 0xFFF));
|
||||
}
|
||||
// Check for pagefile PTE (non-zero, not transition, not prototype)
|
||||
if pte.is_pagefile() {
|
||||
log::trace!(
|
||||
"PageFileFault: VA=0x{:x} PTE=0x{:016x} pfn={} offset=0x{:x}",
|
||||
vaddr, pte.raw(), pte.pagefile_number(), pte.pagefile_offset()
|
||||
);
|
||||
return Err(GovmemError::PageFileFault(vaddr, pte.raw()));
|
||||
}
|
||||
return Err(GovmemError::PageFault(vaddr, "PT"));
|
||||
}
|
||||
|
||||
@@ -143,10 +151,13 @@ impl<'a, P: PhysicalMemory> PageTableWalker<'a, P> {
|
||||
}
|
||||
|
||||
/// Process virtual memory: combines a DTB (CR3) with physical memory for address translation.
|
||||
/// Optional pagefile reader resolves pages swapped to pagefile.sys on disk.
|
||||
pub struct ProcessMemory<'a, P: PhysicalMemory> {
|
||||
phys: &'a P,
|
||||
walker: PageTableWalker<'a, P>,
|
||||
dtb: u64,
|
||||
#[cfg(feature = "sam")]
|
||||
pagefile: Option<&'a crate::paging::pagefile::PagefileReader>,
|
||||
}
|
||||
|
||||
impl<'a, P: PhysicalMemory> ProcessMemory<'a, P> {
|
||||
@@ -155,6 +166,22 @@ impl<'a, P: PhysicalMemory> ProcessMemory<'a, P> {
|
||||
phys,
|
||||
walker: PageTableWalker::new(phys),
|
||||
dtb,
|
||||
#[cfg(feature = "sam")]
|
||||
pagefile: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sam")]
|
||||
pub fn with_pagefile(
|
||||
phys: &'a P,
|
||||
dtb: u64,
|
||||
pagefile: Option<&'a crate::paging::pagefile::PagefileReader>,
|
||||
) -> Self {
|
||||
Self {
|
||||
phys,
|
||||
walker: PageTableWalker::new(phys),
|
||||
dtb,
|
||||
pagefile,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +209,25 @@ impl<'a, P: PhysicalMemory> VirtualMemory for ProcessMemory<'a, P> {
|
||||
match self.walker.translate(self.dtb, current_vaddr) {
|
||||
Ok(phys_addr) => {
|
||||
if self.phys.read_phys(phys_addr, &mut buf[offset..offset + chunk]).is_err() {
|
||||
// Physical read failed, zero-fill
|
||||
buf[offset..offset + chunk].fill(0);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "sam")]
|
||||
Err(GovmemError::PageFileFault(_vaddr, raw_pte)) => {
|
||||
// Try to resolve from pagefile.sys on disk
|
||||
if let Some(pf) = self.pagefile {
|
||||
if let Some(page_data) = pf.resolve_pte(raw_pte) {
|
||||
let page_off = (current_vaddr & 0xFFF) as usize;
|
||||
buf[offset..offset + chunk]
|
||||
.copy_from_slice(&page_data[page_off..page_off + chunk]);
|
||||
} else {
|
||||
buf[offset..offset + chunk].fill(0);
|
||||
}
|
||||
} else {
|
||||
buf[offset..offset + chunk].fill(0);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Page not present (paged out / demand paging), zero-fill
|
||||
buf[offset..offset + chunk].fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -128,7 +128,7 @@ fn process_hive_data(
|
||||
}
|
||||
|
||||
/// Parse MBR/GPT and find all NTFS partitions, returning their byte offsets.
|
||||
fn find_ntfs_partitions<R: Read + Seek>(reader: &mut R) -> Result<Vec<u64>> {
|
||||
pub(crate) fn find_ntfs_partitions<R: Read + Seek>(reader: &mut R) -> Result<Vec<u64>> {
|
||||
use std::io::SeekFrom;
|
||||
reader.seek(SeekFrom::Start(0))?;
|
||||
let mut mbr = [0u8; 512];
|
||||
@@ -281,7 +281,7 @@ fn read_hive_files<R: Read + Seek>(
|
||||
}
|
||||
|
||||
/// Find a directory entry by name (case-insensitive).
|
||||
fn find_entry<'n, R: Read + Seek>(
|
||||
pub(crate) fn find_entry<'n, R: Read + Seek>(
|
||||
ntfs: &'n ntfs::Ntfs,
|
||||
dir: &ntfs::NtfsFile<'n>,
|
||||
reader: &mut R,
|
||||
@@ -351,13 +351,13 @@ fn read_file_data<R: Read + Seek>(
|
||||
}
|
||||
|
||||
/// Wraps a Read+Seek with a partition offset.
|
||||
struct PartitionReader<'a, R: Read + Seek> {
|
||||
pub(crate) struct PartitionReader<'a, R: Read + Seek> {
|
||||
inner: &'a mut R,
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
impl<'a, R: Read + Seek> PartitionReader<'a, R> {
|
||||
fn new(inner: &'a mut R, offset: u64) -> Self {
|
||||
pub(crate) fn new(inner: &'a mut R, offset: u64) -> Self {
|
||||
Self { inner, offset }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user