Skip to content

Commit 8c07921

Browse files
committed
Some CLI stuff
1 parent dffcd19 commit 8c07921

File tree

7 files changed

+97
-28
lines changed

7 files changed

+97
-28
lines changed

jingle/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ tracing = "0.1.40"
2727
clap = { version = "4.5.14", optional = true, features = ["derive"] }
2828
confy = { version = "0.6.1" , optional = true}
2929
hex = { version = "0.4.3" , optional = true}
30+
anyhow = { version = "1.0.95", optional = true }
3031
[features]
3132
default = []
32-
bin_features = ["dep:clap", "dep:confy", "dep:hex"]
33+
bin_features = ["dep:clap", "dep:confy", "dep:hex", "dep:anyhow"]
3334
gimli = ["jingle_sleigh/gimli"]

jingle/README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,41 @@
11
# `jingle`: Z3 + SLEIGH
22

33
`jingle` uses the sleigh bindings provided by `jingle_sleigh` and the excellent
4-
z3 bindings from the `z3` crate to provide SMT modeling of sequences of `PCODE` instructions
4+
z3 bindings from the `z3` crate to provide SMT modeling of sequences of `PCODE` instructions.
5+
6+
## CLI
7+
8+
`jingle` exposes a simple CLI tool for disassembling strings of executable bytes and modeling them in logic.
9+
10+
### Installation
11+
12+
From this folder:
13+
14+
```shell
15+
cargo install --path . --features="bin_features"
16+
```
17+
18+
This will install `jingle` in your path. Note that
19+
20+
### Usage
21+
22+
`jingle` requires that a Ghidra installation be present.
23+
24+
```shell
25+
Usage: jingle [GHIDRA_PATH] <COMMAND>
26+
27+
Commands:
28+
disassemble Adds files to myapp
29+
lift
30+
model
31+
architectures
32+
help Print this message or the help of the given subcommand(s)
33+
34+
Arguments:
35+
[GHIDRA_PATH]
36+
37+
Options:
38+
-h, --help Print help
39+
-V, --version Print version
40+
41+
```

