Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Machine Learning-Driven Trait Classification and Filtering #155

Open
lemanschik opened this issue Nov 8, 2024 · 9 comments

Comments

@lemanschik
Copy link

lemanschik commented Nov 8, 2024

Is your feature request related to a problem? Please describe.
When analyzing hundreds or thousands of binary traits, it becomes time-consuming to manually classify and identify relevant patterns, especially when looking for traits that are similar to known malware families. Filtering out irrelevant or low-priority traits is challenging without an automated classification system, leading to inefficiencies and potentially missed detections.

Describe the solution you'd like
A machine learning-driven classification and filtering feature within binlex to automatically sort extracted binary traits based on similarity scores or association with known malware families. This feature would use lightweight pre-trained models to assess the likelihood that certain traits are associated with known patterns, saving researchers time by focusing their attention on high-priority traits. The solution should include:

  • A command-line option to toggle machine learning-based filtering on or off.
  • Configuration options to adjust classification thresholds, load custom-trained models, and select specific model types if available.
  • An option to download and use pre-trained models for commonly researched malware types.

Describe alternatives you've considered

  1. Manual Filtering: Continuing to manually sort and classify traits, which is less efficient and more prone to oversight, especially when handling large volumes.
  2. Scripted Filters: Using rule-based scripts or simple filters to exclude traits, though this approach lacks the flexibility and adaptability of a machine learning solution, as rule-based filters may miss new or evolving malware patterns.

Additional context
Integrating machine learning would help binlex maintain its core philosophy of being simple and extendable. This feature would make it more scalable for large-scale binary analysis and could be a significant time-saver for production environments, as it would reduce manual filtering and improve detection accuracy. Additionally, providing an option to load custom models would support the research community in experimenting with their own classification methods without altering the core binlex code.

Example

Here’s a simple Rust example demonstrating how you might implement a trait classification and filtering system with machine learning-like functionality. This example doesn’t use a full machine learning model but simulates a similarity score for traits, showing how Rust could handle binary data traits and filter based on a threshold. In a real implementation, you might use an ML library like linfa for actual model predictions.

use std::collections::HashMap;

// Define a struct to represent a binary trait
#[derive(Debug)]
struct BinaryTrait {
    id: u32,
    data: Vec<u8>,  // Raw binary data
    similarity_score: f64,  // Score representing similarity to known malware patterns
}

// Function to "score" a binary trait's similarity to known malware patterns
fn calculate_similarity_score(trait_data: &[u8]) -> f64 {
    // For illustration, we sum the byte values and mod by 1.0 to get a score between 0 and 1
    // In practice, this could involve a machine learning model or a complex heuristic.
    trait_data.iter().map(|&b| b as f64).sum::<f64>() % 1.0
}

// Function to filter traits based on a threshold score
fn filter_traits(traits: &[BinaryTrait], threshold: f64) -> Vec<BinaryTrait> {
    traits
        .iter()
        .filter(|t| t.similarity_score >= threshold)
        .cloned()  // Clone here since we're returning new vector of filtered traits
        .collect()
}

fn main() {
    // Simulated known traits for classification reference (could be from a database in practice)
    let known_traits: HashMap<u32, Vec<u8>> = vec![
        (1, vec![0x01, 0x02, 0x03, 0x04]),
        (2, vec![0x05, 0x06, 0x07, 0x08]),
        (3, vec![0x09, 0x0A, 0x0B, 0x0C]),
    ].into_iter().collect();

    // Example set of binary traits to classify
    let mut traits: Vec<BinaryTrait> = known_traits
        .iter()
        .map(|(id, data)| {
            let similarity_score = calculate_similarity_score(data);
            BinaryTrait {
                id: *id,
                data: data.clone(),
                similarity_score,
            }
        })
        .collect();

    // Set a similarity threshold
    let threshold = 0.5;
    println!("Filtering traits with similarity score >= {}", threshold);

    // Filter traits based on the threshold
    let filtered_traits = filter_traits(&traits, threshold);

    // Display results
    println!("Filtered traits:");
    for t in filtered_traits {
        println!("Trait ID: {}, Similarity Score: {:.2}", t.id, t.similarity_score);
    }
}

Explanation

