use anyhow::Context;
use clap::{Parser, Subcommand};
use hex::decode;
use jingle::display::JingleDisplay;
use jingle::modeling::{ModeledBlock, ModelingContext, State};
use jingle_sleigh::context::SleighContextBuilder;
use jingle_sleigh::context::loaded::LoadedSleighContext;
use jingle_sleigh::{Disassembly, Instruction, JingleSleighError, PcodeOperation, VarNode};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use z3::Solver;
use z3::ast::Ast;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct JingleConfig {
pub ghidra_path: PathBuf,
}
impl JingleConfig {
pub fn sleigh_builder(&self) -> Result<SleighContextBuilder, JingleSleighError> {
SleighContextBuilder::load_ghidra_installation(&self.ghidra_path)
}
}
impl Default for JingleConfig {
fn default() -> Self {
if cfg!(target_os = "windows") {
let path = PathBuf::from(r"C:\Program Files\ghidra");
Self { ghidra_path: path }
} else if cfg!(target_os = "macos") {
let path = PathBuf::from(r"/Applications/ghidra");
Self { ghidra_path: path }
} else {
let path = PathBuf::from(r"/opt/ghidra");
Self { ghidra_path: path }
}
}
}
impl From<&JingleParams> for JingleConfig {
fn from(value: &JingleParams) -> Self {
let path = value.ghidra_path.clone();
Self {
ghidra_path: path
.map(PathBuf::from)
.unwrap_or(JingleConfig::default().ghidra_path),
}
}
}
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
struct JingleParams {
#[command(subcommand)]
pub command: Commands,
pub ghidra_path: Option<String>,
}
#[derive(Debug, Subcommand)]
enum Commands {
Disassemble {
architecture: String,
hex_bytes: String,
},
Lift {
architecture: String,
hex_bytes: String,
},
Model {
architecture: String,
hex_bytes: String,
},
Architectures,
}
fn main() -> anyhow::Result<()> {
let params: JingleParams = JingleParams::parse();
update_config(¶ms);
let config: JingleConfig = confy::load("jingle", None)?;
match params.command {
Commands::Disassemble {
architecture,
hex_bytes,
} => disassemble(&config, architecture, hex_bytes),
Commands::Lift {
architecture,
hex_bytes,
} => lift(&config, architecture, hex_bytes),
Commands::Model {
architecture,
hex_bytes,
} => model(&config, architecture, hex_bytes),
Commands::Architectures => {
list_architectures(&config);
Ok(())
}
}
}
fn update_config(params: &JingleParams) {
let stored_config: JingleConfig = confy::load("jingle", None).unwrap();
if params.ghidra_path.is_some() {
let new_config = JingleConfig::from(params);
if stored_config != new_config {
confy::store("jingle", None, new_config).unwrap()
}
}
}
fn list_architectures(config: &JingleConfig) {
let sleigh = config.sleigh_builder().unwrap();
for language_id in sleigh.get_language_ids() {
println!("{language_id}")
}
}
fn get_instructions(
config: &JingleConfig,
architecture: String,
hex_bytes: String,
) -> anyhow::Result<(LoadedSleighContext<'_>, Vec<Instruction>)> {
let sleigh_build = config.sleigh_builder().context(format!(
"Unable to parse selected architecture. \n\
This may indicate that your configured Ghidra path is incorrect: {}",
config.ghidra_path.display()
))?;
let img = decode(hex_bytes)?;
let max_len = img.len();
let mut offset = 0;
let sleigh = sleigh_build.build(&architecture).context(
"Unable to build the selected architecture.\n\
This is either a bug in sleigh or the .sinc file for your architecture is malformed.",
)?;
let sleigh = sleigh.initialize_with_image(img)?;
let mut instrs = vec![];
while offset < max_len {
if let Some(instruction) = sleigh.instruction_at(offset as u64) {
offset += instruction.length;
instrs.push(instruction);
}
if sleigh.instruction_at(offset as u64).is_none() {
break;
}
}
Ok((sleigh, instrs))
}
fn disassemble(
config: &JingleConfig,
architecture: String,
hex_bytes: String,
) -> anyhow::Result<()> {
for instr in get_instructions(config, architecture, hex_bytes)?.1 {
println!("{}", instr.disassembly)
}
Ok(())
}
fn lift(config: &JingleConfig, architecture: String, hex_bytes: String) -> anyhow::Result<()> {
let (sleigh, instrs) = get_instructions(config, architecture, hex_bytes)?;
let info = sleigh.arch_info();
for instr in instrs {
for x in instr.ops {
let x_disp = x.display(info);
println!("{x_disp}")
}
}
Ok(())
}
fn model(config: &JingleConfig, architecture: String, hex_bytes: String) -> anyhow::Result<()> {
let solver = Solver::new();
let (sleigh, mut instrs) = get_instructions(config, architecture, hex_bytes)?;
instrs.push(Instruction {
address: 0,
disassembly: Disassembly {
args: "".to_string(),
mnemonic: "".to_string(),
},
ops: vec![PcodeOperation::Branch {
input: VarNode::new(0, 1, 1),
}],
length: 1,
});
let block = ModeledBlock::read(sleigh.arch_info(), instrs.into_iter())?;
let final_state = State::new(sleigh.arch_info());
solver.assert(final_state._eq(block.get_final_state())?.simplify());
println!("{}", solver.to_smt2());
Ok(())
}