///|
/// The load-configuration directory (slot 10).
///
/// This is where the linker records the process-wide mitigations an image was
/// built with: the security cookie, the SafeSEH handler table, Control Flow
/// Guard tables and flags, the heap flags and the code-integrity entry.
///
/// The structure grew over many linkers, so every field is read through the
/// declared `Size`: a field that the producer did not emit reads as zero rather
/// than running past the structure.
///|
pub(all) struct LoadConfig {
size : Int32
timestamp : Int32
global_flags_clear : Int32
global_flags_set : Int32
critical_section_timeout : Int32
process_heap_flags : Int32
csd_version : Int32
dependent_load_flags : Int32
security_cookie : Int64
seh_handler_table : Int64
seh_handler_count : Int64
guard_cf_check : Int64
guard_cf_dispatch : Int64
guard_cf_function_table : Int64
guard_cf_function_count : Int64
guard_flags : Int32
code_integrity_flags : Int32
code_integrity_catalog : Int32
} derive(@debug.Debug)
///|
/// Read the load-configuration directory, or `None` when absent.
pub fn Pe::load_config(self : Pe) -> LoadConfig? raise PeError {
let directory = match self.directory(10) {
None => return None
Some(directory) => directory
}
if directory.virtual_address == 0 || directory.size == 0 {
return None
}
let base = match self.rva_to_offset(directory.virtual_address) {
Some(offset) => offset
None =>
raise PeError::Malformed(
"load-config directory RVA " +
hex(directory.virtual_address) +
" is not backed by file bytes",
0,
)
}
let head = Reader::at(self.bytes, base)
let size = head.u32()
if size < 4 {
return None
}
let wide = self.bits() == 64
let timestamp = load_read(self, base, size, 4, false).to_int()
let global_flags_clear = load_read(self, base, size, 12, false).to_int()
let global_flags_set = load_read(self, base, size, 16, false).to_int()
let critical_section_timeout = load_read(self, base, size, 20, false).to_int()
let heap_offset = if wide { 72 } else { 48 }
let affinity_offset = if wide { 64 } else { 44 }
let process_heap_flags = load_read(self, base, size, heap_offset, false).to_int()
let csd_version = load_read(self, base, size, heap_offset + 4, false).to_int()
let dependent_load_flags = load_read(self, base, size, heap_offset + 6, false).to_int()
let cookie_offset = if wide { 88 } else { 60 }
let seh_offset = if wide { 96 } else { 64 }
let guard_offset = if wide { 112 } else { 72 }
let guard_flags_offset = if wide { 144 } else { 88 }
let code_integrity_offset = if wide { 148 } else { 92 }
let security_cookie = load_read(self, base, size, cookie_offset, true)
let seh_handler_table = load_read(self, base, size, seh_offset, true)
let seh_handler_count = load_read(
self,
base,
size,
seh_offset + (if wide { 8 } else { 4 }),
true,
)
let guard_cf_check = load_read(self, base, size, guard_offset, true)
let guard_cf_dispatch = load_read(
self,
base,
size,
guard_offset + (if wide { 8 } else { 4 }),
true,
)
let guard_cf_function_table = load_read(
self,
base,
size,
guard_offset + (if wide { 16 } else { 8 }),
true,
)
let guard_cf_function_count = load_read(
self,
base,
size,
guard_offset + (if wide { 24 } else { 12 }),
true,
)
let guard_flags = load_read(self, base, size, guard_flags_offset, false).to_int()
let code_integrity_flags = load_read(
self, base, size, code_integrity_offset, false,
).to_int()
let code_integrity_catalog = load_read(
self,
base,
size,
code_integrity_offset + 2,
false,
).to_int()
let _ = affinity_offset
Some({
size,
timestamp,
global_flags_clear,
global_flags_set,
critical_section_timeout,
process_heap_flags,
csd_version,
dependent_load_flags,
security_cookie,
seh_handler_table,
seh_handler_count,
guard_cf_check,
guard_cf_dispatch,
guard_cf_function_table,
guard_cf_function_count,
guard_flags,
code_integrity_flags,
code_integrity_catalog,
})
}
///|
/// Read a load-config field, returning zero when the declared structure size
/// says the field is not present.
fn load_read(
pe : Pe,
base : Int32,
size : Int32,
field_offset : Int32,
pointer_sized : Bool,
) -> Int64 raise PeError {
let width = if pointer_sized && pe.bits() == 64 {
8
} else if pointer_sized {
4
} else {
4
}
if field_offset + width > size ||
base + field_offset + width > pe.bytes.length() {
return 0L
}
let reader = Reader::at(pe.bytes, base + field_offset)
if width == 8 {
reader.u64()
} else {
reader.u32().to_int64()
}
}
///|
/// The SafeSEH handler table: the RVAs of the exception handlers the image was
/// built with.
///
/// SafeSEH is an x86 mitigation. On x64 the table is unused, so this returns
/// `None`; on x86 it returns `Some([])` when the image has a load config but
/// records no handlers, which is exactly the state a mitigation check wants to
/// distinguish from "no load config at all".
pub fn Pe::safe_seh_handlers(self : Pe) -> Array[Int32]? raise PeError {
if self.bits() != 32 {
return None
}
let config = match self.load_config() {
None => return None
Some(config) => config
}
if config.seh_handler_count <= 0L || config.seh_handler_table == 0L {
return Some([])
}
let count = if config.seh_handler_count > 65536L {
65536
} else {
config.seh_handler_count.to_int()
}
let table_rva = match self.va_to_rva(config.seh_handler_table.to_int()) {
Some(rva) => rva
None => return Some([])
}
let reader = match self.reader_at_rva(table_rva) {
Some(reader) => reader
None => return Some([])
}
let handlers : Array[Int32] = []
let mut i = 0
while i < count {
handlers.push(reader.u32())
i = i + 1
}
Some(handlers)
}