mirror of
https://github.com/bee-san/RustScan.git
synced 2026-11-07 17:02:04 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ea3613c1 | ||
|
|
8879fa7ff4 | ||
|
|
5d532356fd | ||
|
|
9f69d36057 | ||
|
|
dc18469af6 | ||
|
|
01fb9a14ba | ||
|
|
e2ddd93835 | ||
|
|
3873117324 | ||
|
|
dcb91d95e4 | ||
|
|
4947158ff8 | ||
|
|
f544f2240b | ||
|
|
f33476468f | ||
|
|
534cf8adf8 | ||
|
|
8290cad8d6 | ||
|
|
ac3ad3a6f7 | ||
|
|
3e74f5cb69 | ||
|
|
bfb827c0b8 | ||
|
|
16250456e6 | ||
|
|
0779071f7f | ||
|
|
8bfb643796 | ||
|
|
457c1643ff | ||
|
|
4ae4853e1d | ||
|
|
d471065dd2 | ||
|
|
99b00c091c | ||
|
|
af34aa4d07 |
No files matched your search
@@ -1,9 +1,13 @@
|
||||
name: Build
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
schedule:
|
||||
- cron: '00 01 * * *'
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Publish Docker image
|
||||
name: Release - Distribute Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
release:
|
||||
types: [published]
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
flavor: latest=true
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ contains(fromJson('["push", "release"]'), github.event_name) }} # Publish to docker registry only on push event or new release.
|
||||
|
||||
@@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
schedule:
|
||||
# Run everyday at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07)
|
||||
- cron: "0 9 * * *"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: Release
|
||||
name: Release - Distribute binaries
|
||||
on:
|
||||
# If we want to make release using github interface.
|
||||
# release:
|
||||
# types: [published]
|
||||
# If we want to make release by pushing new tag.
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
@@ -15,17 +19,29 @@ env:
|
||||
BIN_NAME: rustscan
|
||||
PROJECT_NAME: rustscan
|
||||
REPO_NAME: RustScan/RustScan
|
||||
# BREW_TAP: RustScan/homebrew-tap
|
||||
BREW_TAP: RustScan/homebrew-tap
|
||||
|
||||
jobs:
|
||||
dist:
|
||||
name: Dist
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false # don't fail other jobs if one fails
|
||||
matrix:
|
||||
build: [x86_64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
|
||||
# aarch64-linux is failing due to python3 missing issues during tests. I'm removing it for now.
|
||||
# build: [x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos, x86_64-windows, x86_64-win-gnu]
|
||||
build: [x86_64-linux, x86_64-macos, aarch64-macos, x86_64-windows, x86_64-win-gnu]
|
||||
include:
|
||||
- build: aarch64-macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
target: aarch64-apple-darwin
|
||||
cross: true
|
||||
# - build: aarch64-linux
|
||||
# os: ubuntu-20.04
|
||||
# rust: stable
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
# cross: true
|
||||
- build: x86_64-linux
|
||||
os: ubuntu-20.04
|
||||
rust: stable
|
||||
@@ -41,34 +57,26 @@ jobs:
|
||||
rust: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
cross: false
|
||||
# - build: aarch64-linux
|
||||
# os: ubuntu-20.04
|
||||
# rust: stable
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
# - build: aarch64-macos
|
||||
# os: macos-latest
|
||||
# rust: stable
|
||||
# target: aarch64-apple-darwin
|
||||
# - build: x86_64-win-gnu
|
||||
# os: windows-2019
|
||||
# rust: stable-x86_64-gnu
|
||||
# target: x86_64-pc-windows-gnu
|
||||
# - build: win32-msvc
|
||||
# os: windows-2019
|
||||
# rust: stable
|
||||
# target: i686-pc-windows-msvc
|
||||
- build: x86_64-win-gnu
|
||||
os: windows-2019
|
||||
rust: stable-x86_64-gnu
|
||||
target: x86_64-pc-windows-gnu
|
||||
cross: false
|
||||
|
||||
steps:
|
||||
- name: Set Git config. (windows only)
|
||||
if: contains(matrix.build, 'windows') # Windows is missing some dependencies
|
||||
- name: Set Git config (windows only)
|
||||
if: contains(matrix.os, 'windows') # Windows is missing some dependencies
|
||||
run: |
|
||||
git config --global pack.windowMemory "100m"
|
||||
git config --global pack.packSizeLimit "100m"
|
||||
git config --global pack.threads "1"
|
||||
git config --global pack.deltaCacheSize "512m"
|
||||
|
||||
- name: Install python 3.10 for windows
|
||||
if: contains(matrix.build, 'windows') # Windows is missing some dependencies
|
||||
- name: Install dependencies
|
||||
if: contains(matrix.build, 'aarch64-linux') # Windows is missing some dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu python3
|
||||
|
||||
- name: Install python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
@@ -101,23 +109,17 @@ jobs:
|
||||
args: --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Calculate tag name
|
||||
if: contains(matrix.build, 'linux')
|
||||
# if: contains(matrix.build, 'linux')
|
||||
shell: bash
|
||||
run: |
|
||||
name=dev
|
||||
if [[ ${GITHUB_REF} =~ refs/tags/[0-9]+.[0-9]+.[0-9]+ ]]; then
|
||||
name=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
echo ::set-output name=val::$name
|
||||
echo TAG=$name >> $GITHUB_ENV
|
||||
echo "TAG=$name" >> $GITHUB_ENV
|
||||
id: tagname
|
||||
|
||||
- name: Build deb package (linux only)
|
||||
if: contains(matrix.build, 'linux')
|
||||
run: |
|
||||
cargo install cargo-deb
|
||||
cargo deb --target ${{ matrix.target }} --deb-version ${TAG}
|
||||
|
||||
- name: Build archive
|
||||
- name: Build and package artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir dist
|
||||
@@ -128,17 +130,19 @@ jobs:
|
||||
fi
|
||||
|
||||
if [[ "${{ matrix.build }}" =~ "linux" ]]; then
|
||||
cp "target/${{ matrix.target }}/debian/rustscan_${TAG}_amd64.deb" "dist/"
|
||||
cargo install cargo-deb
|
||||
cargo deb --target ${{ matrix.target }} --deb-version ${TAG}
|
||||
cp "target/${{ matrix.target }}/debian/rustscan_${TAG}_amd64.deb" "dist/" || true
|
||||
fi
|
||||
- uses: actions/[email protected]
|
||||
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
name: bins-${{ matrix.build }}
|
||||
path: dist
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
needs: [dist]
|
||||
if: contains(fromJson('["push", "schedule"]'), github.event_name) && startsWith(github.ref, 'refs/tags/')
|
||||
package:
|
||||
name: Package
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
@@ -147,19 +151,18 @@ jobs:
|
||||
submodules: false
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
# with:
|
||||
# path: dist
|
||||
# - run: ls -al ./dist
|
||||
- run: ls -al bins-*
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Calculate tag name
|
||||
# if: contains(matrix.build, 'linux')
|
||||
shell: bash
|
||||
run: |
|
||||
name=dev
|
||||
if [[ ${GITHUB_REF} =~ refs/tags/[0-9]+.[0-9]+.[0-9]+ ]]; then
|
||||
name=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
echo ::set-output name=val::$name
|
||||
echo TAG=$name >> $GITHUB_ENV
|
||||
echo "TAG=$name" >> $GITHUB_ENV
|
||||
id: tagname
|
||||
|
||||
- name: Build archive
|
||||
@@ -168,78 +171,46 @@ jobs:
|
||||
set -ex
|
||||
rm -rf tmp
|
||||
mkdir tmp
|
||||
mkdir dist
|
||||
for dir in bins-* ; do
|
||||
platform=${dir#"bins-"}
|
||||
for dir in dist/bins-* ; do
|
||||
platform=${dir#"dist/bins-"}
|
||||
unset exe
|
||||
if [[ $platform =~ "windows" ]]; then
|
||||
if [[ $platform =~ "win" ]]; then
|
||||
exe=".exe"
|
||||
fi
|
||||
pkgname=$PROJECT_NAME-$TAG-$platform
|
||||
mkdir tmp/$pkgname
|
||||
# cp LICENSE README.md tmp/$pkgname
|
||||
mv bins-$platform/$BIN_NAME$exe tmp/$pkgname
|
||||
cp $dir/$BIN_NAME$exe dist/ || true
|
||||
mv $dir/$BIN_NAME$exe tmp/$pkgname
|
||||
chmod +x tmp/$pkgname/$BIN_NAME$exe
|
||||
|
||||
if [[ $platform =~ "linux" ]]; then
|
||||
mv "bins-$platform/rustscan_${TAG}_amd64.deb" dist/
|
||||
mv "$dir/rustscan_${TAG}_amd64.deb" dist/ || true
|
||||
fi
|
||||
|
||||
if [ "$exe" = "" ]; then
|
||||
tar cJf dist/$pkgname.tar.xz -C tmp $pkgname
|
||||
else
|
||||
(cd tmp && 7z a -r ../dist/$pkgname.zip $pkgname)
|
||||
fi
|
||||
tar cJf dist/$pkgname.tar.xz -C tmp $pkgname
|
||||
7z a dist/$pkgname.zip tmp/$pkgname
|
||||
done
|
||||
|
||||
- name: Upload binaries to release
|
||||
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: dist/*
|
||||
file_glob: true
|
||||
tag: ${{ steps.tagname.outputs.val }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
- name: Extract version
|
||||
id: extract-version
|
||||
run: |
|
||||
printf "::set-output name=%s::%s\n" tag-name "${GITHUB_REF#refs/tags/}"
|
||||
- uses: mislav/bump-homebrew-formula-action@v3
|
||||
with:
|
||||
formula-path: ${{env.PROJECT_NAME}}.rb
|
||||
homebrew-tap: ${{ env.BREW_TAP }}
|
||||
download-url: 'https://github.com/${{ env.REPO_NAME }}/releases/download/${{ steps.extract-version.outputs.tag-name }}/${{env.PROJECT_NAME}}-${{ steps.extract-version.outputs.tag-name }}-x86_64-macos.tar.xz'
|
||||
commit-message: updating formula for ${{ env.PROJECT_NAME }}
|
||||
env:
|
||||
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
|
||||
#
|
||||
# you can use this initial file in your homebrew-tap if you don't have an initial formula:
|
||||
# <projectname>.rb
|
||||
#
|
||||
# class <Projectname capitalized> < Formula
|
||||
# desc "A test formula"
|
||||
# homepage "http://www.example.com"
|
||||
# url "-----"
|
||||
# version "-----"
|
||||
# sha256 "-----"
|
||||
# - uses: mislav/bump-homebrew-formula-action@v3
|
||||
# with:
|
||||
# formula-path: ${{env.PROJECT_NAME}}.rb
|
||||
# homebrew-tap: ${{ env.BREW_TAP }}
|
||||
# download-url: 'https://github.com/${{ env.REPO_NAME }}/releases/download/${{ github.ref }}/${{env.PROJECT_NAME}}-${{ github.ref }}-x86_64-macos.tar.xz'
|
||||
# commit-message: updating formula for ${{ env.PROJECT_NAME }}
|
||||
# env:
|
||||
# COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
|
||||
|
||||
# def install
|
||||
# bin.install "<bin-name>"
|
||||
# end
|
||||
# end
|
||||
|
||||
# Uncomment this section if you want to release your package to crates.io
|
||||
# Before publishing, make sure you have filled out the following fields:
|
||||
# license or license-file, description, homepage, documentation, repository, readme.
|
||||
# Read more: https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
|
||||
- name: Install ${{ matrix.rust }} toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
- run: cargo publish --token ${CRATES_TOKEN}
|
||||
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'release'
|
||||
env:
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
|
||||
Generated
+482
-472
File diff suppressed because it is too large.
Load diff
+14
-12
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustscan"
|
||||
version = "2.2.3"
|
||||
version = "2.3.0"
|
||||
authors = ["Autumn <[email protected]>"]
|
||||
edition = "2018"
|
||||
description = "Faster Nmap Scanning with Rust"
|
||||
@@ -9,7 +9,7 @@ repository = "https://github.com/rustscan/rustscan"
|
||||
license = "MIT"
|
||||
keywords = ["port", "scanning", "nmap"]
|
||||
categories = ["command-line-utilities"]
|
||||
readme="README.md"
|
||||
readme = "README.md"
|
||||
exclude = [
|
||||
".github/*",
|
||||
"pictures/*",
|
||||
@@ -17,29 +17,31 @@ exclude = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.8", features = ["derive", "wrap_help"] }
|
||||
colored = "2.1.0"
|
||||
structopt = "0.3.20"
|
||||
async-std = "1.7.0"
|
||||
futures = "0.3"
|
||||
rlimit = "0.8.3"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.8.2"
|
||||
dirs = "3.0.1"
|
||||
rlimit = "0.10.1"
|
||||
log = "0.4.22"
|
||||
env_logger = "0.11.3"
|
||||
anstream = "=0.6.14"
|
||||
dirs = "5.0.1"
|
||||
gcd = "2.0.1"
|
||||
rand = "0.7.3"
|
||||
rand = "0.8.5"
|
||||
colorful = "0.2.1"
|
||||
ansi_term = "0.12.1"
|
||||
toml = "0.5.7"
|
||||
toml = "0.8.14"
|
||||
serde = "1.0.124"
|
||||
serde_derive = "1.0.116"
|
||||
cidr-utils = "0.5.1"
|
||||
itertools = "0.9.0"
|
||||
trust-dns-resolver = { version = "0.23.2", features = ["dns-over-rustls"] }
|
||||
cidr-utils = "0.6.1"
|
||||
itertools = "0.13.0"
|
||||
hickory-resolver = { version = "0.24.0", features = ["dns-over-rustls"] }
|
||||
anyhow = "1.0.40"
|
||||
subprocess = "0.2.6"
|
||||
text_placeholder = { version = "0.5", features = ["struct_context"] }
|
||||
|
||||
[dev-dependencies]
|
||||
parameterized = "2.0.0"
|
||||
wait-timeout = "0.2"
|
||||
|
||||
[package.metadata.deb]
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ COPY src/ src/
|
||||
RUN cargo build --release
|
||||
|
||||
# Release Stage
|
||||
FROM alpine:3.19.1 as release
|
||||
FROM alpine:3.20.1 as release
|
||||
LABEL author="Hydragyrum <https://github.com/Hydragyrum>"
|
||||
LABEL author="LeoFVO <https://github.com/LeoFVO>"
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
| 🐋 `(Recommended)` | 👩💻 | 🏗️ | 🔧 |
|
||||
| :---------------------------------------------------------: | :---------------------------: | :-------------------------: | :-------------------------: |
|
||||
| [![Docker][DockerPic]][Docker] | [![Kali][Kali1]][Kali/Debian] | [![Arch][Arch]][Arch-Linux] | [![apple][apple]][Homebrew] |
|
||||
| `docker pull rustscan/rustscan:2.1.1`</br>[Docker][usage-0] | [Link to Documentation][kali] | `yay -S rustscan` | `brew install rustscan` |
|
||||
| `docker pull rustscan/rustscan:latest`</br>[Docker][usage-0] | [Link to Documentation][kali] | `yay -S rustscan` | `brew install rustscan` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
//! Provides functions to parse input IP addresses, CIDRs or files.
|
||||
use std::fs::{self, File};
|
||||
use std::io::{prelude::*, BufReader};
|
||||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
use hickory_resolver::{
|
||||
config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts},
|
||||
Resolver,
|
||||
};
|
||||
use log::debug;
|
||||
|
||||
use crate::input::Opts;
|
||||
use crate::warning;
|
||||
|
||||
/// Parses the string(s) into IP addresses.
|
||||
///
|
||||
/// Goes through all possible IP inputs (files or via argparsing).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rustscan::input::Opts;
|
||||
/// # use rustscan::address::parse_addresses;
|
||||
/// let mut opts = Opts::default();
|
||||
/// opts.addresses = vec!["192.168.0.0/30".to_owned()];
|
||||
///
|
||||
/// let ips = parse_addresses(&opts);
|
||||
/// ```
|
||||
pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
let mut unresolved_addresses: Vec<&str> = Vec::new();
|
||||
let backup_resolver = get_resolver(&input.resolver);
|
||||
|
||||
for address in &input.addresses {
|
||||
let parsed_ips = parse_address(address, &backup_resolver);
|
||||
if !parsed_ips.is_empty() {
|
||||
ips.extend(parsed_ips);
|
||||
} else {
|
||||
unresolved_addresses.push(address);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got to this point this can only be a file path or the wrong input.
|
||||
for file_path in unresolved_addresses {
|
||||
let file_path = Path::new(file_path);
|
||||
|
||||
if !file_path.is_file() {
|
||||
warning!(
|
||||
format!("Host {file_path:?} could not be resolved."),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(x) = read_ips_from_file(file_path, &backup_resolver) {
|
||||
ips.extend(x);
|
||||
} else {
|
||||
warning!(
|
||||
format!("Host {file_path:?} could not be resolved."),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
/// Given a string, parse it as a host, IP address, or CIDR.
|
||||
///
|
||||
/// This allows us to pass files as hosts or cidr or IPs easily
|
||||
/// Call this every time you have a possible IP-or-host.
|
||||
///
|
||||
/// If the address is a domain, we can self-resolve the domain locally
|
||||
/// or resolve it by dns resolver list.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rustscan::address::parse_address;
|
||||
/// # use hickory_resolver::Resolver;
|
||||
/// let ips = parse_address("127.0.0.1", &Resolver::default().unwrap());
|
||||
/// ```
|
||||
pub fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
|
||||
IpCidr::from_str(address)
|
||||
.map(|cidr| cidr.iter().map(|c| c.address()).collect())
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
format!("{}:{}", &address, 80)
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.map(|mut iter| vec![iter.next().unwrap().ip()])
|
||||
})
|
||||
.unwrap_or_else(|| resolve_ips_from_host(address, resolver))
|
||||
}
|
||||
|
||||
/// Uses DNS to get the IPS associated with host
|
||||
fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
|
||||
if let Ok(addrs) = source.to_socket_addrs() {
|
||||
for ip in addrs {
|
||||
ips.push(ip.ip());
|
||||
}
|
||||
} else if let Ok(addrs) = backup_resolver.lookup_ip(source) {
|
||||
ips.extend(addrs.iter());
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
/// Derive a DNS resolver.
|
||||
///
|
||||
/// 1. if the `resolver` parameter has been set:
|
||||
/// 1. assume the parameter is a path and attempt to read IPs.
|
||||
/// 2. parse the input as a comma-separated list of IPs.
|
||||
/// 2. if `resolver` is not set:
|
||||
/// 1. attempt to derive a resolver from the system config. (e.g.
|
||||
/// `/etc/resolv.conf` on *nix).
|
||||
/// 2. finally, build a CloudFlare-based resolver (default
|
||||
/// behaviour).
|
||||
fn get_resolver(resolver: &Option<String>) -> Resolver {
|
||||
match resolver {
|
||||
Some(r) => {
|
||||
let mut config = ResolverConfig::new();
|
||||
let resolver_ips = match read_resolver_from_file(r) {
|
||||
Ok(ips) => ips,
|
||||
Err(_) => r
|
||||
.split(',')
|
||||
.filter_map(|r| IpAddr::from_str(r).ok())
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
for ip in resolver_ips {
|
||||
config.add_name_server(NameServerConfig::new(
|
||||
SocketAddr::new(ip, 53),
|
||||
Protocol::Udp,
|
||||
));
|
||||
}
|
||||
Resolver::new(config, ResolverOpts::default()).unwrap()
|
||||
}
|
||||
None => match Resolver::from_system_conf() {
|
||||
Ok(resolver) => resolver,
|
||||
Err(_) => {
|
||||
Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses and input file of IPs for use in DNS resolution.
|
||||
fn read_resolver_from_file(path: &str) -> Result<Vec<IpAddr>, std::io::Error> {
|
||||
let ips = fs::read_to_string(path)?
|
||||
.lines()
|
||||
.filter_map(|line| IpAddr::from_str(line.trim()).ok())
|
||||
.collect();
|
||||
|
||||
Ok(ips)
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
/// Parses an input file of IPs and uses those
|
||||
fn read_ips_from_file(
|
||||
ips: &std::path::Path,
|
||||
backup_resolver: &Resolver,
|
||||
) -> Result<Vec<IpAddr>, std::io::Error> {
|
||||
let file = File::open(ips)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
|
||||
for address_line in reader.lines() {
|
||||
if let Ok(address) = address_line {
|
||||
ips.extend(parse_address(&address, backup_resolver));
|
||||
} else {
|
||||
debug!("Line in file is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ips)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{get_resolver, parse_addresses, Opts};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
#[test]
|
||||
fn parse_correct_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
ips,
|
||||
[
|
||||
Ipv4Addr::new(127, 0, 0, 1),
|
||||
Ipv4Addr::new(192, 168, 0, 0),
|
||||
Ipv4Addr::new(192, 168, 0, 1),
|
||||
Ipv4Addr::new(192, 168, 0, 2),
|
||||
Ipv4Addr::new(192, 168, 0, 3)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_host_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["google.com".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_and_incorrect_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips, [Ipv4Addr::new(127, 0, 0, 1),]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_incorrect_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert!(ips.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn parse_hosts_file_and_incorrect_hosts() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_hosts_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/empty_hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_naughty_host_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/naughty_string.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_default_cloudflare() {
|
||||
let opts = Opts::default();
|
||||
|
||||
let resolver = get_resolver(&opts.resolver);
|
||||
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
|
||||
|
||||
assert!(opts.resolver.is_none());
|
||||
assert!(lookup.iter().next().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_args_google_dns() {
|
||||
let mut opts = Opts::default();
|
||||
// https://developers.google.com/speed/public-dns
|
||||
opts.resolver = Some("8.8.8.8,8.8.4.4".to_owned());
|
||||
|
||||
let resolver = get_resolver(&opts.resolver);
|
||||
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
|
||||
|
||||
assert!(lookup.iter().next().is_some());
|
||||
}
|
||||
}
|
||||
+91
-44
@@ -1,35 +1,31 @@
|
||||
//! Provides a means to read, parse and hold configuration options for scans.
|
||||
use clap::{Parser, ValueEnum};
|
||||
use serde_derive::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
const LOWEST_PORT_NUMBER: u16 = 1;
|
||||
const TOP_PORT_NUMBER: u16 = 65535;
|
||||
|
||||
arg_enum! {
|
||||
/// Represents the strategy in which the port scanning will run.
|
||||
/// - Serial will run from start to end, for example 1 to 1_000.
|
||||
/// - Random will randomize the order in which ports will be scanned.
|
||||
#[derive(Deserialize, Debug, StructOpt, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScanOrder {
|
||||
Serial,
|
||||
Random,
|
||||
}
|
||||
/// Represents the strategy in which the port scanning will run.
|
||||
/// - Serial will run from start to end, for example 1 to 1_000.
|
||||
/// - Random will randomize the order in which ports will be scanned.
|
||||
#[derive(Deserialize, Debug, ValueEnum, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScanOrder {
|
||||
Serial,
|
||||
Random,
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
/// Represents the scripts variant.
|
||||
/// - none will avoid running any script, only portscan results will be shown.
|
||||
/// - default will run the default embedded nmap script, that's part of RustScan since the beginning.
|
||||
/// - custom will read the ScriptConfig file and the available scripts in the predefined folders
|
||||
#[derive(Deserialize, Debug, StructOpt, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum ScriptsRequired {
|
||||
None,
|
||||
Default,
|
||||
Custom,
|
||||
}
|
||||
/// Represents the scripts variant.
|
||||
/// - none will avoid running any script, only portscan results will be shown.
|
||||
/// - default will run the default embedded nmap script, that's part of RustScan since the beginning.
|
||||
/// - custom will read the ScriptConfig file and the available scripts in the predefined folders
|
||||
#[derive(Deserialize, Debug, ValueEnum, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum ScriptsRequired {
|
||||
None,
|
||||
Default,
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// Represents the range of ports to be scanned.
|
||||
@@ -63,8 +59,13 @@ fn parse_range(input: &str) -> Result<PortRange, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug, Clone)]
|
||||
#[structopt(name = "rustscan", setting = structopt::clap::AppSettings::TrailingVarArg)]
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(
|
||||
name = "rustscan",
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
max_term_width = 120,
|
||||
help_template = "{bin} {version}\n{about}\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}",
|
||||
)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
/// Fast Port Scanner built in Rust.
|
||||
/// WARNING Do not use this program against sensitive infrastructure since the
|
||||
@@ -73,65 +74,69 @@ fn parse_range(input: &str) -> Result<PortRange, String> {
|
||||
/// - GitHub <https://github.com/RustScan/RustScan>
|
||||
pub struct Opts {
|
||||
/// A comma-delimited list or newline-delimited file of separated CIDRs, IPs, or hosts to be scanned.
|
||||
#[structopt(short, long, use_delimiter = true)]
|
||||
#[arg(short, long, value_delimiter = ',')]
|
||||
pub addresses: Vec<String>,
|
||||
|
||||
/// A list of comma separated ports to be scanned. Example: 80,443,8080.
|
||||
#[structopt(short, long, use_delimiter = true)]
|
||||
#[arg(short, long, value_delimiter = ',')]
|
||||
pub ports: Option<Vec<u16>>,
|
||||
|
||||
/// A range of ports with format start-end. Example: 1-1000.
|
||||
#[structopt(short, long, conflicts_with = "ports", parse(try_from_str = parse_range))]
|
||||
#[arg(short, long, conflicts_with = "ports", value_parser = parse_range)]
|
||||
pub range: Option<PortRange>,
|
||||
|
||||
/// Whether to ignore the configuration file or not.
|
||||
#[structopt(short, long)]
|
||||
#[arg(short, long)]
|
||||
pub no_config: bool,
|
||||
|
||||
/// Custom path to config file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
#[arg(short, long, value_parser)]
|
||||
pub config_path: Option<PathBuf>,
|
||||
|
||||
/// Greppable mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
|
||||
#[structopt(short, long)]
|
||||
#[arg(short, long)]
|
||||
pub greppable: bool,
|
||||
|
||||
/// Accessible mode. Turns off features which negatively affect screen readers.
|
||||
#[structopt(long)]
|
||||
#[arg(long)]
|
||||
pub accessible: bool,
|
||||
|
||||
/// A comma-delimited list or file of DNS resolvers.
|
||||
#[arg(long)]
|
||||
pub resolver: Option<String>,
|
||||
|
||||
/// The batch size for port scanning, it increases or slows the speed of
|
||||
/// scanning. Depends on the open file limit of your OS. If you do 65535
|
||||
/// it will do every port at the same time. Although, your OS may not
|
||||
/// support this.
|
||||
#[structopt(short, long, default_value = "4500")]
|
||||
#[arg(short, long, default_value = "4500")]
|
||||
pub batch_size: u16,
|
||||
|
||||
/// The timeout in milliseconds before a port is assumed to be closed.
|
||||
#[structopt(short, long, default_value = "1500")]
|
||||
#[arg(short, long, default_value = "1500")]
|
||||
pub timeout: u32,
|
||||
|
||||
/// The number of tries before a port is assumed to be closed.
|
||||
/// If set to 0, rustscan will correct it to 1.
|
||||
#[structopt(long, default_value = "1")]
|
||||
#[arg(long, default_value = "1")]
|
||||
pub tries: u8,
|
||||
|
||||
/// Automatically ups the ULIMIT with the value you provided.
|
||||
#[structopt(short, long)]
|
||||
#[arg(short, long)]
|
||||
pub ulimit: Option<u64>,
|
||||
|
||||
/// The order of scanning to be performed. The "serial" option will
|
||||
/// scan ports in ascending order while the "random" option will scan
|
||||
/// ports randomly.
|
||||
#[structopt(long, possible_values = &ScanOrder::variants(), case_insensitive = true, default_value = "serial")]
|
||||
#[arg(long, value_enum, ignore_case = true, default_value = "serial")]
|
||||
pub scan_order: ScanOrder,
|
||||
|
||||
/// Level of scripting required for the run.
|
||||
#[structopt(long, possible_values = &ScriptsRequired::variants(), case_insensitive = true, default_value = "default")]
|
||||
#[arg(long, value_enum, ignore_case = true, default_value = "default")]
|
||||
pub scripts: ScriptsRequired,
|
||||
|
||||
/// Use the top 1000 ports.
|
||||
#[structopt(long)]
|
||||
#[arg(long)]
|
||||
pub top: bool,
|
||||
|
||||
/// The Script arguments to run.
|
||||
@@ -139,18 +144,22 @@ pub struct Opts {
|
||||
/// Example: 'rustscan -t 1500 -a 127.0.0.1 -- -A -sC'.
|
||||
/// This command adds -Pn -vvv -p $PORTS automatically to nmap.
|
||||
/// For things like --script '(safe and vuln)' enclose it in quotations marks \"'(safe and vuln)'\"
|
||||
#[structopt(last = true)]
|
||||
#[arg(last = true)]
|
||||
pub command: Vec<String>,
|
||||
|
||||
/// A list of comma separated ports to be excluded from scanning. Example: 80,443,8080.
|
||||
#[structopt(short, long, use_delimiter = true)]
|
||||
#[arg(short, long, value_delimiter = ',')]
|
||||
pub exclude_ports: Option<Vec<u16>>,
|
||||
|
||||
/// UDP scanning mode, finds UDP ports that send back responses
|
||||
#[arg(long)]
|
||||
pub udp: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Opts {
|
||||
pub fn read() -> Self {
|
||||
let mut opts = Opts::from_args();
|
||||
let mut opts = Opts::parse();
|
||||
|
||||
if opts.ports.is_none() && opts.range.is_none() {
|
||||
opts.range = Some(PortRange {
|
||||
@@ -184,7 +193,7 @@ impl Opts {
|
||||
|
||||
merge_required!(
|
||||
addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts,
|
||||
command
|
||||
command, udp
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,14 +210,14 @@ impl Opts {
|
||||
|
||||
// Only use top ports when the user asks for them
|
||||
if self.top && config.ports.is_some() {
|
||||
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.clone().unwrap().len());
|
||||
for entry in config.ports.clone().unwrap().keys() {
|
||||
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.as_ref().unwrap().len());
|
||||
for entry in config.ports.as_ref().unwrap().keys() {
|
||||
ports.push(entry.parse().unwrap());
|
||||
}
|
||||
self.ports = Some(ports);
|
||||
}
|
||||
|
||||
merge_optional!(range, ulimit, exclude_ports);
|
||||
merge_optional!(range, resolver, ulimit, exclude_ports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,12 +234,14 @@ impl Default for Opts {
|
||||
ulimit: None,
|
||||
command: vec![],
|
||||
accessible: false,
|
||||
resolver: None,
|
||||
scan_order: ScanOrder::Serial,
|
||||
no_config: true,
|
||||
top: false,
|
||||
scripts: ScriptsRequired::Default,
|
||||
config_path: None,
|
||||
exclude_ports: None,
|
||||
udp: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,10 +261,12 @@ pub struct Config {
|
||||
timeout: Option<u32>,
|
||||
tries: Option<u8>,
|
||||
ulimit: Option<u64>,
|
||||
resolver: Option<String>,
|
||||
scan_order: Option<ScanOrder>,
|
||||
command: Option<Vec<String>>,
|
||||
scripts: Option<ScriptsRequired>,
|
||||
exclude_ports: Option<Vec<u16>>,
|
||||
udp: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
@@ -269,6 +282,7 @@ impl Config {
|
||||
/// greppable = true
|
||||
/// scan_order: "Serial"
|
||||
/// exclude_ports = [8080, 9090, 80]
|
||||
/// udp = false
|
||||
///
|
||||
pub fn read(custom_config_path: Option<PathBuf>) -> Self {
|
||||
let mut content = String::new();
|
||||
@@ -303,7 +317,11 @@ pub fn default_config_path() -> PathBuf {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::{CommandFactory, Parser};
|
||||
use parameterized::parameterized;
|
||||
|
||||
use super::{Config, Opts, PortRange, ScanOrder, ScriptsRequired};
|
||||
|
||||
impl Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -317,13 +335,40 @@ mod tests {
|
||||
ulimit: None,
|
||||
command: Some(vec!["-A".to_owned()]),
|
||||
accessible: Some(true),
|
||||
resolver: None,
|
||||
scan_order: Some(ScanOrder::Random),
|
||||
scripts: None,
|
||||
exclude_ports: None,
|
||||
udp: Some(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
Opts::command().debug_assert();
|
||||
}
|
||||
|
||||
#[parameterized(input = {
|
||||
vec!["rustscan", "--addresses", "127.0.0.1"],
|
||||
vec!["rustscan", "--addresses", "127.0.0.1", "--", "-sCV"],
|
||||
vec!["rustscan", "--addresses", "127.0.0.1", "--", "-A"],
|
||||
vec!["rustscan", "-t", "1500", "-a", "127.0.0.1", "--", "-A", "-sC"],
|
||||
vec!["rustscan", "--addresses", "127.0.0.1", "--", "--script", r#""'(safe and vuln)'""#],
|
||||
}, command = {
|
||||
vec![],
|
||||
vec!["-sCV".to_owned()],
|
||||
vec!["-A".to_owned()],
|
||||
vec!["-A".to_owned(), "-sC".to_owned()],
|
||||
vec!["--script".to_owned(), "\"'(safe and vuln)'\"".to_owned()],
|
||||
})]
|
||||
fn parse_trailing_command(input: Vec<&str>, command: Vec<String>) {
|
||||
let opts = Opts::parse_from(input);
|
||||
|
||||
assert_eq!(vec!["127.0.0.1".to_owned()], opts.addresses);
|
||||
assert_eq!(command, opts.command);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opts_no_merge_when_config_is_ignored() {
|
||||
let mut opts = Opts::default();
|
||||
@@ -364,10 +409,12 @@ mod tests {
|
||||
end: 1_000,
|
||||
});
|
||||
config.ulimit = Some(1_000);
|
||||
config.resolver = Some("1.1.1.1".to_owned());
|
||||
|
||||
opts.merge_optional(&config);
|
||||
|
||||
assert_eq!(opts.range, config.range);
|
||||
assert_eq!(opts.ulimit, config.ulimit);
|
||||
assert_eq!(opts.resolver, config.resolver);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
//! strategy,
|
||||
//! true,
|
||||
//! vec![9000],
|
||||
//! false,
|
||||
//! );
|
||||
//!
|
||||
//! let scan_result = block_on(scanner.run());
|
||||
@@ -51,3 +52,7 @@ pub mod port_strategy;
|
||||
pub mod benchmark;
|
||||
|
||||
pub mod scripts;
|
||||
|
||||
pub mod address;
|
||||
|
||||
pub mod udp_packets;
|
||||
+11
-183
@@ -9,20 +9,14 @@ use rustscan::scanner::Scanner;
|
||||
use rustscan::scripts::{init_scripts, Script, ScriptFile};
|
||||
use rustscan::{detail, funny_opening, output, warning};
|
||||
|
||||
use cidr_utils::cidr::IpCidr;
|
||||
use colorful::{Color, Colorful};
|
||||
use futures::executor::block_on;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{prelude::*, BufReader};
|
||||
use std::net::{IpAddr, ToSocketAddrs};
|
||||
use std::path::Path;
|
||||
use std::net::IpAddr;
|
||||
use std::string::ToString;
|
||||
use std::time::Duration;
|
||||
use trust_dns_resolver::{
|
||||
config::{ResolverConfig, ResolverOpts},
|
||||
Resolver,
|
||||
};
|
||||
|
||||
use rustscan::address::parse_addresses;
|
||||
|
||||
extern crate colorful;
|
||||
extern crate dirs;
|
||||
@@ -89,6 +83,8 @@ fn main() {
|
||||
// Added by wasuaje - 01/26/2024:
|
||||
// exclude_ports is an exclusion port list
|
||||
//
|
||||
// Added by brendanglancy - 5/19/2024:
|
||||
// udp is an option to do a udp scan
|
||||
let scanner = Scanner::new(
|
||||
&ips,
|
||||
batch_size,
|
||||
@@ -98,6 +94,7 @@ fn main() {
|
||||
PortStrategy::pick(&opts.range, opts.ports, opts.scan_order),
|
||||
opts.accessible,
|
||||
opts.exclude_ports.unwrap_or_default(),
|
||||
opts.udp,
|
||||
);
|
||||
debug!("Scanner finished building: {:?}", scanner);
|
||||
|
||||
@@ -231,104 +228,6 @@ fn print_opening(opts: &Opts) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Goes through all possible IP inputs (files or via argparsing)
|
||||
/// Parses the string(s) into IPs
|
||||
fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<IpAddr> = Vec::new();
|
||||
let mut unresolved_addresses: Vec<&str> = Vec::new();
|
||||
let backup_resolver =
|
||||
Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap();
|
||||
|
||||
for address in &input.addresses {
|
||||
let parsed_ips = parse_address(address, &backup_resolver);
|
||||
if !parsed_ips.is_empty() {
|
||||
ips.extend(parsed_ips);
|
||||
} else {
|
||||
unresolved_addresses.push(address);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got to this point this can only be a file path or the wrong input.
|
||||
for file_path in unresolved_addresses {
|
||||
let file_path = Path::new(file_path);
|
||||
|
||||
if !file_path.is_file() {
|
||||
warning!(
|
||||
format!("Host {file_path:?} could not be resolved."),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(x) = read_ips_from_file(file_path, &backup_resolver) {
|
||||
ips.extend(x);
|
||||
} else {
|
||||
warning!(
|
||||
format!("Host {file_path:?} could not be resolved."),
|
||||
input.greppable,
|
||||
input.accessible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
/// Given a string, parse it as a host, IP address, or CIDR.
|
||||
/// This allows us to pass files as hosts or cidr or IPs easily
|
||||
/// Call this every time you have a possible IP_or_host
|
||||
fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
|
||||
IpCidr::from_str(address)
|
||||
.map(|cidr| cidr.iter().collect())
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
format!("{}:{}", &address, 80)
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.map(|mut iter| vec![iter.next().unwrap().ip()])
|
||||
})
|
||||
.unwrap_or_else(|| resolve_ips_from_host(address, resolver))
|
||||
}
|
||||
|
||||
/// Uses DNS to get the IPS associated with host
|
||||
fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr> {
|
||||
let mut ips: Vec<std::net::IpAddr> = Vec::new();
|
||||
|
||||
if let Ok(addrs) = source.to_socket_addrs() {
|
||||
for ip in addrs {
|
||||
ips.push(ip.ip());
|
||||
}
|
||||
} else if let Ok(addrs) = backup_resolver.lookup_ip(source) {
|
||||
ips.extend(addrs.iter());
|
||||
}
|
||||
|
||||
ips
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
/// Parses an input file of IPs and uses those
|
||||
fn read_ips_from_file(
|
||||
ips: &std::path::Path,
|
||||
backup_resolver: &Resolver,
|
||||
) -> Result<Vec<std::net::IpAddr>, std::io::Error> {
|
||||
let file = File::open(ips)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut ips: Vec<std::net::IpAddr> = Vec::new();
|
||||
|
||||
for address_line in reader.lines() {
|
||||
if let Ok(address) = address_line {
|
||||
ips.extend(parse_address(&address, backup_resolver));
|
||||
} else {
|
||||
debug!("Line in file is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ips)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn adjust_ulimit_size(opts: &Opts) -> u64 {
|
||||
use rlimit::Resource;
|
||||
@@ -398,8 +297,7 @@ fn infer_batch_size(opts: &Opts, ulimit: u64) -> u16 {
|
||||
mod tests {
|
||||
#[cfg(unix)]
|
||||
use super::{adjust_ulimit_size, infer_batch_size};
|
||||
use super::{parse_addresses, print_opening, Opts};
|
||||
use std::net::Ipv4Addr;
|
||||
use super::{print_opening, Opts};
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
@@ -442,13 +340,6 @@ mod tests {
|
||||
|
||||
assert!(batch_size == 2_000);
|
||||
}
|
||||
#[test]
|
||||
fn test_print_opening_no_panic() {
|
||||
let mut opts = Opts::default();
|
||||
opts.ulimit = Some(2_000);
|
||||
// print opening should not panic
|
||||
print_opening(&opts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
@@ -463,73 +354,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_addresses() {
|
||||
fn test_print_opening_no_panic() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(
|
||||
ips,
|
||||
[
|
||||
Ipv4Addr::new(127, 0, 0, 1),
|
||||
Ipv4Addr::new(192, 168, 0, 0),
|
||||
Ipv4Addr::new(192, 168, 0, 1),
|
||||
Ipv4Addr::new(192, 168, 0, 2),
|
||||
Ipv4Addr::new(192, 168, 0, 3)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_host_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["google.com".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correct_and_incorrect_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert_eq!(ips, [Ipv4Addr::new(127, 0, 0, 1),]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_incorrect_addresses() {
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
|
||||
assert!(ips.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn parse_hosts_file_and_incorrect_hosts() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_hosts_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/empty_hosts.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_naughty_host_file() {
|
||||
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
|
||||
let mut opts = Opts::default();
|
||||
opts.addresses = vec!["fixtures/naughty_string.txt".to_owned()];
|
||||
let ips = parse_addresses(&opts);
|
||||
assert_eq!(ips.len(), 0);
|
||||
opts.ulimit = Some(2_000);
|
||||
// print opening should not panic
|
||||
print_opening(&opts);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl RangeIterator {
|
||||
// Randomly choose a number within the range to be the first
|
||||
// and assign it as a pick.
|
||||
let mut rng = rand::thread_rng();
|
||||
let normalized_first_pick = rng.gen_range(0, normalized_end);
|
||||
let normalized_first_pick = rng.gen_range(0..normalized_end);
|
||||
|
||||
Self {
|
||||
active: true,
|
||||
@@ -84,13 +84,13 @@ fn pick_random_coprime(end: u32) -> u32 {
|
||||
let lower_range = range_boundary;
|
||||
let upper_range = end - range_boundary;
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut candidate = rng.gen_range(lower_range, upper_range);
|
||||
let mut candidate = rng.gen_range(lower_range..upper_range);
|
||||
|
||||
for _ in 0..10 {
|
||||
if end.gcd(candidate) == 1 {
|
||||
return candidate;
|
||||
}
|
||||
candidate = rng.gen_range(lower_range, upper_range);
|
||||
candidate = rng.gen_range(lower_range..upper_range);
|
||||
}
|
||||
|
||||
end - 1
|
||||
|
||||
+204
-11
@@ -1,13 +1,14 @@
|
||||
//! Core functionality for actual scanning behaviour.
|
||||
use crate::port_strategy::PortStrategy;
|
||||
use crate::udp_packets::udp_payload::cust_payload;
|
||||
use log::debug;
|
||||
|
||||
mod socket_iterator;
|
||||
use socket_iterator::SocketIterator;
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::TcpStream;
|
||||
use async_std::prelude::*;
|
||||
use async_std::{io, net::UdpSocket};
|
||||
use colored::Colorize;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::{
|
||||
@@ -36,6 +37,7 @@ pub struct Scanner {
|
||||
port_strategy: PortStrategy,
|
||||
accessible: bool,
|
||||
exclude_ports: Vec<u16>,
|
||||
udp: bool,
|
||||
}
|
||||
|
||||
// Allowing too many arguments for clippy.
|
||||
@@ -50,6 +52,7 @@ impl Scanner {
|
||||
port_strategy: PortStrategy,
|
||||
accessible: bool,
|
||||
exclude_ports: Vec<u16>,
|
||||
udp: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
batch_size,
|
||||
@@ -60,6 +63,7 @@ impl Scanner {
|
||||
ips: ips.iter().map(ToOwned::to_owned).collect(),
|
||||
accessible,
|
||||
exclude_ports,
|
||||
udp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,25 +134,32 @@ impl Scanner {
|
||||
///
|
||||
/// Note: `self` must contain `self.ip`.
|
||||
async fn scan_socket(&self, socket: SocketAddr) -> io::Result<SocketAddr> {
|
||||
let tries = self.tries.get();
|
||||
if self.udp {
|
||||
let payload = cust_payload(socket.port());
|
||||
|
||||
let tries = self.tries.get();
|
||||
for _ in 1..=tries {
|
||||
match self.udp_scan(socket, &payload, self.timeout).await {
|
||||
Ok(true) => return Ok(socket),
|
||||
Ok(false) => continue,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
return Ok(socket);
|
||||
}
|
||||
|
||||
let tries = self.tries.get();
|
||||
for nr_try in 1..=tries {
|
||||
match self.connect(socket).await {
|
||||
Ok(x) => {
|
||||
Ok(tcp_stream) => {
|
||||
debug!(
|
||||
"Connection was successful, shutting down stream {}",
|
||||
&socket
|
||||
);
|
||||
if let Err(e) = x.shutdown(Shutdown::Both) {
|
||||
if let Err(e) = tcp_stream.shutdown(Shutdown::Both) {
|
||||
debug!("Shutdown stream error {}", &e);
|
||||
}
|
||||
if !self.greppable {
|
||||
if self.accessible {
|
||||
println!("Open {socket}");
|
||||
} else {
|
||||
println!("Open {}", socket.to_string().purple());
|
||||
}
|
||||
}
|
||||
self.fmt_ports(socket);
|
||||
|
||||
debug!("Return Ok after {} tries", nr_try);
|
||||
return Ok(socket);
|
||||
@@ -191,6 +202,90 @@ impl Scanner {
|
||||
.await?;
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
/// Binds to a UDP socket so we can send and recieve packets
|
||||
/// # Example
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
/// let port: u16 = 80;
|
||||
/// // ip is an IpAddr type
|
||||
/// let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
|
||||
/// let socket = SocketAddr::new(ip, port);
|
||||
/// scanner.udp_bind(socket);
|
||||
/// // returns Result which is either Ok(stream) for port is open, or Err for port is closed.
|
||||
/// // Timeout occurs after self.timeout seconds
|
||||
/// ```
|
||||
///
|
||||
async fn udp_bind(&self, socket: SocketAddr) -> io::Result<UdpSocket> {
|
||||
let local_addr = match socket {
|
||||
SocketAddr::V4(_) => "0.0.0.0:0".parse::<SocketAddr>().unwrap(),
|
||||
SocketAddr::V6(_) => "[::]:0".parse::<SocketAddr>().unwrap(),
|
||||
};
|
||||
|
||||
UdpSocket::bind(local_addr).await
|
||||
}
|
||||
|
||||
/// Performs a UDP scan on the specified socket with a payload and wait duration
|
||||
/// # Example
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
/// # use std::time::Duration;
|
||||
/// let port: u16 = 123;
|
||||
/// // ip is an IpAddr type
|
||||
/// let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
|
||||
/// let socket = SocketAddr::new(ip, port);
|
||||
/// let payload = vec![0, 1, 2, 3];
|
||||
/// let wait = Duration::from_secs(1);
|
||||
/// let result = scanner.udp_scan(socket, payload, wait).await;
|
||||
/// // returns Result which is either Ok(true) if response received, or Ok(false) if timed out.
|
||||
/// // Err is returned for other I/O errors.
|
||||
async fn udp_scan(
|
||||
&self,
|
||||
socket: SocketAddr,
|
||||
payload: &[u8],
|
||||
wait: Duration,
|
||||
) -> io::Result<bool> {
|
||||
match self.udp_bind(socket).await {
|
||||
Ok(udp_socket) => {
|
||||
let mut buf = [0u8; 1024];
|
||||
|
||||
udp_socket.connect(socket).await?;
|
||||
udp_socket.send(payload).await?;
|
||||
|
||||
match io::timeout(wait, udp_socket.recv(&mut buf)).await {
|
||||
Ok(size) => {
|
||||
debug!("Received {} bytes", size);
|
||||
self.fmt_ports(socket);
|
||||
Ok(true)
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::TimedOut {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Err E binding sock {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats and prints the port status
|
||||
fn fmt_ports(&self, socket: SocketAddr) {
|
||||
if !self.greppable {
|
||||
if self.accessible {
|
||||
println!("Open {socket}");
|
||||
} else {
|
||||
println!("Open {}", socket.to_string().purple());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -218,6 +313,7 @@ mod tests {
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
false,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
@@ -241,6 +337,7 @@ mod tests {
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
false,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
@@ -263,6 +360,7 @@ mod tests {
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
false,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
@@ -284,6 +382,7 @@ mod tests {
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
false,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
@@ -308,6 +407,100 @@ mod tests {
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
false,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udp_scan_runs() {
|
||||
// Makes sure the program still runs and doesn't panic
|
||||
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
|
||||
let range = PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
#[test]
|
||||
fn udp_ipv6_runs() {
|
||||
// Makes sure the program still runs and doesn't panic
|
||||
let addrs = vec!["::1".parse::<IpAddr>().unwrap()];
|
||||
let range = PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
#[test]
|
||||
fn udp_quad_zero_scanner_runs() {
|
||||
let addrs = vec!["0.0.0.0".parse::<IpAddr>().unwrap()];
|
||||
let range = PortRange {
|
||||
start: 1,
|
||||
end: 1_000,
|
||||
};
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
#[test]
|
||||
fn udp_google_dns_runs() {
|
||||
let addrs = vec!["8.8.8.8".parse::<IpAddr>().unwrap()];
|
||||
let range = PortRange {
|
||||
start: 100,
|
||||
end: 150,
|
||||
};
|
||||
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
|
||||
let scanner = Scanner::new(
|
||||
&addrs,
|
||||
10,
|
||||
Duration::from_millis(100),
|
||||
1,
|
||||
true,
|
||||
strategy,
|
||||
true,
|
||||
vec![9000],
|
||||
true,
|
||||
);
|
||||
block_on(scanner.run());
|
||||
assert_eq!(1, 1);
|
||||
|
||||
@@ -89,10 +89,14 @@ macro_rules! funny_opening {
|
||||
"TCP handshake? More like a friendly high-five!",
|
||||
"Scanning ports: The virtual equivalent of knocking on doors.",
|
||||
"RustScan: Making sure 'closed' isn't just a state of mind.",
|
||||
"RustScan: allowing you to send UDP packets into the void 1200x faster than NMAP",
|
||||
"Port scanning: Because every port has a story to tell.",
|
||||
"I scanned ports so fast, even my computer was surprised.",
|
||||
"Scanning ports faster than you can say 'SYN ACK'",
|
||||
"RustScan: Where '404 Not Found' meets '200 OK'.",
|
||||
"RustScan: Exploring the digital landscape, one IP at a time.",
|
||||
"TreadStone was here 🚀",
|
||||
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
|
||||
];
|
||||
let random_quote = quotes.choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
pub mod udp_payload {
|
||||
// const MAX_PAYLOAD_IPV4: usize = 65507; // 65,535 - 20 (IP header) - 8 (UDP header)
|
||||
// const MAX_PAYLOAD_IPV6: usize = 65527; // 65,535 - 40 (IPv6 header) - 8 (UDP header)
|
||||
|
||||
/// From [RFC 768](https://www.rfc-editor.org/rfc/rfc768.txt)
|
||||
/// We send blank UDP packets, unless the port is determined to be special.
|
||||
///
|
||||
/// ```text
|
||||
/// HEADER Handled by the OS
|
||||
/// 0 1 2 3
|
||||
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/// | Source Port | Destination Port |
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/// | Length | Checksum |
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/// We append the data, based on the port the the header
|
||||
/// DATA
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/// | Data (based on port) |
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/// ```
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - **Source Port**: The source port number.
|
||||
/// - **Destination Port**: The destination port number.
|
||||
/// - **Length**: The length of the UDP header and data.
|
||||
/// - **Checksum**: The checksum for error-checking.
|
||||
/// - **Data**: The payload data, which can vary based on the port.
|
||||
#[allow(clippy::match_single_binding)]
|
||||
pub fn cust_payload(dst_prt: u16) -> Vec<u8> {
|
||||
match dst_prt {
|
||||
// 53 => craft_dns_query_packet(),
|
||||
// 67 => craft_dhcpc_packet(),
|
||||
// 123 => craft_ntp_packet(),
|
||||
// 161 => craft_snmp_packet(),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn craft_snmptrap_packet() -> Vec<u16> {
|
||||
let version = vec![0x30, 0x82]; // SNMP version
|
||||
let community_string = vec![0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63]; // Community string "public"
|
||||
let pdu_type = vec![0xa4, 0x82]; // PDU type for SNMP Trap
|
||||
let enterprise_oid = vec![0x06, 0x09, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00]; // Enterprise OID
|
||||
|
||||
// Combine the components into a single SNMP Trap packet
|
||||
[version, community_string, pdu_type, enterprise_oid].concat()
|
||||
}
|
||||
|
||||
/// From [RFC 1592](https://www.rfc-editor.org/rfc/rfc1592.txt)
|
||||
/// Crafts an SNMP packet based on the structure from RFC 1592.
|
||||
///
|
||||
/// RFC 1592 defines the structure of an SNMP packet as follows
|
||||
/// - **ASN.1 header**: 0x30
|
||||
/// - **PDU length**: 37 + length of community name
|
||||
/// - **SNMP version**: 0x02 0x01 0x00 (integer, length=1, value=0)
|
||||
/// - **Community name**: A string of varying length
|
||||
/// - **SNMP GET request**: 0xa0 0x1c (request type=0xa0, length=0x1c)
|
||||
/// - **SNMP request ID**: 0x02 0x01 0x01 (integer, length=1, ID=1)
|
||||
/// - **SNMP error status**: 0x02 0x01 0x00 (integer, length=1, error=0)
|
||||
/// - **SNMP index**: 0x02 0x01 0x00 (integer, length=1, index=0)
|
||||
/// - **VarBind list**: 0x30 0x11 (length=0x11)
|
||||
/// - **VarBind**: 0x30 0x0f (length=0x0f)
|
||||
/// - **Object ID**: 0x06 0x0b (length=0x0b)
|
||||
/// - **Object instance**: 1.3.6.1.4.1.2.2.1.1.1.0
|
||||
/// - **Null value**: 0x05 0x00 (length=0)
|
||||
///
|
||||
/// The PDU length formula:
|
||||
/// ```text
|
||||
/// PDU_length = length of version field and string tag (4 bytes)
|
||||
/// + length of community length field (1 byte)
|
||||
/// + length of community name (depends...)
|
||||
/// + length of SNMP GET request (32 bytes)
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<u8>` representing the crafted SNMP packet.
|
||||
pub fn craft_snmp_packet() -> Vec<u8> {
|
||||
// TODO all versions of snmp
|
||||
let v1 = vec![0x30, 0x29, 0x02, 0x01, 0x00];
|
||||
|
||||
let community_string = vec![0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63];
|
||||
let pdu_type = vec![0xa0, 0x1c];
|
||||
|
||||
let request_id = vec![0x02, 0x04, 0x7a, 0x69, 0x67, 0x71];
|
||||
let error_status = vec![0x02, 0x01, 0x00];
|
||||
let error_index = vec![0x02, 0x01, 0x00];
|
||||
let variable_bindings = vec![
|
||||
0x30, 0x0e, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00,
|
||||
0x05, 0x00,
|
||||
];
|
||||
|
||||
let _basic_snmp = [
|
||||
0x30, 0x26, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0,
|
||||
0x19, 0x02, 0x04, 0x71, 0x64, 0xfe, 0xf1, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30,
|
||||
0x0b, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x05, 0x00,
|
||||
];
|
||||
|
||||
[
|
||||
v1,
|
||||
community_string,
|
||||
pdu_type,
|
||||
request_id,
|
||||
error_status,
|
||||
error_index,
|
||||
variable_bindings,
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
/// Constructs a DHCP packet based on the RFC 1541 Dynamic Host Configuration Protocol specification.
|
||||
///
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<u8>` representing the DHCP packet.
|
||||
///
|
||||
/// This function crafts a DHCP Discover packet with the following fields:
|
||||
/// - **op**: BOOTREQUEST (1)
|
||||
/// - **htype**: Ethernet (1)
|
||||
/// - **hlen**: 6
|
||||
/// - **hops**: 0
|
||||
/// - **xid**: Random transaction ID
|
||||
/// - **secs**: 0
|
||||
/// - **flags**: 0x8000 (Broadcast)
|
||||
/// - **ciaddr**: 0.0.0.0
|
||||
/// - **yiaddr**: 0.0.0.0
|
||||
/// - **siaddr**: 0.0.0.0
|
||||
/// - **giaddr**: 0.0.0.0
|
||||
/// - **chaddr**: 00:00:00:00:00:00 (Client MAC address)
|
||||
/// - **sname**: Not given
|
||||
/// - **file**: Not given
|
||||
/// - **options**: DHCP options
|
||||
pub fn craft_dhcpc_packet() -> Vec<u8> {
|
||||
let mut packet = Vec::with_capacity(240);
|
||||
|
||||
// BOOTP message type: Boot Request (1)
|
||||
packet.push(0x01);
|
||||
|
||||
// Hardware type: Ethernet (10Mb) (1)
|
||||
packet.push(0x01);
|
||||
|
||||
// Hardware address length: 6
|
||||
packet.push(0x06);
|
||||
|
||||
// Hops: 0
|
||||
packet.push(0x00);
|
||||
|
||||
// Transaction ID: random (0x3903F326)
|
||||
packet.extend(&[0x39, 0x03, 0xF3, 0x26]);
|
||||
|
||||
// Seconds elapsed: 0
|
||||
packet.extend(&[0x00, 0x00]);
|
||||
|
||||
// Bootp flags: 0x8000 (Broadcast) (0x8000)
|
||||
packet.extend(&[0x80, 0x00]);
|
||||
|
||||
// Client IP address: 0.0.0.0
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Your (client) IP address: 0.0.0.0
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Next server IP address: 0.0.0.0
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Relay agent IP address: 0.0.0.0
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Client MAC address: 00:00:00:00:00:00
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Client hardware address padding: 00000000000000000000
|
||||
packet.extend(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
// Server host name not given
|
||||
packet.extend(&[0x00; 64]);
|
||||
|
||||
// Boot file name not given
|
||||
packet.extend(&[0x00; 128]);
|
||||
|
||||
// Magic cookie: DHCP
|
||||
packet.extend(&[0x63, 0x82, 0x53, 0x63]);
|
||||
|
||||
// DHCP Message Type: DHCP Discover (1)
|
||||
packet.extend(&[0x35, 0x01, 0x01]);
|
||||
|
||||
// DHCP Option: Parameter Request List
|
||||
packet.extend(&[0x37, 0x04, 0x01, 0x03, 0x06, 0x2a]);
|
||||
|
||||
// End Option
|
||||
packet.push(0xff);
|
||||
|
||||
packet
|
||||
}
|
||||
|
||||
/// Constructs a DNS query packet.
|
||||
///
|
||||
/// The structure of the DNS packet is as follows:
|
||||
///
|
||||
/// ```text
|
||||
/// +---------------------+
|
||||
/// | Header |
|
||||
/// +---------------------+
|
||||
/// | Question | the question for the name server
|
||||
/// +---------------------+
|
||||
/// | Answer | RRs answering the question
|
||||
/// +---------------------+
|
||||
/// | Authority | RRs pointing toward an authority
|
||||
/// +---------------------+
|
||||
/// | Additional | RRs holding additional information
|
||||
/// +---------------------+
|
||||
/// ```
|
||||
///
|
||||
/// The function builds a DNS query packet with the following fields:
|
||||
/// - **Header**: Contains the transaction ID and flags.
|
||||
/// - **Question**: Specifies the query (e.g., the domain name to look up).
|
||||
/// - **Answer**: Resource records that answer the question (empty for a query).
|
||||
/// - **Authority**: Resource records that point toward an authority (empty for a query).
|
||||
/// - **Additional**: Resource records holding additional information (empty for a query).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<u8>` representing the DNS query packet.
|
||||
pub fn craft_dns_query_packet() -> Vec<u8> {
|
||||
let mut packet = Vec::new();
|
||||
|
||||
// Transaction ID: Random
|
||||
packet.extend(&[0x12, 0x34]);
|
||||
|
||||
// Flags: Standard query
|
||||
packet.extend(&[0x01, 0x00]);
|
||||
// Header ^
|
||||
|
||||
// Questions: 1
|
||||
packet.extend(&[0x00, 0x01]);
|
||||
|
||||
// Answer RRs: 0
|
||||
packet.extend(&[0x00, 0x00]);
|
||||
|
||||
// Authority RRs: 0
|
||||
packet.extend(&[0x00, 0x00]);
|
||||
|
||||
// Additional RRs: 0
|
||||
packet.extend(&[0x00, 0x00]);
|
||||
|
||||
// Query: www.google.com
|
||||
let query_name = "www.google.com";
|
||||
for part in query_name.split('.') {
|
||||
packet.push(part.len() as u8);
|
||||
packet.extend(part.as_bytes());
|
||||
}
|
||||
packet.push(0); // End of query name
|
||||
|
||||
// Query Type: A (host address)
|
||||
packet.extend(&[0x00, 0x01]);
|
||||
|
||||
// Query Class: IN (internet)
|
||||
packet.extend(&[0x00, 0x01]);
|
||||
|
||||
packet
|
||||
}
|
||||
|
||||
/// Creates the simplest NTP packet possible
|
||||
/// 0x1b followed by 47 0s, (Version = 3, Mode = 3)
|
||||
pub fn craft_ntp_packet() -> Vec<u8> {
|
||||
let mut packet = vec![0u8; 48];
|
||||
packet[0] = 0x1b;
|
||||
packet
|
||||
}
|
||||
|
||||
// TODO add more packets for the top 1000 ports
|
||||
// `sort -r -k3 /usr/share/nmap/nmap-services | grep udp | head -n 1000`
|
||||
// There has to be a better way to structure this, maybe internal crate for packet payload
|
||||
// creation
|
||||
}
|
||||
@@ -106,4 +106,58 @@ mod timelimits {
|
||||
super::Duration::from_secs(26),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn udp_scan_localhost() {
|
||||
let timeout = super::Duration::from_secs(25);
|
||||
super::run_rustscan_with_timeout(&["--greppable", "127.0.0.1", "--udp"], timeout);
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn udp_scan_google_com() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--udp",
|
||||
"--greppable",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"google.com",
|
||||
],
|
||||
super::Duration::from_secs(28),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn udp_scan_example_com() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--udp",
|
||||
"--greppable",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"example.com",
|
||||
],
|
||||
super::Duration::from_secs(28),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn udp_scan_rustscan_cmnatic_co_uk() {
|
||||
super::run_rustscan_with_timeout(
|
||||
&[
|
||||
"--udp",
|
||||
"--greppable",
|
||||
"-u",
|
||||
"5000",
|
||||
"-b",
|
||||
"2500",
|
||||
"rustscan.cmnatic.co.uk",
|
||||
],
|
||||
super::Duration::from_secs(26),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user