Skip to content

Commit 033ac78

Browse files
authored
feat!: Allow providing reference programs as raw pcode (#89)
* Add reference program support for binary and raw pcode specifications - Introduce ReferenceProgram struct representing steps and initial memory valuation - Implement loading reference programs from binary files and raw pcode strings - Add MemoryValuation helper for managing initial memory constraints - Refactor specification config to support binary files or raw pcode - Update dependency on jingle to 0.4.0 to support new features * Update jingle dependency to version 0.4.0 with features * clippy * Refactor SpecificationConfig into a discriminated union Split SpecificationConfig into BinaryFileSpecification and RawPcodeSpecification to clearly represent the two specification variants. * Refactor reference program config using discriminated union Define BinaryFileSpecification and RawPcodeSpecification models with a discriminator field `type` for improved type safety and clarity. Add detailed docstrings and helper repr methods. * Transform specification JSON for Rust enum compatibility in run() * Refactor imports and update __all__ in config init * Improve PythonLoggerLayer to pass metadata via `extra` dictionary Pass Rust file, line, and module path metadata separately to Python logging to enable better integration without embedding them in the message. * Refactor configuration imports and update examples for raw pcode - Rearrange imports in Python and Rust code for clarity and consistency - Replace reference program file with raw pcode specification in examples - Adjust logging levels and output formatting for better debug info - Update bench command to handle specification variants correctly - Simplify tracing calls and improve error/info logging messages * Update jingle dependency to version 0.4.2 in Cargo.toml files Remove backup Cargo.toml~ file from crackers_python directory
1 parent 1961b53 commit 033ac78

23 files changed

Lines changed: 552 additions & 324 deletions

File tree

README.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,26 @@ logging.basicConfig(level=logging.INFO)
6161
from z3 import BoolRef, BoolVal, simplify
6262

6363
from crackers.config import (
64-
MetaConfig,
64+
BinaryFileSpecification,
65+
ConstraintConfig,
66+
CrackersConfig,
6567
LibraryConfig,
66-
SleighConfig,
68+
MetaConfig,
6769
ReferenceProgramConfig,
70+
SleighConfig,
6871
SynthesisConfig,
69-
ConstraintConfig,
70-
CrackersConfig,
7172
)
7273
from crackers.config.constraint import (
73-
RegisterValuation,
74-
RegisterStringValuation,
75-
MemoryValuation,
76-
PointerRange,
7774
CustomStateConstraint,
7875
CustomTransitionConstraint,
76+
MemoryValuation,
77+
PointerRange,
7978
PointerRangeRole,
79+
RegisterStringValuation,
80+
RegisterValuation,
8081
)
8182
from crackers.config.log_level import LogLevel
83+
from crackers.config.specification import BinaryFileSpecification, RawPcodeSpecification
8284
from crackers.config.synthesis import SynthesisStrategy
8385

8486

@@ -95,14 +97,17 @@ def my_transition_constraint(block: ModeledBlock) -> BoolRef:
9597
return BoolVal(True)
9698

9799

98-
meta = MetaConfig(log_level=LogLevel.INFO, seed=42)
100+
pcode = """
101+
RBX = COPY 0x1337:8
102+
BRANCH *[ram]0xdeadbeef:8
103+
"""
104+
105+
meta = MetaConfig(log_level=LogLevel.DEBUG, seed=42)
99106
library = LibraryConfig(
100-
max_gadget_length=8, path="libz.so.1", sample_size=None, base_address=None
107+
max_gadget_length=8, path="libnscgi.so", sample_size=None, base_address=None
101108
)
102109
sleigh = SleighConfig(ghidra_path="/Applications/ghidra")
103-
reference_program = ReferenceProgramConfig(
104-
path="sample.o", max_instructions=8, base_address=library.base_address
105-
)
110+
reference_program = RawPcodeSpecification(raw_pcode=pcode)
106111
synthesis = SynthesisConfig(
107112
strategy=SynthesisStrategy.SAT,
108113
max_candidates_per_slot=200,
@@ -141,7 +146,7 @@ match r:
141146
print(i.disassembly)
142147
print()
143148
for name, bv in a.input_summary(True):
144-
print(f"{name} = {simplify(bv)}")
149+
print(f"{name} = {hex(simplify(bv).as_long())}")
145150

146151
```
147152

crackers/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ toml = ["dep:toml_edit"]
2424
z3-gh-release = ["z3/gh-release"]
2525

2626
[dependencies]
27-
jingle = { version = "0.3.6", features = ["gimli"] }
27+
jingle = { version = "0.4.2", features = ["gimli"] }
2828
z3 = "0.19.0"
2929
serde = { version = "1.0.203", features = ["derive"] }
3030
thiserror = "2.0"

crackers/src/bench/mod.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use std::fs;
22
use std::path::PathBuf;
33

44
use clap::Parser;
5-
use tracing::level_filters::LevelFilter;
6-
use tracing::{Level, event};
5+
use tracing::{Level, error, info};
76
use tracing_indicatif::IndicatifLayer;
87
use tracing_subscriber::EnvFilter;
8+
use tracing_subscriber::filter::LevelFilter;
99
use tracing_subscriber::layer::SubscriberExt;
1010
use tracing_subscriber::util::SubscriberInitExt;
1111

@@ -18,43 +18,55 @@ pub struct BenchCommand {
1818
gadgets_per_slot: usize,
1919
spec_instructions: usize,
2020
}
21+
2122
pub fn bench(config: BenchCommand) -> anyhow::Result<()> {
2223
let cfg_bytes = fs::read(config.crackers_config)?;
2324
let s = String::from_utf8(cfg_bytes)?;
2425
let mut p: CrackersConfig = toml_edit::de::from_str(&s)?;
2526
p.synthesis.max_candidates_per_slot = config.gadgets_per_slot;
26-
p.specification.max_instructions = config.spec_instructions;
27+
match &mut p.specification {
28+
crate::config::specification::SpecificationConfig::BinaryFile(b) => {
29+
b.max_instructions = config.spec_instructions;
30+
}
31+
_ => {}
32+
}
2733

34+
// Resolve log level from config (expects a compatible type in `p.meta.log_level`)
2835
let level = Level::from(p.meta.log_level);
36+
2937
let env_filter = EnvFilter::builder()
3038
.with_default_directive(LevelFilter::ERROR.into())
3139
.from_env()?
3240
.add_directive(format!("crackers={level}").parse()?);
41+
3342
let indicatif_layer = IndicatifLayer::new();
3443
let writer = indicatif_layer.get_stderr_writer();
44+
3545
tracing_subscriber::registry()
3646
.with(env_filter)
3747
.with(indicatif_layer)
3848
.with(tracing_subscriber::fmt::layer().with_writer(writer))
3949
.init();
50+
4051
let params = p.resolve()?;
4152
match params.build_single() {
4253
Ok(mut a) => match a.decide() {
4354
Ok(a) => match a {
4455
DecisionResult::AssignmentFound(_) => {
45-
event!(Level::INFO, "synth_success")
56+
info!("synth_success");
4657
}
4758
DecisionResult::Unsat(_) => {
48-
event!(Level::INFO, "synth_fail")
59+
info!("synth_fail");
4960
}
5061
},
5162
Err(e) => {
52-
event!(Level::ERROR, "synth_error: {}", e)
63+
error!("synth_error: {}", e);
5364
}
5465
},
5566
Err(_) => {
56-
event!(Level::ERROR, "synth_fail")
67+
error!("synth_fail");
5768
}
5869
}
70+
5971
Ok(())
6072
}

crackers/src/bin/crackers/main.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,16 @@ fn main() {
5454
fn new(path: PathBuf) -> anyhow::Result<()> {
5555
let config = CrackersConfig {
5656
meta: Default::default(),
57-
specification: SpecificationConfig {
58-
path: "spec.o".to_string(),
59-
max_instructions: 1,
60-
base_address: None,
61-
},
57+
specification: SpecificationConfig::RawPcode(
58+
r"
59+
MOV RDI, 0xdeadbeef:8
60+
MOV RSI, 0x40:8
61+
MOV RDX, 0x7b:8
62+
MOV RAX, 0xfacefeed:8
63+
BRANCH 0xdeadbeef:8
64+
"
65+
.to_string(),
66+
),
6267
library: Default::default(),
6368
sleigh: SleighConfig {
6469
ghidra_path: "/Applications/ghidra".to_string(),

crackers/src/config/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub enum CrackersConfigError {
2222
"Unable to determine the architecture of the provided object file. This is a config file limitation and not a sleigh limitation."
2323
)]
2424
UnrecognizedArchitecture(String),
25-
#[error("An error initializing sleigh for a file specified in the config")]
25+
#[error("An error initializing sleigh for a file specified in the config: {0}")]
2626
Sleigh(#[from] JingleError),
2727
}
2828

crackers/src/config/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct CrackersConfig {
3535
impl CrackersConfig {
3636
pub fn resolve(&self) -> Result<SynthesisParams, CrackersError> {
3737
let library = self.library.build(&self.sleigh)?;
38+
let lang_id = library.language_id.clone();
3839
let mut b = SynthesisParamsBuilder::default();
3940
if let Some(c) = &self.constraint {
4041
b.preconditions(c.get_preconditions(&library.arch_info()).collect());
@@ -47,6 +48,7 @@ impl CrackersConfig {
4748
&self.specification,
4849
&self.sleigh,
4950
&self.library.operation_blacklist,
51+
&lang_id,
5052
)?);
5153
b.selection_strategy(self.synthesis.strategy);
5254
b.combine_instructions(self.synthesis.combine_instructions);

crackers/src/config/specification.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,22 @@ use crate::config::sleigh::SleighConfig;
1414

1515
#[derive(Clone, Debug, Deserialize, Serialize)]
1616
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
17-
pub struct SpecificationConfig {
17+
pub struct BinaryFileSpecification {
1818
pub path: String,
1919
pub max_instructions: usize,
2020
pub base_address: Option<u64>,
2121
}
2222

23+
#[derive(Clone, Debug, Deserialize, Serialize)]
24+
#[cfg_attr(feature = "pyo3", pyclass)]
25+
pub enum SpecificationConfig {
26+
BinaryFile(BinaryFileSpecification),
27+
RawPcode(String),
28+
}
29+
2330
#[cfg(feature = "pyo3")]
2431
#[pymethods]
25-
impl SpecificationConfig {
32+
impl BinaryFileSpecification {
2633
#[new]
2734
fn new(path: String, max_instructions: usize, base_address: Option<u64>) -> Self {
2835
Self {
@@ -63,7 +70,7 @@ impl SpecificationConfig {
6370
}
6471
}
6572

66-
impl SpecificationConfig {
73+
impl BinaryFileSpecification {
6774
fn load_sleigh<'a>(
6875
&self,
6976
sleigh_config: &'a SleighConfig,

crackers/src/gadget/library/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod image;
1919
pub struct GadgetLibrary {
2020
pub(crate) gadgets: Vec<Gadget>,
2121
arch_info: SleighArchInfo,
22+
pub(crate) language_id: String,
2223
}
2324

2425
impl AsRef<SleighArchInfo> for GadgetLibrary {
@@ -52,6 +53,7 @@ impl GadgetLibrary {
5253
let mut lib: GadgetLibrary = GadgetLibrary {
5354
gadgets: vec![],
5455
arch_info: sleigh.arch_info().clone(),
56+
language_id: sleigh.get_language_id().to_string(),
5557
};
5658
event!(Level::INFO, "Loading gadgets from sleigh");
5759
for section in sleigh.get_sections().filter(|s| s.perms.exec) {

0 commit comments

Comments
 (0)