pub(crate) mod checked_functions;
pub(crate) mod gnu_properties;
pub(crate) mod needed_libc;
use std::collections::HashSet;
use log::{debug, log_enabled, warn};
use crate::elf::gnu_properties::{GNUProperty, GNUPropertyAArch64Feature1, GNUPropertyX86Feature1};
use crate::errors::Result;
use crate::options::status::{ASLRCompatibilityLevel, DisplayInColorTerm};
use crate::options::{
AddressSpaceLayoutRandomizationOption, BinarySecurityOption,
ELFBranchTargetIdentificationOption, ELFFortifySourceOption, ELFGuardedControlStackOption,
ELFImmediateBindingOption, ELFReadOnlyAfterRelocationsOption, ELFStackMustBeExecutableOption,
ELFStackProtectionOption, ELFSupportsShadowStackOption,
};
use crate::parser::BinaryParser;
use self::checked_functions::function_is_checked_version;
use self::needed_libc::NeededLibC;
pub(crate) fn analyze_binary(
parser: &BinaryParser,
options: &crate::cmdline::Options,
) -> Result<Vec<Box<dyn DisplayInColorTerm>>> {
let supports_address_space_layout_randomization =
AddressSpaceLayoutRandomizationOption.check(parser, options)?;
let has_stack_protection = ELFStackProtectionOption.check(parser, options)?;
let stack_must_be_executable = ELFStackMustBeExecutableOption.check(parser, options)?;
let supports_shadow_stack = ELFSupportsShadowStackOption.check(parser, options)?;
let guarded_control_stack = ELFGuardedControlStackOption.check(parser, options)?;
let branch_target_identification =
ELFBranchTargetIdentificationOption.check(parser, options)?;
let read_only_after_reloc = ELFReadOnlyAfterRelocationsOption.check(parser, options)?;
let immediate_bind = ELFImmediateBindingOption.check(parser, options)?;
let mut result = vec![
supports_address_space_layout_randomization,
has_stack_protection,
stack_must_be_executable,
supports_shadow_stack,
guarded_control_stack,
branch_target_identification,
read_only_after_reloc,
immediate_bind,
];
if !options.no_libc {
let fortify_source =
ELFFortifySourceOption::new(options.libc_spec).check(parser, options)?;
result.push(fortify_source);
}
Ok(result)
}
pub(crate) fn get_libc_functions_by_protection<'t>(
elf: &goblin::elf::Elf,
libc_ref: &'t NeededLibC,
) -> (HashSet<&'t str>, HashSet<&'t str>) {
let imported_functions = elf
.dynsyms
.iter()
.filter_map(|symbol| dynamic_symbol_is_named_imported_function(elf, &symbol));
let mut protected_functions = HashSet::<&str>::default();
let mut unprotected_functions = HashSet::<&str>::default();
for imported_function in imported_functions {
if function_is_checked_version(imported_function) {
if let Some(unchecked_function) = libc_ref.exports_function(imported_function) {
protected_functions.insert(unchecked_function);
} else {
warn!(
"Checked function '{imported_function}' is not exported by \
the C runtime library. This might indicate a C runtime mismatch."
);
}
} else if let Some(unchecked_function) =
libc_ref.exports_checked_version_of_function(imported_function)
{
unprotected_functions.insert(unchecked_function);
}
}
(protected_functions, unprotected_functions)
}
pub(crate) fn supports_aslr(elf: &goblin::elf::Elf) -> ASLRCompatibilityLevel {
debug!(
"Header type is 'ET_{}'.",
goblin::elf::header::et_to_str(elf.header.e_type)
);
match elf.header.e_type {
goblin::elf::header::ET_EXEC => {
ASLRCompatibilityLevel::Unsupported
}
goblin::elf::header::ET_DYN => {
if log_enabled!(log::Level::Debug) {
if elf
.program_headers
.iter()
.any(|ph| ph.p_type == goblin::elf::program_header::PT_PHDR)
{
debug!("Found type 'PT_PHDR' inside program headers section.");
} else if let Some(dynamic_section) = elf.dynamic.as_ref() {
let dynamic_section_flags_include_pie = dynamic_section.dyns.iter().any(|e| {
(e.d_tag == goblin::elf::dynamic::DT_FLAGS_1) && ((e.d_val & DF_1_PIE) != 0)
});
if dynamic_section_flags_include_pie {
debug!(
"Bit 'DF_1_PIE' is set in tag 'DT_FLAGS_1' inside \
dynamic linking information."
);
} else {
debug!("Binary is a shared library with dynamic linking information.");
}
} else {
debug!("Binary is a shared library without dynamic linking information.");
}
}
ASLRCompatibilityLevel::Supported
}
_ => {
debug!("Position-independence could not be determined.");
ASLRCompatibilityLevel::Unknown
}
}
}
pub(crate) fn becomes_read_only_after_relocations(elf: &goblin::elf::Elf) -> bool {
let r = elf
.program_headers
.iter()
.any(|ph| ph.p_type == goblin::elf::program_header::PT_GNU_RELRO);
if r {
debug!("Found type 'PT_GNU_RELRO' inside program headers section.");
}
r
}
pub(crate) fn stack_must_be_executable(elf: &goblin::elf::Elf) -> bool {
if let Some(ph) = elf
.program_headers
.iter()
.find(|ph| ph.p_type == goblin::elf::program_header::PT_GNU_STACK)
{
let predicate = (ph.p_flags & goblin::elf::program_header::PF_X) != 0;
debug!(
"Found type 'PT_GNU_STACK' inside program headers section. \
The stack must{} be executable.",
if predicate { "" } else { " not" }
);
predicate
} else {
debug!(
"Did not find type 'PT_GNU_STACK' inside program headers section. \
The stack must be executable."
);
true
}
}
pub(crate) fn supports_shadow_stack(elf: &goblin::elf::Elf, bytes: &[u8]) -> Result<bool> {
let properties = GNUProperty::parse_all(elf, bytes)?;
Ok(properties
.into_iter()
.filter_map(|property| {
if let GNUProperty::X86Feature1And(feature) = property {
Some(feature)
} else {
None
}
})
.any(|feature| feature.contains(GNUPropertyX86Feature1::SHSTK)))
}
pub(crate) fn branch_target_identification(elf: &goblin::elf::Elf, bytes: &[u8]) -> Result<bool> {
let properties = GNUProperty::parse_all(elf, bytes)?;
Ok(properties
.into_iter()
.filter_map(|property| match property {
GNUProperty::X86Feature1And(feature) => {
Some(feature.contains(GNUPropertyX86Feature1::IBT))
}
GNUProperty::AARCH64Feature1And(feature) => {
Some(feature.contains(GNUPropertyAArch64Feature1::BTI))
}
GNUProperty::StackSize(_)
| GNUProperty::NoCopyOnProtected
| GNUProperty::ProcessorSpecific(_)
| GNUProperty::UserSpecific(_)
| GNUProperty::X86UInt32And(_)
| GNUProperty::X86Feature2Used(_)
| GNUProperty::X86UInt32OrAnd(_)
| GNUProperty::X86Feature2Needed(_)
| GNUProperty::X86UInt32Or(_)
| GNUProperty::X86ISA1Used(_)
| GNUProperty::X86ISA1Needed(_)
| GNUProperty::AARCH64FeaturePAuth(_) => None,
})
.any(|feature| feature))
}
pub(crate) fn guarded_control_stack(elf: &goblin::elf::Elf, bytes: &[u8]) -> Result<bool> {
let properties = GNUProperty::parse_all(elf, bytes)?;
Ok(properties
.into_iter()
.filter_map(|property| {
if let GNUProperty::AARCH64Feature1And(feature) = property {
Some(feature)
} else {
None
}
})
.any(|feature| feature.contains(GNUPropertyAArch64Feature1::GCS)))
}
pub(crate) fn has_stack_protection(elf: &goblin::elf::Elf) -> bool {
let r = elf
.dynsyms
.iter()
.filter_map(|symbol| dynamic_symbol_is_named_function(elf, &symbol))
.any(|name| name == "__stack_chk_fail");
if r {
debug!("Found function symbol '__stack_chk_fail' inside dynamic symbols section.");
}
r
}
const STV_DEFAULT: u8 = 0;
pub(crate) fn dynamic_symbol_is_named_exported_function<'elf>(
elf: &'elf goblin::elf::Elf,
symbol: &goblin::elf::sym::Sym,
) -> Option<&'elf str> {
if symbol.st_other == STV_DEFAULT {
let st_type = symbol.st_type();
if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC {
let st_bind = symbol.st_bind();
if (st_bind == goblin::elf::sym::STB_GLOBAL
|| st_bind == goblin::elf::sym::STB_WEAK
|| st_bind == goblin::elf::sym::STB_GNU_UNIQUE)
&& (symbol.st_value != 0)
{
return elf
.dynstrtab
.get_at(symbol.st_name)
.filter(|name| !name.is_empty()); }
}
}
None
}
pub(crate) const DF_1_PIE: u64 = 0x08_00_00_00;
pub(crate) fn symbol_is_named_function_or_unspecified<'elf>(
elf: &'elf goblin::elf::Elf,
symbol: &goblin::elf::sym::Sym,
) -> Option<&'elf str> {
let st_type = symbol.st_type();
if st_type == goblin::elf::sym::STT_FUNC
|| st_type == goblin::elf::sym::STT_GNU_IFUNC
|| st_type == goblin::elf::sym::STT_NOTYPE
{
elf.strtab
.get_at(symbol.st_name)
.filter(|name| !name.is_empty()) } else {
None
}
}
fn dynamic_symbol_is_named_function<'elf>(
elf: &'elf goblin::elf::Elf,
symbol: &goblin::elf::sym::Sym,
) -> Option<&'elf str> {
let st_type = symbol.st_type();
if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC {
elf.dynstrtab
.get_at(symbol.st_name)
.filter(|name| !name.is_empty()) } else {
None
}
}
fn dynamic_symbol_is_named_imported_function<'elf>(
elf: &'elf goblin::elf::Elf,
symbol: &goblin::elf::sym::Sym,
) -> Option<&'elf str> {
let st_type = symbol.st_type();
if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC {
let st_bind = symbol.st_bind();
if (st_bind == goblin::elf::sym::STB_GLOBAL
|| st_bind == goblin::elf::sym::STB_WEAK
|| st_bind == goblin::elf::sym::STB_GNU_UNIQUE)
&& (symbol.st_value == 0)
{
return elf
.dynstrtab
.get_at(symbol.st_name)
.filter(|name| !name.is_empty()); }
}
None
}
pub(crate) fn requires_immediate_binding(elf: &goblin::elf::Elf) -> bool {
elf.dynamic
.as_ref()
.and_then(|dli| {
dli.dyns
.iter()
.find(|dyn_entry| dynamic_linking_info_entry_requires_immediate_binding(dyn_entry))
})
.is_some()
}
fn dynamic_linking_info_entry_requires_immediate_binding(
dyn_entry: &goblin::elf::dynamic::Dyn,
) -> bool {
match dyn_entry.d_tag {
goblin::elf::dynamic::DT_BIND_NOW => {
debug!("Found tag 'DT_BIND_NOW' inside dynamic linking information.");
true
}
goblin::elf::dynamic::DT_FLAGS => {
let r = (dyn_entry.d_val & goblin::elf::dynamic::DF_BIND_NOW) != 0;
if r {
debug!(
"Bit 'DF_BIND_NOW' is set in tag 'DT_FLAGS' inside \
dynamic linking information."
);
}
r
}
goblin::elf::dynamic::DT_FLAGS_1 => {
let r = (dyn_entry.d_val & goblin::elf::dynamic::DF_1_NOW) != 0;
if r {
debug!(
"Bit 'DF_1_NOW' is set in tag 'DT_FLAGS_1' inside \
dynamic linking information."
);
}
r
}
_ => false,
}
}