jingle/src/main.rs

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::Context;
12
use clap::{Parser, Subcommand};
23
use hex::decode;
34
use jingle::modeling::{ModeledBlock, ModelingContext};
@@ -8,7 +9,7 @@ use jingle_sleigh::{Disassembly, Instruction, JingleSleighError, PcodeOperation,
89
use serde::{Deserialize, Serialize};
910
use std::path::PathBuf;
1011
use z3::ast::Ast;
11-
use z3::{Config, Context, Solver};
12+
use z3::{Config, Context as Z3Context, Solver};
1213

1314
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1415
struct JingleConfig {
@@ -73,10 +74,10 @@ enum Commands {
7374
Architectures,
7475
}
7576

76-
fn main() {
77+
fn main() -> anyhow::Result<()> {
7778
let params: JingleParams = JingleParams::parse();
7879
update_config(&params);
79-
let config: JingleConfig = confy::load("jingle", None).unwrap();
80+
let config: JingleConfig = confy::load("jingle", None)?;
8081
match params.command {
8182
Commands::Disassemble {
8283
architecture,
@@ -90,15 +91,17 @@ fn main() {
9091
architecture,
9192
hex_bytes,
9293
} => model(&config, architecture, hex_bytes),
93-
Commands::Architectures => list_architectures(&config),
94+
Commands::Architectures => Ok(list_architectures(&config)),
9495
}
9596
}
9697

9798
fn update_config(params: &JingleParams) {
9899
let stored_config: JingleConfig = confy::load("jingle", None).unwrap();
99-
let new_config = JingleConfig::from(params);
100-
if stored_config != new_config {
101-
confy::store("jingle", None, new_config).unwrap()
100+
if params.ghidra_path.is_some() {
101+
let new_config = JingleConfig::from(params);
102+
if stored_config != new_config {
103+
confy::store("jingle", None, new_config).unwrap()
104+
}
102105
}
103106
}
104107

@@ -113,13 +116,20 @@ fn get_instructions(
113116
config: &JingleConfig,
114117
architecture: String,
115118
hex_bytes: String,
116-
) -> (LoadedSleighContext, Vec<Instruction>) {
117-
let sleigh_build = config.sleigh_builder().unwrap();
118-
let img = decode(hex_bytes).unwrap();
119+
) -> anyhow::Result<(LoadedSleighContext, Vec<Instruction>)> {
120+
let sleigh_build = config.sleigh_builder().context(format!(
121+
"Unable to parse selected architecture. \n\
122+
This may indicate that your configured Ghidra path is incorrect: {}",
123+
config.ghidra_path.display()
124+
))?;
125+
let img = decode(hex_bytes)?;
119126
let max_len = img.len();
120127
let mut offset = 0;
121-
let sleigh = sleigh_build.build(&architecture).unwrap();
122-
let sleigh = sleigh.initialize_with_image(img).unwrap();
128+
let sleigh = sleigh_build
129+
.build(&architecture)
130+
.context("Unable to build the selected architecture.\n\
131+
This is either a bug in sleigh or the .sinc file for your architecture is malformed.")?;
132+
let sleigh = sleigh.initialize_with_image(img)?;
123133
let mut instrs = vec![];
124134
while offset < max_len {
125135
if let Some(instruction) = sleigh.instruction_at(offset as u64) {
@@ -130,29 +140,35 @@ fn get_instructions(
130140
break;
131141
}
132142
}
133-
(sleigh, instrs)
143+
Ok((sleigh, instrs))
134144
}
135145

136-
fn disassemble(config: &JingleConfig, architecture: String, hex_bytes: String) {
137-
for instr in get_instructions(config, architecture, hex_bytes).1 {
146+
fn disassemble(
147+
config: &JingleConfig,
148+
architecture: String,
149+
hex_bytes: String,
150+
) -> anyhow::Result<()> {
151+
for instr in get_instructions(config, architecture, hex_bytes)?.1 {
138152
println!("{}", instr.disassembly)
139153
}
154+
Ok(())
140155
}
141156

142-
fn lift(config: &JingleConfig, architecture: String, hex_bytes: String) {
143-
let (sleigh, instrs) = get_instructions(config, architecture, hex_bytes);
157+
fn lift(config: &JingleConfig, architecture: String, hex_bytes: String) -> anyhow::Result<()> {
158+
let (sleigh, instrs) = get_instructions(config, architecture, hex_bytes)?;
144159
for instr in instrs {
145160
for x in instr.ops {
146-
let x_disp = x.display(&sleigh).unwrap();
161+
let x_disp = x.display(&sleigh)?;
147162
println!("{}", x_disp)
148163
}
149164
}
165+
Ok(())
150166
}
151167

152-
fn model(config: &JingleConfig, architecture: String, hex_bytes: String) {
153-
let z3 = Context::new(&Config::new());
168+
fn model(config: &JingleConfig, architecture: String, hex_bytes: String) -> anyhow::Result<()> {
169+
let z3 = Z3Context::new(&Config::new());
154170
let solver = Solver::new(&z3);
155-
let (sleigh, mut instrs) = get_instructions(config, architecture, hex_bytes);
171+
let (sleigh, mut instrs) = get_instructions(config, architecture, hex_bytes)?;
156172
// todo: this is a disgusting hack to let us read a modeled block without requiring the user
157173
// to enter a block-terminating instruction. Everything with reading blocks needs to be reworked
158174
// at some point. For now, this lets me not break anything else relying on this behavior while
@@ -174,8 +190,10 @@ fn model(config: &JingleConfig, architecture: String, hex_bytes: String) {
174190
});
175191

176192
let jingle_ctx = JingleContext::new(&z3, &sleigh);
177-
let block = ModeledBlock::read(&jingle_ctx, instrs.into_iter()).unwrap();
193+
let block = ModeledBlock::read(&jingle_ctx, instrs.into_iter())?;
178194
let final_state = jingle_ctx.fresh_state();
179-
solver.assert(&final_state._eq(block.get_final_state()).unwrap().simplify());
180-
println!("{}", solver.to_smt2())
195+
println!("{}", block.get_final_state().fmt_smt_arrays());
196+
solver.assert(&final_state._eq(block.get_final_state())?.simplify());
197+
println!("{}", solver.to_smt2());
198+
Ok(())
181199
}

jingle/src/modeling/slice.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ impl<'ctx, T: ModelingContext<'ctx>> ModelingContext<'ctx> for &[T] {
5050
self.last().unwrap().get_branch_constraint()
5151
}
5252
}
53+

jingle/src/modeling/state/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,12 @@ impl<'ctx> State<'ctx> {
310310
let eq_terms: Vec<&Bool> = terms.iter().collect();
311311
Ok(Bool::and(self.jingle.z3, eq_terms.as_slice()))
312312
}
313+
314+
pub fn fmt_smt_arrays(&self)-> String{
315+
let mut lines = vec![];
316+
for x in &self.spaces {
317+
lines.push(x.fmt_smt_array())
318+
}
319+
lines.join("\n")
320+
}
313321
}

jingle/src/modeling/state/space.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::JingleError::{MismatchedAddressSize, UnexpectedArraySort, ZeroSizedVa
22
use crate::{JingleContext, JingleError};
33
use jingle_sleigh::{SleighEndianness, SpaceInfo};
44
use std::ops::Add;
5-
use z3::ast::{Array, BV};
5+
use z3::ast::{Array, Ast, BV};
66
use z3::Sort;
77

88
/// SLEIGH models programs using many spaces. This struct serves as a helper for modeling a single
@@ -89,6 +89,10 @@ impl<'ctx> ModeledSpace<'ctx> {
8989
self.metadata = write_to_array::<1>(&self.metadata, val, offset, self.endianness);
9090
Ok(())
9191
}
92+
93+
pub(crate) fn fmt_smt_array(&self) -> String {
94+
format!("{:?}", self.data.simplify())
95+
}
9296
}
9397

9498
fn read_from_array<'ctx>(

jingle_sleigh/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use thiserror::Error;
55
pub enum JingleSleighError {
66
/// The sleigh compiler was run against a language definition that had some missing files.
77
/// Probably indicates that the path to the language specification was wrong
8-
#[error("missing files needed to init sleigh. Could be sla or ldef or pspec")]
8+
#[error("Unable to parse sleigh language!")]
99
LanguageSpecRead,
1010
/// A language specification existed, but was unable to be parsed
1111
#[error("failed to parse sleigh language definition")]

0 commit comments

Comments
 (0)