Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ travis-ci = { repository = "https://github.com/williballenthin/lancelot", branch

[dependencies]
log = "0.4"
goblin = { version = "0.9", features = ["std", "pe32"], default-features = false }
goblin = { version = "0.9", features = ["std", "pe32", "elf64", "elf32"], default-features = false }
object = "0.36"
gimli = "0.32.3"
zydis = { features = ["wasm", "serialization"], optional = true, version = "3.1.3" }
byteorder = "1"
bitflags = "1"
Expand Down
Binary file added core/resources/test/libc
Binary file not shown.
Binary file added core/resources/test/nop_elf
Binary file not shown.
Binary file added core/resources/test/test_DWARF
Binary file not shown.
Binary file added core/resources/test/tiny-x64
Binary file not shown.
45 changes: 45 additions & 0 deletions core/src/analysis/cfg/code_references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use crate::{
VA,
};

#[cfg(feature = "disassembler")]
use crate::loader::elf::ELF;

pub fn find_executable_pointers(module: &Module) -> Result<Vec<VA>> {
// list of candidates: (address of pointer, address pointed to)
let mut candidates: Vec<(VA, VA)> = vec![];
Expand Down Expand Up @@ -101,6 +104,10 @@ pub fn find_new_code_references(module: &Module, insns: &cfg::InstructionIndex)
dis::Target::Indirect(target) => target,
};

if target == 0 {
continue;
}

if insns.insns_by_address.contains_key(&target) {
// this is already code.
continue;
Expand All @@ -121,6 +128,44 @@ pub fn find_new_code_references(module: &Module, insns: &cfg::InstructionIndex)
Ok(new_code.into_iter().collect())
}

#[cfg(feature = "disassembler")]
pub fn find_new_code_references_elf(elf: &ELF, insns: &cfg::InstructionIndex) -> Result<Vec<VA>> {
let decoder = dis::get_disassembler(&elf.module)?;
let mut reader: CachingPageReader = Default::default();
let mut new_code: BTreeSet<VA> = Default::default();

for &va in insns.insns_by_address.keys() {
if let Ok(Some(insn)) = read_insn_with_cache(&mut reader, &elf.module.address_space, va, &decoder) {
for op in dis::get_operands(&insn) {
if let Ok(Some(xref)) = dis::get_operand_xref(&elf.module, va, &insn, op) {
let target = match xref {
dis::Target::Direct(target) => target,
dis::Target::Indirect(target) => target,
};

if target == 0 || insns.insns_by_address.contains_key(&target) {
continue;
}

// check if target is in an executable section
let in_exec_section = elf.module.sections.iter().any(|section| {
section.permissions.intersects(Permissions::X)
&& section.virtual_range.contains(&target)
&& !section.name.starts_with(".ro")
});

if in_exec_section && heuristics::is_probably_code(&elf.module, &decoder, target) {
log::debug!("code references (ELF): found new likely code at {:#x}", target);
new_code.insert(target);
}
}
}
}
}

Ok(new_code.into_iter().collect())
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
72 changes: 72 additions & 0 deletions core/src/analysis/elf/call_targets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use log::debug;
use std::collections::BTreeSet;

use anyhow::Result;

use crate::{analysis::dis, aspace::AddressSpace, loader::elf::ELF, module::Permissions, util, VA};

pub fn find_elf_call_targets(elf: &ELF) -> Result<BTreeSet<VA>> {
let mut ret = BTreeSet::default();
let decoder = dis::get_disassembler(&elf.module)?;

let mut call_count = 0usize;

for section in elf
.module
.sections
.iter()
.filter(|section| section.permissions.intersects(Permissions::X))
{
let name = &section.name;
let vstart: VA = section.virtual_range.start;
let vsize = (section.virtual_range.end - section.virtual_range.start) as usize;

let sec_buf = match elf.module.address_space.read_bytes(vstart, vsize) {
Ok(buf) => buf,
Err(_) => continue,
};

for (insn_offset, insn) in dis::linear_disassemble(&decoder, &sec_buf) {
if let Ok(Some(insn)) = insn {
if insn.meta.category != zydis::InstructionCategory::CALL {
continue;
}

let insn_va: VA = vstart + insn_offset as u64;
let op0 = &insn.operands[0];

match op0.ty {
zydis::OperandType::IMMEDIATE => {
if op0.imm.is_relative {
let imm = if op0.imm.is_signed {
util::u64_i64(op0.imm.value)
} else {
op0.imm.value as i64
};

// skip call $+5 pattern
if imm == 0 {
debug!("call targets: {insn_va:#x}: call $+5 skipped");
continue;
}

let target = ((insn_va + insn.length as u64) as i64 + imm) as u64;
if elf.module.probe_va(target, Permissions::X) {
ret.insert(target);
}
}
}
_ => continue,
}

call_count += 1;
}
}

let count = ret.len();
debug!("elf call targets: {name}, call count: {call_count}, targets: {count}");
}

Ok(ret)
}

205 changes: 205 additions & 0 deletions core/src/analysis/elf/dwarf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use anyhow::Result;
use log::debug;
use gimli::{EndianSlice, RunTimeEndian};

use crate::{
loader::elf::ELF,
VA,
};

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct DwarfFunction {
pub address: VA,
pub size: u64,
pub name: Option<String>,
}

pub fn find_dwarf_function_starts(elf: &ELF) -> Result<Vec<VA>> {
let goblin_elf = goblin::elf::Elf::parse(&elf.buf)?;
let base_address = elf.module.address_space.base_address;
let endian = if goblin_elf.header.endianness()? == goblin::container::Endian::Little {
RunTimeEndian::Little
} else {
RunTimeEndian::Big
};

// load DWARF
let dwarf = load_dwarf_sections(&goblin_elf, &elf.buf, endian)?;

// find functions
let functions = parse_dwarf_functions(&dwarf, base_address)?;

let mut function_starts: Vec<VA> = functions.iter()
.map(|f| f.address)
.collect();

function_starts.sort();
function_starts.dedup();

debug!("dwarf: found {} function starts", function_starts.len());

Ok(function_starts)
}

fn load_dwarf_sections<'a>(goblin_elf: &goblin::elf::Elf, buf: &'a [u8], endian: RunTimeEndian) -> Result<gimli::Dwarf<EndianSlice<'a, RunTimeEndian>>> {
let load_section = |section_name: &str| -> &'a [u8] {
for section in &goblin_elf.section_headers {
if let Some(name) = goblin_elf.shdr_strtab.get_at(section.sh_name) {
if name == section_name {
let start = section.sh_offset as usize;
let end = start + section.sh_size as usize;
return &buf[start..end];
}
}
}
&[]
};

