From d6c9afb13181369206006d2631e3936ba55f7dd5 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:39:14 -0600 Subject: [PATCH] Add services --- Cargo.toml | 3 + src/database.rs | 259 +++++++----- src/lib.rs | 1 + src/main.rs | 213 +++++----- src/online_scan/online_scan.rs | 7 +- src/port_scan/port_scan.rs | 14 +- src/port_scan/tcp_scan.rs | 49 ++- src/service_scan/mod.rs | 4 + src/service_scan/service_scan.rs | 241 +++++++++++ src/service_scan/services.rs | 664 +++++++++++++++++++++++++++++++ src/service_scan/tcp_http.rs | 21 + src/service_scan/tcp_https.rs | 24 ++ 12 files changed, 1279 insertions(+), 221 deletions(-) create mode 100644 src/service_scan/mod.rs create mode 100644 src/service_scan/service_scan.rs create mode 100644 src/service_scan/services.rs create mode 100644 src/service_scan/tcp_http.rs create mode 100644 src/service_scan/tcp_https.rs diff --git a/Cargo.toml b/Cargo.toml index 7886c00..9230501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,14 @@ version = "0.1.0" edition = "2024" [dependencies] +reqwest = { version = "0.12.15", features = ["blocking"] } byteorder = "1.5.0" indicatif = "0.17.11" +lazy_static = "1.5.0" memchr = "2.7.4" pnet = "0.35.0" rand = "0.9.0" +regex = "1.11.1" rocksdb = "0.23.0" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" diff --git a/src/database.rs b/src/database.rs index e8f290d..8d388ca 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,9 +1,9 @@ use std::{net::IpAddr, sync::Arc, time::Instant}; -use rocksdb::{Cache, ColumnFamily, IteratorMode, Options, WriteBatch, DB}; +use rocksdb::{Cache, ColumnFamily, DB, IteratorMode, Options, WriteBatch}; use serde::{Deserialize, Serialize}; -use crate::port_scan::port_scan::ScanResult; +use crate::{port_scan::port_scan::PortScanResult, service_scan::service_scan::ServiceScanResult}; // Global settings for optimal performance const BLOCK_CACHE_SIZE_MB: usize = 512; // 512MB block cache @@ -18,19 +18,93 @@ pub struct ResultDatabase { } #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct StringRow { - pub id: String, // Row identifier - pub ports: Vec, // Array of string values +pub struct DatabaseResult { + pub id: String, // Row identifier + pub ports: Vec, // Array of string values + pub services: String, // json services } -impl StringRow { +impl DatabaseResult { pub fn to_string(&self) -> String { let mut str = "".to_string(); - str += format!("{} - ports: [{}]", self.id, join_nums(&self.ports, ",")).as_str(); + str += format!( + "{} - ports: [{}] services: [{}]", + self.id, + join_nums(&self.ports, ","), + &self.services + ) + .as_str(); str } + pub fn encode(&self, buf: &mut Vec) { + let values = vec![self.ports_to_string(), self.services.clone()]; + + // Write number of values + buf.extend_from_slice(&(values.len() as u32).to_le_bytes()); + + // Write each value + for value in values { + let value_bytes = value.as_bytes(); + buf.extend_from_slice(&(value_bytes.len() as u32).to_le_bytes()); + buf.extend_from_slice(value_bytes); + } + } + // Binary decoding of row data + pub fn decode(key: &str, data: &[u8]) -> Option { + println!("{}", data.len()); + if data.len() < 8 { + return None; + } + + let mut pos = 0; + + if pos + 4 > data.len() { + return None; + } + let mut values_count_bytes = [0u8; 4]; + values_count_bytes.copy_from_slice(&data[pos..pos + 4]); + let values_count = u32::from_le_bytes(values_count_bytes) as usize; + pos += 4; + + // Read values + let mut values = Vec::with_capacity(values_count); + for _ in 0..values_count { + if pos + 4 > data.len() { + println!("error1!"); + return None; + } + + let mut value_len_bytes = [0u8; 4]; + value_len_bytes.copy_from_slice(&data[pos..pos + 4]); + let value_len = u32::from_le_bytes(value_len_bytes) as usize; + pos += 4; + + if pos + value_len > data.len() { + println!("error!"); + return None; + } + + let value = String::from_utf8_lossy(&data[pos..pos + value_len]).to_string(); + values.push(value); + pos += value_len; + } + + Some(DatabaseResult { + id: key.to_string(), + ports: if 1 > 0 { + split_nums(values[0].as_str(), ",") + } else { + Vec::new() + }, + services: if 1 > 1 { + values[1].clone() + } else { + String::new() + }, + }) + } pub fn ports_to_string(&self) -> String { return join_nums(&self.ports, ","); } @@ -96,7 +170,11 @@ impl ResultDatabase { // Define column families for different indexes - let column_families = vec!["default".to_string(), "ports".to_string()]; + let column_families = vec![ + "default".to_string(), + "ports".to_string(), + "services".to_string(), + ]; Self { path: path.to_string(), @@ -112,9 +190,10 @@ impl ResultDatabase { let mut string_rows = Vec::with_capacity(results.len()); // Pre-allocate capacity for result in results { - string_rows.push(StringRow { + string_rows.push(DatabaseResult { id: result.to_string(), ports: vec![], + services: String::new(), }); } @@ -123,27 +202,46 @@ impl ResultDatabase { pub fn add_tcp_results( &self, - results: &Vec, + results: &Vec, ) -> Result<(), Box> { let mut string_rows = Vec::with_capacity(results.len()); // Pre-allocate capacity for result in results { - string_rows.push(result.to_string_row()); + string_rows.push(result.to_database()); } return self.save_rows(string_rows); } - pub fn save_rows(&self, string_rows: Vec) -> Result<(), Box> { + pub fn add_service_results( + &self, + results: &Vec, + ) -> Result<(), Box> { + let mut string_rows = Vec::with_capacity(results.len()); // Pre-allocate capacity + + for result in results { + let e = result.to_database(); + print!("{}", e.services); + string_rows.push(e); + } + + return self.save_rows(string_rows); + } + + pub fn save_rows( + &self, + string_rows: Vec, + ) -> Result<(), Box> { let db = Arc::new(DB::open_cf(&self.options, &self.path, &self.columns)?); let cf_default = db.cf_handle(&self.columns[0]).unwrap(); let cf_ports = db.cf_handle(&self.columns[1]).unwrap(); + let cf_services = db.cf_handle(&self.columns[2]).unwrap(); let start = Instant::now(); let length = string_rows.len(); // Split the rows into chunks for parallel processing - let chunks: Vec> = string_rows + let chunks: Vec> = string_rows .chunks(BATCH_SIZE) .map(|chunk| chunk.to_vec()) .collect(); @@ -160,19 +258,17 @@ impl ResultDatabase { let mut batch = WriteBatch::default(); for row in chunk { - // Use optimized binary format for the main data - let mut data = Vec::with_capacity(256); + batch.put_cf(cf_default_ref, row.id.as_bytes(), &vec![]); - // Format: id_len + id + count + (len + str) for each value - // Binary format: direct encoding without JSON overhead - encode_row_binary(&mut data, &row); + // Ports + batch.put_cf( + cf_ports, + row.id.as_bytes(), + row.ports_to_string().as_bytes(), + ); - // Store in main column family - batch.put_cf(cf_default_ref, row.id.as_bytes(), &data); - - let idx_key = - format!("{}:{}", fast_escape(row.ports_to_string().as_str()), row.id); - batch.put_cf(cf_ports, idx_key.as_bytes(), row.id.as_bytes()); + // Services + batch.put_cf(cf_services, row.id.as_bytes(), row.services.as_bytes()); } batch @@ -195,19 +291,32 @@ impl ResultDatabase { Ok(()) } - pub fn get_row_by_host(&self, row: &str) -> Option { + pub fn get_row_by_host(&self, row: &str) -> Option { let db = DB::open_cf(&self.options, &self.path, &self.columns); if db.is_err() { return None; }; let db = db.unwrap(); - let cf_default = db.cf_handle("default").unwrap(); - return fetch_row(&db, cf_default, row); + let cfs = vec![ + db.cf_handle(&self.columns[0]).unwrap(), + db.cf_handle(&self.columns[1]).unwrap(), + db.cf_handle(&self.columns[2]).unwrap(), + ]; + + return self.fetch_row(&db, row, &cfs); } - pub fn get_rows_by_port(&self, port: &str) -> Vec { - if let Ok(result) = self.search_substring_in_column(self.columns[0].as_str(), port) { + pub fn get_rows_by_port(&self, port: &str) -> Vec { + if let Ok(result) = self.search_substring_in_column(self.columns[1].as_str(), port) { + return result; + } else { + return Vec::new(); + } + } + + pub fn get_rows_by_service(&self, port: &str) -> Vec { + if let Ok(result) = self.search_substring_in_column(self.columns[2].as_str(), port) { return result; } else { return Vec::new(); @@ -218,12 +327,17 @@ impl ResultDatabase { &self, column: &str, substring: &str, - ) -> Result, rocksdb::Error> { + ) -> Result, rocksdb::Error> { let db = Arc::new(DB::open_cf(&self.options, &self.path, &self.columns)?); let cf = db.cf_handle(column).unwrap(); + let cfs = vec![ + db.cf_handle(&self.columns[0]).unwrap(), + db.cf_handle(&self.columns[1]).unwrap(), + db.cf_handle(&self.columns[2]).unwrap(), + ]; - let mut matching_keys: Vec = Vec::new(); + let mut matching_keys: Vec = Vec::new(); // Use RocksDB's iterator for efficient scanning let iter = db.iterator_cf(cf, IteratorMode::Start); @@ -238,7 +352,7 @@ impl ResultDatabase { if value_str.contains(substring) { // Convert key to string and add to results if let Ok(key_str) = std::str::from_utf8(&key_bytes) { - if let Some(row) = decode_row_binary(key_str, &value_bytes) { + if let Some(row) = self.fetch_row(&db, key_str, &cfs) { matching_keys.push(row); } } @@ -248,76 +362,23 @@ impl ResultDatabase { Ok(matching_keys) } -} -// Fast minimal escaping for key values -#[inline] -fn fast_escape(s: &str) -> String { - // Only escape the colon character which is our separator - s.replace(":", "\\:") -} - -// Fast unescaping for key values -#[inline] - -// Fast direct row fetch by ID -fn fetch_row(db: &DB, cf_default: &ColumnFamily, row_id: &str) -> Option { - match db.get_cf(cf_default, row_id.as_bytes()) { - Ok(Some(value)) => decode_row_binary(row_id, &value), - _ => None, - } -} -// Binary decoding of row data -fn decode_row_binary(key: &str, data: &[u8]) -> Option { - if data.len() < 8 { - return None; - } - - let mut pos = 0; - - let mut values_count_bytes = [0u8; 4]; - values_count_bytes.copy_from_slice(&data[pos..pos + 4]); - let values_count = u32::from_le_bytes(values_count_bytes) as usize; - pos += 4; - - // Read values - let mut values = Vec::with_capacity(values_count); - for _ in 0..values_count { - if pos + 4 > data.len() { - return None; + fn fetch_row(&self, db: &DB, row_id: &str, cfs: &Vec<&ColumnFamily>) -> Option { + match db.get_cf(&cfs[0], row_id.as_bytes()) { + Ok(Some(_)) => Some(DatabaseResult { + id: row_id.to_string(), + ports: split_nums(&self.row_to_string(db, row_id, &cfs[1]), ","), + services: self.row_to_string(db, row_id, &cfs[2]), + }), + _ => None, } + } - let mut value_len_bytes = [0u8; 4]; - value_len_bytes.copy_from_slice(&data[pos..pos + 4]); - let value_len = u32::from_le_bytes(value_len_bytes) as usize; - pos += 4; - - if pos + value_len > data.len() { - return None; + fn row_to_string(&self, db: &DB, row_id: &str, cf: &ColumnFamily) -> String { + if let Ok(Some(data)) = &db.get_cf(cf, row_id) { + String::from_utf8_lossy(data).to_string() + } else { + String::new() } - - let value = String::from_utf8_lossy(&data[pos..pos + value_len]).to_string(); - values.push(value); - pos += value_len; - } - - Some(StringRow { - id: key.to_string(), - ports: split_nums(values[0].as_str(), ","), - }) -} - -// Binary encoding of row data for maximum performance -fn encode_row_binary(buf: &mut Vec, row: &StringRow) { - let values = vec![row.ports_to_string()]; - - // Write number of values - buf.extend_from_slice(&(values.len() as u32).to_le_bytes()); - - // Write each value - for value in vec![row.ports_to_string()] { - let value_bytes = value.as_bytes(); - buf.extend_from_slice(&(value_bytes.len() as u32).to_le_bytes()); - buf.extend_from_slice(value_bytes); } } diff --git a/src/lib.rs b/src/lib.rs index cd994da..4ac70c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,4 @@ pub mod database; pub mod online_scan; pub mod parse_ip_range; pub mod port_scan; +pub mod service_scan; diff --git a/src/main.rs b/src/main.rs index a6d15e5..a2eb05d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ -use std::{env, net::IpAddr, time::Duration}; +use std::{cmp::min, env, net::IpAddr, time::Duration}; use parse_ip_range::parse_ip_targets; -use untitled::{database::ResultDatabase, online_scan, parse_ip_range, port_scan::tcp_scan}; +use untitled::{ + database::ResultDatabase, online_scan, parse_ip_range, port_scan::tcp_scan, + service_scan::service_scan::scan_services, +}; const BATCH_SIZE: usize = 4096; @@ -47,6 +50,74 @@ fn main() -> Result<(), Box> { Ok(()) } + +const PORTS: [i32; 1000] = [ + 25565, 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 37, 42, 43, 49, 53, + 70, 79, 80, 81, 82, 83, 84, 85, 88, 89, 90, 99, 100, 106, 109, 110, 111, 113, 119, 125, 135, + 139, 143, 144, 146, 161, 163, 179, 199, 211, 212, 222, 254, 255, 256, 259, 264, 280, 301, 306, + 311, 340, 366, 389, 406, 407, 416, 417, 425, 427, 443, 444, 445, 458, 464, 465, 481, 497, 500, + 512, 513, 514, 515, 524, 541, 543, 544, 545, 548, 554, 555, 563, 587, 593, 616, 617, 625, 631, + 636, 646, 648, 666, 667, 668, 683, 687, 691, 700, 705, 711, 714, 720, 722, 726, 749, 765, 777, + 783, 787, 800, 801, 808, 843, 873, 880, 888, 898, 900, 901, 902, 903, 911, 912, 981, 987, 990, + 992, 993, 995, 999, 1000, 1001, 1002, 1007, 1009, 1010, 1011, 1021, 1022, 1023, 1024, 1025, + 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, + 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, + 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, + 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, + 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1102, 1104, 1105, 1106, 1107, + 1108, 1110, 1111, 1112, 1113, 1114, 1117, 1119, 1121, 1122, 1123, 1124, 1126, 1130, 1131, 1132, + 1137, 1138, 1141, 1145, 1147, 1148, 1149, 1151, 1152, 1154, 1163, 1164, 1165, 1166, 1169, 1174, + 1175, 1183, 1185, 1186, 1187, 1192, 1198, 1199, 1201, 1213, 1216, 1217, 1218, 1233, 1234, 1236, + 1244, 1247, 1248, 1259, 1271, 1272, 1277, 1287, 1296, 1300, 1301, 1309, 1310, 1311, 1322, 1328, + 1334, 1352, 1417, 1433, 1434, 1443, 1455, 1461, 1494, 1500, 1501, 1503, 1521, 1524, 1533, 1556, + 1580, 1583, 1594, 1600, 1641, 1658, 1666, 1687, 1688, 1700, 1717, 1718, 1719, 1720, 1721, 1723, + 1755, 1761, 1782, 1783, 1801, 1805, 1812, 1839, 1840, 1862, 1863, 1864, 1875, 1900, 1914, 1935, + 1947, 1971, 1972, 1974, 1984, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, + 2009, 2010, 2013, 2020, 2021, 2022, 2030, 2033, 2034, 2035, 2038, 2040, 2041, 2042, 2043, 2045, + 2046, 2047, 2048, 2049, 2065, 2068, 2099, 2100, 2103, 2105, 2106, 2107, 2111, 2119, 2121, 2126, + 2135, 2144, 2160, 2161, 2170, 2179, 2190, 2191, 2196, 2200, 2222, 2251, 2260, 2288, 2301, 2323, + 2366, 2381, 2382, 2383, 2393, 2394, 2399, 2401, 2492, 2500, 2522, 2525, 2557, 2601, 2602, 2604, + 2605, 2607, 2608, 2638, 2701, 2702, 2710, 2717, 2718, 2725, 2800, 2809, 2811, 2869, 2875, 2909, + 2910, 2920, 2967, 2968, 2998, 3000, 3001, 3003, 3005, 3006, 3007, 3011, 3013, 3017, 3030, 3031, + 3052, 3071, 3077, 3128, 3168, 3211, 3221, 3260, 3261, 3268, 3269, 3283, 3300, 3301, 3306, 3322, + 3323, 3324, 3325, 3333, 3351, 3367, 3369, 3370, 3371, 3372, 3389, 3390, 3404, 3476, 3493, 3517, + 3527, 3546, 3551, 3580, 3659, 3689, 3690, 3703, 3737, 3766, 3784, 3800, 3801, 3809, 3814, 3826, + 3827, 3828, 3851, 3869, 3871, 3878, 3880, 3889, 3905, 3914, 3918, 3920, 3945, 3971, 3986, 3995, + 3998, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4045, 4111, 4125, 4126, 4129, 4224, 4242, 4279, + 4321, 4343, 4443, 4444, 4445, 4446, 4449, 4550, 4567, 4662, 4848, 4899, 4900, 4998, 5000, 5001, + 5002, 5003, 5004, 5009, 5030, 5033, 5050, 5051, 5054, 5060, 5061, 5080, 5087, 5100, 5101, 5102, + 5120, 5190, 5200, 5214, 5221, 5222, 5225, 5226, 5269, 5280, 5298, 5357, 5405, 5414, 5431, 5432, + 5440, 5500, 5510, 5544, 5550, 5555, 5560, 5566, 5631, 5633, 5666, 5678, 5679, 5718, 5730, 5800, + 5801, 5802, 5810, 5811, 5815, 5822, 5825, 5850, 5859, 5862, 5877, 5900, 5901, 5902, 5903, 5904, + 5906, 5907, 5910, 5911, 5915, 5922, 5925, 5950, 5952, 5959, 5960, 5961, 5962, 5963, 5987, 5988, + 5989, 5998, 5999, 6000, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 6009, 6025, 6059, 6100, 6101, + 6106, 6112, 6123, 6129, 6156, 6346, 6389, 6502, 6510, 6543, 6547, 6565, 6566, 6567, 6580, 6646, + 6666, 6667, 6668, 6669, 6689, 6692, 6699, 6779, 6788, 6789, 6792, 6839, 6881, 6901, 6969, 7000, + 7001, 7002, 7004, 7007, 7019, 7025, 7070, 7100, 7103, 7106, 7200, 7201, 7402, 7435, 7443, 7496, + 7512, 7625, 7627, 7676, 7741, 7777, 7778, 7800, 7911, 7920, 7921, 7937, 7938, 7999, 8000, 8001, + 8002, 8007, 8008, 8009, 8010, 8011, 8021, 8022, 8031, 8042, 8045, 8080, 8081, 8082, 8083, 8084, + 8085, 8086, 8087, 8088, 8089, 8090, 8093, 8099, 8100, 8180, 8181, 8192, 8193, 8194, 8200, 8222, + 8254, 8290, 8291, 8292, 8300, 8333, 8383, 8400, 8402, 8443, 8500, 8600, 8649, 8651, 8652, 8654, + 8701, 8800, 8873, 8888, 8899, 8994, 9000, 9001, 9002, 9003, 9009, 9010, 9011, 9040, 9050, 9071, + 9080, 9081, 9090, 9091, 9099, 9100, 9101, 9102, 9103, 9110, 9111, 9200, 9207, 9220, 9290, 9415, + 9418, 9485, 9500, 9502, 9503, 9535, 9575, 9593, 9594, 9595, 9618, 9666, 9876, 9877, 9878, 9898, + 9900, 9917, 9929, 9943, 9944, 9968, 9998, 9999, 10000, 10001, 10002, 10003, 10004, 10009, + 10010, 10012, 10024, 10025, 10082, 10180, 10215, 10243, 10566, 10616, 10617, 10621, 10626, + 10628, 10629, 10778, 11110, 11111, 11967, 12000, 12174, 12265, 12345, 13456, 13722, 13782, + 13783, 14000, 14238, 14441, 14442, 15000, 15002, 15003, 15004, 15660, 15742, 16000, 16001, + 16012, 16016, 16018, 16080, 16113, 16992, 16993, 17877, 17988, 18040, 18101, 18988, 19101, + 19283, 19315, 19350, 19780, 19801, 19842, 20000, 20005, 20031, 20221, 20222, 20828, 21571, + 22939, 23502, 24444, 24800, 25734, 25735, 26214, 27000, 27352, 27353, 27355, 27356, 27715, + 28201, 30000, 30718, 30951, 31038, 31337, 32768, 32769, 32770, 32771, 32772, 32773, 32774, + 32775, 32776, 32777, 32778, 32779, 32780, 32781, 32782, 32783, 32784, 32785, 33354, 33899, + 34571, 34572, 34573, 35500, 38292, 40193, 40911, 41511, 42510, 44176, 44442, 44443, 44501, + 45100, 48080, 49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, 49161, 49163, + 49165, 49167, 49175, 49176, 49400, 49999, 50000, 50001, 50002, 50003, 50006, 50300, 50389, + 50500, 50636, 50800, 51103, 51493, 52673, 52822, 52848, 52869, 54045, 54328, 55055, 55056, + 55555, 55600, 56737, 56738, 57294, 57797, 58080, 60020, 60443, 61532, 61900, 62078, 63331, + 64623, 64680, 65000, 65129, 65389, +]; + fn scan( database: ResultDatabase, search_type: String, @@ -83,91 +154,41 @@ fn scan( ); let _ = database.add_ping_results(&up_hosts); - let tcp_ports = vec![ - 25565, 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 37, - 42, 43, 49, 53, 70, 79, 80, 81, 82, 83, 84, 85, 88, 89, 90, 99, 100, 106, 109, - 110, 111, 113, 119, 125, 135, 139, 143, 144, 146, 161, 163, 179, 199, 211, 212, - 222, 254, 255, 256, 259, 264, 280, 301, 306, 311, 340, 366, 389, 406, 407, 416, - 417, 425, 427, 443, 444, 445, 458, 464, 465, 481, 497, 500, 512, 513, 514, 515, - 524, 541, 543, 544, 545, 548, 554, 555, 563, 587, 593, 616, 617, 625, 631, 636, - 646, 648, 666, 667, 668, 683, 687, 691, 700, 705, 711, 714, 720, 722, 726, 749, - 765, 777, 783, 787, 800, 801, 808, 843, 873, 880, 888, 898, 900, 901, 902, 903, - 911, 912, 981, 987, 990, 992, 993, 995, 999, 1000, 1001, 1002, 1007, 1009, - 1010, 1011, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, - 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, - 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, - 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, - 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, - 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, - 1097, 1098, 1099, 1100, 1102, 1104, 1105, 1106, 1107, 1108, 1110, 1111, 1112, - 1113, 1114, 1117, 1119, 1121, 1122, 1123, 1124, 1126, 1130, 1131, 1132, 1137, - 1138, 1141, 1145, 1147, 1148, 1149, 1151, 1152, 1154, 1163, 1164, 1165, 1166, - 1169, 1174, 1175, 1183, 1185, 1186, 1187, 1192, 1198, 1199, 1201, 1213, 1216, - 1217, 1218, 1233, 1234, 1236, 1244, 1247, 1248, 1259, 1271, 1272, 1277, 1287, - 1296, 1300, 1301, 1309, 1310, 1311, 1322, 1328, 1334, 1352, 1417, 1433, 1434, - 1443, 1455, 1461, 1494, 1500, 1501, 1503, 1521, 1524, 1533, 1556, 1580, 1583, - 1594, 1600, 1641, 1658, 1666, 1687, 1688, 1700, 1717, 1718, 1719, 1720, 1721, - 1723, 1755, 1761, 1782, 1783, 1801, 1805, 1812, 1839, 1840, 1862, 1863, 1864, - 1875, 1900, 1914, 1935, 1947, 1971, 1972, 1974, 1984, 1998, 1999, 2000, 2001, - 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2020, 2021, 2022, - 2030, 2033, 2034, 2035, 2038, 2040, 2041, 2042, 2043, 2045, 2046, 2047, 2048, - 2049, 2065, 2068, 2099, 2100, 2103, 2105, 2106, 2107, 2111, 2119, 2121, 2126, - 2135, 2144, 2160, 2161, 2170, 2179, 2190, 2191, 2196, 2200, 2222, 2251, 2260, - 2288, 2301, 2323, 2366, 2381, 2382, 2383, 2393, 2394, 2399, 2401, 2492, 2500, - 2522, 2525, 2557, 2601, 2602, 2604, 2605, 2607, 2608, 2638, 2701, 2702, 2710, - 2717, 2718, 2725, 2800, 2809, 2811, 2869, 2875, 2909, 2910, 2920, 2967, 2968, - 2998, 3000, 3001, 3003, 3005, 3006, 3007, 3011, 3013, 3017, 3030, 3031, 3052, - 3071, 3077, 3128, 3168, 3211, 3221, 3260, 3261, 3268, 3269, 3283, 3300, 3301, - 3306, 3322, 3323, 3324, 3325, 3333, 3351, 3367, 3369, 3370, 3371, 3372, 3389, - 3390, 3404, 3476, 3493, 3517, 3527, 3546, 3551, 3580, 3659, 3689, 3690, 3703, - 3737, 3766, 3784, 3800, 3801, 3809, 3814, 3826, 3827, 3828, 3851, 3869, 3871, - 3878, 3880, 3889, 3905, 3914, 3918, 3920, 3945, 3971, 3986, 3995, 3998, 4000, - 4001, 4002, 4003, 4004, 4005, 4006, 4045, 4111, 4125, 4126, 4129, 4224, 4242, - 4279, 4321, 4343, 4443, 4444, 4445, 4446, 4449, 4550, 4567, 4662, 4848, 4899, - 4900, 4998, 5000, 5001, 5002, 5003, 5004, 5009, 5030, 5033, 5050, 5051, 5054, - 5060, 5061, 5080, 5087, 5100, 5101, 5102, 5120, 5190, 5200, 5214, 5221, 5222, - 5225, 5226, 5269, 5280, 5298, 5357, 5405, 5414, 5431, 5432, 5440, 5500, 5510, - 5544, 5550, 5555, 5560, 5566, 5631, 5633, 5666, 5678, 5679, 5718, 5730, 5800, - 5801, 5802, 5810, 5811, 5815, 5822, 5825, 5850, 5859, 5862, 5877, 5900, 5901, - 5902, 5903, 5904, 5906, 5907, 5910, 5911, 5915, 5922, 5925, 5950, 5952, 5959, - 5960, 5961, 5962, 5963, 5987, 5988, 5989, 5998, 5999, 6000, 6001, 6002, 6003, - 6004, 6005, 6006, 6007, 6009, 6025, 6059, 6100, 6101, 6106, 6112, 6123, 6129, - 6156, 6346, 6389, 6502, 6510, 6543, 6547, 6565, 6566, 6567, 6580, 6646, 6666, - 6667, 6668, 6669, 6689, 6692, 6699, 6779, 6788, 6789, 6792, 6839, 6881, 6901, - 6969, 7000, 7001, 7002, 7004, 7007, 7019, 7025, 7070, 7100, 7103, 7106, 7200, - 7201, 7402, 7435, 7443, 7496, 7512, 7625, 7627, 7676, 7741, 7777, 7778, 7800, - 7911, 7920, 7921, 7937, 7938, 7999, 8000, 8001, 8002, 8007, 8008, 8009, 8010, - 8011, 8021, 8022, 8031, 8042, 8045, 8080, 8081, 8082, 8083, 8084, 8085, 8086, - 8087, 8088, 8089, 8090, 8093, 8099, 8100, 8180, 8181, 8192, 8193, 8194, 8200, - 8222, 8254, 8290, 8291, 8292, 8300, 8333, 8383, 8400, 8402, 8443, 8500, 8600, - 8649, 8651, 8652, 8654, 8701, 8800, 8873, 8888, 8899, 8994, 9000, 9001, 9002, - 9003, 9009, 9010, 9011, 9040, 9050, 9071, 9080, 9081, 9090, 9091, 9099, 9100, - 9101, 9102, 9103, 9110, 9111, 9200, 9207, 9220, 9290, 9415, 9418, 9485, 9500, - 9502, 9503, 9535, 9575, 9593, 9594, 9595, 9618, 9666, 9876, 9877, 9878, 9898, - 9900, 9917, 9929, 9943, 9944, 9968, 9998, 9999, 10000, 10001, 10002, 10003, - 10004, 10009, 10010, 10012, 10024, 10025, 10082, 10180, 10215, 10243, 10566, - 10616, 10617, 10621, 10626, 10628, 10629, 10778, 11110, 11111, 11967, 12000, - 12174, 12265, 12345, 13456, 13722, 13782, 13783, 14000, 14238, 14441, 14442, - 15000, 15002, 15003, 15004, 15660, 15742, 16000, 16001, 16012, 16016, 16018, - 16080, 16113, 16992, 16993, 17877, 17988, 18040, 18101, 18988, 19101, 19283, - 19315, 19350, 19780, 19801, 19842, 20000, 20005, 20031, 20221, 20222, 20828, - 21571, 22939, 23502, 24444, 24800, 25734, 25735, 26214, 27000, 27352, 27353, - 27355, 27356, 27715, 28201, 30000, 30718, 30951, 31038, 31337, 32768, 32769, - 32770, 32771, 32772, 32773, 32774, 32775, 32776, 32777, 32778, 32779, 32780, - 32781, 32782, 32783, 32784, 32785, 33354, 33899, 34571, 34572, 34573, 35500, - 38292, 40193, 40911, 41511, 42510, 44176, 44442, 44443, 44501, 45100, 48080, - 49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, 49161, 49163, - 49165, 49167, 49175, 49176, 49400, 49999, 50000, 50001, 50002, 50003, 50006, - 50300, 50389, 50500, 50636, 50800, 51103, 51493, 52673, 52822, 52848, 52869, - 54045, 54328, 55055, 55056, 55555, 55600, 56737, 56738, 57294, 57797, 58080, - 60020, 60443, 61532, 61900, 62078, 63331, 64623, 64680, 65000, 65129, 65389, - ]; - - let tcp_results = tcp_scan::tcp_scan(up_hosts, tcp_ports, Duration::from_secs(3)); + let tcp_results = + tcp_scan::tcp_scan(up_hosts, PORTS.to_vec(), Duration::from_secs(3)); println!("Saving Data..."); let _ = database.add_tcp_results(&tcp_results); } } + "service" => { + let chunks = hosts.chunks(BATCH_SIZE); + let num_chunks = chunks.len(); + for (i, hosts) in chunks.enumerate() { + let hosts = hosts.to_vec(); + let length = hosts.len(); + + println!("Scanning chunk {}/{} ({} hosts)", i + 1, num_chunks, length); + + let up_hosts: Vec = online_scan::ping_scanner::ping_scan(hosts).unwrap(); + let up_len = up_hosts.len(); + println!( + "Finished Pinging! {} Scanned, {} Up", + length, + up_hosts.len() + ); + let _ = database.add_ping_results(&up_hosts); + + let tcp_results = + tcp_scan::tcp_scan(up_hosts, PORTS.to_vec(), Duration::from_secs(3)); + println!("Finished port scan"); + let _ = database.add_tcp_results(&tcp_results); + + let service_results = + scan_services(tcp_results, min(500, up_len), Duration::from_secs(3)); + println!("Finished service scan"); + let _ = database.add_service_results(&service_results); + } + } _ => { println!("Invalid search type!"); } @@ -194,6 +215,14 @@ fn search(database: ResultDatabase, search_type: String, arg: String) { println!("{}", row.to_string()); } } + + "service" => { + let rows = database.get_rows_by_service(&arg); + + for row in rows { + println!("{}", row.to_string()); + } + } _ => { println!("Invalid search type!"); } @@ -207,18 +236,18 @@ fn print_help(arg: Option<&str>) { None => { "rust-scan help menu Commands: - scan - scan a block of addresses and check for online using icmp echo - search - Search database - help (command) - Print help" - } - Some("pingscan") => { - "pingscan -scan a block of addresses and check for online using icmp echo -Usage: pingscan 10.42.0.1,12.34.0.0-12.34.56.78,127.0.0.0/8" + scan (arguments) - scan a block of addresses and check for online using icmp echo + search - Search database + help (command) - Print help" } + // Some("scan") => { + // "pingscan + // scan a block of addresses and check for online using icmp echo + // Usage: pingscan 10.42.0.1,12.34.0.0-12.34.56.78,127.0.0.0/8" + // } Some(_) => { print_help(None); - "" + "Invalid Command!" } } ); diff --git a/src/online_scan/online_scan.rs b/src/online_scan/online_scan.rs index 9e5adbb..2f1cb3c 100644 --- a/src/online_scan/online_scan.rs +++ b/src/online_scan/online_scan.rs @@ -2,7 +2,7 @@ use std::{net::IpAddr, time::Duration}; use serde::{Deserialize, Serialize}; -use crate::database::StringRow; +use crate::database::DatabaseResult; // Structure to hold ping results #[derive(Debug, Serialize, Deserialize, Clone)] @@ -21,10 +21,11 @@ impl PingResult { } } - pub fn to_string_row(&self) -> StringRow { - StringRow { + pub fn to_database(&self) -> DatabaseResult { + DatabaseResult { id: self.host.to_string(), ports: vec![], + services: String::new(), } } } diff --git a/src/port_scan/port_scan.rs b/src/port_scan/port_scan.rs index a6fd29d..ef7dacb 100644 --- a/src/port_scan/port_scan.rs +++ b/src/port_scan/port_scan.rs @@ -1,26 +1,26 @@ use std::net::IpAddr; -use crate::database::StringRow; +use crate::database::DatabaseResult; #[derive(Debug, Clone)] -pub struct ScanResult { +pub struct PortScanResult { pub ip: IpAddr, pub open_ports: Vec, - // pub data: HashMap>, } -impl ScanResult { +impl PortScanResult { pub fn new(ip: IpAddr) -> Self { - ScanResult { + PortScanResult { ip, open_ports: Vec::new(), // data: HashMap::new(), } } - pub fn to_string_row(&self) -> StringRow { - StringRow { + pub fn to_database(&self) -> DatabaseResult { + DatabaseResult { id: self.ip.to_string(), ports: (*self.open_ports).to_vec(), + services: String::new(), } } } diff --git a/src/port_scan/tcp_scan.rs b/src/port_scan/tcp_scan.rs index 90f49e8..bcbebfb 100644 --- a/src/port_scan/tcp_scan.rs +++ b/src/port_scan/tcp_scan.rs @@ -6,28 +6,23 @@ use std::thread; use std::time::Duration; use indicatif::ProgressBar; -use pnet::datalink::linux::interfaces; use pnet::datalink::{self}; use pnet::packet::ip::IpNextHeaderProtocols; use pnet::packet::tcp::{MutableTcpPacket, TcpFlags, TcpPacket}; -use pnet::packet::{tcp, Packet}; +use pnet::packet::{Packet, tcp}; use pnet::transport::{self, TransportChannelType, TransportSender}; use rand::random_range; -use super::port_scan::ScanResult; +use super::port_scan::PortScanResult; fn std_to_pnet_ipv4(previous: &IpAddr) -> Ipv4Addr { Ipv4Addr::from_str(previous.to_string().as_str()).unwrap() } // Main scanning function -pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec { - // Find network interface - let interfaces = datalink::interfaces(); - - println!("{}", interfaces.len()); - - let interface = interfaces +pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec { + // Search for VPN connection and fall back to regular + let interface = datalink::interfaces() .into_iter() .find(|iface| { iface.is_up() @@ -37,6 +32,14 @@ pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec && iface.is_running() && iface.is_point_to_point() }) + .or(datalink::interfaces().into_iter().find(|iface| { + iface.is_up() + && !iface.is_loopback() + && !iface.ips.is_empty() + && !iface.is_dormant() + && iface.is_running() + && !iface.is_point_to_point() + })) .expect("No valid network interface found"); // Create transport channel for sending and receiving @@ -97,19 +100,21 @@ pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec let pb = ProgressBar::new((targets.len() * ports.len()) as u64); - println!("{:?}", interface.ips); + // println!("{:?}", interface.ips); - let source_ip = interface - .ips - .iter() - .find(|ip| ip.is_ipv4()) - .expect("No IPv4 address found") - .ip(); + let source_ip = std_to_pnet_ipv4( + &interface + .ips + .iter() + .find(|ip| ip.is_ipv4()) + .expect("No IPv4 address found") + .ip(), + ); // let source_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); // let source_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 70, 4)); - println!("Using IP: {}", source_ip.to_string()); + // println!("Using IP: {}", source_ip.to_string()); for target in &targets { for port in &ports { @@ -134,7 +139,11 @@ pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec // Calculate checksum let checksum = tcp::ipv4_checksum( &tcp_header.to_immutable(), - &std_to_pnet_ipv4(&source_ip), + if !target.is_loopback() { + &source_ip + } else { + &Ipv4Addr::LOCALHOST + }, &std_to_pnet_ipv4(&target), ); tcp_header.set_checksum(checksum); @@ -158,7 +167,7 @@ pub fn tcp_scan(targets: Vec, ports: Vec, timeout: Duration) -> Vec let mut open_ports = results_map.get(ip).cloned().unwrap_or_default(); open_ports.sort(); open_ports.dedup(); - ScanResult { + PortScanResult { ip: *ip, open_ports, } diff --git a/src/service_scan/mod.rs b/src/service_scan/mod.rs new file mode 100644 index 0000000..c1da55d --- /dev/null +++ b/src/service_scan/mod.rs @@ -0,0 +1,4 @@ +pub mod service_scan; +pub mod services; +pub mod tcp_http; +pub mod tcp_https; diff --git a/src/service_scan/service_scan.rs b/src/service_scan/service_scan.rs new file mode 100644 index 0000000..42ddd86 --- /dev/null +++ b/src/service_scan/service_scan.rs @@ -0,0 +1,241 @@ +use std::{ + collections::HashMap, + io::{Read, Write}, + net::{IpAddr, SocketAddr, TcpStream}, + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +use indicatif::ProgressBar; + +use crate::{ + database::DatabaseResult, port_scan::port_scan::PortScanResult, service_scan::tcp_http, +}; + +use super::{services::SERVICE_PATTERNS, tcp_https}; + +#[derive(Debug, Clone)] +pub struct ServiceScanResult { + pub ip: IpAddr, + pub open_ports: Vec, + pub services: HashMap, +} + +impl ServiceScanResult { + fn new(ip: IpAddr) -> Self { + ServiceScanResult { + ip, + open_ports: Vec::new(), + services: HashMap::new(), + } + } + pub fn to_database(&self) -> DatabaseResult { + DatabaseResult { + id: self.ip.to_string(), + ports: self.open_ports.clone(), + services: serde_json::to_string(&self.services).unwrap_or(String::new()), + } + } +} + +pub fn identify(ip: IpAddr, port: &i32, timeout: Duration) -> (String, String) { + let e = || basic_identify(ip, port, timeout).unwrap_or(("tcp".to_string(), "".to_string())); + + match port { + 80 | 8080 | 8081 | 8082 | 8083 | 8084 | 8085 | 8086 | 8087 | 8088 | 8089 => { + tuple_or_none("http", tcp_http::scan(ip, port, timeout)).unwrap_or(e()) + } + 443 | 8443 => tuple_or_none("https", tcp_https::scan(ip, port, timeout)).unwrap_or(e()), + _ => e(), + } +} + +fn tuple_or_none( + tag: &str, + data: Result>, +) -> Option<(String, String)> { + if let Ok(data) = data { + Some((tag.to_string(), data)) + } else { + None + } +} + +pub fn scan_services( + port_scan_results: Vec, + num_threads: usize, + timeout: Duration, +) -> Vec { + let mut host_port_count: u64 = 0; + let results: Arc>> = Arc::new(Mutex::new( + port_scan_results + .iter() + .map(|result| { + host_port_count += result.open_ports.len() as u64; + ServiceScanResult::new(result.ip) + }) + .collect(), + )); + + let mut handles = Vec::new(); + let pb = Arc::new(ProgressBar::new(host_port_count)); + + // Create a thread for each chunk of IPs + let chunks = split_ips_into_chunks(port_scan_results, num_threads); + for chunk in chunks { + let chunk_hosts = chunk.clone(); + let thread_results = Arc::clone(&results); + let thread_timeout = timeout; + let thread_pb = Arc::clone(&pb); + handles.push(thread::spawn(move || { + for host in chunk_hosts { + let ports = &host.open_ports; + for port in ports { + // Try to identify the service on the port + let (service_name, banner) = identify(host.ip, port, thread_timeout); + + let mut results_guard = thread_results.lock().unwrap(); + if let Some(result) = results_guard.iter_mut().find(|r| r.ip == host.ip) { + result.open_ports.push(*port); + result.services.insert(*port, (service_name, banner)); + } + + thread_pb.inc(1); + } + } + })); + } + + for handle in handles { + handle.join().unwrap(); + } + + pb.clone().finish_and_clear(); + + Arc::try_unwrap(results) + .expect("Arc still has multiple owners") + .into_inner() + .expect("Mutex poisoned") + .into_iter() + .map(|a| { + println!("{:?}", a); + a + }) + .collect() +} + +// Helper function to split the IPs into roughly equal chunks for threading +fn split_ips_into_chunks(ips: Vec, num_chunks: usize) -> Vec> { + let chunk_size = (ips.len() + num_chunks - 1) / num_chunks; + let mut chunks = Vec::new(); + + for chunk_idx in 0..num_chunks { + let start = chunk_idx * chunk_size; + if start >= ips.len() { + break; + } + let end = (start + chunk_size).min(ips.len()); + chunks.push(ips[start..end].to_vec()); + } + + chunks +} + +// Connect to an IP:port and send a probe +fn try_connect(ip: IpAddr, port: &i32, timeout: Duration, probe: &[u8]) -> Option> { + let addr = SocketAddr::new(ip, *port as u16); + + match TcpStream::connect_timeout(&addr, timeout) { + Ok(mut stream) => { + // Set read/write timeouts + let _ = stream.set_read_timeout(Some(timeout)); + let _ = stream.set_write_timeout(Some(timeout)); + + // Send the probe if it's not empty + if !probe.is_empty() { + if stream.write(probe).is_err() { + return None; + } + } + + // Read the response + let mut buffer = [0; 4096]; // Larger buffer for service banners + let mut response = Vec::new(); + + // Try to read multiple times to get a complete banner + for _ in 0..3 { + match stream.read(&mut buffer) { + Ok(0) => break, // End of stream + Ok(bytes_read) => { + response.extend_from_slice(&buffer[0..bytes_read]); + if bytes_read < buffer.len() { + break; // Likely got all data if we read less than buffer size + } + } + Err(_) => break, // Error reading + } + + // Small delay between reads + thread::sleep(Duration::from_millis(50)); + } + + Some(response) + } + Err(_) => None, // Connection failed + } +} + +fn basic_identify(ip: IpAddr, port: &i32, timeout: Duration) -> Option<(String, String)> { + // Try a simple connection with no probe as last resort + if let Some(response) = try_connect(ip, port, timeout, b"\x00\n") { + if !response.is_empty() { + if let Some(service_name) = identify_service_from_response(&response) { + return Some(( + service_name.to_string(), + String::from_utf8_lossy(response.as_slice()).to_string(), + )); + } + } + + // Port is open but service couldn't be identified + return Some(("tcp".to_string(), "".to_string())); + } + + None +} + +fn identify_service_from_response(response: &[u8]) -> Option<&str> { + // Convert response to string if possible + if let Ok(response_str) = std::str::from_utf8(response) { + // Try to match against known patterns + for (pattern, service_name) in SERVICE_PATTERNS.iter() { + if pattern.is_match(response_str) { + return Some(service_name); + } + } + } + + // For binary responses, check for pattern matches + // Check for SSL/TLS + if response.len() >= 3 && response[0] == 0x16 && (response[1] == 0x03 || response[1] == 0x02) { + return Some("ssl/tls"); + } + + // Check for MySQL protocol + if response.len() >= 5 && response[0] == 0x4a && response[1] == 0x00 { + return Some("mysql"); + } + + // Check for MongoDB wire protocol + if response.len() >= 4 + && response[0] == 0x02 + && response[1] == 0x00 + && response[2] == 0x00 + && response[3] == 0x00 + { + return Some("mongodb"); + } + + None +} diff --git a/src/service_scan/services.rs b/src/service_scan/services.rs new file mode 100644 index 0000000..43ae247 --- /dev/null +++ b/src/service_scan/services.rs @@ -0,0 +1,664 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + pub static ref SERVICE_PATTERNS: Vec<(Regex, &'static str)> = vec![ + // HTTP and Web Services + (Regex::new(r"^HTTP/\d").unwrap(), "http"), + (Regex::new(r"Server:").unwrap(), "http"), + (Regex::new(r"").unwrap(), "http"), + (Regex::new(r".*").unwrap(), "http"), + (Regex::new(r"^HTTP/\d+\.\d+ 4\d\d").unwrap(), "http"), + (Regex::new(r"^HTTP/\d+\.\d+ 5\d\d").unwrap(), "http"), + (Regex::new(r"404 Not Found").unwrap(), "http"), + (Regex::new(r"301 Moved Permanently").unwrap(), "http"), + (Regex::new(r"Content-Type: text/html").unwrap(), "http"), + (Regex::new(r"WebSocket").unwrap(), "websocket"), + (Regex::new(r"^WebSphere Application Server").unwrap(), "websphere"), + (Regex::new(r"Apache Tomcat").unwrap(), "tomcat"), + (Regex::new(r"JBoss").unwrap(), "jboss"), + (Regex::new(r"nginx").unwrap(), "nginx"), + (Regex::new(r"Ruby on Rails").unwrap(), "rails"), + (Regex::new(r"Django").unwrap(), "django"), + (Regex::new(r"Express").unwrap(), "express"), + (Regex::new(r"Microsoft-IIS").unwrap(), "iis"), + (Regex::new(r"Litespeed").unwrap(), "litespeed"), + (Regex::new(r"lighttpd").unwrap(), "lighttpd"), + (Regex::new(r"^Jetty").unwrap(), "jetty"), + (Regex::new(r"^GlassFish Server").unwrap(), "glassfish"), + (Regex::new(r"^Oracle-Application-Server").unwrap(), "oracle-as"), + (Regex::new(r"WAF/\d").unwrap(), "waf"), + (Regex::new(r"Resin/\d").unwrap(), "resin"), + + // SSH + (Regex::new(r"^SSH-\d").unwrap(), "ssh"), + (Regex::new(r"^SSH-1\.").unwrap(), "ssh1"), + (Regex::new(r"^SSH-2\.").unwrap(), "ssh2"), + (Regex::new(r"OpenSSH").unwrap(), "openssh"), + (Regex::new(r"Dropbear").unwrap(), "dropbear-ssh"), + (Regex::new(r"libssh").unwrap(), "libssh"), + + // Email Protocols + (Regex::new(r"^220.*SMTP").unwrap(), "smtp"), + (Regex::new(r"^220.*ESMTP").unwrap(), "smtp"), + (Regex::new(r"^220.*mail").unwrap(), "smtp"), + (Regex::new(r"^220.*Email").unwrap(), "smtp"), + (Regex::new(r"^220.*Simple Mail").unwrap(), "smtp"), + (Regex::new(r"^250[ -]").unwrap(), "smtp"), + (Regex::new(r"^554 ").unwrap(), "smtp"), + (Regex::new(r"^550 ").unwrap(), "smtp"), + (Regex::new(r"^220 .*Exchange").unwrap(), "smtp-exchange"), + (Regex::new(r"^220 .*Postfix").unwrap(), "smtp-postfix"), + (Regex::new(r"^220 .*Sendmail").unwrap(), "smtp-sendmail"), + (Regex::new(r"^\+OK").unwrap(), "pop3"), + (Regex::new(r"^\* OK").unwrap(), "imap"), + (Regex::new(r"^\* OK .*IMAP").unwrap(), "imap"), + (Regex::new(r"^\* OK .*Courier-IMAP").unwrap(), "courier-imap"), + (Regex::new(r"^\* OK .*Dovecot").unwrap(), "dovecot-imap"), + (Regex::new(r"^\* OK .*UW IMAP").unwrap(), "uw-imap"), + (Regex::new(r"^\* PREAUTH").unwrap(), "imap"), + (Regex::new(r"^OK LOGIN").unwrap(), "pop3"), + (Regex::new(r"^OK CAPA").unwrap(), "pop3"), + (Regex::new(r"^\+OK Dovecot").unwrap(), "dovecot-pop3"), + (Regex::new(r"^\+OK Courier").unwrap(), "courier-pop3"), + (Regex::new(r"^501 5\.5\.4").unwrap(), "smtp"), + + // FTP + (Regex::new(r"^220.*FTP").unwrap(), "ftp"), + (Regex::new(r"^220 .*FileZilla").unwrap(), "filezilla-ftp"), + (Regex::new(r"^220 .*ProFTPD").unwrap(), "proftpd"), + (Regex::new(r"^220 .*Pure-FTPd").unwrap(), "pure-ftpd"), + (Regex::new(r"^220 .*vsFTPd").unwrap(), "vsftpd"), + (Regex::new(r"^220 .*WU-FTPD").unwrap(), "wu-ftpd"), + (Regex::new(r"^220 Welcome to Pure-FTPd").unwrap(), "pure-ftpd"), + (Regex::new(r"^220-FileZilla Server").unwrap(), "filezilla-ftp"), + (Regex::new(r"^220 Microsoft FTP").unwrap(), "microsoft-ftp"), + (Regex::new(r"^220 .*FRITZ!Box").unwrap(), "fritzbox-ftp"), + (Regex::new(r"^220 .*IIS .* FTP").unwrap(), "iis-ftp"), + (Regex::new(r"^220 .*FTP server \(GNU").unwrap(), "gnu-inetutils-ftpd"), + (Regex::new(r"^220 .*FTP server ready").unwrap(), "generic-ftp"), + (Regex::new(r"^331 ").unwrap(), "ftp"), + (Regex::new(r"^530 ").unwrap(), "ftp"), + + // Database Servers + (Regex::new(r"^S\x00\x00\x01\x55\x00\x00").unwrap(), "mysql"), + (Regex::new(r"^\x5b\x00\x00\x00").unwrap(), "postgres"), + (Regex::new(r"^220 PostgreSQL").unwrap(), "postgres"), + (Regex::new(r"PostgreSQL SCRAM-SHA-256").unwrap(), "postgres"), + (Regex::new(r"^@REDICULOUS").unwrap(), "redis"), + (Regex::new(r"^@REDISJSON").unwrap(), "redis"), + (Regex::new(r"^-ERR\sERROR").unwrap(), "redis"), + (Regex::new(r"^-ERR\s").unwrap(), "redis"), + (Regex::new(r"^-DENIED\s").unwrap(), "redis"), + (Regex::new(r"^\\-ERR").unwrap(), "redis"), + (Regex::new(r"^\\+OK").unwrap(), "redis"), + (Regex::new(r"^[+]PONG").unwrap(), "redis"), + (Regex::new(r"^-NOAUTH\s").unwrap(), "redis"), + (Regex::new(r"^-BUSY\s").unwrap(), "redis"), + (Regex::new(r"^[$]").unwrap(), "redis"), + (Regex::new(r"^(\*)").unwrap(), "redis"), + (Regex::new(r"^redis_version").unwrap(), "redis"), + (Regex::new(r"Oracle Transparent Network Substrate Protocol").unwrap(), "oracle-tns"), + (Regex::new(r"^\x00\x00\x00\x00\x04\x00\x00\x00").unwrap(), "oracle-tns"), + (Regex::new(r"^@\(#\)sybase").unwrap(), "sybase"), + (Regex::new(r"^\x04\x01\x00").unwrap(), "sybase-ase"), + (Regex::new(r"^MongoDB").unwrap(), "mongodb"), + (Regex::new(r"^\x02\x00\x00\x00").unwrap(), "mongodb"), + // (Regex::new(r#"^{\"ok\":"#).unwrap(), "mongodb-rest"), + (Regex::new(r"^3 ").unwrap(), "mongodb-shell"), + (Regex::new(r"^MemCache").unwrap(), "memcached"), + (Regex::new(r"^VERSION ").unwrap(), "memcached"), + (Regex::new(r"^(?:ERROR|CLIENT_ERROR|SERVER_ERROR)$").unwrap(), "memcached"), + // (Regex::new(r"^\\(\\s+\(\\s+FLUSHDB").unwrap(), "db2"), + (Regex::new(r"^SQLite format 3\x00").unwrap(), "sqlite"), + (Regex::new(r"CouchDB").unwrap(), "couchdb"), + (Regex::new(r"^(?:HBase|ZooKeeper)").unwrap(), "hbase"), + (Regex::new(r"^Cassandra").unwrap(), "cassandra"), + (Regex::new(r"^\\x00\\x58\\x01\\x00\\x19\\x00\\x00\\x00\\x11\\x00\\x00\\x00\\x00").unwrap(), "cassandra"), + (Regex::new(r"^DSN=").unwrap(), "odbc"), + (Regex::new(r"^DLPX-").unwrap(), "delphix"), + (Regex::new(r"^RIAK").unwrap(), "riak"), + (Regex::new(r"^neo4j").unwrap(), "neo4j"), + (Regex::new(r"^\\x00\\x00\\x00\\x78\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00").unwrap(), "influxdb"), + + // Telnet and Terminal Servers + (Regex::new(r"^220.*telnet").unwrap(), "telnet"), + (Regex::new(r"^\xff\xfb\x01\xff\xfb\x03").unwrap(), "telnet"), + (Regex::new(r"^\xff\xfb").unwrap(), "telnet"), + // (Regex::new(r"^\\x1B\\[").unwrap(), "telnet"), + (Regex::new(r"Welcome to the Telnet Server").unwrap(), "telnet"), + (Regex::new(r"BusyBox v").unwrap(), "busybox-telnet"), + (Regex::new(r"^Login:").unwrap(), "telnet"), + (Regex::new(r"^\r\nlogin: ").unwrap(), "telnet"), + (Regex::new(r"username:").unwrap(), "telnet"), + (Regex::new(r"password:").unwrap(), "terminal"), + (Regex::new(r"You are on a Router").unwrap(), "router-terminal"), + (Regex::new(r"^\r\n\r\nRTSP/1.0").unwrap(), "rtsp"), + + // Remote Desktop and VNC + (Regex::new(r"^RFB \d").unwrap(), "vnc"), + (Regex::new(r"^RFB 003.").unwrap(), "vnc"), + (Regex::new(r"^RFB 004.").unwrap(), "vnc"), + (Regex::new(r"^\x03\x00\x00").unwrap(), "rdp"), + (Regex::new(r"^\x03\x00\x00\x0b\x06").unwrap(), "rdp"), + (Regex::new(r"^\x03\x00\x00\x13").unwrap(), "rdp"), + (Regex::new(r"^\x03\x00\x00\x03\x0e\x00\x00\x00").unwrap(), "rdp"), + (Regex::new(r"^Remote Desktop Protocol").unwrap(), "rdp"), + (Regex::new(r"Microsoft Terminal Server").unwrap(), "rdp"), + (Regex::new(r"^\x30\x64\xa0").unwrap(), "pcAnywhere"), + (Regex::new(r"^CONNECTREQUEST").unwrap(), "teamviewer"), + + // LDAP and Directory Services + (Regex::new(r"^\x30\x0c\x02\x01\x01\x61").unwrap(), "ldap"), + (Regex::new(r"^\x30\x84").unwrap(), "ldap"), + (Regex::new(r"Microsoft Active Directory LDAP").unwrap(), "active-directory"), + (Regex::new(r"^objectClass").unwrap(), "ldap"), + (Regex::new(r"OpenLDAP").unwrap(), "openldap"), + (Regex::new(r"389 Directory Server").unwrap(), "389-ds"), + (Regex::new(r"^NDS version").unwrap(), "novell-nds"), + + // Web Services and APIs + (Regex::new(r#"^\{"jsonrpc"#).unwrap(), "jsonrpc"), + (Regex::new(r#"^\{"result"#).unwrap(), "json-api"), + (Regex::new(r"^<\?xml").unwrap(), "xml-service"), + (Regex::new(r"").unwrap(), "soap"), + (Regex::new(r"graphql").unwrap(), "graphql"), + (Regex::new(r"").unwrap(), "graphql"), + (Regex::new(r"REST API").unwrap(), "rest-api"), + (Regex::new(r"Swagger").unwrap(), "swagger-api"), + (Regex::new(r"OpenAPI").unwrap(), "openapi"), + (Regex::new(r"^\\d{3} MCom_Perl").unwrap(), "perl-webservice"), + + // Message Queues and Streaming + (Regex::new(r"^AMQP").unwrap(), "amqp"), + (Regex::new(r"^AMQP\x00\x01\x00\x00").unwrap(), "amqp-0-10"), + (Regex::new(r"^AMQP\x01\x01\x00\x0A").unwrap(), "amqp-1-0"), + (Regex::new(r"^AMQP\x00\x00\x09\x01").unwrap(), "amqp-0-9-1"), + (Regex::new(r"RabbitMQ").unwrap(), "rabbitmq"), + (Regex::new(r"Apache Kafka").unwrap(), "kafka"), + (Regex::new(r"^JMQ").unwrap(), "jms"), + (Regex::new(r"ActiveMQ").unwrap(), "activemq"), + (Regex::new(r"Apache ActiveMQ").unwrap(), "activemq"), + (Regex::new(r"MQTT").unwrap(), "mqtt"), + (Regex::new(r"^\\x10\\x").unwrap(), "mqtt"), + (Regex::new(r"^\\x20\\x").unwrap(), "mqtt"), + (Regex::new(r"Redis Pub/Sub").unwrap(), "redis-pubsub"), + (Regex::new(r"ZeroMQ").unwrap(), "zeromq"), + (Regex::new(r"Apache Pulsar").unwrap(), "pulsar"), + (Regex::new(r"NSQ").unwrap(), "nsq"), + + // SSL/TLS + (Regex::new(r"^\x16\x03").unwrap(), "ssl/tls"), + (Regex::new(r"^\x16\x03\x00").unwrap(), "ssl-3.0"), + (Regex::new(r"^\x16\x03\x01").unwrap(), "tls-1.0"), + (Regex::new(r"^\x16\x03\x02").unwrap(), "tls-1.1"), + (Regex::new(r"^\x16\x03\x03").unwrap(), "tls-1.2"), + (Regex::new(r"^\x16\x03\x04").unwrap(), "tls-1.3"), + (Regex::new(r"^\x80\x80").unwrap(), "ssl-2.0"), + (Regex::new(r"^SSL").unwrap(), "ssl"), + (Regex::new(r"TLSv1").unwrap(), "tls"), + (Regex::new(r"StartTLS").unwrap(), "starttls"), + + // RTSP/SIP/Media Streaming + (Regex::new(r"^RTSP/\d").unwrap(), "rtsp"), + (Regex::new(r"^SIP/\d").unwrap(), "sip"), + (Regex::new(r"^INVITE sip:").unwrap(), "sip"), + (Regex::new(r"^REGISTER sip:").unwrap(), "sip"), + (Regex::new(r"User-Agent: .*Asterisk").unwrap(), "asterisk-sip"), + (Regex::new(r"User-Agent: .*FreeSWITCH").unwrap(), "freeswitch-sip"), + (Regex::new(r"Server: .*Asterisk").unwrap(), "asterisk"), + (Regex::new(r"Server: .*FreeSWITCH").unwrap(), "freeswitch"), + (Regex::new(r"^ICY \d").unwrap(), "shoutcast"), + (Regex::new(r"^ICE/1\.0").unwrap(), "icecast"), + (Regex::new(r"Server: Icecast").unwrap(), "icecast"), + (Regex::new(r"Server: Shoutcast").unwrap(), "shoutcast"), + (Regex::new(r"^\$\$\$\$\$:").unwrap(), "rtmp"), + (Regex::new(r"^RTMP/\d").unwrap(), "rtmp"), + + // Network and Routing + (Regex::new(r"^RIP").unwrap(), "rip"), + (Regex::new(r"^OSPF").unwrap(), "ospf"), + (Regex::new(r"^BGP").unwrap(), "bgp"), + (Regex::new(r"^220.*SNMP").unwrap(), "snmp"), + (Regex::new(r"public\x02\x01\x00\x02\x01\x00").unwrap(), "snmp"), + (Regex::new(r"^\x30\x2c\x02\x01\x00\x04").unwrap(), "snmp"), + (Regex::new(r"X-Openstackinternaltoken").unwrap(), "openstack"), + (Regex::new(r"zabbix").unwrap(), "zabbix-agent"), + (Regex::new(r"^\\x00\\x00\\x00\\x00\\x00\\x07\\x72\\x").unwrap(), "elasticsearch"), + + // File Sharing + (Regex::new(r"^\\x00\\x00.*SAMBA").unwrap(), "samba"), + (Regex::new(r"^SMB").unwrap(), "smb"), + (Regex::new(r"^\\xff\\x53\\x4d\\x42").unwrap(), "smb"), + (Regex::new(r"NFS server").unwrap(), "nfs"), + (Regex::new(r"^\\x80\\x00\\x00\\x18").unwrap(), "nfs"), + (Regex::new(r"^\\x80\\x00\\x00\\x28").unwrap(), "nfs"), + (Regex::new(r"^\\x05\\x00\\x0b\\x03\\x10\\x00\\x00\\x00").unwrap(), "dcerpc"), + (Regex::new(r"AFP").unwrap(), "afp"), + (Regex::new(r"AFPX").unwrap(), "afp"), + (Regex::new(r"Apple Filing Protocol").unwrap(), "afp"), + (Regex::new(r"^\\x00\\x00\\x00\\d.\\xc2\\x80\\x80\\x80").unwrap(), "webdav"), + + // Version Control + (Regex::new(r"^git://").unwrap(), "git"), + (Regex::new(r"git version").unwrap(), "git"), + (Regex::new(r"\\x30\\x30").unwrap(), "git"), + (Regex::new(r"git-upload-pack").unwrap(), "git"), + (Regex::new(r"^\\d{3} \\w{3}\\s+\\d+\\s\\d+:\\d+:\\d+").unwrap(), "syslog"), + (Regex::new(r"WMI").unwrap(), "wmi"), + (Regex::new(r"WBEM").unwrap(), "wbem"), + (Regex::new(r"WS-Management").unwrap(), "ws-man"), + (Regex::new(r"^M-SEARCH").unwrap(), "ssdp"), + (Regex::new(r"NOTIFY").unwrap(), "ssdp-notify"), + (Regex::new(r"UPnP").unwrap(), "upnp"), + (Regex::new(r"DLNA").unwrap(), "dlna"), + + // Print Services + (Regex::new(r"IPP/").unwrap(), "ipp"), + (Regex::new(r"CUPS").unwrap(), "cups"), + (Regex::new(r"LPD").unwrap(), "lpd"), + (Regex::new(r"JetDirect").unwrap(), "jetdirect"), + (Regex::new(r"^\\x01\\x01\\x00\\x").unwrap(), "ipp"), + + // Hardware Management + (Regex::new(r"IPMI").unwrap(), "ipmi"), + (Regex::new(r"BMC").unwrap(), "bmc"), + (Regex::new(r"iDRAC").unwrap(), "idrac"), + (Regex::new(r"iLO").unwrap(), "ilo"), + (Regex::new(r"DRAC").unwrap(), "drac"), + (Regex::new(r"Lights Out").unwrap(), "lights-out"), + (Regex::new(r"\\x06\\x00\\xff\\x07").unwrap(), "ipmi"), + (Regex::new(r"RMCP").unwrap(), "ipmi-rmcp"), + + // Additional Crypto/Blockchain + (Regex::new(r"Cardano").unwrap(), "cardano"), + (Regex::new(r"Polkadot").unwrap(), "polkadot"), + (Regex::new(r"Solana").unwrap(), "solana"), + (Regex::new(r"Chainlink").unwrap(), "chainlink"), + (Regex::new(r"Near Protocol").unwrap(), "near"), + (Regex::new(r"Avalanche").unwrap(), "avalanche"), + (Regex::new(r"Binance").unwrap(), "binance"), + (Regex::new(r"Hyperledger").unwrap(), "hyperledger"), + (Regex::new(r"Corda").unwrap(), "corda"), + (Regex::new(r"^\\xfa\\xce\\xb0\\x0c").unwrap(), "cardano"), + ]; +} diff --git a/src/service_scan/tcp_http.rs b/src/service_scan/tcp_http.rs new file mode 100644 index 0000000..d552ca5 --- /dev/null +++ b/src/service_scan/tcp_http.rs @@ -0,0 +1,21 @@ +use std::{io::Read, net::IpAddr, time::Duration}; + +use reqwest::redirect::Policy; + +pub fn scan( + ip: IpAddr, + port: &i32, + timeout: Duration, +) -> Result> { + let mut result = String::new(); + let _ = reqwest::blocking::Client::builder() + .redirect(Policy::none()) + .timeout(timeout) + .build() + .unwrap() + .get(format!("http://{}:{}", ip.to_string(), port)) + .send()? + .read_to_string(&mut result); + + Ok(result) +} diff --git a/src/service_scan/tcp_https.rs b/src/service_scan/tcp_https.rs new file mode 100644 index 0000000..e7efae4 --- /dev/null +++ b/src/service_scan/tcp_https.rs @@ -0,0 +1,24 @@ +use std::{io::Read, net::IpAddr, time::Duration}; + +use reqwest::redirect::Policy; + +pub fn scan( + ip: IpAddr, + port: &i32, + timeout: Duration, +) -> Result> { + let mut result = String::new(); + let _response = reqwest::blocking::Client::builder() + .danger_accept_invalid_certs(true) + .redirect(Policy::none()) + .timeout(timeout) + .build() + .unwrap() + .get(format!("https://{}:{}", ip.to_string(), port)) + .send()? + .read_to_string(&mut result); + + // println!("{}", result); + + Ok(result) +}