BinaryTrait Struct: Defines a BinaryTrait struct to hold a binary trait's id, raw binary data, and a similarity_score.
calculate_similarity_score Function: Simulates a similarity scoring function by summing the byte values of the trait and taking the modulus to produce a score between 0 and 1. In a real scenario, this could be replaced by a function that uses an ML model.
filter_traits Function: Filters traits by comparing each trait’s similarity score to a specified threshold.
Main Function: Sets up sample traits, calculates similarity scores, filters based on a threshold, and displays the filtered traits.

@c3rb3ru5d3d53c
Copy link
Owner

Hey @lemanschik, I appreciate your thoughts on additional tooling.

There are a couple levels of comparison one would want as an analyst.

  1. Is sample similar to another sample?
  2. Is this trait similar to another trait?

I think both these comparisons are valid questions to ask.

That being said, at the trait level, we have similarity hashing that you can leverage to do this already, it just needs a separate tool that enables you to read and exclude or include traits of your choosing based on a similarity threshold.

As for better similarity detection and full sample classification, I think the tensorflow library in rust would be the best bet for that, ideally it would be able to save a model for each file.

Ideally, these model files would be usable in a variety of ML models in a more universal format.

That is the plan, overall to do what you are saying here, just with similarity hashing on the normalized blocks and functions after memory addressing has been removed.

The binlex rust port code is here: https://github.com/c3rb3ru5d3d53c/binlex-rs

If you would like help create stand-alone helper tools for filtering they go in the src/bin/ directory.

Of course any contributions are very helpful!

@c3rb3ru5d3d53c
Copy link
Owner

Hey @lemanschik, i've experimented with the burn rust library, it looks great and appears to have a ton of support in rust for both GPU and CPU based training.

Ideally, we can have a sub tool call blburn that would leverage this library very simply and output a onnx file for each sample you may want to save

If we are able to handle the import and export of onnx model files, then we should be able to make some simple off the shelf models that people can use for filtering as well as share with others.

At this time I've tried the library but have very little to no experience in machine learning libraries, a very simple CLI program example that takes vectors of f64s for the training using the burn library and output a onnx file would be really helpful, I've looked at their examples and cannot find anything really useful to get me started with it.

@lemanschik
Copy link
Author

@c3rb3ru5d3d53c Here’s a minimal example in Rust that demonstrates how to use the Burn library to create a simple machine learning model, train it with some sample data (vectors of f64), and export it as an ONNX file.

This example will use the Burn library to create a basic linear regression model, a common starting point in ML, and will save the trained model in the ONNX format. You can adapt it to more complex architectures as needed.

Dependencies

First, add the required dependencies in your Cargo.toml file:

[dependencies]
burn = "0.7"  # replace with the latest version
burn-onnx = "0.7"
ndarray = "0.15"

Code Example

Here's the Rust code that sets up a simple linear regression model, trains it with some sample data, and exports it as an ONNX file.

use burn::tensor::backend::Backend;
use burn::tensor::Data;
use burn::module::Module;
use burn::optim::Optimizer;
use burn::train::LearnerBuilder;
use burn::onnx::export_model;
use burn::tensor::Tensor;
use std::path::Path;

// Define a simple linear regression model
#[derive(Module, Debug)]
struct LinearRegression<B: Backend> {
    layer: burn::nn::Linear<B>,
}

impl<B: Backend> LinearRegression<B> {
    fn new(input_dim: usize, output_dim: usize) -> Self {
        let layer = burn::nn::LinearConfig::new(input_dim, output_dim).init();
        Self { layer }
    }

    // Forward pass
    fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
        self.layer.forward(input)
    }
}

fn main() -> burn::Result<()> {
    // Define model parameters
    let input_dim = 1;  // Simple example with single feature
    let output_dim = 1;

    // Initialize the model
    let model = LinearRegression::<burn::backend::TchBackend>::new(input_dim, output_dim);

    // Sample training data
    let x_train = Tensor::from_data(Data::from([[-1.0], [0.0], [1.0], [2.0], [3.0]]));
    let y_train = Tensor::from_data(Data::from([[-2.0], [0.0], [2.0], [4.0], [6.0]]));

    // Configure the optimizer
    let optimizer = burn::optim::Adam::new(1e-3);

    // Set up the learner for training
    let learner = LearnerBuilder::new()
        .model(model)
        .optimizer(optimizer)
        .loss_fn(burn::nn::loss::MSELoss::new())
        .build();

    // Train the model (for demonstration, we train in a loop with 100 steps)
    let epochs = 100;
    for epoch in 0..epochs {
        learner.forward_backward_step(x_train.clone(), y_train.clone());
        println!("Epoch {}/{} completed", epoch + 1, epochs);
    }

    // Save the trained model as an ONNX file
    let model_path = Path::new("linear_regression.onnx");
    export_model(&learner.model(), model_path)?;

    println!("Model saved as {:?}", model_path);
    Ok(())
}

