Skip to content

Commit bcc1bda

Browse files
committed
wip
1 parent e2003fa commit bcc1bda

File tree

11 files changed

+275
-385
lines changed

11 files changed

+275
-385
lines changed

Cargo.lock

Lines changed: 208 additions & 258 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "binsec"
3-
description = "Swiss Army Knife for Binary (In)Security"
3+
description = "Binary (In)Security tool"
44

55
authors = ["ex0dus-0x <ex0dus@codemuch.tech>"]
66
license = "MIT"
@@ -9,7 +9,7 @@ homepage = "https://github.com/ex0dus-0x/binsec"
99
repository = "https://github.com/ex0dus-0x/binsec"
1010
readme = "README.md"
1111

12-
version = "3.0.0"
12+
version = "3.1.0"
1313
edition = "2018"
1414

1515
[profile.release]
@@ -22,7 +22,5 @@ goblin = "0.4.0"
2222
byte-unit = "4.0.10"
2323
chrono = "0.4"
2424

25-
yara = "0.9.0"
26-
2725
serde_json = "1.0"
2826
serde = { version = "1.0", features = ["derive"] }

README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@
99
[crates-binsec-badge]: https://img.shields.io/crates/v/binsec.svg
1010
[crates-binsec]: https://crates.io/crates/binsec
1111

12-
Swiss Army Knife for Binary (In)security
12+
Binary (In)security tool
1313

14-
__binsec__ is a minimal static analysis utility for detecting security capabilities in ELF/PE/Mach-O executables. It's useful
15-
for reverse engineers and vulnerability researchers to gain quick and deeper insights into binary artifacts,
16-
build fast detection pipelines, and improve overall binary analysis.
14+
__binsec__ is a minimal static analysis utility for detecting security capabilities in ELF/PE/Mach-O executables.
1715

1816
## Features
1917

