Unverified Commit 170f44b1 by van den Bosch Committed by GitHub

Refactor runtime memory image (#324)

parent 576c3dd9
......@@ -4,7 +4,8 @@
extern crate cwe_checker_lib; // Needed for the docstring-link to work
use cwe_checker_lib::analysis::graph;
use cwe_checker_lib::utils::binary::{BareMetalConfig, RuntimeMemoryImage};
use cwe_checker_lib::intermediate_representation::RuntimeMemoryImage;
use cwe_checker_lib::utils::binary::BareMetalConfig;
use cwe_checker_lib::utils::log::{print_all_messages, LogLevel};
use cwe_checker_lib::utils::{get_ghidra_plugin_path, read_config_file};
use cwe_checker_lib::AnalysisResults;
......@@ -159,6 +160,9 @@ fn run_with_ghidra(args: &CmdlineArgs) {
// so that other analyses do not have to adjust their addresses.
runtime_memory_image.add_global_memory_offset(project.program.term.address_base_offset);
}
project.runtime_memory_image = runtime_memory_image;
// Generate the control flow graph of the program
let extern_sub_tids = project
.program
......@@ -169,12 +173,7 @@ fn run_with_ghidra(args: &CmdlineArgs) {
.collect();
let control_flow_graph = graph::get_program_cfg(&project.program, extern_sub_tids);
let analysis_results = AnalysisResults::new(
&binary,
&runtime_memory_image,
&control_flow_graph,
&project,
);
let analysis_results = AnalysisResults::new(&binary, &control_flow_graph, &project);
let modules_depending_on_string_abstraction = BTreeSet::from_iter(["CWE78"]);
let modules_depending_on_pointer_inference =
......
......@@ -22,7 +22,7 @@ impl<'a> Context<'a> {
for param in callee_fn_sig.parameters.keys() {
let param_id = AbstractIdentifier::from_arg(callee_tid, param);
if let Ok(param_value) =
state_before_call.eval_parameter_arg(param, self.runtime_memory_image)
state_before_call.eval_parameter_arg(param, &self.project.runtime_memory_image)
{
id_map.insert(param_id, param_value);
} else {
......
use crate::abstract_domain::*;
use crate::analysis::function_signature::FunctionSignature;
use crate::analysis::graph::Graph;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::*;
use crate::{abstract_domain::*, utils::binary::RuntimeMemoryImage};
use std::collections::{BTreeMap, BTreeSet};
use super::state::State;
......@@ -24,9 +24,6 @@ pub struct Context<'a> {
pub graph: &'a Graph<'a>,
/// A reference to the `Project` object representing the binary
pub project: &'a Project,
/// The runtime memory image for reading global read-only variables.
/// Note that values of writeable global memory segments are not tracked.
pub runtime_memory_image: &'a RuntimeMemoryImage,
/// Maps the TIDs of functions that shall be treated as extern symbols to the `ExternSymbol` object representing it.
pub extern_symbol_map: &'a BTreeMap<Tid, ExternSymbol>,
/// Maps the TIDs of internal functions to the function signatures computed for it.
......@@ -54,7 +51,6 @@ impl<'a> Context<'a> {
Context {
graph: analysis_results.control_flow_graph,
project: analysis_results.project,
runtime_memory_image: analysis_results.runtime_memory_image,
extern_symbol_map: &analysis_results.project.program.term.extern_symbols,
fn_signatures: analysis_results.function_signatures.unwrap(),
log_collector,
......@@ -73,7 +69,9 @@ impl<'a> Context<'a> {
address: &Expression,
) -> bool {
if self.project.cpu_architecture.contains("MIPS") && var.name == "gp" {
if let Ok(gp_val) = state.load_value(address, var.size, self.runtime_memory_image) {
if let Ok(gp_val) =
state.load_value(address, var.size, &self.project.runtime_memory_image)
{
gp_val.is_top()
} else {
true
......@@ -137,23 +135,23 @@ impl<'a> Context<'a> {
"malloc" => {
let size_parameter = extern_symbol.parameters.get(0).unwrap();
state
.eval_parameter_arg(size_parameter, self.runtime_memory_image)
.eval_parameter_arg(size_parameter, &self.project.runtime_memory_image)
.unwrap_or_else(|_| Data::new_top(address_bytesize))
}
"realloc" => {
let size_parameter = extern_symbol.parameters.get(1).unwrap();
state
.eval_parameter_arg(size_parameter, self.runtime_memory_image)
.eval_parameter_arg(size_parameter, &self.project.runtime_memory_image)
.unwrap_or_else(|_| Data::new_top(address_bytesize))
}
"calloc" => {
let size_param1 = extern_symbol.parameters.get(0).unwrap();
let size_param2 = extern_symbol.parameters.get(1).unwrap();
let param1_value = state
.eval_parameter_arg(size_param1, self.runtime_memory_image)
.eval_parameter_arg(size_param1, &self.project.runtime_memory_image)
.unwrap_or_else(|_| Data::new_top(address_bytesize));
let param2_value = state
.eval_parameter_arg(size_param2, self.runtime_memory_image)
.eval_parameter_arg(size_param2, &self.project.runtime_memory_image)
.unwrap_or_else(|_| Data::new_top(address_bytesize));
param1_value.bin_op(BinOpType::IntMult, &param2_value)
}
......@@ -225,7 +223,7 @@ impl<'a> Context<'a> {
match extern_symbol.get_unique_parameter() {
Ok(parameter) => {
let parameter_value =
state.eval_parameter_arg(parameter, self.runtime_memory_image);
state.eval_parameter_arg(parameter, &self.project.runtime_memory_image);
match parameter_value {
Ok(memory_object_pointer) => {
if let Err(possible_double_frees) =
......@@ -275,7 +273,7 @@ impl<'a> Context<'a> {
I: Iterator<Item = &'iter Arg>,
{
for parameter in parameters {
match state.eval_parameter_arg(parameter, self.runtime_memory_image) {
match state.eval_parameter_arg(parameter, &self.project.runtime_memory_image) {
Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) {
state
......@@ -317,10 +315,12 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol,
) {
for parameter in extern_symbol.parameters.iter() {
match state.eval_parameter_arg(parameter, self.runtime_memory_image) {
match state.eval_parameter_arg(parameter, &self.project.runtime_memory_image) {
Ok(data) => {
if state.pointer_contains_out_of_bounds_target(&data, self.runtime_memory_image)
{
if state.pointer_contains_out_of_bounds_target(
&data,
&self.project.runtime_memory_image,
) {
let warning = CweWarning {
name: "CWE119".to_string(),
version: VERSION.to_string(),
......@@ -394,7 +394,7 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol,
) -> State {
self.log_debug(
new_state.clear_stack_parameter(extern_symbol, self.runtime_memory_image),
new_state.clear_stack_parameter(extern_symbol, &self.project.runtime_memory_image),
Some(&call.tid),
);
let calling_conv = self.project.get_calling_convention(extern_symbol);
......@@ -413,7 +413,9 @@ impl<'a> Context<'a> {
}
} else {
for parameter in extern_symbol.parameters.iter() {
if let Ok(data) = state.eval_parameter_arg(parameter, self.runtime_memory_image) {
if let Ok(data) =
state.eval_parameter_arg(parameter, &self.project.runtime_memory_image)
{
possible_referenced_ids.extend(data.referenced_ids().cloned());
}
}
......
......@@ -42,7 +42,9 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Ok(false) => (), // no null dereference detected
}
// check for out-of-bounds memory access
if new_state.contains_out_of_bounds_mem_access(&def.term, self.runtime_memory_image) {
if new_state
.contains_out_of_bounds_mem_access(&def.term, &self.project.runtime_memory_image)
{
let (warning_name, warning_description) = match &def.term {
Def::Load { .. } => (
"CWE125",
......@@ -75,7 +77,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
match &def.term {
Def::Store { address, value } => {
self.log_debug(
new_state.handle_store(address, value, self.runtime_memory_image),
new_state.handle_store(address, value, &self.project.runtime_memory_image),
Some(&def.tid),
);
Some(new_state)
......@@ -87,7 +89,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Def::Load { var, address } => {
if !self.is_mips_gp_load_to_top_value(state, var, address) {
self.log_debug(
new_state.handle_load(var, address, self.runtime_memory_image),
new_state.handle_load(var, address, &self.project.runtime_memory_image),
Some(&def.tid),
);
}
......
......@@ -275,7 +275,11 @@ impl<'a> PointerInference<'a> {
}
Def::Load { var, address } => {
let loaded_value = state
.load_value(address, var.size, context.runtime_memory_image)
.load_value(
address,
var.size,
&context.project.runtime_memory_image,
)
.unwrap_or_else(|_| Data::new_top(var.size));
self.values_at_defs.insert(def.tid.clone(), loaded_value);
self.addresses_at_defs
......
......@@ -2,6 +2,8 @@
//! or check whether they are violated.
//! E.g. checks for use-after-free or buffer overflow checks.
use crate::intermediate_representation::RuntimeMemoryImage;
use super::*;
impl AbstractObjectList {
......
use super::object::*;
use super::{Data, ValueDomain};
use crate::abstract_domain::*;
use crate::prelude::*;
use crate::{abstract_domain::*, utils::binary::RuntimeMemoryImage};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
......
//! Methods of [`State`] for handling memory and register access operations.
use crate::utils::binary::RuntimeMemoryImage;
use super::*;
impl State {
......
......@@ -4,7 +4,6 @@ use crate::abstract_domain::*;
use crate::analysis::function_signature::FunctionSignature;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use std::collections::{BTreeMap, BTreeSet};
mod access_handling;
......
use super::super::ValueDomain;
use super::*;
use crate::analysis::pointer_inference::object::*;
use crate::utils::binary::RuntimeMemoryImage;
use Expression::*;
fn bv(value: i64) -> ValueDomain {
......
......@@ -27,7 +27,7 @@ impl<'a> VsaResult for PointerInference<'a> {
let state = self.states_at_tids.get(jmp_tid)?;
let context = self.computation.get_context().get_context();
state
.eval_parameter_arg(parameter, context.runtime_memory_image)
.eval_parameter_arg(parameter, &context.project.runtime_memory_image)
.ok()
}
}
......@@ -17,7 +17,6 @@ use crate::{
pointer_inference::State as PointerInferenceState,
},
intermediate_representation::{Def, ExternSymbol, Project, Term, Tid},
utils::binary::RuntimeMemoryImage,
};
use super::{state::State, Config};
......@@ -31,9 +30,6 @@ mod trait_impls;
pub struct Context<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> {
/// A reference to the `Project` object representing the binary
pub project: &'a Project,
/// The runtime memory image for reading global read-only variables.
/// Note that values of writeable global memory segments are not tracked.
pub runtime_memory_image: &'a RuntimeMemoryImage,
/// A pointer to the results of the pointer inference analysis.
/// They are used to determine the targets of pointers to memory,
/// which in turn is used to keep track of taint on the stack or on the heap.
......@@ -62,7 +58,6 @@ impl<'a, T: AbstractDomain + HasTop + Eq + From<String> + DomainInsertion> Conte
/// Create a new context object for a given project.
pub fn new(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
pointer_inference_results: &'a PointerInferenceComputation<'a>,
config: Config,
) -> Context<'a, T> {
......@@ -95,7 +90,6 @@ impl<'a, T: AbstractDomain + HasTop + Eq + From<String> + DomainInsertion> Conte
Context {
project,
runtime_memory_image,
pointer_inference_results,
format_string_index_map: config.format_string_index.into_iter().collect(),
string_symbol_map,
......
......@@ -169,7 +169,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
/// Inserts a char constant into the format string.
pub fn get_constant_char_domain(&self, constant: Bitvector) -> Option<T> {
if let Ok(Some(char_code)) = self.runtime_memory_image.read(
if let Ok(Some(char_code)) = self.project.runtime_memory_image.read(
&constant,
self.project
.datatype_properties
......@@ -199,6 +199,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
/// Inserts a string constant into the format string.
pub fn get_constant_string_domain(&self, constant: Bitvector) -> Option<T> {
if let Ok(string) = self
.project
.runtime_memory_image
.read_string_until_null_terminator(&constant)
{
......@@ -218,7 +219,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if let Some(dest_arg) = extern_symbol.parameters.first() {
if let Some(pi_state) = state.get_pointer_inference_state() {
if let Ok(pointer) =
pi_state.eval_parameter_arg(dest_arg, self.runtime_memory_image)
pi_state.eval_parameter_arg(dest_arg, &self.project.runtime_memory_image)
{
let heap_to_string_map = state.get_heap_to_string_map();
for (target, _) in pointer.get_relative_values().iter() {
......
......@@ -44,7 +44,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
) -> Result<DataDomain<IntervalDomain>, Error> {
if let Some(return_arg) = extern_symbol.parameters.first() {
if let Ok(return_data) =
pi_state.eval_parameter_arg(return_arg, self.runtime_memory_image)
pi_state.eval_parameter_arg(return_arg, &self.project.runtime_memory_image)
{
if !return_data.get_relative_values().is_empty() {
return Ok(return_data);
......@@ -62,7 +62,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state: &PointerInferenceState,
) -> Result<DataDomain<IntervalDomain>, Error> {
if let Some(input_arg) = extern_symbol.parameters.get(1) {
return pi_state.eval_parameter_arg(input_arg, self.runtime_memory_image);
return pi_state.eval_parameter_arg(input_arg, &self.project.runtime_memory_image);
}
Err(anyhow!("No input values"))
......
......@@ -25,7 +25,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state,
extern_symbol,
&self.format_string_index_map,
self.runtime_memory_image,
) {
self.create_abstract_domain_entries_for_function_return_values(
pi_state,
......@@ -47,7 +46,8 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
) {
for (argument, value) in arg_to_value_map.into_iter() {
if argument.get_data_type().unwrap() == Datatype::Pointer {
if let Ok(data) = pi_state.eval_parameter_arg(&argument, self.runtime_memory_image)
if let Ok(data) =
pi_state.eval_parameter_arg(&argument, &self.project.runtime_memory_image)
{
if !data.get_relative_values().is_empty() {
Context::add_constant_or_top_value_to_return_locations(
......@@ -91,8 +91,8 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
let mut new_state = state.clone();
if let Some(pi_state) = state.get_pointer_inference_state() {
if let Some(source_string_arg) = extern_symbol.parameters.first() {
if let Ok(source_string) =
pi_state.eval_parameter_arg(source_string_arg, self.runtime_memory_image)
if let Ok(source_string) = pi_state
.eval_parameter_arg(source_string_arg, &self.project.runtime_memory_image)
{
if self.source_string_mapped_to_return_locations(
pi_state,
......@@ -120,11 +120,15 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
extern_symbol: &ExternSymbol,
) -> bool {
if let Some(global_address) = source_string.get_absolute_value() {
if let Ok(source_string) = self.runtime_memory_image.read_string_until_null_terminator(
&global_address
.try_to_bitvec()
.expect("Could not translate interval address to bitvector."),
) {
if let Ok(source_string) = self
.project
.runtime_memory_image
.read_string_until_null_terminator(
&global_address
.try_to_bitvec()
.expect("Could not translate interval address to bitvector."),
)
{
if let Ok(source_return_string_map) = self
.map_source_string_parameters_to_return_arguments(
pi_state,
......@@ -158,7 +162,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state,
extern_symbol,
&self.format_string_index_map,
self.runtime_memory_image,
) {
let return_values: Vec<String> =
source_string.split(' ').map(|s| s.to_string()).collect();
......
......@@ -25,7 +25,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if let Some(return_arg) = extern_symbol.parameters.first() {
if let Some(pi_state) = state.get_pointer_inference_state() {
if let Ok(return_pointer) =
pi_state.eval_parameter_arg(return_arg, self.runtime_memory_image)
pi_state.eval_parameter_arg(return_arg, &self.project.runtime_memory_image)
{
if !return_pointer.get_relative_values().is_empty() {
let format_string_index = self
......@@ -63,7 +63,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state,
extern_symbol,
format_string_index,
self.runtime_memory_image,
&self.project.runtime_memory_image,
) {
let returned_abstract_domain = self.create_string_domain_for_sprintf_snprintf(
pi_state,
......@@ -105,7 +105,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state,
extern_symbol,
&self.format_string_index_map,
self.runtime_memory_image,
) {
Ok(var_args) => {
if var_args.is_empty() {
......@@ -258,7 +257,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state: &PointerInferenceState,
state: &State<T>,
) -> T {
if let Ok(data) = pi_state.eval_parameter_arg(arg, self.runtime_memory_image) {
if let Ok(data) = pi_state.eval_parameter_arg(arg, &self.project.runtime_memory_image) {
let constant_domain: Option<T> = self.fetch_constant_domain_if_available(&data, arg);
if let Some(generated_domain) = Context::<T>::fetch_subdomains_if_available(
&data,
......
......@@ -17,7 +17,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if let Some(pi_state) = state.get_pointer_inference_state() {
if let Some(return_arg) = extern_symbol.parameters.first() {
if let Ok(return_pointer) =
pi_state.eval_parameter_arg(return_arg, self.runtime_memory_image)
pi_state.eval_parameter_arg(return_arg, &self.project.runtime_memory_image)
{
if !return_pointer.get_relative_values().is_empty() {
let target_domain =
......@@ -64,7 +64,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
let mut input_domain = T::create_top_value_domain();
if let Some(input_arg) = extern_symbol.parameters.get(1) {
if let Ok(input_value) =
pi_state.eval_parameter_arg(input_arg, self.runtime_memory_image)
pi_state.eval_parameter_arg(input_arg, &self.project.runtime_memory_image)
{
// Check whether the second input string is in read only memory or on stack/heap.
if !input_value.get_relative_values().is_empty() {
......@@ -78,6 +78,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if let Some(value) = input_value.get_absolute_value() {
if let Ok(global_address) = value.try_to_bitvec() {
if let Ok(input_string) = self
.project
.runtime_memory_image
.read_string_until_null_terminator(&global_address)
{
......
......@@ -61,7 +61,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String> + Debu
mock_string_symbol_map(&pi_context.project),
mock_format_index_map(),
&pi_results,
&pi_context.runtime_memory_image,
);
let state_before_call: State<T> = State::mock_with_given_pi_state(
......
......@@ -6,7 +6,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
string_symbols: HashMap<Tid, &'a ExternSymbol>,
format_string_index: HashMap<String, usize>,
pointer_inference_results: &'a PointerInferenceComputation<'a>,
runtime_memory_image: &'a RuntimeMemoryImage,
) -> Self {
let mut extern_symbol_map = HashMap::new();
for (tid, symbol) in project.program.term.extern_symbols.iter() {
......@@ -35,7 +34,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
Context {
project,
runtime_memory_image,
pointer_inference_results,
string_symbol_map: string_symbols,
extern_symbol_map,
......
......@@ -46,7 +46,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
new_state.handle_assign_and_load(
output,
input,
self.runtime_memory_image,
&self.project.runtime_memory_image,
&self.block_first_def_set,
true,
);
......@@ -58,7 +58,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
new_state.handle_assign_and_load(
output,
input,
self.runtime_memory_image,
&self.project.runtime_memory_image,
&self.block_first_def_set,
false,
);
......@@ -66,7 +66,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
Def::Store { address, value } => new_state.handle_store(
address,
value,
self.runtime_memory_image,
&self.project.runtime_memory_image,
&self.block_first_def_set,
),
}
......
......@@ -13,7 +13,6 @@ use crate::{
},
},
intermediate_representation::{Bitvector, Blk, ByteSize, ExternSymbol, Jmp, Tid, Variable},
utils::binary::RuntimeMemoryImage,
};
#[test]
......@@ -23,7 +22,6 @@ fn test_update_def() {
vec![(memcpy_symbol.clone(), vec![true])],
"func",
);
let mem_image = RuntimeMemoryImage::mock();
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute(false);
......@@ -62,7 +60,7 @@ fn test_update_def() {
let _ = setup.pi_state_before_symbol_call.store_value(
&pointer_to_pointer,
&loaded_pointer,
&mem_image,
&project.runtime_memory_image,
);
let r2_reg = Variable {
......
......@@ -10,7 +10,6 @@ use crate::{
abstract_domain::{AbstractDomain, DomainInsertion, HasTop},
intermediate_representation::Project,
prelude::*,
utils::binary::RuntimeMemoryImage,
};
use self::state::State;
......@@ -49,17 +48,11 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
/// Generate a new string abstraction computation for a project.
pub fn new(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
control_flow_graph: &'a Graph<'a>,
pointer_inference_results: &'a PointerInferenceComputation<'a>,
config: Config,
) -> StringAbstraction<'a, T> {
let context = Context::new(
project,
runtime_memory_image,
pointer_inference_results,
config,
);
let context = Context::new(project, pointer_inference_results, config);
let mut sub_to_entry_blocks_map = HashMap::new();
for sub in project.program.term.subs.values() {
......@@ -132,18 +125,12 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
/// Compute the string abstraction and return its results.
pub fn run<'a, T: AbstractDomain + HasTop + Eq + From<String> + DomainInsertion>(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
control_flow_graph: &'a Graph<'a>,
pointer_inference: &'a PointerInferenceComputation<'a>,
config: Config,
) -> StringAbstraction<'a, T> {
let mut string_abstraction = StringAbstraction::new(
project,
runtime_memory_image,
control_flow_graph,
pointer_inference,
config,
);
let mut string_abstraction =
StringAbstraction::new(project, control_flow_graph, pointer_inference, config);
string_abstraction.compute();
......
......@@ -9,14 +9,13 @@ use itertools::Itertools;
use petgraph::graph::NodeIndex;
use crate::abstract_domain::{DataDomain, DomainInsertion, HasTop, TryToBitvec};
use crate::intermediate_representation::{ExternSymbol, Project};
use crate::intermediate_representation::{ExternSymbol, Project, RuntimeMemoryImage};
use crate::{abstract_domain::IntervalDomain, prelude::*};
use crate::{
abstract_domain::{AbstractDomain, AbstractIdentifier},
analysis::pointer_inference::PointerInference as PointerInferenceComputation,
analysis::pointer_inference::State as PointerInferenceState,
intermediate_representation::{Expression, Sub, Variable},
utils::binary::RuntimeMemoryImage,
};
/// Contains all information known about the state of a program at a specific point of time.
......
......@@ -31,8 +31,8 @@ use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::pointer_inference::PointerInference;
use crate::intermediate_representation::ExternSymbol;
use crate::intermediate_representation::Jmp;
use crate::intermediate_representation::RuntimeMemoryImage;
use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::CweWarning;
use crate::utils::log::LogMessage;
use crate::CweModule;
......@@ -91,7 +91,7 @@ pub fn check_cwe(
symbol,
&format_string_index,
pointer_inference_results,
analysis_results.runtime_memory_image,
&analysis_results.project.runtime_memory_image,
);
if matches!(
......@@ -219,7 +219,6 @@ pub mod tests {
#[test]
fn test_locate_format_string() {
let sprintf_symbol = ExternSymbol::mock_string();
let runtime_memory_image = RuntimeMemoryImage::mock();
let project = mock_project();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let mut pi_results = PointerInferenceComputation::mock(&project);
......@@ -241,7 +240,7 @@ pub mod tests {
&sprintf_symbol,
&format_string_index,
&pi_results,
&runtime_memory_image,
&project.runtime_memory_image,
),
StringLocation::GlobalReadable
);
......
......@@ -24,7 +24,6 @@ use crate::abstract_domain::TryToBitvec;
use crate::analysis::pointer_inference::State;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::{get_callsites, get_symbol_map};
use crate::CweModule;
......@@ -45,24 +44,20 @@ pub struct Config {
/// Compute the program state at the end of the given basic block
/// assuming nothing is known about the state at the start of the block.
fn compute_block_end_state(
project: &Project,
global_memory: &RuntimeMemoryImage,
block: &Term<Blk>,
) -> State {
fn compute_block_end_state(project: &Project, block: &Term<Blk>) -> State {
let stack_register = &project.stack_pointer_register;
let mut state = State::new(stack_register, block.tid.clone());
for def in block.term.defs.iter() {
match &def.term {
Def::Store { address, value } => {
let _ = state.handle_store(address, value, global_memory);
let _ = state.handle_store(address, value, &project.runtime_memory_image);
}
Def::Assign { var, value } => {
let _ = state.handle_register_assign(var, value);
}
Def::Load { var, address } => {
let _ = state.handle_load(var, address, global_memory);
let _ = state.handle_load(var, address, &project.runtime_memory_image);
}
}
}
......@@ -72,14 +67,13 @@ fn compute_block_end_state(
/// Check whether a parameter value of the call to `symbol` has value `sizeof(void*)`.
fn check_for_pointer_sized_arg(
project: &Project,
global_memory: &RuntimeMemoryImage,
block: &Term<Blk>,
symbol: &ExternSymbol,
) -> bool {
let pointer_size = project.stack_pointer_register.size;
let state = compute_block_end_state(project, global_memory, block);
let state = compute_block_end_state(project, block);
for parameter in symbol.parameters.iter() {
if let Ok(param) = state.eval_parameter_arg(parameter, global_memory) {
if let Ok(param) = state.eval_parameter_arg(parameter, &project.runtime_memory_image) {
if let Ok(param_value) = param.try_to_bitvec() {
if Ok(u64::from(pointer_size)) == param_value.try_to_u64() {
return true;
......@@ -120,12 +114,7 @@ pub fn check_cwe(
let symbol_map = get_symbol_map(project, &config.symbols);
for sub in project.program.term.subs.values() {
for (block, jmp, symbol) in get_callsites(sub, &symbol_map) {
if check_for_pointer_sized_arg(
project,
analysis_results.runtime_memory_image,
block,
symbol,
) {
if check_for_pointer_sized_arg(project, block, symbol) {
cwe_warnings.push(generate_cwe_warning(jmp, symbol))
}
}
......
......@@ -85,12 +85,7 @@ pub fn check_cwe(
let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
let symbol_map = crate::utils::symbol_utils::get_symbol_map(project, &config.symbols[..]);
let general_context = Context::new(
project,
analysis_results.runtime_memory_image,
pointer_inference_results,
cwe_sender,
);
let general_context = Context::new(project, pointer_inference_results, cwe_sender);
for edge in general_context.get_graph().edge_references() {
if let Edge::ExternCallStub(jmp) = edge.weight() {
......
......@@ -8,7 +8,6 @@ use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::pointer_inference::PointerInference as PointerInferenceComputation;
use crate::analysis::pointer_inference::State as PointerInferenceState;
use crate::intermediate_representation::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::CweWarning;
use petgraph::graph::NodeIndex;
use petgraph::visit::IntoNodeReferences;
......@@ -26,8 +25,6 @@ use std::sync::Arc;
pub struct Context<'a> {
/// A pointer to the corresponding project struct.
project: &'a Project,
/// A pointer to the representation of the runtime memory image.
runtime_memory_image: &'a RuntimeMemoryImage,
/// A pointer to the results of the pointer inference analysis.
/// They are used to determine the targets of pointers to memory,
/// which in turn is used to keep track of taint on the stack or on the heap.
......@@ -64,7 +61,6 @@ impl<'a> Context<'a> {
/// since this function can be expensive!
pub fn new(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
pointer_inference_results: &'a PointerInferenceComputation<'a>,
cwe_collector: crossbeam_channel::Sender<CweWarning>,
) -> Self {
......@@ -92,7 +88,6 @@ impl<'a> Context<'a> {
}
Context {
project,
runtime_memory_image,
pointer_inference_results,
block_start_node_map: Arc::new(block_start_node_map),
extern_symbol_map: Arc::new(extern_symbol_map),
......@@ -203,8 +198,8 @@ impl<'a> Context<'a> {
{
return true;
}
if let Ok(stack_param) =
pi_state.eval_parameter_arg(parameter, self.runtime_memory_image)
if let Ok(stack_param) = pi_state
.eval_parameter_arg(parameter, &self.project.runtime_memory_image)
{
if state.check_if_address_points_to_taint(stack_param, pi_state) {
return true;
......@@ -417,16 +412,14 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::binary::RuntimeMemoryImage;
impl<'a> Context<'a> {
pub fn mock(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
pi_results: &'a PointerInferenceComputation<'a>,
) -> Context<'a> {
let (cwe_sender, _) = crossbeam_channel::unbounded();
let mut context = Context::new(project, runtime_memory_image, pi_results, cwe_sender);
let mut context = Context::new(project, pi_results, cwe_sender);
let taint_source = Box::new(Term {
tid: Tid::new("taint_source"),
term: Jmp::Call {
......@@ -445,9 +438,8 @@ mod tests {
#[test]
fn check_parameter_arg_for_taint() {
let project = Project::mock_x64();
let runtime_memory_image = RuntimeMemoryImage::mock();
let pi_results = PointerInferenceComputation::mock(&project);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let context = Context::mock(&project, &pi_results);
let (mut state, _pi_state) = State::mock_with_pi_state();
assert_eq!(
......@@ -476,9 +468,8 @@ mod tests {
#[test]
fn handle_generic_call() {
let project = Project::mock_x64();
let runtime_memory_image = RuntimeMemoryImage::mock();
let pi_results = PointerInferenceComputation::mock(&project);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let context = Context::mock(&project, &pi_results);
let mut state = State::mock();
assert!(context
......@@ -497,9 +488,8 @@ mod tests {
#[test]
fn update_def() {
let project = Project::mock_x64();
let runtime_memory_image = RuntimeMemoryImage::mock();
let pi_results = PointerInferenceComputation::mock(&project);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let context = Context::mock(&project, &pi_results);
let (mut state, pi_state) = State::mock_with_pi_state();
state.set_pointer_inference_state(Some(pi_state));
......@@ -550,9 +540,8 @@ mod tests {
#[test]
fn update_jump() {
let project = Project::mock_x64();
let runtime_memory_image = RuntimeMemoryImage::mock();
let pi_results = PointerInferenceComputation::mock(&project);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let context = Context::mock(&project, &pi_results);
let (state, _pi_state) = State::mock_with_pi_state();
let jump = Term {
......
......@@ -26,7 +26,6 @@ use crate::abstract_domain::TryToBitvec;
use crate::analysis::pointer_inference::State;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::{get_callsites, get_symbol_map};
use crate::CweModule;
......@@ -50,7 +49,6 @@ fn get_umask_permission_arg(
block: &Term<Blk>,
umask_symbol: &ExternSymbol,
project: &Project,
global_memory: &RuntimeMemoryImage,
) -> Result<u64, Error> {
let stack_register = &project.stack_pointer_register;
let mut state = State::new(stack_register, block.tid.clone());
......@@ -58,19 +56,19 @@ fn get_umask_permission_arg(
for def in block.term.defs.iter() {
match &def.term {
Def::Store { address, value } => {
let _ = state.handle_store(address, value, global_memory);
let _ = state.handle_store(address, value, &project.runtime_memory_image);
}
Def::Assign { var, value } => {
let _ = state.handle_register_assign(var, value);
}
Def::Load { var, address } => {
let _ = state.handle_load(var, address, global_memory);
let _ = state.handle_load(var, address, &project.runtime_memory_image);
}
}
}
let parameter = umask_symbol.get_unique_parameter()?;
let param_value = state.eval_parameter_arg(parameter, global_memory)?;
let param_value = state.eval_parameter_arg(parameter, &project.runtime_memory_image)?;
if let Ok(umask_arg) = param_value.try_to_bitvec() {
Ok(umask_arg.try_to_u64()?)
} else {
......@@ -115,12 +113,7 @@ pub fn check_cwe(
if !umask_symbol_map.is_empty() {
for sub in project.program.term.subs.values() {
for (block, jmp, umask_symbol) in get_callsites(sub, &umask_symbol_map) {
match get_umask_permission_arg(
block,
umask_symbol,
project,
analysis_results.runtime_memory_image,
) {
match get_umask_permission_arg(block, umask_symbol, project) {
Ok(permission_const) => {
if is_chmod_style_arg(permission_const) {
cwes.push(generate_cwe_warning(sub, jmp, permission_const));
......
......@@ -45,9 +45,9 @@ use crate::intermediate_representation::Arg;
use crate::intermediate_representation::Expression;
use crate::intermediate_representation::ExternSymbol;
use crate::intermediate_representation::Jmp;
use crate::intermediate_representation::RuntimeMemoryImage;
use crate::intermediate_representation::Sub;
use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::CweWarning;
use crate::utils::log::LogMessage;
......@@ -117,7 +117,10 @@ pub fn check_cwe(
&jmp.tid,
&cwe_sender,
&log_sender,
string_abstraction.get_context().runtime_memory_image,
&string_abstraction
.get_context()
.project
.runtime_memory_image,
)
}
}
......
......@@ -30,6 +30,8 @@ mod program;
pub use program::*;
mod project;
pub use project::*;
mod runtime_memory_image;
pub use runtime_memory_image::*;
/// An unsigned number of bytes.
///
......
use super::*;
use crate::utils::log::LogMessage;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
mod block_duplication_normalization;
use crate::utils::log::LogMessage;
use block_duplication_normalization::*;
/// The `Project` struct is the main data structure representing a binary.
......@@ -24,6 +23,8 @@ pub struct Project {
pub register_set: BTreeSet<Variable>,
/// Contains the properties of C data types. (e.g. size)
pub datatype_properties: DatatypeProperties,
/// Represents the memory after loading the binary.
pub runtime_memory_image: RuntimeMemoryImage,
}
impl Project {
......@@ -319,6 +320,7 @@ mod tests {
calling_conventions,
register_set: integer_register.iter().cloned().collect(),
datatype_properties: DatatypeProperties::mock_x64(),
runtime_memory_image: RuntimeMemoryImage::mock(),
}
}
......@@ -357,6 +359,7 @@ mod tests {
)]),
register_set: integer_register.collect(),
datatype_properties: DatatypeProperties::mock_arm32(),
runtime_memory_image: RuntimeMemoryImage::mock(),
}
}
}
......
use super::*;
use crate::utils::binary::{parse_hex_string_to_u64, BareMetalConfig, MemorySegment};
use goblin::{elf, Object};
/// A representation of the runtime image of a binary after being loaded into memory by the loader.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct RuntimeMemoryImage {
memory_segments: Vec<MemorySegment>,
is_little_endian: bool,
}
impl RuntimeMemoryImage {
/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub fn empty(is_little_endian: bool) -> RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: Vec::new(),
is_little_endian,
}
}
/// Generate a runtime memory image for a given binary.
///
/// The function can parse ELF and PE files as input.
pub fn new(binary: &[u8]) -> Result<Self, Error> {
let parsed_object = Object::parse(binary)?;
match parsed_object {
Object::Elf(elf_file) => {
let mut memory_segments = Vec::new();
for header in elf_file.program_headers.iter() {
if header.p_type == elf::program_header::PT_LOAD {
memory_segments.push(MemorySegment::from_elf_segment(binary, header));
}
}
if memory_segments.is_empty() {
return Err(anyhow!("No loadable segments found"));
}
Ok(RuntimeMemoryImage {
memory_segments,
is_little_endian: elf_file.header.endianness().unwrap().is_little(),
})
}
Object::PE(pe_file) => {
let mut memory_segments = Vec::new();
for header in pe_file.sections.iter() {
if (header.characteristics & 0x02000000) == 0 {
// Only load segments which are not discardable
memory_segments.push(MemorySegment::from_pe_section(binary, header));
}
}
if memory_segments.is_empty() {
return Err(anyhow!("No loadable segments found"));
}
let mut memory_image = RuntimeMemoryImage {
memory_segments,
is_little_endian: true,
};
memory_image.add_global_memory_offset(pe_file.image_base as u64);
Ok(memory_image)
}
_ => Err(anyhow!("Object type not supported.")),
}
}
/// Generate a runtime memory image for a bare metal binary.
///
/// The generated runtime memory image contains:
/// * one memory region corresponding to non-volatile memory
/// * one memory region corresponding to volatile memory (RAM)
///
/// See [`BareMetalConfig`] for more information about the assumed memory layout for bare metal binaries.
pub fn new_from_bare_metal(
binary: &[u8],
bare_metal_config: &BareMetalConfig,
) -> Result<Self, Error> {
let processor_id_parts: Vec<&str> = bare_metal_config.processor_id.split(':').collect();
if processor_id_parts.len() < 3 {
return Err(anyhow!("Could not parse processor ID."));
}
let is_little_endian = match processor_id_parts[1] {
"LE" => true,
"BE" => false,
_ => return Err(anyhow!("Could not parse endianness of the processor ID.")),
};
let flash_base_address = parse_hex_string_to_u64(&bare_metal_config.flash_base_address)?;
let ram_base_address = parse_hex_string_to_u64(&bare_metal_config.ram_base_address)?;
let ram_size = parse_hex_string_to_u64(&bare_metal_config.ram_size)?;
// Check that the whole binary is contained in addressable space.
let address_bit_length = processor_id_parts[2].parse::<u64>()?;
match flash_base_address.checked_add(binary.len() as u64) {
Some(max_address) => {
if (max_address >> address_bit_length) != 0 {
return Err(anyhow!("Binary too large for given base address"));
}
}
None => return Err(anyhow!("Binary too large for given base address")),
}
Ok(RuntimeMemoryImage {
memory_segments: vec![
MemorySegment::from_bare_metal_file(binary, flash_base_address),
MemorySegment::new_bare_metal_ram_segment(ram_base_address, ram_size),
],
is_little_endian,
})
}
/// Return whether values in the memory image should be interpreted in little-endian
/// or big-endian byte order.
pub fn is_little_endian_byte_order(&self) -> bool {
self.is_little_endian
}
/// Add a global offset to the base addresses of all memory segments.
/// Useful to align the addresses with those reported by Ghidra
/// if the Ghidra backend added such an offset to all addresses.
pub fn add_global_memory_offset(&mut self, offset: u64) {
for segment in self.memory_segments.iter_mut() {
segment.base_address += offset;
}
}
/// Read the contents of the memory image at the given address
/// to emulate a read instruction to global data at runtime.
///
/// The read method is endian-aware,
/// i.e. values are interpreted with the endianness of the CPU architecture.
/// If the address points to a writeable segment, the returned value is a `Ok(None)` value,
/// since the data may change during program execution.
///
/// Returns an error if the address is not contained in the global data address range.
pub fn read(&self, address: &Bitvector, size: ByteSize) -> Result<Option<Bitvector>, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& u64::from(size) <= segment.base_address + segment.bytes.len() as u64
&& address <= segment.base_address + segment.bytes.len() as u64 - u64::from(size)
{
if segment.write_flag {
// The segment is writeable, thus we do not know the content at runtime.
return Ok(None);
}
let index = (address - segment.base_address) as usize;
let mut bytes = segment.bytes[index..index + u64::from(size) as usize].to_vec();
if self.is_little_endian {
bytes = bytes.into_iter().rev().collect();
}
let mut bytes = bytes.into_iter();
let mut bitvector = Bitvector::from_u8(bytes.next().unwrap());
for byte in bytes {
let new_byte = Bitvector::from_u8(byte);
bitvector = bitvector.bin_op(BinOpType::Piece, &new_byte)?;
}
return Ok(Some(bitvector));
}
}
// No segment fully contains the read.
Err(anyhow!("Address is not a valid global memory address."))
}
/// Read the contents of memory from a given address onwards until a null byte is reached and checks whether the
/// content is a valid UTF8 string.
pub fn read_string_until_null_terminator(&self, address: &Bitvector) -> Result<&str, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address <= segment.base_address + segment.bytes.len() as u64
{
let start_index = (address - segment.base_address) as usize;
if let Some(end_index) = segment.bytes[start_index..].iter().position(|&b| b == 0) {
let c_str = std::ffi::CStr::from_bytes_with_nul(
&segment.bytes[start_index..start_index + end_index + 1],
)?;
return Ok(c_str.to_str()?);
} else {
return Err(anyhow!("Not a valid string in memory."));
}
}
}
Err(anyhow!("Address is not a valid global memory address."))
}
/// Checks whether the constant is a global memory address.
pub fn is_global_memory_address(&self, constant: &Bitvector) -> bool {
if self.read(constant, constant.bytesize()).is_ok() {
return true;
}
false
}
/// Check whether all addresses in the given interval point to a readable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub fn is_interval_readable(
&self,
start_address: u64,
end_address: u64,
) -> Result<bool, Error> {
for segment in self.memory_segments.iter() {
if start_address >= segment.base_address
&& start_address < segment.base_address + segment.bytes.len() as u64
{
if end_address <= segment.base_address + segment.bytes.len() as u64 {
return Ok(segment.read_flag);
} else {
return Err(anyhow!("Interval spans more than one segment"));
}
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
/// For an address to global read-only memory, return the memory segment it points to
/// and the index inside the segment, where the address points to.
///
/// Returns an error if the target memory segment is marked as writeable
/// or if the pointer does not point to global memory.
pub fn get_ro_data_pointer_at_address(
&self,
address: &Bitvector,
) -> Result<(&[u8], usize), Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address < segment.base_address + segment.bytes.len() as u64
{
if segment.write_flag {
return Err(anyhow!("Target segment is writeable"));
} else {
return Ok((&segment.bytes, (address - segment.base_address) as usize));
}
}
}
Err(anyhow!("Pointer target not in global memory."))
}
/// Check whether the given address points to a writeable segment in the runtime memory image.
///
/// Returns an error if the address does not point to global memory.
pub fn is_address_writeable(&self, address: &Bitvector) -> Result<bool, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address < segment.base_address + segment.bytes.len() as u64
{
return Ok(segment.write_flag);
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
/// Check whether all addresses in the given interval point to a writeable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub fn is_interval_writeable(
&self,
start_address: u64,
end_address: u64,
) -> Result<bool, Error> {
for segment in self.memory_segments.iter() {
if start_address >= segment.base_address
&& start_address < segment.base_address + segment.bytes.len() as u64
{
if end_address <= segment.base_address + segment.bytes.len() as u64 {
return Ok(segment.write_flag);
} else {
return Err(anyhow!("Interval spans more than one segment"));
}
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
}
impl RuntimeMemoryImage {
/// Creates a mock runtime memory image with: byte series, strings and format strings.
pub fn mock() -> RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: vec![
MemorySegment {
bytes: [0xb0u8, 0xb1, 0xb2, 0xb3, 0xb4].to_vec(),
base_address: 0x1000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
MemorySegment {
bytes: [0u8; 8].to_vec(),
base_address: 0x2000,
read_flag: true,
write_flag: true,
execute_flag: false,
},
// Contains the Hello World string at byte 3002.
MemorySegment {
bytes: [
0x01, 0x02, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x00,
]
.to_vec(),
base_address: 0x3000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
MemorySegment {
bytes: [0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].to_vec(),
base_address: 0x4000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains strings: '/dev/sd%c%d' and 'cat %s'
MemorySegment {
bytes: [
0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x64, 0x25, 0x63, 0x25, 0x64, 0x00,
0x63, 0x61, 0x74, 0x20, 0x25, 0x73, 0x00,
]
.to_vec(),
base_address: 0x5000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains string: 'cat %s %s %s %s' starting at the first byte.
MemorySegment {
bytes: [
0x63, 0x61, 0x74, 0x20, 0x25, 0x73, 0x20, 0x25, 0x73, 0x20, 0x25, 0x73,
0x20, 0x25, 0x73, 0x00,
]
.to_vec(),
base_address: 0x6000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains string: 'str1 str2 str3 str4'
MemorySegment {
bytes: [
0x73, 0x74, 0x72, 0x31, 0x20, 0x73, 0x74, 0x72, 0x32, 0x20, 0x73, 0x74,
0x72, 0x33, 0x20, 0x73, 0x74, 0x72, 0x34, 0x00,
]
.to_vec(),
base_address: 0x7000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
],
is_little_endian: true,
}
}
}
#[cfg(test)]
mod tests {
use crate::intermediate_representation::{Bitvector, ByteSize, RuntimeMemoryImage};
#[test]
fn read_endianness() {
let mut mem_image = RuntimeMemoryImage::mock();
let address = Bitvector::from_u32(0x1001);
assert_eq!(
mem_image.read(&address, ByteSize::new(4)).unwrap(),
Bitvector::from_u32(0xb4b3b2b1).into()
);
mem_image.is_little_endian = false;
assert_eq!(
mem_image.read(&address, ByteSize::new(4)).unwrap(),
Bitvector::from_u32(0xb1b2b3b4).into()
);
}
#[test]
fn ro_data_pointer() {
let mem_image = RuntimeMemoryImage::mock();
let address = Bitvector::from_u32(0x1002);
let (slice, index) = mem_image.get_ro_data_pointer_at_address(&address).unwrap();
assert_eq!(index, 2);
assert_eq!(&slice[index..], &[0xb2u8, 0xb3, 0xb4]);
}
#[test]
fn test_read_string_until_null_terminator() {
let mem_image = RuntimeMemoryImage::mock();
// the byte array contains "Hello World".
let expected_string: &str =
std::str::from_utf8(b"\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64").unwrap();
let address = Bitvector::from_u32(0x3002);
assert_eq!(
expected_string,
mem_image
.read_string_until_null_terminator(&address)
.unwrap(),
);
}
}
......@@ -74,8 +74,8 @@ use analysis::function_signature::FunctionSignature;
use analysis::graph::Graph;
use analysis::pointer_inference::PointerInference;
use analysis::string_abstraction::StringAbstraction;
use intermediate_representation::Project;
use utils::binary::RuntimeMemoryImage;
use utils::log::{CweWarning, LogMessage};
mod prelude {
......@@ -140,8 +140,6 @@ pub fn get_modules() -> Vec<&'static CweModule> {
pub struct AnalysisResults<'a> {
/// The content of the binary file
pub binary: &'a [u8],
/// A representation of the runtime memory image of the binary.
pub runtime_memory_image: &'a RuntimeMemoryImage,
/// The computed control flow graph of the program.
pub control_flow_graph: &'a Graph<'a>,
/// A pointer to the project struct
......@@ -158,13 +156,11 @@ impl<'a> AnalysisResults<'a> {
/// Create a new `AnalysisResults` struct with only the project itself known.
pub fn new(
binary: &'a [u8],
runtime_memory_image: &'a RuntimeMemoryImage,
control_flow_graph: &'a Graph<'a>,
project: &'a Project,
) -> AnalysisResults<'a> {
AnalysisResults {
binary,
runtime_memory_image,
control_flow_graph,
project,
function_signatures: None,
......@@ -231,7 +227,6 @@ impl<'a> AnalysisResults<'a> {
) -> StringAbstraction<BricksDomain> {
crate::analysis::string_abstraction::run(
self.project,
self.runtime_memory_image,
self.control_flow_graph,
pi_results.unwrap(),
serde_json::from_value(config.clone()).unwrap(),
......@@ -264,11 +259,8 @@ mod tests {
HashSet::from_iter(project.program.term.extern_symbols.keys().cloned());
let graph = Box::new(get_program_cfg(&project.program, extern_subs));
let graph: &'a Graph = Box::leak(graph);
let runtime_mem_image = Box::new(RuntimeMemoryImage::mock());
let runtime_mem_image: &'a RuntimeMemoryImage = Box::leak(runtime_mem_image);
let binary: &'a Vec<u8> = Box::leak(Box::new(Vec::new()));
let analysis_results = AnalysisResults::new(binary, runtime_mem_image, graph, project);
let analysis_results = AnalysisResults::new(binary, graph, project);
let (fn_sigs, _) = analysis_results.compute_function_signatures();
let fn_sigs: &'a BTreeMap<_, _> = Box::leak(Box::new(fn_sigs));
let analysis_results = analysis_results.with_function_signatures(Some(fn_sigs));
......
......@@ -13,6 +13,7 @@ use crate::intermediate_representation::ExternSymbol as IrExternSymbol;
use crate::intermediate_representation::Jmp as IrJmp;
use crate::intermediate_representation::Program as IrProgram;
use crate::intermediate_representation::Project as IrProject;
use crate::intermediate_representation::RuntimeMemoryImage;
use crate::intermediate_representation::Sub as IrSub;
use crate::intermediate_representation::Variable as IrVariable;
use crate::prelude::*;
......@@ -870,6 +871,7 @@ impl Project {
calling_conventions,
register_set,
datatype_properties: self.datatype_properties.clone(),
runtime_memory_image: RuntimeMemoryImage::empty(true),
}
}
}
......
//! Handles argument detection by parsing format string arguments during a function call. (e.g. sprintf)
use super::binary::RuntimeMemoryImage;
use crate::prelude::*;
use crate::{
abstract_domain::{IntervalDomain, TryToBitvec},
......@@ -105,7 +104,6 @@ pub fn get_variable_parameters(
pi_state: &PointerInferenceState,
extern_symbol: &ExternSymbol,
format_string_index_map: &HashMap<String, usize>,
runtime_memory_image: &RuntimeMemoryImage,
) -> Result<Vec<Arg>, Error> {
let format_string_index = match format_string_index_map.get(&extern_symbol.name) {
Some(index) => *index,
......@@ -116,7 +114,7 @@ pub fn get_variable_parameters(
pi_state,
extern_symbol,
format_string_index,
runtime_memory_image,
&project.runtime_memory_image,
);
if let Ok(format_string) = format_string_results.as_ref() {
......
......@@ -9,7 +9,6 @@ fn mock_pi_state() -> PointerInferenceState {
#[test]
/// Tests extraction of format string parameters '/dev/sd%c%d' and 'cat %s'.
fn test_get_variable_parameters() {
let mem_image = RuntimeMemoryImage::mock();
let mut pi_state = mock_pi_state();
let sprintf_symbol = ExternSymbol::mock_string();
let mut format_string_index_map: HashMap<String, usize> = HashMap::new();
......@@ -38,7 +37,6 @@ fn test_get_variable_parameters() {
&pi_state,
&sprintf_symbol,
&format_string_index_map,
&mem_image,
)
.unwrap()
);
......@@ -61,7 +59,6 @@ fn test_get_variable_parameters() {
&pi_state,
&sprintf_symbol,
&format_string_index_map,
&mem_image,
)
.unwrap()
);
......
//! Utility structs and functions which directly parse the binary file.
use crate::intermediate_representation::BinOpType;
use crate::intermediate_representation::BitvectorExtended;
use crate::prelude::*;
use goblin::elf;
use goblin::pe;
use goblin::Object;
/// Contains all information parsed out of the bare metal configuration JSON file.
///
......@@ -50,23 +47,16 @@ impl BareMetalConfig {
}
/// A helper function to parse a hex string to an integer.
fn parse_hex_string_to_u64(mut string: &str) -> Result<u64, Error> {
pub fn parse_hex_string_to_u64(mut string: &str) -> Result<u64, Error> {
if string.starts_with("0x") {
string = &string[2..]
}
Ok(u64::from_str_radix(string, 16)?)
}
/// A representation of the runtime image of a binary after being loaded into memory by the loader.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct RuntimeMemoryImage {
memory_segments: Vec<MemorySegment>,
is_little_endian: bool,
}
/// A continuous segment in the memory image.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
struct MemorySegment {
pub struct MemorySegment {
/// The contents of the segment
pub bytes: Vec<u8>,
/// The base address, i.e. the address of the first byte of the segment
......@@ -143,394 +133,3 @@ impl MemorySegment {
}
}
}
impl RuntimeMemoryImage {
/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub fn empty(is_little_endian: bool) -> RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: Vec::new(),
is_little_endian,
}
}
/// Generate a runtime memory image for a given binary.
///
/// The function can parse ELF and PE files as input.
pub fn new(binary: &[u8]) -> Result<Self, Error> {
let parsed_object = Object::parse(binary)?;
match parsed_object {
Object::Elf(elf_file) => {
let mut memory_segments = Vec::new();
for header in elf_file.program_headers.iter() {
if header.p_type == elf::program_header::PT_LOAD {
memory_segments.push(MemorySegment::from_elf_segment(binary, header));
}
}
if memory_segments.is_empty() {
return Err(anyhow!("No loadable segments found"));
}
Ok(RuntimeMemoryImage {
memory_segments,
is_little_endian: elf_file.header.endianness().unwrap().is_little(),
})
}
Object::PE(pe_file) => {
let mut memory_segments = Vec::new();
for header in pe_file.sections.iter() {
if (header.characteristics & 0x02000000) == 0 {
// Only load segments which are not discardable
memory_segments.push(MemorySegment::from_pe_section(binary, header));
}
}
if memory_segments.is_empty() {
return Err(anyhow!("No loadable segments found"));
}
let mut memory_image = RuntimeMemoryImage {
memory_segments,
is_little_endian: true,
};
memory_image.add_global_memory_offset(pe_file.image_base as u64);
Ok(memory_image)
}
_ => Err(anyhow!("Object type not supported.")),
}
}
/// Generate a runtime memory image for a bare metal binary.
///
/// The generated runtime memory image contains:
/// * one memory region corresponding to non-volatile memory
/// * one memory region corresponding to volatile memory (RAM)
///
/// See [`BareMetalConfig`] for more information about the assumed memory layout for bare metal binaries.
pub fn new_from_bare_metal(
binary: &[u8],
bare_metal_config: &BareMetalConfig,
) -> Result<Self, Error> {
let processor_id_parts: Vec<&str> = bare_metal_config.processor_id.split(':').collect();
if processor_id_parts.len() < 3 {
return Err(anyhow!("Could not parse processor ID."));
}
let is_little_endian = match processor_id_parts[1] {
"LE" => true,
"BE" => false,
_ => return Err(anyhow!("Could not parse endianness of the processor ID.")),
};
let flash_base_address = parse_hex_string_to_u64(&bare_metal_config.flash_base_address)?;
let ram_base_address = parse_hex_string_to_u64(&bare_metal_config.ram_base_address)?;
let ram_size = parse_hex_string_to_u64(&bare_metal_config.ram_size)?;
// Check that the whole binary is contained in addressable space.
let address_bit_length = processor_id_parts[2].parse::<u64>()?;
match flash_base_address.checked_add(binary.len() as u64) {
Some(max_address) => {
if (max_address >> address_bit_length) != 0 {
return Err(anyhow!("Binary too large for given base address"));
}
}
None => return Err(anyhow!("Binary too large for given base address")),
}
Ok(RuntimeMemoryImage {
memory_segments: vec![
MemorySegment::from_bare_metal_file(binary, flash_base_address),
MemorySegment::new_bare_metal_ram_segment(ram_base_address, ram_size),
],
is_little_endian,
})
}
/// Return whether values in the memory image should be interpreted in little-endian
/// or big-endian byte order.
pub fn is_little_endian_byte_order(&self) -> bool {
self.is_little_endian
}
/// Add a global offset to the base addresses of all memory segments.
/// Useful to align the addresses with those reported by Ghidra
/// if the Ghidra backend added such an offset to all addresses.
pub fn add_global_memory_offset(&mut self, offset: u64) {
for segment in self.memory_segments.iter_mut() {
segment.base_address += offset;
}
}
/// Read the contents of the memory image at the given address
/// to emulate a read instruction to global data at runtime.
///
/// The read method is endian-aware,
/// i.e. values are interpreted with the endianness of the CPU architecture.
/// If the address points to a writeable segment, the returned value is a `Ok(None)` value,
/// since the data may change during program execution.
///
/// Returns an error if the address is not contained in the global data address range.
pub fn read(&self, address: &Bitvector, size: ByteSize) -> Result<Option<Bitvector>, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& u64::from(size) <= segment.base_address + segment.bytes.len() as u64
&& address <= segment.base_address + segment.bytes.len() as u64 - u64::from(size)
{
if segment.write_flag {
// The segment is writeable, thus we do not know the content at runtime.
return Ok(None);
}
let index = (address - segment.base_address) as usize;
let mut bytes = segment.bytes[index..index + u64::from(size) as usize].to_vec();
if self.is_little_endian {
bytes = bytes.into_iter().rev().collect();
}
let mut bytes = bytes.into_iter();
let mut bitvector = Bitvector::from_u8(bytes.next().unwrap());
for byte in bytes {
let new_byte = Bitvector::from_u8(byte);
bitvector = bitvector.bin_op(BinOpType::Piece, &new_byte)?;
}
return Ok(Some(bitvector));
}
}
// No segment fully contains the read.
Err(anyhow!("Address is not a valid global memory address."))
}
/// Read the contents of memory from a given address onwards until a null byte is reached and checks whether the
/// content is a valid UTF8 string.
pub fn read_string_until_null_terminator(&self, address: &Bitvector) -> Result<&str, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address <= segment.base_address + segment.bytes.len() as u64
{
let start_index = (address - segment.base_address) as usize;
if let Some(end_index) = segment.bytes[start_index..].iter().position(|&b| b == 0) {
let c_str = std::ffi::CStr::from_bytes_with_nul(
&segment.bytes[start_index..start_index + end_index + 1],
)?;
return Ok(c_str.to_str()?);
} else {
return Err(anyhow!("Not a valid string in memory."));
}
}
}
Err(anyhow!("Address is not a valid global memory address."))
}
/// Checks whether the constant is a global memory address.
pub fn is_global_memory_address(&self, constant: &Bitvector) -> bool {
if self.read(constant, constant.bytesize()).is_ok() {
return true;
}
false
}
/// Check whether all addresses in the given interval point to a readable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub fn is_interval_readable(
&self,
start_address: u64,
end_address: u64,
) -> Result<bool, Error> {
for segment in self.memory_segments.iter() {
if start_address >= segment.base_address
&& start_address < segment.base_address + segment.bytes.len() as u64
{
if end_address <= segment.base_address + segment.bytes.len() as u64 {
return Ok(segment.read_flag);
} else {
return Err(anyhow!("Interval spans more than one segment"));
}
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
/// For an address to global read-only memory, return the memory segment it points to
/// and the index inside the segment, where the address points to.
///
/// Returns an error if the target memory segment is marked as writeable
/// or if the pointer does not point to global memory.
pub fn get_ro_data_pointer_at_address(
&self,
address: &Bitvector,
) -> Result<(&[u8], usize), Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address < segment.base_address + segment.bytes.len() as u64
{
if segment.write_flag {
return Err(anyhow!("Target segment is writeable"));
} else {
return Ok((&segment.bytes, (address - segment.base_address) as usize));
}
}
}
Err(anyhow!("Pointer target not in global memory."))
}
/// Check whether the given address points to a writeable segment in the runtime memory image.
///
/// Returns an error if the address does not point to global memory.
pub fn is_address_writeable(&self, address: &Bitvector) -> Result<bool, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address < segment.base_address + segment.bytes.len() as u64
{
return Ok(segment.write_flag);
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
/// Check whether all addresses in the given interval point to a writeable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub fn is_interval_writeable(
&self,
start_address: u64,
end_address: u64,
) -> Result<bool, Error> {
for segment in self.memory_segments.iter() {
if start_address >= segment.base_address
&& start_address < segment.base_address + segment.bytes.len() as u64
{
if end_address <= segment.base_address + segment.bytes.len() as u64 {
return Ok(segment.write_flag);
} else {
return Err(anyhow!("Interval spans more than one segment"));
}
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
impl RuntimeMemoryImage {
/// Creates a mock runtime memory image with: byte series, strings and format strings.
pub fn mock() -> RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: vec![
MemorySegment {
bytes: [0xb0u8, 0xb1, 0xb2, 0xb3, 0xb4].to_vec(),
base_address: 0x1000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
MemorySegment {
bytes: [0u8; 8].to_vec(),
base_address: 0x2000,
read_flag: true,
write_flag: true,
execute_flag: false,
},
// Contains the Hello World string at byte 3002.
MemorySegment {
bytes: [
0x01, 0x02, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x00,
]
.to_vec(),
base_address: 0x3000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
MemorySegment {
bytes: [0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].to_vec(),
base_address: 0x4000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains strings: '/dev/sd%c%d' and 'cat %s'
MemorySegment {
bytes: [
0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x64, 0x25, 0x63, 0x25, 0x64, 0x00,
0x63, 0x61, 0x74, 0x20, 0x25, 0x73, 0x00,
]
.to_vec(),
base_address: 0x5000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains string: 'cat %s %s %s %s' starting at the first byte.
MemorySegment {
bytes: [
0x63, 0x61, 0x74, 0x20, 0x25, 0x73, 0x20, 0x25, 0x73, 0x20, 0x25, 0x73,
0x20, 0x25, 0x73, 0x00,
]
.to_vec(),
base_address: 0x6000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
// Contains string: 'str1 str2 str3 str4'
MemorySegment {
bytes: [
0x73, 0x74, 0x72, 0x31, 0x20, 0x73, 0x74, 0x72, 0x32, 0x20, 0x73, 0x74,
0x72, 0x33, 0x20, 0x73, 0x74, 0x72, 0x34, 0x00,
]
.to_vec(),
base_address: 0x7000,
read_flag: true,
write_flag: false,
execute_flag: false,
},
],
is_little_endian: true,
}
}
}
#[test]
fn read_endianness() {
let mut mem_image = RuntimeMemoryImage::mock();
let address = Bitvector::from_u32(0x1001);
assert_eq!(
mem_image.read(&address, ByteSize::new(4)).unwrap(),
Bitvector::from_u32(0xb4b3b2b1).into()
);
mem_image.is_little_endian = false;
assert_eq!(
mem_image.read(&address, ByteSize::new(4)).unwrap(),
Bitvector::from_u32(0xb1b2b3b4).into()
);
}
#[test]
fn ro_data_pointer() {
let mem_image = RuntimeMemoryImage::mock();
let address = Bitvector::from_u32(0x1002);
let (slice, index) = mem_image.get_ro_data_pointer_at_address(&address).unwrap();
assert_eq!(index, 2);
assert_eq!(&slice[index..], &[0xb2u8, 0xb3, 0xb4]);
}
#[test]
fn test_read_string_until_null_terminator() {
let mem_image = RuntimeMemoryImage::mock();
// the byte array contains "Hello World".
let expected_string: &str =
std::str::from_utf8(b"\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64").unwrap();
let address = Bitvector::from_u32(0x3002);
assert_eq!(
expected_string,
mem_image
.read_string_until_null_terminator(&address)
.unwrap(),
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment