use std::{cmp::min, fmt::Write};
use anyhow::Result;
use crate::{analysis::dis::zydis, arch::Arch, aspace::AddressSpace, VA};
use super::Workspace;
#[derive(Default, Clone)]
struct OriginalHooks {
print_address_abs: Option<zydis::Hook>,
print_mnemonic: Option<zydis::Hook>,
pre_instruction: Option<zydis::Hook>,
}
#[derive(Clone, Copy)]
pub struct FormatterOptions {
colors: bool,
hex_column_size: usize,
mnemonic_width: usize,
}
struct UserData<'a> {
ws: &'a dyn Workspace,
orig: OriginalHooks,
options: FormatterOptions,
}
pub struct FormatterBuilder {
options: FormatterOptions,
}
impl FormatterBuilder {
#[must_use]
pub fn build(self) -> Formatter {
Formatter::from_options(self.options)
}
#[must_use]
pub fn with_colors(mut self, colors: bool) -> FormatterBuilder {
self.options.colors = colors;
self
}
#[must_use]
pub fn with_hex_column_size(mut self, hex_column_size: usize) -> FormatterBuilder {
self.options.hex_column_size = min(hex_column_size, 0x10);
self
}
}
pub struct Formatter {
options: FormatterOptions,
inner: zydis::Formatter,
orig: OriginalHooks,
}
pub const TOKEN_USER_SYMBOLNAME: zydis::Token = zydis::Token(zydis::TOKEN_USER.0 + 1);
pub const TOKEN_USER_HEX: zydis::Token = zydis::Token(zydis::TOKEN_USER.0 + 2);
impl Formatter {
const COLOR_ADDRESS_ABS: ansi_term::Color = ansi_term::Color::Blue;
const COLOR_ADDRESS_REL: ansi_term::Color = ansi_term::Color::Blue;
const COLOR_DECORATOR: ansi_term::Color = Formatter::GREY;
const COLOR_DELIMITER: ansi_term::Color = Formatter::GREY;
const COLOR_DISPLACEMENT: ansi_term::Color = ansi_term::Color::Blue;
const COLOR_HEX: ansi_term::Color = ansi_term::Color::Cyan;
const COLOR_IMMEDIATE: ansi_term::Color = ansi_term::Color::Blue;
const COLOR_INVALID: ansi_term::Color = ansi_term::Color::Red;
const COLOR_MNEMONIC: ansi_term::Color = ansi_term::Color::Green;
const COLOR_PARENTHESIS_CLOSE: ansi_term::Color = Formatter::GREY;
const COLOR_PARENTHESIS_OPEN: ansi_term::Color = Formatter::GREY;
const COLOR_PREFIX: ansi_term::Color = Formatter::GREY;
const COLOR_REGISTER: ansi_term::Color = ansi_term::Color::Yellow;
const COLOR_SYMBOL: ansi_term::Color = Formatter::GREY;
const COLOR_SYMBOLNAME: ansi_term::Color = ansi_term::Color::Purple;
const COLOR_TYPECAST: ansi_term::Color = Formatter::GREY;
const COLOR_USER: ansi_term::Color = Formatter::GREY;
const COLOR_WHITESPACE: ansi_term::Color = ansi_term::Color::Black;
const GREY: ansi_term::Color = ansi_term::Color::Fixed(242);
#[must_use]
pub fn new() -> Formatter {
FormatterBuilder {
options: FormatterOptions {
colors: true,
hex_column_size: 7,
mnemonic_width: 7,
},
}
.build()
}
#[must_use]
pub fn with_options() -> FormatterBuilder {
FormatterBuilder {
options: FormatterOptions {
colors: true,
hex_column_size: 7,
mnemonic_width: 7,
},
}
}
pub fn from_options(options: FormatterOptions) -> Formatter {
let mut inner = zydis::Formatter::new(zydis::FormatterStyle::INTEL).unwrap();
let mut orig: OriginalHooks = Default::default();
let f = inner
.set_pre_instruction(Box::new(
|_formatter: &zydis::Formatter,
buf: &mut zydis::FormatterBuffer,
ctx: &mut zydis::FormatterContext,
userdata: Option<&mut dyn core::any::Any>|
-> zydis::Result<()> {
let userdata = userdata.expect("no userdata");
let userdata = userdata.downcast_ref::<UserData>().expect("incorrect userdata");
let va = ctx.runtime_address;
if let Some(sec) = userdata
.ws
.module()
.sections
.iter()
.find(|&sec| sec.virtual_range.contains(&va))
{
buf.append(TOKEN_USER_SYMBOLNAME)?;
buf.get_string()?.append(&sec.name)?;
} else {
buf.append(zydis::TOKEN_INVALID)?;
buf.get_string()?.append("???")?;
}
buf.append(zydis::TOKEN_DELIMITER)?;
buf.get_string()?.append(":")?;
buf.append(zydis::TOKEN_ADDRESS_ABS)?;
match userdata.ws.module().arch {
Arch::X32 => {
buf.get_string()?.append(&format!("{va:08x}"))?;
}
Arch::X64 => {
buf.get_string()?.append(&format!("{va:016x}"))?;
}
}
buf.append(zydis::TOKEN_WHITESPACE)?;
buf.get_string()?.append(" ")?;
if userdata.options.hex_column_size > 0 {
let mut insn_buf = [0u8; 0x10];
let insn_len = (unsafe { &*ctx.instruction }).length as usize;
let col_count = userdata.options.hex_column_size;
userdata
.ws
.module()
.address_space
.read_into(va, &mut insn_buf[..insn_len])
.expect("failed to read instruction");
let mut hex = String::new();
for (i, b) in insn_buf.iter().enumerate().take(col_count) {
if insn_len > col_count && i == col_count - 1 {
hex.write_str("...").unwrap();
} else if i < insn_len {
if i != 0 {
hex.write_str(" ").unwrap();
}
hex.write_str(&format!("{b:02X}")).unwrap();
} else {
hex.write_str(" ").unwrap();
}
}
buf.append(TOKEN_USER_HEX)?;
buf.get_string()?.append(&hex)?;
buf.append(zydis::TOKEN_WHITESPACE)?;
buf.get_string()?.append(" ")?;
}
Ok(())
},
))
.unwrap();
orig.pre_instruction = Some(f);
let f = inner
.set_print_address_abs(Box::new(
|formatter: &zydis::Formatter,
buf: &mut zydis::FormatterBuffer,
ctx: &mut zydis::FormatterContext,
userdata: Option<&mut dyn core::any::Any>|
-> zydis::Result<()> {
let userdata = userdata.expect("no userdata");
let userdata = userdata.downcast_ref::<UserData>().expect("incorrect userdata");
let absolute_address = unsafe {
let insn: &zydis::DecodedInstruction = &*ctx.instruction;
let op: &zydis::DecodedOperand = &*ctx.operand;
insn.calc_absolute_address(ctx.runtime_address, op)
.expect("failed to calculate absolute address")
};
if let Some(name) = userdata.ws.analysis().names.names_by_address.get(&absolute_address) {
buf.append(TOKEN_USER_SYMBOLNAME)?;
return buf.get_string()?.append(name);
} else {
let orig = userdata.orig.print_address_abs.as_ref().expect("no original hook");
if let zydis::Hook::PrintAddressAbs(Some(f)) = orig {
let status =
unsafe { f(formatter as *const _ as *const zydis::ffi::ZydisFormatter, buf, ctx) };
if status.is_error() {
Err(status)
} else {
Ok(())
}
} else {
panic!("unexpected original hook");
}
}
},
))
.unwrap();
orig.print_address_abs = Some(f);
let f = inner
.set_print_mnemonic(Box::new(
|formatter: &zydis::Formatter,
buf: &mut zydis::FormatterBuffer,
ctx: &mut zydis::FormatterContext,
userdata: Option<&mut dyn core::any::Any>|
-> zydis::Result<()> {
let userdata = userdata.expect("no userdata");
let userdata = userdata.downcast_ref::<UserData>().expect("incorrect userdata");
let orig = userdata.orig.print_mnemonic.as_ref().expect("no original hook");
if let zydis::Hook::PrintMnemonic(Some(f)) = orig {
let status = unsafe { f(formatter as *const _ as *const zydis::ffi::ZydisFormatter, buf, ctx) };
if status.is_error() {
return Err(status);
}
let (_, mnemonic) = buf.get_token()?.get_value()?;
if mnemonic.len() < userdata.options.mnemonic_width {
let mut padding = String::new();
for _ in 0..userdata.options.mnemonic_width - mnemonic.len() {
padding.write_str(" ").unwrap();
}
buf.append(zydis::TOKEN_WHITESPACE)?;
buf.get_string()?.append(&padding)?;
}
Ok(())
} else {
panic!("unexpected original hook");
}
},
))
.unwrap();
orig.print_mnemonic = Some(f);
Formatter { options, inner, orig }
}
fn get_token_color(token: zydis::Token) -> ansi_term::Color {
match token {
zydis::TOKEN_INVALID => Formatter::COLOR_INVALID,
zydis::TOKEN_WHITESPACE => Formatter::COLOR_WHITESPACE,
zydis::TOKEN_DELIMITER => Formatter::COLOR_DELIMITER,
zydis::TOKEN_PARENTHESIS_OPEN => Formatter::COLOR_PARENTHESIS_OPEN,
zydis::TOKEN_PARENTHESIS_CLOSE => Formatter::COLOR_PARENTHESIS_CLOSE,
zydis::TOKEN_PREFIX => Formatter::COLOR_PREFIX,
zydis::TOKEN_MNEMONIC => Formatter::COLOR_MNEMONIC,
zydis::TOKEN_REGISTER => Formatter::COLOR_REGISTER,
zydis::TOKEN_ADDRESS_ABS => Formatter::COLOR_ADDRESS_ABS,
zydis::TOKEN_ADDRESS_REL => Formatter::COLOR_ADDRESS_REL,
zydis::TOKEN_DISPLACEMENT => Formatter::COLOR_DISPLACEMENT,
zydis::TOKEN_IMMEDIATE => Formatter::COLOR_IMMEDIATE,
zydis::TOKEN_TYPECAST => Formatter::COLOR_TYPECAST,
zydis::TOKEN_DECORATOR => Formatter::COLOR_DECORATOR,
zydis::TOKEN_SYMBOL => Formatter::COLOR_SYMBOL,
zydis::TOKEN_USER => Formatter::COLOR_USER,
TOKEN_USER_SYMBOLNAME => Formatter::COLOR_SYMBOLNAME,
TOKEN_USER_HEX => Formatter::COLOR_HEX,
_ => unimplemented!("token: {}", token),
}
}
fn render_token<T: Write>(&self, o: &mut T, token: zydis::Token, s: &str) -> Result<()> {
if self.options.colors {
let s = Formatter::get_token_color(token).paint(s).to_string();
o.write_str(&s)?;
} else {
o.write_str(s)?;
}
Ok(())
}
pub fn format_instruction(&self, ws: &dyn Workspace, insn: &zydis::DecodedInstruction, va: VA) -> Result<String> {
let mut buffer = [0u8; 400];
let x = unsafe { std::mem::transmute::<&'_ dyn Workspace, &'static dyn Workspace>(ws) };
let mut ud = UserData {
orig: self.orig.clone(),
ws: x,
options: self.options,
};
let mut out = String::new();
for (token, s) in self
.inner
.tokenize_instruction(insn, &mut buffer, Some(va), Some(&mut ud))?
{
self.render_token(&mut out, token, s)?;
}
Ok(out)
}
}
impl Default for Formatter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{super::*, *};
use crate::rsrc::*;
use anyhow::Result;
#[test]
fn with_colors() -> Result<()> {
let buf = get_buf(Rsrc::NOP);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let config = config::empty();
let ws = PEWorkspace::from_pe(config, pe)?;
let fmt = Formatter::with_options().with_colors(true).build();
let insn = crate::test::read_insn(&ws.pe.module, 0x401C53);
let s = fmt.format_instruction(&ws, &insn, 0x401C53)?;
assert!(s.contains("\u{1b}"));
assert!(s.contains("call"));
assert!(s.contains("GetModuleHandleA"));
Ok(())
}
#[test]
fn no_colors() -> Result<()> {
let buf = get_buf(Rsrc::NOP);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let config = config::empty();
let ws = PEWorkspace::from_pe(config, pe)?;
let fmt = Formatter::with_options()
.with_colors(false)
.with_hex_column_size(0)
.build();
let insn = crate::test::read_insn(&ws.pe.module, 0x401C53);
assert_eq!(
fmt.format_instruction(&ws, &insn, 0x401C53)?,
".text:00401c53 call [kernel32.dll!GetModuleHandleA]"
);
Ok(())
}
#[test]
fn hex() -> Result<()> {
let buf = get_buf(Rsrc::NOP);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let config = config::empty();
let ws = PEWorkspace::from_pe(config, pe)?;
let insn = crate::test::read_insn(&ws.pe.module, 0x401C53);
let fmt = Formatter::with_options()
.with_colors(false)
.with_hex_column_size(0)
.build();
assert_eq!(
fmt.format_instruction(&ws, &insn, 0x401C53)?,
".text:00401c53 call [kernel32.dll!GetModuleHandleA]"
);
let fmt = Formatter::with_options()
.with_colors(false)
.with_hex_column_size(7)
.build();
assert_eq!(
fmt.format_instruction(&ws, &insn, 0x401C53)?,
".text:00401c53 FF 15 00 60 40 00 call [kernel32.dll!GetModuleHandleA]"
);
Ok(())
}
}