Skip to content

Commit 713c846

Browse files
authored
chore: bump deps (#57)
* bump z3 and jingle stuff * clippy * Update Cargo.toml * fmt * python * fmt * remove parallel flag * fmt again * Bump libs and update readme
1 parent 30c88de commit 713c846

File tree

9 files changed

+92
-82
lines changed

9 files changed

+92
-82
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,29 @@
88
# `crackers`: A Tool for Synthesizing Code-Reuse Attacks from `p-code` Programs
99

1010
This repo contains the source code of `crackers`, a procedure for synthesizing
11-
code-reuse attacks (e.g. ROP). `crackers` takes as input a specification computation, usually
11+
code-reuse attacks (e.g. ROP). `crackers` takes as input a "reference program", usually
1212
written in an assembly language, a binary (of the same architecture) in which to look
1313
for gadgets, and user-provided constraints to enforce on synthesized chains. It will always
1414
return an answer (though there is no strict bound to runtime), reporting either that the problem
1515
is UNSAT, or providing an assignment of gadgets that meet all constraints, and a model
16-
of the memory state of the PCODE virtual machine at every stage of the comptuation.
16+
of the memory state of the PCODE virtual machine at every stage of the computation.
17+
18+
`crackers` will assume that _all_ system state is usable unless the user prohibits it by providing a constraint.
19+
This approach, while requiring more human guidance, allows it to minimize the assumptions it makes about the
20+
arrangement and roles of memory in an exploit.
21+
22+
To validate chains, `crackers` builds a mathematical model of the trace through the gadgets. This model, along
23+
with user-provided constraints, is verified against a model of the "reference program". When this verification
24+
returns SAT, `crackers` returns the Z3 model of the memory state of the chain at every point of its execution. This
25+
memory model may be used to derive the contents of memory needed to invoke the chain, and transitively the input needed to
26+
provide to the program to realize it.
27+
28+
Note that the provided CLI of crackers currently only prints the selected gadgets to the command line.
29+
To make use of the logical memory model it must be used through the programmatic API.
30+
31+
There is also an experimental Python binding for `crackers`, allowing for basic configuration, execution, and model
32+
inspection from python. These bindings also feature cross-FFI support with the python z3 bindings, allowing
33+
constraints to be expressed as python functions or `lambda` expressions returning Python Z3 `BoolRef` instances.
1734

1835
### This software is still in alpha and may change at any time
1936

crackers/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ toml = ["dep:toml_edit"]
2424
z3-gh-release = ["z3/gh-release"]
2525

2626
[dependencies]
27-
jingle = { version = "0.2.0", features = ["gimli"] }
28-
z3 = "0.14.0"
27+
jingle = { version = "0.2.2", features = ["gimli"] }
28+
z3 = "0.14.1"
2929
serde = { version = "1.0.203", features = ["derive"] }
3030
thiserror = "2.0"
3131
tracing = "0.1"

crackers/src/gadget/library/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use jingle::modeling::ModeledInstruction;
22
use jingle::sleigh::context::loaded::LoadedSleighContext;
3-
use jingle::sleigh::{ArchInfoProvider, Instruction, SpaceInfo, VarNode};
3+
use jingle::sleigh::{ArchInfoProvider, Instruction, SleighArchInfo, SpaceInfo, VarNode};
44
use jingle::{JingleContext, JingleError};
55
use rand::SeedableRng;
66
use rand::rngs::StdRng;
@@ -27,6 +27,13 @@ impl GadgetLibrary {
2727
self.gadgets.len()
2828
}
2929

30+
pub(crate) fn arch_info(&self) -> SleighArchInfo {
31+
SleighArchInfo::new(
32+
self.get_registers(),
33+
self.get_all_space_info(),
34+
self.default_code_space_index,
35+
)
36+
}
3037
pub fn get_random_candidates_for_trace<'a>(
3138
&'a self,
3239
jingle: &JingleContext,

crackers/src/synthesis/assignment_model/builder.rs

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,24 @@
11
use crate::error::CrackersError;
22
use crate::gadget::Gadget;
3-
use crate::gadget::library::GadgetLibrary;
43
use crate::reference_program::ReferenceProgram;
54
use crate::synthesis::assignment_model::AssignmentModel;
65
use crate::synthesis::builder::{StateConstraintGenerator, TransitionConstraintGenerator};
76
use crate::synthesis::pcode_theory::pcode_assignment::PcodeAssignment;
87
use jingle::JingleContext;
98
use jingle::modeling::{ModeledBlock, ModeledInstruction};
10-
use jingle::sleigh::{ArchInfoProvider, SpaceInfo, VarNode};
9+
use jingle::sleigh::SleighArchInfo;
1110
use std::fmt::{Debug, Formatter};
1211
use std::sync::Arc;
1312
use z3::{Context, Solver};
1413

15-
// todo: this should probably just be a struct in Jingle and we can stop with this trait nonsense
16-
#[derive(Clone, Debug)]
17-
pub struct ArchInfo {
18-
spaces: Vec<SpaceInfo>,
19-
default_code_space_index: usize,
20-
registers: Vec<(VarNode, String)>,
21-
}
22-
23-
impl From<&GadgetLibrary> for ArchInfo {
24-
fn from(value: &GadgetLibrary) -> Self {
25-
Self {
26-
default_code_space_index: value.get_code_space_idx(),
27-
registers: value
28-
.get_registers()
29-
.map(|(a, b)| (a.clone(), b.to_string()))
30-
.collect(),
31-
spaces: value.get_all_space_info().cloned().collect(),
32-
}
33-
}
34-
}
35-
36-
impl ArchInfoProvider for ArchInfo {
37-
fn get_space_info(&self, idx: usize) -> Option<&SpaceInfo> {
38-
self.spaces.get(idx)
39-
}
40-
41-
fn get_all_space_info(&self) -> impl Iterator<Item = &SpaceInfo> {
42-
self.spaces.iter()
43-
}
44-
45-
fn get_code_space_idx(&self) -> usize {
46-
self.default_code_space_index
47-
}
48-
49-
fn get_register(&self, name: &str) -> Option<&VarNode> {
50-
self.registers.iter().find(|f| f.1 == name).map(|f| &f.0)
51-
}
52-
53-
fn get_register_name(&self, location: &VarNode) -> Option<&str> {
54-
self.registers
55-
.iter()
56-
.find(|f| f.0 == *location)
57-
.map(|f| f.1.as_str())
58-
}
59-
60-
fn get_registers(&self) -> impl Iterator<Item = (&VarNode, &str)> {
61-
self.registers.iter().map(|(f, v)| (f, v.as_str()))
62-
}
63-
}
64-
6514
#[derive(Clone)]
6615
pub struct AssignmentModelBuilder {
6716
pub templates: ReferenceProgram,
6817
pub gadgets: Vec<Gadget>,
6918
pub preconditions: Vec<Arc<StateConstraintGenerator>>,
7019
pub postconditions: Vec<Arc<StateConstraintGenerator>>,
7120
pub pointer_invariants: Vec<Arc<TransitionConstraintGenerator>>,
72-
pub arch_info: ArchInfo,
21+
pub arch_info: SleighArchInfo,
7322
}
7423

7524
impl Debug for AssignmentModelBuilder {

crackers/src/synthesis/assignment_model/mod.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub mod builder;
33
use std::fmt::{Display, Formatter};
44

55
use jingle::modeling::{ModelingContext, State};
6-
use jingle::sleigh::{ArchInfoProvider, GeneralizedVarNode};
6+
use jingle::sleigh::{ArchInfoProvider, GeneralizedVarNode, SleighArchInfo};
77
use jingle::varnode::ResolvedVarnode;
88
use z3::Model;
99
use z3::ast::BV;
@@ -12,11 +12,16 @@ use z3::ast::BV;
1212
pub struct AssignmentModel<T: ModelingContext> {
1313
model: Model,
1414
pub gadgets: Vec<T>,
15+
pub arch_info: SleighArchInfo,
1516
}
1617

1718
impl<T: ModelingContext> AssignmentModel<T> {
18-
pub fn new(model: Model, gadgets: Vec<T>) -> Self {
19-
Self { model, gadgets }
19+
pub fn new(model: Model, gadgets: Vec<T>, arch_info: SleighArchInfo) -> Self {
20+
Self {
21+
model,
22+
gadgets,
23+
arch_info,
24+
}
2025
}
2126

2227
pub fn model(&self) -> &Model {
@@ -61,6 +66,14 @@ impl<T: ModelingContext> AssignmentModel<T> {
6166
.unwrap();
6267
self.model.eval(&val, false)
6368
}
69+
70+
pub fn inputs(&self) -> impl Iterator<Item = ResolvedVarnode> {
71+
self.gadgets.iter().flat_map(|gadget| gadget.get_inputs())
72+
}
73+
74+
pub fn outputs(&self) -> impl Iterator<Item = ResolvedVarnode> {
75+
self.gadgets.iter().flat_map(|gadget| gadget.get_outputs())
76+
}
6477
}
6578

6679
impl<T: ModelingContext + Display> Display for AssignmentModel<T> {

crackers/src/synthesis/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::error::CrackersError::EmptySpecification;
1010
use crate::gadget::candidates::{CandidateBuilder, Candidates};
1111
use crate::gadget::library::GadgetLibrary;
1212
use crate::reference_program::ReferenceProgram;
13-
use crate::synthesis::assignment_model::builder::{ArchInfo, AssignmentModelBuilder};
13+
use crate::synthesis::assignment_model::builder::AssignmentModelBuilder;
1414
use crate::synthesis::builder::{
1515
StateConstraintGenerator, SynthesisParams, SynthesisSelectionStrategy,
1616
TransitionConstraintGenerator,
@@ -112,7 +112,7 @@ impl AssignmentSynthesis {
112112
preconditions: self.preconditions.clone(),
113113
postconditions: self.postconditions.clone(),
114114
pointer_invariants: self.pointer_invariants.clone(),
115-
arch_info: ArchInfo::from(self.library.as_ref()),
115+
arch_info: self.library.arch_info(),
116116
}
117117
}
118118

crackers/src/synthesis/pcode_theory/pcode_assignment.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ impl PcodeAssignment {
7575
let model = solver
7676
.get_model()
7777
.ok_or(CrackersError::ModelGenerationError)?;
78-
Ok(AssignmentModel::new(model, self.eval_trace.to_vec()))
78+
Ok(AssignmentModel::new(
79+
model,
80+
self.eval_trace.to_vec(),
81+
jingle.info.clone(),
82+
))
7983
}
8084
}
8185
}

crackers_python/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
1212
[dependencies]
1313
pyo3 = { version = "0.25", features = ["extension-module", "py-clone"] }
1414
crackers = {path = "../crackers", features = ["pyo3"], version = "0.3.0" }
15-
jingle = {version = "0.2.0", features = ["pyo3"]}
15+
jingle = {version = "0.2.2", features = ["pyo3"]}
1616
toml_edit = "0.22.22"
1717
z3 = "0.14.0"
1818
serde_json = "1.0.140"

crackers_python/src/decision/assignment_model/mod.rs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use crate::decision::assignment_model::model_varnode_iterator::ModelVarNodeItera
44
use crackers::synthesis::assignment_model::AssignmentModel;
55
use jingle::modeling::{ModeledBlock, ModelingContext, State};
66
use jingle::python::modeled_block::PythonModeledBlock;
7-
use jingle::python::resolved_varnode::PythonResolvedVarNode;
7+
use jingle::python::resolved_varnode::{PythonResolvedVarNode, PythonResolvedVarNodeInner};
88
use jingle::python::state::PythonState;
99
use jingle::python::varode_iterator::VarNodeIterator;
1010
use jingle::python::z3::ast::{TryFromPythonZ3, TryIntoPythonZ3};
11-
use jingle::sleigh::{SpaceType, VarNode, VarNodeDisplay};
11+
use jingle::sleigh::{ArchInfoProvider, SpaceType};
1212
use jingle::varnode::{ResolvedIndirectVarNode, ResolvedVarnode};
1313
use pyo3::exceptions::PyRuntimeError;
1414
use pyo3::{Py, PyAny, PyResult, pyclass, pymethods};
@@ -28,23 +28,29 @@ impl PythonAssignmentModel {
2828
vn: PythonResolvedVarNode,
2929
completion: bool,
3030
) -> Option<(String, BV)> {
31-
match vn {
32-
PythonResolvedVarNode::Direct(a) => {
33-
let bv = state.read_varnode(&VarNode::from(a.clone())).ok()?;
31+
match vn.inner {
32+
PythonResolvedVarNodeInner::Direct(a) => {
33+
let bv = state.read_varnode(a.inner()).ok()?;
3434
let val = self.inner.model().eval(&bv, completion)?;
3535
Some((format!("{a}"), val))
3636
}
37-
PythonResolvedVarNode::Indirect(i) => {
38-
let pointer_value = self.inner.model().eval(&i.inner.pointer, completion)?;
39-
let space_name = i.inner.pointer_space_info.name.clone();
40-
let access_size = i.inner.access_size_bytes;
37+
PythonResolvedVarNodeInner::Indirect(i) => {
38+
let info = i.info();
39+
let i = i.inner();
40+
let pointer_value = self.inner.model().eval(&i.pointer, completion)?;
41+
let space_name = info
42+
.get_space_info(i.pointer_space_idx)
43+
.unwrap()
44+
.name
45+
.clone();
46+
let access_size = i.access_size_bytes;
4147
let pointed_value = self.inner.model().eval(
4248
&state
4349
.read_resolved(&ResolvedVarnode::Indirect(ResolvedIndirectVarNode {
44-
access_size_bytes: i.inner.access_size_bytes,
45-
pointer_location: i.inner.pointer_location,
46-
pointer: i.inner.pointer,
47-
pointer_space_idx: i.inner.pointer_space_info.index,
50+
access_size_bytes: i.access_size_bytes,
51+
pointer_location: i.pointer_location.clone(),
52+
pointer: i.pointer.clone(),
53+
pointer_space_idx: i.pointer_space_idx,
4854
}))
4955
.ok()?,
5056
completion,
@@ -100,8 +106,15 @@ impl PythonAssignmentModel {
100106
.flat_map(|g| g.get_input_vns().ok())
101107
.flatten()
102108
.filter(|a| {
103-
if let PythonResolvedVarNode::Direct(VarNodeDisplay::Raw(r)) = a {
104-
r.space_info._type == SpaceType::IPTR_PROCESSOR
109+
if let PythonResolvedVarNode {
110+
inner: PythonResolvedVarNodeInner::Direct(a),
111+
} = a
112+
{
113+
a.info()
114+
.get_space_info(a.inner().space_index)
115+
.unwrap()
116+
._type
117+
== SpaceType::IPTR_PROCESSOR
105118
} else {
106119
true
107120
}
@@ -116,8 +129,15 @@ impl PythonAssignmentModel {
116129
.flat_map(|g| g.get_output_vns().ok())
117130
.flatten()
118131
.filter(|a| {
119-
if let PythonResolvedVarNode::Direct(VarNodeDisplay::Raw(r)) = a {
120-
r.space_info._type == SpaceType::IPTR_PROCESSOR
132+
if let PythonResolvedVarNode {
133+
inner: PythonResolvedVarNodeInner::Direct(a),
134+
} = a
135+
{
136+
a.info()
137+
.get_space_info(a.inner().space_index)
138+
.unwrap()
139+
._type
140+
== SpaceType::IPTR_PROCESSOR
121141
} else {
122142
true
123143
}

0 commit comments

Comments
 (0)