use anyhow::Result;
use byteorder::{ByteOrder, LittleEndian};
use log::debug;
use crate::{
aspace::AddressSpace,
loader::pe::PE,
module::{Arch, Permissions},
RVA, VA,
};
const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK: u32 = 0xF000_0000;
const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT: u32 = 28;
const IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT: u32 = 0x400;
pub fn find_pe_cfguard_functions(pe: &PE) -> Result<Vec<VA>> {
let mut ret = vec![];
let load_config_directory_rva: RVA = {
let opt_header = match pe.header.optional_header {
Some(opt_header) => opt_header,
_ => return Ok(vec![]),
};
let load_config_directory = match opt_header.data_directories.get_load_config_table() {
Some(load_config_directory) => load_config_directory,
_ => return Ok(vec![]),
};
load_config_directory.virtual_address as RVA
};
debug!(
"load config directory: {:#x}",
pe.module.address_space.base_address + load_config_directory_rva
);
let size = pe.module.address_space.relative.read_u32(load_config_directory_rva)?;
let max_config_directory_offset = match pe.module.arch {
Arch::X32 => 0x58,
Arch::X64 => 0x94,
};
if max_config_directory_offset > size {
debug!("no CF Guard table: load config directory too small");
return Ok(vec![]);
}
let cfg_flags: u32 = match pe.module.arch {
Arch::X32 => pe
.module
.address_space
.relative
.read_u32(load_config_directory_rva + 0x58)?,
Arch::X64 => pe
.module
.address_space
.relative
.read_u32(load_config_directory_rva + 0x90)?,
};
debug!("CF guard flags: {:#x}", cfg_flags);
if cfg_flags & IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT > 0 {
let stride = ((cfg_flags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT)
as usize;
if stride > 8 {
debug!("unexpected CF guard stride: {:#x}", stride);
return Ok(vec![]);
}
let cfg_table_va: VA = match pe.module.arch {
Arch::X32 => pe.module.read_va_at_rva(load_config_directory_rva + 0x50)?,
Arch::X64 => pe.module.read_va_at_rva(load_config_directory_rva + 0x80)?,
};
if cfg_table_va == 0 {
debug!("CF guard table empty");
return Ok(vec![]);
};
debug!("CF Guard table: {:#x}", cfg_table_va);
let cfg_table_count = match pe.module.arch {
Arch::X32 => pe
.module
.address_space
.relative
.read_u32(load_config_directory_rva + 0x54)? as u64,
Arch::X64 => pe
.module
.address_space
.relative
.read_u64(load_config_directory_rva + 0x88)? as u64,
};
debug!("CF Guard table count: {:#x}", cfg_table_count);
let cfg_table_entry_size: usize = 4 + stride;
let cfg_table = pe
.module
.address_space
.read_bytes(cfg_table_va, cfg_table_count as usize * cfg_table_entry_size)?;
for entry_buf in cfg_table.chunks_exact(cfg_table_entry_size) {
let target = pe.module.address_space.base_address + LittleEndian::read_i32(entry_buf) as u64;
if pe.module.probe_va(target, Permissions::X) {
ret.push(target);
} else {
debug!("unexpected non-executable CFG target: {:#x}", target);
break;
}
}
let guard_check_icall_fptr: VA = match pe.module.arch {
Arch::X32 => pe.module.read_va_at_rva(load_config_directory_rva + 0x48)?,
Arch::X64 => pe.module.read_va_at_rva(load_config_directory_rva + 0x70)?,
};
if guard_check_icall_fptr != 0 {
debug!(
"CF Guard check indirect call function pointer: {:#x}",
guard_check_icall_fptr
);
if let Ok(guard_check_icall) = pe.module.read_va_at_va(guard_check_icall_fptr) {
if pe.module.probe_va(guard_check_icall, Permissions::X) {
debug!("CF Guard check icall: {:#x}", guard_check_icall);
ret.push(guard_check_icall);
}
}
}
let guard_dispatch_icall_fptr: VA = match pe.module.arch {
Arch::X32 => pe.module.read_va_at_rva(load_config_directory_rva + 0x4C)?,
Arch::X64 => pe.module.read_va_at_rva(load_config_directory_rva + 0x78)?,
};
if guard_dispatch_icall_fptr != 0 {
debug!(
"CF Guard dispatch indirect call function pointer: {:#x}",
guard_dispatch_icall_fptr
);
if let Ok(guard_dispatch_icall) = pe.module.read_va_at_va(guard_dispatch_icall_fptr) {
if pe.module.probe_va(guard_dispatch_icall, Permissions::X) {
debug!("CF Guard dispatch icall: {:#x}", guard_dispatch_icall);
ret.push(guard_dispatch_icall);
}
}
}
}
Ok(ret)
}
#[cfg(test)]
mod tests {
use crate::rsrc::*;
use anyhow::Result;
#[test]
fn k32() -> Result<()> {
let buf = get_buf(Rsrc::K32);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::control_flow_guard::find_pe_cfguard_functions(&pe)?;
assert_eq!(1502, fns.len());
Ok(())
}
#[test]
fn tiny() -> Result<()> {
let buf = get_buf(Rsrc::TINY);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::control_flow_guard::find_pe_cfguard_functions(&pe)?;
assert_eq!(0, fns.len());
Ok(())
}
#[test]
fn nop() -> Result<()> {
let buf = get_buf(Rsrc::NOP);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::control_flow_guard::find_pe_cfguard_functions(&pe)?;
assert_eq!(0, fns.len());
Ok(())
}
#[test]
fn mimi() -> Result<()> {
let buf = get_buf(Rsrc::MIMI);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let fns = crate::analysis::pe::control_flow_guard::find_pe_cfguard_functions(&pe)?;
assert_eq!(0, fns.len());
Ok(())
}
}