// load all possible sections
let debug_abbrev = gimli::DebugAbbrev::new(load_section(".debug_abbrev"), endian);
let debug_addr = gimli::DebugAddr::from(EndianSlice::new(load_section(".debug_addr"), endian));
let debug_aranges = gimli::DebugAranges::new(load_section(".debug_aranges"), endian);
let debug_info = gimli::DebugInfo::new(load_section(".debug_info"), endian);
let debug_line = gimli::DebugLine::new(load_section(".debug_line"), endian);
let debug_line_str = gimli::DebugLineStr::from(EndianSlice::new(load_section(".debug_line_str"), endian));
let debug_str = gimli::DebugStr::new(load_section(".debug_str"), endian);
let debug_str_offsets = gimli::DebugStrOffsets::from(EndianSlice::new(load_section(".debug_str_offsets"), endian));
let debug_types = gimli::DebugTypes::new(load_section(".debug_types"), endian);

// Location and range sections
let debug_loc = gimli::DebugLoc::from(EndianSlice::new(load_section(".debug_loc"), endian));
let debug_loclists = gimli::DebugLocLists::from(EndianSlice::new(load_section(".debug_loclists"), endian));
let debug_ranges = gimli::DebugRanges::new(load_section(".debug_ranges"), endian);
let debug_rnglists = gimli::DebugRngLists::new(load_section(".debug_rnglists"), endian);

