diff --git a/.cargo/config b/.cargo/config index 1180d59..5a0b9a5 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ [target.x86_64-pc-windows-msvc] +# CRT static to make run on machine without VC++ +# https://github.com/rust-lang/rust/pull/66801#issuecomment-558947376 +# >> "-Clink-args=/subsystem:console,5.02" rustflags = ["-Ctarget-feature=+crt-static"] diff --git a/README.md b/README.md index f4512e6..ebcbe82 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ # LPUS (A live pool-tag scanning solution) -This is the frontend to the live pool tag scanning solution, the backend is a driver (which is now closed source). +This is the frontend to the live pool tag scanning solution, the backend is a +driver (which is now closed source). + +Works on Windows 7 and above (Vista not tested, but 7 ok and 10 ok), and on x64 +systems only. (I hardcoded the address as u64 so only 64 systems should run this). + +> The binary is runable, without crashing. But I still need to add some +manual instructions on referencing the structs and offset on some places. +> Windows 10, versions 2018, 2019 and 2020 is tested and works. + +Windows XP is not supported: Windows XP Win32Api is missing here and there. ## How this works @@ -21,7 +31,7 @@ use lpus::{ fn main() -> Result<(), Box> { let mut driver = DriverState::new(); println!("NtLoadDriver() -> 0x{:x}", driver.startup()); - driver.scan_pool(b"Tag ", |pool_addr, header, data_addr| { + driver.scan_pool(b"Tag ", "_STRUCT_NAME", |pool_addr, header, data_addr| { })?; println!("NtUnloadDriver() -> 0x{:x}", driver.shutdown()); } @@ -33,3 +43,67 @@ Parsing the struct data is up to you. You can use `driver.deref_addr(addr, &value)` to dereference an address in kernel space and `driver.pdb_store.get_offset_r("offset")?` to get an offset from PDB file. +We also have a set of functions for scanning a specific tag/object. + +- `pub fn scan_eprocess(driver: &DriverState) -> BoxResult>` +- `pub fn scan_file(driver: &DriverState) -> BoxResult>` +- `pub fn scan_ethread(driver: &DriverState) -> BoxResult>` +- `pub fn scan_mutant(driver: &DriverState) -> BoxResult>` +- `pub fn scan_driver(driver: &DriverState) -> BoxResult>` +- `pub fn scan_kernel_module(driver: &DriverState) -> BoxResult>` + +And a list traversing the kernel object: + +- `pub fn traverse_loadedmodulelist(driver: &DriverState) -> BoxResult>` +- `pub fn traverse_activehead(driver: &DriverState) -> BoxResult>` +- missing symbols `pub fn traverse_afdendpoint(driver: &DriverState) -> BoxResult>` +- `pub fn traverse_kiprocesslist(driver: &DriverState) -> BoxResult>` +- `pub fn traverse_handletable(driver: &DriverState) -> BoxResult>` +- `pub fn traverse_unloadeddrivers(driver: &DriverState) -> BoxResult>` + +## Things to note + +Right now, we only have one symbol file of ntoskrnl.exe. While we may need more +symbols, kernel32.sys, win32k.sys, tcpis.sys... This will be a future update +where symbols are combined into one big `HashMap` but still retain the module. I +haven't tested the debug symbols of others binary, I wonder if the PDB file even +exists. + +The pdb file is not restricted in ntoskrnl.exe, I might need to split to a +smaller module or such. + +Also the symbols list is parsed directly from the PDB file, but some structs +(like the callback routine members or network structs) are missing. Right now a +simple hardcoded to add in a struct member is used, but it would break if the +OS running have a different layout. + +The HashMap of symbols/struct is now using string and u32 to store member +offset and types, this should be changed into something that would be type-safe +and more functional. + +I also follow a few Volatility implementation on Rootkit, The art of Memory +forensics Chapter 13. Scanning in Windows 10 yields promising result, though I +haven't tested on any malware to see if we can have the "same" result. + +At the pace of development, I seperate the binary to functionalities for +testing, I would add a CLI and a REPL. + +One last thing, the backend doesn't have any check on address referencing, so +one may get a blue screen, eventhough I tried to avoid it, I'm not 100% sure it +would not crash the system. + +## Future works + +- [ ] An interactive repl (1) +- [ ] More kernel modules symbols (2) +- [ ] Implementation of more technique (reference Volatility here) +- [ ] Quick and easy way to add manual struct, symbols (3) + +(1) This is quite hard to work out, because we have to make the *types* works. +The currently chosen repl is based on Lisp, because lisp is cool. If the repl +is online, we can combine everything into one binary. + +(2) We may need to download it all and combine to one `HashMap`, with their +types as a specific struct. (Try to avoid string). + +(3) Have no idea on this. diff --git a/src/address.rs b/src/address.rs index 205b3d3..280d0fc 100644 --- a/src/address.rs +++ b/src/address.rs @@ -160,9 +160,12 @@ impl fmt::Display for Address { if let Some(p) = &self.pointer { write!(f, "*({}) + 0x{:x}", *p, self.offset) } - else { + else if self.offset != 0 { write!(f, "0x{:x} + 0x{:x}", self.base, self.offset) } + else { + write!(f, "0x{:x}", self.base) + } } } diff --git a/src/driver_state.rs b/src/driver_state.rs index aa453e5..b0abfdf 100644 --- a/src/driver_state.rs +++ b/src/driver_state.rs @@ -191,13 +191,12 @@ impl DriverState { } let data_addr = Address::from_base(pool_addr.address() + pool_header_size); - let success = handler(pool_addr, &header, data_addr)?; if success { - ptr += chunk_size; /* skip this chunk */ + ptr += chunk_size; // skip this chunk } else { - ptr += 0x4; /* search next */ + ptr += 0x4; // search next } } Ok(true) @@ -298,7 +297,7 @@ impl DriverState { pub fn get_nonpaged_range(&self, ntosbase: &Address) -> BoxResult<[Address; 2]> { // TODO: Add support for other Windows version here match self.windows_ffi.short_version { - WindowsVersion::Windows10FastRing => { + WindowsVersion::WindowsFastRing => { let mistate = ntosbase.clone() + self.pdb_store.get_offset_r("MiState")?; let path_first_va: String = vec![ "_MI_SYSTEM_INFORMATION", @@ -335,6 +334,13 @@ impl DriverState { let last_va = Address::from_base(self.decompose(&mistate, &path_last_va)?); Ok([first_va, last_va]) }, + WindowsVersion::Windows7 => { + let path_first_va = ntosbase.clone() + self.pdb_store.get_offset_r("MmNonPagedPoolStart")?; + let path_last_va = ntosbase.clone() + self.pdb_store.get_offset_r("MiNonPagedPoolEnd")?; + let first_va = Address::from_base(self.deref_addr_new(path_first_va.address())); + let last_va = Address::from_base(self.deref_addr_new(path_last_va.address())); + Ok([first_va, last_va]) + }, _ => { Err("Windows version for nonpaged pool algorithm is not implemented".into()) } diff --git a/src/ioctl_protocol.rs b/src/ioctl_protocol.rs index 8661777..26dd331 100644 --- a/src/ioctl_protocol.rs +++ b/src/ioctl_protocol.rs @@ -21,8 +21,10 @@ pub struct OffsetData { // TODO: Move to WindowsScanStrategy and return the corresponding struct base on Windows version impl OffsetData { pub fn new(pdb_store: &PdbStore, windows_version: WindowsVersion) -> Self { + // TODO: Fix the backend so that only neccessary fields are used + // This is too much, most of the functionality has been move to the frontend match windows_version { - WindowsVersion::Windows10FastRing => Self { + WindowsVersion::WindowsFastRing => Self { eprocess_name_offset: pdb_store.get_offset("_EPROCESS.ImageFileName").unwrap_or(0u64), eprocess_link_offset: pdb_store.get_offset("_EPROCESS.ActiveProcessLinks").unwrap_or(0u64), list_blink_offset: pdb_store.get_offset("_LIST_ENTRY.Blink").unwrap_or(0u64), @@ -51,6 +53,20 @@ impl OffsetData { large_page_size_offset: pdb_store.get_offset("PoolBigPageTableSize").unwrap_or(0u64), pool_chunk_size: pdb_store.get_offset("_POOL_HEADER.struct_size").unwrap_or(0u64), }, + WindowsVersion::Windows7 => Self { + eprocess_name_offset: pdb_store.get_offset("_EPROCESS.ImageFileName").unwrap_or(0u64), + eprocess_link_offset: pdb_store.get_offset("_EPROCESS.ActiveProcessLinks").unwrap_or(0u64), + list_blink_offset: pdb_store.get_offset("_LIST_ENTRY.Blink").unwrap_or(0u64), + process_head_offset: pdb_store.get_offset("PsActiveProcessHead").unwrap_or(0u64), + mistate_offset: pdb_store.get_offset("MiState").unwrap_or(0u64), + hardware_offset: pdb_store.get_offset("_MI_SYSTEM_INFORMATION.Hardware").unwrap_or(0u64), + system_node_offset: pdb_store.get_offset("_MI_HARDWARE_STATE.SystemNodeInformation").unwrap_or(0u64), + first_va_offset: pdb_store.get_offset("_MI_SYSTEM_NODE_INFORMATION.NonPagedPoolFirstVa").unwrap_or(0u64), + last_va_offset: pdb_store.get_offset("_MI_SYSTEM_NODE_INFORMATION.NonPagedPoolLastVa").unwrap_or(0u64), + large_page_table_offset: pdb_store.get_offset("PoolBigPageTable").unwrap_or(0u64), + large_page_size_offset: pdb_store.get_offset("PoolBigPageTableSize").unwrap_or(0u64), + pool_chunk_size: pdb_store.get_offset("_POOL_HEADER.struct_size").unwrap_or(0u64), + }, // TODO: Add other version of Windows here // TODO: Warn user of unknown windows version, because BSOD will occur _ => Self { diff --git a/src/lib.rs b/src/lib.rs index 4683358..83f9617 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,10 +143,13 @@ pub fn scan_eprocess(driver: &DriverState) -> BoxResult> { let eprocess_ptr = &try_eprocess_ptr; + println!("EPROCESS: 0x{:x}", eprocess_ptr.address()); + let pid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.UniqueProcessId")?; let ppid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?; let image_name: Vec = driver.decompose_array(eprocess_ptr, "_EPROCESS.ImageFileName", 15)?; - let unicode_str_ptr = driver.address_of(eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?; + let unicode_str_ptr = driver.address_of(eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName") + .unwrap_or(0); // ImageFilePointer is Windows 10+ let eprocess_name = if let Ok(name) = from_utf8(&image_name) { @@ -525,7 +528,8 @@ pub fn traverse_activehead(driver: &DriverState) -> BoxResult> { let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?; let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?; let image_name: Vec = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?; - let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?; + let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName") + .unwrap_or(0); let eprocess_name = if let Ok(name) = from_utf8(&image_name) { @@ -608,7 +612,8 @@ pub fn traverse_kiprocesslist(driver: &DriverState) -> BoxResult> { let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?; let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?; let image_name: Vec = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?; - let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?; + let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName") + .unwrap_or(0); let eprocess_name = if let Ok(name) = from_utf8(&image_name) { @@ -652,7 +657,8 @@ pub fn traverse_handletable(driver: &DriverState) -> BoxResult> { let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?; let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?; let image_name: Vec = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?; - let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?; + let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName") + .unwrap_or(0); let eprocess_name = if let Ok(name) = from_utf8(&image_name) { diff --git a/src/windows.rs b/src/windows.rs index 5e8e2db..ca703e1 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -29,14 +29,17 @@ const STR_DRIVER_REGISTRY_PATH: &str = "\\Registry\\Machine\\System\\CurrentCont #[allow(dead_code)] #[derive(Debug, Copy, Clone)] pub enum WindowsVersion { + Windows7, + Windows8, + Windows10Legacy, Windows10_2015, Windows10_2016, Windows10_2017, Windows10_2018, Windows10_2019, Windows10_2020, - Windows10FastRing, - Windows10VersionUnknown + WindowsFastRing, + WindowsUnknown } #[allow(dead_code)] @@ -142,11 +145,19 @@ impl WindowsFFI { rtl_get_version(&mut version_info); let short_version = match version_info.dwBuildNumber { + // 2600 => WindowsVersion::WindowsXP, + // 6000 | 6001 | 6002 => WindowsVersion::WindowsVista, + 7600 | 7601 => WindowsVersion::Windows7, + 9200 | 9600 => WindowsVersion::Windows8, + 10240 => WindowsVersion::Windows10Legacy, + 10586 => WindowsVersion::Windows10_2015, + 14393 => WindowsVersion::Windows10_2016, + 15063 | 16299 => WindowsVersion::Windows10_2017, 17134 | 17763 => WindowsVersion::Windows10_2018, - 18362 | 18363 => WindowsVersion::Windows10_2019, + 18363 | 18362 => WindowsVersion::Windows10_2019, 19041 => WindowsVersion::Windows10_2020, - _ if version_info.dwBuildNumber >= 19536 => WindowsVersion::Windows10FastRing, - _ => WindowsVersion::Windows10VersionUnknown + x if x >= 19536 => WindowsVersion::WindowsFastRing, + _ => WindowsVersion::WindowsUnknown }; Self {