diff --git a/.gitignore b/.gitignore index 60d78c4..51f3724 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /bin /.idea +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 83bcd46..41a0dd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,47 +1,6 @@ -[package] -name = "crackers" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "crackers" -required-features = ["bin"] - -[[example]] -name = "crackers_gpt" -required-features = ["gpt"] - -[features] -default = ["toml"] -bin = ["dep:tracing-subscriber", "toml", "dep:clap", "dep:anyhow", "dep:tracing-indicatif"] -gpt = ["dep:tracing-subscriber", "dep:cc", "dep:anyhow", "dep:tempfile", "dep:tokio", "dep:async-openai", "dep:clap"] - -toml = ["dep:toml_edit"] -bundled = ["z3/bundled"] +[workspace] +resolver = "2" +members = ["crackers", "crackers_python"] [profile.dev] opt-level = 3 - -[dependencies] -jingle = { git = "https://github.com/toolCHAINZ/jingle", branch = "main", features = ["gimli"] } -z3 = { git = "https://github.com/toolCHAINZ/z3.rs.git", branch = "patch-1" } -serde = { version = "1.0.203", features = ["derive"] } -thiserror = "1.0.58" -rmp-serde = "1.1.2" -tracing = "0.1.40" -colored = "2.1.0" -tracing-subscriber = { version = "0.3.18", optional = true, features = ["env-filter"] } -toml_edit = { version = "0.22.12", optional = true, features = ["serde"] } -object = "0.36.7" -clap = { version = "4.0.32", optional = true , features = ["derive"]} -rand = "0.8.5" -derive_builder = "0.20.0" -# gpt example deps -async-openai = { version = "0.23.4", optional = true } -cc = { version = "1.1.10", optional = true } -anyhow = { version = "1.0.86", optional = true } -tempfile = { version = "3.12.0", optional = true } -tokio = { version = "1.39.2", optional = true, features = ["rt", "rt-multi-thread", "macros"] } -tracing-indicatif = { version = "0.3.6", optional = true } diff --git a/crackers/Cargo.toml b/crackers/Cargo.toml new file mode 100644 index 0000000..207f171 --- /dev/null +++ b/crackers/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "crackers" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "crackers" +required-features = ["bin"] + +[features] +default = ["toml"] +bin = ["dep:tracing-subscriber", "toml", "dep:clap", "dep:anyhow", "dep:tracing-indicatif"] +pyo3 = ["dep:pyo3", "jingle/pyo3"] +toml = ["dep:toml_edit"] +bundled = ["z3/bundled"] + +[dependencies] +jingle = { git = "https://github.com/toolCHAINZ/jingle", branch = "main", features = ["gimli"] } +z3 = { git = "https://github.com/toolCHAINZ/z3.rs.git", branch = "patch-1" } +serde = { version = "1.0.203", features = ["derive"] } +thiserror = "2.0" +tracing = "0.1" +colored = "3.0" +tracing-subscriber = { version = "0.3", optional = true, features = ["env-filter"] } +toml_edit = { version = "0.22", optional = true, features = ["serde"] } +object = "0.36" +clap = { version = "4.0", optional = true , features = ["derive"]} +rand = "0.8" +derive_builder = "0.20" +anyhow = { version = "1.0", optional = true } +tracing-indicatif = { version = "0.3", optional = true } +pyo3 = { version = "0.24", optional = true } diff --git a/src/bench/mod.rs b/crackers/src/bench/mod.rs similarity index 100% rename from src/bench/mod.rs rename to crackers/src/bench/mod.rs diff --git a/src/bin/crackers/main.rs b/crackers/src/bin/crackers/main.rs similarity index 97% rename from src/bin/crackers/main.rs rename to crackers/src/bin/crackers/main.rs index c6f5350..8bf7467 100644 --- a/src/bin/crackers/main.rs +++ b/crackers/src/bin/crackers/main.rs @@ -14,7 +14,7 @@ use z3::{Config, Context}; use crackers::bench::{bench, BenchCommand}; use crackers::config::constraint::{ - Constraint, MemoryEqualityConstraint, PointerRange, PointerRangeConstraints, + ConstraintConfig, MemoryEqualityConstraint, PointerRange, PointerRangeConstraints, StateEqualityConstraint, }; use crackers::config::sleigh::SleighConfig; @@ -64,7 +64,7 @@ fn new(path: PathBuf) -> anyhow::Result<()> { sleigh: SleighConfig { ghidra_path: "/Applications/ghidra".to_string(), }, - constraint: Some(Constraint { + constraint: Some(ConstraintConfig { precondition: Some(StateEqualityConstraint { register: Some(HashMap::from([("ABC".to_string(), 123)])), memory: Some(MemoryEqualityConstraint { diff --git a/src/config/constraint.rs b/crackers/src/config/constraint.rs similarity index 94% rename from src/config/constraint.rs rename to crackers/src/config/constraint.rs index 8e8d3be..78b2313 100644 --- a/src/config/constraint.rs +++ b/crackers/src/config/constraint.rs @@ -4,6 +4,8 @@ use jingle::modeling::{ModeledBlock, ModelingContext, State}; use jingle::sleigh::{ArchInfoProvider, VarNode}; use jingle::varnode::{ResolvedIndirectVarNode, ResolvedVarnode}; use jingle::JingleContext; +#[cfg(feature = "pyo3")] +use pyo3::pyclass; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::ops::Add; @@ -11,14 +13,15 @@ use std::sync::Arc; use tracing::{event, Level}; use z3::ast::{Ast, Bool, BV}; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Constraint { +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] +pub struct ConstraintConfig { pub precondition: Option, pub postcondition: Option, pub pointer: Option, } -impl Constraint { +impl ConstraintConfig { pub fn get_preconditions<'a, T: ArchInfoProvider>( &'a self, sleigh: &'a T, @@ -45,6 +48,7 @@ impl Constraint { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct StateEqualityConstraint { pub register: Option>, pub pointer: Option>, @@ -94,6 +98,7 @@ impl StateEqualityConstraint { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct MemoryEqualityConstraint { pub space: String, pub address: u64, @@ -101,7 +106,8 @@ pub struct MemoryEqualityConstraint { pub value: u8, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct PointerRangeConstraints { pub read: Option>, pub write: Option>, @@ -113,6 +119,7 @@ impl PointerRangeConstraints { } } #[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct PointerRange { pub min: u64, pub max: u64, diff --git a/src/config/error.rs b/crackers/src/config/error.rs similarity index 95% rename from src/config/error.rs rename to crackers/src/config/error.rs index 59ed2a4..9bd8e9a 100644 --- a/src/config/error.rs +++ b/crackers/src/config/error.rs @@ -4,6 +4,8 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum CrackersConfigError { + #[error("Invalid log level")] + InvalidLogLevel, #[error("An error reading a file referenced from the config")] Io(#[from] std::io::Error), #[error("An error parsing a file with gimli object: {0}")] diff --git a/src/config/meta.rs b/crackers/src/config/meta.rs similarity index 87% rename from src/config/meta.rs rename to crackers/src/config/meta.rs index 4cb5383..aecb8be 100644 --- a/src/config/meta.rs +++ b/crackers/src/config/meta.rs @@ -1,11 +1,13 @@ +#[cfg(feature = "pyo3")] +use pyo3::pyclass; use rand::random; use serde::{Deserialize, Serialize}; use tracing::Level; #[derive(Copy, Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] +#[cfg_attr(feature = "pyo3", pyclass)] pub enum CrackersLogLevel { - #[serde(rename = "TRACE")] Trace, Debug, Warn, @@ -26,6 +28,7 @@ impl From for Level { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct MetaConfig { #[serde(default = "random")] pub seed: i64, diff --git a/src/config/mod.rs b/crackers/src/config/mod.rs similarity index 77% rename from src/config/mod.rs rename to crackers/src/config/mod.rs index 1af459f..5dfb87f 100644 --- a/src/config/mod.rs +++ b/crackers/src/config/mod.rs @@ -1,13 +1,12 @@ -use serde::{Deserialize, Serialize}; - -use crate::config::constraint::Constraint; +use crate::config::constraint::ConstraintConfig; use crate::config::meta::MetaConfig; use crate::config::sleigh::SleighConfig; use crate::config::specification::SpecificationConfig; use crate::config::synthesis::SynthesisConfig; use crate::error::CrackersError; -use crate::gadget::library::builder::GadgetLibraryParams; +use crate::gadget::library::builder::GadgetLibraryConfig; use crate::synthesis::builder::{SynthesisParams, SynthesisParamsBuilder}; +use serde::{Deserialize, Serialize}; pub mod constraint; pub mod error; @@ -18,14 +17,18 @@ pub mod specification; pub mod synthesis; #[derive(Clone, Debug, Deserialize, Serialize)] +/// This struct represents the serializable configuration found +/// in a crackers .toml file. Once parsed from a file or constructed +/// programmatically, it can be used to produce a [crate::synthesis::builder::SynthesisParams] +/// struct, which can run the actual algorithm pub struct CrackersConfig { #[serde(default)] pub meta: MetaConfig, pub specification: SpecificationConfig, - pub library: GadgetLibraryParams, + pub library: GadgetLibraryConfig, pub sleigh: SleighConfig, pub synthesis: SynthesisConfig, - pub constraint: Option, + pub constraint: Option, } impl CrackersConfig { diff --git a/src/config/object.rs b/crackers/src/config/object.rs similarity index 100% rename from src/config/object.rs rename to crackers/src/config/object.rs diff --git a/crackers/src/config/sleigh.rs b/crackers/src/config/sleigh.rs new file mode 100644 index 0000000..08ae135 --- /dev/null +++ b/crackers/src/config/sleigh.rs @@ -0,0 +1,38 @@ +use jingle::sleigh::context::SleighContextBuilder; +#[cfg(feature = "pyo3")] +use pyo3::{pyclass, pymethods}; +use serde::{Deserialize, Serialize}; + +use crate::config::error::CrackersConfigError; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] +pub struct SleighConfig { + pub ghidra_path: String, +} + +impl SleighConfig { + pub fn context_builder(&self) -> Result { + let b = SleighContextBuilder::load_ghidra_installation(&self.ghidra_path)?; + Ok(b) + } +} + +#[cfg(feature = "pyo3")] +#[pymethods] +impl SleighConfig { + #[new] + fn new(ghidra_path: String) -> Self { + SleighConfig { ghidra_path } + } + + #[getter] + fn ghidra_path(&self) -> String { + self.ghidra_path.clone() + } + + #[setter] + fn set_ghidra_path(&mut self, ghidra_path: String) { + self.ghidra_path = ghidra_path; + } +} diff --git a/src/config/specification.rs b/crackers/src/config/specification.rs similarity index 62% rename from src/config/specification.rs rename to crackers/src/config/specification.rs index 2d52433..5cf1d6a 100644 --- a/src/config/specification.rs +++ b/crackers/src/config/specification.rs @@ -3,6 +3,8 @@ use std::fs; use jingle::sleigh::context::loaded::LoadedSleighContext; use jingle::sleigh::Instruction; use object::{File, Object, ObjectSymbol}; +#[cfg(feature = "pyo3")] +use pyo3::{pyclass, pymethods}; use serde::{Deserialize, Serialize}; use crate::config::error::CrackersConfigError; @@ -11,12 +13,56 @@ use crate::config::object::load_sleigh_spec; use crate::config::sleigh::SleighConfig; #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] pub struct SpecificationConfig { pub path: String, pub max_instructions: usize, pub base_address: Option, } +#[cfg(feature = "pyo3")] +#[pymethods] +impl SpecificationConfig { + #[new] + fn new(path: String, max_instructions: usize, base_address: Option) -> Self { + Self { + path, + max_instructions, + base_address, + } + } + + #[getter] + fn get_path(&self) -> String { + self.path.clone() + } + + #[setter] + fn set_path(&mut self, path: String) { + self.path = path; + } + + #[getter] + fn get_max_instructions(&self) -> usize { + self.max_instructions + } + + #[setter] + fn set_max_instructions(&mut self, max_instructions: usize) { + self.max_instructions = max_instructions; + } + + #[getter] + fn get_base_address(&self) -> Option { + self.base_address + } + + #[setter] + fn set_base_address(&mut self, address: u64) { + self.base_address = Some(address); + } +} + impl SpecificationConfig { fn load_sleigh<'a>( &self, diff --git a/crackers/src/config/synthesis.rs b/crackers/src/config/synthesis.rs new file mode 100644 index 0000000..4ec1430 --- /dev/null +++ b/crackers/src/config/synthesis.rs @@ -0,0 +1,84 @@ +#[cfg(feature = "pyo3")] +use pyo3::{pyclass, pymethods}; +use serde::{Deserialize, Serialize}; + +use crate::synthesis::builder::SynthesisSelectionStrategy; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))] +pub struct SynthesisConfig { + pub strategy: SynthesisSelectionStrategy, + pub max_candidates_per_slot: usize, + pub parallel: usize, + pub combine_instructions: bool, +} + +impl Default for SynthesisConfig { + fn default() -> Self { + SynthesisConfig { + strategy: SynthesisSelectionStrategy::OptimizeStrategy, + max_candidates_per_slot: 50, + parallel: 4, + combine_instructions: true, + } + } +} + +#[cfg(feature = "pyo3")] +#[pymethods] +impl SynthesisConfig { + #[new] + fn new( + strategy: SynthesisSelectionStrategy, + max_candidates_per_slot: usize, + parallel: usize, + combine_instructions: bool, + ) -> Self { + SynthesisConfig { + strategy, + max_candidates_per_slot, + parallel, + combine_instructions, + } + } + + #[getter] + fn strategy(&self) -> SynthesisSelectionStrategy { + self.strategy + } + + #[setter] + fn set_strategy(&mut self, strategy: SynthesisSelectionStrategy) { + self.strategy = strategy; + } + + #[getter] + fn max_candidates_per_slot(&self) -> usize { + self.max_candidates_per_slot + } + + #[setter] + fn set_max_candidates_per_slot(&mut self, max_candidates_per_slot: usize) { + self.max_candidates_per_slot = max_candidates_per_slot + } + + #[getter] + fn parallel(&self) -> usize { + self.parallel + } + + #[setter] + fn set_parallel(&mut self, parallel: usize) { + self.parallel = parallel + } + + #[getter] + fn combine_instructions(&self) -> bool { + self.combine_instructions + } + + #[setter] + fn set_combine_instructions(&mut self, combine_instructions: bool) { + self.combine_instructions = combine_instructions + } +} diff --git a/src/error.rs b/crackers/src/error.rs similarity index 76% rename from src/error.rs rename to crackers/src/error.rs index 8b3aef9..01a1f68 100644 --- a/src/error.rs +++ b/crackers/src/error.rs @@ -1,8 +1,12 @@ use jingle::JingleError; +#[cfg(feature = "pyo3")] +use pyo3::exceptions::PyRuntimeError; +#[cfg(feature = "pyo3")] +use pyo3::PyErr; use thiserror::Error; use crate::config::error::CrackersConfigError; -use crate::gadget::library::builder::GadgetLibraryParamsBuilderError; +use crate::gadget::library::builder::GadgetLibraryConfigBuilderError; use crate::synthesis::builder::SynthesisParamsBuilderError; #[derive(Debug, Error)] @@ -30,7 +34,14 @@ pub enum CrackersError { #[error("Jingle error")] Jingle(#[from] JingleError), #[error("Invalid gadget library params")] - LibraryConfig(#[from] GadgetLibraryParamsBuilderError), + LibraryConfig(#[from] GadgetLibraryConfigBuilderError), #[error("Invalid synthesis params")] SynthesisParams(#[from] SynthesisParamsBuilderError), } + +#[cfg(feature = "pyo3")] +impl From for PyErr { + fn from(value: CrackersError) -> Self { + PyRuntimeError::new_err(value.to_string()) + } +} diff --git a/src/gadget/another_iterator.rs b/crackers/src/gadget/another_iterator.rs similarity index 100% rename from src/gadget/another_iterator.rs rename to crackers/src/gadget/another_iterator.rs diff --git a/src/gadget/candidates.rs b/crackers/src/gadget/candidates.rs similarity index 100% rename from src/gadget/candidates.rs rename to crackers/src/gadget/candidates.rs diff --git a/src/gadget/error.rs b/crackers/src/gadget/error.rs similarity index 100% rename from src/gadget/error.rs rename to crackers/src/gadget/error.rs diff --git a/src/gadget/iterator.rs b/crackers/src/gadget/iterator.rs similarity index 100% rename from src/gadget/iterator.rs rename to crackers/src/gadget/iterator.rs diff --git a/src/gadget/library/builder.rs b/crackers/src/gadget/library/builder.rs similarity index 94% rename from src/gadget/library/builder.rs rename to crackers/src/gadget/library/builder.rs index 546d5d1..4d1da1f 100644 --- a/src/gadget/library/builder.rs +++ b/crackers/src/gadget/library/builder.rs @@ -2,6 +2,8 @@ use std::collections::HashSet; use derive_builder::Builder; use jingle::sleigh::OpCode; +#[cfg(feature = "pyo3")] +use pyo3::pyclass; use serde::{Deserialize, Serialize}; use crate::config::error::CrackersConfigError; @@ -11,7 +13,8 @@ use crate::gadget::library::GadgetLibrary; #[derive(Clone, Debug, Default, Builder, Deserialize, Serialize)] #[builder(default)] -pub struct GadgetLibraryParams { +#[cfg_attr(feature = "pyo3", pyclass)] +pub struct GadgetLibraryConfig { pub max_gadget_length: usize, #[serde(skip, default = "default_blacklist")] pub operation_blacklist: HashSet, @@ -20,7 +23,7 @@ pub struct GadgetLibraryParams { pub base_address: Option, } -impl GadgetLibraryParams { +impl GadgetLibraryConfig { pub fn build(&self, sleigh: &SleighConfig) -> Result { let mut library_sleigh = load_sleigh(&self.path, sleigh)?; if let Some(addr) = self.base_address { diff --git a/src/gadget/library/image.rs b/crackers/src/gadget/library/image.rs similarity index 100% rename from src/gadget/library/image.rs rename to crackers/src/gadget/library/image.rs diff --git a/src/gadget/library/mod.rs b/crackers/src/gadget/library/mod.rs similarity index 96% rename from src/gadget/library/mod.rs rename to crackers/src/gadget/library/mod.rs index 617bead..10d1cc7 100644 --- a/src/gadget/library/mod.rs +++ b/crackers/src/gadget/library/mod.rs @@ -8,7 +8,7 @@ use rand::SeedableRng; use tracing::{event, Level}; use crate::gadget::another_iterator::TraceCandidateIterator; -use crate::gadget::library::builder::GadgetLibraryParams; +use crate::gadget::library::builder::GadgetLibraryConfig; use crate::gadget::Gadget; pub mod builder; @@ -39,7 +39,7 @@ impl GadgetLibrary { } pub(super) fn build_from_image( sleigh: LoadedSleighContext, - builder: &GadgetLibraryParams, + builder: &GadgetLibraryConfig, ) -> Result { let spaces: Vec<_> = sleigh.get_all_space_info().cloned().collect(); let mut registers = vec![]; @@ -119,7 +119,7 @@ mod tests { use std::fs; use std::path::Path; - use crate::gadget::library::builder::GadgetLibraryParams; + use crate::gadget::library::builder::GadgetLibraryConfig; use crate::gadget::library::GadgetLibrary; use jingle::sleigh::context::SleighContextBuilder; use object::File; @@ -135,6 +135,6 @@ mod tests { let sleigh = builder.build("x86:LE:64:default").unwrap(); let bin_sleigh = sleigh.initialize_with_image(file).unwrap(); let _lib = - GadgetLibrary::build_from_image(bin_sleigh, &GadgetLibraryParams::default()).unwrap(); + GadgetLibrary::build_from_image(bin_sleigh, &GadgetLibraryConfig::default()).unwrap(); } } diff --git a/src/gadget/mod.rs b/crackers/src/gadget/mod.rs similarity index 100% rename from src/gadget/mod.rs rename to crackers/src/gadget/mod.rs diff --git a/src/gadget/signature.rs b/crackers/src/gadget/signature.rs similarity index 100% rename from src/gadget/signature.rs rename to crackers/src/gadget/signature.rs diff --git a/src/lib.rs b/crackers/src/lib.rs similarity index 100% rename from src/lib.rs rename to crackers/src/lib.rs diff --git a/src/synthesis/assignment_model.rs b/crackers/src/synthesis/assignment_model.rs similarity index 79% rename from src/synthesis/assignment_model.rs rename to crackers/src/synthesis/assignment_model.rs index 4801b9b..498969a 100644 --- a/src/synthesis/assignment_model.rs +++ b/crackers/src/synthesis/assignment_model.rs @@ -21,27 +21,27 @@ impl<'ctx, T: ModelingContext<'ctx>> AssignmentModel<'ctx, T> { &self.model } - pub fn initial_state(&'ctx self) -> Option<&'ctx State<'ctx>> { + pub fn initial_state<'a>(&'a self) -> Option<&'a State<'ctx>> { self.gadgets.first().map(|f| f.get_original_state()) } - pub fn final_state(&'ctx self) -> Option<&'ctx State<'ctx>> { + pub fn final_state<'a>(&'a self) -> Option<&'a State<'ctx>> { self.gadgets.last().map(|f| f.get_final_state()) } - pub fn read_original(&'ctx self, vn: GeneralizedVarNode) -> Option> { + pub fn read_original<'a>(&'a self, vn: GeneralizedVarNode) -> Option> { self.initial_state().and_then(|f| f.read(vn).ok()) } - pub fn read_output(&'ctx self, vn: GeneralizedVarNode) -> Option> { + pub fn read_output<'a>(&'a self, vn: GeneralizedVarNode) -> Option> { self.final_state().and_then(|f| f.read(vn).ok()) } - pub fn read_resolved(&'ctx self, vn: &ResolvedVarnode<'ctx>) -> Option> { + pub fn read_resolved<'a>(&'a self, vn: &ResolvedVarnode<'ctx>) -> Option> { self.final_state().and_then(|f| f.read_resolved(vn).ok()) } - pub fn print_trace_of_reg(&'ctx self, reg: &str) { + pub fn print_trace_of_reg(&self, reg: &str) { let r = self.final_state().unwrap().get_register(reg).unwrap(); for gadget in &self.gadgets { let val = gadget.get_original_state().read_varnode(r).unwrap(); @@ -51,7 +51,7 @@ impl<'ctx, T: ModelingContext<'ctx>> AssignmentModel<'ctx, T> { } } - pub fn initial_reg(&'ctx self, reg: &str) -> Option> { + pub fn initial_reg<'a>(&'a self, reg: &str) -> Option> { let r = self.final_state().unwrap().get_register(reg).unwrap(); let val = self.gadgets[0] .get_original_state() @@ -63,7 +63,6 @@ impl<'ctx, T: ModelingContext<'ctx>> AssignmentModel<'ctx, T> { impl<'ctx, T: ModelingContext<'ctx> + Display> Display for AssignmentModel<'ctx, T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - dbg!("hello"); writeln!(f, "Gadgets:\n")?; for block in &self.gadgets { writeln!(f, "{}\n", block)?; diff --git a/src/synthesis/builder.rs b/crackers/src/synthesis/builder.rs similarity index 93% rename from src/synthesis/builder.rs rename to crackers/src/synthesis/builder.rs index 10a15a4..f3e8c18 100644 --- a/src/synthesis/builder.rs +++ b/crackers/src/synthesis/builder.rs @@ -4,17 +4,20 @@ use derive_builder::Builder; use jingle::modeling::{ModeledBlock, State}; use jingle::sleigh::Instruction; use jingle::JingleContext; +#[cfg(feature = "pyo3")] +use pyo3::pyclass; use serde::{Deserialize, Serialize}; use z3::ast::Bool; use z3::Context; use crate::error::CrackersError; -use crate::gadget::library::builder::GadgetLibraryParams; +use crate::gadget::library::builder::GadgetLibraryConfig; use crate::gadget::library::GadgetLibrary; use crate::synthesis::combined::CombinedAssignmentSynthesis; use crate::synthesis::AssignmentSynthesis; #[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "pyo3", pyclass)] pub enum SynthesisSelectionStrategy { #[serde(rename = "sat")] SatStrategy, @@ -34,7 +37,7 @@ pub type TransitionConstraintGenerator = dyn for<'a> Fn(&JingleContext<'a>, &Mod #[derive(Clone, Debug)] pub enum Library { Library(GadgetLibrary), - Params(GadgetLibraryParams), + Params(GadgetLibraryConfig), } #[derive(Clone, Builder)] pub struct SynthesisParams { diff --git a/src/synthesis/combined.rs b/crackers/src/synthesis/combined.rs similarity index 100% rename from src/synthesis/combined.rs rename to crackers/src/synthesis/combined.rs diff --git a/src/synthesis/mod.rs b/crackers/src/synthesis/mod.rs similarity index 97% rename from src/synthesis/mod.rs rename to crackers/src/synthesis/mod.rs index 3c5220c..d9aa832 100644 --- a/src/synthesis/mod.rs +++ b/crackers/src/synthesis/mod.rs @@ -183,7 +183,6 @@ impl<'ctx> AssignmentSynthesis<'ctx> { "Theory returned SAT for {:?}!", response.assignment ); - dbg!("Terminating remaining workers"); req_channels.clear(); for x in &kill_senders { x.send(()).unwrap(); @@ -193,11 +192,14 @@ impl<'ctx> AssignmentSynthesis<'ctx> { let jingle = JingleContext::new(self.z3, self.library.as_ref()); let a: PcodeAssignment<'ctx> = t.build_assignment(&jingle, response.assignment)?; - dbg!("Workers Terminated; building and checking model"); + event!( + Level::DEBUG, + "Workers Terminated; building and checking model" + ); let solver = Solver::new(self.z3); let jingle = JingleContext::new(self.z3, self.library.as_ref()); let model = a.check(&jingle, &solver)?; - dbg!("Model built"); + event!(Level::DEBUG, "Model built"); return Ok(DecisionResult::AssignmentFound(model)); } Some(c) => { diff --git a/src/synthesis/partition_iterator.rs b/crackers/src/synthesis/partition_iterator.rs similarity index 100% rename from src/synthesis/partition_iterator.rs rename to crackers/src/synthesis/partition_iterator.rs diff --git a/src/synthesis/pcode_theory/builder.rs b/crackers/src/synthesis/pcode_theory/builder.rs similarity index 100% rename from src/synthesis/pcode_theory/builder.rs rename to crackers/src/synthesis/pcode_theory/builder.rs diff --git a/src/synthesis/pcode_theory/conflict_clause.rs b/crackers/src/synthesis/pcode_theory/conflict_clause.rs similarity index 100% rename from src/synthesis/pcode_theory/conflict_clause.rs rename to crackers/src/synthesis/pcode_theory/conflict_clause.rs diff --git a/src/synthesis/pcode_theory/mod.rs b/crackers/src/synthesis/pcode_theory/mod.rs similarity index 100% rename from src/synthesis/pcode_theory/mod.rs rename to crackers/src/synthesis/pcode_theory/mod.rs diff --git a/src/synthesis/pcode_theory/pcode_assignment.rs b/crackers/src/synthesis/pcode_theory/pcode_assignment.rs similarity index 100% rename from src/synthesis/pcode_theory/pcode_assignment.rs rename to crackers/src/synthesis/pcode_theory/pcode_assignment.rs diff --git a/src/synthesis/pcode_theory/theory_constraint.rs b/crackers/src/synthesis/pcode_theory/theory_constraint.rs similarity index 100% rename from src/synthesis/pcode_theory/theory_constraint.rs rename to crackers/src/synthesis/pcode_theory/theory_constraint.rs diff --git a/src/synthesis/pcode_theory/theory_worker.rs b/crackers/src/synthesis/pcode_theory/theory_worker.rs similarity index 100% rename from src/synthesis/pcode_theory/theory_worker.rs rename to crackers/src/synthesis/pcode_theory/theory_worker.rs diff --git a/src/synthesis/selection_strategy/mod.rs b/crackers/src/synthesis/selection_strategy/mod.rs similarity index 95% rename from src/synthesis/selection_strategy/mod.rs rename to crackers/src/synthesis/selection_strategy/mod.rs index 7f3bf9d..2dd1233 100644 --- a/src/synthesis/selection_strategy/mod.rs +++ b/crackers/src/synthesis/selection_strategy/mod.rs @@ -1,4 +1,6 @@ use jingle::modeling::{ModeledBlock, ModeledInstruction}; +#[cfg(feature = "pyo3")] +use pyo3::pyclass; use z3::Context; use crate::error::CrackersError; @@ -49,9 +51,11 @@ pub enum AssignmentResult { Success(SlotAssignments), Failure(SelectionFailure), } + #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "pyo3", pyclass)] pub struct SelectionFailure { - pub indexes: Vec, + pub indices: Vec, } pub trait SelectionStrategy<'ctx> { fn initialize(z3: &'ctx Context, choices: &[Vec]) -> Self; diff --git a/src/synthesis/selection_strategy/optimization_problem.rs b/crackers/src/synthesis/selection_strategy/optimization_problem.rs similarity index 99% rename from src/synthesis/selection_strategy/optimization_problem.rs rename to crackers/src/synthesis/selection_strategy/optimization_problem.rs index 53b68fd..7beff8f 100644 --- a/src/synthesis/selection_strategy/optimization_problem.rs +++ b/crackers/src/synthesis/selection_strategy/optimization_problem.rs @@ -26,7 +26,7 @@ impl<'ctx> OptimizationProblem<'ctx> { fn get_unsat_reason(&self, core: Vec>) -> SelectionFailure { SelectionFailure { - indexes: self + indices: self .index_bools .iter() .enumerate() diff --git a/src/synthesis/selection_strategy/sat_problem.rs b/crackers/src/synthesis/selection_strategy/sat_problem.rs similarity index 99% rename from src/synthesis/selection_strategy/sat_problem.rs rename to crackers/src/synthesis/selection_strategy/sat_problem.rs index 60c8461..f9a38bf 100644 --- a/src/synthesis/selection_strategy/sat_problem.rs +++ b/crackers/src/synthesis/selection_strategy/sat_problem.rs @@ -44,7 +44,7 @@ impl<'ctx> SatProblem<'ctx> { fn get_unsat_reason(&self, core: Vec>) -> SelectionFailure { SelectionFailure { - indexes: self + indices: self .index_bools .iter() .enumerate() diff --git a/src/synthesis/slot_assignments/display.rs b/crackers/src/synthesis/slot_assignments/display.rs similarity index 100% rename from src/synthesis/slot_assignments/display.rs rename to crackers/src/synthesis/slot_assignments/display.rs diff --git a/src/synthesis/slot_assignments/mod.rs b/crackers/src/synthesis/slot_assignments/mod.rs similarity index 100% rename from src/synthesis/slot_assignments/mod.rs rename to crackers/src/synthesis/slot_assignments/mod.rs diff --git a/crackers_python/.gitignore b/crackers_python/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/crackers_python/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/crackers_python/Cargo.toml b/crackers_python/Cargo.toml new file mode 100644 index 0000000..003719f --- /dev/null +++ b/crackers_python/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crackers_python" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "crackers" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.24", features = ["extension-module", "py-clone"] } +crackers = {path = "../crackers", features = ["pyo3"]} +jingle = {git = "https://github.com/toolCHAINZ/jingle.git", branch = "main", features = ["pyo3"]} +toml_edit = "0.22.22" +z3 = { git = "https://github.com/toolCHAINZ/z3.rs.git", branch = "patch-1" } + +[dev-dependencies] +pyo3 = { version = "0.24", features = ["extension-module"] } +z3 = { git = "https://github.com/toolCHAINZ/z3.rs.git", branch = "patch-1" } diff --git a/crackers_python/build.rs b/crackers_python/build.rs new file mode 100644 index 0000000..4d81ffb --- /dev/null +++ b/crackers_python/build.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +fn main() { + // Run the Python script to get the venv's lib directory + let output = Command::new("python3") + .arg("find_venv_library_path.py") + .output() + .expect("Failed to execute Python script"); + + if !output.status.success() { + panic!("cargo:warning=Could not find python's z3, this wheel is unlikely to work."); + } else { + let venv_lib = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let z3_python_lib = std::path::Path::new(&venv_lib).join("z3").join("lib"); + println!("cargo:rustc-link-search=native={}", z3_python_lib.display()); + std::env::set_var("Z3_PATH", z3_python_lib); + } + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=Z3_PATH") +} diff --git a/crackers_python/find_venv_library_path.py b/crackers_python/find_venv_library_path.py new file mode 100644 index 0000000..a7b5259 --- /dev/null +++ b/crackers_python/find_venv_library_path.py @@ -0,0 +1,9 @@ +import sys +import os +import sysconfig + +# Get the virtual environment's lib directory +venv_lib = sysconfig.get_paths()["purelib"] + +# Print the path so that we can capture it in build.rs +print(venv_lib) \ No newline at end of file diff --git a/crackers_python/pyproject.toml b/crackers_python/pyproject.toml new file mode 100644 index 0000000..2a29130 --- /dev/null +++ b/crackers_python/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = [ + "maturin>=1.8,<2.0", + "z3-solver==4.12.4.0", + +] +build-backend = "maturin" + +[project] +name = "crackers" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +dependencies = [ + "z3-solver==4.12.4.0", +] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/crackers_python/src/config/constraint.rs b/crackers_python/src/config/constraint.rs new file mode 100644 index 0000000..4d5d14f --- /dev/null +++ b/crackers_python/src/config/constraint.rs @@ -0,0 +1,159 @@ +use crackers::config::constraint::{ + ConstraintConfig, MemoryEqualityConstraint, PointerRange, PointerRangeConstraints, + StateEqualityConstraint, +}; +use pyo3::{pyclass, Py, PyErr, Python}; +use std::collections::HashMap; + +#[pyclass(get_all, set_all)] +#[derive(Clone)] +pub struct PythonConstraintConfig { + pub precondition: Py, + pub postcondition: Py, + pub pointer: Py, +} + +impl TryFrom for PythonConstraintConfig { + type Error = PyErr; + + fn try_from(value: ConstraintConfig) -> Result { + let precondition: PythonStateEqualityConstraint = value + .precondition + .and_then(|f| f.try_into().ok()) + .unwrap_or_default(); + let postcondition: PythonStateEqualityConstraint = value + .postcondition + .and_then(|f| f.try_into().ok()) + .unwrap_or_default(); + let pointer: PythonPointerRangeConstraints = value + .pointer + .and_then(|f| f.try_into().ok()) + .unwrap_or_default(); + Python::with_gil(|py| { + Ok(Self { + precondition: Py::new(py, precondition)?, + postcondition: Py::new(py, postcondition)?, + pointer: Py::new(py, pointer)?, + }) + }) + } +} + +impl TryFrom for ConstraintConfig { + type Error = PyErr; + + fn try_from(value: PythonConstraintConfig) -> Result { + Python::with_gil(|py| { + let precondition = Some(value.precondition.borrow(py).clone().try_into()?); + let postcondition = Some(value.postcondition.borrow(py).clone().try_into()?); + let pointer = Some(value.pointer.borrow(py).clone().try_into()?); + Ok(ConstraintConfig { + precondition, + postcondition, + pointer, + }) + }) + } +} + +#[derive(Default, Clone)] +#[pyclass(get_all)] +pub struct PythonStateEqualityConstraint { + pub register: HashMap, + pub pointer: HashMap, + #[pyo3(set)] + pub memory: Option>, +} + +impl TryFrom for PythonStateEqualityConstraint { + type Error = PyErr; + + fn try_from(value: StateEqualityConstraint) -> Result { + Python::with_gil(|py| { + let mem = value.memory.map(|f| Py::new(py, f).unwrap()); + Ok(Self { + register: value.register.clone().unwrap_or_default(), + pointer: value.pointer.clone().unwrap_or_default(), + memory: mem, + }) + }) + } +} + +impl TryFrom for StateEqualityConstraint { + type Error = PyErr; + + fn try_from(value: PythonStateEqualityConstraint) -> Result { + let register = if value.register.is_empty() { + None + } else { + Some(value.register) + }; + let pointer = if value.pointer.is_empty() { + None + } else { + Some(value.pointer) + }; + Python::with_gil(|py| { + let memory = value.memory.map(|a| a.borrow(py).clone()); + Ok(Self { + register, + pointer, + memory, + }) + }) + } +} + +#[pyclass(get_all)] +#[derive(Default)] +pub struct PythonPointerRangeConstraints { + pub read: Vec>, + pub write: Vec>, +} + +impl Clone for PythonPointerRangeConstraints { + fn clone(&self) -> Self { + Python::with_gil(|_| Self { + read: self.read.to_vec(), + write: self.write.to_vec(), + }) + } +} + +impl TryFrom for PythonPointerRangeConstraints { + type Error = PyErr; + fn try_from(value: PointerRangeConstraints) -> Result { + Python::with_gil(|py| { + let read: Result>, PyErr> = value + .read + .into_iter() + .flatten() + .map(|f| Py::new(py, f)) + .collect(); + let write: Result>, PyErr> = value + .write + .into_iter() + .flatten() + .map(|f| Py::new(py, f)) + .collect(); + Ok(Self { + read: read?, + write: write?, + }) + }) + } +} + +impl TryFrom for PointerRangeConstraints { + type Error = PyErr; + fn try_from(value: PythonPointerRangeConstraints) -> Result { + Python::with_gil(|py| { + let read: Vec<_> = value.read.iter().map(|f| *f.borrow(py)).collect(); + let read = if !read.is_empty() { Some(read) } else { None }; + let write: Vec<_> = value.write.iter().map(|f| *f.borrow(py)).collect(); + let write = if !write.is_empty() { Some(write) } else { None }; + Ok(Self { read, write }) + }) + } +} diff --git a/crackers_python/src/config/mod.rs b/crackers_python/src/config/mod.rs new file mode 100644 index 0000000..60787bb --- /dev/null +++ b/crackers_python/src/config/mod.rs @@ -0,0 +1,83 @@ +use crate::config::constraint::PythonConstraintConfig; +use crate::synthesis::PythonSynthesisParams; +use crackers::config::meta::MetaConfig; +use crackers::config::sleigh::SleighConfig; +use crackers::config::specification::SpecificationConfig; +use crackers::config::synthesis::SynthesisConfig; +use crackers::config::CrackersConfig; +use crackers::gadget::library::builder::GadgetLibraryConfig; +use pyo3::exceptions::PyRuntimeError; +use pyo3::types::PyType; +use pyo3::{pyclass, pymethods, Bound, Py, PyErr, PyResult, Python}; + +mod constraint; + +#[pyclass(get_all, set_all, name = "CrackersConfig")] +pub struct PythonCrackersConfig { + pub meta: Py, + pub spec: Py, + pub library: Py, + pub sleigh: Py, + pub synthesis: Py, + pub constraint: Py, +} + +impl TryFrom for PythonCrackersConfig { + type Error = PyErr; + + fn try_from(value: CrackersConfig) -> Result { + Python::with_gil(|py| { + let constraint: PythonConstraintConfig = value + .constraint + .ok_or(PyRuntimeError::new_err("Bad constraint"))? + .try_into()?; + Ok(Self { + meta: Py::new(py, value.meta)?, + spec: Py::new(py, value.specification)?, + library: Py::new(py, value.library)?, + sleigh: Py::new(py, value.sleigh)?, + synthesis: Py::new(py, value.synthesis)?, + constraint: Py::new(py, constraint)?, + }) + }) + } +} + +impl TryFrom<&PythonCrackersConfig> for CrackersConfig { + type Error = PyErr; + + fn try_from(value: &PythonCrackersConfig) -> Result { + Python::with_gil(|py| { + Ok(CrackersConfig { + meta: value.meta.borrow(py).clone(), + specification: value.spec.borrow(py).clone(), + library: value.library.borrow(py).clone(), + sleigh: value.sleigh.borrow(py).clone(), + synthesis: value.synthesis.borrow(py).clone(), + constraint: value + .constraint + .extract::(py)? + .try_into() + .ok(), + }) + }) + } +} + +#[pymethods] +impl PythonCrackersConfig { + #[classmethod] + pub fn from_toml(_: &Bound<'_, PyType>, path: &str) -> PyResult { + let cfg_bytes = std::fs::read(path)?; + let s = String::from_utf8(cfg_bytes)?; + let p: CrackersConfig = + toml_edit::de::from_str(&s).map_err(|e| PyRuntimeError::new_err(format!("{}", e)))?; + p.try_into() + } + + pub fn resolve_config(&self) -> PyResult { + let cfg = CrackersConfig::try_from(self)?; + let syn = cfg.resolve()?; + Ok(PythonSynthesisParams { inner: syn }) + } +} diff --git a/crackers_python/src/decision/assignment_model/mod.rs b/crackers_python/src/decision/assignment_model/mod.rs new file mode 100644 index 0000000..d4c7fc2 --- /dev/null +++ b/crackers_python/src/decision/assignment_model/mod.rs @@ -0,0 +1,141 @@ +mod model_varnode_iterator; + +use crate::decision::assignment_model::model_varnode_iterator::ModelVarNodeIterator; +use crackers::synthesis::assignment_model::AssignmentModel; +use jingle::modeling::{ModeledBlock, ModelingContext, State}; +use jingle::python::modeled_block::PythonModeledBlock; +use jingle::python::resolved_varnode::PythonResolvedVarNode; +use jingle::python::state::PythonState; +use jingle::python::varode_iterator::VarNodeIterator; +use jingle::python::z3::ast::{TryFromPythonZ3, TryIntoPythonZ3}; +use jingle::sleigh::{SpaceType, VarNode, VarNodeDisplay}; +use jingle::varnode::{ResolvedIndirectVarNode, ResolvedVarnode}; +use pyo3::exceptions::PyRuntimeError; +use pyo3::{pyclass, pymethods, Py, PyAny, PyResult}; +use z3::ast::BV; + +#[pyclass(unsendable)] +pub struct PythonAssignmentModel { + pub inner: AssignmentModel<'static, ModeledBlock<'static>>, +} + +impl PythonAssignmentModel { + fn eval_vn( + &self, + state: &State<'static>, + vn: PythonResolvedVarNode, + completion: bool, + ) -> Option<(String, BV<'static>)> { + match vn { + PythonResolvedVarNode::Direct(a) => { + let bv = state.read_varnode(&VarNode::from(a.clone())).ok()?; + let val = self.inner.model().eval(&bv, completion)?; + Some((format!("{}", a), val)) + } + PythonResolvedVarNode::Indirect(i) => { + let pointer_value = self.inner.model().eval(&i.inner.pointer, completion)?; + let space_name = i.inner.pointer_space_info.name.clone(); + let access_size = i.inner.access_size_bytes; + let pointed_value = self.inner.model().eval( + &state + .read_resolved(&ResolvedVarnode::Indirect(ResolvedIndirectVarNode { + access_size_bytes: i.inner.access_size_bytes, + pointer_location: i.inner.pointer_location, + pointer: i.inner.pointer, + pointer_space_idx: i.inner.pointer_space_info.index, + })) + .ok()?, + completion, + )?; + Some(( + format!("{space_name}[{pointer_value}]:{access_size:x}"), + pointed_value, + )) + } + } + } +} + +#[pymethods] +impl PythonAssignmentModel { + fn eval_bv(&self, bv: Py, model_completion: bool) -> PyResult> { + let bv = BV::try_from_python(bv)?; + let val = self + .inner + .model() + .eval(&bv, model_completion) + .ok_or(PyRuntimeError::new_err("Could not eval model"))?; + val.try_into_python() + } + + pub fn initial_state(&self) -> Option { + self.inner.gadgets.first().map(|f| PythonState { + state: f.get_original_state().clone(), + }) + } + + pub fn final_state(&self) -> Option { + self.inner.gadgets.last().map(|f| PythonState { + state: f.get_final_state().clone(), + }) + } + + pub fn gadgets(&self) -> Vec { + self.inner + .gadgets + .clone() + .into_iter() + .map(|g| PythonModeledBlock { instr: g }) + .collect() + } + + pub fn inputs(&self) -> Option { + let hi = self + .gadgets() + .into_iter() + .flat_map(|g| g.get_input_vns().ok()) + .flatten() + .filter(|a| { + if let PythonResolvedVarNode::Direct(VarNodeDisplay::Raw(r)) = a { + r.space_info._type == SpaceType::IPTR_PROCESSOR + } else { + true + } + }); + Some(VarNodeIterator::new(hi)) + } + + pub fn outputs(&self) -> Option { + let hi = self + .gadgets() + .into_iter() + .flat_map(|g| g.get_output_vns().ok()) + .flatten() + .filter(|a| { + if let PythonResolvedVarNode::Direct(VarNodeDisplay::Raw(r)) = a { + r.space_info._type == SpaceType::IPTR_PROCESSOR + } else { + true + } + }); + Some(VarNodeIterator::new(hi)) + } + + pub fn input_summary(&self, model_completion: bool) -> Option { + let initial = self.initial_state()?.state; + let iter: Vec<_> = self + .inputs()? + .flat_map(|p| self.eval_vn(&initial, p, model_completion)) + .collect(); + Some(ModelVarNodeIterator::new(iter.into_iter())) + } + + pub fn output_summary(&self, model_completion: bool) -> Option { + let initial = self.final_state()?.state; + let iter: Vec<_> = self + .outputs()? + .flat_map(|p| self.eval_vn(&initial, p, model_completion)) + .collect(); + Some(ModelVarNodeIterator::new(iter.into_iter())) + } +} diff --git a/crackers_python/src/decision/assignment_model/model_varnode_iterator.rs b/crackers_python/src/decision/assignment_model/model_varnode_iterator.rs new file mode 100644 index 0000000..b0b8f51 --- /dev/null +++ b/crackers_python/src/decision/assignment_model/model_varnode_iterator.rs @@ -0,0 +1,30 @@ +use jingle::python::z3::ast::TryIntoPythonZ3; +use pyo3::{pyclass, pymethods, Py, PyAny, PyRef, PyRefMut}; +use z3::ast::BV; + +#[pyclass(unsendable)] +pub struct ModelVarNodeIterator { + vn: Box)>>, +} + +impl ModelVarNodeIterator { + pub fn new)> + 'static>(vn: T) -> Self { + Self { vn: Box::new(vn) } + } +} + +#[pymethods] +impl ModelVarNodeIterator { + pub fn __iter__(slf: PyRef) -> PyRef { + slf + } + + pub fn __next__(mut slf: PyRefMut) -> Option<(String, Py)> { + let (name, bv) = slf.vn.next()?; + if let Ok(bv) = bv.try_into_python() { + Some((name, bv)) + } else { + None + } + } +} diff --git a/crackers_python/src/decision/mod.rs b/crackers_python/src/decision/mod.rs new file mode 100644 index 0000000..9e18001 --- /dev/null +++ b/crackers_python/src/decision/mod.rs @@ -0,0 +1,11 @@ +use crate::decision::assignment_model::PythonAssignmentModel; +use crackers::synthesis::selection_strategy::SelectionFailure; +use pyo3::{pyclass, Py}; + +pub mod assignment_model; + +#[pyclass(unsendable)] +pub enum PythonDecisionResult { + AssignmentFound(Py), + Unsat(SelectionFailure), +} diff --git a/crackers_python/src/lib.rs b/crackers_python/src/lib.rs new file mode 100644 index 0000000..6b4adcb --- /dev/null +++ b/crackers_python/src/lib.rs @@ -0,0 +1,37 @@ +mod config; +mod decision; +mod synthesis; + +use crate::config::PythonCrackersConfig; +use ::crackers::config::constraint::{ + ConstraintConfig, MemoryEqualityConstraint, PointerRange, PointerRangeConstraints, + StateEqualityConstraint, +}; +use ::crackers::config::meta::{CrackersLogLevel, MetaConfig}; +use ::crackers::config::sleigh::SleighConfig; +use ::crackers::config::specification::SpecificationConfig; +use ::crackers::config::synthesis::SynthesisConfig; +use ::crackers::gadget::library::builder::GadgetLibraryConfig; +use ::crackers::synthesis::builder::SynthesisSelectionStrategy; +use pyo3::prelude::*; +use std::ffi::CString; + +/// A Python module implemented in Rust. +#[pymodule] +fn crackers(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.py().run(&CString::new("import z3")?, None, None)?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/crackers_python/src/synthesis.rs b/crackers_python/src/synthesis.rs new file mode 100644 index 0000000..b80fe86 --- /dev/null +++ b/crackers_python/src/synthesis.rs @@ -0,0 +1,31 @@ +use crate::decision::assignment_model::PythonAssignmentModel; +use crate::decision::PythonDecisionResult; +use crackers::synthesis::builder::SynthesisParams; +use crackers::synthesis::DecisionResult; +use jingle::python::z3::get_python_z3; +use pyo3::{pyclass, pymethods, Py, PyResult, Python}; + +#[pyclass] +pub struct PythonSynthesisParams { + pub inner: SynthesisParams, +} + +#[pymethods] +impl PythonSynthesisParams { + pub fn run(&self) -> PyResult { + let z3 = get_python_z3()?; + let res = match self.inner.combine_instructions { + false => self.inner.build_single(z3)?.decide()?, + true => self.inner.build_combined(z3)?.decide()?, + }; + let res = Python::with_gil(|py| -> PyResult { + match res { + DecisionResult::AssignmentFound(a) => Ok(PythonDecisionResult::AssignmentFound( + Py::new(py, PythonAssignmentModel { inner: a })?, + )), + DecisionResult::Unsat(u) => Ok(PythonDecisionResult::Unsat(u)), + } + })?; + Ok(res) + } +} diff --git a/examples/crackers_gpt/agents/assembly/first_shot_system.txt b/examples/crackers_gpt/agents/assembly/first_shot_system.txt deleted file mode 100644 index cc43624..0000000 --- a/examples/crackers_gpt/agents/assembly/first_shot_system.txt +++ /dev/null @@ -1,6 +0,0 @@ -You are an x86-64 assembly coding assistant. -You will be asked to provide an implementation of a verbal program specification. -Return only the text of the program. -Use AT&T Syntax. -All code you emit must be position-independent. -Wrap the output in a code block. diff --git a/examples/crackers_gpt/agents/assembly/mod.rs b/examples/crackers_gpt/agents/assembly/mod.rs deleted file mode 100644 index 9f362b1..0000000 --- a/examples/crackers_gpt/agents/assembly/mod.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::anyhow; -use async_openai::config::Config; -use async_openai::error::OpenAIError; -use async_openai::types::{ChatCompletionRequestUserMessageContent, CreateChatCompletionResponse}; -use async_openai::Client; - -use crate::agents::generic::{Agent, GenericAgent}; -use crate::agents::model::Model; - -pub enum AssemblyMode { - First, - Reflection, -} - -impl AssemblyMode { - pub fn get_system_prompt(&self) -> &'static str { - match self { - AssemblyMode::First => include_str!("first_shot_system.txt"), - AssemblyMode::Reflection => include_str!("reflection_system.txt"), - } - } -} -pub struct AssemblyAgent { - model: Model, - agent: GenericAgent, -} - -impl AssemblyAgent { - pub fn new(c: Client, model: Model) -> Self { - let mode = AssemblyMode::First; - let prompt = mode.get_system_prompt(); - Self { - model, - agent: GenericAgent::new(c, prompt, model), - } - } - - pub async fn code>( - &mut self, - message: T, - ) -> anyhow::Result { - let msg = self.agent.chat(message).await?; - let result = msg.choices[0] - .message - .clone() - .content - .ok_or(anyhow!("huh"))?; - if result.starts_with("```") { - if let Some(idx) = result.rfind("```") { - if let Some(a) = result.find('\n') { - return Ok(result[a + 1..idx].to_string()); - } - } - } - Ok(result) - } - - pub fn reset_for_reflection(&mut self) { - let client = self.agent.client().clone(); - self.agent = GenericAgent::new( - client, - AssemblyMode::Reflection.get_system_prompt(), - self.model, - ); - } -} - -impl Agent for AssemblyAgent { - async fn chat>( - &mut self, - message: T, - ) -> Result { - self.agent.chat(message).await - } -} - -#[cfg(test)] -mod tests { - use async_openai::Client; - - use crate::agents::assembly::AssemblyAgent; - use crate::agents::generic::Agent; - use crate::agents::model::Model; - - #[tokio::test] - async fn test_first_shot() { - let mut agent = AssemblyAgent::new(Client::new(), Model::Gpt4o); - agent - .chat(include_str!("../../procedure/user.txt")) - .await - .unwrap(); - } -} diff --git a/examples/crackers_gpt/agents/assembly/reflection_system.txt b/examples/crackers_gpt/agents/assembly/reflection_system.txt deleted file mode 100644 index 4ea9262..0000000 --- a/examples/crackers_gpt/agents/assembly/reflection_system.txt +++ /dev/null @@ -1,7 +0,0 @@ -You are an x86-64 assembly coding assistant. -You will be given a verbal specification, your previous implementation of a program, a series of unit test failures, and a hint on how to fix them. -Provide an updated implementation of the program. -Return only the text of the program. -Use AT&T Syntax. -All code you emit must be position-independent. -Wrap the output in a code block. \ No newline at end of file diff --git a/examples/crackers_gpt/agents/generic.rs b/examples/crackers_gpt/agents/generic.rs deleted file mode 100644 index 47c87a9..0000000 --- a/examples/crackers_gpt/agents/generic.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::agents::model::Model; -use async_openai::config::Config; -use async_openai::error::OpenAIError; -use async_openai::types::{ - ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, - ChatCompletionRequestMessageContentPart, ChatCompletionRequestSystemMessage, - ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent, - ChatCompletionResponseMessage, CreateChatCompletionRequest, CreateChatCompletionRequestArgs, - CreateChatCompletionResponse, -}; -use async_openai::Client; -use std::fmt::Debug; -use tracing::{event, Level}; - -#[derive(Debug, Clone)] -pub struct GenericAgent { - history: Vec, - model: Model, - client: Client, -} - -impl GenericAgent { - pub fn new>(client: Client, system_prompt: S, model: Model) -> Self { - Self { - client, - history: vec![Self::build_system_prompt(system_prompt)], - model, - } - } - pub async fn chat>( - &mut self, - message: T, - ) -> Result { - self.add_user_message(message); - let req = self.build_request()?; - - let resp = self.client.chat().create(req).await?; - self.add_assistant_message(&resp); - Ok(resp) - } - - #[allow(unused)] - pub fn last_message(&self) -> Option<&ChatCompletionRequestMessage> { - self.history.last() - } - - #[allow(unused)] - pub fn model(&self) -> Model { - self.model - } - - pub fn client(&self) -> &Client { - &self.client - } - fn add_user_message>(&mut self, msg: T) { - let req = Self::build_user_prompt(msg); - print_request(&req); - self.history.push(req); - } - - fn add_assistant_message(&mut self, msg: &CreateChatCompletionResponse) { - let req = Self::build_assistant_prompt(&msg.choices[0].message); - print_request(&req); - self.history.push(req); - } - - fn build_request(&self) -> Result { - CreateChatCompletionRequestArgs::default() - .model(self.model.name()) - .messages(self.history.clone()) - .build() - } - - fn build_system_prompt>(s: S) -> ChatCompletionRequestMessage { - ChatCompletionRequestSystemMessage { - content: s.into(), - name: None, - } - .into() - } - - fn build_assistant_prompt( - resp: &ChatCompletionResponseMessage, - ) -> ChatCompletionRequestMessage { - ChatCompletionRequestAssistantMessageArgs::default() - .content(resp.content.clone().unwrap()) - .build() - .unwrap() - .into() - } - - fn build_user_prompt>( - text: T, - ) -> ChatCompletionRequestMessage { - ChatCompletionRequestUserMessage { - content: text.into(), - name: None, - } - .into() - } -} - -pub trait Agent { - // todo: maybe having this trait is overkill for what I'm doing. Consider removing. - #[allow(unused)] - async fn chat>( - &mut self, - message: T, - ) -> Result; -} - -impl Agent for GenericAgent { - async fn chat>( - &mut self, - message: T, - ) -> Result { - self.chat(message).await - } -} - -fn print_request(req: &ChatCompletionRequestMessage) { - match req { - ChatCompletionRequestMessage::System(sys) => { - event!(Level::INFO, "\n(system):\n{}", sys.content) - } - ChatCompletionRequestMessage::User(u) => match &u.content { - ChatCompletionRequestUserMessageContent::Text(t) => { - event!(Level::INFO, "\n(user):\n{}", &t) - } - ChatCompletionRequestUserMessageContent::Array(a) => { - event!(Level::INFO, "\n(user)"); - for x in a { - match x { - ChatCompletionRequestMessageContentPart::Text(t) => { - event!(Level::INFO, "{}", &t.text) - } - ChatCompletionRequestMessageContentPart::ImageUrl(i) => { - event!(Level::INFO, "{}", &i.image_url.url) - } - } - } - } - }, - ChatCompletionRequestMessage::Assistant(a) => { - if let Some(a) = &a.content { - event!(Level::INFO, "\n(assistant):\n{}", &a) - } - } - ChatCompletionRequestMessage::Tool(_) => {} - ChatCompletionRequestMessage::Function(_) => {} - } -} diff --git a/examples/crackers_gpt/agents/mod.rs b/examples/crackers_gpt/agents/mod.rs deleted file mode 100644 index b28b9f8..0000000 --- a/examples/crackers_gpt/agents/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod assembly; -pub mod generic; -pub mod model; -pub mod reflection; diff --git a/examples/crackers_gpt/agents/model.rs b/examples/crackers_gpt/agents/model.rs deleted file mode 100644 index ecfa756..0000000 --- a/examples/crackers_gpt/agents/model.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[derive(Copy, Clone, Debug)] -pub enum Model { - #[allow(unused)] - Gpt35, - #[allow(unused)] - Gpt4, - #[allow(unused)] - Gpt4o, -} - -impl Model { - pub fn name(&self) -> &'static str { - match self { - Model::Gpt35 => "gpt-3.5-turbo", - Model::Gpt4 => "gpt-4", - Model::Gpt4o => "gpt-4o", - } - } -} diff --git a/examples/crackers_gpt/agents/reflection/mod.rs b/examples/crackers_gpt/agents/reflection/mod.rs deleted file mode 100644 index ae8688a..0000000 --- a/examples/crackers_gpt/agents/reflection/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use async_openai::config::Config; -use async_openai::Client; - -use crate::agents::generic::GenericAgent; -use crate::agents::model::Model; - -pub struct ReflectionAgent { - agent: GenericAgent, -} - -impl ReflectionAgent { - pub fn new(c: Client, model: Model) -> Self { - Self { - agent: GenericAgent::new(c, include_str!("system.txt"), model), - } - } - - pub async fn reflect(&mut self, spec: &str, test_output: &[String]) -> anyhow::Result { - let reflection_prompt = format!( - include_str!("reflection_format.txt"), - spec, - test_output.join("\n") - ); - let response = self.agent.chat(reflection_prompt).await?; - Ok(response.choices[0].clone().message.content.unwrap()) - } -} diff --git a/examples/crackers_gpt/agents/reflection/reflection_format.txt b/examples/crackers_gpt/agents/reflection/reflection_format.txt deleted file mode 100644 index f1a8510..0000000 --- a/examples/crackers_gpt/agents/reflection/reflection_format.txt +++ /dev/null @@ -1,9 +0,0 @@ -[program] -``` -{} -``` - -[test failures] -``` -{} -``` diff --git a/examples/crackers_gpt/agents/reflection/system.txt b/examples/crackers_gpt/agents/reflection/system.txt deleted file mode 100644 index df1e752..0000000 --- a/examples/crackers_gpt/agents/reflection/system.txt +++ /dev/null @@ -1,4 +0,0 @@ -You are an x86-64 assembly coding assistant. -You will be given an implementation of an x86 program in a code block followed by a summary of test failures produced by this implementation. -These tests are checking black-box out-of-band constraints that the program must pass. -Write a maximum of three sentences describing what went wrong. \ No newline at end of file diff --git a/examples/crackers_gpt/config/mod.rs b/examples/crackers_gpt/config/mod.rs deleted file mode 100644 index 12f5eba..0000000 --- a/examples/crackers_gpt/config/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crackers::config::constraint::Constraint; -use crackers::config::meta::MetaConfig; -use crackers::config::sleigh::SleighConfig; -use crackers::config::synthesis::SynthesisConfig; -use crackers::error::CrackersError; -use crackers::gadget::library::builder::GadgetLibraryParams; -use crackers::synthesis::builder::{SynthesisParams, SynthesisParamsBuilder}; -use jingle::sleigh::Instruction; -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize)] -pub struct CrackersGptConfig { - #[serde(default)] - pub(crate) meta: MetaConfig, - pub(crate) constraint: Option, - pub(crate) library: GadgetLibraryParams, - pub(crate) synthesis: SynthesisConfig, - pub(crate) sleigh: SleighConfig, -} - -impl CrackersGptConfig { - pub fn resolve(&self, spec: Vec) -> Result { - let library = self.library.build(&self.sleigh)?; - let mut b = SynthesisParamsBuilder::default(); - if let Some(c) = &self.constraint { - b.preconditions(c.get_preconditions(&library).collect()); - b.postconditions(c.get_postconditions(&library).collect()); - b.pointer_invariants(c.get_pointer_constraints().collect()); - } - b.gadget_library(library) - .seed(self.meta.seed) - .instructions(spec); - b.selection_strategy(self.synthesis.strategy); - b.candidates_per_slot(self.synthesis.max_candidates_per_slot); - b.parallel(self.synthesis.parallel).seed(self.meta.seed); - - let params = b.build()?; - Ok(params) - } -} diff --git a/examples/crackers_gpt/evaluator/mod.rs b/examples/crackers_gpt/evaluator/mod.rs deleted file mode 100644 index a359978..0000000 --- a/examples/crackers_gpt/evaluator/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crackers::assert_concat; -use crackers::synthesis::assignment_model::AssignmentModel; -use crackers::synthesis::builder::SynthesisParams; -use crackers::synthesis::selection_strategy::SelectionFailure; -use crackers::synthesis::DecisionResult; -use jingle::modeling::{ModeledBlock, ModeledInstruction, ModelingContext}; -use jingle::sleigh::{ArchInfoProvider, Instruction}; -use jingle::JingleContext; -use z3::ast::{Ast, BV}; -use z3::{Config, Context, SatResult, Solver}; - -use crate::config::CrackersGptConfig; - -pub struct ExecveEvaluator { - config: SynthesisParams, - z3: Context, -} - -#[derive(Debug)] -pub enum ExecveEvaluation<'ctx> { - /// Something is wrong with the spec itself - SpecError(Vec), - ImplementationError(SelectionFailure), - Success(AssignmentModel<'ctx, ModeledBlock<'ctx>>), -} - -pub fn summarize_implementation_error(spec: &[Instruction], e: SelectionFailure) -> Vec { - let mut res = vec![]; - for x in e.indexes { - res.push(format!( - "Ordering error: do not perform {} at offset {:x} in the trace", - spec[x].disassembly, x - )); - } - res -} - -impl<'ctx> From>> for ExecveEvaluation<'ctx> { - fn from(value: DecisionResult<'ctx, ModeledBlock<'ctx>>) -> Self { - match value { - DecisionResult::AssignmentFound(a) => ExecveEvaluation::Success(a), - DecisionResult::Unsat(d) => ExecveEvaluation::ImplementationError(d), - } - } -} -impl ExecveEvaluator { - pub fn new(config: CrackersGptConfig) -> anyhow::Result { - let synth_params = config.resolve(vec![])?; - Ok(Self { - config: synth_params, - z3: Context::new(&Config::new()), - }) - } - - pub fn eval(&self, spec: &[Instruction]) -> anyhow::Result { - let spec_eval = self.eval_spec_model(spec)?; - if let Some(a) = spec_eval { - return Ok(ExecveEvaluation::SpecError(a)); - } - // todo: gross hack to avoid dealing with some liftimes. I can live with one huge memcpy - let mut c = self.config.clone(); - c.instructions = spec.to_vec(); - let mut problem = c.build_combined(&self.z3)?; - let p: ExecveEvaluation = problem.decide()?.into(); - Ok(p) - } - - pub fn eval_spec_model(&self, spec: &[Instruction]) -> anyhow::Result>> { - let solver = Solver::new(&self.z3); - let jingle = JingleContext::new(&self.z3, self.config.gadget_library.as_ref()); - let mut model = vec![]; - for x in spec { - model.push(ModeledInstruction::new(x.clone(), &jingle)?); - } - solver.assert(&assert_concat(&self.z3, &model)?.simplify()); - // verify basic requirements of execve - let rsi = model.as_slice().get_final_state().read( - self.config - .gadget_library - .get_register("RSI") - .unwrap() - .into(), - )?; - rsi.simplify(); - let rax = model.as_slice().get_final_state().read( - self.config - .gadget_library - .get_register("RAX") - .unwrap() - .into(), - )?; - rax.simplify(); - - let rdx = model.as_slice().get_final_state().read( - self.config - .gadget_library - .get_register("RDX") - .unwrap() - .into(), - )?; - rdx.simplify(); - - let rsi_eq = rsi._eq(&BV::from_u64(&self.z3, 0, rsi.get_size())); - let rax_eq = rax._eq(&BV::from_u64(&self.z3, 0x3b, rsi.get_size())); - let rdx_eq = rdx._eq(&BV::from_u64(&self.z3, 0, rsi.get_size())); - // todo: check string pointer - // todo: check invariants - match solver.check_assumptions(&[rsi_eq.clone(), rax_eq.clone(), rdx_eq.clone()]) { - SatResult::Unsat => { - let core = solver.get_unsat_core(); - let mut res = vec![]; - if core.contains(&rsi_eq) { - res.push("Assertion failed: 'rsi' == 0".to_string()); - } - if core.contains(&rax_eq) { - res.push("Assertion failed: 'rax' == 0x3b".to_string()); - } - if core.contains(&rdx_eq) { - res.push("Assertion failed: 'rdx' == 0".to_string()); - } - Ok(Some(res)) - } - SatResult::Unknown => { - unreachable!() - } - SatResult::Sat => Ok(None), - } - } -} diff --git a/examples/crackers_gpt/main.rs b/examples/crackers_gpt/main.rs deleted file mode 100644 index cc5a55e..0000000 --- a/examples/crackers_gpt/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::fs; - -use clap::Parser; -use crackers::synthesis::DecisionResult; -use tracing::{event, Level}; -use tracing_subscriber::FmtSubscriber; - -use crate::config::CrackersGptConfig; -use crate::procedure::GptProcedure; - -mod agents; -mod config; -mod evaluator; -mod procedure; -mod specification; - -#[derive(Parser, Debug)] -struct Arguments { - pub cfg_path: String, -} -#[tokio::main] -async fn main() { - let args = Arguments::parse(); - let cfg_bytes = fs::read(args.cfg_path).unwrap(); - let s = String::from_utf8(cfg_bytes).unwrap(); - - let p: CrackersGptConfig = toml_edit::de::from_str(&s).unwrap(); - let level = Level::from(p.meta.log_level); - let sub = FmtSubscriber::builder().with_max_level(level).finish(); - tracing::subscriber::set_global_default(sub).unwrap(); - let mut gpt_proc = GptProcedure::new(p).unwrap(); - let rest = gpt_proc.run().await.unwrap(); - match rest { - DecisionResult::AssignmentFound(a) => { - event!(Level::INFO, "It worked!"); - println!("{}", a) - } - DecisionResult::Unsat(_) => { - event!(Level::ERROR, "Didn't work :(") - } - } -} diff --git a/examples/crackers_gpt/procedure/mod.rs b/examples/crackers_gpt/procedure/mod.rs deleted file mode 100644 index 421aea1..0000000 --- a/examples/crackers_gpt/procedure/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::io::Write; - -use async_openai::config::OpenAIConfig; -use async_openai::Client; -use jingle::modeling::ModeledBlock; -use jingle::sleigh::Instruction; -use tempfile::{tempdir, NamedTempFile}; -use tracing::{event, Level}; - -use crackers::config::specification::SpecificationConfig; -use crackers::synthesis::DecisionResult; - -use crate::agents::assembly::AssemblyAgent; -use crate::agents::model::Model; -use crate::agents::reflection::ReflectionAgent; -use crate::config::CrackersGptConfig; -use crate::evaluator::{summarize_implementation_error, ExecveEvaluation, ExecveEvaluator}; -use crate::procedure::AssemblyResult::{Failure, Success}; -use crate::specification::AssemblyParametersBuilder; - -pub struct GptProcedure { - config: CrackersGptConfig, - assembly_agent: AssemblyAgent, - pub reflection_agent: ReflectionAgent, - evaluator: ExecveEvaluator, - max_iterations: usize, -} - -pub enum AssemblyResult { - Success(Vec), - Failure(String), -} - -impl GptProcedure { - pub fn new(config: CrackersGptConfig) -> anyhow::Result { - Ok(Self { - assembly_agent: AssemblyAgent::new(Client::new(), Model::Gpt4o), - reflection_agent: ReflectionAgent::new(Client::new(), Model::Gpt4o), - evaluator: ExecveEvaluator::new(config.clone())?, - max_iterations: 4, - config, - }) - } - - pub async fn run(&mut self) -> anyhow::Result> { - let mut assembly_program = self.assembly_agent.code(include_str!("user.txt")).await?; - let assembly_result = self.assemble(&assembly_program)?; - let evaluation = match assembly_result { - Success(instructions) => match self.evaluator.eval(&instructions)? { - ExecveEvaluation::SpecError(s) => s, - ExecveEvaluation::ImplementationError(a) => { - summarize_implementation_error(&instructions, a) - } - ExecveEvaluation::Success(a) => return Ok(DecisionResult::AssignmentFound(a)), - }, - Failure(s) => vec![s], - }; - let mut reflection = self - .reflection_agent - .reflect(&assembly_program, evaluation.as_slice()) - .await - .unwrap(); - self.assembly_agent.reset_for_reflection(); - let max = self.max_iterations; - for _ in 0..max { - let reflection_prompt = format!( - include_str!("reflection_format.txt"), - include_str!("user.txt"), - assembly_program, - evaluation.join("\n"), - reflection - ); - assembly_program = self.assembly_agent.code(reflection_prompt).await?; - let assembly_result = self.assemble(&assembly_program)?; - let evaluation = match assembly_result { - Success(instructions) => match self.evaluator.eval(&instructions)? { - ExecveEvaluation::SpecError(s) => s, - ExecveEvaluation::ImplementationError(a) => { - summarize_implementation_error(&instructions, a) - } - ExecveEvaluation::Success(a) => return Ok(DecisionResult::AssignmentFound(a)), - }, - Failure(s) => vec![s], - }; - reflection = self - .reflection_agent - .reflect(&assembly_program, evaluation.as_slice()) - .await - .unwrap(); - } - todo!() - } - - fn assemble(&self, assembly: &str) -> anyhow::Result { - let dir = tempdir()?; - let asm = self.create_assembly_file(assembly)?; - let params = AssemblyParametersBuilder::default() - .target("x86_64-unknown-linux-gnu") - .compiler("x86_64-unknown-linux-gnu-gcc") - .host("aarch64-apple-darwin") - .build()?; - let obj_path = params.assemble(&dir, &asm); - if obj_path.is_err() { - return Ok(Failure( - "Unable to compile program. Did you use AT&T syntax?".to_string(), - )); - } - let obj_path = obj_path.unwrap(); - let spec_config = SpecificationConfig { - base_address: Some(0), - path: obj_path[0].to_str().unwrap().to_string(), - max_instructions: 10, - }; - let spec = spec_config.get_spec(&self.config.sleigh)?; - for x in &spec { - event!(Level::DEBUG, "{}", x.disassembly); - } - Ok(Success(spec)) - } - - fn create_assembly_file(&self, asm: &str) -> anyhow::Result { - let mut a = tempfile::Builder::new() - .suffix(".S") - .prefix("spec") - .tempfile()?; - a.write_all(asm.as_bytes())?; - Ok(a) - } -} diff --git a/examples/crackers_gpt/procedure/reflection_format.txt b/examples/crackers_gpt/procedure/reflection_format.txt deleted file mode 100644 index 93498d6..0000000 --- a/examples/crackers_gpt/procedure/reflection_format.txt +++ /dev/null @@ -1,18 +0,0 @@ -[verbal specification] -``` -{} -``` -[previous program] -``` -{} -``` - -[test failures] -``` -{} -``` - -[hint] -``` -{} -``` \ No newline at end of file diff --git a/examples/crackers_gpt/procedure/user.txt b/examples/crackers_gpt/procedure/user.txt deleted file mode 100644 index f13a210..0000000 --- a/examples/crackers_gpt/procedure/user.txt +++ /dev/null @@ -1,4 +0,0 @@ -Write a program that performs the following syscall: execve('/bin/sh/'). -Do not include comments. -Assume the string `/bin/sh` is already allocated and pointed to by R10. -Do NOT use the xor instruction. \ No newline at end of file diff --git a/examples/crackers_gpt/specification/mod.rs b/examples/crackers_gpt/specification/mod.rs deleted file mode 100644 index e74c766..0000000 --- a/examples/crackers_gpt/specification/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -use cc::Build; -use derive_builder::Builder; -use std::path::PathBuf; -use tempfile::{NamedTempFile, TempDir}; - -#[derive(Debug, Clone, Builder)] -#[builder(setter(into))] -pub struct AssemblyParameters { - target: String, - #[builder(setter(strip_option))] - compiler: Option, - host: String, -} - -impl AssemblyParameters { - pub fn assemble(&self, dir: &TempDir, file: &NamedTempFile) -> Result, cc::Error> { - let mut build = Build::new(); - // We aren't running in a build script, so no need to print all those cargo directives - // and environment variables - build - .cargo_warnings(false) - .cargo_metadata(false) - .cargo_debug(false) - // location of the file to assemble - .file(file.path()) - // directory to place the built object - .out_dir(dir.path()) - // target architecture/os triple - .target(&self.target) - .opt_level(0) - .host(&self.host); - if let Some(c) = &self.compiler { - // name of the compiler to use - // necessary on macOS because - build.compiler(c); - } - build.try_compile_intermediates() - } -} - -#[cfg(test)] -mod tests { - use crate::specification::AssemblyParametersBuilder; - - #[test] - fn simple_test() { - let params = AssemblyParametersBuilder::default() - .target("x86_64-unknown-linux-gnu") - .compiler("x86_64-unknown-linux-gnu-gcc") - .host("aarch64-apple-darwin") - .build() - .unwrap(); - //params.assemble("src/specification", "src/specification/test.S").unwrap(); - } -} diff --git a/src/config/sleigh.rs b/src/config/sleigh.rs deleted file mode 100644 index 565b8a3..0000000 --- a/src/config/sleigh.rs +++ /dev/null @@ -1,16 +0,0 @@ -use jingle::sleigh::context::SleighContextBuilder; -use serde::{Deserialize, Serialize}; - -use crate::config::error::CrackersConfigError; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SleighConfig { - pub ghidra_path: String, -} - -impl SleighConfig { - pub fn context_builder(&self) -> Result { - let b = SleighContextBuilder::load_ghidra_installation(&self.ghidra_path)?; - Ok(b) - } -} diff --git a/src/config/synthesis.rs b/src/config/synthesis.rs deleted file mode 100644 index bbd937c..0000000 --- a/src/config/synthesis.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::synthesis::builder::SynthesisSelectionStrategy; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SynthesisConfig { - pub strategy: SynthesisSelectionStrategy, - pub max_candidates_per_slot: usize, - pub parallel: usize, - pub combine_instructions: bool, -} - -impl Default for SynthesisConfig { - fn default() -> Self { - SynthesisConfig { - strategy: SynthesisSelectionStrategy::OptimizeStrategy, - max_candidates_per_slot: 50, - parallel: 4, - combine_instructions: true, - } - } -}