2018
* Cross-platform, supports robust checks for ELF/PE/Mach-Os while running on any host.
21-
* Backends [libgoblin](https://github.com/m4b/goblin) for efficient and cross-platform binary parsing.
22-
* JSON serializable for storage/logging consumption.
23-
* Small and ast: final release build is ~2.44Mb, with analysis done in 30ms.
19+
* Backends [libgoblin](https://github.com/m4b/goblin) for binary parsing
20+
* JSON serialization
21+
* Small release builds at ~2.44Mb, with analysis done in 30ms.
2422

2523
### Static Analysis Checks
2624

@@ -29,7 +27,6 @@ The project currently supports static detection for a variety of executable chec
2927
* __Compilation Features__ - insights about how the executable was compiled, and runtimes used in that process.
3028
* __Exploit Mitigations__ - OS-supported binary hardening features used to limit exploitation and priviledge escalation.
3129
* __Dynamic Instrumentation__ - detects any known instrumentation frameworks used for dynamic analysis and/or profiling.
32-
* __Anti-Analysis (WIP)__ - noticeable anti-analysis checks employed to mitigate reverse engineering.
3330

3431
## Usage
3532

src/check/elf.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::rules;
2626
const GLIBC: &str = "GLIBC_2.";
2727

2828
impl Analyze for Elf<'_> {
29-
fn run_compilation_checks(&self, bytes: &[u8]) -> BinResult<GenericMap> {
29+
fn compilation(&self, bytes: &[u8]) -> BinResult<GenericMap> {
3030
let mut comp_map: GenericMap = GenericMap::new();
3131

3232
// supported: shared object (pie exec or .so) or executable
@@ -72,7 +72,7 @@ impl Analyze for Elf<'_> {
7272
Ok(comp_map)
7373
}
7474

75-
fn run_mitigation_checks(&self) -> GenericMap {
75+
fn mitigations(&self) -> GenericMap {
7676
let mut mitigate_map: GenericMap = GenericMap::new();
7777

7878
// features we are checking for
@@ -120,6 +120,8 @@ impl Analyze for Elf<'_> {
120120
if let Some(Ok(symbol)) = _symbol {
121121
if symbol == "__stack_chk_fail" {
122122
stack_canary = true;
123+
124+
// TODO: make tighter
123125
} else if symbol.ends_with("_chk") {
124126
fortify_source = true;
125127
}
@@ -129,4 +131,31 @@ impl Analyze for Elf<'_> {
129131
mitigate_map.insert("FORTIFY_SOURCE".to_string(), json!(fortify_source));
130132
mitigate_map
131133
}
134+
135+
fn instrumentation(&self) -> Option<GenericMap> {
136+
let mut instr_map: GenericMap = GenericMap::new();
137+
for _sym in self.syms.iter() {
138+
let _symbol = self.strtab.get(_sym.st_name);
139+
if let Some(Ok(symbol)) = _symbol {
140+
141+
// /__ubsan\w+\d+/
142+
if symbol.starts_with("__ubsan") {
143+
instr_map.insert("Address Sanitizer (ASAN)".to_string(), json!(true));
144+
145+
// /_ZN\w+__asan\w+\d+/
146+
} else if symbol.starts_with("__asan") {
147+
instr_map.insert("Undefined Behavior Sanitizer (UBSAN)".to_string(), json!(true));
148+
149+
// /__afl\w+\d+/
150+
} else if symbol.starts_with("__afl") {
151+
instr_map.insert("AFL Instrumentation".to_string(), json!(true));
152+
153+
// /__llvm\w+\d+/
154+
} else if symbol.starts_with("__llvm") {
155+
instr_map.insert("LLVM Code Coverage".to_string(), json!(true));
156+
}
157+
}
158+
}
159+
unimplemented!();
160+
}
132161
}

src/check/mach.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const MH_ALLOW_STACK_EXECUTION: u32 = 0x20000;
1616
const MH_NO_HEAP_EXECUTION: u32 = 0x1000000;
1717

1818
impl Analyze for MachO<'_> {
19-
fn run_compilation_checks(&self, _bytes: &[u8]) -> BinResult<GenericMap> {
19+
fn compilation(&self, _bytes: &[u8]) -> BinResult<GenericMap> {
2020
todo!()
2121
}
2222

23-
fn run_mitigation_checks(&self) -> GenericMap {
23+
fn mitigations(&self) -> GenericMap {
2424
let mut mitigate_map: GenericMap = GenericMap::new();
2525

2626
let nx_stack: bool = matches!(self.header.flags & MH_ALLOW_STACK_EXECUTION, 0);
@@ -59,4 +59,8 @@ impl Analyze for MachO<'_> {
5959
mitigate_map.insert("__RESTRICT segment".to_string(), json!(restrict));
6060
mitigate_map
6161
}
62+
63+
fn instrumentation(&self) -> Option<GenericMap> {
64+
unimplemented!();
65+
}
6266
}

src/check/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pub mod elf;
22
pub mod mach;
33
pub mod pe;
44

5-
use yara::{Compiler, MetadataValue};
5+
//use yara::{Compiler, MetadataValue};
66

77
use crate::errors::BinResult;
88
use crate::rules::UNIVERSAL_COMPILER_RULES;
@@ -14,6 +14,7 @@ pub type GenericMap = std::collections::BTreeMap<String, serde_json::Value>;
1414
/// reusable functions for parsing out features and doing static analysis.
1515
pub trait Analyze {
1616
fn detect_compiler_runtime(&self, os_specific: &str, bytes: &[u8]) -> BinResult<String> {
17+
/*
1718
// initialize with universal compiler runtime rules first, then add os_specific ones
1819
let mut compiler = Compiler::new()?;
1920
compiler.add_rules_str(UNIVERSAL_COMPILER_RULES)?;
@@ -30,10 +31,12 @@ pub trait Analyze {
3031
Ok(name.to_string())
3132
} else {
3233
Ok("N/A".to_string())
33-
}
34+
}*/
35+
Ok("N/A".to_string())
3436
}
3537

3638
/// To be implemented for each specific binary format
37-
fn run_compilation_checks(&self, bytes: &[u8]) -> BinResult<GenericMap>;
38-
fn run_mitigation_checks(&self) -> GenericMap;
39+
fn compilation(&self, bytes: &[u8]) -> BinResult<GenericMap>;
40+
fn mitigations(&self) -> GenericMap;
41+
fn instrumentation(&self) -> Option<GenericMap>;
3942
}

src/check/pe.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::errors::BinResult;
2121
use crate::rules;
2222

2323
impl Analyze for PE<'_> {
24-
fn run_compilation_checks(&self, bytes: &[u8]) -> BinResult<GenericMap> {
24+
fn compilation(&self, bytes: &[u8]) -> BinResult<GenericMap> {
2525
let mut comp_map = GenericMap::new();
2626

2727
// supported: DLL or EXE
@@ -44,7 +44,7 @@ impl Analyze for PE<'_> {
4444
Ok(comp_map)
4545
}
4646

47-
fn run_mitigation_checks(&self) -> GenericMap {
47+
fn mitigations(&self) -> GenericMap {
4848
let mut mitigation_checks: GenericMap = GenericMap::new();
4949

5050
if let Some(optional_header) = self.header.optional_header {
@@ -89,4 +89,8 @@ impl Analyze for PE<'_> {
8989
}
9090
mitigation_checks
9191
}
92+
93+
fn instrumentation(&self) -> Option<GenericMap> {
94+
unimplemented!();
95+
}
9296
}

src/detect.rs

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ use crate::rules;
99
use goblin::mach::Mach;
1010
use goblin::Object;
1111

12-
use yara::Compiler;
13-
1412
use byte_unit::Byte;
1513
use chrono::prelude::*;
1614
use serde_json::{json, Value};
@@ -25,7 +23,6 @@ pub struct Detector {
2523
compilation: GenericMap,
2624
mitigations: GenericMap,
2725
instrumentation: Option<GenericMap>,
28-
//anti_analysis: AntiAnalysis,
2926
}
3027

3128
impl Detector {
@@ -56,9 +53,6 @@ impl Detector {
5653
// read raw binary from path
5754
let data: Vec<u8> = std::fs::read(&binpath)?;
5855

59-
// detect presence of dynamic instrumentation frameworks
60-
let instrumentation = Detector::detect_instrumentation(&data)?;
61-
6256
// parse executable as format and run format-specific mitigation checks
6357
match Object::parse(&data)? {
6458
Object::Elf(elf) => Ok(Self {
@@ -76,9 +70,9 @@ impl Detector {
7670
basic_map.insert("Entry Point Address".to_string(), json!(entry_point));
7771
basic_map
7872
},
79-
compilation: elf.run_compilation_checks(&data)?,
80-
mitigations: elf.run_mitigation_checks(),
81-
instrumentation,
73+
compilation: elf.compilation(&data)?,
74+
mitigations: elf.mitigations(),
75+
instrumentation: elf.instrumentation(),
8276
}),
8377
Object::PE(pe) => Ok(Self {
8478
basic: {
@@ -97,48 +91,23 @@ impl Detector {
9791
basic_map.insert("Entry Point Address".to_string(), json!(entry_point));
9892
basic_map
9993
},
100-
compilation: pe.run_compilation_checks(&data)?,
101-
mitigations: pe.run_mitigation_checks(),
102-
instrumentation,
94+
compilation: pe.compilation(&data)?,
95+
mitigations: pe.mitigations(),
96+
instrumentation: None,
10397
}),
10498
Object::Mach(Mach::Binary(mach)) => Ok(Self {
10599
basic: {
106100
basic_map.insert("Binary Format".to_string(), json!("Mach-O"));
107101
basic_map
108102
},
109-
compilation: mach.run_compilation_checks(&data)?,
110-
mitigations: mach.run_mitigation_checks(),
111-
instrumentation,
103+
compilation: mach.compilation(&data)?,
104+
mitigations: mach.mitigations(),
105+
instrumentation: None,
112106
}),
113107
_ => Err(BinError::new("unsupported filetype for analysis")),
114108
}
115109
}
116110

117-
#[inline]
118-
fn detect_instrumentation(data: &[u8]) -> BinResult<Option<GenericMap>> {
119-
use yara::MetadataValue;
120-
121-
// execute YARA rules for instrumentation frameworks
122-
let mut compiler = Compiler::new()?;
123-
compiler.add_rules_str(rules::INSTRUMENTATION_RULES)?;
124-
let rules = compiler.compile_rules()?;
125-
126-
// parse out matches into genericmap
127-
let inst_matches = rules.scan_mem(&data, 5)?;
128-
let mut instrumentation = GenericMap::new();
129-
for rule in inst_matches.iter() {
130-
if let MetadataValue::String(name) = rule.metadatas[0].value {
131-
instrumentation.insert(String::from(name), json!(true));
132-
}
133-
}
134-
135-
if instrumentation.is_empty() {
136-
Ok(None)
137-
} else {
138-
Ok(Some(instrumentation))
139-
}
140-
}
141-
142111
/// Output all the finalized report collected on the specific executable, writing to
143112
/// JSON path if specificed not as `-`.
144113
pub fn output(&self, json: Option<&str>) -> serde_json::Result<()> {

src/errors.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,4 @@ impl From<serde_json::Error> for BinError {
3737
}
3838
}
3939

40-
impl From<yara::Error> for BinError {
41-
fn from(error: yara::Error) -> Self {
42-
Self(error.to_string())
43-
}
44-
}
45-
46-
// not sure why there are two error types
47-
impl From<yara::YaraError> for BinError {
48-
fn from(error: yara::YaraError) -> Self {
49-
Self(error.to_string())
50-
}
51-
}
52-
5340
impl Error for BinError {}

src/main.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,14 @@ fn parse_args<'a>() -> ArgMatches<'a> {
2323
App::new(env!("CARGO_PKG_NAME"))
2424
.version(env!("CARGO_PKG_VERSION"))
2525
.author("ex0dus <ex0dus at codemuch.tech>")
26-
.about("Swiss Army Knife for Binary (In)security")
26+
.about("Binary (In)security tool")
2727
.setting(AppSettings::ArgRequiredElseHelp)
2828
.arg(
2929
Arg::with_name("BINARY")
3030
.help("Path to binary or binaries to analyze.")
3131
.takes_value(true)
3232
.required(true),
3333
)
34-
/*
35-
.arg(
36-
Arg::with_name("info")
37-
.help("Given a tag, get more information about a specific mitigation.")
38-
.short("i")
39-
.long("info")
40-
.takes_value(true)
41-
.required(false),
42-
)
43-
*/
4434
.arg(
4535
Arg::with_name("json")
4636
.help("Output results in JSON. Use - for stdout.")

src/rules.rs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,3 @@
1-
//! YARA rules to apply against all binary formats. We use this rather than parsing through the
2-
//! executable format through libgoblin to cut out implementing repetition for each format.
3-
4-
pub const INSTRUMENTATION_RULES: &str = r#"
5-
rule afl {
6-
meta:
7-
name = "AFL Instrumentation"
8-
strings:
9-
$afl = /__afl\w+\d+/
10-
condition:
11-
any of them
12-
}
13-
14-
rule asan {
15-
meta:
16-
name = "Address Sanitizer (ASan)"
17-
strings:
18-
$asan = /_ZN\w+__asan\w+\d+/
19-
condition:
20-
any of them
21-
}
22-
23-
rule ubsan {
24-
meta:
25-
name = "Undefined Behavior Sanitizer (UBsan)"
26-
strings:
27-
$ubsan = /__ubsan\w+\d+/
28-
condition:
29-
any of them
30-
}
31-
32-
rule llvm {
33-
meta:
34-
name = "LLVM Code Coverage"
35-
strings:
36-
$llvm = /__llvm\w+\d+/
37-
condition:
38-
any of them
39-
}
40-
"#;
41-
421
pub const UNIVERSAL_COMPILER_RULES: &str = r#"
432
rule rust {
443
meta:

0 commit comments

Comments
 (0)