Explanation

  1. Model Definition: LinearRegression struct is defined using Burn’s Module trait, representing a basic linear model.
  2. Training Data: Some dummy training data is created using Tensor with f64 values for simplicity.
  3. Training Loop: A simple training loop runs 100 epochs, using Mean Squared Error (MSE) as the loss function to simulate training on the dataset.
  4. ONNX Export: After training, the model is saved as an ONNX file using export_model.

Running the Example

Compile and run this with:

cargo run

If successful, this will output a file named linear_regression.onnx, which can be loaded and used in other applications. This basic example sets up the foundation for blburn and could be extended with additional Burn capabilities and data processing steps.

@c3rb3ru5d3d53c
Copy link
Owner

Hey @lemanschik, this is very helpful, I'll give this a shot right now and let you know how it goes.

@lemanschik
Copy link
Author

Reaktion 1:
To meet the maintainer's requirements, here’s a structured approach using Burn and TensorFlow Rust libraries for similarity detection, model training, and trait filtering tools. Below is a step-by-step guide to implement comparison tools for sample and trait similarity and an ONNX model training/exporting CLI.

High-Level Approach
Trait-Level Filtering with Similarity Hashing: Create a CLI tool that performs trait similarity checks using existing similarity hashing, with options for threshold-based inclusion/exclusion of traits.
Sample-Level Similarity Detection: Use TensorFlow for full sample comparison, leveraging models to detect similarities across entire files.
ONNX Model Export for Compatibility: Train and export models as ONNX for portability, enabling their use across various ML frameworks and languages.
Each section includes code snippets and details on implementation.

Step 1: Trait-Level Similarity Filtering CLI Tool
Place this tool in src/bin/trait_filter.rs to read similarity hashes of traits and filter them based on a given threshold.

trait_filter.rs Example
This tool will load traits and compare their similarity hashes. Here, similarity_score is a placeholder for your hash-based similarity metric.

use binlex_rs::traits::{TraitLoader, Trait};  // Assuming TraitLoader and Trait exist in binlex-rs
use std::path::PathBuf;
use std::fs;

fn calculate_similarity(trait_a: &Trait, trait_b: &Trait) -> f64 {
    // Placeholder for hash similarity calculation
    // Implement with specific hash comparison logic
    trait_a.similarity_score(trait_b)
}

fn filter_traits(traits: &[Trait], threshold: f64) -> Vec<&Trait> {
    traits.iter()
        .filter(|&t| t.similarity_score >= threshold)
        .collect()
}

fn main() {
    let trait_path = PathBuf::from("path/to/trait/file");
    let threshold: f64 = 0.7;

    // Load traits using TraitLoader
    let traits = TraitLoader::load(trait_path).expect("Failed to load traits");

    // Filter traits based on similarity score
    let filtered_traits = filter_traits(&traits, threshold);

    println!("Filtered traits with similarity >= {}:", threshold);
    for t in filtered_traits {
        println!("Trait ID: {}, Score: {:.2}", t.id, t.similarity_score);
    }
}

Run this tool with:

cargo run --bin trait_filter -- --trait-path "path/to/traits" --threshold 0.7

This script loads a list of traits, calculates similarity scores, and outputs traits that meet the threshold.

Step 2: Sample-Level Similarity Detection Using TensorFlow Rust
For file-level comparisons, we can use TensorFlow Rust bindings. Save this as src/bin/sample_classifier.rs.

sample_classifier.rs Example
This script uses TensorFlow to train a classifier that groups similar samples based on extracted features.

use tensorflow::{Graph, Session, SessionOptions, SessionRunArgs, Tensor};
use std::path::PathBuf;

fn create_sample_classification_model() -> tensorflow::Result<Graph> {
    let mut graph = Graph::new();
    // Build model graph here, e.g., add input, hidden layers, and output layers.
    // ... (TensorFlow model building code)
    Ok(graph)
}

