Created
January 29, 2024 16:23
-
-
Save SilverBut/384e9a4de8412d7ae2bc4863603d9317 to your computer and use it in GitHub Desktop.
Mitigation patch for virtio-win/kvm-guest-drivers-windows#1004 if you are using vm.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Put it under memflow examples, install memflow, and cargo run this. | |
use log::Level; | |
use std::str::FromStr; | |
use crc::{Crc, CRC_32_CKSUM}; | |
use pelite::{self, pe64::exports::Export, PeView}; | |
use std::convert::TryInto; | |
use iced_x86::{code_asm::CodeAssembler, code_asm as ca}; | |
use memflow::prelude::v1::*; | |
pub const CKSUM: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM); | |
pub fn try_get_pe_size<T: MemoryView>(mem: &mut T, probe_addr: Address) -> Result<umem> { | |
let mut probe_buf = vec![0; size::kb(4)]; | |
mem.read_raw_into(probe_addr, &mut probe_buf)?; | |
let pe_probe = PeView::from_bytes(&probe_buf) | |
.map_err(|err| Error(ErrorOrigin::OsLayer, ErrorKind::InvalidExeFile).log_trace(err))?; | |
let opt_header = pe_probe.optional_header(); | |
let size_of_image = match opt_header { | |
pelite::Wrap::T32(opt32) => opt32.SizeOfImage, | |
pelite::Wrap::T64(opt64) => opt64.SizeOfImage, | |
}; | |
if size_of_image > 0 { | |
Ok(size_of_image as umem) | |
} else { | |
Err(Error(ErrorOrigin::OsLayer, ErrorKind::InvalidExeFile) | |
.log_trace("pe size_of_image is zero")) | |
} | |
} | |
pub fn try_get_pe_image<T: MemoryView>(mem: &mut T, probe_addr: Address) -> Result<Vec<u8>> { | |
let size_of_image = try_get_pe_size(mem, probe_addr)?; | |
mem.read_raw(probe_addr, size_of_image.try_into().unwrap()) | |
.data_part() | |
} | |
fn get_export(pe: &PeView, name: &str) -> Result<umem> { | |
let export = match pe | |
.get_export_by_name(name) | |
.map_err(|err| Error(ErrorOrigin::OsLayer, ErrorKind::ExportNotFound).log_info(err))? | |
{ | |
Export::Symbol(s) => *s as umem, | |
Export::Forward(_) => { | |
return Err(Error(ErrorOrigin::OsLayer, ErrorKind::ExportNotFound) | |
.log_info("Export found but it was a forwarded export")) | |
} | |
}; | |
Ok(export) | |
} | |
fn main() -> core::result::Result<(), Box<dyn std::error::Error>> { | |
simplelog::TermLogger::init( | |
Level::Info.to_level_filter(), | |
simplelog::Config::default(), | |
simplelog::TerminalMode::Stdout, | |
simplelog::ColorChoice::Auto, | |
).unwrap(); | |
// create inventory + os | |
let inventory = Inventory::scan(); | |
let connector = inventory.create_connector("qemu", None, Some(&ConnectorArgs::from_str("vm_name").unwrap()))?; | |
let mut os = inventory.create_os("win32", Some(connector), None)?; | |
//let process_list = os.process_info_list()?; | |
let viofs_module = os.module_by_name("viofs.sys")?; | |
let ntoskrnl_module = os.module_by_name("ntoskrnl.exe")?; | |
println!("viofs.sys module found: {:?}", viofs_module); | |
println!("ntoskrnl.exe module found: {:?}", ntoskrnl_module); | |
let virt_mem = as_mut!(os impl MemoryView).expect("no virt memory found"); | |
// verify viofs.sys function part | |
const VIOFS_FreeVirtFsRequest_OFFSET: u32 = 0x2e90; | |
const VIOFS_FreeVirtFsRequest_CKSUM: u32 = 0x9d8eba2b; | |
{ | |
let mut out = [0u8; 0x70]; | |
virt_mem.read_into(viofs_module.base+VIOFS_FreeVirtFsRequest_OFFSET, &mut out).unwrap(); | |
assert_eq!(CKSUM.checksum(&out), VIOFS_FreeVirtFsRequest_CKSUM); | |
} | |
// Get offset of ntoskrnl.exe | |
let image = try_get_pe_image(virt_mem, ntoskrnl_module.base)?; | |
let pe = PeView::from_bytes(&image) | |
.map_err(|err| Error(ErrorOrigin::OsLayer, ErrorKind::InvalidExeFile).log_info(err))?; | |
// make sure we are getting correct offset for both module | |
{ | |
let mut out = [0u8; 0x8]; | |
virt_mem.read_into(viofs_module.base+0x9048, &mut out).unwrap(); | |
assert_eq!( | |
get_export(&pe, "MmFreePagesFromMdl").unwrap() + ntoskrnl_module.base.to_umem(), | |
u64::from_le_bytes(out) | |
); | |
} | |
// everything looks fine! find a place to write our patch | |
{ | |
let patch_base = viofs_module.base+0x7948; | |
let mut out = [0u8; 0x30]; | |
virt_mem.read_into(patch_base, &mut out).unwrap(); | |
assert!(out.iter().all(|&i| i==0xcc)); | |
// we start our code from 0x00. the original call to MmFreePagesFromMdl will be | |
// replaced, so we need to save its parameter. this makes it possible to reuse thunk. | |
let ptr_ex_free_pool = get_export(&pe, "ExFreePool").unwrap() + ntoskrnl_module.base.to_umem(); | |
let ptr_mm_free_pages_from_mdl = get_export(&pe, "MmFreePagesFromMdl").unwrap() + ntoskrnl_module.base.to_umem(); | |
let addr_t1: Address = viofs_module.base + 0x2ea2; | |
let addr_t2: Address = viofs_module.base + 0x2ebb; | |
let asmed_s1 = { | |
let mut asm = CodeAssembler::new(64).unwrap(); | |
let mut l_ptr_ex_free_pool = asm.create_label(); | |
let mut l_ptr_mm_free_pages_from_mdl = asm.create_label(); | |
// save rbx since it will be reused later. | |
// save rcx twice since we need to use it, and restore it. | |
asm.push(ca::rbx)?; | |
asm.push(ca::rcx)?; | |
asm.push(ca::rcx)?; | |
asm.call(ca::qword_ptr(l_ptr_mm_free_pages_from_mdl))?; | |
asm.pop(ca::rcx)?; | |
asm.call(ca::qword_ptr(l_ptr_ex_free_pool))?; | |
asm.pop(ca::rcx)?; | |
asm.pop(ca::rbx)?; | |
asm.ret()?; | |
asm.set_label(&mut l_ptr_ex_free_pool)?; | |
asm.db(&(u64::to_le_bytes(ptr_ex_free_pool)))?; | |
asm.set_label(&mut l_ptr_mm_free_pages_from_mdl)?; | |
asm.db(&(u64::to_le_bytes(ptr_mm_free_pages_from_mdl)))?; | |
asm | |
}.assemble(patch_base.to_umem())?; | |
println!("{} {}", patch_base, asmed_s1.len()); | |
println!("{:02x?}", asmed_s1); | |
assert!(asmed_s1.len() <= 0x30); | |
// then we can change our old insn | |
let mut asm_t1 = CodeAssembler::new(64)?; | |
asm_t1.call(patch_base.to_umem())?; | |
asm_t1.nop()?; | |
let asmed_t1 = asm_t1.assemble(addr_t1.to_umem())?; | |
assert_eq!(asmed_t1.len(), 6); | |
let mut asm_t2 = CodeAssembler::new(64)?; | |
asm_t2.call(patch_base.to_umem())?; | |
asm_t2.nop()?; | |
let asmed_t2 = asm_t2.assemble(addr_t2.to_umem())?; | |
assert_eq!(asmed_t2.len(), 6); | |
println!("{}, {}", addr_t1, addr_t2); | |
println!("{:02x?}, {:02x?}", asmed_t1, asmed_t2); | |
// uncomment for debugging purpose | |
//panic!("gg"); | |
virt_mem.write(patch_base, &(asmed_s1[..])).unwrap(); | |
virt_mem.write(addr_t1, &(asmed_t1[..])).unwrap(); | |
virt_mem.write(addr_t2, &(asmed_t2[..])).unwrap(); | |
// done! | |
}; | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment