0% found this document useful (0 votes)
6 views

Message

This Rust code defines a SignatureScanner to find patterns or symbols within shared libraries. It represents signatures as byte patterns or symbol names. It scans libraries linearly to find matches, returning an error if multiple matches are found. Signatures are parsed from strings and the scanner drops and closes the library handle on drop.

Uploaded by

Devs Rank
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Message

This Rust code defines a SignatureScanner to find patterns or symbols within shared libraries. It represents signatures as byte patterns or symbol names. It scans libraries linearly to find matches, returning an error if multiple matches are found. Signatures are parsed from strings and the scanner drops and closes the library handle on drop.

Uploaded by

Devs Rank
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 5

#!

[allow(dead_code)]
use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use thiserror::Error;

type Result<T> = std::result::Result<T, SignatureScannerError>;

#[derive(Error, Debug)]
pub enum SignatureScannerError {
#[error("library matching the address {0:?} not found")]
LibraryNotFound(*const ()),

#[error("no file status")]


NoFileStatus,

#[error("couldn't open the library: {0}")]


LibraryLoadFailed(String),

#[error(transparent)]
NulError(#[from] std::ffi::NulError),

#[error("found no matches for \"{0}\"")]


NoMatches(String),

#[error("found multiple matches for \"{0}\"")]


MultipleMatches(String),
}

#[derive(Error, Debug)]
pub enum PatternError {
#[error("string is empty")]
EmptyString,

#[error("symbol is empty")]
EmptySymbol,

#[error("unknown character '{0}' found at position {1}")]


UnknownChar(char, usize),

#[error("unexpected EOS")]
UnexpectedEos,

#[error("unpaired hex digit at position {0}")]


UnpairedHexDigit(usize),
}

#[derive(Debug, Clone, Copy)]


pub enum Unit {
Byte(u8),
Skip(u8),
}

impl std::fmt::Display for Unit {


fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Unit::Byte(value) => write!(f, "{:x}", value),
Unit::Skip(count) => {
write!(f, "{}", (0..*count).map(|_| "?").collect::<Vec<_>>().join("
"))
}
}
}
}

#[derive(Debug, Clone)]
pub enum Signature {
Pattern(Vec<Unit>),
Symbol(String),
}

impl std::fmt::Display for Signature {


fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Signature::Pattern(pattern) => write!(f, "{}",
pattern_to_string(pattern)),
Signature::Symbol(symbol) => write!(f, "{}", symbol_to_string(symbol)),
}
}
}

fn pattern_to_string(pattern: &[Unit]) -> String {


pattern.iter().map(|unit| format!("{}", unit)).collect::<Vec<_>>().join(" ")
}

fn symbol_to_string(symbol: &str) -> String {


format!("@{}", symbol)
}

impl std::str::FromStr for Signature {


type Err = PatternError;

fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {


if value.is_empty() {
return Err(PatternError::EmptyString);
}

// It's a symbol if it starts with '@'


if value.starts_with('@') {
let symbol = &value[1..];
if symbol.is_empty() {
return Err(PatternError::EmptySymbol);
}

return Ok(Signature::Symbol(symbol.to_string()));
}

// Else, it's a pattern


let mut chars = value.chars().enumerate();
let mut pattern = Vec::new();
while let Some((index, c)) = chars.next() {
match c {
'0'..='9' | 'A'..='F' | 'a'..='f' => {
if let Some((index2, c2)) = chars.next() {
match u8::from_str_radix(&format!("{}{}", c, c2), 16) {
Ok(value) => pattern.push(Unit::Byte(value)),
Err(_) => return
Err(PatternError::UnpairedHexDigit(index2)),
}
} else {
return Err(PatternError::UnexpectedEos);
}
}
'?' => {
if let Some(Unit::Skip(count)) = pattern.last_mut() {
if *count < 255u8 {
*count += 1;
}
} else {
pattern.push(Unit::Skip(1));
}
}
' ' => {}
_ => return Err(PatternError::UnknownChar(c, index)),
}
}

Ok(Signature::Pattern(pattern))
}
}

pub struct SignatureScanner {


lib_handle: *mut c_void,
lib_start_addr: *const u8,
lib_end_addr: *const u8,
}