let locations = gimli::LocationLists::new(debug_loc, debug_loclists);
let ranges = gimli::RangeLists::new(debug_ranges, debug_rnglists);

Ok(gimli::Dwarf {
debug_abbrev,
debug_addr,
debug_aranges,
debug_info,
debug_line,
debug_line_str,
debug_str,
debug_str_offsets,
debug_types,
locations,
ranges,
..Default::default()
})
}

fn parse_dwarf_functions(dwarf: &gimli::Dwarf<EndianSlice<RunTimeEndian>>, base_address: VA) -> Result<Vec<DwarfFunction>> {
let mut functions = Vec::new();

let mut units = dwarf.units();
while let Some(header) = units.next()? {
let unit = dwarf.unit(header)?;
let mut entries = unit.entries();
while let Some((_, entry)) = entries.next_dfs()? {
if entry.tag() == gimli::DW_TAG_subprogram {
if let Some(func) = parse_function_entry(dwarf, &unit, entry, base_address)? {
functions.push(func);
}
}
}
}

Ok(functions)
}

fn parse_function_entry(dwarf: &gimli::Dwarf<EndianSlice<RunTimeEndian>>, unit: &gimli::Unit<EndianSlice<RunTimeEndian>>, entry: &gimli::DebuggingInformationEntry<EndianSlice<RunTimeEndian>>, base_address: VA) -> Result<Option<DwarfFunction>> {
let mut low_pc: Option<u64> = None;
let mut high_pc: Option<u64> = None;
let mut high_pc_is_offset = false;
let mut name: Option<String> = None;

let mut attrs = entry.attrs();
while let Some(attr) = attrs.next()? {
match attr.name() {
gimli::DW_AT_low_pc => {
if let gimli::AttributeValue::Addr(addr) = attr.value() {
low_pc = Some(addr);
}
}
gimli::DW_AT_high_pc => {
match attr.value() {
gimli::AttributeValue::Addr(addr) => {
high_pc = Some(addr);
high_pc_is_offset = false;
}
gimli::AttributeValue::Udata(offset) => {
high_pc = Some(offset);
high_pc_is_offset = true;
}
_ => {}
}
}
gimli::DW_AT_name => {
if let Ok(s) = dwarf.attr_string(unit, attr.value()) {
if let Ok(s_str) = s.to_string() {
name = Some(s_str.to_string());
}
}
}
_ => {}
}
}

if let Some(addr) = low_pc {
let actual_address = addr + base_address;
let size = if let Some(hp) = high_pc {
if high_pc_is_offset {
hp
} else {
hp.saturating_sub(addr)
}
} else {
0
};

debug!("dwarf: found function at {:#x} (size: {:#x})", actual_address, size);
if let Some(ref n) = name {
debug!(" name: {}", n);
}

Ok(Some(DwarfFunction {
address: actual_address,
size,
name,
}))
} else {
Ok(None)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::rsrc::*;

#[test]
fn test_nop_elf_no_dwarf() -> Result<()> {
let buf = get_buf(Rsrc::NOPELF);
let elf = crate::loader::elf::ELF::from_bytes(&buf)?;

let result = find_dwarf_function_starts(&elf)?;
assert_eq!(0, result.len());

Ok(())
}

#[test]
fn test_dwarf_functions() -> Result<()> {
let buf = get_buf(Rsrc::TESTDWARF);
let elf = crate::loader::elf::ELF::from_bytes(&buf)?;

let function_starts = find_dwarf_function_starts(&elf)?;
assert_eq!(2, function_starts.len());

Ok(())
}
}
Loading