fn main() -> tensorflow::Result<()> {
    // Define paths
    let model_path = PathBuf::from("path/to/save/model");
    let sample_data = vec![
        vec![0.1, 0.2, 0.3], // Sample features
        vec![0.4, 0.5, 0.6],
    ];

    // Create and save the model
    let mut session = Session::new(&SessionOptions::new(), &create_sample_classification_model()?)?;
    let inputs = Tensor::new(&[2, 3]).with_values(&sample_data.concat())?;
    let mut run_args = SessionRunArgs::new();
    run_args.add_feed(&graph.operation_by_name_required("input")?, 0, &inputs);

    session.run(&mut run_args)?;

    println!("Sample classification model saved at {:?}", model_path);
    Ok(())
}

Use this script to train and save TensorFlow models that can classify samples, which you can later load and use for sample-level similarity checks.

Step 3: ONNX Model Training and Export CLI Tool
In src/bin/blburn.rs, use Burn to create a training utility that outputs ONNX models.

blburn.rs Example
This example will use Burn to train a model, then save it as an ONNX file for cross-platform usage.

use burn::tensor::Data;
use burn::module::Module;
use burn::optim::Optimizer;
use burn::train::LearnerBuilder;
use burn::onnx::export_model;
use burn::tensor::Tensor;
use std::path::Path;

#[derive(Module, Debug)]
struct SimpleModel<B: Backend> {
    layer: burn::nn::Linear<B>,
}

impl<B: Backend> SimpleModel<B> {
    fn new(input_dim: usize, output_dim: usize) -> Self {
        let layer = burn::nn::LinearConfig::new(input_dim, output_dim).init();
        Self { layer }
    }

    fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
        self.layer.forward(input)
    }
}

fn main() -> burn::Result<()> {
    let input_dim = 3;
    let output_dim = 1;
    let model = SimpleModel::<burn::backend::TchBackend>::new(input_dim, output_dim);

    let x_train = Tensor::from_data(Data::from([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]));
    let y_train = Tensor::from_data(Data::from([[1.0], [0.0]]));

    let optimizer = burn::optim::Adam::new(1e-3);

    let learner = LearnerBuilder::new()
        .model(model)
        .optimizer(optimizer)
        .loss_fn(burn::nn::loss::MSELoss::new())
        .build();

    for _ in 0..100 {
        learner.forward_backward_step(x_train.clone(), y_train.clone());
    }

    let model_path = Path::new("output_model.onnx");
    export_model(&learner.model(), model_path)?;

    println!("Model saved as ONNX at {:?}", model_path);
    Ok(())
}

run it with

cargo run --bin blburn

This script creates and saves a model that could serve as a foundation for various filtering tasks within the binlex ecosystem.

Contributions
All code can be integrated into the binlex project as follows:

trait_filter.rs: Tool for trait-level filtering based on similarity hashing.
sample_classifier.rs: Sample classification tool using TensorFlow.
blburn.rs: Burn-based ONNX model trainer, which could evolve into more robust ML models as you gain familiarity.
This setup aligns with the maintainer’s vision for binlex while providing tools that simplify similarity comparisons at multiple levels.

@lemanschik
Copy link
Author

Reaktion 2:
Based on your approach, here’s a more detailed breakdown to help with your next steps in binlex for similarity detection, trait filtering, and TensorFlow-based classification. I’ve also added a Rust code example to demonstrate a standalone filtering tool that could go into the src/bin/ directory.

Key Components
Trait-Level Similarity Tool:
Using similarity hashing for trait-level comparison, a standalone tool could be created to read extracted traits, calculate a similarity hash, and filter based on a threshold.
This tool could be combined with the existing similarity hashing algorithms in binlex or external hashing libraries if needed for fast computation.
Full Sample Classification Tool with TensorFlow:
For more comprehensive sample-to-sample comparisons, TensorFlow Rust would allow you to build models that classify entire samples, save them in a universal format (like ONNX), and make them accessible across other ML tools.
A helper tool can be built to train, save, and load TensorFlow-based models for binary classification, outputting universal formats for broader compatibility.
Output to Universal Formats:
Since you’re targeting models in ONNX, you could also explore using TensorFlow Rust to export directly to ONNX for consistency in your pipeline.
Example: Trait-Level Similarity Hash Filtering Tool
Here’s a Rust example that demonstrates a simple CLI for trait similarity filtering based on a hypothetical similarity hash. This tool could go in src/bin/ and work with the binlex trait hash data to filter out traits by similarity threshold.