impl SignatureScanner {
pub fn new(addr: *const ()) -> Result<Self> {
let info = lib_info(addr)?;
let (base_addr, lib_len) = lib_base_and_len(&info)?;
let end_addr = unsafe { base_addr.add(lib_len) };
let handle = lib_handle(&info)?;

Ok(Self { lib_handle: handle, lib_start_addr: base_addr, lib_end_addr:


end_addr })
}

pub fn find(&self, signature: &Signature) -> Result<*const ()> {


match signature {
Signature::Pattern(pattern) => {
find_pattern(self.lib_start_addr, self.lib_end_addr, pattern)
}
Signature::Symbol(symbol) => find_symbol(self.lib_handle, symbol),
}
}
}

impl Drop for SignatureScanner {


fn drop(&mut self) {
unsafe { libc::dlclose(self.lib_handle) };
}
}

fn lib_info(addr: *const ()) -> Result<libc::Dl_info> {


let mut info = MaybeUninit::<libc::Dl_info>::uninit();
if unsafe { libc::dladdr(addr as *const _, info.as_mut_ptr()) } == 0 {
return Err(SignatureScannerError::LibraryNotFound(addr));
}

let info = unsafe { info.assume_init() };

if info.dli_fbase.is_null() || info.dli_fname.is_null() {
return Err(SignatureScannerError::LibraryNotFound(addr));
}

Ok(info)
}

fn lib_base_and_len(info: &libc::Dl_info) -> Result<(*const u8, usize)> {


let mut stat_buf = MaybeUninit::<libc::stat>::uninit();

if unsafe { libc::stat(info.dli_fname, stat_buf.as_mut_ptr()) } != 0 {


return Err(SignatureScannerError::NoFileStatus);
}

let stat_buf = unsafe { stat_buf.assume_init() };

Ok((info.dli_fbase as *const _, stat_buf.st_size as usize))


}

fn lib_handle(info: &libc::Dl_info) -> Result<*mut c_void> {


let handle = unsafe { libc::dlopen(info.dli_fname, libc::RTLD_NOW) };
if handle.is_null() {
let error = unsafe { libc::dlerror() };
let error = unsafe { CStr::from_ptr(error) };
let error = error.to_string_lossy().to_string();
Err(SignatureScannerError::LibraryLoadFailed(error))
} else {
Ok(handle)
}
}

fn find_pattern(start_addr: *const u8, end_addr: *const u8, pattern: &[Unit]) ->


Result<*const ()> {
let mut ptr = start_addr;
let mut matched_addr: *const u8 = std::ptr::null();

while ptr < end_addr {


if pattern_matches(ptr, end_addr, pattern)? {
if !matched_addr.is_null() {
return
Err(SignatureScannerError::MultipleMatches(pattern_to_string(pattern)));
}

matched_addr = ptr;
}

ptr = unsafe { ptr.add(1) };


}

if matched_addr.is_null() {
Err(SignatureScannerError::NoMatches(pattern_to_string(pattern)))
} else {
Ok(matched_addr as *const _)
}
}

fn pattern_matches(mut ptr: *const u8, end_addr: *const u8, pattern: &[Unit]) ->
Result<bool> {
for unit in pattern {
if ptr >= end_addr {
return
Err(SignatureScannerError::NoMatches(pattern_to_string(pattern)));
}

let byte = unsafe { *ptr };


match unit {
Unit::Byte(value) => {
if byte == *value {
ptr = unsafe { ptr.add(1) }
} else {
return Ok(false);
}
}
Unit::Skip(count) => ptr = unsafe { ptr.add(*count as usize) },
}
}

// Reached the end of the pattern - success


Ok(true)
}

fn find_symbol(lib_handle: *mut c_void, symbol: &str) -> Result<*const ()> {


let symbol_cstr = CString::new(symbol)?;
let symbol_ptr = unsafe { libc::dlsym(lib_handle, symbol_cstr.as_ptr()) };

if symbol_ptr.is_null() {
Err(SignatureScannerError::NoMatches(symbol_to_string(symbol)))
} else {
Ok(symbol_ptr as *const _)
}
}

You might also like