multiple binary and code refactor
This commit is contained in:
parent
3214e79d63
commit
dae10a5312
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -337,6 +337,18 @@ dependencies = [
|
|||||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lpus"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pdb 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
@ -482,18 +494,6 @@ dependencies = [
|
|||||||
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse_pdb_for_offsets"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"pdb 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdb"
|
name = "pdb"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "parse_pdb_for_offsets"
|
name = "lpus"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["nganhkhoa <mail.nganhkhoa@gmail.com>"]
|
authors = ["nganhkhoa <mail.nganhkhoa@gmail.com>"]
|
||||||
|
description = "Live pool tag scanning frontend"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[lib]
|
||||||
|
name = "lpus"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
|
73
src/bin/eprocess_scan.rs
Normal file
73
src/bin/eprocess_scan.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::str::{from_utf8};
|
||||||
|
use chrono::Utc;
|
||||||
|
use chrono::{DateTime};
|
||||||
|
use std::time::{UNIX_EPOCH, Duration};
|
||||||
|
|
||||||
|
use lpus::{
|
||||||
|
driver_state::{DriverState /* , EprocessPoolChunk */}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn to_str_time(time_ms: u64) -> String {
|
||||||
|
if time_ms == 0 {
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
let d = UNIX_EPOCH + Duration::from_millis(time_ms);
|
||||||
|
let datetime = DateTime::<Utc>::from(d);
|
||||||
|
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string();
|
||||||
|
timestamp_str
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// for windows admin require
|
||||||
|
// https://github.com/nabijaczleweli/rust-embed-resource
|
||||||
|
|
||||||
|
let mut driver = DriverState::new();
|
||||||
|
println!("NtLoadDriver() -> 0x{:x}", driver.startup());
|
||||||
|
|
||||||
|
// let eprocess_scan_head = driver.scan_active_head(ntosbase)?;
|
||||||
|
// let mut eprocess_list: Vec<EprocessPoolChunk> = Vec::new();
|
||||||
|
driver.scan_pool(b"Proc", |pool_addr, header, data_addr| {
|
||||||
|
let eprocess_name_offset = driver.pdb_store.get_offset_r("_EPROCESS.ImageFileName")?;
|
||||||
|
let eprocess_create_time_offset = driver.pdb_store.get_offset_r("_EPROCESS.CreateTime")?;
|
||||||
|
let eprocess_exit_time_offset = driver.pdb_store.get_offset_r("_EPROCESS.ExitTime")?;
|
||||||
|
let eprocess_size = driver.pdb_store.get_offset_r("_EPROCESS.struct_size")?;
|
||||||
|
|
||||||
|
let chunk_size = (header[2] as u64) * 16u64;
|
||||||
|
let eprocess_valid_start = data_addr;
|
||||||
|
let eprocess_valid_end = pool_addr + chunk_size - eprocess_size;
|
||||||
|
let mut try_eprocess_ptr = eprocess_valid_start;
|
||||||
|
|
||||||
|
let mut create_time = 0u64;
|
||||||
|
let mut exit_time = 0u64;
|
||||||
|
while try_eprocess_ptr <= eprocess_valid_end {
|
||||||
|
driver.deref_addr(try_eprocess_ptr + eprocess_create_time_offset, &mut create_time);
|
||||||
|
driver.deref_addr(try_eprocess_ptr + eprocess_exit_time_offset, &mut exit_time);
|
||||||
|
// using heuristics to eliminate false positive
|
||||||
|
if driver.windows_ffi.valid_process_time(create_time) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try_eprocess_ptr += 0x4; // search exhaustively
|
||||||
|
}
|
||||||
|
let mut image_name = [0u8; 15];
|
||||||
|
driver.deref_addr(try_eprocess_ptr + eprocess_name_offset, &mut image_name);
|
||||||
|
let eprocess_name = from_utf8(&image_name)?
|
||||||
|
.to_string()
|
||||||
|
.trim_end_matches(char::from(0))
|
||||||
|
.to_string();
|
||||||
|
// eprocess_list.push(EprocessPoolChunk {
|
||||||
|
// pool_addr,
|
||||||
|
// eprocess_addr: try_eprocess_ptr,
|
||||||
|
// eprocess_name: eprocess_name,
|
||||||
|
// create_time: to_epoch(create_time),
|
||||||
|
// exit_time: to_epoch(exit_time)
|
||||||
|
// });
|
||||||
|
println!("pool: {} | eprocess: {}: {}", pool_addr, try_eprocess_ptr, eprocess_name);
|
||||||
|
Ok(try_eprocess_ptr <= eprocess_valid_end)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
println!("NtUnloadDriver() -> 0x{:x}", driver.shutdown());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
12
src/bin/print_pdb.rs
Normal file
12
src/bin/print_pdb.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use lpus::{
|
||||||
|
driver_state::{DriverState}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let driver = DriverState::new();
|
||||||
|
driver.windows_ffi.print_version();
|
||||||
|
driver.pdb_store.print_default_information();
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -10,7 +10,7 @@ use winapi::um::winioctl::{
|
|||||||
METHOD_IN_DIRECT, METHOD_OUT_DIRECT, /* METHOD_BUFFERED, */ METHOD_NEITHER
|
METHOD_IN_DIRECT, METHOD_OUT_DIRECT, /* METHOD_BUFFERED, */ METHOD_NEITHER
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::pdb_store::{PdbStore};
|
use crate::pdb_store::{PdbStore, parse_pdb};
|
||||||
use crate::windows::{WindowsFFI, WindowsVersion};
|
use crate::windows::{WindowsFFI, WindowsVersion};
|
||||||
use crate::ioctl_protocol::{
|
use crate::ioctl_protocol::{
|
||||||
InputData, OffsetData, DerefAddr, ScanPoolData, /* HideProcess, */
|
InputData, OffsetData, DerefAddr, ScanPoolData, /* HideProcess, */
|
||||||
@ -79,12 +79,10 @@ pub struct DriverState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DriverState {
|
impl DriverState {
|
||||||
pub fn new(pdb_store: PdbStore, windows_ffi: WindowsFFI) -> Self {
|
pub fn new() -> Self {
|
||||||
pdb_store.print_default_information();
|
|
||||||
windows_ffi.print_version();
|
|
||||||
Self {
|
Self {
|
||||||
pdb_store,
|
pdb_store: parse_pdb(),
|
||||||
windows_ffi
|
windows_ffi: WindowsFFI::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +100,12 @@ impl DriverState {
|
|||||||
self.windows_ffi.unload_driver()
|
self.windows_ffi.unload_driver()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_kernel_base(&self) -> BoxResult<u64> {
|
pub fn get_kernel_base(&self) -> u64 {
|
||||||
let mut ntosbase = 0u64;
|
let mut ntosbase = 0u64;
|
||||||
self.windows_ffi.device_io(DriverAction::GetKernelBase.get_code(),
|
self.windows_ffi.device_io(DriverAction::GetKernelBase.get_code(),
|
||||||
&mut Nothing, &mut ntosbase);
|
&mut Nothing, &mut ntosbase);
|
||||||
// println!("ntosbase: 0x{:x}", self.ntosbase);
|
// println!("ntosbase: 0x{:x}", self.ntosbase);
|
||||||
Ok(ntosbase)
|
ntosbase
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_active_head(&self, ntosbase: u64) -> BoxResult<Vec<EprocessPoolChunk>> {
|
pub fn scan_active_head(&self, ntosbase: u64) -> BoxResult<Vec<EprocessPoolChunk>> {
|
||||||
@ -144,9 +142,14 @@ impl DriverState {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_pool<F>(&self, ntosbase: u64, tag: [u8; 4], mut handler: F) -> BoxResult<bool>
|
pub fn scan_pool<F>(&self, tag: &[u8; 4], mut handler: F) -> BoxResult<bool>
|
||||||
where F: FnMut(&DriverState, u64) -> BoxResult<bool>
|
where F: FnMut(u64, &[u8], u64) -> BoxResult<bool>
|
||||||
|
// F(Pool Address, Pool Header Data, Pool Data Address)
|
||||||
|
// TODO: Pool Header as a real struct
|
||||||
{
|
{
|
||||||
|
let ntosbase = self.get_kernel_base();
|
||||||
|
// TODO: check valid tag
|
||||||
|
let pool_header_size = self.pdb_store.get_offset_r("_POOL_HEADER.struct_size")?;
|
||||||
let code = DriverAction::ScanPoolRemote.get_code();
|
let code = DriverAction::ScanPoolRemote.get_code();
|
||||||
let range = self.get_nonpaged_range(ntosbase)?;
|
let range = self.get_nonpaged_range(ntosbase)?;
|
||||||
let start_address = range[0];
|
let start_address = range[0];
|
||||||
@ -154,22 +157,23 @@ impl DriverState {
|
|||||||
let mut ptr = start_address;
|
let mut ptr = start_address;
|
||||||
while ptr < end_address {
|
while ptr < end_address {
|
||||||
let mut input = InputData {
|
let mut input = InputData {
|
||||||
scan_range: ScanPoolData::new(&[ptr, end_address], &tag)
|
scan_range: ScanPoolData::new(&[ptr, end_address], tag)
|
||||||
};
|
};
|
||||||
self.windows_ffi.device_io(code, &mut input, &mut ptr);
|
self.windows_ffi.device_io(code, &mut input, &mut ptr);
|
||||||
if ptr >= end_address {
|
if ptr >= end_address {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ptr += match handler(&self, ptr) {
|
let pool_addr = ptr;
|
||||||
Ok(success) => {
|
let mut header = vec![0u8; pool_header_size as usize];
|
||||||
if success {
|
self.deref_addr_ptr(pool_addr, header.as_mut_ptr(), pool_header_size);
|
||||||
}
|
|
||||||
else {
|
let success = handler(ptr, &header, pool_addr + pool_header_size)?;
|
||||||
}
|
if success {
|
||||||
},
|
let chunk_size = (header[2] as u64) * 16u64;
|
||||||
Err(e) => println!("Handle error {:?}", e),
|
ptr += chunk_size /* pass this chunk */
|
||||||
// found, ptr += chunk size
|
}
|
||||||
// ptr += pool_header_size;
|
else {
|
||||||
|
ptr += 0x4 /* search next */
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
@ -67,7 +67,7 @@ pub struct DerefAddr {
|
|||||||
pub struct ScanPoolData {
|
pub struct ScanPoolData {
|
||||||
pub start: u64,
|
pub start: u64,
|
||||||
pub end: u64,
|
pub end: u64,
|
||||||
pub tag: [u8; 4]
|
pub tag: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScanPoolData{
|
impl ScanPoolData{
|
||||||
@ -75,7 +75,7 @@ impl ScanPoolData{
|
|||||||
Self {
|
Self {
|
||||||
start: arr[0],
|
start: arr[0],
|
||||||
end: arr[1],
|
end: arr[1],
|
||||||
tag: *tag
|
tag: u32::from_le_bytes(*tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/lib.rs
Normal file
7
src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
extern crate chrono;
|
||||||
|
|
||||||
|
pub mod pdb_store;
|
||||||
|
pub mod windows;
|
||||||
|
pub mod ioctl_protocol;
|
||||||
|
pub mod driver_state;
|
||||||
|
|
124
src/main.rs
124
src/main.rs
@ -1,124 +0,0 @@
|
|||||||
extern crate chrono;
|
|
||||||
|
|
||||||
mod pdb_store;
|
|
||||||
mod windows;
|
|
||||||
mod ioctl_protocol;
|
|
||||||
mod driver_state;
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::str::{from_utf8};
|
|
||||||
// use chrono::prelude::DateTime;
|
|
||||||
// use chrono::Utc;
|
|
||||||
// use chrono::{Local, DateTime};
|
|
||||||
// use std::time::{SystemTime, UNIX_EPOCH, Duration};
|
|
||||||
|
|
||||||
use pdb_store::parse_pdb;
|
|
||||||
use windows::WindowsFFI;
|
|
||||||
use driver_state::{DriverState, EprocessPoolChunk, to_epoch};
|
|
||||||
|
|
||||||
fn to_str_time(_time_ms: u64) -> String {
|
|
||||||
// if time_ms == 0 {
|
|
||||||
// return "".to_string();
|
|
||||||
// }
|
|
||||||
// let d = UNIX_EPOCH + Duration::from_millis(time_ms);
|
|
||||||
// let datetime = DateTime::<Utc>::from(d);
|
|
||||||
// let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string();
|
|
||||||
// timestamp_str
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// for windows admin require
|
|
||||||
// https://github.com/nabijaczleweli/rust-embed-resource
|
|
||||||
|
|
||||||
let mut driver = DriverState::new(parse_pdb(), WindowsFFI::new());
|
|
||||||
println!("NtLoadDriver() -> 0x{:x}", driver.startup());
|
|
||||||
|
|
||||||
let ntosbase = driver.get_kernel_base()?;
|
|
||||||
let pool_header_size = driver.pdb_store.get_offset_r("_POOL_HEADER.struct_size")?;
|
|
||||||
|
|
||||||
let eprocess_tag: [u8; 4] = [80, 114, 111, 99]; // Proc
|
|
||||||
let eprocess_name_offset = driver.pdb_store.get_offset_r("_EPROCESS.ImageFileName")?;
|
|
||||||
let eprocess_create_time_offset = driver.pdb_store.get_offset_r("_EPROCESS.CreateTime")?;
|
|
||||||
let eprocess_exit_time_offset = driver.pdb_store.get_offset_r("_EPROCESS.ExitTime")?;
|
|
||||||
let eprocess_size = driver.pdb_store.get_offset_r("_EPROCESS.struct_size")?;
|
|
||||||
|
|
||||||
let eprocess_scan_head = driver.scan_active_head(ntosbase)?;
|
|
||||||
let mut eprocess_list: Vec<EprocessPoolChunk> = Vec::new();
|
|
||||||
driver.scan_pool(ntosbase, eprocess_tag, |dr, pool_addr| {
|
|
||||||
let mut pool = vec![0u8; pool_header_size as usize];
|
|
||||||
dr.deref_addr_ptr(pool_addr, pool.as_mut_ptr(), pool_header_size);
|
|
||||||
|
|
||||||
let chunk_size = (pool[2] as u64) * 16u64;
|
|
||||||
let eprocess_valid_start = pool_addr + pool_header_size;
|
|
||||||
let eprocess_valid_end = pool_addr + chunk_size - eprocess_size;
|
|
||||||
let mut try_eprocess_ptr = eprocess_valid_start;
|
|
||||||
|
|
||||||
let mut create_time = 0u64;
|
|
||||||
let mut exit_time = 0u64;
|
|
||||||
while try_eprocess_ptr <= eprocess_valid_end {
|
|
||||||
dr.deref_addr(try_eprocess_ptr + eprocess_create_time_offset, &mut create_time);
|
|
||||||
dr.deref_addr(try_eprocess_ptr + eprocess_exit_time_offset, &mut exit_time);
|
|
||||||
if dr.windows_ffi.valid_process_time(create_time) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try_eprocess_ptr += 0x4; // search exhaustively
|
|
||||||
}
|
|
||||||
let mut image_name = [0u8; 15];
|
|
||||||
dr.deref_addr(try_eprocess_ptr + eprocess_name_offset, &mut image_name);
|
|
||||||
let eprocess_name = from_utf8(&image_name)?
|
|
||||||
.to_string()
|
|
||||||
.trim_end_matches(char::from(0))
|
|
||||||
.to_string();
|
|
||||||
eprocess_list.push(EprocessPoolChunk {
|
|
||||||
pool_addr,
|
|
||||||
eprocess_addr: try_eprocess_ptr,
|
|
||||||
eprocess_name: eprocess_name,
|
|
||||||
create_time: to_epoch(create_time),
|
|
||||||
exit_time: to_epoch(exit_time)
|
|
||||||
});
|
|
||||||
Ok(try_eprocess_ptr <= eprocess_valid_end)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let ethread_tag: [u8; 4] = [84, 104, 114, 101]; // Thre
|
|
||||||
let ethread_create_time_offset = driver.pdb_store.get_offset_r("_ETHREAD.CreateTime")?;
|
|
||||||
let ethread_exit_time_offset = driver.pdb_store.get_offset_r("_ETHREAD.ExitTime")?;
|
|
||||||
let ethread_threadname_offset = driver.pdb_store.get_offset_r("_ETHREAD.TheadName")?;
|
|
||||||
let ethread_size = driver.pdb_store.get_offset_r("_ETHREAD.struct_size")?;
|
|
||||||
|
|
||||||
// let mut ethread_list: Vec<EprocessPoolChunk> = Vec::new();
|
|
||||||
driver.scan_pool(ntosbase, ethread_tag, |dr, pool_addr| {
|
|
||||||
let mut pool = vec![0u8; pool_header_size as usize];
|
|
||||||
dr.deref_addr_ptr(pool_addr, pool.as_mut_ptr(), pool_header_size);
|
|
||||||
|
|
||||||
let chunk_size = (pool[2] as u64) * 16u64;
|
|
||||||
let ethread_valid_start = pool_addr + pool_header_size;
|
|
||||||
let ethread_valid_end = pool_addr + chunk_size - ethread_size;
|
|
||||||
let mut try_ethread_ptr = ethread_valid_start;
|
|
||||||
|
|
||||||
let mut create_time = 0u64;
|
|
||||||
let mut exit_time = 0u64;
|
|
||||||
while try_ethread_ptr <= ethread_valid_end {
|
|
||||||
dr.deref_addr(try_ethread_ptr + ethread_create_time_offset, &mut create_time);
|
|
||||||
dr.deref_addr(try_ethread_ptr + ethread_exit_time_offset, &mut exit_time);
|
|
||||||
if dr.windows_ffi.valid_process_time(create_time) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try_ethread_ptr += 0x4; // search exhaustively
|
|
||||||
}
|
|
||||||
let mut threadname_ptr = 0u64;
|
|
||||||
dr.deref_addr(try_ethread_ptr + ethread_threadname_offset, &mut threadname_ptr);
|
|
||||||
let threadname = dr.get_unicode_string(threadname_ptr)?;
|
|
||||||
println!("threadname: {}", threadname);
|
|
||||||
Ok(try_ethread_ptr <= ethread_valid_end)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// for result in &driver.eprocess_traverse_result {
|
|
||||||
// println!("- [{}] 0x{:x} {}",
|
|
||||||
// driver.pool_scan_result.contains(&result),
|
|
||||||
// result.eprocess_addr, result.eprocess_name);
|
|
||||||
// }
|
|
||||||
|
|
||||||
println!("NtUnloadDriver() -> 0x{:x}", driver.shutdown());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -299,7 +299,15 @@ pub fn download_pdb() {
|
|||||||
|
|
||||||
pub fn parse_pdb() -> PdbStore {
|
pub fn parse_pdb() -> PdbStore {
|
||||||
// TODO: Detect pdb file and ntoskrnl file version differs
|
// TODO: Detect pdb file and ntoskrnl file version differs
|
||||||
// The guid of ntoskrnl and pdb file are different
|
// Use a folder at %APPDATA% to save pdb files
|
||||||
|
// %APPDATA%\lpus
|
||||||
|
// |--ntoskrnl
|
||||||
|
// |--|--GUID
|
||||||
|
// |--|--|--ntkrnlmp.pdb
|
||||||
|
// |--file
|
||||||
|
// |--|--GUID
|
||||||
|
// |--|--|--file.pdb
|
||||||
|
// TODO: Turn function to Result to handle error
|
||||||
if !Path::new(PDBNAME).exists() {
|
if !Path::new(PDBNAME).exists() {
|
||||||
download_pdb();
|
download_pdb();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user