use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;

// Function to calculate a simple similarity hash (example only)
fn calculate_similarity_hash(trait_data: &str) -> u64 {
    let mut hash = 0u64;
    for byte in trait_data.as_bytes() {
        hash = hash.wrapping_mul(31).wrapping_add(*byte as u64);
    }
    hash
}

// Function to filter traits based on a similarity threshold
fn filter_traits(traits: &HashMap<String, String>, threshold: u64) -> Vec<String> {
    let mut filtered_traits = Vec::new();
    for (id, data) in traits {
        let hash = calculate_similarity_hash(data);
        if hash >= threshold {
            filtered_traits.push(id.clone());
        }
    }
    filtered_traits
}

fn main() -> io::Result<()> {
    // Parse CLI arguments
    let args: Vec<String> = env::args().collect();
    if args.len() < 3 {
        eprintln!("Usage: {} <traits_file> <threshold>", args[0]);
        return Ok(());
    }
    let traits_file = &args[1];
    let threshold: u64 = args[2].parse().expect("Threshold must be a number");

    // Load traits from file
    let path = Path::new(traits_file);
    let file = File::open(path)?;
    let reader = BufReader::new(file);

    // Parse traits and compute hashes
    let mut traits = HashMap::new();
    for line in reader.lines() {
        let line = line?;
        let parts: Vec<&str> = line.split(',').collect();
        if parts.len() != 2 {
            continue;  // Skip malformed lines
        }
        let id = parts[0].to_string();
        let data = parts[1].to_string();
        traits.insert(id, data);
    }

    // Filter traits based on similarity threshold
    let filtered_traits = filter_traits(&traits, threshold);

    // Output filtered traits
    println!("Filtered traits (ID):");
    for trait_id in filtered_traits {
        println!("{}", trait_id);
    }

    Ok(())
}

Explanation
CLI Parsing: Accepts a traits_file containing trait data and a threshold value for filtering.
Trait Hash Calculation: Simulates a similarity hash function. In practice, you could replace this with a more sophisticated hash function, possibly leveraging binlex's existing similarity hashing.
Filtering Logic: Only includes traits with a similarity hash that meets or exceeds the threshold.
Output: Prints the IDs of traits that meet the similarity criteria.
Full Sample Classification Using TensorFlow Rust
For the full sample classification, TensorFlow Rust is still somewhat limited, so your best approach is to use a Python script for model training and export it as an ONNX file, then load the model in Rust for inference.

However, here’s a minimal Rust example to show how you could load and use an ONNX model once it’s exported. Place this in another helper tool within src/bin/.

use burn::onnx::import_model;
use burn::tensor::{Data, Tensor};
use std::path::Path;

fn main() -> burn::Result<()> {
    // Path to the ONNX model file
    let model_path = Path::new("linear_regression.onnx");

    // Import the model
    let model = import_model::<burn::backend::TchBackend>(model_path)?;

    // Dummy input data
    let input_data = Tensor::from_data(Data::from([[1.0], [2.0], [3.0]]));

    // Run inference
    let output = model.forward(input_data);
    println!("Model output: {:?}", output);

    Ok(())
}

Next Steps
Integrate with binlex’s Traits: Adjust the hashing or similarity check functions to align with your existing trait structures.
Train TensorFlow Models in Python: For now, train models in Python, export them to ONNX, and load them in Rust for deployment.
Extend CLI for Filtering Options: Enhance the CLI to support dynamic threshold input, configuration files, and maybe even comparison against a library of saved models.
This setup should align well with binlex's modular philosophy and provide a scalable foundation for similarity filtering and sample classification!

@c3rb3ru5d3d53c
Copy link
Owner

hey @lemanschik, I believe you are likely AI bot, but your code for the simple example does not work there is no burn-onnx crate and there are other syntax related errors likely due to changes in the lastest burn library

@lemanschik
Copy link
Author

@c3rb3ru5d3d53c good conclusion at all i wanted to make a usefull suggestion so i used chatgpt to make your life better :)

@c3rb3ru5d3d53c
Copy link
Owner

Hey @lemanschik, I already have access to chatgpt, and it had the same errors with its suggestions on the burn library

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants