Skip to content

Instantly share code, notes, and snippets.

@SilverBut
Created January 29, 2024 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SilverBut/384e9a4de8412d7ae2bc4863603d9317 to your computer and use it in GitHub Desktop.
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.
/// 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