mirror of
https://github.com/EgeBalci/deoptimizer
synced 2026-06-08 10:55:50 +00:00
Several tracer and disassembler improvments.
This commit is contained in:
+4
-5
@@ -1,3 +1,5 @@
|
||||
// use crate::x86_64::disassembler;
|
||||
use crate::x86_64::disassembler::disassemble;
|
||||
use base64::prelude::*;
|
||||
use colored::Colorize;
|
||||
use log::{error, info, warn, LevelFilter};
|
||||
@@ -53,16 +55,13 @@ fn main() {
|
||||
info!("Input file size: {}", file.len());
|
||||
let mut deopt = x86_64::Deoptimizer::new();
|
||||
deopt.freq = opts.freq;
|
||||
deopt.trace = opts.trace;
|
||||
deopt.allow_invalid = opts.allow_invalid;
|
||||
deopt.set_skipped_offsets(opts.skip_offsets);
|
||||
if let Err(e) = deopt.set_transform_gadgets(opts.transforms) {
|
||||
error!("{}", e);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = deopt.set_syntax(opts.syntax) {
|
||||
error!("{}", e);
|
||||
return;
|
||||
}
|
||||
let start_addr = match u64::from_str_radix(opts.addr.trim_start_matches("0x"), 16) {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
@@ -103,7 +102,7 @@ fn main() {
|
||||
}
|
||||
info!("De-optimized binary written into {}", opts.outfile);
|
||||
} else {
|
||||
let source = match deopt.disassemble(opts.bitness, start_addr, output) {
|
||||
let source = match disassemble(&output, opts.bitness, start_addr, opts.syntax) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
|
||||
+36
-16
@@ -17,6 +17,8 @@ pub enum ArgParseError {
|
||||
FileNotFound,
|
||||
#[error("Invalid offset values.")]
|
||||
InvalidOffsetValues,
|
||||
#[error("Invalid formatter syntax.")]
|
||||
InvalidSyntax,
|
||||
#[error("Address parsing failed: {0}")]
|
||||
AddressParseError(#[from] FromHexError),
|
||||
#[error("Integer parsing failed: {0}")]
|
||||
@@ -35,55 +37,59 @@ pub struct Options {
|
||||
#[arg(long, short = 'a', default_value_t = String::from("x86"))]
|
||||
pub arch: String,
|
||||
|
||||
/// target binary file name.
|
||||
/// Target binary file name.
|
||||
#[arg(long, short = 'f', default_value_t = String::new())]
|
||||
pub file: String,
|
||||
|
||||
/// output file name.
|
||||
/// Output file name.
|
||||
#[arg(long, short = 'o', default_value_t = String::new())]
|
||||
pub outfile: String,
|
||||
|
||||
/// source assembly file.
|
||||
/// Source assembly file.
|
||||
#[arg(long, short = 's', default_value_t = String::new())]
|
||||
pub source: String,
|
||||
|
||||
/// assembler formatter syntax (nasm/masm/intel/gas).
|
||||
/// Assembler formatter syntax (nasm/masm/intel/gas).
|
||||
#[arg(long, default_value_t = String::from("keystone"))]
|
||||
pub syntax: String,
|
||||
|
||||
/// bitness of the binary file (16/32/64).
|
||||
/// Bitness of the binary file (16/32/64).
|
||||
#[arg(long, short = 'b', default_value_t = 64)]
|
||||
pub bitness: u32,
|
||||
|
||||
/// start address in hexadecimal form.
|
||||
/// Start address in hexadecimal form.
|
||||
#[arg(long, short = 'A', default_value_t = String::from("0x0000000000000000"))]
|
||||
pub addr: String,
|
||||
|
||||
/// File offset range for skipping deoptimization (eg: 0-10 for skipping first ten bytes).
|
||||
#[arg(long, value_parser=parse_offset, num_args = 1.., value_delimiter = ',')]
|
||||
pub skip_offsets: Vec<(u32, u32)>,
|
||||
pub skip_offsets: Vec<(u64, u64)>,
|
||||
|
||||
/// total number of deoptimization cycles.
|
||||
/// Auto-skip dead-code and strings by control flow tracing.
|
||||
#[arg(long, short = 'T')]
|
||||
pub trace: bool,
|
||||
|
||||
/// Total number of deoptimization cycles.
|
||||
#[arg(long, short = 'c', default_value_t = 1)]
|
||||
pub cycle: u32,
|
||||
|
||||
/// deoptimization frequency.
|
||||
/// Deoptimization frequency.
|
||||
#[arg(long, short = 'F', default_value_t = 0.5)]
|
||||
pub freq: f32,
|
||||
|
||||
/// allowed transform routines (ap/li/lp/om/rs).
|
||||
/// Allowed transform routines (ap/li/lp/om/rs).
|
||||
#[arg(long, default_value_t = String::from("ap,li,lp,om,rs"))]
|
||||
pub transforms: String,
|
||||
|
||||
/// allow processing of invalid instructions.
|
||||
/// Allow processing of invalid instructions.
|
||||
#[arg(long)]
|
||||
pub allow_invalid: bool,
|
||||
|
||||
/// verbose output mode.
|
||||
/// Verbose output mode.
|
||||
#[arg(long, short = 'v')]
|
||||
pub verbose: bool,
|
||||
|
||||
/// debug output mode.
|
||||
/// Debug output mode.
|
||||
#[arg(long)]
|
||||
pub debug: bool,
|
||||
}
|
||||
@@ -106,6 +112,13 @@ pub fn parse_options() -> Result<Options, ArgParseError> {
|
||||
opts.outfile = opts.source.clone();
|
||||
}
|
||||
|
||||
if !matches!(
|
||||
opts.syntax.to_uppercase().as_str(),
|
||||
"KEYSTONE" | "NASM" | "MASM" | "INTEL" | "GAX"
|
||||
) {
|
||||
return Err(ArgParseError::InvalidSyntax);
|
||||
}
|
||||
|
||||
if opts.outfile.is_empty() {
|
||||
opts.outfile = format!("{}_deopt.bin", opts.file);
|
||||
}
|
||||
@@ -119,14 +132,14 @@ pub fn parse_options() -> Result<Options, ArgParseError> {
|
||||
Ok(opts)
|
||||
}
|
||||
|
||||
pub fn parse_offset(offsets: &str) -> Result<(u32, u32), ArgParseError> {
|
||||
pub fn parse_offset(offsets: &str) -> Result<(u64, u64), ArgParseError> {
|
||||
if offsets.matches('-').count() != 1 {
|
||||
return Err(ArgParseError::InvalidOffsetValues);
|
||||
}
|
||||
let mut off: Vec<u32> = Vec::new();
|
||||
let mut off: Vec<u64> = Vec::new();
|
||||
for part in offsets.split('-') {
|
||||
if part.starts_with("0x") {
|
||||
off.push(u32::from_str_radix(part.trim_start_matches("0x"), 16)?)
|
||||
off.push(u64::from_str_radix(part.trim_start_matches("0x"), 16)?)
|
||||
} else {
|
||||
off.push(part.parse()?)
|
||||
}
|
||||
@@ -147,6 +160,7 @@ pub fn print_summary(opts: &Options) {
|
||||
}
|
||||
|
||||
let freq_str = format!("%{:.4}", opts.freq * 100.0);
|
||||
let trace_str = format!("{:?}", opts.trace);
|
||||
println!(
|
||||
"\n[ {} {} {} ]",
|
||||
"#".repeat(wspace / 2 + 2).yellow().bold(),
|
||||
@@ -201,5 +215,11 @@ pub fn print_summary(opts: &Options) {
|
||||
opts.transforms,
|
||||
" ".repeat(wspace - opts.transforms.len())
|
||||
);
|
||||
println!(
|
||||
"[ {} {}{}]",
|
||||
"Auto Trace: ".blue().bold(),
|
||||
opts.trace,
|
||||
" ".repeat(wspace - trace_str.len())
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
+73
-145
@@ -2,10 +2,11 @@ use crate::x86_64::*;
|
||||
use bitflags::bitflags;
|
||||
use iced_x86::code_asm::*;
|
||||
use iced_x86::*;
|
||||
use log::{error, info, trace, warn};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use tracer::*;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeoptimizerError {
|
||||
@@ -21,8 +22,6 @@ pub enum DeoptimizerError {
|
||||
// UnexpectedMemorySize,
|
||||
// #[error("Offset skipping failed!")]
|
||||
// OffsetSkipFail,
|
||||
#[error("Invalid formatter syntax.")]
|
||||
InvalidSyntax,
|
||||
#[error("Invalid processor mode(bitness). (16/32/64 accepted)")]
|
||||
InvalidProcessorMode,
|
||||
#[error("All available instruction transform gadgets failed.")]
|
||||
@@ -47,16 +46,18 @@ pub enum DeoptimizerError {
|
||||
TransposeFailed,
|
||||
#[error("Invalid transform gadget.")]
|
||||
InvalidTransformGadget,
|
||||
#[error("Tracer Error: {0}")]
|
||||
TracerError(#[from] TracerError),
|
||||
#[error("Instruction encoding failed: {0}")]
|
||||
EncodingFail(#[from] IcedError),
|
||||
}
|
||||
|
||||
enum AssemblySyntax {
|
||||
Keystone,
|
||||
Nasm,
|
||||
Masm,
|
||||
Intel,
|
||||
Gas,
|
||||
#[allow(dead_code)]
|
||||
pub enum FileType {
|
||||
Pe,
|
||||
Elf,
|
||||
Coff,
|
||||
Shellcode,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -78,26 +79,38 @@ impl AvailableTransforms {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct AnalyzedCode {
|
||||
bitness: u32,
|
||||
bytes: Vec<u8>,
|
||||
bitness: u32,
|
||||
start_addr: u64,
|
||||
code: Vec<Instruction>,
|
||||
file_type: FileType,
|
||||
instructions: Vec<Instruction>,
|
||||
known_addr_table: Vec<u64>,
|
||||
branch_targets: Vec<u64>,
|
||||
// addr_map: HashMap<u64, Instruction>,
|
||||
trace_results: Option<TraceResults>,
|
||||
}
|
||||
|
||||
impl AnalyzedCode {
|
||||
fn is_known_address(&self, addr: u64) -> bool {
|
||||
// pub fn get_bytes(&self) -> Vec<u8> {
|
||||
// self.bytes
|
||||
// }
|
||||
// pub fn get_instructions(&self) -> Vec<Instruction> {
|
||||
// self.instructions
|
||||
// }
|
||||
// pub fn get_bitness(&self) -> u32 {
|
||||
// self.bitness
|
||||
// }
|
||||
// pub fn get_start_address(&self) -> u64 {
|
||||
// self.start_addr
|
||||
// }
|
||||
pub fn is_known_address(&self, addr: u64) -> bool {
|
||||
self.known_addr_table.contains(&addr)
|
||||
}
|
||||
fn is_branch_target(&self, addr: u64) -> bool {
|
||||
pub fn is_branch_target(&self, addr: u64) -> bool {
|
||||
self.branch_targets.contains(&addr)
|
||||
}
|
||||
// fn get_random_cfe_addr(&self) -> Option<u64> {
|
||||
// self.cfe_addr_table.choose(&mut rand::thread_rng()).copied()
|
||||
// }
|
||||
|
||||
// pub fn is_affecting_cf(&self, inst: &Instruction) -> Result<bool, DeoptimizerError> {
|
||||
// // Check if the instruction exists in self.code
|
||||
// if self.addr_map.get(&inst.ip()).is_none() {
|
||||
@@ -135,9 +148,9 @@ pub struct Deoptimizer {
|
||||
pub transforms: AvailableTransforms,
|
||||
/// Allow processing of invalid instructions.
|
||||
pub allow_invalid: bool,
|
||||
/// Disassembler syntax.
|
||||
syntax: AssemblySyntax,
|
||||
skipped_offsets: Option<Vec<(u32, u32)>>,
|
||||
/// Trace the control flow of the given binary
|
||||
pub trace: bool,
|
||||
skipped_offsets: HashMap<u64, bool>,
|
||||
}
|
||||
|
||||
impl Deoptimizer {
|
||||
@@ -145,10 +158,10 @@ impl Deoptimizer {
|
||||
Self {
|
||||
cycle: 1,
|
||||
freq: 0.5,
|
||||
trace: false,
|
||||
allow_invalid: false,
|
||||
transforms: AvailableTransforms::All,
|
||||
syntax: AssemblySyntax::Nasm,
|
||||
skipped_offsets: None,
|
||||
skipped_offsets: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,32 +182,13 @@ impl Deoptimizer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_syntax(&mut self, syntax: String) -> Result<(), DeoptimizerError> {
|
||||
match syntax.to_lowercase().as_str() {
|
||||
"keystone" => self.syntax = AssemblySyntax::Keystone,
|
||||
"nasm" => self.syntax = AssemblySyntax::Nasm,
|
||||
"masm" => self.syntax = AssemblySyntax::Masm,
|
||||
"intel" => self.syntax = AssemblySyntax::Intel,
|
||||
"gas" => self.syntax = AssemblySyntax::Gas,
|
||||
_ => return Err(DeoptimizerError::InvalidSyntax),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_skipped_offsets(&mut self, skipped: Vec<(u32, u32)>) {
|
||||
self.skipped_offsets = Some(skipped);
|
||||
}
|
||||
|
||||
fn is_offset_skipped(&self, offset: u32) -> bool {
|
||||
if self.skipped_offsets.is_none() {
|
||||
return false;
|
||||
}
|
||||
for (o1, o2) in self.skipped_offsets.clone().unwrap() {
|
||||
if offset >= o1 && offset <= o2 {
|
||||
return true;
|
||||
pub fn set_skipped_offsets(&mut self, skipped: Vec<(u64, u64)>) {
|
||||
for range in skipped {
|
||||
let (o1, o2) = range;
|
||||
for o in o1..o2 {
|
||||
self.skipped_offsets.insert(o, true);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn replace_skipped_offsets(
|
||||
@@ -202,13 +196,13 @@ impl Deoptimizer {
|
||||
bytes: &[u8],
|
||||
fill: u8,
|
||||
) -> Result<Vec<u8>, DeoptimizerError> {
|
||||
if self.skipped_offsets.is_none() {
|
||||
if self.skipped_offsets.is_empty() {
|
||||
return Ok(bytes.to_vec());
|
||||
}
|
||||
let mut replaced_bytes = Vec::new();
|
||||
trace!("Replacing skipped offsets with NOPs...");
|
||||
for (i, b) in bytes.iter().enumerate() {
|
||||
if self.is_offset_skipped(i as u32) {
|
||||
if self.skipped_offsets.contains_key(&(i as u64)) {
|
||||
replaced_bytes.push(fill);
|
||||
continue;
|
||||
}
|
||||
@@ -229,11 +223,30 @@ impl Deoptimizer {
|
||||
bitness
|
||||
);
|
||||
|
||||
// let trace_results = tracer::trace(bytes, bitness, start_addr)?;
|
||||
// Determine the file type here...
|
||||
if bytes.len() > 1000 && self.trace {
|
||||
warn!("Given shellcode seems to be too large for effective tracing.")
|
||||
}
|
||||
|
||||
let mut trace_results = None;
|
||||
if self.trace {
|
||||
info!("Tracing the execution control flow...");
|
||||
let tr = tracer::trace(bytes, bitness, start_addr)?;
|
||||
info!("Done tracing!");
|
||||
info!("Found {} possible strings.", tr.possible_strings.len());
|
||||
debug!("Total coverage: {}", tr.total_coverage);
|
||||
debug!("Coverage without strings: {}", tr.coverage_whitout_strings);
|
||||
for o in 0..bytes.len() {
|
||||
if !tr.active_offsets.contains(&(o as u64 + start_addr)) {
|
||||
self.skipped_offsets.insert(o as u64 + start_addr, true);
|
||||
}
|
||||
}
|
||||
trace_results = Some(tr);
|
||||
}
|
||||
|
||||
let mut decoder = Decoder::with_ip(bitness, bytes, start_addr, DecoderOptions::NONE);
|
||||
let replaced_bytes: Vec<u8>;
|
||||
if self.skipped_offsets.is_some() {
|
||||
if !self.skipped_offsets.is_empty() {
|
||||
replaced_bytes = self.replace_skipped_offsets(bytes, 0x90)?;
|
||||
decoder = Decoder::with_ip(bitness, &replaced_bytes, start_addr, DecoderOptions::NONE);
|
||||
}
|
||||
@@ -241,22 +254,23 @@ impl Deoptimizer {
|
||||
let mut inst = Instruction::default();
|
||||
let mut known_addr_table = Vec::new();
|
||||
let mut branch_targets = Vec::new();
|
||||
let mut code = Vec::new();
|
||||
let mut instructions = Vec::new();
|
||||
let mut addr_map: HashMap<u64, Instruction> = HashMap::new();
|
||||
let mut offset = 0;
|
||||
while decoder.can_decode() {
|
||||
decoder.decode_out(&mut inst);
|
||||
if self.is_offset_skipped(offset) {
|
||||
if self.skipped_offsets.contains_key(&offset) {
|
||||
let mut db = Instruction::with_declare_byte_1(bytes[offset as usize]);
|
||||
db.set_ip(inst.ip());
|
||||
db.set_code(Code::DeclareByte);
|
||||
// Push to known address table
|
||||
known_addr_table.push(db.ip());
|
||||
addr_map.insert(db.ip(), db);
|
||||
code.push(db);
|
||||
instructions.push(db);
|
||||
offset += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if inst.is_invalid() {
|
||||
warn!("Found invalid instruction at: 0x{:016X}", inst.ip());
|
||||
if !self.allow_invalid {
|
||||
@@ -266,21 +280,13 @@ impl Deoptimizer {
|
||||
// Push to known address table
|
||||
known_addr_table.push(inst.ip());
|
||||
addr_map.insert(inst.ip(), inst);
|
||||
code.push(inst);
|
||||
instructions.push(inst);
|
||||
|
||||
let bt = get_branch_target(&inst).unwrap_or(0);
|
||||
if bt != 0 {
|
||||
branch_targets.push(bt);
|
||||
}
|
||||
|
||||
// // Push to control flow exit address table if it is a JMP of RET
|
||||
// if inst.mnemonic() == Mnemonic::Ret
|
||||
// || inst.mnemonic() == Mnemonic::Retf
|
||||
// || inst.mnemonic() == Mnemonic::Jmp
|
||||
// {
|
||||
// cfe_addr_table.push(inst.ip())
|
||||
// }
|
||||
offset += inst.len() as u32;
|
||||
offset += inst.len() as u64;
|
||||
}
|
||||
|
||||
for bt in branch_targets.iter() {
|
||||
@@ -296,92 +302,14 @@ impl Deoptimizer {
|
||||
bitness,
|
||||
bytes: bytes.to_vec(),
|
||||
start_addr,
|
||||
code,
|
||||
file_type: FileType::Shellcode,
|
||||
instructions,
|
||||
known_addr_table,
|
||||
branch_targets,
|
||||
// addr_map,
|
||||
trace_results,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn format_instruction(&self, inst: &Instruction) -> String {
|
||||
let mut result = String::new();
|
||||
match self.syntax {
|
||||
AssemblySyntax::Keystone => {
|
||||
let mut formatter = IntelFormatter::new();
|
||||
formatter.options_mut().set_uppercase_keywords(false);
|
||||
formatter
|
||||
.options_mut()
|
||||
.set_memory_size_options(iced_x86::MemorySizeOptions::Always);
|
||||
formatter.options_mut().set_hex_prefix("0x");
|
||||
formatter.options_mut().set_hex_suffix("");
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
AssemblySyntax::Nasm => {
|
||||
let mut formatter = NasmFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
AssemblySyntax::Masm => {
|
||||
let mut formatter = MasmFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
AssemblySyntax::Intel => {
|
||||
let mut formatter = IntelFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
AssemblySyntax::Gas => {
|
||||
let mut formatter = GasFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
pub fn disassemble(
|
||||
&mut self,
|
||||
bitness: u32,
|
||||
start_addr: u64,
|
||||
bytes: Vec<u8>,
|
||||
) -> Result<String, DeoptimizerError> {
|
||||
info!(
|
||||
"Disassembling at -> 0x{:016X} (mode={})",
|
||||
start_addr, bitness
|
||||
);
|
||||
let acode = self.analyze(bytes.as_slice(), bitness, start_addr)?;
|
||||
let mut result = String::new();
|
||||
let mut decoder = Decoder::new(acode.bitness, bytes.as_slice(), DecoderOptions::NONE);
|
||||
let mut inst = Instruction::default();
|
||||
while decoder.can_decode() {
|
||||
decoder.decode_out(&mut inst);
|
||||
if inst.is_invalid() {
|
||||
warn!(
|
||||
"Inlining invalid instruction bytes at: 0x{:016X}",
|
||||
inst.ip()
|
||||
);
|
||||
let start_index = (inst.ip() - acode.start_addr) as usize;
|
||||
let instr_bytes = &acode.bytes[start_index..start_index + inst.len()];
|
||||
result += &format!("loc_{:016X}: {}\n", inst.ip(), to_db_mnemonic(instr_bytes));
|
||||
continue;
|
||||
}
|
||||
let temp = self.format_instruction(&inst);
|
||||
let nbt = inst.near_branch_target();
|
||||
if nbt != 0 {
|
||||
if acode.is_known_address(nbt) {
|
||||
result += &format!(
|
||||
"loc_{:016X}: {} {}\n",
|
||||
inst.ip(),
|
||||
temp.split(' ').next().unwrap(),
|
||||
&format!("loc_{:016X}", nbt)
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
warn!("Misaligned instruction detected at {}", inst.ip())
|
||||
}
|
||||
}
|
||||
result += &format!("loc_{:016X}: {}\n", inst.ip(), temp);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn apply_transform(
|
||||
bitness: u32,
|
||||
inst: &Instruction,
|
||||
@@ -455,7 +383,7 @@ impl Deoptimizer {
|
||||
let mut ip_to_index_table: HashMap<u64, usize> = HashMap::new();
|
||||
let mut index_to_index_table: HashMap<usize, usize> = HashMap::new();
|
||||
// let mut new_ip: u64 = acode.start_addr;
|
||||
for inst in acode.code.clone() {
|
||||
for inst in acode.instructions.clone() {
|
||||
if acode.is_branch_target(inst.ip()) {
|
||||
ip_to_index_table.insert(inst.ip(), result.len());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use crate::x86_64::*;
|
||||
use iced_x86::*;
|
||||
use log::{error, info, warn};
|
||||
|
||||
pub fn format_instruction(syntax: String, inst: &Instruction) -> String {
|
||||
let mut result = String::new();
|
||||
match syntax.to_uppercase().as_str() {
|
||||
"KEYSTONE" => {
|
||||
let mut formatter = IntelFormatter::new();
|
||||
formatter.options_mut().set_uppercase_keywords(false);
|
||||
formatter
|
||||
.options_mut()
|
||||
.set_memory_size_options(iced_x86::MemorySizeOptions::Always);
|
||||
formatter.options_mut().set_hex_prefix("0x");
|
||||
formatter.options_mut().set_hex_suffix("");
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
"NASM" => {
|
||||
let mut formatter = NasmFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
"MASM" => {
|
||||
let mut formatter = MasmFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
"INTEL" => {
|
||||
let mut formatter = IntelFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
"GAS" => {
|
||||
let mut formatter = GasFormatter::new();
|
||||
formatter.format(inst, &mut result);
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown disassembler syntax: {}", syntax);
|
||||
panic!("Instruction formatting failed!")
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
pub fn disassemble(
|
||||
bytes: &[u8],
|
||||
bitness: u32,
|
||||
start_addr: u64,
|
||||
syntax: String,
|
||||
) -> Result<String, DeoptimizerError> {
|
||||
info!(
|
||||
"Disassembling at -> 0x{:016X} (mode={})",
|
||||
start_addr, bitness
|
||||
);
|
||||
|
||||
let mut result = String::new();
|
||||
let mut decoder = Decoder::with_ip(bitness, bytes, start_addr, DecoderOptions::NONE);
|
||||
let mut inst = Instruction::default();
|
||||
while decoder.can_decode() {
|
||||
decoder.decode_out(&mut inst);
|
||||
if inst.is_invalid() {
|
||||
warn!(
|
||||
"Inlining invalid instruction bytes at: 0x{:016X}",
|
||||
inst.ip()
|
||||
);
|
||||
let start_index = (inst.ip() - start_addr) as usize;
|
||||
let instr_bytes = &bytes[start_index..start_index + inst.len()];
|
||||
result += &format!("loc_{:016X}: {}\n", inst.ip(), to_db_mnemonic(instr_bytes));
|
||||
continue;
|
||||
}
|
||||
let temp = format_instruction(syntax.clone(), &inst);
|
||||
let nbt = inst.near_branch_target();
|
||||
if nbt != 0 {
|
||||
result += &format!(
|
||||
"loc_{:016X}: {} {}\n",
|
||||
inst.ip(),
|
||||
temp.split(' ').next().unwrap(),
|
||||
&format!("loc_{:016X}", nbt)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
result += &format!("loc_{:016X}: {}\n", inst.ip(), temp);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod deoptimizer;
|
||||
pub mod disassembler;
|
||||
mod helpers;
|
||||
mod tests;
|
||||
mod tracer;
|
||||
|
||||
+1
-1
@@ -529,7 +529,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trace_exec_flow() {
|
||||
fn test_trace() {
|
||||
let code_64: &[u8] = &[
|
||||
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
|
||||
0x52, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x51, 0x48, 0x8b, 0x52, 0x18,
|
||||
|
||||
+26
-85
@@ -1,51 +1,15 @@
|
||||
use crate::x86_64::*;
|
||||
use iced_x86::code_asm::*;
|
||||
use iced_x86::*;
|
||||
use log::{error, info, trace, warn};
|
||||
use log::{error, trace};
|
||||
use regex::bytes::Regex;
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TracerError {
|
||||
// #[error("Instruction with unexpected operand count.")]
|
||||
// UnexpectedOperandCount,
|
||||
// #[error("Given instruction not found in code map.")]
|
||||
// InstructionNotFound,
|
||||
// #[error("Code analysis results are not found.")]
|
||||
// MissingCodeAnalysis,
|
||||
// #[error("Near branch value too large.")]
|
||||
// NearBranchTooBig,
|
||||
// #[error("Unexpected memory size given.")]
|
||||
// UnexpectedMemorySize,
|
||||
// #[error("Offset skipping failed!")]
|
||||
// OffsetSkipFail,
|
||||
#[error("Unknown stack operation.")]
|
||||
UnknownStackOp,
|
||||
#[error("Invalid formatter syntax.")]
|
||||
InvalidSyntax,
|
||||
#[error("Invalid processor mode(bitness). (16/32/64 accepted)")]
|
||||
InvalidProcessorMode,
|
||||
#[error("All available instruction transform gadgets failed.")]
|
||||
AllTransformsFailed,
|
||||
#[error("Far branch value too large.")]
|
||||
FarBranchTooBig,
|
||||
#[error("Found invalid instruction.")]
|
||||
InvalidInstruction,
|
||||
#[error("Branch target not found.")]
|
||||
BracnhTargetNotFound,
|
||||
#[error("This transform not possible for given instruction.")]
|
||||
TransformNotPossible,
|
||||
#[error("Unexpected register size given.")]
|
||||
UnexpectedRegisterSize,
|
||||
#[error("Unexpected operand type encountered.")]
|
||||
UnexpectedOperandType,
|
||||
#[error("No GP register found with given parameters.")]
|
||||
RegisterNotFound,
|
||||
#[error("Instruction transpose attempt failed.")]
|
||||
TransposeFailed,
|
||||
#[error("Invalid transform gadget.")]
|
||||
InvalidTransformGadget,
|
||||
#[error("Instruction encoding failed: {0}")]
|
||||
EncodingFail(#[from] IcedError),
|
||||
}
|
||||
@@ -81,29 +45,29 @@ pub struct TraceResults {
|
||||
pub coverage_whitout_strings: f64,
|
||||
}
|
||||
|
||||
impl TraceResults {
|
||||
pub fn print_dead_code(&self) {
|
||||
let mut last = 0;
|
||||
for (i, b) in self.bytes.iter().enumerate() {
|
||||
if self.active_offsets.contains(&(i as u64)) {
|
||||
continue;
|
||||
}
|
||||
if i - last != 1 {
|
||||
print!("\n0x{:016X}:\t", i);
|
||||
}
|
||||
|
||||
if *b >= 0x20 && *b <= 0x7E {
|
||||
print!("{}", String::from_utf8_lossy(&[*b]));
|
||||
} else if *b == 0x00 {
|
||||
continue;
|
||||
} else {
|
||||
print!("\\x{:X}", b);
|
||||
}
|
||||
last = i
|
||||
}
|
||||
println!("\n");
|
||||
}
|
||||
}
|
||||
// impl TraceResults {
|
||||
// pub fn print_dead_code(&self) {
|
||||
// let mut last = 0;
|
||||
// for (i, b) in self.bytes.iter().enumerate() {
|
||||
// if self.active_offsets.contains(&(i as u64)) {
|
||||
// continue;
|
||||
// }
|
||||
// if i - last != 1 {
|
||||
// print!("\n0x{:016X}:\t", i);
|
||||
// }
|
||||
//
|
||||
// if *b >= 0x20 && *b <= 0x7E {
|
||||
// print!("{}", String::from_utf8_lossy(&[*b]));
|
||||
// } else if *b == 0x00 {
|
||||
// continue;
|
||||
// } else {
|
||||
// print!("\\x{:X}", b);
|
||||
// }
|
||||
// last = i
|
||||
// }
|
||||
// println!("\n");
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Tracer {
|
||||
fn new(bytes: &[u8], bitness: u32) -> Self {
|
||||
@@ -121,7 +85,7 @@ impl Tracer {
|
||||
pso.push(o as u64);
|
||||
}
|
||||
}
|
||||
info!("Found {} possible strings.", ps.len());
|
||||
// info!("Found {} possible strings.", ps.len());
|
||||
Self {
|
||||
bytes: bytes.to_vec(),
|
||||
bitness,
|
||||
@@ -141,7 +105,7 @@ impl Tracer {
|
||||
}
|
||||
|
||||
fn trace_code_paths(&mut self, start_offset: u64) -> Result<HaltResason, TracerError> {
|
||||
info!("[TRACER] Tracing -> 0x{:016X}", start_offset);
|
||||
trace!("[TRACER] Tracing -> 0x{:016X}", start_offset);
|
||||
let mut ip = start_offset;
|
||||
loop {
|
||||
if ip >= self.bytes.len() as u64 {
|
||||
@@ -293,32 +257,9 @@ fn is_conditional_branch(inst: Instruction) -> bool {
|
||||
|| inst.is_loopcc()
|
||||
}
|
||||
|
||||
// NO_BRANCH
|
||||
// - Check if the stack is not empty
|
||||
// - Check if current instruction is POP
|
||||
// - Save poped address in current context with register
|
||||
// - Pop the value from stack
|
||||
// - Add to cf_addr_map
|
||||
// - Add to active_offsets
|
||||
// - Advance to next_ip
|
||||
// CONDITIONAL_BRANCH
|
||||
// - Step-in
|
||||
// - Advance to next_ip
|
||||
// ADDRESS_CALL
|
||||
// - Push next_ip to stack
|
||||
// - Step-in
|
||||
// - Continue if HaltResason is Return
|
||||
// REGISTER_CALL
|
||||
// - Check if current context contains the register value
|
||||
// - Step-in
|
||||
// - Halt->DynamicBranch
|
||||
//
|
||||
|
||||
pub fn trace(bytes: &[u8], bitness: u32, start_addr: u64) -> Result<TraceResults, TracerError> {
|
||||
let mut tracer = Tracer::new(bytes, bitness);
|
||||
info!("Tracing execution flow and possible code paths...");
|
||||
tracer.trace_code_paths(start_addr)?;
|
||||
info!("Done tracing!");
|
||||
tracer.active_offsets.sort_unstable();
|
||||
tracer.active_offsets.dedup();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user