use crate::antidebug::{self, AntiDebugFinding, BypassConfig};
use crate::antianalysis::{BypassAction, BypassEngine, BypassEngineConfig};
use crate::event_log::{EventLog, EventKind};
use crate::got_hook::GotHookManager;
use crate::secret_scan::{SecretScanner, SecretScanConfig};
use crate::memscan::{self, ScanMatch};
use crate::breakpoint::BreakpointManager;
use crate::checksec::{self, ChecksecResult};
use crate::disasm::{self, DisasmInstruction, DisasmStyle};
use crate::dwarf::{DwarfInfo, SourceLocation};
use crate::elf::{self, ElfFile, GotPltEntry};
use crate::entropy::{self, SectionEntropy};
use crate::error::{Error, Result};
use crate::patch::{self, PatchResult};
use crate::process::Process;
use crate::procfs::{self, MemoryRegion};
use crate::registers::{self, Registers};
use crate::rop::{self, Gadget};
use crate::strings::{self, ExtractedString};
use crate::syscall_trace;
use crate::types::{ProcessState, StopReason, VirtAddr};
use crate::unwind::Unwinder;
use crate::rust_type;
use crate::variables::{self, Variable, VariableReader};
use crate::watchpoint::{WatchpointManager, WatchpointType, WatchpointSize, Watchpoint};
use crate::shared_lib::{self, SharedLibrary};
use nix::sys::signal::Signal;
use std::collections::{HashMap, HashSet};
use std::path::Path;
#[derive(Debug, Clone, Copy)]
pub struct SignalPolicy {
pub stop: bool,
pub pass: bool,
}
impl Default for SignalPolicy {
fn default() -> Self {
SignalPolicy {
stop: true,
pass: false,
}
}
}
#[derive(Debug)]
pub struct BacktraceFrame {
pub index: usize,
pub pc: VirtAddr,
pub function: Option<String>,
pub location: Option<SourceLocation>,
}
pub struct Target {
process: Process,
breakpoints: BreakpointManager,
watchpoints: WatchpointManager,
program_path: String,
elf: ElfFile,
dwarf: Option<DwarfInfo>,
var_reader: Option<VariableReader>,
unwinder: Option<Unwinder>,
signal_policies: HashMap<Signal, SignalPolicy>,
pending_signal: Option<Signal>,
caught_syscalls: HashSet<u64>,
catch_all_syscalls: bool,
bypass_config: BypassConfig,
got_hooks: GotHookManager,
event_log: EventLog,
bypass_engine: BypassEngine,
secret_scanner: SecretScanner,
}
impl Target {
pub fn launch(program: &Path, args: &[&str]) -> Result<Self> {
let process = Process::launch(program, args)?;
let pid = process.pid().as_raw();
let elf = ElfFile::load(program)?;
let dwarf = DwarfInfo::load(program).ok();
let var_reader = VariableReader::load(program).ok();
let unwinder = Unwinder::load(program).ok();
let mut bypass_engine = BypassEngine::new(BypassEngineConfig::default());
bypass_engine.set_tracee_ids(pid, pid);
Ok(Target {
program_path: program.to_string_lossy().into_owned(),
process,
breakpoints: BreakpointManager::new(),
watchpoints: WatchpointManager::new(),
elf,
dwarf,
var_reader,
unwinder,
signal_policies: HashMap::new(),
pending_signal: None,
caught_syscalls: HashSet::new(),
catch_all_syscalls: false,
bypass_config: BypassConfig::default(),
got_hooks: GotHookManager::new(),
event_log: EventLog::new(),
bypass_engine,
secret_scanner: SecretScanner::default_config(),
})
}
pub fn attach(pid: nix::unistd::Pid) -> Result<Self> {
let process = Process::attach(pid)?;
let pid_raw = pid.as_raw();
let exe_path = format!("/proc/{}/exe", pid_raw);
let elf = ElfFile::load(Path::new(&exe_path))?;
let dwarf = DwarfInfo::load(Path::new(&exe_path)).ok();
let var_reader = VariableReader::load(Path::new(&exe_path)).ok();
let unwinder = Unwinder::load(Path::new(&exe_path)).ok();
let mut bypass_engine = BypassEngine::new(BypassEngineConfig::default());
bypass_engine.set_tracee_ids(pid_raw, pid_raw);
Ok(Target {
program_path: exe_path,
process,
breakpoints: BreakpointManager::new(),
watchpoints: WatchpointManager::new(),
elf,
dwarf,
var_reader,
unwinder,
signal_policies: HashMap::new(),
pending_signal: None,
caught_syscalls: HashSet::new(),
catch_all_syscalls: false,
bypass_config: BypassConfig::default(),
got_hooks: GotHookManager::new(),
event_log: EventLog::new(),
bypass_engine,
secret_scanner: SecretScanner::default_config(),
})
}
pub fn resume(&mut self) -> Result<StopReason> {
let bypass_active = self.bypass_engine.config().bypass_ptrace
|| self.bypass_engine.config().bypass_proc_status
|| self.bypass_engine.config().bypass_prctl_dumpable;
loop {
let sig = self.pending_signal.take();
if self.catch_all_syscalls || !self.caught_syscalls.is_empty() || bypass_active {
self.process.resume_with_syscall_trap(sig)?;
} else if let Some(s) = sig {
self.process.resume_with_signal(s)?;
} else {
self.process.resume()?;
}
let reason = self.process.wait_on_signal()?;
self.handle_stop(&reason)?;
self.log_stop_reason(&reason);
if let StopReason::SyscallEntry { number, args } = &reason {
let read_string = |addr: u64| -> Option<String> {
self.process
.read_memory(VirtAddr(addr), 256)
.ok()
.map(|bytes| {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).into_owned()
})
};
let action =
self.bypass_engine.on_syscall_entry(*number, args, &read_string);
match action {
BypassAction::RewriteArg { arg_index, new_value } => {
if let Ok(mut regs) = self.read_registers() {
let reg_name = match arg_index {
0 => "rdi",
1 => "rsi",
2 => "rdx",
3 => "r10",
4 => "r8",
5 => "r9",
_ => "",
};
if !reg_name.is_empty() {
let _ = regs.set(reg_name, new_value);
let _ = self.write_registers(®s);
self.bypass_engine
.record_prctl_bypass(&mut self.event_log);
}
}
}
BypassAction::SkipSyscall => {
if let Ok(mut regs) = self.read_registers() {
let _ = regs.set("orig_rax", u64::MAX); let _ = self.write_registers(®s);
}
}
_ => {} }
}
if let StopReason::SyscallExit { number, retval } = &reason {
let action =
self.bypass_engine
.on_syscall_exit(*number, *retval, &mut self.event_log);
match action {
BypassAction::FakeReturnValue(fake_val) => {
if let Ok(mut regs) = self.read_registers() {
let _ = regs.set("rax", fake_val as u64);
let _ = self.write_registers(®s);
}
}
BypassAction::SpoofTracerPid => {
if let Ok(regs) = self.read_registers() {
let buf_addr = regs.get("rsi").unwrap_or(0);
let count = regs.get("rax").unwrap_or(0) as usize;
if buf_addr != 0 && count > 0 {
if let Ok(buf) =
self.process.read_memory(VirtAddr(buf_addr), count)
{
let spoofed = BypassEngine::spoof_tracer_pid(&buf);
let _ = self
.process
.write_memory(VirtAddr(buf_addr), &spoofed);
}
}
}
}
_ => {}
}
if self.secret_scanner.should_trigger_on_syscall(*number) {
self.run_secret_scan();
}
}
match &reason {
StopReason::SyscallEntry { number, .. }
| StopReason::SyscallExit { number, .. } => {
if !self.catch_all_syscalls && !self.caught_syscalls.contains(number) {
continue; }
}
_ => {}
}
if let StopReason::BreakpointHit { addr } = &reason {
if let Some(site) = self.breakpoints.get_at(*addr) {
if let Some(cond) = site.condition() {
if !self.evaluate_condition(cond) {
self.breakpoints.step_over_breakpoint(&mut self.process, *addr)?;
continue;
}
}
}
}
if let StopReason::BreakpointHit { addr } = &reason {
if self.bypass_engine.should_skip_int3()
&& self.breakpoints.get_at(*addr).is_none()
{
self.bypass_engine
.record_int3_skip(*addr, &mut self.event_log);
continue;
}
}
return Ok(reason);
}
}
pub fn step_instruction(&mut self) -> Result<StopReason> {
let pc = VirtAddr(self.read_registers()?.pc());
if self.breakpoints.get_at(pc).is_some() {
self.breakpoints.step_over_breakpoint(&mut self.process, pc)?;
return Ok(StopReason::SingleStep);
}
self.process.step_instruction()?;
let reason = self.process.wait_on_signal()?;
self.handle_stop(&reason)?;
self.log_stop_reason(&reason);
Ok(reason)
}
pub fn step_in(&mut self) -> Result<StopReason> {
let start_loc = self.source_location()?;
loop {
let reason = self.step_instruction()?;
match &reason {
StopReason::SingleStep => {
let current_loc = self.source_location()?;
if start_loc.is_none() || current_loc != start_loc {
return Ok(reason);
}
}
_ => return Ok(reason),
}
}
}
pub fn step_over(&mut self) -> Result<StopReason> {
let start_loc = self.source_location()?;
loop {
let pc = VirtAddr(self.read_registers()?.pc());
let code = self.read_memory(pc, 15)?;
if let Some(info) = disasm::decode_instruction_info(&code, pc) {
if info.is_call {
let return_pc = VirtAddr(pc.addr() + info.len as u64);
let already_has_bp = self.breakpoints.get_at(return_pc).is_some();
if !already_has_bp {
self.set_breakpoint(return_pc)?;
}
let result = self.resume();
if !already_has_bp {
let _ = self.remove_breakpoint(return_pc);
}
let reason = result?;
match &reason {
StopReason::BreakpointHit { addr } if *addr == return_pc => {
let current_loc = self.source_location()?;
if start_loc.is_none() || current_loc != start_loc {
return Ok(reason);
}
continue;
}
_ => return Ok(reason),
}
}
}
let reason = self.step_instruction()?;
match &reason {
StopReason::SingleStep => {
let current_loc = self.source_location()?;
if start_loc.is_none() || current_loc != start_loc {
return Ok(reason);
}
}
_ => return Ok(reason),
}
}
}
pub fn step_out(&mut self) -> Result<StopReason> {
let regs = self.read_registers()?;
let pc = regs.pc();
let ret_addr = if let Some(unwinder) = &self.unwinder {
let dwarf_regs = self.dwarf_register_snapshot(®s);
unwinder.return_address(
pc,
&dwarf_regs,
&|addr, len| self.process.read_memory(VirtAddr(addr), len),
)?
} else {
None
};
let ret_addr = match ret_addr {
Some(addr) => VirtAddr(addr),
None => {
let rbp = regs.get("rbp")?;
if rbp == 0 {
return Err(Error::Other(
"cannot step out: no CFI data and no frame pointer".into(),
));
}
let bytes = self.read_memory(VirtAddr(rbp + 8), 8)?;
VirtAddr(u64::from_le_bytes(bytes[..8].try_into().unwrap()))
}
};
let already_has_bp = self.breakpoints.get_at(ret_addr).is_some();
if !already_has_bp {
self.set_breakpoint(ret_addr)?;
}
let result = self.resume();
if !already_has_bp {
let _ = self.remove_breakpoint(ret_addr);
}
Ok(result?)
}
pub fn backtrace(&self) -> Result<Vec<BacktraceFrame>> {
let regs = self.read_registers()?;
let pc = regs.pc();
let unwinder = self
.unwinder
.as_ref()
.ok_or_else(|| Error::Other("no unwind info available".into()))?;
let dwarf_regs = self.dwarf_register_snapshot(®s);
let raw_frames = unwinder.walk_stack(
pc,
&dwarf_regs,
&|addr, len| self.process.read_memory(VirtAddr(addr), len),
)?;
let mut bt = Vec::with_capacity(raw_frames.len());
for (i, frame) in raw_frames.iter().enumerate() {
let function = self
.elf
.find_symbol_at(frame.pc)
.map(|s| rust_type::demangle_symbol(&s.name))
.or_else(|| {
self.dwarf
.as_ref()
.and_then(|d| d.find_function(frame.pc).ok().flatten())
.map(|name| rust_type::demangle_symbol(&name))
});
let location = self
.dwarf
.as_ref()
.and_then(|d| d.find_location(frame.pc).ok().flatten());
bt.push(BacktraceFrame {
index: i,
pc: frame.pc,
function,
location,
});
}
Ok(bt)
}
pub fn set_breakpoint(&mut self, addr: VirtAddr) -> Result<u32> {
self.breakpoints.set(&self.process, addr)
}
pub fn set_conditional_breakpoint(
&mut self,
addr: VirtAddr,
condition: String,
) -> Result<u32> {
self.breakpoints
.set_with_condition(&self.process, addr, Some(condition))
}
pub fn remove_breakpoint(&mut self, addr: VirtAddr) -> Result<()> {
self.breakpoints.remove(&self.process, addr)
}
pub fn list_breakpoints(&self) -> Vec<(VirtAddr, Option<&str>)> {
self.breakpoints
.list()
.map(|s| (s.addr(), s.condition()))
.collect()
}
pub fn set_watchpoint(
&mut self,
addr: VirtAddr,
wp_type: WatchpointType,
size: WatchpointSize,
) -> Result<u32> {
self.watchpoints
.set(self.process.pid(), addr, wp_type, size)
}
pub fn remove_watchpoint(&mut self, id: u32) -> Result<()> {
self.watchpoints.remove(self.process.pid(), id)
}
pub fn list_watchpoints(&self) -> Vec<&Watchpoint> {
self.watchpoints.list()
}
pub fn read_registers(&self) -> Result<Registers> {
Registers::read(self.process.current_tid())
}
pub fn write_registers(&self, regs: &Registers) -> Result<()> {
regs.write(self.process.current_tid())
}
pub fn read_memory(&self, addr: VirtAddr, len: usize) -> Result<Vec<u8>> {
self.process.read_memory(addr, len)
}
pub fn write_memory(&self, addr: VirtAddr, data: &[u8]) -> Result<()> {
self.process.write_memory(addr, data)
}
pub fn memory_maps(&self) -> Result<Vec<MemoryRegion>> {
procfs::read_memory_maps(self.process.pid())
}
pub fn checksec(&self) -> Result<ChecksecResult> {
checksec::checksec(Path::new(&self.program_path))
}
pub fn got_plt_entries(&self) -> Result<Vec<GotPltEntry>> {
let data = std::fs::read(&self.program_path)
.map_err(|e| Error::Other(format!("read binary: {}", e)))?;
elf::parse_got_plt(&data)
}
pub fn got_dyn_entries(&self) -> Result<Vec<GotPltEntry>> {
let data = std::fs::read(&self.program_path)
.map_err(|e| Error::Other(format!("read binary: {}", e)))?;
elf::parse_got_dyn(&data)
}
pub fn read_got_value(&self, got_addr: u64) -> Result<u64> {
let bytes = self.process.read_memory(VirtAddr(got_addr), 8)?;
Ok(u64::from_le_bytes(bytes[..8].try_into().unwrap()))
}
pub fn extract_strings(&self, min_length: usize) -> Result<Vec<ExtractedString>> {
strings::extract_strings(Path::new(&self.program_path), min_length)
}
pub fn find_gadgets(&self, max_depth: usize) -> Result<Vec<Gadget>> {
rop::find_gadgets(Path::new(&self.program_path), max_depth)
}
pub fn section_entropy(&self) -> Result<Vec<SectionEntropy>> {
entropy::analyze_sections(Path::new(&self.program_path))
}
pub fn patch_file(&self, vaddr: u64, patch_bytes: &[u8]) -> Result<PatchResult> {
patch::patch_file(Path::new(&self.program_path), vaddr, patch_bytes)
}
pub fn antidebug_scan(&self) -> Result<Vec<AntiDebugFinding>> {
antidebug::scan(Path::new(&self.program_path))
}
pub fn bypass_config(&self) -> &BypassConfig {
&self.bypass_config
}
pub fn set_bypass_config(&mut self, config: BypassConfig) {
self.bypass_config = config;
}
pub fn heap_chunks(&self, max_chunks: usize) -> Result<Vec<crate::heap::MallocChunk>> {
let maps = self.memory_maps()?;
let heap_region = maps
.iter()
.find(|m| m.pathname == "[heap]")
.ok_or_else(|| Error::Other("no [heap] region found in memory maps".into()))?;
let read_mem = |addr: u64, len: usize| -> Result<Vec<u8>> {
self.process.read_memory(VirtAddr(addr), len)
};
crate::heap::walk_chunks(heap_region.start.addr(), 0, &read_mem, max_chunks)
}
pub fn generate_coredump(&self) -> Result<Vec<u8>> {
let regs = self.read_registers()?;
let maps = self.memory_maps()?;
let reg_names = [
"r15", "r14", "r13", "r12", "rbp", "rbx", "r11", "r10",
"r9", "r8", "rax", "rcx", "rdx", "rsi", "rdi", "orig_rax",
"rip", "cs", "eflags", "rsp", "ss", "fs_base", "gs_base",
"ds", "es", "fs", "gs",
];
let mut reg_vals = Vec::new();
for name in ®_names {
reg_vals.push(regs.get(name).unwrap_or(0));
}
let mut mappings = Vec::new();
for region in &maps {
if !region.perms.read {
continue;
}
let size = (region.end.addr() - region.start.addr()) as usize;
if size > 64 * 1024 * 1024 {
continue;
}
let data = match self.process.read_memory(region.start, size) {
Ok(d) => d,
Err(_) => continue, };
let flags = {
let mut f = 0u32;
if region.perms.read { f |= crate::coredump::PF_R; }
if region.perms.write { f |= crate::coredump::PF_W; }
if region.perms.execute { f |= crate::coredump::PF_X; }
f
};
mappings.push(crate::coredump::CoreMapping {
start: region.start.addr(),
end: region.end.addr(),
flags,
data,
pathname: region.pathname.clone(),
file_offset: 0,
});
}
let info = crate::coredump::CoreDumpInfo {
registers: reg_vals,
signal: 0,
pid: self.process.pid().as_raw() as u32,
mappings,
auxv: Vec::new(), };
crate::coredump::generate(&info)
}
pub fn scan_hex_pattern(&self, hex_pattern: &str) -> Result<Vec<ScanMatch>> {
let (pattern, mask) = memscan::parse_hex_pattern(hex_pattern)?;
let maps = self.memory_maps()?;
let regions: Vec<(u64, u64)> = maps
.iter()
.filter(|m| m.perms.read)
.map(|m| (m.start.addr(), m.end.addr()))
.collect();
let read_mem = |addr: u64, len: usize| -> Result<Vec<u8>> {
self.process.read_memory(VirtAddr(addr), len)
};
memscan::scan_regions(®ions, &pattern, &mask, &read_mem)
}
pub fn scan_bytes(&self, needle: &[u8]) -> Result<Vec<ScanMatch>> {
let mask = vec![true; needle.len()];
let maps = self.memory_maps()?;
let regions: Vec<(u64, u64)> = maps
.iter()
.filter(|m| m.perms.read)
.map(|m| (m.start.addr(), m.end.addr()))
.collect();
let read_mem = |addr: u64, len: usize| -> Result<Vec<u8>> {
self.process.read_memory(VirtAddr(addr), len)
};
memscan::scan_regions(®ions, needle, &mask, &read_mem)
}
pub fn install_got_hook(&mut self, function_name: &str, hook_target: u64) -> Result<usize> {
let entries = self.got_plt_entries()?;
let entry = entries
.iter()
.find(|e| e.name == function_name)
.ok_or_else(|| Error::Other(format!("GOT entry '{}' not found", function_name)))?;
let original = self.read_got_value(entry.got_addr)?;
self.process.write_memory(
VirtAddr(entry.got_addr),
&hook_target.to_le_bytes(),
)?;
let idx = self.got_hooks.record_hook(
function_name.to_string(),
VirtAddr(entry.got_addr),
original,
hook_target,
);
Ok(idx)
}
pub fn remove_got_hook(&mut self, function_name: &str) -> Result<()> {
let hook = self
.got_hooks
.get_hook(function_name)
.ok_or_else(|| Error::Other(format!("no active hook for '{}'", function_name)))?;
let got_addr = hook.got_address;
let original = hook.original_target;
self.process.write_memory(got_addr, &original.to_le_bytes())?;
self.got_hooks.deactivate_hook(function_name);
Ok(())
}
pub fn list_got_hooks(&self) -> Vec<&crate::got_hook::GotHook> {
self.got_hooks.active_hooks()
}
pub fn shared_libraries(&self) -> Result<Vec<SharedLibrary>> {
shared_lib::read_shared_libraries(&self.process)
}
pub fn disassemble(
&self,
addr: VirtAddr,
count: usize,
style: DisasmStyle,
) -> Result<Vec<DisasmInstruction>> {
let read_len = count * 15;
let code = self.process.read_memory(addr, read_len)?;
Ok(disasm::disassemble(&code, addr, count, style))
}
pub fn disassemble_at_pc(
&self,
count: usize,
style: DisasmStyle,
) -> Result<Vec<DisasmInstruction>> {
let regs = self.read_registers()?;
let pc = VirtAddr(regs.pc());
self.disassemble(pc, count, style)
}
pub fn source_location(&self) -> Result<Option<SourceLocation>> {
let pc = VirtAddr(self.read_registers()?.pc());
match &self.dwarf {
Some(dwarf) => dwarf.find_location(pc),
None => Ok(None),
}
}
pub fn current_function(&self) -> Result<Option<String>> {
let pc = VirtAddr(self.read_registers()?.pc());
if let Some(sym) = self.elf.find_symbol_at(pc) {
return Ok(Some(rust_type::demangle_symbol(&sym.name)));
}
if let Some(dwarf) = &self.dwarf {
return Ok(dwarf.find_function(pc)?.map(|n| rust_type::demangle_symbol(&n)));
}
Ok(None)
}
pub fn find_symbol(&self, name: &str) -> Option<VirtAddr> {
self.elf.find_symbol(name).map(|s| s.addr)
}
pub fn has_debug_info(&self) -> bool {
self.dwarf.is_some()
}
pub fn read_variable(&self, name: &str) -> Result<Option<(Variable, String)>> {
let var_reader = self
.var_reader
.as_ref()
.ok_or_else(|| Error::Other("no DWARF variable info".into()))?;
let regs = self.read_registers()?;
let pc = regs.pc();
let var = match var_reader.find_variable(pc, name)? {
Some(v) => v,
None => return Ok(None),
};
let encoding = var_reader.encoding_for_pc(pc)?;
let dwarf_regs = self.dwarf_register_snapshot(®s);
let ctx = crate::dwarf_expr::EvalContext {
registers: &dwarf_regs,
cfa: 0, frame_base: Some(regs.get("rbp").unwrap_or(0)),
read_memory: &|addr, len| self.process.read_memory(VirtAddr(addr), len),
};
let result =
crate::dwarf_expr::evaluate(&var.location_expr, encoding, &ctx)?;
let read_mem = |addr: u64, len: usize| self.process.read_memory(VirtAddr(addr), len);
let formatted = match &result {
crate::dwarf_expr::ExprResult::Address(addr) => {
let data = self
.process
.read_memory(VirtAddr(*addr), var.type_info.byte_size as usize)?;
rust_type::format_rust_value(&data, &var.type_info, &read_mem)
.unwrap_or_else(|| variables::format_value(&data, &var.type_info))
}
crate::dwarf_expr::ExprResult::Register(reg) => {
let val = dwarf_regs
.iter()
.find(|(r, _)| *r == *reg)
.map(|(_, v)| *v)
.unwrap_or(0);
let data = val.to_le_bytes();
rust_type::format_rust_value(&data, &var.type_info, &read_mem)
.unwrap_or_else(|| variables::format_value(&data, &var.type_info))
}
crate::dwarf_expr::ExprResult::Constant(val) => {
let data = val.to_le_bytes();
rust_type::format_rust_value(&data, &var.type_info, &read_mem)
.unwrap_or_else(|| variables::format_value(&data, &var.type_info))
}
crate::dwarf_expr::ExprResult::OptimizedOut => "<optimized out>".into(),
crate::dwarf_expr::ExprResult::Pieces(_) => "<multi-piece>".into(),
};
Ok(Some((var, formatted)))
}
pub fn read_variable_data(&self, name: &str) -> Result<Option<(Variable, Vec<u8>)>> {
let var_reader = self
.var_reader
.as_ref()
.ok_or_else(|| Error::Other("no DWARF variable info".into()))?;
let regs = self.read_registers()?;
let pc = regs.pc();
let var = match var_reader.find_variable(pc, name)? {
Some(v) => v,
None => return Ok(None),
};
let encoding = var_reader.encoding_for_pc(pc)?;
let dwarf_regs = self.dwarf_register_snapshot(®s);
let ctx = crate::dwarf_expr::EvalContext {
registers: &dwarf_regs,
cfa: 0,
frame_base: Some(regs.get("rbp").unwrap_or(0)),
read_memory: &|addr, len| self.process.read_memory(VirtAddr(addr), len),
};
let result =
crate::dwarf_expr::evaluate(&var.location_expr, encoding, &ctx)?;
let data = match &result {
crate::dwarf_expr::ExprResult::Address(addr) => {
self.process
.read_memory(VirtAddr(*addr), var.type_info.byte_size as usize)?
}
crate::dwarf_expr::ExprResult::Register(reg) => {
let val = dwarf_regs
.iter()
.find(|(r, _)| *r == *reg)
.map(|(_, v)| *v)
.unwrap_or(0);
val.to_le_bytes().to_vec()
}
crate::dwarf_expr::ExprResult::Constant(val) => {
val.to_le_bytes().to_vec()
}
crate::dwarf_expr::ExprResult::OptimizedOut => {
return Err(Error::Other("variable optimized out".into()));
}
crate::dwarf_expr::ExprResult::Pieces(_) => {
return Err(Error::Other("multi-piece variable not supported".into()));
}
};
Ok(Some((var, data)))
}
pub fn list_variables(&self) -> Result<Vec<Variable>> {
let var_reader = self
.var_reader
.as_ref()
.ok_or_else(|| Error::Other("no DWARF variable info".into()))?;
let pc = self.read_registers()?.pc();
var_reader.find_variables_at(pc)
}
pub fn thread_list(&self) -> &[nix::unistd::Pid] {
self.process.thread_list()
}
pub fn current_tid(&self) -> nix::unistd::Pid {
self.process.current_tid()
}
pub fn switch_thread(&mut self, tid: nix::unistd::Pid) -> Result<()> {
self.process.set_current_tid(tid)
}
pub fn state(&self) -> ProcessState {
self.process.state()
}
pub fn pid(&self) -> nix::unistd::Pid {
self.process.pid()
}
pub fn program_path(&self) -> &str {
&self.program_path
}
pub fn elf(&self) -> &ElfFile {
&self.elf
}
fn handle_stop(&mut self, reason: &StopReason) -> Result<()> {
match reason {
StopReason::BreakpointHit { addr } => {
let mut regs = self.read_registers()?;
regs.set_pc(addr.addr());
self.write_registers(®s)?;
}
StopReason::Signal(sig) => {
let policy = self.signal_policy(*sig);
if policy.pass {
self.pending_signal = Some(*sig);
}
}
_ => {}
}
Ok(())
}
pub fn signal_policy(&self, sig: Signal) -> SignalPolicy {
self.signal_policies
.get(&sig)
.copied()
.unwrap_or_default()
}
pub fn set_signal_policy(&mut self, sig: Signal, policy: SignalPolicy) {
self.signal_policies.insert(sig, policy);
}
pub fn catch_syscall(&mut self, number: u64) {
self.caught_syscalls.insert(number);
}
pub fn uncatch_syscall(&mut self, number: u64) {
self.caught_syscalls.remove(&number);
}
pub fn set_catch_all_syscalls(&mut self, enable: bool) {
self.catch_all_syscalls = enable;
}
pub fn is_catching_syscalls(&self) -> bool {
self.catch_all_syscalls || !self.caught_syscalls.is_empty()
}
pub fn caught_syscalls(&self) -> &HashSet<u64> {
&self.caught_syscalls
}
fn evaluate_condition(&self, cond: &str) -> bool {
let expr = match crate::expr_eval::parse(cond) {
Ok(e) => e,
Err(_) => return true, };
match &expr {
crate::expr_eval::Expr::IntLit(val) => *val != 0,
crate::expr_eval::Expr::Variable(name) => {
if let Ok(regs) = self.read_registers() {
if let Ok(val) = regs.get(name) {
return val != 0;
}
}
match self.read_variable(name) {
Ok(Some((_, formatted))) => {
formatted.trim() != "0" && formatted.trim() != "false"
}
_ => true, }
}
_ => true, }
}
fn dwarf_register_snapshot(&self, regs: &Registers) -> Vec<(u16, u64)> {
registers::REGISTERS
.iter()
.filter(|r| r.dwarf_id >= 0)
.filter_map(|r| regs.get(r.name).ok().map(|v| (r.dwarf_id as u16, v)))
.collect()
}
fn log_stop_reason(&mut self, reason: &StopReason) {
let kind = match reason {
StopReason::SyscallEntry { number, args } => {
let read_string = |addr: u64| -> Result<String> {
let bytes = self.process.read_memory(VirtAddr(addr), 256)?;
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
};
let name = crate::syscall::name(*number)
.unwrap_or("unknown")
.to_string();
let args_formatted =
syscall_trace::format_syscall_entry(*number, args, &read_string);
EventKind::SyscallEntry {
number: *number,
name,
args_formatted,
}
}
StopReason::SyscallExit { number, retval } => {
let name = crate::syscall::name(*number)
.unwrap_or("unknown")
.to_string();
let retval_formatted =
syscall_trace::format_syscall_return(*number, *retval);
EventKind::SyscallExit {
number: *number,
name,
retval: *retval,
retval_formatted,
}
}
StopReason::Signal(sig) => EventKind::Signal {
signal: format!("{}", sig),
addr: self
.read_registers()
.ok()
.map(|r| VirtAddr(r.pc())),
},
StopReason::BreakpointHit { addr } => {
let function = self.current_function().ok().flatten();
let location = self
.source_location()
.ok()
.flatten()
.map(|loc| format!("{}:{}", loc.file, loc.line));
EventKind::BreakpointHit {
addr: *addr,
function,
location,
}
}
StopReason::WatchpointHit { addr, .. } => EventKind::BreakpointHit {
addr: *addr,
function: None,
location: Some("watchpoint".into()),
},
StopReason::Exited(code) => EventKind::ProcessExited { code: *code },
StopReason::Terminated(sig) => EventKind::ProcessTerminated {
signal: format!("{}", sig),
},
StopReason::ThreadCreated(pid) => EventKind::ThreadCreated {
tid: pid.as_raw(),
},
StopReason::ThreadExited(pid) => EventKind::ThreadExited {
tid: pid.as_raw(),
},
StopReason::SingleStep => return, };
self.event_log.record(kind);
}
pub fn event_log(&self) -> &EventLog {
&self.event_log
}
pub fn event_log_mut(&mut self) -> &mut EventLog {
&mut self.event_log
}
pub fn bypass_engine(&self) -> &BypassEngine {
&self.bypass_engine
}
pub fn bypass_engine_mut(&mut self) -> &mut BypassEngine {
&mut self.bypass_engine
}
pub fn secret_scanner(&self) -> &SecretScanner {
&self.secret_scanner
}
pub fn secret_scanner_mut(&mut self) -> &mut SecretScanner {
&mut self.secret_scanner
}
pub fn run_secret_scan(&mut self) -> Vec<crate::secret_scan::SecretFinding> {
let maps = match self.memory_maps() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
let regions: Vec<(u64, u64, bool)> = maps
.iter()
.filter(|m| m.perms.read)
.map(|m| (m.start.addr(), m.end.addr(), m.perms.write))
.collect();
let process = &self.process;
let read_mem = |addr: u64, len: usize| -> Option<Vec<u8>> {
process.read_memory(VirtAddr(addr), len).ok()
};
self.secret_scanner
.scan_regions(®ions, &read_mem, &mut self.event_log)
}
}