From ea8d43c4655921b48eca6a791e065b0f162232f2 Mon Sep 17 00:00:00 2001 From: Maxi Biandratti Date: Sun, 1 Dec 2024 17:13:23 +0100 Subject: [PATCH] [README] (#27) * upgrade README * fix fmt * document code * example in readme --- Cargo.lock | 2 +- Cargo.toml | 9 ++---- README.md | 81 +++++++++++++++++++++++++++++++++++++++------- examples/README.md | 10 ++++++ examples/p0f.rs | 3 +- src/db.rs | 12 ++++++- src/lib.rs | 21 ++++++++++++ 7 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 examples/README.md diff --git a/Cargo.lock b/Cargo.lock index 115eff7..0b867b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,7 +269,7 @@ dependencies = [ [[package]] name = "passivetcp-rs" -version = "0.0.0" +version = "0.1.0-alpha.0" dependencies = [ "clap", "failure", diff --git a/Cargo.toml b/Cargo.toml index 6942452..1ea0cd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "passivetcp-rs" +version = "0.1.0-alpha.0" edition = "2021" -description = "A Rust library for passive traffic fingerprinting [p0f]" -license = "MIT OR Apache-2.0" +description = "Passive traffic fingerprinting [p0f]" +license = "MIT" authors = ["Maximiliano Biandratti "] repository = "https://github.com/biandratti/passivetcp-rs" readme = "README.md" @@ -18,10 +19,6 @@ log = "0.4.22" lazy_static = "1.5.0" ttl_cache = "0.5.1" -[lib] -name = "passivetcp" -path = "src/lib.rs" - [[example]] name = "p0f" path = "examples/p0f.rs" diff --git a/README.md b/README.md index 68ca9d4..dc770e7 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ -### Get network Interface -``` -ip link show -``` +# Passive traffic fingerprinting +An experimental Rust library inspired by p0f, the well-known passive OS fingerprinting tool originally written in C. This library aims to bring the power of passive TCP/IP fingerprinting to the Rust ecosystem while offering a more modern, efficient, and extensible implementation. -### Process packages -``` -cargo build --release --examples -sudo RUST_BACKTRACE=1 ./target/release/examples/p0f --interface -``` +#### What is Passive TCP Fingerprinting? +Passive TCP fingerprinting is a technique that allows you to infer information about a remote host's operating system and network stack without sending any probes. By analyzing characteristics of the TCP/IP packets that are exchanged during a normal network conversation, passivetcp-rs provides insights into the remote system’s OS type, version, and network stack implementation. +#### This technique is useful for a variety of purposes, including: +- Network analysis: Identifying the types of devices and systems on a network without active scanning. +- Security: Discovering hidden or obscure systems by their network behavior. +- Fingerprinting for research: Understanding patterns in network traffic and improving security posture. +About passivetcp-rs -### A snippet of typical p0f output may look like this: +This Rust implementation of passive TCP fingerprinting is still in its experimental phase, and while it builds upon the established ideas of p0f, it is not yet feature-complete. The library currently provides basic functionality, but we plan to expand its capabilities as the project matures. -``` +#### A snippet of typical p0f output may look like this: + +```text .-[ 1.2.3.4/1524 -> 4.3.2.1/80 (syn) ]- | | client = 1.2.3.4 @@ -48,4 +50,59 @@ sudo RUST_BACKTRACE=1 ./target/release/examples/p0f --interface | raw_freq = 250.00 Hz | `---- -``` \ No newline at end of file +``` + +### Installation +To use passivetcp-rs in your Rust project, add the following dependency to your `Cargo.toml`: +```toml +[dependencies] +passivetcp-rs = "0.1.0-alpha.0" +``` + +### Usage +Here’s a basic example of how to use passivetcp-rs: +```rust +use passivetcp_rs::db::Database; +use passivetcp_rs::P0f; + +let args = Args::parse(); +let interface_name = args.interface; +let db = Database::default(); +let mut p0f = P0f::new(&db, 100); + +let interfaces: Vec = datalink::interfaces(); +let interface = interfaces + .into_iter() + .find(|iface| iface.name == interface_name) + .expect("Could not find the interface"); + +let config = Config { + promiscuous: true, + ..Config::default() +}; + +let (_tx, mut rx) = match datalink::channel(&interface, config) { + Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx), + Ok(_) => panic!("Unhandled channel type"), + Err(e) => panic!("Unable to create channel: {}", e), +}; + +loop { + match rx.next() { + Ok(packet) => { + let p0f_output = p0f.analyze_tcp(packet); + p0f_output.syn.map(|syn| println!("{}", syn)); + p0f_output.syn_ack.map(|syn_ack| println!("{}", syn_ack)); + p0f_output.mtu.map(|mtu| println!("{}", mtu)); + p0f_output.uptime.map(|uptime| println!("{}", uptime)); + } + Err(e) => eprintln!("Failed to read packet: {}", e), + } +} +``` + +### Contributing +This library is in its early stages, and contributions are very welcome. If you have ideas for additional features, bug fixes, or optimizations, please feel free to open issues or submit pull requests. We are particularly looking for help with extending the feature set and improving the performance of the library. + +### License +This project is licensed under the MIT License. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..379bba9 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,10 @@ +### Get network Interface +``` +ip link show +``` + +### Process packages +``` +cargo build --release --examples +sudo RUST_BACKTRACE=1 ./target/release/examples/p0f --interface +``` diff --git a/examples/p0f.rs b/examples/p0f.rs index 255dc24..7ae931b 100644 --- a/examples/p0f.rs +++ b/examples/p0f.rs @@ -1,6 +1,7 @@ use clap::Parser; use log::debug; -use passivetcp::{db::Database, P0f}; +use passivetcp_rs::db::Database; +use passivetcp_rs::P0f; use pnet::datalink::{self, Config, NetworkInterface}; #[derive(Parser, Debug)] diff --git a/src/db.rs b/src/db.rs index e183a03..e6a8d69 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,7 +1,9 @@ use crate::{http, tcp}; use std::fmt; -#[allow(dead_code)] //TODO: WIP +/// Represents the database used by `P0f` to store signatures and associated metadata. +/// The database contains signatures for analyzing TCP and HTTP traffic, as well as +/// other metadata such as MTU mappings and user agent-to-operating system mappings. #[derive(Debug)] pub struct Database { pub classes: Vec, @@ -13,6 +15,8 @@ pub struct Database { pub http_response: Vec<(Label, Vec)>, } +/// Represents a label associated with a signature, which provides metadata about +/// the signature, such as type, class, name, and optional flavor details. #[derive(Clone, Debug, PartialEq)] pub struct Label { pub ty: Type, @@ -21,6 +25,9 @@ pub struct Label { pub flavor: Option, } +/// Enum representing the type of `Label`. +/// - `Specified`: A specific label with well-defined characteristics. +/// - `Generic`: A generic label with broader characteristics. #[derive(Clone, Debug, PartialEq)] pub enum Type { Specified, @@ -28,6 +35,9 @@ pub enum Type { } impl fmt::Display for Type { + /// Creates a default instance of the `Database` by parsing a configuration file + /// located at `config/p0f.fp`. This file is expected to define the default + /// signatures and mappings used for analysis. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } diff --git a/src/lib.rs b/src/lib.rs index 3001a90..f431a6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,13 +21,34 @@ pub struct P0f<'a> { cache: TtlCache, } +/// A passive TCP fingerprinting engine inspired by `p0f`. +/// +/// The `P0f` struct acts as the core component of the library, handling TCP packet +/// analysis and matching signatures using a database of known fingerprints. impl<'a> P0f<'a> { + /// Creates a new instance of `P0f`. + /// + /// # Parameters + /// - `database`: A reference to the database containing known TCP/IP signatures. + /// - `cache_capacity`: The maximum number of connections to maintain in the TTL cache. + /// + /// # Returns + /// A new `P0f` instance initialized with the given database and cache capacity. + pub fn new(database: &'a Database, cache_capacity: usize) -> Self { let matcher: SignatureMatcher = SignatureMatcher::new(database); let cache: TtlCache = TtlCache::new(cache_capacity); Self { matcher, cache } } + /// Analyzes a TCP packet and returns the corresponding `P0fOutput`. + /// + /// # Parameters + /// - `packet`: A byte slice representing the raw TCP packet to analyze. + /// + /// # Returns + /// A `P0fOutput` containing the analysis results, including matched signatures, + /// observed MTU, uptime information, and other details. If no valid data is observed, an empty output is returned. pub fn analyze_tcp(&mut self, packet: &[u8]) -> P0fOutput { if let Ok(observable_signature) = ObservableSignature::extract(packet, &mut self.cache) { if observable_signature.from_client {