Compare commits

..

No commits in common. "master" and "v0.2.0" have entirely different histories.

67 changed files with 1197 additions and 10957 deletions

View file

@ -1,22 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily

View file

@ -1,120 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - 2024 The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
name: Build masscanned
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Get Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Install packages for build
run: sudo apt-get -q update && sudo apt-get -qy install libpcap-dev
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
- name: Create build archive
run: tar cf masscanned.tar target/debug/masscanned
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: masscanned.tar
path: masscanned.tar
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Get binary
uses: actions/download-artifact@v4
with:
name: masscanned.tar
- name: Extract build archive
run: tar xf masscanned.tar
- name: Use Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: sudo pip install -U -r test/requirements.txt
- name: Install linting tools
run: sudo pip install -U flake8 black
- name: Install packages for tests
run: sudo apt-get -q update && sudo apt-get -qy install nmap rpcbind smbclient
- name: Run black
run: black -t py36 --check test/test_masscanned.py test/src/
- name: Run flake8
run: flake8 --ignore=E266,E501,W503 test/test_masscanned.py test/src/
- name: Run tests
run: sudo python test/test_masscanned.py
- name: Display logs
run: echo STDOUT; cat test/res/masscanned.stdout && echo && echo STDERR && cat test/res/masscanned.stderr
if: failure()
docker:
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Build archive
run: git archive --format=tar --prefix=masscanned-master/ HEAD -o docker/masscanned.tar
- name: Build image
uses: docker/build-push-action@v5
with:
push: false
context: docker/
file: docker/Dockerfile-local

3
.gitignore vendored
View file

@ -1,11 +1,10 @@
/target/ /target/
Cargo.lock
**/*.rs.bk **/*.rs.bk
# Vim temporary files # Vim temporary files
*.swp *.swp
*.swo *.swo
# Emacs temporary files
*~
*__pycache__* *__pycache__*
test/res/* test/res/*

1139
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,23 +21,22 @@ authors = ["_Frky <3105926+Frky@users.noreply.github.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
bitflags = "2.9.4" pcap = "0.7.0"
byteorder = "1.5.0" pcap-file = "1.1.1"
chrono = "0.4.42" pnet = "0.26.0"
clap = "4.5.47" # pnet = { path = "libpnet" }
clap = "2.33.3"
log = "0.4.11"
stderrlog = "0.5.0"
itertools = "0.9.0"
rand = "0.7.3"
dns-parser = "0.8.0" dns-parser = "0.8.0"
flate2 = "1.1" netdevice = "0.1.1"
itertools = "0.14.0" bitflags = "1.2.1"
lazy_static = "1.5.0" lazy_static = "1.4.0"
log = "0.4.28" siphasher = "0.3"
pcap = "2.3.0" chrono = "0.4.19"
pcap-file = "2.0.0" byteorder = "1.4.3"
pnet = { version = "0.33.0", features = ["std"] }
rand = "0.9.2"
siphasher = "1.0"
stderrlog = "0.6.0"
strum = "0.27.2"
strum_macros = "0.27.2"
[[bin]] [[bin]]
name = "masscanned" name = "masscanned"

388
README.md
View file

@ -1,10 +1,8 @@
[![Build masscanned](https://github.com/ivre/masscanned/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/ivre/masscanned/actions/workflows/test.yml?branch=master)
# Masscanned # Masscanned
**Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan)) **Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan))
is a network responder. Its purpose is to provide generic answers to as many protocols as possible, is a network responder. Its purpose is to provide generic answers to as many protocols as possible,
and with as few assumptions as possible on the client's intentions. and with as few asumptions as possible on the client's intentions.
> *Let them talk first.* > *Let them talk first.*
@ -19,32 +17,22 @@ For example, when it receives network packets:
* **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port, * **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port,
* **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page. * **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page.
![demo](doc/img/demo.gif) ![demo](doc/demo.gif)
## Overview
**Masscanned** currently supports most common protocols at layers 2-3-4, and a few application **Masscanned** currently supports most common protocols at layers 2-3-4, and a few application
protocols. protocols:
### Network protocols * `Eth::ARP::REQ`,
* `Eth::IPv{4,6}::ICMP::ECHO-REQ`,
* ARP (answers to ARP requests) * `Eth::IPv{4,6}::TCP::SYN` (all ports),
* ICMP (answers to ping) * `Eth::IPv{4,6}::TCP::PSHACK` (all ports),
* ICMPv6 (answers to ND NS) * `Eth::IPv6::ICMP::ND_NS`.
* TCP (answers to SYN and PUSH) * `Eth::IPv{4,6}::{TCP,UDP}::HTTP` (all HTTP verbs),
* `Eth::IPv{4,6}::{TCP,UDP}::STUN`,
### Application protocols * `Eth::IPv{4,6}::{TCP,UDP}::SSH` (Server Protocol only).
* HTTP (answers to all verbs)
* SSH (answers to the client banner)
* STUN (answers to binding requests)
* SMB
* DNS (answers to IN/A queries)
## Try it locally ## Try it locally
### On your host
1. Build **masscanned** 1. Build **masscanned**
``` ```
$ cargo build $ cargo build
@ -76,77 +64,13 @@ $ cargo build
... ...
``` ```
### In a Docker ## Protocols
1. Install docker:
```
# apt install docker.io
```
1. Build docker container:
```
$ cd masscanned/docker && docker build -t masscanned:test .
```
1. Run docker container:
```
$ docker run --cap-add=NET_ADMIN masscanned:test
```
1. Send packets to **masscanned**
```
# arping 172.17.0.2
# ping 172.17.0.2
# nc -n -v 172.17.0.2 80
# nc -n -v -u 172.17.0.2 80
...
```
## Use it
A good use of **masscanned** is to deploy it on a VPS with one or more public IP addresses.
To use the results, the best way is to capture all network traffic on the interface **masscanned** is listening to/responding on.
The pcaps can then be analyzed using [zeek](https://zeek.org/) and the output files can typically be pushed in an instance of **IVRE**.
A documentation on how to deploy an instance of **masscanned** on a VPS is coming (see [Issue #2](https://github.com/ivre/masscanned/issues/2)).
### Supported options
```
Network answering machine for various network protocols (L2-L3-L4 + applications)
Usage: masscanned [OPTIONS] --iface <iface>
Options:
-i, --iface <iface>
the interface to use for receiving/sending packets
-m, --mac-addr <mac>
MAC address to use in the response packets
--self-ip-file <selfipfile>
File with the list of IP addresses handled by masscanned
--self-ip-list <selfiplist>
Inline list of IP addresses handled by masscanned, comma-separated
--remote-ip-deny-file <remoteipdenyfile>
File with the list of IP addresses from which masscanned will ignore packets
--remote-ip-deny-list <remoteipdenylist>
Inline list of IP addresses from which masscanned will ignore packets
-v...
Increase message verbosity
-q, --quiet
Quiet mode: do not output anything on stdout
--format <format>
Format in which to output logs [default: console] [possible values: console, logfmt]
-h, --help
Print help information
-V, --version
Print version information
```
## Supported protocols - details
### Layer 2 ### Layer 2
#### ARP #### ARP
`masscanned` answers to `ARP` requests, for requests that target an `IPv4` address `masscanned` anwsers to `ARP` requests, for requests that target an `IPv4` address
that is handled by `masscanned` (*i.e.*, an address that is in the that is handled by `masscanned` (*i.e.*, an address that is in the
IP address file given with option `-f`). IP address file given with option `-f`).
@ -188,7 +112,7 @@ An additionnal requirement is that the next layer protocol is supported - see be
#### IPv4 #### IPv4
The following L3+/4 protocols are supported for an `IPv4` packet: The following L4 protocols are suppported for an `IPv4` packet:
* `ICMPv4` * `ICMPv4`
* `UDP` * `UDP`
@ -198,7 +122,7 @@ If the next layer protocol is not one of them, the packet is dropped.
#### IPv6 #### IPv6
The following L3+/4 protocols are supported for an `IPv6` packet: The following L4 protocols are suppported for an `IPv6` packet:
* `ICMPv6` * `ICMPv6`
* `UDP` * `UDP`
@ -224,7 +148,7 @@ code `0` and the same payload as the incoming packet, as specified by [RFC 792](
* the `ICMP` type is `NeighborSol` (`135`) **and**: * the `ICMP` type is `NeighborSol` (`135`) **and**:
* no IP (v4 or v6) was speficied for `masscanned` * no IP (v4 or v6) was speficied for `masscanned`
* **or** the target address of the Neighbor Solicitation is one of `masscanned` * **or** the target address of the Neighbor Solicitation is one of `masccanned`
*In that case, the answer is a `Neighbor Advertisement` (`136`) packet with `masscanned` `MAC` address* *In that case, the answer is a `Neighbor Advertisement` (`136`) packet with `masscanned` `MAC` address*
@ -242,68 +166,19 @@ code `0` and the same payload as the incoming packet, as specified by [RFC 792](
a supported protocol (Layer 5/6/7) has been detected, a supported protocol (Layer 5/6/7) has been detected,
* if the received packet has flag `ACK`, it is ignored, * if the received packet has flag `ACK`, it is ignored,
* if the received packet has flag `RST` or `FIN-ACK`, it is ignored, * if the received packet has flag `RST` or `FIN-ACK`, it is ignored,
* if the received packet has flag `SYN`, then `masscanned` tries to imitate the behaviour * if the received packet has flag `SYN`, then `masscanned` answers with a `SYN-ACK` packet, setting a **SYNACK-cookie** in the sequence number.
of a standard Linux stack - which is:
* if there are additional flags that are not among `PSH`, `URG`, `CWR`, `ECE`, then the `SYN` is ignored,
* if the flags `CWR` and`ECE` are simultaneously set, then the `SYN` is ignored,
* in any other case, `masscanned` answers with a `SYN-ACK` packet, setting a **SYNACK-cookie** in the sequence number.
#### UDP #### UDP
`masscanned` answers to an `UDP` packet if and only if the upper-layer protocol `masscanned` answers to an `UDP` packet if and only if the upper-layer protocol
is handled and provides an answer. is handled and provides an answer.
### Application protocols ### Protocols
#### HTTP #### HTTP
`masscanned` answers to any `HTTP` request (any **valid** verb) with a `401 Authorization Required`.
Note that `HTTP` requests with an invalid verb will not be answered.
Example:
```
$ curl -X GET 10.11.10.129
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
$ curl -X OPTIONS 10.11.10.129
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
$ curl -X HEAD 10.11.10.129
Warning: Setting custom HTTP method to HEAD with -X/--request may not work the
Warning: way you want. Consider using -I/--head instead.
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
$ curl -X XXX 10.11.10.129
[timeout]
```
#### STUN #### STUN
Example:
```
$ stun 10.11.10.129
STUN client version 0.97
Primary: Open
Return value is 0x000001
```
#### SSH #### SSH
`masscanned` answers to `SSH` `Client: Protocol` messages with the following `Server: Protocol` message: `masscanned` answers to `SSH` `Client: Protocol` messages with the following `Server: Protocol` message:
@ -312,57 +187,6 @@ Return value is 0x000001
SSH-2.0-1\r\n SSH-2.0-1\r\n
``` ```
#### SMB
`masscanned` answers to `Negotiate Protocol Request` packets in order for the
client to send a `NTLMSSP_NEGOTIATE`, to which `masscanned` answers with a challenge.
Example:
```
##$ smbclient -U user \\\\10.11.10.129\\shared
Enter WORKGROUP\user's password:
```
#### DNS
`masscanned` answers to `DNS` queries of class `IN` and type `A` (for now).
The answer it provides always contains the IP address the query was sent to.
Example:
```
$ host -t A masscan.ned 10.11.10.129
Using domain server:
Name: 10.11.10.129
Address: 10.11.10.129#53
Aliases:
masscan.ned has address 10.11.10.129
$ host -t A masscan.ned 10.11.10.130
Using domain server:
Name: 10.11.10.130
Address: 10.11.10.130#53
Aliases:
masscan.ned has address 10.11.10.130
$ host -t A masscan.ned 10.11.10.131
Using domain server:
Name: 10.11.10.131
Address: 10.11.10.131#53
Aliases:
masscan.ned has address 10.11.10.131
$ host -t A masscan.ned 10.11.10.132
Using domain server:
Name: 10.11.10.132
Address: 10.11.10.132#53
Aliases:
masscan.ned has address 10.11.10.132
```
## Internals ## Internals
### Tests ### Tests
@ -372,118 +196,68 @@ masscan.ned has address 10.11.10.132
``` ```
$ cargo test $ cargo test
Compiling masscanned v0.2.0 (/zdata/workdir/masscanned) Compiling masscanned v0.2.0 (/zdata/workdir/masscanned)
Finished test [unoptimized + debuginfo] target(s) in 3.83s Finished test [unoptimized + debuginfo] target(s) in 2.34s
Running unittests (target/debug/deps/masscanned-f9292f8600038978) Running target/debug/deps/masscanned-b86211a090e50323
running 92 tests running 36 tests
test client::client_info::tests::test_client_info_eq ... ok test client::client_info::tests::test_client_info_eq ... ok
test layer_2::arp::tests::test_arp_reply ... ok test layer_2::arp::tests::test_arp_reply ... ok
test layer_2::tests::test_eth_empty ... ok
test layer_2::tests::test_eth_reply ... ok
test layer_3::ipv4::tests::test_ipv4_reply ... ok test layer_3::ipv4::tests::test_ipv4_reply ... ok
test layer_3::ipv4::tests::test_ipv4_empty ... ok
test layer_3::ipv6::tests::test_ipv6_empty ... ok
test layer_3::ipv6::tests::test_ipv6_reply ... ok test layer_3::ipv6::tests::test_ipv6_reply ... ok
test layer_4::icmpv4::tests::test_icmpv4_reply ... ok
test layer_4::icmpv6::tests::test_icmpv6_reply ... ok test layer_4::icmpv6::tests::test_icmpv6_reply ... ok
test layer_2::tests::test_eth_reply ... ok
test layer_4::icmpv6::tests::test_nd_na_reply ... ok test layer_4::icmpv6::tests::test_nd_na_reply ... ok
test layer_4::tcp::tests::test_synack_cookie_ipv6 ... ok
test layer_4::tcp::tests::test_tcp_fin_ack_wrap ... ok
test proto::dns::cst::tests::class_parse ... ok
test layer_4::tcp::tests::test_tcp_fin_ack ... ok
test layer_4::tcp::tests::test_synack_cookie_ipv4 ... ok test layer_4::tcp::tests::test_synack_cookie_ipv4 ... ok
test proto::dns::cst::tests::type_parse ... ok test layer_4::icmpv4::tests::test_icmpv4_reply ... ok
test proto::dns::header::tests::parse_byte_by_byte ... ok test layer_4::tcp::tests::test_synack_cookie_ipv6 ... ok
test proto::dns::header::tests::repl_id ... ok test proto::http::test_http_request_field ... ok
test proto::dns::header::tests::repl_opcode ... ok test proto::http::test_http_request_no_field ... ok
test proto::dns::header::tests::repl_ancount ... ok test proto::http::test_http_request_line ... ok
test proto::dns::header::tests::repl_rd ... ok test proto::http::test_http_verb ... ok
test proto::dns::query::tests::parse_in_a_all ... ok
test proto::dns::header::tests::parse_all ... ok
test proto::dns::query::tests::repl ... ok
test proto::dns::query::tests::reply_in_a ... ok
test proto::dns::rr::tests::parse_all ... ok
test proto::dns::rr::tests::parse_byte_by_byte ... ok
test proto::dns::query::tests::parse_in_a_byte_by_byte ... ok
test proto::dns::tests::parse_qd_all ... ok
test proto::dns::tests::parse_qd_byte_by_byte ... ok
test proto::dns::rr::tests::build ... ok
test proto::dns::tests::parse_qd_rr_all ... ok
test proto::dns::tests::parse_qr_rr_byte_by_byte ... ok
test proto::dns::tests::parse_rr_byte_by_byte ... ok
test proto::dns::tests::parse_rr_all ... ok
test proto::dns::tests::reply_in_a ... ok
test proto::http::tests::test_http_request_line ... ok
test proto::http::tests::test_http_request_no_field ... ok
test proto::http::tests::test_http_request_field ... ok
test proto::http::tests::test_http_verb ... ok
test proto::rpc::tests::test_probe_nmap ... ok
test proto::rpc::tests::test_probe_nmap_split1 ... ok
test proto::rpc::tests::test_probe_portmap_v4_dump ... ok
test proto::rpc::tests::test_probe_nmap_split2 ... ok
test proto::rpc::tests::test_probe_nmap_udp ... ok
test proto::smb::tests::test_smb1_session_setup_request_parse ... ok
test proto::smb::tests::test_smb1_protocol_nego_parsing ... ok
test proto::smb::tests::test_smb1_protocol_nego_reply ... ok
test proto::smb::tests::test_smb1_session_setup_request_reply ... ok
test proto::smb::tests::test_smb2_protocol_nego_parsing ... ok
test proto::smb::tests::test_smb2_protocol_nego_reply ... ok
test proto::smb::tests::test_smb2_session_setup_request_reply ... ok
test proto::smb::tests::test_smb2_session_setup_request_parse ... ok
test proto::ssh::tests::ssh_1_banner_cr ... ok
test proto::ssh::tests::ssh_1_banner_crlf ... ok
test proto::ssh::tests::ssh_1_banner_lf ... ok
test proto::ssh::tests::ssh_1_banner_space ... ok
test proto::ssh::tests::ssh_2_banner_cr ... ok
test proto::ssh::tests::ssh_1_banner_parse ... ok
test proto::ssh::tests::ssh_2_banner_parse ... ok
test proto::ssh::tests::ssh_2_banner_lf ... ok
test proto::ssh::tests::ssh_2_banner_crlf ... ok
test proto::stun::tests::test_change_request_port_overflow ... ok
test proto::stun::tests::test_proto_stun_ipv4 ... ok
test proto::stun::tests::test_change_request_port ... ok test proto::stun::tests::test_change_request_port ... ok
test proto::ssh::tests::ssh_2_banner_space ... ok
test proto::stun::tests::test_proto_stun_ipv6 ... ok test proto::stun::tests::test_proto_stun_ipv6 ... ok
test proto::tcb::tests::test_proto_tcb_proto_state_http ... ok test proto::stun::tests::test_proto_stun_ipv4 ... ok
test proto::tests::dispatch_dns ... ok test proto::stun::tests::test_change_request_port_overflow ... ok
test proto::tcb::tests::test_proto_tcb_proto_state_rpc ... ok
test proto::tcb::tests::test_proto_tcb_proto_id ... ok
test proto::tests::test_proto_dispatch_http ... ok
test proto::tests::test_proto_dispatch_ssh ... ok
test proto::tests::test_proto_dispatch_ghost ... ok
test proto::tests::test_proto_dispatch_stun ... ok
test smack::smack::tests::test_anchor_end ... ok test smack::smack::tests::test_anchor_end ... ok
test smack::smack::tests::test_multiple_matches_wildcard ... ok
test smack::smack::tests::test_multiple_matches ... ok
test smack::smack::tests::test_anchor_begin ... ok test smack::smack::tests::test_anchor_begin ... ok
test smack::smack::tests::test_multiple_matches ... ok
test smack::smack::tests::test_http_banner ... ok test smack::smack::tests::test_http_banner ... ok
test smack::smack::tests::test_multiple_matches_wildcard ... ok
test smack::smack::tests::test_proto ... ok
test smack::smack::tests::test_wildcard ... ok
test proto::tests::test_proto_dispatch_ssh ... ok
test proto::tests::test_proto_dispatch_stun ... ok
test synackcookie::tests::test_clientinfo ... ok test synackcookie::tests::test_clientinfo ... ok
test synackcookie::tests::test_ip4 ... ok
test synackcookie::tests::test_ip4_dst ... ok test synackcookie::tests::test_ip4_dst ... ok
test synackcookie::tests::test_ip4_src ... ok test synackcookie::tests::test_ip4_src ... ok
test synackcookie::tests::test_ip4 ... ok
test synackcookie::tests::test_ip6 ... ok test synackcookie::tests::test_ip6 ... ok
test synackcookie::tests::test_key ... ok test synackcookie::tests::test_key ... ok
test synackcookie::tests::test_tcp_dst ... ok test synackcookie::tests::test_tcp_dst ... ok
test synackcookie::tests::test_tcp_src ... ok test synackcookie::tests::test_tcp_src ... ok
test smack::smack::tests::test_wildcard ... ok
test smack::smack::tests::test_proto ... ok
test smack::smack::tests::test_pattern ... ok test smack::smack::tests::test_pattern ... ok
test result: ok. 92 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
``` ```
#### Functional tests #### Functional tests
``` ```
# ./test/test_masscanned.py # ./test/test_masscanned.py
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
INFO test_arp_req......................................OK INFO test_arp_req......................................OK
INFO test_arp_req_other_ip.............................OK INFO test_arp_req_other_ip.............................OK
INFO test_ipv4_udp_dns_in_a............................OK INFO test_ipv4_req.....................................OK
INFO test_ipv4_udp_dns_in_a_multiple_queries...........OK INFO test_eth_req_other_mac............................OK
INFO test_ipv4_tcp_ghost...............................OK INFO test_ipv4_req_other_ip............................OK
INFO test_icmpv4_echo_req..............................OK
INFO test_icmpv6_neighbor_solicitation.................OK
INFO test_icmpv6_neighbor_solicitation_other_ip........OK
INFO test_icmpv6_echo_req..............................OK
INFO test_tcp_syn......................................OK
INFO test_ipv4_tcp_psh_ack.............................OK
INFO test_ipv6_tcp_psh_ack.............................OK
INFO test_ipv4_tcp_http................................OK INFO test_ipv4_tcp_http................................OK
INFO test_ipv4_tcp_http_segmented......................OK
INFO test_ipv4_tcp_http_incomplete.....................OK
INFO test_ipv6_tcp_http................................OK INFO test_ipv6_tcp_http................................OK
INFO test_ipv4_udp_http................................OK INFO test_ipv4_udp_http................................OK
INFO test_ipv6_udp_http................................OK INFO test_ipv6_udp_http................................OK
@ -491,64 +265,26 @@ INFO test_ipv4_tcp_http_ko.............................OK
INFO test_ipv4_udp_http_ko.............................OK INFO test_ipv4_udp_http_ko.............................OK
INFO test_ipv6_tcp_http_ko.............................OK INFO test_ipv6_tcp_http_ko.............................OK
INFO test_ipv6_udp_http_ko.............................OK INFO test_ipv6_udp_http_ko.............................OK
INFO test_icmpv4_echo_req..............................OK
INFO test_icmpv6_neighbor_solicitation.................OK
INFO test_icmpv6_neighbor_solicitation_other_ip........OK
INFO test_icmpv6_echo_req..............................OK
INFO test_ipv4_req.....................................OK
INFO test_eth_req_other_mac............................OK
INFO test_ipv4_req_other_ip............................OK
INFO test_rpc_nmap.....................................OK
INFO test_rpcinfo......................................OK
INFO test_smb1_network_req.............................OK
INFO test_smb2_network_req.............................OK
INFO test_ipv4_tcp_ssh.................................OK
INFO test_ipv4_udp_ssh.................................OK
INFO test_ipv6_tcp_ssh.................................OK
INFO test_ipv6_udp_ssh.................................OK
INFO test_ipv4_udp_stun................................OK INFO test_ipv4_udp_stun................................OK
INFO test_ipv6_udp_stun................................OK INFO test_ipv6_udp_stun................................OK
INFO test_ipv4_udp_stun_change_port....................OK INFO test_ipv4_udp_stun_change_port....................OK
INFO test_ipv6_udp_stun_change_port....................OK INFO test_ipv6_udp_stun_change_port....................OK
INFO test_ipv4_tcp_empty...............................OK INFO test_ipv4_tcp_ssh.................................OK
INFO test_ipv6_tcp_empty...............................OK INFO test_ipv4_udp_ssh.................................OK
INFO test_tcp_syn......................................OK INFO test_ipv6_tcp_ssh.................................OK
INFO test_ipv4_tcp_psh_ack.............................OK INFO test_ipv6_udp_ssh.................................OK
INFO test_ipv6_tcp_psh_ack.............................OK tcpdump: pcap_loop: The interface disappeared
INFO test_ipv4_udp_empty...............................OK 604 packets captured
INFO test_ipv6_udp_empty...............................OK 604 packets received by filter
INFO Ran 41 tests with 0 errors 0 packets dropped by kernel
``` ```
You can also chose what tests to run using the `TESTS` environment variable ### Logging Policy
```
TESTS=smb ./test/test_masscanned.py
INFO test_smb1_network_req.............................OK
INFO test_smb2_network_req.............................OK
INFO Ran 2 tests with 0 errors
```
## Logging * `ERR`: any error - will always be displayed.
* `WARN`, `-v`: responses sent by `masscanned`.
### Console Logger * `INFO`, `-vv`: packets not handled, packets ignored.
* `DEBUG`, `-vvv`: all packets received and sent by `masscanned`.
**Verbs**:
* `init`
* `recv`
* `send`
* `drop`
#### ARP
```
$ts arp $verb $operation $client_mac $client_ip $masscanned_mac $masscanned_ip
```
#### Ethernet
```
$ts eth $verb $ethertype $client_mac $masscanned_mac
```
## To Do ## To Do

View file

@ -1,19 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

@ -1,53 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config
from ast import literal_eval
import configparser
import os
# -- Path setup --------------------------------------------------------------
# -- Project information -----------------------------------------------------
project = "IVRE"
copyright = "2021, The IVRE project"
html_logo = "img/logo.png"
master_doc = "index"
def parse_cargo():
config = configparser.ConfigParser()
config.read(os.path.join("..", "Cargo.toml"))
if "package" not in config:
return None, None, None
package = config["package"]
try:
author = literal_eval(package.get("authors"))[0].split("<", 1)[0].strip()
except KeyError:
authors = None
return literal_eval(package.get("name")), author, literal_eval(package.get("version"))
project, author, version = parse_cargo()
# -- General configuration ---------------------------------------------------
extensions = []
autosectionlabel_prefix_document = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"

View file

Before

Width:  |  Height:  |  Size: 4.2 MiB

After

Width:  |  Height:  |  Size: 4.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1,35 +0,0 @@
Welcome to Masscanned documentation!
====================================
Introduction
------------
Masscanned is a low-interaction honeypot, primarily designed to help
gather intelligence about network scanners and bots.
It has been built as a companion tool for `IVRE
<https://ivre.rocks/>`_ but can be used independently.
The code is on `GitHub <https://github.com/ivre/masscanned>`_.
Here is a quick demo:
|demo|
Status of this documentation
----------------------------
This documentation is a work in progress!
Content
-------
.. toctree::
:maxdepth: 3
:caption: Usage:
:glob:
usage
.. |demo| image:: img/demo.gif

View file

@ -1,92 +0,0 @@
Using Masscanned
================
Dedicated addresses
~~~~~~~~~~~~~~~~~~~
Masscanned is designed to handle its own IP addresses, which means
that the host should not have those addresses configured, and
Masscanned will answer ``ARP`` requests (or ``ICMPv6`` ``ND`` neighbor
sollicitations).
The host may have one or more (``IPv4`` and/or ``IPv6``) addresses configured
on an interface also used by masscanned, but those addresses must be
different from those configured to be used by masscanned.
In that situation (dedicated addresses), just run:
::
# masscanned -i <iface> -f <ip_addr_file>
where ``<ip_addr_file>`` is the path of a text file with one address (``IPv4``
or ``IPv6``) per line.
Addresses shared with the host
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes it is desirable to have an IP address used by the host
(*e.g.*, for administration tasks) and by masscanned (to handle all
other incoming packets).
Since this is not implemented in masscanned, a tiny hack is needed: we
are going to run it on a ``veth`` interface.
For this example, we suppose:
- The interface is ``eth0``, the address is ``192.168.0.10``.
- We want masscanned to handle all the traffic except for incoming SSH
connections on TCP/22 port.
We create a ``veth`` pair of interfaces, on which we are going to use
the 0.255.0.0/31 network (which should not be a problem since
0.0.0.0/8 is reserved as "Current Network"):
::
# ip link add to_masscanned type veth peer masscanned
# ip link set masscanned up
# ip link set to_masscanned up
# ip addr add 0.255.0.0/31 dev to_masscanned
# masscanned -i masscanned
Masscanned can now be used, but only from the host where it runs:
::
# ping -c 1 0.255.0.1
PING 0.255.0.1 (0.255.0.1) 56(84) octets de données.
64 octets de 0.255.0.1 : icmp_seq=1 ttl=64 temps=0.442 ms
--- statistiques ping 0.255.0.1 ---
1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.442/0.442/0.442/0.000 ms
Now, we are going to use Netfilter / ``iptables`` to redirect incoming
traffic to masscanned:
::
# sysctl -w net.ipv4.ip_forward=1
# iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10 -p tcp --dport 22 -j ACCEPT
# iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10/32 -j DNAT --to-destination 0.255.0.1
And, from another host on the 192.168.0.0/24 network:
::
# ping -c 1 192.168.0.10
PING 192.168.0.10 (192.168.0.10) 56(84) octets de données.
64 octets de 192.168.0.10 : icmp_seq=1 ttl=63 temps=0.366 ms
--- statistiques ping 192.168.0.10 ---
1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
The masscanned output:
::
WARN - ARP-Reply to ea:c0:d6:20:0c:6a for IP 0.255.0.1
WARN - ICMP-Echo-Reply to ICMP-Echo-Request

View file

@ -1,45 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - 2024 The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
FROM debian:12 AS fetcher
RUN apt-get -q update && \
apt-get -qy --no-install-recommends install ca-certificates curl && \
curl -L https://github.com/ivre/masscanned/archive/refs/heads/master.tar.gz | tar zxf -
FROM rust AS builder
COPY --from=fetcher /masscanned-master /masscanned-master
RUN cd masscanned-master && \
cargo build --release
FROM debian:12
LABEL maintainer="Pierre LALET <pierre@droids-corp.org>"
COPY --from=builder /masscanned-master/target/release/masscanned /usr/local/bin/masscanned
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -q update && \
apt-get -qy --no-install-recommends install iproute2 iptables && \
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY runmasscanned /usr/local/bin/runmasscanned
CMD /usr/local/bin/runmasscanned

View file

@ -1,38 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - 2024 The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
FROM rust AS builder
ADD masscanned.tar ./
RUN cd masscanned-master && \
cargo build --release
FROM debian:12
LABEL maintainer="Pierre LALET <pierre@droids-corp.org>"
COPY --from=builder /masscanned-master/target/release/masscanned /usr/local/bin/masscanned
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -q update && \
apt-get -qy --no-install-recommends install iproute2 iptables && \
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY runmasscanned /usr/local/bin/runmasscanned
CMD /usr/local/bin/runmasscanned

View file

@ -1,35 +0,0 @@
#! /bin/bash
# This file is part of masscanned.
# Copyright 2021 - 2023 The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
iface="$(ip route get 0.0.0.1 | awk '/^0\.0\.0\.1 via / {print $5}')"
addrs="$(ip a show eth0 | awk '/ inet6? / {print $2}' | sed 's#/.*##' | tr '\n' ',' | sed 's#,$##')"
if ! capsh --print | awk '/^Current: / {print $2}' | tr ',' '\n' | grep -q '^cap_net_admin$'; then
echo "WARNING: cannot run iptables (need capability cap_net_admin)" >&2
exit 1
fi
for v in '' 6; do
for c in INPUT OUTPUT FORWARD; do
ip${v}tables -P $c DROP
done
done
echo Interface: "$iface"
echo Addresses: "$addrs"
/usr/local/bin/masscanned -i "$iface" --self-ip-list "$addrs"

View file

@ -21,7 +21,7 @@ use std::net::IpAddr;
use pnet::packet::ip::IpNextHeaderProtocol; use pnet::packet::ip::IpNextHeaderProtocol;
use pnet::util::MacAddr; use pnet::util::MacAddr;
#[derive(PartialEq, Hash, Copy, Clone, Debug)] #[derive(PartialEq, Hash, Copy, Clone)]
pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> { pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
pub src: Option<A>, pub src: Option<A>,
pub dst: Option<A>, pub dst: Option<A>,
@ -35,7 +35,7 @@ pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
* - source and dest. transport port * - source and dest. transport port
* - syn cookie * - syn cookie
**/ **/
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone)]
pub struct ClientInfo { pub struct ClientInfo {
pub mac: ClientInfoSrcDst<MacAddr>, pub mac: ClientInfoSrcDst<MacAddr>,
pub ip: ClientInfoSrcDst<IpAddr>, pub ip: ClientInfoSrcDst<IpAddr>,
@ -65,6 +65,30 @@ impl ClientInfo {
} }
} }
impl PartialEq for ClientInfo {
fn eq(&self, other: &Self) -> bool {
if self.mac != other.mac {
return false;
}
if self.ip != other.ip {
return false;
}
if self.transport != other.transport {
return false;
}
if self.port != other.port {
return false;
}
/* this next case should never occur with TCP and UDP,
* but this implementation tries to remain transport-protocol-agnostic
**/
if self.cookie != other.cookie {
return false;
}
true
}
}
impl Eq for ClientInfo {} impl Eq for ClientInfo {}
impl Display for ClientInfo { impl Display for ClientInfo {

View file

@ -29,18 +29,20 @@ pub fn repl<'a, 'b>(
arp_req: &'a ArpPacket, arp_req: &'a ArpPacket,
masscanned: &Masscanned, masscanned: &Masscanned,
) -> Option<MutableArpPacket<'b>> { ) -> Option<MutableArpPacket<'b>> {
masscanned.log.arp_recv(arp_req);
let mut arp_repl = let mut arp_repl =
MutableArpPacket::owned(arp_req.packet().to_vec()).expect("error parsing ARP packet"); MutableArpPacket::owned(arp_req.packet().to_vec()).expect("error parsing ARP packet");
/* Build ARP answer depending of the type of request */ /* Build ARP answer depending of the type of request */
match arp_req.get_operation() { match arp_req.get_operation() {
ArpOperations::Request => { ArpOperations::Request => {
masscanned.log.arp_recv(arp_req);
let ip = IpAddr::V4(arp_req.get_target_proto_addr()); let ip = IpAddr::V4(arp_req.get_target_proto_addr());
/* Ignore ARP requests for IP addresses not handled by masscanned */ /* Ignore ARP requests for IP addresses not handled by masscanned */
if let Some(ip_addr_list) = masscanned.self_ip_list { if let Some(ip_addr_list) = masscanned.ip_addresses {
if !ip_addr_list.contains(&ip) { if !ip_addr_list.contains(&ip) {
masscanned.log.arp_drop(arp_req); info!(
"Ignoring ARP request from {} for IP {}",
arp_req.get_sender_hw_addr(),
ip
);
return None; return None;
} }
} }
@ -51,15 +53,17 @@ pub fn repl<'a, 'b>(
arp_repl.set_target_hw_addr(arp_req.get_sender_hw_addr().to_owned()); arp_repl.set_target_hw_addr(arp_req.get_sender_hw_addr().to_owned());
arp_repl.set_target_proto_addr(arp_req.get_sender_proto_addr().to_owned()); arp_repl.set_target_proto_addr(arp_req.get_sender_proto_addr().to_owned());
arp_repl.set_sender_proto_addr(arp_req.get_target_proto_addr().to_owned()); arp_repl.set_sender_proto_addr(arp_req.get_target_proto_addr().to_owned());
masscanned.log.arp_send(&arp_repl); warn!(
"ARP-Reply to {} for IP {}",
arp_req.get_sender_hw_addr(),
arp_repl.get_sender_proto_addr()
);
} }
_ => { _ => {
info!("ARP Operation not handled: {:?}", arp_repl.get_operation()); info!("ARP Operation not handled: {:?}", arp_repl.get_operation());
masscanned.log.arp_drop(arp_req);
return None; return None;
} }
}; };
masscanned.log.arp_send(&arp_repl);
Some(arp_repl) Some(arp_repl)
} }
@ -72,8 +76,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test] #[test]
fn test_arp_reply() { fn test_arp_reply() {
let mut ips = HashSet::new(); let mut ips = HashSet::new();
@ -83,9 +85,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
let mut arp_req = let mut arp_req =
MutableArpPacket::owned([0; 28].to_vec()).expect("error constructing ARP request"); MutableArpPacket::owned([0; 28].to_vec()).expect("error constructing ARP request");

View file

@ -104,32 +104,30 @@ pub fn reply<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
) -> Option<MutableEthernetPacket<'b>> { ) -> Option<MutableEthernetPacket<'b>> {
/* Fill client information for this packet with MAC addresses (src and dst) */ debug!("receiving Ethernet packet: {:?}", eth_req);
client_info.mac.src = Some(eth_req.get_source());
client_info.mac.dst = Some(eth_req.get_destination());
masscanned.log.eth_recv(eth_req, &client_info);
let mut eth_repl; let mut eth_repl;
/* First, check if the destination MAC address is one of those masscanned /* First, check if the destination MAC address is one of those masscanned
* is authorized to answer to (avoid answering to packets addressed to * is authorized to answer to (avoid answering to packets addressed to
* other machines) * other machines)
**/ **/
if !get_authorized_eth_addr(&masscanned.mac, masscanned.self_ip_list) if !get_authorized_eth_addr(&masscanned.mac, masscanned.ip_addresses)
.contains(&eth_req.get_destination()) .contains(&eth_req.get_destination())
{ {
masscanned.log.eth_drop(eth_req, &client_info); info!(
"Ignoring Ethernet packet from {} to {}",
eth_req.get_source(),
eth_req.get_destination(),
);
return None; return None;
} }
/* Fill client information for this packet with MAC addresses (src and dst) */
client_info.mac.src = Some(eth_req.get_source());
client_info.mac.dst = Some(eth_req.get_destination());
/* Build next layer payload for answer depending on the incoming packet */ /* Build next layer payload for answer depending on the incoming packet */
match eth_req.get_ethertype() { match eth_req.get_ethertype() {
/* Construct answer to ARP request */ /* Construct answer to ARP request */
EtherTypes::Arp => { EtherTypes::Arp => {
let arp_req = if let Some(p) = ArpPacket::new(eth_req.payload()) { let arp_req = ArpPacket::new(eth_req.payload()).expect("error parsing ARP packet");
p
} else {
warn!("error parsing ARP packet");
masscanned.log.eth_drop(eth_req, &client_info);
return None;
};
if let Some(arp_repl) = arp::repl(&arp_req, masscanned) { if let Some(arp_repl) = arp::repl(&arp_req, masscanned) {
let arp_len = arp_repl.packet().len(); let arp_len = arp_repl.packet().len();
let eth_len = EthernetPacket::minimum_packet_size() + arp_len; let eth_len = EthernetPacket::minimum_packet_size() + arp_len;
@ -138,7 +136,6 @@ pub fn reply<'a, 'b>(
eth_repl.set_ethertype(EtherTypes::Arp); eth_repl.set_ethertype(EtherTypes::Arp);
eth_repl.set_payload(arp_repl.packet()); eth_repl.set_payload(arp_repl.packet());
} else { } else {
masscanned.log.eth_drop(eth_req, &client_info);
return None; return None;
} }
} }
@ -148,7 +145,6 @@ pub fn reply<'a, 'b>(
p p
} else { } else {
warn!("error parsing IPv4 packet"); warn!("error parsing IPv4 packet");
masscanned.log.eth_drop(eth_req, &client_info);
return None; return None;
}; };
if let Some(mut ipv4_repl) = if let Some(mut ipv4_repl) =
@ -162,19 +158,12 @@ pub fn reply<'a, 'b>(
eth_repl.set_ethertype(EtherTypes::Ipv4); eth_repl.set_ethertype(EtherTypes::Ipv4);
eth_repl.set_payload(ipv4_repl.packet()); eth_repl.set_payload(ipv4_repl.packet());
} else { } else {
masscanned.log.eth_drop(eth_req, &client_info);
return None; return None;
} }
} }
/* Construct answer to IPv6 packet */ /* Construct answer to IPv6 packet */
EtherTypes::Ipv6 => { EtherTypes::Ipv6 => {
let ipv6_req = if let Some(p) = Ipv6Packet::new(eth_req.payload()) { let ipv6_req = Ipv6Packet::new(eth_req.payload()).expect("error parsing IPv6 packet");
p
} else {
warn!("error parsing IPv6 packet");
masscanned.log.eth_drop(eth_req, &client_info);
return None;
};
if let Some(ipv6_repl) = layer_3::ipv6::repl(&ipv6_req, masscanned, &mut client_info) { if let Some(ipv6_repl) = layer_3::ipv6::repl(&ipv6_req, masscanned, &mut client_info) {
let ipv6_len = ipv6_repl.packet().len(); let ipv6_len = ipv6_repl.packet().len();
let eth_len = EthernetPacket::minimum_packet_size() + ipv6_len; let eth_len = EthernetPacket::minimum_packet_size() + ipv6_len;
@ -183,20 +172,18 @@ pub fn reply<'a, 'b>(
eth_repl.set_ethertype(EtherTypes::Ipv6); eth_repl.set_ethertype(EtherTypes::Ipv6);
eth_repl.set_payload(ipv6_repl.packet()); eth_repl.set_payload(ipv6_repl.packet());
} else { } else {
masscanned.log.eth_drop(eth_req, &client_info);
return None; return None;
} }
} }
/* Log & drop unknown network protocol */ /* Log & drop unknown network protocol */
_ => { _ => {
info!("Ethernet type not handled: {:?}", eth_req.get_ethertype()); info!("Ethernet type not handled: {:?}", eth_req.get_ethertype());
masscanned.log.eth_drop(eth_req, &client_info);
return None; return None;
} }
}; };
eth_repl.set_source(masscanned.mac); eth_repl.set_source(masscanned.mac);
eth_repl.set_destination(eth_req.get_source()); eth_repl.set_destination(eth_req.get_source());
masscanned.log.eth_send(&eth_repl, &client_info); debug!("sending Ethernet packet: {:?}", eth_repl);
Some(eth_repl) Some(eth_repl)
} }
@ -206,46 +193,6 @@ mod tests {
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr; use std::str::FromStr;
use crate::logger::MetaLogger;
#[test]
fn test_eth_empty() {
let payload = b"";
let test_mac_addr =
MacAddr::from_str("55:44:33:22:11:00").expect("error parsing MAC address");
let mac = MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address");
let mut client_info = ClientInfo::new();
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(Ipv4Addr::new(0xaa, 0x99, 0x88, 0x77)));
ips.insert(IpAddr::V6(Ipv6Addr::new(
0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0xaabb, 0xccdd,
)));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: mac,
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
for proto in [EtherTypes::Ipv4, EtherTypes::Ipv6, EtherTypes::Arp] {
let mut eth_req = MutableEthernetPacket::owned(vec![
0;
EthernetPacket::minimum_packet_size(
) + payload.len()
])
.expect("error constructing ethernet packet");
eth_req.set_source(test_mac_addr);
eth_req.set_payload(payload);
eth_req.set_ethertype(proto);
eth_req.set_destination(mac);
if let Some(_) = reply(&eth_req.to_immutable(), &masscanned, &mut client_info) {
panic!("expected no Ethernet answer, got one");
}
}
}
#[test] #[test]
fn test_eth_reply() { fn test_eth_reply() {
/* test payload is IP(src="3.2.1.0", dst=".".join(str(b) for b in [0xaa, 0x99, /* test payload is IP(src="3.2.1.0", dst=".".join(str(b) for b in [0xaa, 0x99,
@ -264,9 +211,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
let mut eth_req = MutableEthernetPacket::owned(vec![ let mut eth_req = MutableEthernetPacket::owned(vec![
0; 0;

View file

@ -39,43 +39,31 @@ pub fn repl<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
) -> Option<MutableIpv4Packet<'b>> { ) -> Option<MutableIpv4Packet<'b>> {
/* Fill client info with source and dest. IP addresses */ debug!("receiving IPv4 packet: {:?}", ip_req);
client_info.ip.src = Some(IpAddr::V4(ip_req.get_source()));
client_info.ip.dst = Some(IpAddr::V4(ip_req.get_destination()));
masscanned.log.ipv4_recv(&ip_req, &client_info);
/* If masscanned is configured with IP addresses, then /* If masscanned is configured with IP addresses, then
* check that the dest. IP address of the packet is one of * check that the dest. IP address of the packet is one of
* those handled by masscanned - otherwise, drop the packet. * those handled by masscanned - otherwise, drop the packet.
**/ **/
if let Some(ip_addr_list) = masscanned.self_ip_list { if let Some(ip_addr_list) = masscanned.ip_addresses {
if !ip_addr_list.contains(&IpAddr::V4(ip_req.get_destination())) { if !ip_addr_list.contains(&IpAddr::V4(ip_req.get_destination())) {
masscanned.log.ipv4_drop(&ip_req, &client_info); info!(
return None; "Ignoring IP packet from {} for {}",
} ip_req.get_source(),
} ip_req.get_destination()
/* If masscanned is configured with a remote ip deny list, then );
* check if the src. IP address of the packet is one of
* those ignored by masscanned - if so, drop the packet.
**/
if let Some(remote_ip_deny_list) = masscanned.remote_ip_deny_list {
if remote_ip_deny_list.contains(&IpAddr::V4(ip_req.get_source())) {
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None; return None;
} }
} }
/* Fill client info with source and dest. IP addresses */
client_info.ip.src = Some(IpAddr::V4(ip_req.get_source()));
client_info.ip.dst = Some(IpAddr::V4(ip_req.get_destination()));
/* Fill client info with transport layer procotol */ /* Fill client info with transport layer procotol */
client_info.transport = Some(ip_req.get_next_level_protocol()); client_info.transport = Some(ip_req.get_next_level_protocol());
let mut ip_repl; let mut ip_repl;
match ip_req.get_next_level_protocol() { match ip_req.get_next_level_protocol() {
/* Answer to an ICMP packet */ /* Answer to an ICMP packet */
IpNextHeaderProtocols::Icmp => { IpNextHeaderProtocols::Icmp => {
let icmp_req = if let Some(p) = IcmpPacket::new(ip_req.payload()) { let icmp_req = IcmpPacket::new(ip_req.payload()).expect("error parsing ICMP packet");
p
} else {
warn!("error parsing ICMP packet");
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None;
};
if let Some(mut icmp_repl) = layer_4::icmpv4::repl(&icmp_req, masscanned, &client_info) if let Some(mut icmp_repl) = layer_4::icmpv4::repl(&icmp_req, masscanned, &client_info)
{ {
icmp_repl.set_checksum(ipv4_checksum_icmp(&icmp_repl.to_immutable())); icmp_repl.set_checksum(ipv4_checksum_icmp(&icmp_repl.to_immutable()));
@ -89,19 +77,12 @@ pub fn repl<'a, 'b>(
ip_repl.set_payload(icmp_repl.packet()); ip_repl.set_payload(icmp_repl.packet());
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Icmp); ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
} else { } else {
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None; return None;
} }
} }
/* Answer to a TCP packet */ /* Answer to a TCP packet */
IpNextHeaderProtocols::Tcp => { IpNextHeaderProtocols::Tcp => {
let tcp_req = if let Some(p) = TcpPacket::new(ip_req.payload()) { let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
p
} else {
warn!("error parsing TCP packet");
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None;
};
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) { if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
tcp_repl.set_checksum(ipv4_checksum_tcp( tcp_repl.set_checksum(ipv4_checksum_tcp(
&tcp_repl.to_immutable(), &tcp_repl.to_immutable(),
@ -118,19 +99,12 @@ pub fn repl<'a, 'b>(
ip_repl.set_payload(tcp_repl.packet()); ip_repl.set_payload(tcp_repl.packet());
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Tcp); ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
} else { } else {
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None; return None;
} }
} }
/* Answer to an UDP packet */ /* Answer to an UDP packet */
IpNextHeaderProtocols::Udp => { IpNextHeaderProtocols::Udp => {
let udp_req = if let Some(p) = UdpPacket::new(ip_req.payload()) { let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
p
} else {
warn!("error parsing UDP packet");
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None;
};
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) { if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
udp_repl.set_checksum(ipv4_checksum_udp( udp_repl.set_checksum(ipv4_checksum_udp(
&udp_repl.to_immutable(), &udp_repl.to_immutable(),
@ -149,13 +123,15 @@ pub fn repl<'a, 'b>(
ip_repl.set_payload(udp_repl.packet()); ip_repl.set_payload(udp_repl.packet());
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Udp); ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Udp);
} else { } else {
masscanned.log.ipv4_drop(&ip_req, &client_info);
return None; return None;
} }
} }
/* Next layer protocol not handled (yet) - dropping packet */ /* Next layer protocol not handled (yet) - dropping packet */
_ => { _ => {
masscanned.log.ipv4_drop(&ip_req, &client_info); info!(
"IPv4 upper layer not handled: {:?}",
ip_req.get_next_level_protocol()
);
return None; return None;
} }
}; };
@ -174,7 +150,7 @@ pub fn repl<'a, 'b>(
/* FIXME when dest. was a multicast IP address */ /* FIXME when dest. was a multicast IP address */
ip_repl.set_source(ip_req.get_destination()); ip_repl.set_source(ip_req.get_destination());
ip_repl.set_destination(ip_req.get_source()); ip_repl.set_destination(ip_req.get_source());
masscanned.log.ipv4_send(&ip_repl, &client_info); debug!("sending IPv4 packet: {:?}", ip_repl);
Some(ip_repl) Some(ip_repl)
} }
@ -187,55 +163,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test]
fn test_ipv4_empty() {
let payload = b"";
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
for proto in [
IpNextHeaderProtocols::Tcp,
IpNextHeaderProtocols::Udp,
IpNextHeaderProtocols::Icmp,
] {
let mut ip_req = MutableIpv4Packet::owned(vec![
0;
Ipv4Packet::minimum_packet_size()
+ payload.len()
])
.expect("error constructing IPv4 packet");
ip_req.set_version(4);
ip_req.set_ttl(64);
ip_req.set_identification(0);
ip_req.set_flags(Ipv4Flags::DontFragment);
ip_req.set_source(test_ip_addr);
ip_req.set_header_length(5);
/* Set test payload for layer 4 */
ip_req.set_total_length(ip_req.packet().len() as u16);
ip_req.set_payload(payload);
/* Set next protocol */
ip_req.set_next_level_protocol(proto);
/* Send to a legitimate IP address */
ip_req.set_destination(masscanned_ip_addr);
if let Some(_) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
panic!("expected no IP answer, got one");
}
}
}
#[test] #[test]
fn test_ipv4_reply() { fn test_ipv4_reply() {
/* test payload is scapy> ICMP() */ /* test payload is scapy> ICMP() */
@ -243,19 +170,14 @@ mod tests {
let mut client_info = ClientInfo::new(); let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0); let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3); let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
let blacklist_ip_addr = Ipv4Addr::new(3, 3, 3, 3);
let mut ips = HashSet::new(); let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr)); ips.insert(IpAddr::V4(masscanned_ip_addr));
let mut blacklist_ips = HashSet::new();
blacklist_ips.insert(IpAddr::V4(blacklist_ip_addr));
/* Construct masscanned context object */ /* Construct masscanned context object */
let masscanned = Masscanned { let masscanned = Masscanned {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: Some(&blacklist_ips),
log: MetaLogger::new(),
}; };
let mut ip_req = let mut ip_req =
MutableIpv4Packet::owned(vec![0; Ipv4Packet::minimum_packet_size() + payload.len()]) MutableIpv4Packet::owned(vec![0; Ipv4Packet::minimum_packet_size() + payload.len()])
@ -284,9 +206,5 @@ mod tests {
/* Send to a non-legitimate IP address */ /* Send to a non-legitimate IP address */
ip_req.set_destination(Ipv4Addr::new(2, 2, 2, 2)); ip_req.set_destination(Ipv4Addr::new(2, 2, 2, 2));
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None); assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
/* Send from a non-legitimate IP address */
ip_req.set_source(blacklist_ip_addr);
ip_req.set_destination(masscanned_ip_addr);
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
} }
} }

View file

@ -35,31 +35,18 @@ pub fn repl<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
) -> Option<MutableIpv6Packet<'b>> { ) -> Option<MutableIpv6Packet<'b>> {
/* Fill client info with source and dest. IP address */ debug!("receiving IPv6 packet: {:?}", ip_req);
client_info.ip.src = Some(IpAddr::V6(ip_req.get_source()));
client_info.ip.dst = Some(IpAddr::V6(ip_req.get_destination()));
masscanned.log.ipv6_recv(ip_req, client_info);
let src = ip_req.get_source(); let src = ip_req.get_source();
let mut dst = ip_req.get_destination(); let mut dst = ip_req.get_destination();
/* If masscanned is configured with IP addresses, then /* If masscanned is configured with IP addresses, check that
* check that the dest. IP address of the packet is one of * the dest. IP address corresponds to one of those
* those handled by masscanned - otherwise, drop the packet. * Otherwise, drop the packet.
**/ **/
if let Some(ip_addr_list) = masscanned.self_ip_list { if let Some(ip_addr_list) = masscanned.ip_addresses {
if !ip_addr_list.contains(&IpAddr::V6(dst)) if !ip_addr_list.contains(&IpAddr::V6(dst))
&& ip_req.get_next_header() != IpNextHeaderProtocols::Icmpv6 && ip_req.get_next_header() != IpNextHeaderProtocols::Icmpv6
{ {
masscanned.log.ipv6_drop(ip_req, client_info); info!("Ignoring IP packet from {} for {}", &src, &dst);
return None;
}
}
/* If masscanned is configured with a remote ip deny list, then
* check if the src. IP address of the packet is one of
* those ignored by masscanned - if so, drop the packet.
**/
if let Some(remote_ip_deny_list) = masscanned.remote_ip_deny_list {
if remote_ip_deny_list.contains(&IpAddr::V6(src)) {
masscanned.log.ipv6_drop(ip_req, client_info);
return None; return None;
} }
} }
@ -72,13 +59,8 @@ pub fn repl<'a, 'b>(
match ip_req.get_next_header() { match ip_req.get_next_header() {
/* Answer to ICMPv6 */ /* Answer to ICMPv6 */
IpNextHeaderProtocols::Icmpv6 => { IpNextHeaderProtocols::Icmpv6 => {
let icmp_req = if let Some(p) = Icmpv6Packet::new(ip_req.payload()) { let icmp_req =
p Icmpv6Packet::new(ip_req.payload()).expect("error parsing ICMPv6 packet");
} else {
warn!("error parsing ICMPv6 packet");
masscanned.log.ipv6_drop(&ip_req, &client_info);
return None;
};
if let (Some(mut icmp_repl), dst_addr) = if let (Some(mut icmp_repl), dst_addr) =
layer_4::icmpv6::repl(&icmp_req, masscanned, &client_info) layer_4::icmpv6::repl(&icmp_req, masscanned, &client_info)
{ {
@ -102,19 +84,12 @@ pub fn repl<'a, 'b>(
ip_repl.set_hop_limit(255); ip_repl.set_hop_limit(255);
}; };
} else { } else {
masscanned.log.ipv6_drop(ip_req, client_info);
return None; return None;
} }
} }
/* Answer to TCP */ /* Answer to TCP */
IpNextHeaderProtocols::Tcp => { IpNextHeaderProtocols::Tcp => {
let tcp_req = if let Some(p) = TcpPacket::new(ip_req.payload()) { let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
p
} else {
warn!("error parsing TCP packet");
masscanned.log.ipv6_drop(&ip_req, &client_info);
return None;
};
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) { if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
/* Compute and set TCP checksum */ /* Compute and set TCP checksum */
tcp_repl.set_checksum(ipv6_checksum_tcp( tcp_repl.set_checksum(ipv6_checksum_tcp(
@ -133,19 +108,12 @@ pub fn repl<'a, 'b>(
ip_repl.set_payload_length(tcp_len as u16); ip_repl.set_payload_length(tcp_len as u16);
ip_repl.set_payload(&tcp_repl.packet()); ip_repl.set_payload(&tcp_repl.packet());
} else { } else {
masscanned.log.ipv6_drop(ip_req, client_info);
return None; return None;
} }
} }
/* Answer to UDP */ /* Answer to UDP */
IpNextHeaderProtocols::Udp => { IpNextHeaderProtocols::Udp => {
let udp_req = if let Some(p) = UdpPacket::new(ip_req.payload()) { let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
p
} else {
warn!("error parsing UDP packet");
masscanned.log.ipv6_drop(&ip_req, &client_info);
return None;
};
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) { if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
/* Compute and set UDP checksum */ /* Compute and set UDP checksum */
udp_repl.set_checksum(ipv6_checksum_udp( udp_repl.set_checksum(ipv6_checksum_udp(
@ -164,13 +132,15 @@ pub fn repl<'a, 'b>(
ip_repl.set_payload_length(udp_len as u16); ip_repl.set_payload_length(udp_len as u16);
ip_repl.set_payload(&udp_repl.packet()); ip_repl.set_payload(&udp_repl.packet());
} else { } else {
masscanned.log.ipv6_drop(ip_req, client_info);
return None; return None;
} }
} }
/* Other protocols are not handled (yet) - dropping */ /* Other protocols are not handled (yet) - dropping */
_ => { _ => {
masscanned.log.ipv6_drop(ip_req, client_info); info!(
"IPv6 upper layer not handled: {:?}",
ip_req.get_next_header()
);
return None; return None;
} }
}; };
@ -183,7 +153,7 @@ pub fn repl<'a, 'b>(
/* Set packet source and dest. */ /* Set packet source and dest. */
ip_repl.set_source(dst); ip_repl.set_source(dst);
ip_repl.set_destination(src); ip_repl.set_destination(src);
masscanned.log.ipv6_send(&ip_repl, client_info); debug!("sending IPv6 packet: {:?}", ip_repl);
Some(ip_repl) Some(ip_repl)
} }
@ -196,55 +166,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test]
fn test_ipv6_empty() {
let payload = b"";
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv6Addr::new(
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
);
let masscanned_ip_addr = Ipv6Addr::new(
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
);
let mut ips = HashSet::new();
ips.insert(IpAddr::V6(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
for proto in [
IpNextHeaderProtocols::Tcp,
IpNextHeaderProtocols::Udp,
IpNextHeaderProtocols::Icmp,
] {
let mut ip_req = MutableIpv6Packet::owned(vec![
0;
Ipv6Packet::minimum_packet_size()
+ payload.len()
])
.expect("error constructing IPv6 packet");
ip_req.set_version(6);
ip_req.set_source(test_ip_addr);
/* Set test payload for layer 4 */
ip_req.set_payload_length(payload.len() as u16);
ip_req.set_payload(payload);
/* Set next protocol */
ip_req.set_next_header(proto);
/* Send to a legitimate IP address */
ip_req.set_destination(masscanned_ip_addr);
if let Some(_) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
panic!("expected no IP answer, got one");
}
}
}
#[test] #[test]
fn test_ipv6_reply() { fn test_ipv6_reply() {
/* test payload is scapy> IPv6(src="7777:6666:5555:4444:3333:2222:1111:0000", /* test payload is scapy> IPv6(src="7777:6666:5555:4444:3333:2222:1111:0000",
@ -258,21 +179,14 @@ mod tests {
let masscanned_ip_addr = Ipv6Addr::new( let masscanned_ip_addr = Ipv6Addr::new(
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
); );
let blacklist_ip_addr = Ipv6Addr::new(
0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111,
);
let mut ips = HashSet::new(); let mut ips = HashSet::new();
ips.insert(IpAddr::V6(masscanned_ip_addr)); ips.insert(IpAddr::V6(masscanned_ip_addr));
let mut blacklist_ips = HashSet::new();
blacklist_ips.insert(IpAddr::V6(blacklist_ip_addr));
/* Construct masscanned context object */ /* Construct masscanned context object */
let masscanned = Masscanned { let masscanned = Masscanned {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: Some(&blacklist_ips),
log: MetaLogger::new(),
}; };
let mut ip_req = let mut ip_req =
MutableIpv6Packet::owned(vec![0; Ipv6Packet::minimum_packet_size() + payload.len()]) MutableIpv6Packet::owned(vec![0; Ipv6Packet::minimum_packet_size() + payload.len()])
@ -299,9 +213,5 @@ mod tests {
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7778, 0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7778,
)); ));
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None); assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
/* Send from a non-legitimate IP address */
ip_req.set_source(blacklist_ip_addr);
ip_req.set_destination(masscanned_ip_addr);
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
} }
} }

View file

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>. // along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use log::*;
use pnet::packet::{ use pnet::packet::{
icmp::{IcmpCode, IcmpPacket, IcmpTypes, MutableIcmpPacket}, icmp::{IcmpCode, IcmpPacket, IcmpTypes, MutableIcmpPacket},
Packet, Packet,
@ -24,16 +26,16 @@ use crate::Masscanned;
pub fn repl<'a, 'b>( pub fn repl<'a, 'b>(
icmp_req: &'a IcmpPacket, icmp_req: &'a IcmpPacket,
masscanned: &Masscanned, _masscanned: &Masscanned,
client_info: &ClientInfo, mut _client_info: &ClientInfo,
) -> Option<MutableIcmpPacket<'b>> { ) -> Option<MutableIcmpPacket<'b>> {
masscanned.log.icmpv4_recv(icmp_req, client_info); debug!("receiving ICMPv4 packet: {:?}", icmp_req);
let mut icmp_repl; let mut icmp_repl;
match icmp_req.get_icmp_type() { match icmp_req.get_icmp_type() {
IcmpTypes::EchoRequest => { IcmpTypes::EchoRequest => {
/* Check code of ICMP packet */ /* Check code of ICMP packet */
if icmp_req.get_icmp_code() != IcmpCode(0) { if icmp_req.get_icmp_code() != IcmpCode(0) {
masscanned.log.icmpv4_drop(icmp_req, client_info); info!("ICMP code not handled: {:?}", icmp_req.get_icmp_code());
return None; return None;
} }
/* Compute answer length */ /* Compute answer length */
@ -51,13 +53,13 @@ pub fn repl<'a, 'b>(
* reply message." * reply message."
**/ **/
icmp_repl.set_payload(icmp_req.payload()); icmp_repl.set_payload(icmp_req.payload());
warn!("ICMP-Echo-Reply to ICMP-Echo-Request");
} }
_ => { _ => {
masscanned.log.icmpv4_drop(icmp_req, client_info);
return None; return None;
} }
}; };
masscanned.log.icmpv4_send(&icmp_repl, client_info); debug!("sending ICMPv4 packet: {:?}", icmp_repl);
Some(icmp_repl) Some(icmp_repl)
} }
@ -68,8 +70,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test] #[test]
fn test_icmpv4_reply() { fn test_icmpv4_reply() {
/* test payload is scapy> ICMP() */ /* test payload is scapy> ICMP() */
@ -80,9 +80,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: None, ip_addresses: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
let mut icmp_req = let mut icmp_req =
MutableIcmpPacket::owned(vec![0; IcmpPacket::minimum_packet_size() + payload.len()]) MutableIcmpPacket::owned(vec![0; IcmpPacket::minimum_packet_size() + payload.len()])

View file

@ -40,7 +40,7 @@ pub fn nd_ns_repl<'a, 'b>(
* check that the dest. IP address of the packet is one of * check that the dest. IP address of the packet is one of
* those handled by masscanned - otherwise, drop the packet. * those handled by masscanned - otherwise, drop the packet.
**/ **/
if let Some(addresses) = masscanned.self_ip_list { if let Some(addresses) = masscanned.ip_addresses {
if !addresses.contains(&IpAddr::V6(nd_ns_req.get_target_addr())) { if !addresses.contains(&IpAddr::V6(nd_ns_req.get_target_addr())) {
return None; return None;
} }
@ -103,7 +103,7 @@ pub fn repl<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
client_info: &ClientInfo, client_info: &ClientInfo,
) -> (Option<MutableIcmpv6Packet<'b>>, Option<Ipv6Addr>) { ) -> (Option<MutableIcmpv6Packet<'b>>, Option<Ipv6Addr>) {
masscanned.log.icmpv6_recv(icmp_req, client_info); debug!("receiving ICMPv6 packet: {:?}", icmp_req);
let mut dst_ip = None; let mut dst_ip = None;
if icmp_req.get_icmpv6_code() != Icmpv6Codes::NoCode { if icmp_req.get_icmpv6_code() != Icmpv6Codes::NoCode {
return (None, None); return (None, None);
@ -120,7 +120,6 @@ pub fn repl<'a, 'b>(
icmp_repl = MutableIcmpv6Packet::owned(nd_na_repl.packet().to_vec()) icmp_repl = MutableIcmpv6Packet::owned(nd_na_repl.packet().to_vec())
.expect("error constructing an ICMPv6 packet"); .expect("error constructing an ICMPv6 packet");
} else { } else {
masscanned.log.icmpv6_drop(icmp_req, client_info);
return (None, None); return (None, None);
} }
} }
@ -137,13 +136,17 @@ pub fn repl<'a, 'b>(
icmp_repl = MutableIcmpv6Packet::owned(vec![0; Icmpv6Packet::packet_size(&echo_repl)]) icmp_repl = MutableIcmpv6Packet::owned(vec![0; Icmpv6Packet::packet_size(&echo_repl)])
.expect("error constructing an ICMPv6 packet"); .expect("error constructing an ICMPv6 packet");
icmp_repl.populate(&echo_repl); icmp_repl.populate(&echo_repl);
warn!("ICMPv6-Echo-Reply to ICMPv6-Echo-Request");
} }
_ => { _ => {
masscanned.log.icmpv6_drop(icmp_req, client_info); info!(
"ICMPv6 packet not handled: {:?}",
icmp_req.get_icmpv6_type()
);
return (None, None); return (None, None);
} }
}; };
masscanned.log.icmpv6_send(&icmp_repl, client_info); debug!("sending ICMPv6 packet: {:?}", icmp_repl);
(Some(icmp_repl), dst_ip) (Some(icmp_repl), dst_ip)
} }
@ -157,8 +160,6 @@ mod tests {
use pnet::packet::icmpv6::ndp::{MutableNeighborSolicitPacket, NeighborSolicit}; use pnet::packet::icmpv6::ndp::{MutableNeighborSolicitPacket, NeighborSolicit};
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test] #[test]
fn test_nd_na_reply() { fn test_nd_na_reply() {
let client_info = ClientInfo::new(); let client_info = ClientInfo::new();
@ -172,9 +173,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
/* Legitimate solicitation */ /* Legitimate solicitation */
let ndp_ns = NeighborSolicit { let ndp_ns = NeighborSolicit {
@ -246,9 +245,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
let mut icmpv6_echo_req = MutableIcmpv6Packet::owned(vec![ let mut icmpv6_echo_req = MutableIcmpv6Packet::owned(vec![
0; 0;

View file

@ -31,10 +31,10 @@ pub fn repl<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
) -> Option<MutableTcpPacket<'b>> { ) -> Option<MutableTcpPacket<'b>> {
debug!("receiving TCP packet: {:?}", tcp_req);
/* Fill client info with source and dest. TCP port */ /* Fill client info with source and dest. TCP port */
client_info.port.src = Some(tcp_req.get_source()); client_info.port.src = Some(tcp_req.get_source());
client_info.port.dst = Some(tcp_req.get_destination()); client_info.port.dst = Some(tcp_req.get_destination());
masscanned.log.tcp_recv(tcp_req, client_info);
/* Construct response TCP packet */ /* Construct response TCP packet */
let mut tcp_repl; let mut tcp_repl;
match tcp_req.get_flags() { match tcp_req.get_flags() {
@ -49,24 +49,16 @@ pub fn repl<'a, 'b>(
}; };
/* Compute syncookie */ /* Compute syncookie */
if let Ok(cookie) = synackcookie::generate(&client_info, &masscanned.synack_key) { if let Ok(cookie) = synackcookie::generate(&client_info, &masscanned.synack_key) {
client_info.cookie = Some(cookie);
if !proto::is_tcb_set(cookie) {
/* First Ack: check syncookie, create tcb */
if cookie != ackno { if cookie != ackno {
masscanned.log.tcp_drop(tcp_req, client_info); info!("PSH-ACK ignored: synackcookie not valid");
return None; return None;
} }
proto::add_tcb(cookie); client_info.cookie = Some(cookie);
}
} }
warn!("ACK to PSH-ACK on port {}", tcp_req.get_destination()); warn!("ACK to PSH-ACK on port {}", tcp_req.get_destination());
let payload = tcp_req.payload(); let payload = tcp_req.payload();
/* Any answer to upper-layer protocol? */ /* Any answer to upper-layer protocol? */
let mut payload_repl = None; if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
proto::get_tcb(client_info.cookie.unwrap(), |tcb| {
payload_repl = proto::repl(&payload, masscanned, &mut client_info, tcb);
});
if let Some(repl) = payload_repl {
tcp_repl = MutableTcpPacket::owned( tcp_repl = MutableTcpPacket::owned(
[vec![0; MutableTcpPacket::minimum_packet_size()], repl].concat(), [vec![0; MutableTcpPacket::minimum_packet_size()], repl].concat(),
) )
@ -78,52 +70,33 @@ pub fn repl<'a, 'b>(
.expect("error constructing a TCP packet"); .expect("error constructing a TCP packet");
tcp_repl.set_flags(TcpFlags::ACK); tcp_repl.set_flags(TcpFlags::ACK);
} }
tcp_repl.set_acknowledgement( tcp_repl.set_acknowledgement(tcp_req.get_sequence() + (tcp_req.payload().len() as u32));
tcp_req
.get_sequence()
.wrapping_add(tcp_req.payload().len() as u32),
);
tcp_repl.set_sequence(tcp_req.get_acknowledgement()); tcp_repl.set_sequence(tcp_req.get_acknowledgement());
} }
/* Answer to ACK: nothing */ /* Answer to ACK: nothing */
flags if flags == TcpFlags::ACK => { flags if flags == TcpFlags::ACK => {
/* answer here when server needs to speak first after handshake */ /* answer here when server needs to speak first after handshake */
masscanned.log.tcp_drop(tcp_req, client_info);
return None; return None;
} }
/* Answer to RST: nothing */ /* Answer to RST and FIN: nothing */
flags if flags == TcpFlags::RST => { flags if (flags == TcpFlags::RST || flags == (TcpFlags::FIN | TcpFlags::ACK)) => {
masscanned.log.tcp_drop(tcp_req, client_info);
return None; return None;
} }
/* Answer to FIN,ACK with FIN,ACK */ /* Answer to SYN */
flags if flags == (TcpFlags::FIN | TcpFlags::ACK) => { flags if flags & TcpFlags::SYN == TcpFlags::SYN => {
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
.expect("error constructing a TCP packet");
tcp_repl.set_flags(TcpFlags::FIN | TcpFlags::ACK);
tcp_repl.set_acknowledgement(tcp_req.get_sequence().wrapping_add(1));
tcp_repl.set_sequence(tcp_req.get_acknowledgement());
}
/* Answer to SYN + P|U|C|E + !(C && E) to imitate Linux network stack */
flags
if (flags & TcpFlags::SYN) == TcpFlags::SYN &&
/* no other flag than S,P,U,C,E */
(flags & !(TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR | TcpFlags::ECE)) == 0 &&
/* not C && E */
((flags & TcpFlags::CWR == 0) || (flags & TcpFlags::ECE == 0)) =>
{
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]) tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
.expect("error constructing a TCP packet"); .expect("error constructing a TCP packet");
tcp_repl.set_flags(TcpFlags::ACK); tcp_repl.set_flags(TcpFlags::ACK);
tcp_repl.set_flags(TcpFlags::SYN | TcpFlags::ACK); tcp_repl.set_flags(TcpFlags::SYN | TcpFlags::ACK);
tcp_repl.set_acknowledgement(tcp_req.get_sequence().wrapping_add(1)); tcp_repl.set_acknowledgement(tcp_req.get_sequence() + 1);
/* generate a SYNACK-cookie (same as masscan) */ /* generate a SYNACK-cookie (same as masscan) */
tcp_repl.set_sequence( tcp_repl.set_sequence(
synackcookie::generate(&client_info, &masscanned.synack_key).unwrap(), synackcookie::generate(&client_info, &masscanned.synack_key).unwrap(),
); );
warn!("SYN-ACK to ACK on port {}", tcp_req.get_destination());
} }
_ => { _ => {
masscanned.log.tcp_drop(tcp_req, client_info); info!("TCP flag not handled: {}", tcp_req.get_flags());
return None; return None;
} }
} }
@ -134,7 +107,7 @@ pub fn repl<'a, 'b>(
/* Set TCP headers */ /* Set TCP headers */
tcp_repl.set_data_offset(5); tcp_repl.set_data_offset(5);
tcp_repl.set_window(65535); tcp_repl.set_window(65535);
masscanned.log.tcp_send(&tcp_repl, client_info); debug!("sending TCP packet: {:?}", tcp_repl);
Some(tcp_repl) Some(tcp_repl)
} }
@ -145,212 +118,13 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use crate::logger::MetaLogger;
#[test]
fn test_tcp_syn() {
let masscanned = Masscanned {
mac: MacAddr(0, 0, 0, 0, 0, 0),
self_ip_list: None,
remote_ip_deny_list: None,
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
iface: None,
log: MetaLogger::new(),
};
/* reference */
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
let tcp_sport = 65500;
let tcp_dport = 80;
let seq = 1234567;
let ack = 0;
let mut client_info = ClientInfo {
mac: ClientInfoSrcDst {
src: None,
dst: None,
},
ip: ClientInfoSrcDst {
src: Some(ip_src),
dst: Some(ip_dst),
},
transport: None,
port: ClientInfoSrcDst {
src: Some(tcp_sport),
dst: Some(tcp_dport),
},
cookie: None,
};
/* flags OK - list is exhaustive */
/* aim at imitating a Linux network stack */
let flags_ok = [
TcpFlags::SYN,
TcpFlags::SYN | TcpFlags::PSH,
TcpFlags::SYN | TcpFlags::URG,
TcpFlags::SYN | TcpFlags::CWR,
TcpFlags::SYN | TcpFlags::ECE,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::CWR,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::ECE,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::ECE,
TcpFlags::SYN | TcpFlags::URG | TcpFlags::CWR,
TcpFlags::SYN | TcpFlags::URG | TcpFlags::ECE,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::ECE,
];
for flags in flags_ok {
let mut tcp_req =
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
tcp_req.set_source(tcp_sport);
tcp_req.set_destination(tcp_dport);
tcp_req.set_sequence(seq);
tcp_req.set_acknowledgement(ack);
tcp_req.set_flags(flags);
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
if some_tcp_repl == None {
panic!("expected a reply, got none for flags: {:?}", flags);
}
let tcp_repl = some_tcp_repl.unwrap();
/* check reply flags */
assert!(tcp_repl.get_flags() == (TcpFlags::SYN | TcpFlags::ACK));
/* check reply seq and ack */
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
}
/* flags KO - list is *not* exhaustive */
let flags_ko = [
TcpFlags::SYN | TcpFlags::ACK,
TcpFlags::SYN | TcpFlags::FIN,
TcpFlags::SYN | TcpFlags::CWR | TcpFlags::ECE,
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR | TcpFlags::ECE,
TcpFlags::PSH,
];
for flags in flags_ko {
let mut tcp_req =
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
tcp_req.set_source(tcp_sport);
tcp_req.set_destination(tcp_dport);
tcp_req.set_sequence(seq);
tcp_req.set_acknowledgement(ack);
tcp_req.set_flags(flags);
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
if some_tcp_repl != None {
panic!("expected no reply, got one");
}
}
}
#[test]
fn test_tcp_fin_ack() {
let masscanned = Masscanned {
mac: MacAddr(0, 0, 0, 0, 0, 0),
self_ip_list: None,
remote_ip_deny_list: None,
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
iface: None,
log: MetaLogger::new(),
};
/* reference */
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
let tcp_sport = 65500;
let tcp_dport = 80;
let seq = 1234567;
let ack = 7654321;
let mut client_info = ClientInfo {
mac: ClientInfoSrcDst {
src: None,
dst: None,
},
ip: ClientInfoSrcDst {
src: Some(ip_src),
dst: Some(ip_dst),
},
transport: None,
port: ClientInfoSrcDst {
src: Some(tcp_sport),
dst: Some(tcp_dport),
},
cookie: None,
};
let mut tcp_req =
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
tcp_req.set_source(tcp_sport);
tcp_req.set_destination(tcp_dport);
tcp_req.set_sequence(seq);
tcp_req.set_acknowledgement(ack);
tcp_req.set_flags(TcpFlags::FIN | TcpFlags::ACK);
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
if some_tcp_repl == None {
panic!("expected a reply, got none");
}
let tcp_repl = some_tcp_repl.unwrap();
/* check reply flags */
assert!(tcp_repl.get_flags() == (TcpFlags::FIN | TcpFlags::ACK));
/* check reply seq and ack */
assert!(tcp_repl.get_sequence() == ack);
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
}
#[test]
fn test_tcp_fin_ack_wrap() {
let masscanned = Masscanned {
mac: MacAddr(0, 0, 0, 0, 0, 0),
self_ip_list: None,
remote_ip_deny_list: None,
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
iface: None,
log: MetaLogger::new(),
};
/* reference */
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
let tcp_sport = 65500;
let tcp_dport = 80;
let seq = 0xffffffff;
let ack = 0xffffffff;
let mut client_info = ClientInfo {
mac: ClientInfoSrcDst {
src: None,
dst: None,
},
ip: ClientInfoSrcDst {
src: Some(ip_src),
dst: Some(ip_dst),
},
transport: None,
port: ClientInfoSrcDst {
src: Some(tcp_sport),
dst: Some(tcp_dport),
},
cookie: None,
};
let mut tcp_req =
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
tcp_req.set_source(tcp_sport);
tcp_req.set_destination(tcp_dport);
tcp_req.set_sequence(seq);
tcp_req.set_acknowledgement(ack);
tcp_req.set_flags(TcpFlags::FIN | TcpFlags::ACK);
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
if some_tcp_repl == None {
panic!("expected a reply, got none");
}
let tcp_repl = some_tcp_repl.unwrap();
/* check reply flags */
assert!(tcp_repl.get_flags() == (TcpFlags::FIN | TcpFlags::ACK));
/* check reply seq and ack */
assert!(tcp_repl.get_sequence() == ack);
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
}
#[test] #[test]
fn test_synack_cookie_ipv4() { fn test_synack_cookie_ipv4() {
let masscanned = Masscanned { let masscanned = Masscanned {
mac: MacAddr(0, 0, 0, 0, 0, 0), mac: MacAddr(0, 0, 0, 0, 0, 0),
self_ip_list: None, ip_addresses: None,
remote_ip_deny_list: None,
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f], synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
iface: None, iface: None,
log: MetaLogger::new(),
}; };
/* reference */ /* reference */
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1)); let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
@ -397,11 +171,9 @@ mod tests {
fn test_synack_cookie_ipv6() { fn test_synack_cookie_ipv6() {
let masscanned = Masscanned { let masscanned = Masscanned {
mac: MacAddr(0, 0, 0, 0, 0, 0), mac: MacAddr(0, 0, 0, 0, 0, 0),
self_ip_list: None, ip_addresses: None,
remote_ip_deny_list: None,
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f], synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
iface: None, iface: None,
log: MetaLogger::new(),
}; };
/* reference */ /* reference */
let ip_src = IpAddr::V6(Ipv6Addr::new(234, 52, 183, 47, 184, 172, 64, 141)); let ip_src = IpAddr::V6(Ipv6Addr::new(234, 52, 183, 47, 184, 172, 64, 141));

View file

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>. // along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use log::*;
use pnet::packet::{ use pnet::packet::{
udp::{MutableUdpPacket, UdpPacket}, udp::{MutableUdpPacket, UdpPacket},
Packet, Packet,
@ -28,26 +30,25 @@ pub fn repl<'a, 'b>(
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
) -> Option<MutableUdpPacket<'b>> { ) -> Option<MutableUdpPacket<'b>> {
debug!("receiving UDP packet: {:?}", udp_req);
/* Fill client info with source and dest. UDP port */ /* Fill client info with source and dest. UDP port */
client_info.port.src = Some(udp_req.get_source()); client_info.port.src = Some(udp_req.get_source());
client_info.port.dst = Some(udp_req.get_destination()); client_info.port.dst = Some(udp_req.get_destination());
masscanned.log.udp_recv(udp_req, client_info);
let payload = udp_req.payload(); let payload = udp_req.payload();
let mut udp_repl; let mut udp_repl;
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info, None) { if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
udp_repl = MutableUdpPacket::owned( udp_repl = MutableUdpPacket::owned(
[vec![0; MutableUdpPacket::minimum_packet_size()], repl].concat(), [vec![0; MutableUdpPacket::minimum_packet_size()], repl].concat(),
) )
.expect("error constructing a UDP packet"); .expect("error constructing a UDP packet");
udp_repl.set_length(udp_repl.packet().len() as u16); udp_repl.set_length(udp_repl.packet().len() as u16);
} else { } else {
masscanned.log.udp_drop(udp_req, client_info);
return None; return None;
} }
/* Set source and dest. port for response packet from client info */ /* Set source and dest. port for response packet from client info */
/* Note: client info could have been modified by upper layers (e.g., STUN) */ /* Note: client info could have been modified by upper layers (e.g., STUN) */
udp_repl.set_source(client_info.port.dst.unwrap()); udp_repl.set_source(client_info.port.dst.unwrap());
udp_repl.set_destination(client_info.port.src.unwrap()); udp_repl.set_destination(client_info.port.src.unwrap());
masscanned.log.udp_send(&udp_repl, client_info); debug!("sending UDP packet: {:?}", udp_repl);
Some(udp_repl) Some(udp_repl)
} }

View file

@ -1,308 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use std::time::SystemTime;
use pnet::packet::{
arp::{ArpPacket, MutableArpPacket},
ethernet::{EthernetPacket, MutableEthernetPacket},
icmp::{IcmpPacket, MutableIcmpPacket},
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
ipv4::{Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
tcp::{MutableTcpPacket, TcpPacket},
udp::{MutableUdpPacket, UdpPacket},
};
use crate::client::ClientInfo;
use crate::logger::Logger;
pub struct ConsoleLogger {
arp: bool,
eth: bool,
ipv4: bool,
ipv6: bool,
icmpv4: bool,
icmpv6: bool,
tcp: bool,
udp: bool,
}
impl ConsoleLogger {
pub fn new() -> Self {
ConsoleLogger {
arp: true,
eth: true,
ipv4: true,
ipv6: true,
icmpv4: true,
icmpv6: true,
tcp: true,
udp: true,
}
}
fn prolog(&self, proto: &str, verb: &str, crlf: bool) {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
print!(
"{}.{}\t{}\t{}{}",
now.as_secs(),
now.subsec_millis(),
proto,
verb,
if crlf { "\n" } else { "\t" },
);
}
fn client_info(&self, c: &ClientInfo) {
print!(
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t",
if let Some(m) = c.mac.src {
format!("{}", m)
} else {
"".to_string()
},
if let Some(m) = c.mac.dst {
format!("{}", m)
} else {
"".to_string()
},
if let Some(i) = c.ip.src {
format!("{}", i)
} else {
"".to_string()
},
if let Some(i) = c.ip.dst {
format!("{}", i)
} else {
"".to_string()
},
if let Some(t) = c.transport {
format!("{}", t)
} else {
"".to_string()
},
if let Some(p) = c.port.src {
format!("{}", p)
} else {
"".to_string()
},
if let Some(p) = c.port.dst {
format!("{}", p)
} else {
"".to_string()
},
);
}
}
impl Logger for ConsoleLogger {
fn init(&self) {
self.prolog("arp", "init", true);
self.prolog("eth", "init", true);
self.prolog("ipv4", "init", true);
self.prolog("ipv6", "init", true);
self.prolog("icmpv4", "init", true);
self.prolog("icmpv6", "init", true);
self.prolog("tcp", "init", true);
self.prolog("udp", "init", true);
}
/* ARP */
fn arp_enabled(&self) -> bool {
self.arp
}
fn arp_recv(&self, p: &ArpPacket) {
self.prolog("arp", "recv", false);
println!(
"{:}\t{:}\t{:}\t{:}\t{:?}",
p.get_sender_hw_addr(),
p.get_target_hw_addr(),
p.get_sender_proto_addr(),
p.get_target_proto_addr(),
p.get_operation(),
);
}
fn arp_drop(&self, p: &ArpPacket) {
self.prolog("arp", "drop", false);
println!(
"{:}\t{:}\t{:}\t{:}\t{:?}",
p.get_sender_hw_addr(),
p.get_target_hw_addr(),
p.get_sender_proto_addr(),
p.get_target_proto_addr(),
p.get_operation(),
);
}
fn arp_send(&self, p: &MutableArpPacket) {
self.prolog("arp", "send", false);
println!(
"{:}\t{:}\t{:}\t{:}\t{:?}",
p.get_target_hw_addr(),
p.get_sender_hw_addr(),
p.get_target_proto_addr(),
p.get_sender_proto_addr(),
p.get_operation(),
);
}
/* Ethernet */
fn eth_enabled(&self) -> bool {
self.eth
}
fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
self.prolog("eth", "recv", false);
self.client_info(c);
println!("{:}", p.get_ethertype(),);
}
fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
self.prolog("eth", "drop", false);
self.client_info(c);
println!("{:}", p.get_ethertype(),);
}
fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
self.prolog("eth", "send", false);
self.client_info(c);
println!("{:}", p.get_ethertype(),);
}
/* IPv4 */
fn ipv4_enabled(&self) -> bool {
self.ipv4
}
fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "recv", false);
self.client_info(c);
println!("{:}", p.get_next_level_protocol(),);
}
fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "drop", false);
self.client_info(c);
println!("{:}", p.get_next_level_protocol(),);
}
fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "send", false);
self.client_info(c);
println!("{:}", p.get_next_level_protocol(),);
}
/* IPv6 */
fn ipv6_enabled(&self) -> bool {
self.ipv6
}
fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "recv", false);
self.client_info(c);
println!("{:}", p.get_next_header(),);
}
fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "drop", false);
self.client_info(c);
println!("{:}", p.get_next_header(),);
}
fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "send", false);
self.client_info(c);
println!("{:}", p.get_next_header(),);
}
/* ICMPv4 */
fn icmpv4_enabled(&self) -> bool {
self.icmpv4
}
fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "recv", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
}
fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "drop", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
}
fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "send", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
}
/* ICMPv6 */
fn icmpv6_enabled(&self) -> bool {
self.icmpv6
}
fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "recv", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
}
fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "drop", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
}
fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "send", false);
self.client_info(c);
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
}
/* TCP */
fn tcp_enabled(&self) -> bool {
self.tcp
}
fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
self.prolog("tcp", "recv", false);
self.client_info(c);
println!(
"{:?}\t{:}\t{:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
self.prolog("tcp", "drop", false);
self.client_info(c);
println!(
"{:?}\t{:}\t{:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
self.prolog("tcp", "send", false);
self.client_info(c);
println!(
"{:?}\t{:}\t{:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
/* UDP */
fn udp_enabled(&self) -> bool {
self.udp
}
fn udp_recv(&self, _p: &UdpPacket, c: &ClientInfo) {
self.prolog("udp", "recv", false);
self.client_info(c);
println!("");
}
fn udp_drop(&self, _p: &UdpPacket, c: &ClientInfo) {
self.prolog("udp", "drop", false);
self.client_info(c);
println!("");
}
fn udp_send(&self, _p: &MutableUdpPacket, c: &ClientInfo) {
self.prolog("udp", "send", false);
self.client_info(c);
println!("");
}
}

View file

@ -1,332 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use std::time::SystemTime;
use pnet::packet::{
arp::{ArpPacket, MutableArpPacket},
ethernet::{EthernetPacket, MutableEthernetPacket},
icmp::{IcmpPacket, MutableIcmpPacket},
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
ipv4::{Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
tcp::{MutableTcpPacket, TcpPacket},
udp::{MutableUdpPacket, UdpPacket},
};
use crate::client::ClientInfo;
use crate::logger::Logger;
pub struct LogfmtLogger {
arp: bool,
eth: bool,
ipv4: bool,
ipv6: bool,
icmpv4: bool,
icmpv6: bool,
tcp: bool,
udp: bool,
}
impl LogfmtLogger {
pub fn new() -> Self {
LogfmtLogger {
arp: true,
eth: true,
ipv4: true,
ipv6: true,
icmpv4: true,
icmpv6: true,
tcp: true,
udp: true,
}
}
fn prolog(&self, proto: &str, verb: &str, crlf: bool) {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
print!(
"ts={}.{} proto={} verb={}{}",
now.as_secs(),
now.subsec_millis(),
proto,
verb,
if crlf { "\n" } else { " " },
);
}
fn client_info(&self, c: &ClientInfo) {
print!(
"{}{}{}{}{}{}{}",
if let Some(m) = c.mac.src {
format!(" mac_src={}", m)
} else {
"".to_string()
},
if let Some(m) = c.mac.dst {
format!(" mac_dst={}", m)
} else {
"".to_string()
},
if let Some(i) = c.ip.src {
format!(" ip_src={}", i)
} else {
"".to_string()
},
if let Some(i) = c.ip.dst {
format!(" ip_dst={}", i)
} else {
"".to_string()
},
if let Some(t) = c.transport {
format!(" transport={}", t)
} else {
"".to_string()
},
if let Some(p) = c.port.src {
format!(" port_src={}", p)
} else {
"".to_string()
},
if let Some(p) = c.port.dst {
format!(" port_dst={}", p)
} else {
"".to_string()
},
);
}
}
impl Logger for LogfmtLogger {
fn init(&self) {
self.prolog("arp", "init", true);
self.prolog("eth", "init", true);
self.prolog("ipv4", "init", true);
self.prolog("ipv6", "init", true);
self.prolog("icmpv4", "init", true);
self.prolog("icmpv6", "init", true);
self.prolog("tcp", "init", true);
self.prolog("udp", "init", true);
}
/* ARP */
fn arp_enabled(&self) -> bool {
self.arp
}
fn arp_recv(&self, p: &ArpPacket) {
self.prolog("arp", "recv", false);
println!(
" mac_src={:} mac_dst={:} ip_src={:} ip_dst={:} op={:?}",
p.get_sender_hw_addr(),
p.get_target_hw_addr(),
p.get_sender_proto_addr(),
p.get_target_proto_addr(),
p.get_operation(),
);
}
fn arp_drop(&self, p: &ArpPacket) {
self.prolog("arp", "drop", false);
println!(
" mac_src={:} mac_dst={:} ip_src={:} ip_dst={:} op={:?}",
p.get_sender_hw_addr(),
p.get_target_hw_addr(),
p.get_sender_proto_addr(),
p.get_target_proto_addr(),
p.get_operation(),
);
}
fn arp_send(&self, p: &MutableArpPacket) {
self.prolog("arp", "send", false);
println!(
" mac_dst={:} mac_src={:} ip_dst={:} ip_src={:} op={:?}",
p.get_target_hw_addr(),
p.get_sender_hw_addr(),
p.get_target_proto_addr(),
p.get_sender_proto_addr(),
p.get_operation(),
);
}
/* Ethernet */
fn eth_enabled(&self) -> bool {
self.eth
}
fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
self.prolog("eth", "recv", false);
self.client_info(c);
println!(" eth_type={:}", p.get_ethertype(),);
}
fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
self.prolog("eth", "drop", false);
self.client_info(c);
println!(" eth_type={:}", p.get_ethertype(),);
}
fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
self.prolog("eth", "send", false);
self.client_info(c);
println!(" eth_type={:}", p.get_ethertype(),);
}
/* IPv4 */
fn ipv4_enabled(&self) -> bool {
self.ipv4
}
fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "recv", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_level_protocol(),);
}
fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "drop", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_level_protocol(),);
}
fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
self.prolog("ipv4", "send", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_level_protocol(),);
}
/* IPv6 */
fn ipv6_enabled(&self) -> bool {
self.ipv6
}
fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "recv", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_header(),);
}
fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "drop", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_header(),);
}
fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
self.prolog("ipv6", "send", false);
self.client_info(c);
println!(" next_proto={:}", p.get_next_header(),);
}
/* ICMPv4 */
fn icmpv4_enabled(&self) -> bool {
self.icmpv4
}
fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "recv", false);
self.client_info(c);
println!(
" icmp_type={:?} icmp_code={:?}",
p.get_icmp_type(),
p.get_icmp_code(),
);
}
fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "drop", false);
self.client_info(c);
println!(
" icmp_type={:?} icmp_code={:?}",
p.get_icmp_type(),
p.get_icmp_code(),
);
}
fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
self.prolog("icmpv4", "send", false);
self.client_info(c);
println!(
" icmp_type={:?} icmp_code={:?}",
p.get_icmp_type(),
p.get_icmp_code(),
);
}
/* ICMPv6 */
fn icmpv6_enabled(&self) -> bool {
self.icmpv6
}
fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "recv", false);
self.client_info(c);
println!(
" icmpv6_type={:?} icmpv6_code={:?}",
p.get_icmpv6_type(),
p.get_icmpv6_code(),
);
}
fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "drop", false);
self.client_info(c);
println!(
" icmpv6_type={:?} icmpv6_code={:?}",
p.get_icmpv6_type(),
p.get_icmpv6_code(),
);
}
fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
self.prolog("icmpv6", "send", false);
self.client_info(c);
println!(
" icmpv6_type={:?} icmpv6_code={:?}",
p.get_icmpv6_type(),
p.get_icmpv6_code(),
);
}
/* TCP */
fn tcp_enabled(&self) -> bool {
self.tcp
}
fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
self.prolog("tcp", "recv", false);
self.client_info(c);
println!(
" flags={:?} seq={:} ack={:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
self.prolog("tcp", "drop", false);
self.client_info(c);
println!(
" flags={:?} seq={:} ack={:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
self.prolog("tcp", "send", false);
self.client_info(c);
println!(
" flags={:?} seq={:} ack={:}",
p.get_flags(),
p.get_sequence(),
p.get_acknowledgement(),
);
}
/* UDP */
fn udp_enabled(&self) -> bool {
self.udp
}
fn udp_recv(&self, _p: &UdpPacket, c: &ClientInfo) {
self.prolog("udp", "recv", false);
self.client_info(c);
println!("");
}
fn udp_drop(&self, _p: &UdpPacket, c: &ClientInfo) {
self.prolog("udp", "drop", false);
self.client_info(c);
println!("");
}
fn udp_send(&self, _p: &MutableUdpPacket, c: &ClientInfo) {
self.prolog("udp", "send", false);
self.client_info(c);
println!("");
}
}

View file

@ -1,225 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use pnet::packet::{
arp::{ArpPacket, MutableArpPacket},
ethernet::{EthernetPacket, MutableEthernetPacket},
icmp::{IcmpPacket, MutableIcmpPacket},
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
ipv4::{Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
tcp::{MutableTcpPacket, TcpPacket},
udp::{MutableUdpPacket, UdpPacket},
};
use crate::client::ClientInfo;
use crate::logger::Logger;
pub struct MetaLogger {
loggers: Vec<Box<dyn Logger>>,
}
impl MetaLogger {
pub fn new() -> Self {
MetaLogger {
loggers: Vec::new(),
}
}
pub fn add(&mut self, log: Box<dyn Logger>) {
self.loggers.push(log);
}
pub fn init(&self) {
for l in &self.loggers {
l.init();
}
}
/* ARP */
pub fn arp_recv(&self, p: &ArpPacket) {
for l in &self.loggers {
if l.arp_enabled() {
l.arp_recv(p);
}
}
}
pub fn arp_drop(&self, p: &ArpPacket) {
for l in &self.loggers {
if l.arp_enabled() {
l.arp_drop(p);
}
}
}
pub fn arp_send(&self, p: &MutableArpPacket) {
for l in &self.loggers {
if l.arp_enabled() {
l.arp_send(p);
}
}
}
/* Ethernet */
pub fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.eth_enabled() {
l.eth_recv(p, c);
}
}
}
pub fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.eth_enabled() {
l.eth_drop(p, c);
}
}
}
pub fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.eth_enabled() {
l.eth_send(p, c);
}
}
}
/* IPv4 */
pub fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv4_enabled() {
l.ipv4_recv(p, c);
}
}
}
pub fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv4_enabled() {
l.ipv4_drop(p, c);
}
}
}
pub fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv4_enabled() {
l.ipv4_send(p, c);
}
}
}
/* IPv6 */
pub fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv6_enabled() {
l.ipv6_recv(p, c);
}
}
}
pub fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv6_enabled() {
l.ipv6_drop(p, c);
}
}
}
pub fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.ipv6_enabled() {
l.ipv6_send(p, c);
}
}
}
/* ICMPv4 */
pub fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv4_enabled() {
l.icmpv4_recv(p, c);
}
}
}
pub fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv4_enabled() {
l.icmpv4_drop(p, c);
}
}
}
pub fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv4_enabled() {
l.icmpv4_send(p, c);
}
}
}
/* ICMPv6 */
pub fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv6_enabled() {
l.icmpv6_recv(p, c);
}
}
}
pub fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv6_enabled() {
l.icmpv6_drop(p, c);
}
}
}
pub fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
for l in &self.loggers {
if l.icmpv6_enabled() {
l.icmpv6_send(p, c);
}
}
}
/* TCP */
pub fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.tcp_enabled() {
l.tcp_recv(p, c);
}
}
}
pub fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.tcp_enabled() {
l.tcp_drop(p, c);
}
}
}
pub fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.tcp_enabled() {
l.tcp_send(p, c);
}
}
}
/* UDP */
pub fn udp_recv(&self, p: &UdpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.udp_enabled() {
l.udp_recv(p, c);
}
}
}
pub fn udp_drop(&self, p: &UdpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.udp_enabled() {
l.udp_drop(p, c);
}
}
}
pub fn udp_send(&self, p: &MutableUdpPacket, c: &ClientInfo) {
for l in &self.loggers {
if l.udp_enabled() {
l.udp_send(p, c);
}
}
}
}

View file

@ -1,97 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use pnet::packet::{
arp::{ArpPacket, MutableArpPacket},
ethernet::{EthernetPacket, MutableEthernetPacket},
icmp::{IcmpPacket, MutableIcmpPacket},
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
ipv4::{Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
tcp::{MutableTcpPacket, TcpPacket},
udp::{MutableUdpPacket, UdpPacket},
};
use crate::client::ClientInfo;
mod console;
mod logfmt;
mod meta;
pub use console::ConsoleLogger;
pub use logfmt::LogfmtLogger;
pub use meta::MetaLogger;
pub trait Logger {
fn init(&self);
/* list of notifications that a logger might or might not implement */
/* ARP */
fn arp_enabled(&self) -> bool {
true
}
fn arp_recv(&self, _p: &ArpPacket) {}
fn arp_drop(&self, _p: &ArpPacket) {}
fn arp_send(&self, _p: &MutableArpPacket) {}
/* Ethernet */
fn eth_enabled(&self) -> bool {
true
}
fn eth_recv(&self, _p: &EthernetPacket, _c: &ClientInfo) {}
fn eth_drop(&self, _p: &EthernetPacket, _c: &ClientInfo) {}
fn eth_send(&self, _p: &MutableEthernetPacket, _c: &ClientInfo) {}
/* IPv4 */
fn ipv4_enabled(&self) -> bool {
true
}
fn ipv4_recv(&self, _p: &Ipv4Packet, _c: &ClientInfo) {}
fn ipv4_drop(&self, _p: &Ipv4Packet, _c: &ClientInfo) {}
fn ipv4_send(&self, _p: &MutableIpv4Packet, _c: &ClientInfo) {}
/* IPv6 */
fn ipv6_enabled(&self) -> bool {
true
}
fn ipv6_recv(&self, _p: &Ipv6Packet, _c: &ClientInfo) {}
fn ipv6_drop(&self, _p: &Ipv6Packet, _c: &ClientInfo) {}
fn ipv6_send(&self, _p: &MutableIpv6Packet, _c: &ClientInfo) {}
/* ICMPv4 */
fn icmpv4_enabled(&self) -> bool {
true
}
fn icmpv4_recv(&self, _p: &IcmpPacket, _c: &ClientInfo) {}
fn icmpv4_drop(&self, _p: &IcmpPacket, _c: &ClientInfo) {}
fn icmpv4_send(&self, _p: &MutableIcmpPacket, _c: &ClientInfo) {}
/* ICMPv6 */
fn icmpv6_enabled(&self) -> bool {
true
}
fn icmpv6_recv(&self, _p: &Icmpv6Packet, _c: &ClientInfo) {}
fn icmpv6_drop(&self, _p: &Icmpv6Packet, _c: &ClientInfo) {}
fn icmpv6_send(&self, _p: &MutableIcmpv6Packet, _c: &ClientInfo) {}
/* TCP */
fn tcp_enabled(&self) -> bool {
true
}
fn tcp_recv(&self, _p: &TcpPacket, _c: &ClientInfo) {}
fn tcp_drop(&self, _p: &TcpPacket, _c: &ClientInfo) {}
fn tcp_send(&self, _p: &MutableTcpPacket, _c: &ClientInfo) {}
/* UDP */
fn udp_enabled(&self) -> bool {
true
}
fn udp_recv(&self, _p: &UdpPacket, _c: &ClientInfo) {}
fn udp_drop(&self, _p: &UdpPacket, _c: &ClientInfo) {}
fn udp_send(&self, _p: &MutableUdpPacket, _c: &ClientInfo) {}
}

View file

@ -1,5 +1,5 @@
// This file is part of masscanned. // This file is part of masscanned.
// Copyright 2021 - 2022 The IVRE project // Copyright 2021 - The IVRE project
// //
// Masscanned is free software: you can redistribute it and/or modify it // Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by // under the terms of the GNU General Public License as published by
@ -24,7 +24,7 @@ use std::fs::File;
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use clap::{builder::PossibleValuesParser, Arg, ArgAction, Command}; use clap::{App, Arg};
use log::*; use log::*;
use pnet::{ use pnet::{
datalink::{self, Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface}, datalink::{self, Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface},
@ -35,14 +35,12 @@ use pnet::{
util::MacAddr, util::MacAddr,
}; };
use crate::logger::{ConsoleLogger, LogfmtLogger, Logger, MetaLogger};
use crate::utils::IpAddrParser; use crate::utils::IpAddrParser;
mod client; mod client;
mod layer_2; mod layer_2;
mod layer_3; mod layer_3;
mod layer_4; mod layer_4;
mod logger;
mod proto; mod proto;
mod smack; mod smack;
mod synackcookie; mod synackcookie;
@ -56,10 +54,7 @@ pub struct Masscanned<'a> {
pub mac: MacAddr, pub mac: MacAddr,
/* iface is an Option to make tests easier */ /* iface is an Option to make tests easier */
pub iface: Option<&'a NetworkInterface>, pub iface: Option<&'a NetworkInterface>,
pub self_ip_list: Option<&'a HashSet<IpAddr>>, pub ip_addresses: Option<&'a HashSet<IpAddr>>,
pub remote_ip_deny_list: Option<&'a HashSet<IpAddr>>,
/* loggers */
pub log: MetaLogger,
} }
/* Get the L2 network interface from its name */ /* Get the L2 network interface from its name */
@ -103,75 +98,40 @@ fn reply<'a, 'b>(packet: &'a [u8], masscanned: &Masscanned) -> Option<MutableEth
fn main() { fn main() {
/* parse arguments from CLI */ /* parse arguments from CLI */
let args = Command::new("Network responder - answer them all") let args = App::new("Network responder - answer them all")
.version(VERSION) .version(VERSION)
.about("Network answering machine for various network protocols (L2-L3-L4 + applications)") .about("Network answering machine for various network protocols (L2-L3-L4 + applications)")
.arg( .arg(
Arg::new("interface") Arg::with_name("interface")
.short('i') .short("i")
.long("iface") .long("iface")
.value_name("iface") .value_name("iface")
.help("the interface to use for receiving/sending packets") .help("the interface to use for receiving/sending packets")
.required(true) .required(true)
.num_args(1), .takes_value(true),
) )
.arg( .arg(
Arg::new("mac") Arg::with_name("mac")
.short('m') .short("a")
.long("mac-addr") .long("mac-addr")
.help("MAC address to use in the response packets") .help("MAC address to use in the response packets")
.num_args(1), .takes_value(true),
) )
.arg( .arg(
Arg::new("selfipfile") Arg::with_name("ip")
.long("self-ip-file") .short("f")
.help("File with the list of IP addresses handled by masscanned") .long("ip-addr-file")
.num_args(1), .help("File with the list of IP addresses to impersonate")
.takes_value(true),
) )
.arg( .arg(
Arg::new("selfiplist") Arg::with_name("verbosity")
.long("self-ip-list") .short("v")
.help("Inline list of IP addresses handled by masscanned, comma-separated") .multiple(true)
.num_args(1),
)
.arg(
Arg::new("remoteipdenyfile")
.long("remote-ip-deny-file")
.help(
"File with the list of IP addresses from which masscanned will ignore packets",
)
.num_args(1),
)
.arg(
Arg::new("remoteipdenylist")
.long("remote-ip-deny-list")
.help("Inline list of IP addresses from which masscanned will ignore packets")
.num_args(1),
)
.arg(
Arg::new("verbosity")
.short('v')
.action(ArgAction::Count)
.help("Increase message verbosity"), .help("Increase message verbosity"),
) )
.arg(
Arg::new("quiet")
.long("quiet")
.short('q')
.action(ArgAction::SetTrue)
.required(false)
.help("Quiet mode: do not output anything on stdout"),
)
.arg(
Arg::new("format")
.long("format")
.help("Format in which to output logs")
.default_value("console")
.value_parser(PossibleValuesParser::new(["console", "logfmt"]))
.num_args(1),
)
.get_matches(); .get_matches();
let verbose = args.value_source("verbosity").unwrap() as usize; let verbose = args.occurrences_of("verbosity") as usize;
/* initialise logger */ /* initialise logger */
stderrlog::new() stderrlog::new()
.module(module_path!()) .module(module_path!())
@ -183,24 +143,27 @@ fn main() {
debug!("debug messages enabled"); debug!("debug messages enabled");
trace!("trace messages enabled"); trace!("trace messages enabled");
info!("Command line arguments:"); info!("Command line arguments:");
for arg in &args.args {
info!("....{:?}", arg);
}
let iface = if let Some(i) = get_interface( let iface = if let Some(i) = get_interface(
args.get_one::<String>("interface") args.value_of("interface")
.expect("error parsing iface argument"), .expect("error parsing iface argument"),
) { ) {
i i
} else { } else {
error!( error!(
"Cannot open interface \"{}\" - are you sure it exists?", "Cannot open interface \"{}\" - are you sure it exists?",
args.get_one::<String>("interface") args.value_of("interface")
.expect("error parsing iface argument") .expect("error parsing iface argument")
); );
return; return;
}; };
if !iface.is_up() { if iface.flags & (netdevice::IFF_UP.bits() as u32) == 0 {
error!("specified interface is DOWN"); error!("specified interface is DOWN");
return; return;
} }
let mac = if let Some(m) = args.get_one::<String>("mac") { let mac = if let Some(m) = args.value_of("mac") {
MacAddr::from_str(m).expect("error parsing provided MAC address") MacAddr::from_str(m).expect("error parsing provided MAC address")
} else if let Some(m) = iface.mac { } else if let Some(m) = iface.mac {
m m
@ -209,9 +172,9 @@ fn main() {
}; };
/* Parse ip address file specified */ /* Parse ip address file specified */
/* FIXME: .and_then(|path| File::open(path).map(|file| )).unwrap_or_default() ? */ /* FIXME: .and_then(|path| File::open(path).map(|file| )).unwrap_or_default() ? */
let mut ip_list = if let Some(ref path) = args.get_one::<String>("selfipfile") { let ip_list = if let Some(ref path) = args.value_of("ip") {
if let Ok(file) = File::open(path) { if let Ok(file) = File::open(path) {
info!("parsing self ip file: {}", &path); info!("parsing ip address file: {}", &path);
file.extract_ip_addresses_only(None) file.extract_ip_addresses_only(None)
} else { } else {
HashSet::new() HashSet::new()
@ -219,74 +182,23 @@ fn main() {
} else { } else {
HashSet::new() HashSet::new()
}; };
if let Some(ip_inline) = args.get_one::<String>("selfiplist") { let ip_addresses = if !ip_list.is_empty() {
ip_list.extend(ip_inline.extract_ip_addresses_only(None));
}
let self_ip_list = if !ip_list.is_empty() {
for ip in &ip_list {
info!("binding........{}", ip);
}
Some(&ip_list)
} else {
info!("binding........0.0.0.0");
info!("binding........::");
None
};
/* Parse remote ip deny file specified */
let mut ip_list = if let Some(ref path) = args.get_one::<String>("remoteipdenyfile") {
if let Ok(file) = File::open(path) {
info!("parsing remote ip deny file: {}", &path);
file.extract_ip_addresses_only(None)
} else {
HashSet::new()
}
} else {
HashSet::new()
};
if let Some(ip_inline) = args.get_one::<String>("remoteipdenylist") {
ip_list.extend(ip_inline.extract_ip_addresses_only(None));
}
let remote_ip_deny_list = if !ip_list.is_empty() {
for ip in &ip_list {
info!("ignoring.......{}", ip);
}
Some(&ip_list) Some(&ip_list)
} else { } else {
None None
}; };
let masscanned = Masscanned {
let mut masscanned = Masscanned {
synack_key: [0, 0], synack_key: [0, 0],
mac, mac,
iface: Some(&iface), iface: Some(&iface),
self_ip_list, ip_addresses,
remote_ip_deny_list,
log: MetaLogger::new(),
}; };
info!("interface......{}", masscanned.iface.unwrap().name); info!("interface......{}", masscanned.iface.unwrap().name);
info!("mac address....{}", masscanned.mac); info!("mac address....{}", masscanned.mac);
if !args
.get_one::<bool>("quiet")
.expect("unexpected error parsing argument")
{
if let Some(format) = args.get_one::<String>("format") {
let chosen_logger: Box<dyn Logger> = match format.as_str() {
"console" => Box::new(ConsoleLogger::new()),
"logfmt" => Box::new(LogfmtLogger::new()),
// clap should already ensure we're using a valid format
_ => panic!("illegal format"),
};
masscanned.log.add(chosen_logger);
} else {
masscanned.log.add(Box::new(ConsoleLogger::new()));
}
masscanned.log.init();
}
let (mut tx, mut rx) = get_channel(masscanned.iface.unwrap()); let (mut tx, mut rx) = get_channel(masscanned.iface.unwrap());
loop { loop {
/* check if network interface is still up */ /* check if network interface is still up */
if !masscanned.iface.unwrap().is_up() { if masscanned.iface.unwrap().flags & (netdevice::IFF_UP.bits() as u32) == 0 {
error!("interface is DOWN - aborting"); error!("interface is DOWN - aborting");
break; break;
} }

View file

@ -1,92 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use crate::proto::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
////////////
// Common //
////////////
/// ### PacketDissector
/// A util class used to dissect fields.
#[derive(Debug, Clone)]
pub struct PacketDissector<T> {
pub i: usize,
pub state: T,
}
impl<T> PacketDissector<T> {
pub fn new(initial_state: T) -> PacketDissector<T> {
return PacketDissector {
i: 0,
state: initial_state,
};
}
pub fn next_state(&mut self, state: T) {
self.state = state;
self.i = 0;
}
pub fn next_state_when_i_reaches(&mut self, state: T, i: usize) {
if self.i == i {
self.next_state(state);
}
}
fn _read_usize(&mut self, byte: &u8, value: usize, next_state: T, size: usize) -> usize {
self.i += 1;
self.next_state_when_i_reaches(next_state, size);
(value << 8) + *byte as usize
}
fn _read_ulesize(&mut self, byte: &u8, value: usize, next_state: T, size: usize) -> usize {
let ret = value + ((*byte as usize) << (8 * self.i));
self.i += 1;
self.next_state_when_i_reaches(next_state, size);
ret
}
pub fn read_u16(&mut self, byte: &u8, value: u16, next_state: T) -> u16 {
self._read_usize(byte, value as usize, next_state, 2) as u16
}
pub fn read_ule16(&mut self, byte: &u8, value: u16, next_state: T) -> u16 {
self._read_ulesize(byte, value as usize, next_state, 2) as u16
}
pub fn read_u32(&mut self, byte: &u8, value: u32, next_state: T) -> u32 {
self._read_usize(byte, value as usize, next_state, 4) as u32
}
pub fn read_ule32(&mut self, byte: &u8, value: u32, next_state: T) -> u32 {
self._read_ulesize(byte, value as usize, next_state, 4) as u32
}
pub fn read_ule64(&mut self, byte: &u8, value: u64, next_state: T) -> u64 {
self._read_ulesize(byte, value as usize, next_state, 8) as u64
}
}
pub trait MPacket {
fn new() -> Self;
fn repl(
&self,
_masscanned: &Masscanned,
_client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>>;
fn parse(&mut self, byte: &u8);
fn parse_all(&mut self, bytes: &[u8]) {
for byte in bytes {
self.parse(byte);
}
}
}

View file

@ -1,93 +0,0 @@
// This file is part of masscanned.
// Copyright 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use strum_macros::EnumIter;
#[derive(PartialEq, Debug, Clone, Copy, EnumIter)]
pub enum DNSType {
NONE,
A,
TXT, // value: 16 - text strings
}
impl From<u16> for DNSType {
fn from(item: u16) -> Self {
match item {
1 => DNSType::A,
16 => DNSType::TXT,
_ => DNSType::NONE,
}
}
}
impl From<DNSType> for u16 {
fn from(item: DNSType) -> Self {
match item {
DNSType::A => 1,
DNSType::TXT => 16,
_ => 0,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, EnumIter)]
pub enum DNSClass {
NONE,
IN, // value: 1 - the Internet
CH, // value: 3 - the CHAOS class
}
impl From<u16> for DNSClass {
fn from(item: u16) -> Self {
match item {
1 => DNSClass::IN,
3 => DNSClass::CH,
_ => DNSClass::NONE,
}
}
}
impl From<DNSClass> for u16 {
fn from(item: DNSClass) -> Self {
match item {
DNSClass::IN => 1,
DNSClass::CH => 3,
_ => 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_parse() {
/* type TXT */
assert!(DNSType::from(1) == DNSType::A);
assert!(1 as u16 == DNSType::A.into());
assert!(DNSType::from(16) == DNSType::TXT);
assert!(16 as u16 == DNSType::TXT.into());
}
#[test]
fn class_parse() {
assert!(DNSClass::from(1) == DNSClass::IN);
assert!(1 as u16 == DNSClass::IN.into());
assert!(DNSClass::from(3) == DNSClass::CH);
assert!(3 as u16 == DNSClass::CH.into());
}
}

View file

@ -1,387 +0,0 @@
// This file is part of masscanned.
// Copyright 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use std::convert::TryFrom;
use crate::proto::dissector::{MPacket, PacketDissector};
use crate::proto::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
#[derive(PartialEq)]
pub enum DNSHeaderState {
Id,
Flags,
QDCount,
ANCount,
NSCount,
ARCount,
End,
}
pub struct DNSHeader {
pub d: PacketDissector<DNSHeaderState>,
pub id: u16,
pub flags: u16,
pub _qr: bool,
pub _opcode: u8,
pub _aa: bool,
pub _tc: bool,
pub _rd: bool,
pub _ra: bool,
pub _z: u8,
pub _rcode: u8,
pub qdcount: u16,
pub ancount: u16,
pub nscount: u16,
pub arcount: u16,
}
impl TryFrom<Vec<u8>> for DNSHeader {
type Error = &'static str;
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
let mut hdr = DNSHeader::new();
for b in item {
hdr.parse(&b);
}
if hdr.d.state == DNSHeaderState::End {
Ok(hdr)
} else {
Err("packet is incomplete")
}
}
}
impl From<&DNSHeader> for Vec<u8> {
fn from(item: &DNSHeader) -> Self {
let mut v = Vec::new();
/* id */
v.push((item.id >> 8) as u8);
v.push((item.id & 0xFF) as u8);
/* flags */
/* QR | OPCODE | AA | TC | RD */
v.push(
((item._qr as u8) << 7)
| (item._opcode << 3)
| ((item._aa as u8) << 2)
| ((item._tc as u8) << 1)
| (item._rd as u8),
);
/* AA | ZZZ | RCODE */
v.push(0);
/* qdcount */
v.push((item.qdcount >> 8) as u8);
v.push((item.qdcount & 0xFF) as u8);
/* ancount */
v.push((item.ancount >> 8) as u8);
v.push((item.ancount & 0xFF) as u8);
/* nscount */
v.push((item.nscount >> 8) as u8);
v.push((item.nscount & 0xFF) as u8);
/* arcount */
v.push((item.arcount >> 8) as u8);
v.push((item.arcount & 0xFF) as u8);
v
}
}
impl MPacket for DNSHeader {
fn new() -> Self {
DNSHeader {
d: PacketDissector::new(DNSHeaderState::Id),
id: 0,
flags: 0,
_qr: false,
_opcode: 0,
_aa: false,
_tc: false,
_rd: false,
_ra: false,
_z: 0,
_rcode: 0,
qdcount: 0,
ancount: 0,
nscount: 0,
arcount: 0,
}
}
fn parse(&mut self, byte: &u8) {
match self.d.state {
DNSHeaderState::Id => {
self.id = self.d.read_u16(byte, self.id, DNSHeaderState::Flags);
}
DNSHeaderState::Flags => {
self.flags = self.d.read_u16(byte, self.flags, DNSHeaderState::QDCount);
}
DNSHeaderState::QDCount => {
self.qdcount = self.d.read_u16(byte, self.qdcount, DNSHeaderState::ANCount);
}
DNSHeaderState::ANCount => {
self.ancount = self.d.read_u16(byte, self.ancount, DNSHeaderState::NSCount);
}
DNSHeaderState::NSCount => {
self.nscount = self.d.read_u16(byte, self.nscount, DNSHeaderState::ARCount);
}
DNSHeaderState::ARCount => {
self.arcount = self.d.read_u16(byte, self.arcount, DNSHeaderState::End);
}
DNSHeaderState::End => {}
}
/* we need this to be executed at the same call
* the state changes to End, hence it is not in the
* match structure
**/
if self.d.state == DNSHeaderState::End {
self._qr = (self.flags >> 15) == 1;
self._opcode = ((self.flags >> 11) & 0x0F) as u8;
self._aa = (self.flags >> 10) & 0x01 == 1;
self._tc = (self.flags >> 9) & 0x01 == 1;
self._rd = (self.flags >> 8) & 0x01 == 1;
self._ra = (self.flags >> 7) & 0x01 == 1;
self._z = ((self.flags >> 4) & 0x07) as u8;
self._rcode = (self.flags & 0x0F) as u8;
}
}
fn repl(
&self,
_masscanned: &Masscanned,
_client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
let mut r = DNSHeader::new();
r.id = self.id;
r._qr = true;
r._opcode = self._opcode;
r._aa = true;
r._tc = false;
/* RFC1035
* Recursion Desired - this bit may be set in a query and
* is copied into the response. */
r._rd = self._rd;
r._ra = false;
r.qdcount = self.qdcount;
r.ancount = self.qdcount;
Some(Vec::<u8>::from(&r))
}
}
#[cfg(test)]
mod tests {
use super::*;
use pnet::util::MacAddr;
use std::str::FromStr;
use crate::logger::MetaLogger;
#[test]
fn parse_all() {
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00";
let hdr = match DNSHeader::try_from(payload.to_vec()) {
Ok(_hdr) => _hdr,
Err(e) => panic!("error while parsing DNS header: {}", e),
};
assert!(hdr.d.state == DNSHeaderState::End);
assert!(hdr.id == 0xb307);
assert!(hdr.flags == 0x0100);
assert!(hdr._qr == false);
assert!(hdr._opcode == 0);
assert!(hdr._aa == false);
assert!(hdr._tc == false);
assert!(hdr._rd == true);
assert!(hdr._ra == false);
assert!(hdr._z == 0);
assert!(hdr._rcode == 0);
assert!(hdr.qdcount == 1);
assert!(hdr.ancount == 0);
assert!(hdr.nscount == 0);
assert!(hdr.arcount == 0);
assert!(Vec::<u8>::from(&hdr) == payload.to_vec());
/* KO */
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00";
match DNSHeader::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
};
}
#[test]
fn parse_byte_by_byte() {
/* OK */
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00";
let mut hdr = DNSHeader::new();
for b in payload {
assert!(hdr.d.state != DNSHeaderState::End);
hdr.parse(b);
}
assert!(hdr.d.state == DNSHeaderState::End);
assert!(hdr.id == 0xb307);
assert!(hdr.flags == 0x0100);
assert!(hdr._qr == false);
assert!(hdr._opcode == 0);
assert!(hdr._aa == false);
assert!(hdr._tc == false);
assert!(hdr._rd == true);
assert!(hdr._ra == false);
assert!(hdr._z == 0);
assert!(hdr._rcode == 0);
assert!(hdr.qdcount == 1);
assert!(hdr.ancount == 0);
assert!(hdr.nscount == 0);
assert!(hdr.arcount == 0);
assert!(Vec::<u8>::from(&hdr) == payload.to_vec());
/* KO */
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00";
let mut hdr = DNSHeader::new();
for b in payload {
hdr.parse(b);
}
assert!(hdr.d.state != DNSHeaderState::End);
}
fn consistency_qd_rr(qd: &DNSHeader, rr: &DNSHeader) {
assert!(rr.id == qd.id);
assert!(rr._qr == true);
assert!(rr._opcode == qd._opcode);
assert!(rr._aa == true);
assert!(rr._tc == false);
assert!(rr._rd == qd._rd);
assert!(rr._ra == false);
assert!(rr._z == 0);
assert!(rr._rcode == 0);
/* check flags */
assert!(rr.flags >> 15 == rr._qr as u16);
assert!((rr.flags >> 11) & 0xF == rr._opcode as u16);
assert!((rr.flags >> 10) & 0x1 == rr._aa as u16);
assert!((rr.flags >> 9) & 0x1 == rr._tc as u16);
assert!((rr.flags >> 8) & 0x1 == rr._rd as u16);
assert!((rr.flags >> 7) & 0x1 == rr._ra as u16);
assert!((rr.flags >> 4) & 0x7 == rr._z as u16);
assert!(rr.flags & 0xF == rr._rcode as u16);
assert!(rr.qdcount == qd.qdcount);
assert!(rr.ancount == qd.qdcount);
assert!(rr.nscount == 0);
assert!(rr.arcount == 0);
}
#[test]
fn repl_id() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let client_info = ClientInfo::new();
let mut hdr = DNSHeader::new();
hdr._qr = false;
for id in [0x1234, 0x4321, 0xffff, 0x0, 0x1337] {
hdr.id = id;
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
DNSHeader::try_from(r).unwrap()
} else {
panic!("expected DNS header answer, got None");
};
consistency_qd_rr(&hdr, &hdr_repl);
}
}
#[test]
fn repl_opcode() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let client_info = ClientInfo::new();
let mut hdr = DNSHeader::new();
hdr._qr = false;
/* opcode */
for opcode in 0..3 {
hdr._opcode = opcode;
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
DNSHeader::try_from(r).unwrap()
} else {
panic!("expected DNS header answer, got None");
};
consistency_qd_rr(&hdr, &hdr_repl);
}
}
#[test]
fn repl_rd() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let client_info = ClientInfo::new();
let mut hdr = DNSHeader::new();
hdr._qr = false;
/* rd */
for rd in [false, true] {
hdr._rd = rd;
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
DNSHeader::try_from(r).unwrap()
} else {
panic!("expected DNS header answer, got None");
};
consistency_qd_rr(&hdr, &hdr_repl);
}
}
#[test]
fn repl_ancount() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let client_info = ClientInfo::new();
let mut hdr = DNSHeader::new();
hdr._qr = false;
/* rd */
for qdcount in 0..16 {
hdr.qdcount = qdcount;
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
DNSHeader::try_from(r).unwrap()
} else {
panic!("expected DNS header answer, got None");
};
consistency_qd_rr(&hdr, &hdr_repl);
}
}
}

View file

@ -1,688 +0,0 @@
// This file is part of masscanned.
// Copyright 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use std::convert::TryFrom;
mod cst;
mod header;
use header::{DNSHeader, DNSHeaderState};
mod query;
use query::{DNSQuery, DNSQueryState};
mod rr;
use rr::{DNSRRState, DNSRR};
use crate::proto::dissector::{MPacket, PacketDissector};
use crate::proto::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
#[derive(PartialEq, Debug)]
enum DNSState {
Header,
Query,
Answer,
Authority,
Additional,
End,
}
pub struct DNSPacket {
d: PacketDissector<DNSState>,
header: DNSHeader,
qd: Vec<DNSQuery>,
rr: Vec<DNSRR>,
ns: Vec<DNSRR>,
ar: Vec<DNSRR>,
}
impl TryFrom<Vec<u8>> for DNSPacket {
type Error = &'static str;
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
let mut dns = DNSPacket::new();
for b in item {
dns.parse(&b);
}
if dns.d.state == DNSState::End {
Ok(dns)
} else {
Err("packet is incomplete")
}
}
}
impl From<&DNSPacket> for Vec<u8> {
fn from(item: &DNSPacket) -> Self {
let mut v = Vec::new();
v.extend(Vec::<u8>::from(&item.header));
for qd in &item.qd {
v.extend(Vec::<u8>::from(qd));
}
for rr in &item.rr {
v.extend(Vec::<u8>::from(rr));
}
for ns in &item.ns {
v.extend(Vec::<u8>::from(ns));
}
for ar in &item.ar {
v.extend(Vec::<u8>::from(ar));
}
v
}
}
impl MPacket for DNSPacket {
fn new() -> Self {
DNSPacket {
d: PacketDissector::new(DNSState::Header),
header: DNSHeader::new(),
qd: Vec::new(),
rr: Vec::new(),
ns: Vec::new(),
ar: Vec::new(),
}
}
fn parse(&mut self, byte: &u8) {
match self.d.state {
DNSState::Header => {
self.header.parse(byte);
if self.header.d.state == DNSHeaderState::End {
if self.header.qdcount > 0 {
self.qd.push(DNSQuery::new());
self.d.next_state(DNSState::Query);
} else if self.header.ancount > 0 {
self.rr.push(DNSRR::new());
self.d.next_state(DNSState::Answer);
} else if self.header.nscount > 0 {
self.d.next_state(DNSState::Authority);
} else if self.header.arcount > 0 {
self.d.next_state(DNSState::Additional);
} else {
self.d.next_state(DNSState::End);
}
}
}
DNSState::Query => {
let qdcount = self.qd.len();
self.qd[qdcount - 1].parse(byte);
if self.qd[qdcount - 1].d.state == DNSQueryState::End {
if self.header.qdcount as usize > self.qd.len() {
self.qd.push(DNSQuery::new());
} else if self.header.ancount > 0 {
self.rr.push(DNSRR::new());
self.d.next_state(DNSState::Answer);
} else if self.header.nscount > 0 {
self.d.next_state(DNSState::Authority);
} else if self.header.arcount > 0 {
self.d.next_state(DNSState::Additional);
} else {
self.d.next_state(DNSState::End);
}
}
}
DNSState::Answer => {
let ancount = self.rr.len();
self.rr[ancount - 1].parse(byte);
if self.rr[ancount - 1].d.state == DNSRRState::End {
if self.header.ancount as usize > self.rr.len() {
self.rr.push(DNSRR::new());
} else if self.header.nscount > 0 {
self.d.next_state(DNSState::Authority);
} else if self.header.arcount > 0 {
self.d.next_state(DNSState::Additional);
} else {
self.d.next_state(DNSState::End);
}
}
}
_ => {}
}
}
fn repl(
&self,
masscanned: &Masscanned,
client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
let mut ans = DNSPacket::new();
ans.header = if let Some(hdr) = self.header.repl(&masscanned, &client_info, None) {
if let Ok(h) = DNSHeader::try_from(hdr) {
h
} else {
return None;
}
} else {
return None;
};
/* reply to qd */
for qd in &self.qd {
if let Ok(q) = DNSQuery::try_from(Vec::<u8>::from(qd)) {
ans.qd.push(q);
} else {
return None;
}
if let Some(raw_rr) = qd.repl(&masscanned, &client_info, None) {
if let Ok(rr) = DNSRR::try_from(raw_rr) {
ans.rr.push(rr);
} else {
return None;
}
} else {
return None;
}
}
Some(Vec::<u8>::from(&ans))
}
}
#[cfg(test)]
mod tests {
use super::cst::{DNSClass, DNSType};
use super::*;
use pnet::util::MacAddr;
use std::net::{IpAddr, Ipv4Addr};
use std::str::FromStr;
use crate::logger::MetaLogger;
#[test]
fn parse_qd_all() {
/* OK */
/* scapy: DNS(id=0x1337,
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"))
**/
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01";
let dns = match DNSPacket::try_from(payload.to_vec()) {
Ok(_dns) => _dns,
Err(e) => panic!("error while parsing DNS packet: {}", e),
};
assert!(dns.header.id == 0x1337);
assert!(dns.header._qr == false);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == false);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 3);
assert!(dns.header.ancount == 0);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.qd.len() == 3);
assert!(
dns.qd[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[0].type_ == DNSType::A);
assert!(dns.qd[0].class == DNSClass::IN);
assert!(
dns.qd[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[1].type_ == DNSType::A);
assert!(dns.qd[1].class == DNSClass::IN);
assert!(
dns.qd[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[2].type_ == DNSType::A);
assert!(dns.qd[2].class == DNSClass::IN);
/* KO */
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00";
match DNSPacket::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
}
let payload = b"xxx";
match DNSPacket::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
}
}
#[test]
fn parse_qd_byte_by_byte() {
/* scapy: DNS(id=0x1337,
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"))
**/
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01";
let mut dns = DNSPacket::new();
for b in payload {
assert!(dns.d.state != DNSState::End);
dns.parse(&b);
}
assert!(dns.d.state == DNSState::End);
assert!(dns.header.id == 0x1337);
assert!(dns.header._qr == false);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == false);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 3);
assert!(dns.header.ancount == 0);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.qd.len() == 3);
assert!(
dns.qd[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[0].type_ == DNSType::A);
assert!(dns.qd[0].class == DNSClass::IN);
assert!(
dns.qd[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[1].type_ == DNSType::A);
assert!(dns.qd[1].class == DNSClass::IN);
assert!(
dns.qd[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[2].type_ == DNSType::A);
assert!(dns.qd[2].class == DNSClass::IN);
}
#[test]
fn parse_rr_all() {
/* OK */
/* scapy: DNS(id=1234, qr=True, aa=True, qd=None,
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
**/
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
let dns = match DNSPacket::try_from(payload.to_vec()) {
Ok(_dns) => _dns,
Err(e) => panic!("error while parsing DNS packet: {}", e),
};
assert!(dns.header.id == 1234);
assert!(dns.header._qr == true);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == true);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 0);
assert!(dns.header.ancount == 3);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.rr.len() == 3);
assert!(
dns.rr[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[0].type_ == DNSType::A);
assert!(dns.rr[0].class == DNSClass::IN);
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
assert!(
dns.rr[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[1].type_ == DNSType::A);
assert!(dns.rr[1].class == DNSClass::IN);
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
assert!(
dns.rr[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[2].type_ == DNSType::A);
assert!(dns.rr[2].class == DNSClass::IN);
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
/* KO */
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x04\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
match DNSPacket::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
}
let payload = b"xxx";
match DNSPacket::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
}
}
#[test]
fn parse_rr_byte_by_byte() {
/* scapy: DNS(id=1234, qr=True, aa=True, qd=None,
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
**/
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
let mut dns = DNSPacket::new();
for b in payload {
assert!(dns.d.state != DNSState::End);
dns.parse(&b);
}
assert!(dns.d.state == DNSState::End);
assert!(dns.header.id == 1234);
assert!(dns.header._qr == true);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == true);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 0);
assert!(dns.header.ancount == 3);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.rr.len() == 3);
assert!(
dns.rr[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[0].type_ == DNSType::A);
assert!(dns.rr[0].class == DNSClass::IN);
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
assert!(
dns.rr[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[1].type_ == DNSType::A);
assert!(dns.rr[1].class == DNSClass::IN);
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
assert!(
dns.rr[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[2].type_ == DNSType::A);
assert!(dns.rr[2].class == DNSClass::IN);
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
}
#[test]
fn parse_qd_rr_all() {
/* scapy: DNS(id=1234, qr=True, aa=True,
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"),
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
*/
let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
let dns = match DNSPacket::try_from(payload.to_vec()) {
Ok(_dns) => _dns,
Err(e) => panic!("error while parsing DNS packet: {}", e),
};
assert!(dns.header.id == 1234);
assert!(dns.header._qr == true);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == true);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 3);
assert!(dns.header.ancount == 3);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.qd.len() == 3);
assert!(
dns.qd[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[0].type_ == DNSType::A);
assert!(dns.qd[0].class == DNSClass::IN);
assert!(
dns.qd[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[1].type_ == DNSType::A);
assert!(dns.qd[1].class == DNSClass::IN);
assert!(
dns.qd[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[2].type_ == DNSType::A);
assert!(dns.qd[2].class == DNSClass::IN);
assert!(dns.rr.len() == 3);
assert!(
dns.rr[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[0].type_ == DNSType::A);
assert!(dns.rr[0].class == DNSClass::IN);
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
assert!(
dns.rr[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[1].type_ == DNSType::A);
assert!(dns.rr[1].class == DNSClass::IN);
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
assert!(
dns.rr[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[2].type_ == DNSType::A);
assert!(dns.rr[2].class == DNSClass::IN);
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
}
#[test]
fn parse_qr_rr_byte_by_byte() {
/* scapy: DNS(id=1234, qr=True, aa=True,
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"),
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
*/
let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
let mut dns = DNSPacket::new();
for b in payload {
assert!(dns.d.state != DNSState::End);
dns.parse(&b);
}
assert!(dns.d.state == DNSState::End);
assert!(dns.header.id == 1234);
assert!(dns.header._qr == true);
assert!(dns.header._opcode == 0);
assert!(dns.header._aa == true);
assert!(dns.header._tc == false);
assert!(dns.header._rd == true);
assert!(dns.header._ra == false);
assert!(dns.header._z == 0);
assert!(dns.header._rcode == 0);
assert!(dns.header.qdcount == 3);
assert!(dns.header.ancount == 3);
assert!(dns.header.nscount == 0);
assert!(dns.header.arcount == 0);
assert!(dns.qd.len() == 3);
assert!(
dns.qd[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[0].type_ == DNSType::A);
assert!(dns.qd[0].class == DNSClass::IN);
assert!(
dns.qd[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[1].type_ == DNSType::A);
assert!(dns.qd[1].class == DNSClass::IN);
assert!(
dns.qd[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.qd[2].type_ == DNSType::A);
assert!(dns.qd[2].class == DNSClass::IN);
assert!(dns.rr.len() == 3);
assert!(
dns.rr[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[0].type_ == DNSType::A);
assert!(dns.rr[0].class == DNSClass::IN);
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
assert!(
dns.rr[1].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[1].type_ == DNSType::A);
assert!(dns.rr[1].class == DNSClass::IN);
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
assert!(
dns.rr[2].name
== [
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(dns.rr[2].type_ == DNSType::A);
assert!(dns.rr[2].class == DNSClass::IN);
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
}
#[test]
fn reply_in_a() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let mut client_info = ClientInfo::new();
/* scapy: DNS(id=0x1337,
* qd=DNSQR(qname="www.example.com"))
**/
let payload = b"\x137\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00\x00\x01\x00\x01";
let dns = DNSPacket::try_from(payload.to_vec()).unwrap();
for ip in [
Ipv4Addr::new(127, 0, 0, 1),
Ipv4Addr::new(0, 0, 0, 0),
Ipv4Addr::new(4, 3, 2, 1),
] {
client_info.ip.dst = Some(IpAddr::V4(ip));
let ans = if let Some(a) = dns.repl(&masscanned, &client_info, None) {
DNSPacket::try_from(a).unwrap()
} else {
panic!("expected a reply, got None");
};
assert!(ans.header.id == 0x1337);
assert!(ans.header._qr == true);
assert!(ans.header._opcode == 0);
assert!(ans.header._aa == true);
assert!(ans.header._tc == false);
assert!(ans.header._rd == dns.header._rd);
assert!(ans.header._ra == false);
assert!(ans.header._z == 0);
assert!(ans.header._rcode == 0);
assert!(ans.header.qdcount == 1);
assert!(ans.header.ancount == 1);
assert!(ans.header.nscount == 0);
assert!(ans.header.arcount == 0);
assert!(ans.qd.len() == 1);
assert!(
ans.qd[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(ans.qd[0].type_ == DNSType::A);
assert!(ans.qd[0].class == DNSClass::IN);
assert!(ans.rr.len() == 1);
assert!(
ans.rr[0].name
== [
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00
]
);
assert!(ans.rr[0].type_ == DNSType::A);
assert!(ans.rr[0].class == DNSClass::IN);
assert!(ans.rr[0].rdata == ip.octets());
}
}
}

View file

@ -1,337 +0,0 @@
// This file is part of masscanned.
// Copyright 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use super::cst::{DNSClass, DNSType};
use super::rr::DNSRR;
use std::convert::TryFrom;
use std::net::IpAddr;
use crate::proto::dissector::{MPacket, PacketDissector};
use crate::proto::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
#[derive(PartialEq)]
pub enum DNSQueryState {
Name,
Type,
Class,
End,
}
pub struct DNSQuery {
pub d: PacketDissector<DNSQueryState>,
/* RFC 1035 - Section 4.1.2 */
pub name: Vec<u8>,
_u_type: u16,
pub type_: DNSType,
_u_class: u16,
pub class: DNSClass,
}
impl TryFrom<Vec<u8>> for DNSQuery {
type Error = &'static str;
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
let mut query = DNSQuery::new();
for b in item {
query.parse(&b);
}
if query.d.state == DNSQueryState::End {
Ok(query)
} else {
Err("packet is incomplete")
}
}
}
impl From<&DNSQuery> for Vec<u8> {
fn from(item: &DNSQuery) -> Self {
let mut v = Vec::new();
/* name */
v.extend(&item.name);
/* type */
v.push(((u16::from(item.type_)) >> 8) as u8);
v.push(((u16::from(item.type_)) & 0xFF) as u8);
/* class */
v.push(((u16::from(item.class)) >> 8) as u8);
v.push(((u16::from(item.class)) & 0xFF) as u8);
/* return */
v
}
}
impl MPacket for DNSQuery {
fn new() -> Self {
DNSQuery {
d: PacketDissector::new(DNSQueryState::Name),
name: Vec::new(),
_u_type: 0,
type_: DNSType::NONE,
_u_class: 0,
class: DNSClass::NONE,
}
}
fn parse(&mut self, byte: &u8) {
match self.d.state {
DNSQueryState::Name => {
self.name.push(*byte);
if *byte == 0 {
self.d.next_state(DNSQueryState::Type);
}
}
DNSQueryState::Type => {
self._u_type = self.d.read_u16(byte, self._u_type, DNSQueryState::Class);
}
DNSQueryState::Class => {
self._u_class = self.d.read_u16(byte, self._u_class, DNSQueryState::End);
}
DNSQueryState::End => {}
}
/* we need this to be executed at the same call
* the state changes to End, hence it is not in the
* match structure
**/
if self.d.state == DNSQueryState::End {
self.type_ = DNSType::from(self._u_type);
self.class = DNSClass::from(self._u_class);
}
}
fn repl(
&self,
_masscanned: &Masscanned,
client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
match self.class {
DNSClass::IN => {
match self.type_ {
DNSType::A => {
let mut rr = DNSRR::new();
/* copy request */
for b in &self.name {
rr.name.push(*b);
}
rr.type_ = DNSType::A;
rr.class = DNSClass::IN;
rr.ttl = 43200;
rr.rdata = match client_info.ip.dst {
Some(IpAddr::V4(ip)) => ip.octets().to_vec(),
Some(IpAddr::V6(_)) => Vec::new(),
None => Vec::new(),
};
rr.rdlen = rr.rdata.len() as u16;
Some(Vec::<u8>::from(&rr))
}
_ => None,
}
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pnet::util::MacAddr;
use std::net::{IpAddr, Ipv4Addr};
use std::str::FromStr;
use strum::IntoEnumIterator;
use crate::client::ClientInfoSrcDst;
use crate::logger::MetaLogger;
#[test]
fn parse_in_a_all() {
/* A */
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
let qr = match DNSQuery::try_from(payload.to_vec()) {
Ok(_qr) => _qr,
Err(e) => panic!("error while parsing DNS query: {}", e),
};
assert!(
qr.name
== [
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
0x63, 0x6f, 0x6d, 0x00
]
);
assert!(qr.type_ == DNSType::A);
assert!(qr.class == DNSClass::IN);
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
/* TXT */
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
let qr = match DNSQuery::try_from(payload.to_vec()) {
Ok(_qr) => _qr,
Err(e) => panic!("error while parsing DNS query: {}", e),
};
assert!(qr.type_ == DNSType::TXT);
assert!(qr.class == DNSClass::CH);
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
/* KO */
let payload = b"xxx";
match DNSQuery::try_from(payload.to_vec()) {
Ok(_) => panic!("parsing should have failed"),
Err(_) => {}
}
}
#[test]
fn parse_in_a_byte_by_byte() {
/* A */
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
let mut qr = DNSQuery::new();
for b in payload {
qr.parse(b);
}
assert!(qr.d.state == DNSQueryState::End);
assert!(
qr.name
== [
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
0x63, 0x6f, 0x6d, 0x00
]
);
assert!(qr.type_ == DNSType::A);
assert!(qr.class == DNSClass::IN);
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
/* TXT */
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
let mut qr = DNSQuery::new();
for b in payload {
qr.parse(b);
}
assert!(qr.d.state == DNSQueryState::End);
assert!(qr.type_ == DNSType::TXT);
assert!(qr.class == DNSClass::CH);
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
/* KO */
let payload = b"xxx";
let mut qr = DNSQuery::new();
for b in payload {
qr.parse(b);
}
assert!(qr.d.state != DNSQueryState::End);
}
#[test]
fn reply_in_a() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let ip_src = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let ip_dst = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2));
let client_info = ClientInfo {
mac: ClientInfoSrcDst {
src: None,
dst: None,
},
ip: ClientInfoSrcDst {
src: Some(ip_src),
dst: Some(ip_dst),
},
transport: None,
port: ClientInfoSrcDst {
src: None,
dst: None,
},
cookie: None,
};
/* TXT */
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
let mut qr = DNSQuery::new();
for b in payload {
qr.parse(b);
}
assert!(qr.type_ == DNSType::TXT);
assert!(qr.class == DNSClass::CH);
/* A */
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
let mut qr = DNSQuery::new();
for b in payload {
qr.parse(b);
}
assert!(
qr.name
== [
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
0x63, 0x6f, 0x6d, 0x00
]
);
assert!(qr.type_ == DNSType::A);
assert!(qr.class == DNSClass::IN);
let rr_raw = match qr.repl(&masscanned, &client_info, None) {
None => {
panic!()
}
Some(r) => r,
};
let mut rr = DNSRR::new();
for b in rr_raw {
rr.parse(&b);
}
assert!(rr.name == qr.name);
assert!(rr.type_ == DNSType::A);
assert!(rr.class == DNSClass::IN);
assert!(rr.ttl == 43200);
assert!(rr.rdata == [127, 0, 0, 2]);
}
#[test]
fn repl() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let client_info = ClientInfo::new();
/* exhaustive tests */
let supported: Vec<(DNSClass, DNSType)> = vec![(DNSClass::IN, DNSType::A)];
let mut qd = DNSQuery::new();
qd.name = vec![
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63,
0x6f, 0x6d, 0x00,
];
for c in DNSClass::iter() {
qd.class = c;
for t in DNSType::iter() {
qd.type_ = t;
if supported.contains(&(c, t)) {
if qd.repl(&masscanned, &client_info, None) == None {
panic!("expected reply, got None");
}
} else {
if qd.repl(&masscanned, &client_info, None) != None {
panic!("expected no reply, got one for {:?}, {:?}", c, t);
}
}
}
}
}
}

View file

@ -1,251 +0,0 @@
// This file is part of masscanned.
// Copyright 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use super::cst::{DNSClass, DNSType};
use std::convert::TryFrom;
use crate::proto::dissector::{MPacket, PacketDissector};
use crate::proto::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
#[derive(PartialEq, Debug)]
pub enum DNSRRState {
Name,
Type,
Class,
TTL,
RDLength,
RData,
End,
}
pub struct DNSRR {
pub d: PacketDissector<DNSRRState>,
/* RFC 1035 - Section 3.2.1 */
pub name: Vec<u8>,
_u_type: u16,
pub type_: DNSType,
_u_class: u16,
pub class: DNSClass,
pub ttl: u32,
pub rdlen: u16,
pub rdata: Vec<u8>,
}
impl From<&DNSRR> for Vec<u8> {
fn from(item: &DNSRR) -> Self {
/* CAUTION: for the rdlen field:
* - if item.rdlen is not 0, its value is packed
* - if item.rdlen = 0, then the length of item.rdata is used instead
*/
let mut v = Vec::new();
/* name */
for b in &item.name {
v.push(b.clone());
}
/* type */
let type_: u16 = item.type_.into();
v.push((type_ >> 8) as u8);
v.push((type_ & 0xFF) as u8);
/* class */
let class: u16 = item.class.into();
v.push((class >> 8) as u8);
v.push((class & 0xFF) as u8);
/* ttl */
v.push((item.ttl >> 24) as u8);
v.push((item.ttl >> 16) as u8);
v.push((item.ttl >> 8) as u8);
v.push((item.ttl & 0xFF) as u8);
/* rdlen */
let rdlen = if item.rdlen == 0 {
item.rdata.len() as u16
} else {
item.rdlen
};
v.push((rdlen >> 8) as u8);
v.push((rdlen & 0xFF) as u8);
/* rdata */
for b in &item.rdata {
v.push(b.clone());
}
v
}
}
impl TryFrom<Vec<u8>> for DNSRR {
type Error = &'static str;
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
let mut rr = DNSRR::new();
for b in item {
rr.parse(&b);
}
if rr.d.state == DNSRRState::End {
Ok(rr)
} else {
Err("packet is incomplete")
}
}
}
impl MPacket for DNSRR {
fn new() -> Self {
DNSRR {
d: PacketDissector::new(DNSRRState::Name),
name: Vec::new(),
_u_type: 0,
type_: DNSType::NONE,
_u_class: 0,
class: DNSClass::NONE,
rdlen: 0,
ttl: 0,
rdata: Vec::new(),
}
}
fn parse(&mut self, byte: &u8) {
match self.d.state {
DNSRRState::Name => {
self.name.push(*byte);
if *byte == 0 {
self.d.next_state(DNSRRState::Type);
}
}
DNSRRState::Type => {
self._u_type = self.d.read_u16(byte, self._u_type, DNSRRState::Class);
}
DNSRRState::Class => {
self._u_class = self.d.read_u16(byte, self._u_class, DNSRRState::TTL);
}
DNSRRState::TTL => {
self.ttl = self.d.read_u32(byte, self.ttl, DNSRRState::RDLength);
}
DNSRRState::RDLength => {
self.rdlen = self.d.read_u16(byte, self.rdlen, DNSRRState::RData);
/* when read the rdlen, check if len is 0 */
if self.d.state == DNSRRState::RData && self.rdlen == 0 {
self.d.state = DNSRRState::End;
}
}
DNSRRState::RData => {
self.rdata.push(*byte);
if self.rdata.len() == self.rdlen as usize {
self.d.next_state(DNSRRState::End);
}
}
DNSRRState::End => {}
}
/* we need this to be executed at the same call
* the state changes to End, hence it is not in the
* match structure
**/
if self.d.state == DNSRRState::End {
self.type_ = DNSType::from(self._u_type);
self.class = DNSClass::from(self._u_class);
}
}
fn repl(
&self,
_masscanned: &Masscanned,
_client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build() {
let mut rr = DNSRR::new();
rr.name = b"\x03www\x07example\x03com\x00".to_vec();
rr.class = DNSClass::IN;
rr.type_ = DNSType::A;
rr.ttl = 1234;
rr.rdlen = 4;
rr.rdata = b"\x7f\x00\x00\x01".to_vec();
assert!(Vec::<u8>::from(&rr) == b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x04\xd2\x00\x04\x7f\x00\x00\x01");
}
#[test]
fn parse_all() {
/*
* raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1"))
*/
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01";
let rr = match DNSRR::try_from(payload.to_vec()) {
Ok(r) => r,
Err(e) => panic!("error while parsing DNS RR: {}", e),
};
assert!(rr.name == b"\x03www\x07example\x03com\x00");
assert!(rr.class == DNSClass::IN);
assert!(rr.type_ == DNSType::A);
assert!(rr.rdata == b"\x7f\x00\x00\x01");
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
/*
* empty data
*/
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00";
let rr = match DNSRR::try_from(payload.to_vec()) {
Ok(r) => r,
Err(e) => panic!("error while parsing DNS RR: {}", e),
};
assert!(rr.name == b"\x03www\x07example\x03com\x00");
assert!(rr.class == DNSClass::IN);
assert!(rr.type_ == DNSType::A);
assert!(rr.rdata == b"");
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
}
#[test]
fn parse_byte_by_byte() {
/*
* raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1"))
*/
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01";
let mut rr = DNSRR::new();
for b in payload {
assert!(rr.d.state != DNSRRState::End);
rr.parse(b);
}
assert!(rr.d.state == DNSRRState::End);
assert!(rr.name == b"\x03www\x07example\x03com\x00");
assert!(rr.class == DNSClass::IN);
assert!(rr.type_ == DNSType::A);
assert!(rr.rdata == b"\x7f\x00\x00\x01");
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
/*
* empty data
*/
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00";
let mut rr = DNSRR::new();
for b in payload {
assert!(rr.d.state != DNSRRState::End);
rr.parse(b);
}
assert!(rr.name == b"\x03www\x07example\x03com\x00");
assert!(rr.class == DNSClass::IN);
assert!(rr.type_ == DNSType::A);
assert!(rr.rdata == b"");
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
}
}

View file

@ -1,59 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use log::*;
use std::io::Write;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use crate::client::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned;
pub const GHOST_PATTERN_SIGNATURE: &[u8; 5] = b"Gh0st";
pub fn repl<'a>(
_data: &'a [u8],
_masscanned: &Masscanned,
_client_info: &mut ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
debug!("receiving Gh0st data, sending one null byte payload");
// Packet structure:
// GHOST_PATTERN_SIGNATURE + [ packet size ] + [ uncompressed payload size ] + payload
let mut result = GHOST_PATTERN_SIGNATURE.to_vec();
let uncompressed_data = b"\x00";
let mut compressed_data = ZlibEncoder::new(Vec::new(), Compression::default());
compressed_data
.write_all(uncompressed_data)
.expect("Ghost: cannot decompress payload");
let mut compressed_data = compressed_data
.finish()
.expect("Ghost: cannot decompress payload");
let mut packet_len = compressed_data.len() + GHOST_PATTERN_SIGNATURE.len() + 4 * 2;
for _ in 0..4 {
result.push((packet_len % 256) as u8);
packet_len /= 256;
}
let mut uncompressed_len = uncompressed_data.len();
for _ in 0..4 {
result.push((uncompressed_len % 256) as u8);
uncompressed_len /= 256;
}
result.append(&mut compressed_data);
Some(result)
}

View file

@ -21,7 +21,6 @@ use lazy_static::lazy_static;
use std::str; use std::str;
use crate::client::ClientInfo; use crate::client::ClientInfo;
use crate::proto::{ProtocolState as GenericProtocolState, TCPControlBlock};
use crate::smack::{ use crate::smack::{
Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_INSENSITIVE, UNANCHORED_STATE, Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_INSENSITIVE, UNANCHORED_STATE,
}; };
@ -63,7 +62,7 @@ const HTTP_STATE_CONTENT: usize = 64;
const HTTP_STATE_FAIL: usize = 0xFFFF; const HTTP_STATE_FAIL: usize = 0xFFFF;
pub struct ProtocolState { struct ProtocolState {
state: usize, state: usize,
state_bis: usize, state_bis: usize,
smack_state: usize, smack_state: usize,
@ -224,39 +223,15 @@ pub fn repl<'a>(
data: &'a [u8], data: &'a [u8],
_masscanned: &Masscanned, _masscanned: &Masscanned,
_client_info: &ClientInfo, _client_info: &ClientInfo,
tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
debug!("receiving HTTP data"); debug!("receiving HTTP data");
let mut state = ProtocolState::new(); let mut pstate = ProtocolState::new();
let mut pstate = {
if let Some(t) = tcb {
match t.proto_state {
None => t.proto_state = Some(GenericProtocolState::HTTP(ProtocolState::new())),
Some(GenericProtocolState::HTTP(_)) => {}
_ => {
panic!()
}
};
if let Some(GenericProtocolState::HTTP(p)) = &mut t.proto_state {
p
} else {
panic!();
}
} else {
&mut state
}
};
http_parse(&mut pstate, data); http_parse(&mut pstate, data);
if pstate.state == HTTP_STATE_FAIL { if pstate.state == HTTP_STATE_FAIL {
debug!("data in not correctly formatted - not responding"); debug!("data in not correctly formatted - not responding");
debug!("pstate: {}", pstate.state); debug!("pstate: {}", pstate.state);
return None; return None;
} }
/* if not in CONTENT state, not responding yet (it means the client
* has not finished sending headers yet) */
if pstate.state != HTTP_STATE_CONTENT {
return None;
}
let content = "\ let content = "\
<html> <html>
<head><title>401 Authorization Required</title></head> <head><title>401 Authorization Required</title></head>
@ -292,12 +267,8 @@ WWW-Authenticate: Basic realm=\"Access to admin page\"
Some(repl_data) Some(repl_data)
} }
#[cfg(test)] #[test]
mod tests { fn test_http_verb() {
use super::*;
#[test]
fn test_http_verb() {
/* all at once */ /* all at once */
for verb in HTTP_VERBS.iter() { for verb in HTTP_VERBS.iter() {
let mut pstate = ProtocolState::new(); let mut pstate = ProtocolState::new();
@ -362,10 +333,10 @@ mod tests {
assert!(pstate.state == HTTP_STATE_FAIL); assert!(pstate.state == HTTP_STATE_FAIL);
assert!(pstate.smack_state == UNANCHORED_STATE); assert!(pstate.smack_state == UNANCHORED_STATE);
assert!(pstate.smack_id == NO_MATCH); assert!(pstate.smack_id == NO_MATCH);
} }
#[test] #[test]
fn test_http_request_line() { fn test_http_request_line() {
let mut pstate = ProtocolState::new(); let mut pstate = ProtocolState::new();
let data = "GET /index.php HTTP/1.1\r\n".as_bytes(); let data = "GET /index.php HTTP/1.1\r\n".as_bytes();
for i in 0..data.len() { for i in 0..data.len() {
@ -386,10 +357,10 @@ mod tests {
assert!(pstate.state == HTTP_STATE_FIELD_START); assert!(pstate.state == HTTP_STATE_FIELD_START);
} }
} }
} }
#[test] #[test]
fn test_http_request_field() { fn test_http_request_field() {
let mut pstate = ProtocolState::new(); let mut pstate = ProtocolState::new();
let req = "POST /index.php HTTP/2.0\r\n".as_bytes(); let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
http_parse(&mut pstate, req); http_parse(&mut pstate, req);
@ -403,10 +374,10 @@ mod tests {
let value = b": 0\r\n"; let value = b": 0\r\n";
http_parse(&mut pstate, value); http_parse(&mut pstate, value);
assert!(pstate.state == HTTP_STATE_FIELD_START); assert!(pstate.state == HTTP_STATE_FIELD_START);
} }
#[test] #[test]
fn test_http_request_no_field() { fn test_http_request_no_field() {
let mut pstate = ProtocolState::new(); let mut pstate = ProtocolState::new();
let req = "POST /index.php HTTP/2.0\r\n".as_bytes(); let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
http_parse(&mut pstate, req); http_parse(&mut pstate, req);
@ -414,5 +385,4 @@ mod tests {
let crlf = "\r\n".as_bytes(); let crlf = "\r\n".as_bytes();
http_parse(&mut pstate, crlf); http_parse(&mut pstate, crlf);
assert!(pstate.state == HTTP_STATE_CONTENT); assert!(pstate.state == HTTP_STATE_CONTENT);
}
} }

View file

@ -17,15 +17,13 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::*; use log::*;
use pnet::packet::ip::IpNextHeaderProtocols; use pnet::packet::ip::IpNextHeaderProtocols;
use std::convert::TryFrom; use std::collections::HashMap;
use std::sync::Mutex;
use crate::client::ClientInfo; use crate::client::ClientInfo;
use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE}; use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE};
use crate::Masscanned; use crate::Masscanned;
mod dns;
use dns::DNSPacket;
mod http; mod http;
use http::HTTP_VERBS; use http::HTTP_VERBS;
@ -33,38 +31,19 @@ mod stun;
use stun::{STUN_PATTERN_CHANGE_REQUEST, STUN_PATTERN_EMPTY, STUN_PATTERN_MAGIC}; use stun::{STUN_PATTERN_CHANGE_REQUEST, STUN_PATTERN_EMPTY, STUN_PATTERN_MAGIC};
mod ssh; mod ssh;
use ssh::{SSH_PATTERN_CLIENT_PROTOCOL_1, SSH_PATTERN_CLIENT_PROTOCOL_2}; use ssh::SSH_PATTERN_CLIENT_PROTOCOL;
mod ghost;
use ghost::GHOST_PATTERN_SIGNATURE;
mod rpc;
use rpc::{RPC_CALL_TCP, RPC_CALL_UDP};
mod smb;
use smb::{SMB1_PATTERN_MAGIC, SMB2_PATTERN_MAGIC};
mod dissector;
use dissector::MPacket;
// mod dissector;
// pub use dissector::PacketDissector;
//
mod tcb;
pub use tcb::{add_tcb, get_tcb, is_tcb_set, ProtocolState, TCPControlBlock};
const PROTO_NONE: usize = 0;
const PROTO_HTTP: usize = 1; const PROTO_HTTP: usize = 1;
const PROTO_STUN: usize = 2; const PROTO_STUN: usize = 2;
const PROTO_SSH: usize = 3; const PROTO_SSH: usize = 3;
const PROTO_GHOST: usize = 4;
const PROTO_RPC_TCP: usize = 5; struct TCPControlBlock {
const PROTO_RPC_UDP: usize = 6; proto_state: usize,
const PROTO_SMB1: usize = 7; }
const PROTO_SMB2: usize = 8;
lazy_static! { lazy_static! {
static ref PROTO_SMACK: Smack = proto_init(); static ref PROTO_SMACK: Smack = proto_init();
static ref CONTABLE: Mutex<HashMap<u32, TCPControlBlock>> = Mutex::new(HashMap::new());
} }
fn proto_init() -> Smack { fn proto_init() -> Smack {
@ -93,40 +72,10 @@ fn proto_init() -> Smack {
SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS, SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS,
); );
smack.add_pattern( smack.add_pattern(
SSH_PATTERN_CLIENT_PROTOCOL_2, SSH_PATTERN_CLIENT_PROTOCOL,
PROTO_SSH, PROTO_SSH,
SmackFlags::ANCHOR_BEGIN, SmackFlags::ANCHOR_BEGIN,
); );
smack.add_pattern(
SSH_PATTERN_CLIENT_PROTOCOL_1,
PROTO_SSH,
SmackFlags::ANCHOR_BEGIN,
);
smack.add_pattern(
GHOST_PATTERN_SIGNATURE,
PROTO_GHOST,
SmackFlags::ANCHOR_BEGIN,
);
smack.add_pattern(
RPC_CALL_TCP,
PROTO_RPC_TCP,
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
);
smack.add_pattern(
RPC_CALL_UDP,
PROTO_RPC_UDP,
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
);
smack.add_pattern(
SMB1_PATTERN_MAGIC,
PROTO_SMB1,
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
);
smack.add_pattern(
SMB2_PATTERN_MAGIC,
PROTO_SMB2,
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
);
smack.compile(); smack.compile();
smack smack
} }
@ -135,59 +84,50 @@ pub fn repl<'a>(
data: &'a [u8], data: &'a [u8],
masscanned: &Masscanned, masscanned: &Masscanned,
mut client_info: &mut ClientInfo, mut client_info: &mut ClientInfo,
mut tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
debug!("packet payload: {:?}", data); debug!("packet payload: {:?}", data);
let mut id; let mut id;
if client_info.transport == Some(IpNextHeaderProtocols::Tcp) && client_info.cookie == None { if client_info.transport == Some(IpNextHeaderProtocols::Tcp) && client_info.cookie == None {
error!("Unexpected empty cookie"); error!("Unexpected empty cookie");
return None; return None;
} else if let Some(t) = &mut tcb { } else if client_info.cookie != None {
/* proto over TCP */ /* proto over TCP */
let mut i = 0; let cookie = client_info.cookie.unwrap();
if t.proto_id == PROTO_NONE { let mut ct = CONTABLE.lock().unwrap();
let mut state = t.smack_state; if !ct.contains_key(&cookie) {
t.proto_id = PROTO_SMACK.search_next(&mut state, data, &mut i); ct.insert(
t.smack_state = state; cookie,
TCPControlBlock {
proto_state: BASE_STATE,
},
);
} }
id = t.proto_id; let mut i = 0;
let mut tcb = ct.get_mut(&cookie).unwrap();
let mut state = tcb.proto_state;
id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
tcb.proto_state = state;
} else { } else {
/* proto over else (e.g., UDP) */ /* proto over else (e.g., UDP) */
let mut i = 0; let mut i = 0;
let mut state = BASE_STATE; let mut state = BASE_STATE;
id = PROTO_SMACK.search_next(&mut state, data, &mut i); id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
/* because we are not over TCP, we can afford to assume end of pattern */ /* because we are not over TCP, we can afford to assume end of pattern */
if id == NO_MATCH { if id == NO_MATCH {
id = PROTO_SMACK.search_next_end(&mut state); id = PROTO_SMACK.search_next_end(&mut state);
} }
/* still no match: let us try to parse packet with protocoles
* that are not matched with a regex */
if id == NO_MATCH {
/* try to parse data as a DNS packet */
if let Ok(dns) = DNSPacket::try_from(data.to_vec()) {
if let Some(r) = dns.repl(&masscanned, &client_info, None) {
return Some(r);
}
}
}
} }
/* proto over else (e.g., UDP) */ /* proto over else (e.g., UDP) */
match id { if id == PROTO_HTTP {
PROTO_HTTP => http::repl(data, masscanned, client_info, tcb), return http::repl(data, masscanned, client_info);
PROTO_STUN => stun::repl(data, masscanned, &mut client_info, tcb), } else if id == PROTO_STUN {
PROTO_SSH => ssh::repl(data, masscanned, &mut client_info, tcb), return stun::repl(data, masscanned, &mut client_info);
PROTO_GHOST => ghost::repl(data, masscanned, &mut client_info, tcb), } else if id == PROTO_SSH {
PROTO_RPC_TCP => rpc::repl_tcp(data, masscanned, &mut client_info, tcb), return ssh::repl(data, masscanned, &mut client_info);
PROTO_RPC_UDP => rpc::repl_udp(data, masscanned, &mut client_info, tcb), } else {
PROTO_SMB1 => smb::repl_smb1(data, masscanned, &mut client_info, tcb), debug!("id: {}", id);
PROTO_SMB2 => smb::repl_smb2(data, masscanned, &mut client_info, tcb),
_ => {
if let Some(t) = &mut tcb {
t.proto_id = PROTO_NONE;
} }
None None
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -199,8 +139,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test] #[test]
fn test_proto_dispatch_stun() { fn test_proto_dispatch_stun() {
let mut client_info = ClientInfo::new(); let mut client_info = ClientInfo::new();
@ -215,9 +153,7 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
/***** TEST STUN - MAGIC *****/ /***** TEST STUN - MAGIC *****/
/* test payload is: /* test payload is:
@ -228,7 +164,7 @@ mod tests {
*/ */
let payload = let payload =
b"\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; b"\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got nothing"); panic!("expected an answer, got nothing");
@ -242,7 +178,7 @@ mod tests {
*/ */
let payload = let payload =
b"\x00\x01\x00\x00\xaa\xbb\xcc\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; b"\x00\x01\x00\x00\xaa\xbb\xcc\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got nothing"); panic!("expected an answer, got nothing");
@ -255,7 +191,7 @@ mod tests {
*/ */
let payload = let payload =
b"\x00\x01\x00\x08\x01\xdb\xd4]4\x9f\xe2RQ\x19\x05,\x93\x14f4\x00\x03\x00\x04\x00\x00\x00\x00"; b"\x00\x01\x00\x08\x01\xdb\xd4]4\x9f\xe2RQ\x19\x05,\x93\x14f4\x00\x03\x00\x04\x00\x00\x00\x00";
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got nothing"); panic!("expected an answer, got nothing");
@ -276,123 +212,28 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
/***** TEST SSH *****/ /***** TEST SSH *****/
let payloads = [ let payloads = [
"SSH-2.0-PUTTY\r\n", "SSH-2.0-PUTTY",
"SSH-2.0-Go\r\n", "SSH-2.0-Go",
"SSH-2.0-libssh2_1.4.3\r\n", "SSH-2.0-libssh2_1.4.3",
"SSH-2.0-PuTTY\r\n", "SSH-2.0-PuTTY",
"SSH-2.0-AsyncSSH_2.1.0\r\n", "SSH-2.0-AsyncSSH_2.1.0",
"SSH-2.0-libssh2_1.9.0\r\n", "SSH-2.0-libssh2_1.9.0",
"SSH-2.0-libssh2_1.7.0\r\n", "SSH-2.0-libssh2_1.7.0",
"SSH-2.0-8.35 FlowSsh: FlowSshNet_SftpStress54.38.116.473\r\n", "SSH-2.0-8.35 FlowSsh: FlowSshNet_SftpStress54.38.116.473",
"SSH-2.0-libssh_0.9.5\r\n", "SSH-2.0-libssh_0.9.5",
"SSH-2.0-OpenSSH_6.7p1 Raspbian-5+deb8u3\r\n", "SSH-2.0-OpenSSH_6.7p1 Raspbian-5+deb8u3",
"SSH-1.99-Cisco-1.25\r\n",
]; ];
for payload in payloads.iter() { for payload in payloads.iter() {
let _ssh_resp = let _ssh_resp = if let Some(r) = repl(payload.as_bytes(), &masscanned, &mut client_info)
if let Some(r) = repl(payload.as_bytes(), &masscanned, &mut client_info, None) { {
r r
} else { } else {
panic!("expected an answer, got nothing"); panic!("expected an answer, got nothing");
}; };
} }
} }
#[test]
fn test_proto_dispatch_ghost() {
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.port.src = Some(65000);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
/***** TEST GHOST *****/
let payloads = [
b"Gh0st\xad\x00\x00\x00\xe0\x00\x00\x00x\x9cKS``\x98\xc3\xc0\xc0\xc0\x06\xc4\x8c@\xbcQ\x96\x81\x81\tH\x07\xa7\x16\x95e&\xa7*\x04$&g+\x182\x94\xf6\xb000\xac\xa8rc\x00\x01\x11\xa0\x82\x1f\\`&\x83\xc7K7\x86\x19\xe5n\x0c9\x95n\x0c;\x84\x0f3\xac\xe8sch\xa8^\xcf4'J\x97\xa9\x82\xe30\xc3\x91h]&\x90\xf8\xce\x97S\xcbA4L?2=\xe1\xc4\x92\x86\x0b@\xf5`\x0cT\x1f\xae\xaf]\nr\x0b\x03#\xa3\xdc\x02~\x06\x86\x03+\x18m\xc2=\xfdtC,C\xfdL<<==\\\x9d\x19\x88\x00\xe5 \x02\x00T\xf5+\\"
];
for payload in payloads.iter() {
let _ghost_resp =
if let Some(r) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
r
} else {
panic!("expected an answer, got nothing");
};
}
}
#[test]
fn test_proto_dispatch_http() {
/* ensure that HTTP FSM does not answer until completion of request
* (at least headers) */
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.port.src = Some(65000);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
/***** TEST COMPLETE REQUEST *****/
let payload = b"GET / HTTP/1.1\r\n\r\n";
if let None = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
panic!("expected an answer, got nothing");
}
/***** TEST INCOMPLETE REQUEST *****/
let payload = b"GET / HTTP/1.1\r\n";
if let Some(_) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
panic!("expected no answer, got one");
}
}
#[test]
fn dispatch_dns() {
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: None,
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let mut client_info = ClientInfo::new();
client_info.ip.dst = Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
let payloads = [
b"\x04\xd2\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01",
];
for payload in payloads.iter() {
let dns_resp =
if let Some(r) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
r
} else {
panic!("expected an answer, got nothing");
};
if let Err(e) = DNSPacket::try_from(dns_resp) {
panic!("error trying to parse the DNS answer: {}", e);
}
}
}
} }

View file

@ -1,561 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use log::warn;
use std::convert::TryInto;
use std::net::IpAddr;
use crate::client::ClientInfo;
use crate::proto::{ProtocolState as GenericProtocolState, TCPControlBlock};
use crate::Masscanned;
// last fragment (1 bit) + fragment len (31 bits) / length XID (random) / message type: call (0) / RPC version (0-255) / Program: Portmap (99840 - 100095) / Program version (*, random versions used, see below) / / Procedure: ??? (0-255)
pub const RPC_CALL_TCP: &[u8; 28] =
b"********\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
// UDP: last fragment and fragment len are missing
pub const RPC_CALL_UDP: &[u8; 24] =
b"****\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
#[derive(Debug)]
enum RpcState {
Frag,
Xid,
MessageType,
RpcVersion,
Program,
ProgramVersion,
Procedure,
CredsFlavor,
CredsLen,
Creds,
VerifFlavor,
VerifLen,
Verif,
End,
}
#[derive(Debug)]
pub struct ProtocolState {
state: RpcState,
last_frag: bool,
frag_len: u32,
xid: u32,
message_type: u32,
rpc_version: u32,
program: u32,
prog_version: u32,
procedure: u32,
creds_flavor: u32,
creds_data: Vec<u8>,
verif_flavor: u32,
verif_data: Vec<u8>,
payload: Vec<u8>,
cur_len: u32,
data_len: u32,
}
struct Rpcb {
program: u32,
version: u32,
netid: String,
addr: String,
port: u16,
owner: String,
}
impl ProtocolState {
fn new() -> Self {
ProtocolState {
state: RpcState::Frag,
last_frag: false,
frag_len: 0,
xid: 0,
message_type: 0,
rpc_version: 0,
program: 0,
prog_version: 0,
procedure: 0,
creds_flavor: 0,
creds_data: Vec::<u8>::new(),
verif_flavor: 0,
verif_data: Vec::<u8>::new(),
payload: Vec::<u8>::new(),
cur_len: 0,
data_len: 0,
}
}
}
fn read_u32(pstate: &mut ProtocolState, byte: u8, value: u32, next_state: RpcState) -> u32 {
pstate.cur_len += 1;
if pstate.cur_len == 4 {
pstate.state = next_state;
pstate.cur_len = 0;
}
value * 256 + byte as u32
}
fn read_string(pstate: &mut ProtocolState, next_state: RpcState) {
pstate.data_len -= 1;
if pstate.data_len == 0 {
pstate.state = next_state;
}
}
fn rpc_parse(pstate: &mut ProtocolState, data: &[u8]) {
for byte in data {
match pstate.state {
RpcState::Frag => {
if pstate.cur_len == 0 {
match byte & 128 {
0 => pstate.last_frag = false,
_ => pstate.last_frag = true,
};
pstate.frag_len = (*byte & 127) as u32;
} else {
pstate.frag_len = *byte as u32;
}
pstate.cur_len += 1;
if pstate.cur_len == 4 {
pstate.state = RpcState::Xid;
pstate.cur_len = 0;
}
}
RpcState::Xid => {
pstate.xid = read_u32(pstate, *byte, pstate.xid, RpcState::MessageType)
}
RpcState::MessageType => {
pstate.message_type =
read_u32(pstate, *byte, pstate.message_type, RpcState::RpcVersion)
}
RpcState::RpcVersion => {
pstate.rpc_version = read_u32(pstate, *byte, pstate.rpc_version, RpcState::Program)
}
RpcState::Program => {
pstate.program = read_u32(pstate, *byte, pstate.program, RpcState::ProgramVersion)
}
RpcState::ProgramVersion => {
pstate.prog_version =
read_u32(pstate, *byte, pstate.prog_version, RpcState::Procedure)
}
RpcState::Procedure => {
pstate.procedure = read_u32(pstate, *byte, pstate.procedure, RpcState::CredsFlavor)
}
RpcState::CredsFlavor => {
pstate.creds_flavor =
read_u32(pstate, *byte, pstate.creds_flavor, RpcState::CredsLen)
}
RpcState::CredsLen => {
pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Creds);
if matches!(pstate.state, RpcState::Creds) && pstate.data_len == 0 {
pstate.state = RpcState::VerifFlavor
}
}
RpcState::Creds => {
pstate.creds_data.push(*byte);
read_string(pstate, RpcState::VerifFlavor)
}
RpcState::VerifFlavor => {
pstate.verif_flavor =
read_u32(pstate, *byte, pstate.verif_flavor, RpcState::VerifLen)
}
RpcState::VerifLen => {
pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Verif);
if matches!(pstate.state, RpcState::Verif) && pstate.cur_len == 0 {
pstate.state = RpcState::End
}
}
RpcState::Verif => {
pstate.verif_data.push(*byte);
read_string(pstate, RpcState::End)
}
RpcState::End => {
pstate.payload.push(*byte);
}
};
}
}
fn get_nth_byte(value: u32, nth: u8) -> u8 {
let shift = 8 * (3 - nth);
((value & (0xff << shift)) >> shift).try_into().unwrap()
}
fn push_u32(buffer: &mut Vec<u8>, data: u32) {
for i in 0..4 {
buffer.push(get_nth_byte(data, i));
}
}
fn push_string_pad(buffer: &mut Vec<u8>, data: String) {
let len: u32 = data.len().try_into().unwrap();
push_u32(buffer, len);
buffer.append(&mut data.as_bytes().to_vec());
if len % 4 != 0 {
for _ in 0..(4 - (len % 4)) {
buffer.append(&mut b"\x00".to_vec());
}
}
}
fn build_repl_portmap(pstate: &mut ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
let mut resp = Vec::<u8>::new();
match pstate.procedure {
// 0 => {}
3 => {
// getaddr / getport
// accepted state: 0 (RPC executed successfully)
resp.extend([0, 0, 0, 0]);
let localport = client_info.port.dst.unwrap();
match pstate.prog_version {
2 => {
push_u32(&mut resp, localport as u32);
}
3 | 4 => {
let addr = format!(
"{}.{}.{}",
client_info.ip.dst.unwrap(),
localport >> 8,
localport % 256
);
push_string_pad(&mut resp, addr);
}
_ => panic!("Wrong RPC version"),
}
}
4 => {
// dump
// accepted state: 0 (RPC executed successfully)
resp.extend([0, 0, 0, 0]);
let localaddr = client_info.ip.dst.unwrap();
let localport = client_info.port.dst.unwrap();
let netid = match localaddr {
IpAddr::V4(_) => "tcp",
IpAddr::V6(_) => "tcp6",
};
for rpcb in [
Rpcb {
program: 100000,
version: 2,
netid: netid.to_string(),
addr: format!("{}", localaddr),
port: localport,
owner: "superuser".to_string(),
},
Rpcb {
program: 100000,
version: 3,
netid: netid.to_string(),
addr: format!("{}", localaddr),
port: localport,
owner: "superuser".to_string(),
},
Rpcb {
program: 100000,
version: 4,
netid: netid.to_string(),
addr: format!("{}", localaddr),
port: localport,
owner: "superuser".to_string(),
},
] {
resp.append(&mut b"\x00\x00\x00\x01".to_vec()); // value follows: yes
push_u32(&mut resp, rpcb.program);
push_u32(&mut resp, rpcb.version);
match pstate.prog_version {
2 => {
push_u32(
&mut resp,
match rpcb.netid.as_str() {
"tcp" => 6,
"tcp6" => 6,
"udp" => 17,
"udp6" => 17,
_ => 0,
},
);
push_u32(&mut resp, localport as u32);
}
3 | 4 => {
push_string_pad(&mut resp, rpcb.netid);
push_string_pad(
&mut resp,
format!("{}.{}.{}", rpcb.addr, rpcb.port >> 8, rpcb.port & 0xff),
);
push_string_pad(&mut resp, rpcb.owner);
}
_ => panic!("Wrong RPC version"),
}
}
resp.append(&mut b"\x00\x00\x00\x00".to_vec()); // value follows: no
}
_ => {
// accepted state: 5 (program can't support procedure)
resp.extend([0, 0, 0, 5]);
}
}
warn!(
"RPC: Portmap version {}, procedure {}",
pstate.prog_version, pstate.procedure
);
resp
}
fn build_repl_unknownprog(pstate: &mut ProtocolState, _client_info: &ClientInfo) -> Vec<u8> {
warn!(
"Unknown program {}, procedure {}: accepted state 1",
pstate.program, pstate.procedure
);
// accepted state: 1 (remote hasn't exported program)
vec![0, 0, 0, 1]
}
fn build_repl(pstate: &mut ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
// TODO: test RPC versions, drop non calls?
let mut resp = Vec::<u8>::new();
push_u32(&mut resp, pstate.xid);
// message_type: 1 (reply)
// reply_state: 0 (accepted)
// verifier: 0 (auth null)
// verifier length: 0
resp.extend([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
if pstate.prog_version < 2 || pstate.prog_version > 4 {
/*
* Scanners (e.g., Nmap script rpc-grind) often use random
* values for program version to find out if a program is
* supported, so for any program, we answer with "remote can't
* support version" accepted state.
*/
// accepted state: 2 (remote can't support version)
// prog_version min: 2
// prog_version max: 4
let prog_version = match pstate.prog_version {
104316 => "104316 (Nmap probe TCP RPCCheck)".to_string(),
x => x.to_string(),
};
warn!(
"RPC: unsupported version {} for program {}",
prog_version, pstate.program
);
resp.extend([0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 4]);
} else if pstate.procedure == 0 {
/*
* RPC clients (e.g., Linux kernel NFS client, rpcbind CLI
* tool) would often send a NULL procedure (0) call before any
* real operation .
*/
// accepted state: 0 (RPC executed successfully)
warn!("RPC: NULL procedure call for program {}", pstate.program);
resp.extend([0, 0, 0, 0]);
} else {
let mut specif_resp = match pstate.program {
100000 => build_repl_portmap(pstate, client_info),
_ => build_repl_unknownprog(pstate, client_info),
};
resp.append(&mut specif_resp);
}
resp
}
pub fn repl_tcp<'a>(
data: &'a [u8],
_masscanned: &Masscanned,
client_info: &ClientInfo,
tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
let mut state = ProtocolState::new();
let mut pstate = {
if let Some(t) = tcb {
match t.proto_state {
None => t.proto_state = Some(GenericProtocolState::RPC(ProtocolState::new())),
Some(GenericProtocolState::RPC(_)) => {}
_ => {
panic!()
}
};
if let Some(GenericProtocolState::RPC(p)) = &mut t.proto_state {
p
} else {
panic!();
}
} else {
&mut state
}
};
rpc_parse(&mut pstate, data);
// warn!("RPC {:#?}", pstate);
let resp = match pstate.state {
RpcState::End => Some(build_repl(pstate, client_info)),
_ => None,
};
match resp {
Some(mut resp) => {
let length: u32 = resp.len().try_into().unwrap();
let mut final_resp = Vec::<u8>::new();
for i in 0..4 {
match i {
0 => final_resp.push(get_nth_byte(length, i) | 0x80),
_ => final_resp.push(get_nth_byte(length, i)),
};
}
final_resp.append(&mut resp);
Some(final_resp)
}
_ => None,
}
}
pub fn repl_udp<'a>(
data: &'a [u8],
_masscanned: &Masscanned,
client_info: &ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> {
let mut pstate = ProtocolState::new();
pstate.state = RpcState::Xid;
pstate.last_frag = true;
pstate.frag_len = data.len().try_into().unwrap();
rpc_parse(&mut pstate, data);
// warn!("RPC {:#?}", pstate);
match pstate.state {
RpcState::End => Some(build_repl(&mut pstate, client_info)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::ClientInfoSrcDst;
use std::net::Ipv4Addr;
const CLIENT_INFO: ClientInfo = ClientInfo {
mac: ClientInfoSrcDst {
src: None,
dst: None,
},
ip: ClientInfoSrcDst {
src: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 0))),
dst: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1))),
},
transport: None,
port: ClientInfoSrcDst {
src: Some(12345),
dst: Some(111),
},
cookie: None,
};
#[test]
fn test_probe_nmap() {
let mut pstate = ProtocolState::new();
rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
assert!(matches!(pstate.state, RpcState::End));
assert!(pstate.xid == 0x72fe1d13);
assert!(pstate.rpc_version == 2);
assert!(pstate.program == 100000);
assert!(pstate.prog_version == 104316);
assert!(pstate.procedure == 0);
assert!(pstate.creds_flavor == 0);
assert!(pstate.creds_data.len() == 0);
assert!(pstate.verif_flavor == 0);
assert!(pstate.verif_data.len() == 0);
let resp = build_repl(&mut pstate, &CLIENT_INFO);
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
}
#[test]
fn test_probe_nmap_udp() {
let mut pstate = ProtocolState::new();
pstate.state = RpcState::Xid;
rpc_parse(&mut pstate, b"\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
assert!(matches!(pstate.state, RpcState::End));
assert!(pstate.xid == 0x72fe1d13);
assert!(pstate.rpc_version == 2);
assert!(pstate.program == 100000);
assert!(pstate.prog_version == 104316);
assert!(pstate.procedure == 0);
assert!(pstate.creds_flavor == 0);
assert!(pstate.creds_data.len() == 0);
assert!(pstate.verif_flavor == 0);
assert!(pstate.verif_data.len() == 0);
let resp = build_repl(&mut pstate, &CLIENT_INFO);
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
}
#[test]
fn test_probe_nmap_split1() {
let mut pstate = ProtocolState::new();
for byte in b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" {
rpc_parse(&mut pstate, &[*byte]);
}
assert!(matches!(pstate.state, RpcState::End));
assert!(pstate.xid == 0x72fe1d13);
assert!(pstate.rpc_version == 2);
assert!(pstate.program == 100000);
assert!(pstate.prog_version == 104316);
assert!(pstate.procedure == 0);
assert!(pstate.creds_flavor == 0);
assert!(pstate.creds_data.len() == 0);
assert!(pstate.verif_flavor == 0);
assert!(pstate.verif_data.len() == 0);
let resp = build_repl(&mut pstate, &CLIENT_INFO);
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
}
#[test]
fn test_probe_nmap_split2() {
let mut pstate = ProtocolState::new();
for data in [
b"\x80\x00\x00\x28\x72\xfe\x1d",
b"\x13\x00\x00\x00\x00\x00\x00",
b"\x00\x02\x00\x01\x86\xa0\x00",
b"\x01\x97\x7c\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x00\x00\x00",
] {
rpc_parse(&mut pstate, data);
}
rpc_parse(&mut pstate, b"\x00\x00");
assert!(matches!(pstate.state, RpcState::End));
assert!(pstate.xid == 0x72fe1d13);
assert!(pstate.rpc_version == 2);
assert!(pstate.program == 100000);
assert!(pstate.prog_version == 104316);
assert!(pstate.procedure == 0);
assert!(pstate.creds_flavor == 0);
assert!(pstate.creds_data.len() == 0);
assert!(pstate.verif_flavor == 0);
assert!(pstate.verif_data.len() == 0);
let resp = build_repl(&mut pstate, &CLIENT_INFO);
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
}
#[test]
fn test_probe_portmap_v4_dump() {
let mut pstate = ProtocolState::new();
rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x01\x1b\x60\xa6\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
assert!(matches!(pstate.state, RpcState::End));
assert!(pstate.rpc_version == 2);
assert!(pstate.program == 100000);
assert!(pstate.prog_version == 4);
assert!(pstate.procedure == 4); // dump
assert!(pstate.creds_flavor == 0);
assert!(pstate.creds_data.len() == 0);
assert!(pstate.verif_flavor == 0);
assert!(pstate.verif_data.len() == 0);
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,635 +16,21 @@
use log::*; use log::*;
use std::str;
use crate::client::ClientInfo; use crate::client::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::utils::byte2str;
use crate::Masscanned; use crate::Masscanned;
pub const SSH_PATTERN_CLIENT_PROTOCOL_2: &[u8; 7] = b"SSH-2.0"; pub const SSH_PATTERN_CLIENT_PROTOCOL: &[u8; 7] = b"SSH-2.0";
pub const SSH_PATTERN_CLIENT_PROTOCOL_1: &[u8; 8] = b"SSH-1.99";
const SSH_STATE_START: usize = 0;
const SSH_STATE_S1: usize = 1;
const SSH_STATE_S2: usize = 2;
const SSH_STATE_H: usize = 3;
const SSH_STATE_DASH: usize = 4;
const SSH_STATE_VERSION: usize = 5;
const SSH_STATE_SOFTWARE: usize = 6;
const SSH_STATE_COMMENT: usize = 7;
const SSH_STATE_EOB: usize = 8;
const SSH_STATE_LF: usize = 9;
const SSH_STATE_FAIL: usize = 0xFFFF;
struct ProtocolState {
state: usize,
prev_state: usize,
ssh_version: Vec<u8>,
ssh_software: Vec<u8>,
ssh_comment: Vec<u8>,
}
impl ProtocolState {
fn new() -> Self {
ProtocolState {
state: SSH_STATE_START,
prev_state: SSH_STATE_START,
ssh_version: Vec::<u8>::new(),
ssh_software: Vec::<u8>::new(),
ssh_comment: Vec::<u8>::new(),
}
}
}
fn ssh_parse(pstate: &mut ProtocolState, data: &[u8]) {
/* RFC 4253:
*
* 4.2. Protocol Version Exchange
*
* When the connection has been established, both sides MUST send an
* identification string. This identification string MUST be
*
* SSH-protoversion-softwareversion SP comments CR LF
*
* Since the protocol being defined in this set of documents is version
* 2.0, the 'protoversion' MUST be "2.0". The 'comments' string is
* OPTIONAL. If the 'comments' string is included, a 'space' character
* (denoted above as SP, ASCII 32) MUST separate the 'softwareversion'
* and 'comments' strings. The identification MUST be terminated by a
* single Carriage Return (CR) and a single Line Feed (LF) character
* (ASCII 13 and 10, respectively). Implementers who wish to maintain
* compatibility with older, undocumented versions of this protocol may
* want to process the identification string without expecting the
* presence of the carriage return character for reasons described in
* Section 5 of this document. The null character MUST NOT be sent.
* The maximum length of the string is 255 characters, including the
* Carriage Return and Line Feed.
*/
let mut i = 0;
while i < data.len() {
match pstate.state {
SSH_STATE_START => {
pstate.state = SSH_STATE_S1;
continue;
}
/* first bytes should be "SSH-" */
SSH_STATE_S1 | SSH_STATE_S2 | SSH_STATE_H | SSH_STATE_DASH => {
if data[i] != b"SSH-"[pstate.state - SSH_STATE_S1] {
pstate.state = SSH_STATE_FAIL;
} else {
pstate.state += 1;
}
}
/* expect LF after a CR was read */
SSH_STATE_LF => {
if data[i] == b'\n' {
pstate.state = SSH_STATE_EOB;
} else {
if pstate.prev_state == SSH_STATE_SOFTWARE {
/* when reading software, \r can be followed by something else than \n */
pstate.state = pstate.prev_state;
/* cancel the read of this char */
i -= 1;
/* add the previously read \r to the software string */
pstate.ssh_software.push(b'\r');
} else if pstate.prev_state == SSH_STATE_COMMENT {
/* when reading comment, \r can be followed by something else than \n */
pstate.state = pstate.prev_state;
/* cancel the read of this char */
i -= 1;
/* add the previously read \r to the software string */
pstate.ssh_comment.push(b'\r');
} else {
/* in some other cases, it fails */
pstate.state = SSH_STATE_FAIL;
}
}
}
SSH_STATE_VERSION => {
if data[i] == b'-' {
pstate.state = SSH_STATE_SOFTWARE;
} else if !data[i].is_ascii_digit() && data[i] != b'.' {
pstate.state = SSH_STATE_FAIL;
} else {
pstate.ssh_version.push(data[i]);
}
}
SSH_STATE_SOFTWARE => {
if data[i] == b'\r' {
/* look for LF in the next char */
pstate.prev_state = pstate.state;
pstate.state = SSH_STATE_LF;
} else if data[i] == b' ' {
pstate.state = SSH_STATE_COMMENT;
} else {
pstate.ssh_software.push(data[i]);
}
}
SSH_STATE_COMMENT => {
if data[i] == b'\r' {
/* look for LF in the next char */
pstate.prev_state = pstate.state;
pstate.state = SSH_STATE_LF;
} else {
pstate.ssh_comment.push(data[i]);
}
}
SSH_STATE_FAIL => {
return;
}
SSH_STATE_EOB => { /* so far, do not parse after banner */ }
_ => {}
};
i += 1;
}
}
pub fn repl<'a>( pub fn repl<'a>(
data: &'a [u8], data: &'a [u8],
_masscanned: &Masscanned, _masscanned: &Masscanned,
mut _client_info: &ClientInfo, mut _client_info: &mut ClientInfo,
_tcb: Option<&mut TCPControlBlock>,
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
debug!("receiving SSH data"); debug!("receiving SSH data");
let mut pstate = ProtocolState::new();
ssh_parse(&mut pstate, data);
if pstate.state != SSH_STATE_EOB {
debug!("data in not correctly formatted - not responding");
debug!("pstate: {}", pstate.state);
return None;
}
let repl_data = b"SSH-2.0-1\r\n".to_vec(); let repl_data = b"SSH-2.0-1\r\n".to_vec();
debug!("sending SSH answer"); debug!("sending SSH answer");
warn!("SSH server banner to {}", byte2str(&pstate.ssh_software)); warn!("SSH server banner to {}", str::from_utf8(&data).unwrap().trim_end());
Some(repl_data) return Some(repl_data);
}
#[cfg(test)]
mod tests {
use super::*;
/* Reconstruct client's banner from the parsed information */
fn ssh_banner(pstate: &ProtocolState) -> Vec<u8> {
let mut banner = b"SSH-".to_vec();
for b in &pstate.ssh_version {
banner.push(*b);
}
banner.push(b'-');
for b in &pstate.ssh_software {
banner.push(*b);
}
if pstate.ssh_comment.len() > 0 {
banner.push(b' ');
for b in &pstate.ssh_comment {
banner.push(*b);
}
}
banner.push(b'\r');
banner.push(b'\n');
banner
}
#[test]
fn ssh_2_banner_parse() {
/* all at once */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
/* byte by byte */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
for i in 0..test_banner.len() {
if i == 0 {
assert!(pstate.state == SSH_STATE_START);
} else if i > 0 && i < 4 {
assert!(pstate.state == SSH_STATE_S1 + i);
} else if i >= 4 && i < 8 {
assert!(pstate.state == SSH_STATE_VERSION);
} else if i >= 8 && i < 17 {
assert!(pstate.state == SSH_STATE_SOFTWARE);
} else if i >= 17 && i < test_banner.len() - 1 {
assert!(pstate.state == SSH_STATE_COMMENT);
} else {
assert!(pstate.state == SSH_STATE_LF);
}
ssh_parse(&mut pstate, &test_banner[i..i + 1]);
}
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_1_banner_parse() {
/* all at once */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* byte by byte */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
for i in 0..test_banner.len() {
if i == 0 {
assert!(pstate.state == SSH_STATE_START);
} else if i > 0 && i < 4 {
assert!(pstate.state == SSH_STATE_S1 + i);
} else if i >= 4 && i < 9 {
assert!(pstate.state == SSH_STATE_VERSION);
} else if i >= 9 && i < 18 {
assert!(pstate.state == SSH_STATE_SOFTWARE);
} else if i >= 18 && i < test_banner.len() - 1 {
assert!(pstate.state == SSH_STATE_COMMENT);
} else {
assert!(pstate.state == SSH_STATE_LF);
}
ssh_parse(&mut pstate, &test_banner[i..i + 1]);
}
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_2_banner_space() {
/* space in SSH */
let test_banner = b"S SH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* space in VERSION */
let test_banner = b"SSH-2. 0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* space in software */
let test_banner = b"SSH-2.0-SOFT WARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFT");
assert!(pstate.ssh_comment == b"WARE COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* space in comment */
let test_banner = b"SSH-2.0-SOFTWARE COM MENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM MENT");
assert!(ssh_banner(&pstate) == test_banner);
/* double space */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b" COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_1_banner_space() {
/* space in SSH */
let test_banner = b"S SH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* space in VERSION */
let test_banner = b"SSH-1. 99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* space in software */
let test_banner = b"SSH-1.99-SOFT WARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFT");
assert!(pstate.ssh_comment == b"WARE COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* space in comment */
let test_banner = b"SSH-1.99-SOFTWARE COM MENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM MENT");
assert!(ssh_banner(&pstate) == test_banner);
/* double space */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b" COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_2_banner_cr() {
/* CR in SSH */
let test_banner = b"S\rSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CR in VERSION */
let test_banner = b"SSH-2.\r0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CR in SOFTWARE */
let test_banner = b"SSH-2.0-SOFT\rWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFT\rWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* CR in COMMENT */
let test_banner = b"SSH-2.0-SOFTWARE COM\rMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM\rMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* CR at the end */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT\r");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_1_banner_cr() {
/* CR in SSH */
let test_banner = b"S\rSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CR in VERSION */
let test_banner = b"SSH-1.\r99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CR in SOFTWARE */
let test_banner = b"SSH-1.99-SOFT\rWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFT\rWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* CR in COMMENT */
let test_banner = b"SSH-1.99-SOFTWARE COM\rMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM\rMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* CR at the end */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT\r");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_2_banner_lf() {
/* LF in SSH */
let test_banner = b"S\nSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* LF in VERSION */
let test_banner = b"SSH-2.\n0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* LF in SOFTWARE */
let test_banner = b"SSH-2.0-SOFT\nWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFT\nWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* LF in COMMENT */
let test_banner = b"SSH-2.0-SOFTWARE COM\nMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM\nMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* LF at the end */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\n\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT\n");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_1_banner_lf() {
/* LF in SSH */
let test_banner = b"S\nSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* LF in VERSION */
let test_banner = b"SSH-1.\n99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* LF in SOFTWARE */
let test_banner = b"SSH-1.99-SOFT\nWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFT\nWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* LF in COMMENT */
let test_banner = b"SSH-1.99-SOFTWARE COM\nMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM\nMENT");
assert!(ssh_banner(&pstate) == test_banner);
/* LF at the end */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\n\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT\n");
assert!(ssh_banner(&pstate) == test_banner);
}
#[test]
fn ssh_2_banner_crlf() {
/* CRLF in SSH */
let test_banner = b"S\r\nSH-2.0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CRLF in VERSION */
let test_banner = b"SSH-2.\r\n0-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CRLF in SOFTWARE */
let test_banner = b"SSH-2.0-SOFT\r\nWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFT");
assert!(pstate.ssh_comment == b"");
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFT\r\n");
/* CRLF in COMMENT */
let test_banner = b"SSH-2.0-SOFTWARE COM\r\nMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM");
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFTWARE COM\r\n");
/* CRLF at the end */
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"2.0");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFTWARE COMMENT\r\n");
}
#[test]
fn ssh_1_banner_crlf() {
/* CRLF in SSH */
let test_banner = b"S\r\nSH-1.99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CRLF in VERSION */
let test_banner = b"SSH-1.\r\n99-SOFTWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_FAIL);
/* CRLF in SOFTWARE */
let test_banner = b"SSH-1.99-SOFT\r\nWARE COMMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFT");
assert!(pstate.ssh_comment == b"");
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFT\r\n");
/* CRLF in COMMENT */
let test_banner = b"SSH-1.99-SOFTWARE COM\r\nMENT\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COM");
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFTWARE COM\r\n");
/* CRLF at the end */
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n\r\n";
let mut pstate = ProtocolState::new();
assert!(pstate.state == SSH_STATE_START);
ssh_parse(&mut pstate, test_banner);
assert!(pstate.state == SSH_STATE_EOB);
assert!(pstate.ssh_version == b"1.99");
assert!(pstate.ssh_software == b"SOFTWARE");
assert!(pstate.ssh_comment == b"COMMENT");
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFTWARE COMMENT\r\n");
}
} }

View file

@ -24,7 +24,6 @@ use byteorder::{BigEndian, ByteOrder};
use std::io; use std::io;
use crate::client::ClientInfo; use crate::client::ClientInfo;
use crate::proto::TCPControlBlock;
use crate::Masscanned; use crate::Masscanned;
/* RFC 5389: The magic cookie field MUST contain the fixed value 0x2112A442 in /* RFC 5389: The magic cookie field MUST contain the fixed value 0x2112A442 in
@ -258,6 +257,107 @@ impl Into<Vec<u8>> for &StunAttribute {
} }
} }
/*
struct StunPacket {
class: u8,
method: u16,
length: u16,
magic: u32,
id: u128,
data: Vec<u8>,
attributes: Vec<StunAttribute>,
}
impl StunPacket {
fn new(data: &[u8]) -> Result<Self, io::Error> {
if data.len() < 20 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not enough data",
));
}
let class: u8 = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4);
let method: u16 = (((data[0] & 0b00111110) << 7) as u16) | ((data[1] & 0b11101111) as u16);
let length: u16 = BigEndian::read_u16(&data[2..4]);
let magic: u32 = BigEndian::read_u32(&data[4..8]);
let id: u128 = ((BigEndian::read_u64(&data[8..16]) as u128) << 32)
| (BigEndian::read_u32(&data[16..20]) as u128);
if data.len() < 20 + length as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not enough data",
));
}
let data: Vec<u8> = data[20..(20 + length) as usize].to_vec();
let mut stun = StunPacket {
class,
method,
length,
magic,
id,
data,
attributes: Vec::<StunAttribute>::new(),
};
stun.attributes = stun.get_attributes();
Ok(stun)
}
fn empty() -> Self {
StunPacket {
class: 0,
method: 0,
length: 0,
magic: 0,
id: 0,
data: Vec::new(),
attributes: Vec::new(),
}
}
fn get_attributes(&self) -> Vec<StunAttribute> {
let mut i = 0;
let mut attributes = Vec::<StunAttribute>::new();
while i + 4 < self.data.len() {
let attr = StunAttribute::from(self.data[i..].to_vec());
i += 4 + attr.len() as usize;
attributes.push(attr);
}
attributes
}
fn set_length(&mut self) {
self.length = 0;
for attr in &self.attributes {
self.length += 4 + attr.len();
}
}
}
impl Into<Vec<u8>> for StunPacket {
fn into(self) -> Vec<u8> {
let mut v = Vec::<u8>::new();
// first cocktail with class and method bits
v.push(
TryInto::<u8>::try_into((self.method >> 7) & 0b00111110).unwrap()
| TryInto::<u8>::try_into((self.class & 0b10) >> 1).unwrap(),
);
// second cocktail with class and method bits
v.push(
TryInto::<u8>::try_into((self.method & 0b01110000) << 1).unwrap()
| TryInto::<u8>::try_into((self.class & 0b01) << 4).unwrap()
| TryInto::<u8>::try_into(self.method & 0b00001111).unwrap(),
);
v.append(&mut self.length.to_be_bytes().to_vec());
v.append(&mut self.magic.to_be_bytes().to_vec());
v.append(&mut self.id.to_be_bytes()[4..].to_vec());
for attr in &self.attributes {
v.append(&mut attr.into());
}
v
}
}
*/
struct StunPacket { struct StunPacket {
class: u8, class: u8,
method: u16, method: u16,
@ -351,11 +451,67 @@ impl Into<Vec<u8>> for StunPacket {
} }
} }
/*
pub fn repl<'a>( pub fn repl<'a>(
data: &'a [u8], data: &'a [u8],
_masscanned: &Masscanned, _masscanned: &Masscanned,
client_info: &mut ClientInfo, client_info: ClientInfo,
_tcb: Option<&mut TCPControlBlock>, ) -> Option<Vec<u8>> {
debug!("receiving STUN data");
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
s
} else {
return None;
};
if stun_req.class != STUN_CLASS_REQUEST {
info!(
"STUN packet not handled (class unknown: 0b{:b})",
stun_req.class
);
return None;
}
if stun_req.method != STUN_METHOD_BINDING {
info!(
"STUN packet not handled (method unknown: 0x{:03x})",
stun_req.method
);
return None;
}
/*
* To be compatible with RFC3489: ignore magic
if stun_req.magic != STUN_MAGIC {
info!(
"STUN packet not handled (magic unknown: 0x{:04x})",
stun_req.magic
);
return None;
}
*/
if client_info.ip.src == None {
error!("STUN packet not handled (expected client ip address not found)");
return None;
}
if client_info.port.src == None {
error!("STUN packet not handled (expected client port address not found)");
return None;
}
let mut stun_resp: StunPacket = StunPacket::empty();
stun_resp.class = STUN_CLASS_SUCCESS_RESPONSE;
stun_resp.method = STUN_METHOD_BINDING;
stun_resp.id = stun_req.id;
stun_resp.attributes = Vec::<StunAttribute>::new();
stun_resp.attributes.push(StunAttribute::MappedAddress(
StunMappedAddressAttribute::new(client_info.ip.src.unwrap(), client_info.port.src.unwrap()),
));
stun_resp.set_length();
return Some(stun_resp.into());
}
*/
pub fn repl<'a>(
data: &'a [u8],
_masscanned: &Masscanned,
mut client_info: &mut ClientInfo,
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
debug!("receiving STUN data"); debug!("receiving STUN data");
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) { let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
@ -415,8 +571,6 @@ mod tests {
use pnet::util::MacAddr; use pnet::util::MacAddr;
use crate::logger::MetaLogger;
#[test] #[test]
fn test_proto_stun_ipv4() { fn test_proto_stun_ipv4() {
/* test payload is: /* test payload is:
@ -442,11 +596,9 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got None"); panic!("expected an answer, got None");
@ -503,15 +655,13 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
client_info.ip.src = Some(IpAddr::V6(test_ip_addr)); client_info.ip.src = Some(IpAddr::V6(test_ip_addr));
client_info.ip.dst = Some(IpAddr::V6(masscanned_ip_addr)); client_info.ip.dst = Some(IpAddr::V6(masscanned_ip_addr));
client_info.port.src = Some(55000); client_info.port.src = Some(55000);
client_info.port.dst = Some(65000); client_info.port.dst = Some(65000);
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got None"); panic!("expected an answer, got None");
@ -556,15 +706,13 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
client_info.ip.src = Some(IpAddr::V4(test_ip_addr)); client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr)); client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
client_info.port.src = Some(55000); client_info.port.src = Some(55000);
client_info.port.dst = Some(65000); client_info.port.dst = Some(65000);
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got None"); panic!("expected an answer, got None");
@ -607,15 +755,13 @@ mod tests {
synack_key: [0, 0], synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"), mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None, iface: None,
self_ip_list: Some(&ips), ip_addresses: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
}; };
client_info.ip.src = Some(IpAddr::V4(test_ip_addr)); client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr)); client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
client_info.port.src = Some(55000); client_info.port.src = Some(55000);
client_info.port.dst = Some(65535); client_info.port.dst = Some(65535);
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) { let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
r r
} else { } else {
panic!("expected an answer, got None"); panic!("expected an answer, got None");

View file

@ -1,270 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
use super::http::ProtocolState as HTTPProtocolState;
use super::rpc::ProtocolState as RPCProtocolState;
use crate::proto::{BASE_STATE, PROTO_NONE};
pub enum ProtocolState {
HTTP(HTTPProtocolState),
RPC(RPCProtocolState),
}
pub struct TCPControlBlock {
/* state used to detect protocols (not specific) */
pub smack_state: usize,
/* detected protocol */
pub proto_id: usize,
/* internal state of protocol parser (e.g., HTTP parsing) */
pub proto_state: Option<ProtocolState>,
}
lazy_static! {
static ref CONTABLE: Mutex<HashMap<u32, TCPControlBlock>> = Mutex::new(HashMap::new());
}
pub fn is_tcb_set(cookie: u32) -> bool {
CONTABLE.lock().unwrap().contains_key(&cookie)
}
pub fn get_tcb<F>(cookie: u32, mut f: F)
where
F: FnMut(Option<&mut TCPControlBlock>),
{
f(CONTABLE.lock().unwrap().get_mut(&cookie));
}
pub fn add_tcb(cookie: u32) {
let mut ct = CONTABLE.lock().unwrap();
let tcb = TCPControlBlock {
smack_state: BASE_STATE,
proto_id: PROTO_NONE,
proto_state: None,
};
if !ct.contains_key(&cookie) {
ct.insert(cookie, tcb);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr};
use std::str::FromStr;
use pnet::{
packet::{ip::IpNextHeaderProtocols, tcp::TcpPacket},
util::MacAddr,
};
use crate::client::ClientInfo;
use crate::layer_4::tcp;
use crate::logger::MetaLogger;
use crate::proto::{PROTO_HTTP, PROTO_RPC_TCP};
use crate::synackcookie;
use crate::Masscanned;
fn get_dummy_tcp(&client_info: &ClientInfo) -> Vec<u8> {
/* Craft a TCP ACK+PUSH packet with correct ports and ack */
let mut pkt = Vec::new();
pkt.extend_from_slice(&client_info.port.src.unwrap().to_be_bytes());
pkt.extend_from_slice(&client_info.port.dst.unwrap().to_be_bytes());
pkt.extend_from_slice(b"\x00\x00\x00\x00");
pkt.extend_from_slice(&(client_info.cookie.unwrap() + 1).to_be_bytes());
pkt.extend_from_slice(b"P\x18 \x00\x00\x00\x00\x00");
pkt
}
#[test]
fn test_proto_tcb_proto_id() {
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.port.src = Some(65000);
client_info.port.dst = Some(80);
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
client_info.cookie = Some(cookie);
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
/***** TEST PROTOCOL ID IN TCB *****/
let payload = [get_dummy_tcp(&client_info), b"GET / HTTP/1.1\r\n".to_vec()].concat();
tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
);
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_HTTP);
});
/***** SENDING MORE DATA *****/
let payload = [
get_dummy_tcp(&client_info),
b"garbage data with no specific format (no protocol)\r\n\r\n".to_vec(),
]
.concat();
tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
);
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_HTTP);
});
}
#[test]
fn test_proto_tcb_proto_state_http() {
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.port.src = Some(65001);
client_info.port.dst = Some(80);
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
client_info.cookie = Some(cookie);
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
/***** TEST PROTOCOL ID IN TCB *****/
let payload = [get_dummy_tcp(&client_info), b"GET / HTTP/1.1\r\n".to_vec()].concat();
tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
);
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_HTTP);
if let Some(ProtocolState::HTTP(_)) = t.proto_state {
} else {
panic!("expected a HTTP protocole state, found None");
}
});
/***** SENDING MORE DATA *****/
let payload = [
get_dummy_tcp(&client_info),
b"Field: empty\r\n\r\n".to_vec(),
]
.concat();
/* Should have an answer here */
if let None = tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
) {
panic!("expected an HTTP response, got nothing");
}
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_HTTP);
})
}
#[test]
fn test_proto_tcb_proto_state_rpc() {
let mut client_info = ClientInfo::new();
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
client_info.port.src = Some(65002);
client_info.port.dst = Some(80);
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
let mut ips = HashSet::new();
ips.insert(IpAddr::V4(masscanned_ip_addr));
/* Construct masscanned context object */
let masscanned = Masscanned {
synack_key: [0, 0],
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
iface: None,
self_ip_list: Some(&ips),
remote_ip_deny_list: None,
log: MetaLogger::new(),
};
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
client_info.cookie = Some(cookie);
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
/***** TEST PROTOCOL ID IN TCB *****/
let full_payload = b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let payload = [get_dummy_tcp(&client_info), full_payload[0..28].to_vec()].concat();
tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
);
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_RPC_TCP);
if let Some(ProtocolState::RPC(_)) = t.proto_state {
} else {
panic!("expected a RPC protocole state, found None");
}
});
/***** SENDING MORE DATA *****/
/* Should have an answer here */
let payload = [get_dummy_tcp(&client_info), full_payload[28..].to_vec()].concat();
if let None = tcp::repl(
&TcpPacket::new(&payload).unwrap(),
&masscanned,
&mut client_info,
) {
panic!("expected a RPC response, got nothing");
}
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
get_tcb(cookie, |t| {
let t = t.unwrap();
assert!(t.proto_id == PROTO_RPC_TCP);
});
}
}

View file

@ -61,7 +61,7 @@ pub struct Smack {
} }
fn make_copy_of_pattern(pattern: &[u8], is_nocase: bool) -> Vec<u8> { fn make_copy_of_pattern(pattern: &[u8], is_nocase: bool) -> Vec<u8> {
let mut p = pattern.to_vec(); let mut p = pattern.clone().to_vec();
for i in 0..p.len() { for i in 0..p.len() {
if is_nocase { if is_nocase {
p[i] = p[i].to_ascii_lowercase(); p[i] = p[i].to_ascii_lowercase();

View file

@ -1,5 +1,4 @@
bitflags! { bitflags! {
#[derive(Clone, Copy)]
pub struct SmackFlags: usize { pub struct SmackFlags: usize {
const EMPTY = 0x00; const EMPTY = 0x00;
const ANCHOR_BEGIN = 0x01; const ANCHOR_BEGIN = 0x01;

View file

@ -1,48 +0,0 @@
// This file is part of masscanned.
// Copyright 2021 - 2022 - The IVRE project
//
// Masscanned is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
static CHARS: [&'static str; 256] = [
"\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07", "\\x08", "\\x09",
"\\x0a", "\\x0b", "\\x0c", "\\x0d", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13",
"\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d",
"\\x1e", "\\x1f", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".",
"/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A",
"B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g",
"h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"{", "|", "}", "~", "\\x7f", "\\x80", "\\x81", "\\x82", "\\x83", "\\x84", "\\x85", "\\x86",
"\\x87", "\\x88", "\\x89", "\\x8a", "\\x8b", "\\x8c", "\\x8d", "\\x8e", "\\x8f", "\\x90",
"\\x91", "\\x92", "\\x93", "\\x94", "\\x95", "\\x96", "\\x97", "\\x98", "\\x99", "\\x9a",
"\\x9b", "\\x9c", "\\x9d", "\\x9e", "\\x9f", "\\xa0", "\\xa1", "\\xa2", "\\xa3", "\\xa4",
"\\xa5", "\\xa6", "\\xa7", "\\xa8", "\\xa9", "\\xaa", "\\xab", "\\xac", "\\xad", "\\xae",
"\\xaf", "\\xb0", "\\xb1", "\\xb2", "\\xb3", "\\xb4", "\\xb5", "\\xb6", "\\xb7", "\\xb8",
"\\xb9", "\\xba", "\\xbb", "\\xbc", "\\xbd", "\\xbe", "\\xbf", "\\xc0", "\\xc1", "\\xc2",
"\\xc3", "\\xc4", "\\xc5", "\\xc6", "\\xc7", "\\xc8", "\\xc9", "\\xca", "\\xcb", "\\xcc",
"\\xcd", "\\xce", "\\xcf", "\\xd0", "\\xd1", "\\xd2", "\\xd3", "\\xd4", "\\xd5", "\\xd6",
"\\xd7", "\\xd8", "\\xd9", "\\xda", "\\xdb", "\\xdc", "\\xdd", "\\xde", "\\xdf", "\\xe0",
"\\xe1", "\\xe2", "\\xe3", "\\xe4", "\\xe5", "\\xe6", "\\xe7", "\\xe8", "\\xe9", "\\xea",
"\\xeb", "\\xec", "\\xed", "\\xee", "\\xef", "\\xf0", "\\xf1", "\\xf2", "\\xf3", "\\xf4",
"\\xf5", "\\xf6", "\\xf7", "\\xf8", "\\xf9", "\\xfa", "\\xfb", "\\xfc", "\\xfd", "\\xfe",
"\\xff",
];
pub fn byte2str(data: &[u8]) -> String {
let mut result = String::new();
for byte in data {
result.push_str(CHARS[usize::from(*byte)]);
}
return result;
}

View file

@ -1,7 +1,3 @@
mod parsers; mod parsers;
pub use parsers::IpAddrParser; pub use parsers::IpAddrParser;
mod display;
pub use display::byte2str;

View file

@ -5,12 +5,12 @@ use std::io::BufReader;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use log::*; use log::*;
use pcap_file::pcap::{PcapPacket, PcapReader}; use pcap_file::pcap::{Packet, PcapReader};
use pnet::packet::{ use pnet::packet::{
ethernet::{EtherTypes, EthernetPacket}, ethernet::{EtherTypes, EthernetPacket},
ipv4::Ipv4Packet, ipv4::Ipv4Packet,
ipv6::Ipv6Packet, ipv6::Ipv6Packet,
Packet, Packet as Pkt,
}; };
/* Generic IP packet (either IPv4 or IPv6) */ /* Generic IP packet (either IPv4 or IPv6) */
@ -134,46 +134,9 @@ impl IpAddrParser for File {
} }
} }
/* Parse IP addresses from a comma-separated list in a string */
impl IpAddrParser for &str {
fn extract_ip_addresses_with_count(
self,
_blacklist: Option<HashSet<IpAddr>>,
) -> HashMap<IpAddr, u32> {
panic!("not implemented");
}
fn extract_ip_addresses_only(self, blacklist: Option<HashSet<IpAddr>>) -> HashSet<IpAddr> {
let mut ip_addresses = HashSet::new();
for line in self.split(",") {
/* Should never occur */
if line.is_empty() {
warn!("cannot parse line: {}", line);
continue;
}
let ip: IpAddr;
if let Ok(val) = line.parse::<Ipv4Addr>() {
ip = IpAddr::V4(val);
} else if let Ok(val) = line.parse::<Ipv6Addr>() {
ip = IpAddr::V6(val);
} else {
warn!("cannot parse IP address from line: {}", line);
continue;
}
if let Some(ref b) = blacklist {
if b.contains(&ip) {
info!("[blacklist] ignoring {}", &ip);
continue;
}
}
ip_addresses.insert(ip);
}
ip_addresses
}
}
/* Get the IP address of source and dest. from an IP packet. /* Get the IP address of source and dest. from an IP packet.
* works with both IPv4 and IPv6 packets/addresses */ * works with both IPv4 and IPv6 packets/addresses */
fn extract_ip(pkt: PcapPacket) -> Option<(IpAddr, IpAddr)> { fn extract_ip(pkt: Packet) -> Option<(IpAddr, IpAddr)> {
let eth = EthernetPacket::new(&pkt.data).expect("error parsing Ethernet packet"); let eth = EthernetPacket::new(&pkt.data).expect("error parsing Ethernet packet");
let payload = eth.payload(); let payload = eth.payload();
let ip = match eth.get_ethertype() { let ip = match eth.get_ethertype() {
@ -206,13 +169,13 @@ impl IpAddrParser for PcapReader<std::fs::File> {
/* Extract IP addresses (v4 and v6) from a capture and count occurrences of /* Extract IP addresses (v4 and v6) from a capture and count occurrences of
* each. */ * each. */
fn extract_ip_addresses_with_count( fn extract_ip_addresses_with_count(
mut self: PcapReader<std::fs::File>, self: PcapReader<std::fs::File>,
blacklist: Option<HashSet<IpAddr>>, blacklist: Option<HashSet<IpAddr>>,
) -> HashMap<IpAddr, u32> { ) -> HashMap<IpAddr, u32> {
let mut ip_addresses = HashMap::new(); let mut ip_addresses = HashMap::new();
// pcap.map(fn) , map_Ok // pcap.map(fn) , map_Ok
// .iter, into_iter // .iter, into_iter
while let Some(pkt) = self.next_packet() { for pkt in self {
match pkt { match pkt {
Ok(pkt) => { Ok(pkt) => {
// map_Some map_None // map_Some map_None
@ -246,13 +209,13 @@ impl IpAddrParser for PcapReader<std::fs::File> {
ip_addresses ip_addresses
} }
fn extract_ip_addresses_only( fn extract_ip_addresses_only(
mut self: PcapReader<std::fs::File>, self: PcapReader<std::fs::File>,
blacklist: Option<HashSet<IpAddr>>, blacklist: Option<HashSet<IpAddr>>,
) -> HashSet<IpAddr> { ) -> HashSet<IpAddr> {
let mut ip_addresses = HashSet::new(); let mut ip_addresses = HashSet::new();
// pcap.map(fn) , map_Ok // pcap.map(fn) , map_Ok
// .iter, into_iter // .iter, into_iter
while let Some(pkt) = self.next_packet() { for pkt in self {
match pkt { match pkt {
Ok(pkt) => { Ok(pkt) => {
// map_Some map_None // map_Some map_None

View file

@ -1,3 +0,0 @@
ivre
scapy
requests

View file

@ -14,31 +14,580 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>. # along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
import importlib from scapy.all import *
import os import requests
import requests.packages.urllib3.util.connection as urllib3_cn
import logging
# Export / other tests from .conf import *
from .core import test_all # noqa: F401
DEFAULT_TESTS = [ fmt = logging.Formatter("%(levelname)s\t%(message)s")
"arp", ch = logging.StreamHandler()
"dns", ch.setFormatter(fmt)
"ghost", ch.setLevel(logging.DEBUG)
"http", LOG = logging.getLogger(__name__)
"icmpv4", LOG.setLevel(logging.DEBUG)
"icmpv6", LOG.addHandler(ch)
"ip",
"rpc",
"smb",
"ssh",
"stun",
"tcp",
"udp",
]
ENABLED_TESTS = DEFAULT_TESTS tests = list()
if tests := os.environ.get("TESTS"):
ENABLED_TESTS = [x.strip() for x in tests.split(",")]
for test in ENABLED_TESTS: # decorator to automatically add a function to tests
importlib.import_module(".tests." + test, package="src") def test(f):
OK = "\033[1mOK\033[0m"
KO = "\033[1m\033[1;%dmKO\033[0m" % 31
global tests
fname = f.__name__.ljust(50, '.')
def w(iface):
try:
f(iface)
LOG.info("{}{}".format(fname, OK))
except AssertionError as e:
LOG.info("{}{}: {}".format(fname, KO, e))
tests.append(w)
return w
def multicast(ip6):
a, b = ip6.split(":")[-2:]
mac = ["33", "33", "ff"]
if len(a) == 4:
mac.append(a[2:])
else:
mac.append("00")
if len(b) >= 2:
mac.append(b[:2])
else:
mac.append("00")
if len(b) >= 4:
mac.append(b[2:])
else:
mac.append("00")
return ":".join(mac)
def check_ip_checksum(pkt):
assert(IP in pkt), "no IP layer found"
ip_pkt = pkt[IP]
chksum = ip_pkt.chksum
del ip_pkt.chksum
assert(IP(raw(ip_pkt)).chksum == chksum), "bad IPv4 checksum"
def check_ipv6_checksum(pkt):
assert(IPv6 in pkt), "no IP layer found"
ip_pkt = pkt[IPv6]
chksum = ip_pkt.chksum
del ip_pkt.chksum
assert(IPv6(raw(ip_pkt)).chksum == chksum), "bad IPv6 checksum"
@test
def test_arp_req(iface):
##### ARP #####
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst=IPV4_ADDR)
arp_repl = iface.sr1(arp_req, timeout=1)
assert(arp_repl is not None), "expecting answer, got nothing"
assert(ARP in arp_repl), "no ARP layer found"
arp_repl = arp_repl[ARP]
# check answer
## op is "is-at"
assert(arp_repl.op == 2), "unexpected ARP op: {}".format(arp_repl.op)
## answer for the requested IP
assert(arp_repl.psrc == arp_req.pdst), "unexpected ARP psrc: {}".format(arp_repl.psrc)
assert(arp_repl.pdst == arp_req.psrc), "unexpected ARP pdst: {}".format(arp_repl.pdst)
## answer is expected MAC address
assert(arp_repl.hwsrc == MAC_ADDR), "unexpected ARP hwsrc: {}".format(arp_repl.hwsrc)
@test
def test_arp_req_other_ip(iface):
##### ARP #####
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst='1.2.3.4')
arp_repl = iface.sr1(arp_req, timeout=1)
assert(arp_repl is None), "responding to ARP requests for other IP addresses"
@test
def test_ipv4_req(iface):
##### IP #####
ip_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR, id=0x1337)/ICMP(type=8, code=0)
ip_repl = iface.sr1(ip_req, timeout=1)
assert(ip_repl is not None), "expecting answer, got nothing"
check_ip_checksum(ip_repl)
assert(IP in ip_repl), "no IP layer in response"
ip_repl = ip_repl[IP]
assert(ip_repl.id == 0), "IP identification unexpected"
@test
def test_eth_req_other_mac(iface):
#### ETH ####
ip_req = Ether(dst="00:00:00:11:11:11")/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)
ip_repl = iface.sr1(ip_req, timeout=1)
assert(ip_repl is None), "responding to other MAC addresses"
@test
def test_ipv4_req_other_ip(iface):
##### IP #####
ip_req = Ether(dst=MAC_ADDR)/IP(dst="1.2.3.4")/ICMP(type=8, code=0)
ip_repl = iface.sr1(ip_req, timeout=1)
assert(ip_repl is None), "responding to other IP addresses"
@test
def test_icmpv4_echo_req(iface):
##### ICMPv4 #####
icmp_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)/Raw("idrinkwaytoomuchcoffee")
icmp_repl = iface.sr1(icmp_req, timeout=1)
assert(icmp_repl is not None), "expecting answer, got nothing"
check_ip_checksum(icmp_repl)
assert(ICMP in icmp_repl)
icmp_repl = icmp_repl[ICMP]
# check answer
## type is "echo-reply"
assert(icmp_repl.type == 0)
assert(icmp_repl.code == 0)
## data is the same as sent
assert(icmp_repl.load == icmp_req.load)
@test
def test_icmpv6_neighbor_solicitation(iface):
##### IPv6 Neighbor Solicitation #####
for mac in ["ff:ff:ff:ff:ff:ff", "33:33:00:00:00:01", MAC_ADDR, multicast(IPV6_ADDR)]:
nd_ns = Ether(dst=mac)/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
nd_na = iface.sr1(nd_ns, timeout=1)
assert(nd_na is not None), "expecting answer, got nothing"
assert(ICMPv6ND_NA in nd_na)
nd_na = nd_na[ICMPv6ND_NA]
# check answer content
assert(nd_na.code == 0)
assert(nd_na.R == 0)
assert(nd_na.S == 1)
assert(nd_na.O == 1)
assert(nd_na.tgt == IPV6_ADDR)
# check ND Option
assert(nd_na.haslayer(ICMPv6NDOptDstLLAddr))
assert(nd_na.getlayer(ICMPv6NDOptDstLLAddr).lladdr == MAC_ADDR)
for mac in ["00:00:00:00:00:00", "33:33:33:00:00:01"]:
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
nd_na = iface.sr1(nd_ns, timeout=1)
assert(nd_na is not None), "expecting no answer, got one"
@test
def test_icmpv6_neighbor_solicitation_other_ip(iface):
##### IPv6 Neighbor Solicitation #####
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt="2020:4141:3030:2020::bdbd")
nd_na = iface.sr1(nd_ns, timeout=1)
assert(nd_na is None), "responding to ND_NS for other IP addresses"
@test
def test_icmpv6_echo_req(iface):
##### IPv6 Ping #####
echo_req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/ICMPv6EchoRequest(data="waytoomanynapkins")
echo_repl = iface.sr1(echo_req, timeout=1)
assert(echo_repl is not None), "expecting answer, got nothing"
assert(ICMPv6EchoReply in echo_repl)
echo_repl = echo_repl[ICMPv6EchoReply]
# check answer content
assert(echo_repl.code == 0)
assert(echo_repl.data == echo_req.data)
@test
def test_tcp_syn(iface):
##### SYN-ACK #####
# test a list of ports, randomly generated once
ports_to_test = [
1152, 2003, 2193, 3709, 4054, 6605, 6737, 6875, 7320, 8898, 9513, 9738, 10623, 10723,
11253, 12125, 12189, 12873, 14648, 14659, 16242, 16243, 17209, 17492, 17667, 17838,
18081, 18682, 18790, 19124, 19288, 19558, 19628, 19789, 20093, 21014, 21459, 21740,
24070, 24312, 24576, 26939, 27136, 27165, 27361, 29971, 31088, 33011, 33068, 34990,
35093, 35958, 36626, 36789, 37130, 37238, 37256, 37697, 37890, 38958, 42131, 43864,
44420, 44655, 44868, 45157, 46213, 46497, 46955, 49049, 49067, 49452, 49480, 50498,
50945, 51181, 52890, 53301, 53407, 53417, 53980, 55827, 56483, 58552, 58713, 58836,
59362, 59560, 60534, 60555, 60660, 61615, 62402, 62533, 62941, 63240, 63339, 63616,
64380, 65438,
]
for p in ports_to_test:
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=p)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
@test
def test_ipv4_tcp_psh_ack(iface):
##### PSH-ACK #####
sport = 26695
port = 445
# send PSH-ACK first
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
syn_ack = iface.sr1(psh_ack, timeout=1)
assert(syn_ack is None), "no answer expected, got one"
# test the anti-injection mechanism
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=port)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", dport=port)
# should fail because no ack given
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)
ack = iface.sr1(psh_ack, timeout=1)
assert(ack is None), "no answer expected, got one"
# should get an answer this time
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
ack = iface.sr1(psh_ack, timeout=1)
assert(ack is not None), "expecting answer, got nothing"
check_ip_checksum(ack)
assert(TCP in ack)
ack = ack[TCP]
assert(ack.flags == "A")
@test
def test_ipv6_tcp_psh_ack(iface):
##### PSH-ACK #####
sport = 26695
port = 445
# send PSH-ACK first
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
syn_ack = iface.sr1(psh_ack, timeout=1)
assert(syn_ack is None), "no answer expected, got one"
# test the anti-injection mechanism
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", dport=port)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", dport=port)
# should fail because no ack given
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)
ack = iface.sr1(psh_ack, timeout=1)
assert(ack is None), "no answer expected, got one"
# should get an answer this time
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
ack = iface.sr1(psh_ack, timeout=1)
assert(ack is not None), "expecting answer, got nothing"
check_ipv6_checksum(ack)
assert(TCP in ack)
ack = ack[TCP]
assert(ack.flags == "A")
@test
def test_ipv4_tcp_http(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(TCP in resp)
tcp = resp[TCP]
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
@test
def test_ipv6_tcp_http(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(TCP in resp)
tcp = resp[TCP]
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
@test
def test_ipv4_udp_http(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(UDP in resp)
udp = resp[UDP]
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
@test
def test_ipv6_udp_http(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(UDP in resp)
udp = resp[UDP]
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
@test
def test_ipv4_tcp_http_ko(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(TCP in resp)
assert("P" not in resp[TCP].flags)
assert(len(resp[TCP].payload) == 0)
@test
def test_ipv4_udp_http_ko(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
resp = iface.sr1(req, timeout=1)
assert(resp is None), "expecting no answer, got one"
@test
def test_ipv6_tcp_http_ko(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(TCP in resp)
assert("P" not in resp[TCP].flags)
assert(len(resp[TCP].payload) == 0)
@test
def test_ipv6_udp_http_ko(iface):
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
resp = iface.sr1(req, timeout=1)
assert(resp is None), "expecting no answer, got one"
@test
def test_ipv4_udp_stun(iface):
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478]
payload = bytes.fromhex("000100002112a442000000000000000000000000")
for sport in sports:
for dport in dports:
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(UDP in resp), "no UDP layer found"
udp = resp[UDP]
assert(udp.sport == dport), "unexpected UDP sport: {}".format(udp.sport)
assert(udp.dport == sport), "unexpected UDP dport: {}".format(udp.dport)
resp_payload = udp.payload.load
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
tid = resp_payload[8:20]
data = resp_payload[20:]
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
assert(length == 12), "expected length 12, got {}".format(length)
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data"
@test
def test_ipv6_udp_stun(iface):
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478]
payload = bytes.fromhex("000100002112a442000000000000000000000000")
for sport in sports:
for dport in dports:
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(UDP in resp)
udp = resp[UDP]
assert(udp.sport == dport)
assert(udp.dport == sport)
resp_payload = udp.payload.load
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
tid = resp_payload[8:20]
data = resp_payload[20:]
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
assert(length == 24), "expected length 24, got {}".format(length)
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4)), "unexpected data: {}".format(data)
@test
def test_ipv4_udp_stun_change_port(iface):
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478, 65535]
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
for sport in sports:
for dport in dports:
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(UDP in resp), "no UDP layer found"
udp = resp[UDP]
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
resp_payload = udp.payload.load
type_, length = struct.unpack(">HH", resp_payload[:4])
tid = resp_payload[4:20]
data = resp_payload[20:]
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
assert(length == 12), "expected length 12, got {}".format(length)
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data"
@test
def test_ipv6_udp_stun_change_port(iface):
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478, 65535]
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
for sport in sports:
for dport in dports:
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(UDP in resp), "expecting UDP layer in answer, got nothing"
udp = resp[UDP]
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
resp_payload = udp.payload.load
type_, length = struct.unpack(">HH", resp_payload[:4])
tid = resp_payload[4:20]
data = resp_payload[20:]
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
assert(length == 24), "expected length 12, got {}".format(length)
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4))
@test
def test_ipv4_tcp_ssh(iface):
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(TCP in resp)
tcp = resp[TCP]
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
assert(len(tcp.payload) > 0), "expecting payload, got none"
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
@test
def test_ipv4_udp_ssh(iface):
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ip_checksum(resp)
assert(UDP in resp)
udp = resp[UDP]
assert(len(udp.payload) > 0), "expecting payload, got none"
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
@test
def test_ipv6_tcp_ssh(iface):
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
syn_ack = iface.sr1(syn, timeout=1)
assert(syn_ack is not None), "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert(TCP in syn_ack)
syn_ack = syn_ack[TCP]
assert(syn_ack.flags == "SA")
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
_ = iface.sr1(ack, timeout=1)
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(TCP in resp)
tcp = resp[TCP]
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
assert(len(tcp.payload) > 0), "expecting payload, got none"
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
@test
def test_ipv6_udp_ssh(iface):
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
resp = iface.sr1(req, timeout=1)
assert(resp is not None), "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert(UDP in resp)
udp = resp[UDP]
assert(len(udp.payload) > 0), "expecting payload, got none"
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
def test_all(iface):
global tests
# execute tests
for t in tests:
t(iface)

View file

@ -1,102 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - 2025 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
import logging
from scapy.compat import raw
from scapy.layers.inet import IP
from scapy.layers.inet6 import IPv6
def setup_logs():
log = logging.getLogger()
log.setLevel(logging.DEBUG)
if not log.handlers:
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter("%(levelname)s\t%(message)s"))
ch.setLevel(logging.DEBUG)
log.addHandler(ch)
return log
LOG = setup_logs()
TESTS = []
ERRORS = []
# decorator to automatically add a function to tests
def test(f):
OK = "\033[1mOK\033[0m"
KO = "\033[1m\033[1;%dmKO\033[0m" % 31
fname = f.__name__.ljust(50, ".")
def w(m):
try:
# check that masscanned is still running
assert m.poll() is None, "masscanned not running"
f()
# check that masscanned is still running
assert m.poll() is None, "masscanned terminated unexpectedly"
LOG.info("{}{}".format(fname, OK))
except AssertionError as e:
LOG.error("{}{}: {}".format(fname, KO, e))
ERRORS.append(fname)
TESTS.append(w)
return w
def test_all(m):
# execute tests
for t in TESTS:
# perform unit test
t(m)
LOG.info(f"\033[1mRan {len(TESTS)} tests with {len(ERRORS)} errors\033[0m")
return len(ERRORS)
def multicast(ip6):
a, b = ip6.split(":")[-2:]
mac = ["33", "33", "ff"]
if len(a) == 4:
mac.append(a[2:])
else:
mac.append("00")
if len(b) >= 2:
mac.append(b[:2])
else:
mac.append("00")
if len(b) >= 4:
mac.append(b[2:])
else:
mac.append("00")
return ":".join(mac)
def check_ip_checksum(pkt):
assert IP in pkt, "no IP layer found"
ip_pkt = pkt[IP]
chksum = ip_pkt.chksum
del ip_pkt.chksum
assert IP(raw(ip_pkt)).chksum == chksum, "bad IPv4 checksum"
def check_ipv6_checksum(pkt):
assert IPv6 in pkt, "no IP layer found"
ip_pkt = pkt[IPv6]
chksum = ip_pkt.chksum
del ip_pkt.chksum
assert IPv6(raw(ip_pkt)).chksum == chksum, "bad IPv6 checksum"

View file

@ -1,15 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.

View file

@ -1,51 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.l2 import Ether, ARP, ETHER_BROADCAST
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, MAC_ADDR
from ..core import test
@test
def test_arp_req():
##### ARP #####
arp_req = Ether(dst=ETHER_BROADCAST) / ARP(pdst=IPV4_ADDR)
arp_repl = srp1(arp_req, timeout=1)
assert arp_repl is not None, "expecting answer, got nothing"
assert ARP in arp_repl, "no ARP layer found"
arp_repl = arp_repl[ARP]
# check answer
## op is "is-at"
assert arp_repl.op == 2, "unexpected ARP op: {}".format(arp_repl.op)
## answer for the requested IP
assert arp_repl.psrc == arp_req.pdst, "unexpected ARP psrc: {}".format(
arp_repl.psrc
)
assert arp_repl.pdst == arp_req.psrc, "unexpected ARP pdst: {}".format(
arp_repl.pdst
)
## answer is expected MAC address
assert arp_repl.hwsrc == MAC_ADDR, "unexpected ARP hwsrc: {}".format(arp_repl.hwsrc)
@test
def test_arp_req_other_ip():
##### ARP #####
arp_req = Ether(dst=ETHER_BROADCAST) / ARP(pdst="1.2.3.4")
arp_repl = srp1(arp_req, timeout=1)
assert arp_repl is None, "responding to ARP requests for other IP addresses"

View file

@ -1,158 +0,0 @@
# This file is part of masscanned.
# Copyright 2022 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.compat import raw
from scapy.layers.dns import DNS, DNSQR
from scapy.layers.inet import IP, UDP
from scapy.layers.l2 import Ether
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum
@test
def test_ipv4_udp_dns_in_a():
sports = [53, 13274, 0]
dports = [53, 5353, 80, 161, 24732]
for sport in sports:
for dport in dports:
for domain in ["example.com", "www.example.com", "masscan.ned"]:
qd = DNSQR(qname=domain, qtype="A", qclass="IN")
dns_req = DNS(id=1234, rd=False, opcode=0, qd=qd)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ dns_req
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp, "no UDP layer found"
udp = resp[UDP]
assert (
udp.sport == dport
), f"unexpected UDP sport: {udp.sport!r} ({domain})"
assert (
udp.dport == sport
), f"unexpected UDP dport: {udp.dport!r} ({domain})"
if DNS not in udp:
try:
dns_rep = DNS(udp.load)
except Exception:
raise AssertionError("no DNS layer found")
else:
dns_rep = udp[DNS]
assert (
dns_rep.id == 1234
), f"unexpected id value: {dns_rep.id!r} ({domain})"
assert dns_rep.qr, "unexpected qr value"
assert dns_rep.opcode == 0, "unexpected opcode value"
assert dns_rep.aa, "unexpected aa value"
assert not dns_rep.tc, "unexpected tc value"
assert not dns_rep.rd, "unexpected rd value"
assert not dns_rep.ra, "unexpected ra value"
assert dns_rep.z == 0, "unexpected z value"
assert dns_rep.rcode == 0, "unexpected rcode value"
assert (
dns_rep.qdcount == 1
), f"unexpected qdcount value: {dns_rep.qdcount!r} vs 1 ({domain})"
assert dns_rep.ancount == 1, "unexpected ancount value"
assert dns_rep.nscount == 0, "unexpected nscount value"
assert dns_rep.arcount == 0, "unexpected arcount value"
assert raw(dns_rep.qd[0]) == raw(
dns_req.qd[0]
), "query in request and response do not match"
assert raw(dns_rep.qd[0].qname) == raw(
dns_req.qd[0].qname
), "qname query in request and response do not match"
assert (
dns_rep.an[0].rrname == dns_req.qd[0].qname
), "rrname in answer does not match qname in request"
assert (
dns_rep.an[0].rclass == dns_req.qd[0].qclass
), "class in answer does not match query"
assert (
dns_rep.an[0].type == dns_req.qd[0].qtype
), "type in answer does not match query"
assert dns_rep.an[0].rdata == IPV4_ADDR
@test
def test_ipv4_udp_dns_in_a_multiple_queries():
sports = [53, 13274, 12198, 888, 0]
dports = [53, 5353, 80, 161, 24732]
for sport in sports:
for dport in dports:
qd = [
DNSQR(qname="www.example1.com", qtype="A", qclass="IN"),
DNSQR(qname="www.example2.com", qtype="A", qclass="IN"),
DNSQR(qname="www.example3.com", qtype="A", qclass="IN"),
]
dns_req = DNS(id=1234, rd=False, opcode=0, qd=qd)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ dns_req
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp, "no UDP layer found"
udp = resp[UDP]
assert udp.sport == dport, "unexpected UDP sport: {}".format(udp.sport)
assert udp.dport == sport, "unexpected UDP dport: {}".format(udp.dport)
if DNS not in udp:
try:
dns_rep = DNS(udp.load)
except Exception:
raise AssertionError("no DNS layer found")
else:
dns_rep = udp[DNS]
assert dns_rep.id == 1234, f"unexpected id value: {dns_rep.id}"
assert dns_rep.qr, "unexpected qr value"
assert dns_rep.opcode == 0, "unexpected opcode value"
assert dns_rep.aa, "unexpected aa value"
assert not dns_rep.tc, "unexpected tc value"
assert not dns_rep.rd, "unexpected rd value"
assert not dns_rep.ra, "unexpected ra value"
assert dns_rep.z == 0, "unexpected z value"
assert dns_rep.rcode == 0, "unexpected rcode value"
assert (
dns_rep.qdcount == 3
), f"unexpected qdcount value: {dns_rep.qdcount} vs 3"
assert dns_rep.ancount == 3, "unexpected ancount value"
assert dns_rep.nscount == 0, "unexpected nscount value"
assert dns_rep.arcount == 0, "unexpected arcount value"
for i, q in enumerate(qd):
assert raw(dns_rep.qd[i]) == raw(
dns_req.qd[i]
), f"query in request and response do not match ({i})"
assert raw(dns_rep.qd[i].qname) == raw(
dns_req.qd[i].qname
), f"qname query in request and response do not match ({i})"
assert (
dns_rep.an[i].rrname == dns_req.qd[i].qname
), f"rrname in answer does not match qname in request ({i})"
assert (
dns_rep.an[i].rclass == dns_req.qd[i].qclass
), f"class in answer does not match query ({i})"
assert (
dns_rep.an[i].type == dns_req.qd[i].qtype
), f"type in answer does not match query ({i})"
assert dns_rep.an[i].rdata == IPV4_ADDR

View file

@ -1,87 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
import struct
import zlib
from scapy.compat import raw
from scapy.layers.inet import IP, TCP
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from scapy.volatile import RandInt
from ..conf import IPV4_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum
@test
def test_ipv4_tcp_ghost():
sport = 37184
dports = [22, 23874]
for dport in dports:
seq_init = int(RandInt())
banner = b"Gh0st\xad\x00\x00\x00\xe0\x00\x00\x00x\x9cKS``\x98\xc3\xc0\xc0\xc0\x06\xc4\x8c@\xbcQ\x96\x81\x81\tH\x07\xa7\x16\x95e&\xa7*\x04$&g+\x182\x94\xf6\xb000\xac\xa8rc\x00\x01\x11\xa0\x82\x1f\\`&\x83\xc7K7\x86\x19\xe5n\x0c9\x95n\x0c;\x84\x0f3\xac\xe8sch\xa8^\xcf4'J\x97\xa9\x82\xe30\xc3\x91h]&\x90\xf8\xce\x97S\xcbA4L?2=\xe1\xc4\x92\x86\x0b@\xf5`\x0cT\x1f\xae\xaf]\nr\x0b\x03#\xa3\xdc\x02~\x06\x86\x03+\x18m\xc2=\xfdtC,C\xfdL<<==\\\x9d\x19\x88\x00\xe5 \x02\x00T\xf5+\\"
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw(banner)
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
data = raw(tcp.payload)
assert data, "expecting payload, got none"
assert data.startswith(b"Gh0st"), "unexpected banner: %r" % tcp.payload.load
data_len, uncompressed_len = struct.unpack("<II", data[5:13])
assert len(data) == data_len, "invalid Ghost payload: %r" % data
assert len(zlib.decompress(data[13:])) == uncompressed_len, (
"invalid Ghost payload: %r" % data
)

View file

@ -1,409 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP, TCP, UDP
from scapy.layers.inet6 import IPv6
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from scapy.volatile import RandInt
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum, check_ipv6_checksum
@test
def test_ipv4_tcp_http():
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw("GET / HTTP/1.1\r\n\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
@test
def test_ipv4_tcp_http_segmented():
sport = 24593
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
# request is not complete yet
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw("GET / HTTP/1.1\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
assert resp[TCP].flags == "A", (
'expecting TCP flag "A", got %r' % resp[TCP].flags
)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + len(req) + 1,
ack=syn_ack.seq + 1,
)
/ Raw("\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert tcp.flags == "PA"
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
@test
def test_ipv4_tcp_http_incomplete():
sport = 24595
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
# purposedly incomplete request (missing additionnal ending \r\n)
/ Raw("GET / HTTP/1.1\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting an answer, got none"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert tcp.flags == "A", "expecting TCP flag A, got {}".format(tcp.flags)
@test
def test_ipv6_tcp_http():
sport = 24594
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw("GET / HTTP/1.1\r\n\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
@test
def test_ipv4_udp_http():
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw("GET / HTTP/1.1\r\n\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp
udp = resp[UDP]
assert udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
@test
def test_ipv6_udp_http():
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw("GET / HTTP/1.1\r\n\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert UDP in resp
udp = resp[UDP]
assert udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
@test
def test_ipv4_tcp_http_ko():
sport = 24596
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw(bytes.fromhex("4f5054494f4e53"))
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
assert "P" not in resp[TCP].flags
assert len(resp[TCP].payload) == 0
@test
def test_ipv4_udp_http_ko():
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(bytes.fromhex("4f5054494f4e53"))
)
resp = srp1(req, timeout=1)
assert resp is None, "expecting no answer, got one"
@test
def test_ipv6_tcp_http_ko():
sport = 24597
dports = [80, 443, 5000, 53228]
for dport in dports:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw(bytes.fromhex("4f5054494f4e53"))
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
assert "P" not in resp[TCP].flags
assert len(resp[TCP].payload) == 0
@test
def test_ipv6_udp_http_ko():
sport = 24592
dports = [80, 443, 5000, 53228]
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(bytes.fromhex("4f5054494f4e53"))
)
resp = srp1(req, timeout=1)
assert resp is None, "expecting no answer, got one"

View file

@ -1,45 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP, ICMP
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum
@test
def test_icmpv4_echo_req():
##### ICMPv4 #####
icmp_req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ ICMP(type=8, code=0)
/ Raw("idrinkwaytoomuchcoffee")
)
icmp_repl = srp1(icmp_req, timeout=1)
assert icmp_repl is not None, "expecting answer, got nothing"
check_ip_checksum(icmp_repl)
assert ICMP in icmp_repl
icmp_repl = icmp_repl[ICMP]
# check answer
## type is "echo-reply"
assert icmp_repl.type == 0
assert icmp_repl.code == 0
## data is the same as sent
assert icmp_repl.load == icmp_req.load

View file

@ -1,87 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet6 import (
ICMPv6EchoReply,
ICMPv6EchoRequest,
ICMPv6NDOptDstLLAddr,
ICMPv6ND_NA,
ICMPv6ND_NS,
IPv6,
)
from scapy.layers.l2 import Ether
from scapy.sendrecv import srp1
from ..conf import IPV6_ADDR, MAC_ADDR
from ..core import test, multicast
@test
def test_icmpv6_neighbor_solicitation():
##### IPv6 Neighbor Solicitation #####
for mac in [
"ff:ff:ff:ff:ff:ff",
"33:33:00:00:00:01",
MAC_ADDR,
multicast(IPV6_ADDR),
]:
nd_ns = Ether(dst=mac) / IPv6() / ICMPv6ND_NS(tgt=IPV6_ADDR)
nd_na = srp1(nd_ns, timeout=1)
assert nd_na is not None, "expecting answer, got nothing"
assert ICMPv6ND_NA in nd_na
nd_na = nd_na[ICMPv6ND_NA]
# check answer content
assert nd_na.code == 0
assert nd_na.R == 0
assert nd_na.S == 1
assert nd_na.O == 1 # noqa: E741
assert nd_na.tgt == IPV6_ADDR
# check ND Option
assert nd_na.haslayer(ICMPv6NDOptDstLLAddr)
assert nd_na.getlayer(ICMPv6NDOptDstLLAddr).lladdr == MAC_ADDR
for mac in ["00:00:00:00:00:00", "33:33:33:00:00:01"]:
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff") / IPv6() / ICMPv6ND_NS(tgt=IPV6_ADDR)
nd_na = srp1(nd_ns, timeout=1)
assert nd_na is not None, "expecting no answer, got one"
@test
def test_icmpv6_neighbor_solicitation_other_ip():
##### IPv6 Neighbor Solicitation #####
nd_ns = (
Ether(dst="ff:ff:ff:ff:ff:ff")
/ IPv6()
/ ICMPv6ND_NS(tgt="2020:4141:3030:2020::bdbd")
)
nd_na = srp1(nd_ns, timeout=1)
assert nd_na is None, "responding to ND_NS for other IP addresses"
@test
def test_icmpv6_echo_req():
##### IPv6 Ping #####
echo_req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ ICMPv6EchoRequest(data="waytoomanynapkins")
)
echo_repl = srp1(echo_req, timeout=1)
assert echo_repl is not None, "expecting answer, got nothing"
assert ICMPv6EchoReply in echo_repl
echo_repl = echo_repl[ICMPv6EchoReply]
# check answer content
assert echo_repl.code == 0
assert echo_repl.data == echo_req.data

View file

@ -1,50 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP, ICMP
from scapy.layers.l2 import Ether
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum
@test
def test_ipv4_req():
##### IP #####
ip_req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, id=0x1337) / ICMP(type=8, code=0)
ip_repl = srp1(ip_req, timeout=1)
assert ip_repl is not None, "expecting answer, got nothing"
check_ip_checksum(ip_repl)
assert IP in ip_repl, "no IP layer in response"
ip_repl = ip_repl[IP]
assert ip_repl.id == 0, "IP identification unexpected"
@test
def test_eth_req_other_mac():
#### ETH ####
ip_req = Ether(dst="00:00:00:11:11:11") / IP(dst=IPV4_ADDR) / ICMP(type=8, code=0)
ip_repl = srp1(ip_req, timeout=1)
assert ip_repl is None, "responding to other MAC addresses"
@test
def test_ipv4_req_other_ip():
##### IP #####
ip_req = Ether(dst=MAC_ADDR) / IP(dst="1.2.3.4") / ICMP(type=8, code=0)
ip_repl = srp1(ip_req, timeout=1)
assert ip_repl is None, "responding to other IP addresses"

View file

@ -1,112 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from subprocess import check_call
from tempfile import NamedTemporaryFile
import json
import os
import re
from ivre.db import DBNmap
from ..conf import IPV4_ADDR
from ..core import test
@test
def test_rpc_nmap():
for scan in "SU":
with NamedTemporaryFile(delete=False) as xml_result:
check_call(
[
"nmap",
"-n",
"-vv",
"-oX",
"-",
IPV4_ADDR,
f"-s{scan}V",
"-p",
"111",
"--script",
"rpcinfo,rpc-grind",
],
stdout=xml_result,
)
with NamedTemporaryFile(delete=False, mode="w") as json_result:
DBNmap(output=json_result).store_scan(xml_result.name)
os.unlink(xml_result.name)
with open(json_result.name) as fdesc:
results = [json.loads(line) for line in fdesc]
os.unlink(json_result.name)
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
result = results[0]
assert len(result["ports"]) == 1, f"Expected 1 port, got {len(result['ports'])}"
port = result["ports"][0]
assert port["port"] == 111, f"Expected port 111, got {port['port']}"
assert port["protocol"] == (
"tcp" if scan == "S" else "udp"
), f"Unexpected proto {port['protocol']} for scan {scan}"
assert port["service_name"] in {
"nfs",
"rpcbind",
"rstatd",
"rusersd",
}, f"Unexpected service_name: {port['service_name']}"
assert port["service_extrainfo"] in {
"RPC #100000",
"RPC #100001",
"RPC #100002",
"RPC #100003",
}, f"Unexpected service_extrainfo: {port['service_extrainfo']}"
assert (
len(port["scripts"]) == 1
), f"Expected 1 script, got {len(port['scripts'])}"
script = port["scripts"][0]
assert script["id"] == "rpcinfo", "Expected rpcinfo script, not found"
assert (
len(script["rpcinfo"]) == 1
), f"Expected 1 rpcinfo, got {len(script['rpcinfo'])}"
@test
def test_rpcinfo():
with NamedTemporaryFile(delete=False) as rpcout:
check_call(["rpcinfo", "-p", IPV4_ADDR], stdout=rpcout)
with open(rpcout.name) as fdesc:
found = []
for line in fdesc:
line = line.split()
if line[0] == "program":
# header
continue
assert line[0] == "100000", f"Expected program 100000, got {line[0]}"
found.append(int(line[1]))
assert len(found) == 3, f"Expected three versions, got {found}"
for i in range(2, 5):
assert i in found, f"Missing version {i} in {found}"
os.unlink(rpcout.name)
with NamedTemporaryFile(delete=False) as rpcout:
check_call(["rpcinfo", "-u", IPV4_ADDR, "100000"], stdout=rpcout)
with open(rpcout.name) as fdesc:
found = []
expr = re.compile("^program 100000 version ([0-9]) ready and waiting$")
for line in fdesc:
found.append(int(expr.search(line.strip()).group(1)))
assert len(found) == 3, f"Expected three versions, got {found}"
for i in range(2, 5):
assert i in found, f"Missing version {i} in {found}"
os.unlink(rpcout.name)

View file

@ -1,65 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
import subprocess
from ..core import test
from ..conf import IPV4_ADDR
@test
def test_smb1_network_req():
proc = subprocess.Popen(
[
"smbclient",
"-U ''",
"-N",
"-d 6",
"-t 1",
"-L",
IPV4_ADDR,
"--option=client min protocol=NT1",
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
out, _ = proc.communicate()
assert f"Connecting to {IPV4_ADDR} at port 445" in out, "\n" + out
assert "session request ok" in out, "\n" + out
assert f"negotiated dialect[NT1] against server[{IPV4_ADDR}]" in out, "\n" + out
@test
def test_smb2_network_req():
proc = subprocess.Popen(
[
"smbclient",
"-U ''",
"-N",
"-d 5",
"-t 1",
"-L",
IPV4_ADDR,
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
out, _ = proc.communicate()
assert f"Connecting to {IPV4_ADDR} at port 445" in out, "\n" + out
assert "session request ok" in out, "\n" + out
assert f"negotiated dialect[SMB2_02] against server[{IPV4_ADDR}]" in out, "\n" + out

View file

@ -1,217 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP, TCP, UDP
from scapy.layers.inet6 import IPv6
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from scapy.volatile import RandInt
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum, check_ipv6_checksum
@test
def test_ipv4_tcp_ssh():
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
seq_init = int(RandInt())
banner = [
b"SSH-2.0-AsyncSSH_2.1.0",
b"SSH-2.0-PuTTY",
b"SSH-2.0-libssh2_1.4.3",
b"SSH-2.0-Go",
b"SSH-2.0-PUTTY",
][i % 5]
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw(banner + b"\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
assert len(tcp.payload) > 0, "expecting payload, got none"
assert tcp.payload.load.startswith(b"SSH-2.0-"), (
"unexpected banner: %r" % tcp.payload.load
)
assert tcp.payload.load.endswith(b"\r\n"), (
"unexpected banner: %r" % tcp.payload.load
)
@test
def test_ipv4_udp_ssh():
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [
b"SSH-2.0-AsyncSSH_2.1.0",
b"SSH-2.0-PuTTY",
b"SSH-2.0-libssh2_1.4.3",
b"SSH-2.0-Go",
b"SSH-2.0-PUTTY",
][i % 5]
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(banner + b"\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp
udp = resp[UDP]
assert len(udp.payload) > 0, "expecting payload, got none"
assert udp.payload.load.startswith(b"SSH-2.0-"), (
"unexpected banner: %r" % udp.payload.load
)
assert udp.payload.load.endswith(b"\r\n"), (
"unexpected banner: %r" % udp.payload.load
)
@test
def test_ipv6_tcp_ssh():
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
seq_init = int(RandInt())
banner = [
b"SSH-2.0-AsyncSSH_2.1.0",
b"SSH-2.0-PuTTY",
b"SSH-2.0-libssh2_1.4.3",
b"SSH-2.0-Go",
b"SSH-2.0-PUTTY",
][i % 5]
syn = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA"
ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="A",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
)
_ = srp1(ack, timeout=1)
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="PA",
sport=sport,
dport=dport,
seq=seq_init + 1,
ack=syn_ack.seq + 1,
)
/ Raw(banner + b"\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
tcp = resp[TCP]
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
assert len(tcp.payload) > 0, "expecting payload, got none"
assert tcp.payload.load.startswith(b"SSH-2.0-"), (
"unexpected banner: %r" % tcp.payload.load
)
assert tcp.payload.load.endswith(b"\r\n"), (
"unexpected banner: %r" % tcp.payload.load
)
@test
def test_ipv6_udp_ssh():
sport = 37183
dports = [22, 80, 2222, 2022, 23874, 50000]
for i, dport in enumerate(dports):
banner = [
b"SSH-2.0-AsyncSSH_2.1.0",
b"SSH-2.0-PuTTY",
b"SSH-2.0-libssh2_1.4.3",
b"SSH-2.0-Go",
b"SSH-2.0-PUTTY",
][i % 5]
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(banner + b"\r\n")
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert UDP in resp
udp = resp[UDP]
assert len(udp.payload) > 0, "expecting payload, got none"
assert udp.payload.load.startswith(b"SSH-2.0-"), (
"unexpected banner: %r" % udp.payload.load
)
assert udp.payload.load.endswith(b"\r\n"), (
"unexpected banner: %r" % udp.payload.load
)

View file

@ -1,196 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from socket import AF_INET6
import struct
from scapy.layers.inet import IP, UDP
from scapy.layers.inet6 import IPv6
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.pton_ntop import inet_pton
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum, check_ipv6_checksum
@test
def test_ipv4_udp_stun():
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478]
payload = bytes.fromhex("000100002112a442000000000000000000000000")
for sport in sports:
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(payload)
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp, "no UDP layer found"
udp = resp[UDP]
assert udp.sport == dport, "unexpected UDP sport: {}".format(udp.sport)
assert udp.dport == sport, "unexpected UDP dport: {}".format(udp.dport)
resp_payload = udp.payload.load
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
tid = resp_payload[8:20]
data = resp_payload[20:]
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
assert length == 12, "expected length 12, got {}".format(length)
assert (
magic == 0x2112A442
), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
assert (
tid == b"\x00" * 12
), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
expected_data = b"\x00\x01\x00\x08\x00\x01" + struct.pack(
">HBBBB", sport, 192, 0, 0, 0
)
assert (
data == expected_data
), f"unexpected data {data!r} != {expected_data!r}"
@test
def test_ipv6_udp_stun():
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478]
payload = bytes.fromhex("000100002112a442000000000000000000000000")
for sport in sports:
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(payload)
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert UDP in resp
udp = resp[UDP]
assert udp.sport == dport
assert udp.dport == sport
resp_payload = udp.payload.load
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
tid = resp_payload[8:20]
data = resp_payload[20:]
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
assert length == 24, "expected length 24, got {}".format(length)
assert (
magic == 0x2112A442
), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
assert (
tid == b"\x00" * 12
), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
expected_data = (
bytes.fromhex("000100140002")
+ struct.pack(">H", sport)
+ inet_pton(AF_INET6, "2001:41d0::1234:5678")
)
assert data == expected_data, "unexpected data: {}".format(data)
@test
def test_ipv4_udp_stun_change_port():
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478, 65535]
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
for sport in sports:
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(payload)
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ip_checksum(resp)
assert UDP in resp, "no UDP layer found"
udp = resp[UDP]
assert (
udp.sport == (dport + 1) % 2**16
), "expected answer from UDP/{}, got it from UDP/{}".format(
(dport + 1) % 2**16, udp.sport
)
assert (
udp.dport == sport
), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
resp_payload = udp.payload.load
type_, length = struct.unpack(">HH", resp_payload[:4])
tid = resp_payload[4:20]
data = resp_payload[20:]
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
assert length == 12, "expected length 12, got {}".format(length)
assert tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c"), (
"expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
)
expected_data = b"\x00\x01\x00\x08\x00\x01" + struct.pack(
">HBBBB", sport, 192, 0, 0, 0
)
assert (
data == expected_data
), f"unexpected data {data!r} != {expected_data!r}"
@test
def test_ipv6_udp_stun_change_port():
sports = [12345, 55555, 80, 43273]
dports = [80, 800, 8000, 3478, 65535]
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
for sport in sports:
for dport in dports:
req = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ UDP(sport=sport, dport=dport)
/ Raw(payload)
)
resp = srp1(req, timeout=1)
assert resp is not None, "expecting answer, got nothing"
check_ipv6_checksum(resp)
assert UDP in resp, "expecting UDP layer in answer, got nothing"
udp = resp[UDP]
assert (
udp.sport == (dport + 1) % 2**16
), "expected answer from UDP/{}, got it from UDP/{}".format(
(dport + 1) % 2**16, udp.sport
)
assert (
udp.dport == sport
), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
resp_payload = udp.payload.load
type_, length = struct.unpack(">HH", resp_payload[:4])
tid = resp_payload[4:20]
data = resp_payload[20:]
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
assert length == 24, "expected length 12, got {}".format(length)
assert tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c"), (
"expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
)
expected_data = (
bytes.fromhex("000100140002")
+ struct.pack(">H", sport)
+ inet_pton(AF_INET6, "2001:41d0::1234:5678")
)
assert (
data == expected_data
), f"unexpected data {data!r} != {expected_data!r}"

View file

@ -1,303 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP, TCP
from scapy.layers.inet6 import IPv6
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from scapy.volatile import RandInt
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
from ..core import test, check_ip_checksum, check_ipv6_checksum
@test
def test_ipv4_tcp_empty():
for p in [0, 80, 443]:
req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, proto=6) / Raw() # UDP
repl = srp1(req, timeout=1)
assert repl is None, "expecting no answer, got one"
@test
def test_ipv6_tcp_empty():
for p in [0, 80, 443]:
req = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR, nh=6) / Raw() # UDP
repl = srp1(req, timeout=1)
assert repl is None, "expecting no answer, got one"
@test
def test_tcp_syn():
##### SYN-ACK #####
# test a list of ports, randomly generated once
ports_to_test = [
1152,
2003,
2193,
3709,
4054,
6605,
6737,
6875,
7320,
8898,
9513,
9738,
10623,
10723,
11253,
12125,
12189,
12873,
14648,
14659,
16242,
16243,
17209,
17492,
17667,
17838,
18081,
18682,
18790,
19124,
19288,
19558,
19628,
19789,
20093,
21014,
21459,
21740,
24070,
24312,
24576,
26939,
27136,
27165,
27361,
29971,
31088,
33011,
33068,
34990,
35093,
35958,
36626,
36789,
37130,
37238,
37256,
37697,
37890,
38958,
42131,
43864,
44420,
44655,
44868,
45157,
46213,
46497,
46955,
49049,
49067,
49452,
49480,
50498,
50945,
51181,
52890,
53301,
53407,
53417,
53980,
55827,
56483,
58552,
58713,
58836,
59362,
59560,
60534,
60555,
60660,
61615,
62402,
62533,
62941,
63240,
63339,
63616,
64380,
65438,
]
for p in ports_to_test:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", dport=p, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
syn_ack.ack,
seq_init + 1,
)
@test
def test_ipv4_tcp_psh_ack():
##### PSH-ACK #####
sport = 26695
port = 445
seq_init = int(RandInt())
# send PSH-ACK first
psh_ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="PA", sport=sport, dport=port, seq=seq_init)
/ Raw("payload")
)
syn_ack = srp1(psh_ack, timeout=1)
assert syn_ack is None, "no answer expected, got one"
# test the anti-injection mechanism
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="S", sport=sport, dport=port, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ip_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
syn_ack.ack,
seq_init + 1,
)
ack = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR) / TCP(flags="A", dport=port)
# should fail because no ack given
psh_ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags="PA", sport=sport, dport=port, ack=0, seq=seq_init + 1)
)
ack = srp1(psh_ack, timeout=1)
assert ack is None, "no answer expected, got one"
# should get an answer this time
psh_ack = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(
flags="PA", sport=sport, dport=port, ack=syn_ack.seq + 1, seq=seq_init + 1
)
)
ack = srp1(psh_ack, timeout=1)
assert ack is not None, "expecting answer, got nothing"
check_ip_checksum(ack)
assert TCP in ack, "expecting TCP, got %r" % ack.summary()
ack = ack[TCP]
assert ack.flags == "A", "expecting TCP A, got %r" % syn_ack.flags
@test
def test_ipv6_tcp_psh_ack():
##### PSH-ACK #####
sport = 26695
port = 445
seq_init = int(RandInt())
# send PSH-ACK first
psh_ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="PA", sport=sport, dport=port, seq=seq_init)
/ Raw("payload")
)
syn_ack = srp1(psh_ack, timeout=1)
assert syn_ack is None, "no answer expected, got one"
# test the anti-injection mechanism
syn = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="S", sport=sport, dport=port, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got nothing"
check_ipv6_checksum(syn_ack)
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
syn_ack = syn_ack[TCP]
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
syn_ack.ack,
seq_init + 1,
)
ack = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR) / TCP(flags="A", dport=port)
# should fail because no ack given
psh_ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(flags="PA", sport=sport, dport=port, ack=0, seq=seq_init + 1)
)
ack = srp1(psh_ack, timeout=1)
assert ack is None, "no answer expected, got one"
# should get an answer this time
psh_ack = (
Ether(dst=MAC_ADDR)
/ IPv6(dst=IPV6_ADDR)
/ TCP(
flags="PA", sport=sport, dport=port, ack=syn_ack.seq + 1, seq=seq_init + 1
)
)
ack = srp1(psh_ack, timeout=1)
assert ack is not None, "expecting answer, got nothing"
check_ipv6_checksum(ack)
assert TCP in ack, "expecting TCP, got %r" % ack.summary()
ack = ack[TCP]
assert ack.flags == "A", "expecting TCP A, got %r" % syn_ack.flags
@test
def test_tcp_syn_with_flags():
# send a SYN packet with other TCP flags, should not be answered
for flags in ["SA", "SR", "SF", "SPUCE"]:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags=flags, dport=80, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is None, "expecting no answer, got one"
# some should be accepted to imitate a Linux network stack
for flags in ["SP", "SU", "SC", "SE", "SPU", "SPC", "SPE", "SPUC", "SPUE"]:
seq_init = int(RandInt())
syn = (
Ether(dst=MAC_ADDR)
/ IP(dst=IPV4_ADDR)
/ TCP(flags=flags, dport=80, seq=seq_init)
)
syn_ack = srp1(syn, timeout=1)
assert syn_ack is not None, "expecting answer, got None"

View file

@ -1,40 +0,0 @@
# This file is part of masscanned.
# Copyright 2021 - The IVRE project
#
# Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
from scapy.layers.inet import IP
from scapy.layers.inet6 import IPv6
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.sendrecv import srp1
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
from ..core import test
@test
def test_ipv4_udp_empty():
for p in [0, 53, 1000]:
req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, proto=17) / Raw() # UDP
repl = srp1(req, timeout=1)
assert repl is None, "expecting no answer, got one"
@test
def test_ipv6_udp_empty():
for p in [0, 53, 1000]:
req = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR, nh=17) / Raw() # UDP
repl = srp1(req, timeout=1)
assert repl is None, "expecting no answer, got one"

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# This file is part of masscanned. # This file is part of masscanned.
# Copyright 2021 - 2025 - The IVRE project # Copyright 2021 - The IVRE project
# #
# Masscanned is free software: you can redistribute it and/or modify it # Masscanned is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by # under the terms of the GNU General Public License as published by
@ -16,180 +16,63 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>. # along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
import atexit from scapy.all import *
import functools
import os
from signal import SIGINT
import subprocess
import sys
from time import sleep from time import sleep
from tempfile import NamedTemporaryFile from tempfile import _get_candidate_names as gen_tmp_filename
from tempfile import gettempdir
try: import subprocess
from ivre.config import guess_prefix import logging
except ImportError: import sys
HAS_IVRE = False import os
else:
HAS_IVRE = True
from scapy.config import conf
from scapy.interfaces import resolve_iface
from src.all import test_all from src.all import test_all
from src.conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR, OUTDIR from src.conf import *
# if args in CLI, they are passed to masscanned
def cleanup_net(iface): if len(sys.argv) > 1:
subprocess.check_call(["ip", "link", "delete", iface]) args = " ".join(sys.argv[1:])
subprocess.check_call(
[
"iptables",
"-D",
"INPUT",
"-i",
iface,
"-m",
"state",
"--state",
"ESTABLISHED",
"-j",
"ACCEPT",
]
)
subprocess.check_call(["iptables", "-D", "INPUT", "-i", iface, "-j", "DROP"])
try:
os.unlink(ipfile.name)
except NameError:
pass
def setup_net(iface):
# create the interfaces pair
atexit.register(functools.partial(cleanup_net, f"{iface}a"))
subprocess.check_call(
["ip", "link", "add", f"{iface}a", "type", "veth", "peer", f"{iface}b"]
)
for sub in "a", "b":
subprocess.check_call(["ip", "link", "set", f"{iface}{sub}", "up"])
subprocess.check_call(["ip", "addr", "add", "dev", f"{iface}a", "192.0.0.0/31"])
subprocess.check_call(
["ip", "addr", "add", "dev", f"{iface}a", "2001:41d0::1234:5678/96"]
)
subprocess.check_call(["ip", "route", "add", "1.2.3.4/32", "via", IPV4_ADDR])
# prevent problems between raw scanners (Scapy, Nmap, Masscan) and
# the host IP stack
subprocess.check_call(
[
"iptables",
"-A",
"INPUT",
"-i",
f"{iface}a",
"-m",
"state",
"--state",
"ESTABLISHED",
"-j",
"ACCEPT",
]
)
subprocess.check_call(["iptables", "-A", "INPUT", "-i", f"{iface}a", "-j", "DROP"])
conf.route.resync()
conf.route6.resync()
IFACE = "masscanned"
setup_net(IFACE)
TCPDUMP = bool(os.environ.get("USE_TCPDUMP"))
if HAS_IVRE:
ZEEK_PASSIVERECON = bool(os.environ.get("USE_ZEEK"))
else: else:
ZEEK_PASSIVERECON = False args = ""
P0F = bool(os.environ.get("USE_P0F"))
fmt = logging.Formatter("%(levelname)s\t%(message)s")
ch = logging.StreamHandler()
ch.setFormatter(fmt)
ch.setLevel(logging.INFO)
LOG = logging.getLogger(__name__)
LOG.setLevel(logging.INFO)
LOG.addHandler(ch)
conf.iface = 'tap0'
conf.verb = 0 conf.verb = 0
# prepare configuration file for masscanned # prepare configuration file for masscanned
with NamedTemporaryFile(delete=False, mode="w") as ipfile: ipfile = os.path.join(gettempdir(), next(gen_tmp_filename()))
ipfile.write(f"{IPV4_ADDR}\n") with open(ipfile, "w") as f:
ipfile.write(f"{IPV6_ADDR}\n") f.write("{}\n".format(IPV4_ADDR))
f.write("{}\n".format(IPV6_ADDR))
# create test interface # create test interface
conf.iface = resolve_iface(f"{IFACE}a") tap = TunTapInterface(resolve_iface(conf.iface))
# set interface
subprocess.run("ip a a dev {} 192.0.0.2".format(conf.iface), shell=True)
subprocess.run("ip link set {} up".format(conf.iface), shell=True)
# start capture # start capture
if TCPDUMP: tcpdump = subprocess.Popen("tcpdump -enli {} -w {}".format(conf.iface, os.path.join(OUTDIR, "test_capture.pcap")), shell=True,
tcpdump = subprocess.Popen( stdin=None, stdout=None, stderr=None, close_fds=True)
[
"tcpdump",
"-enli",
f"{IFACE}a",
"-w",
os.path.join(OUTDIR, "test_capture.pcap"),
]
)
if ZEEK_PASSIVERECON:
zeek = subprocess.Popen(
[
"zeek",
"-C",
"-b",
"-i",
f"{IFACE}a",
os.path.join(
guess_prefix("zeek"),
"ivre",
"passiverecon",
"bare.zeek",
),
"-e",
"redef tcp_content_deliver_all_resp = T; "
"redef tcp_content_deliver_all_orig = T; "
f"redef PassiveRecon::HONEYPOTS += {{ {IPV4_ADDR}, [{IPV6_ADDR}] }}",
],
stdout=open(os.path.join(OUTDIR, "zeek_passiverecon.stdout"), "w"),
stderr=open(os.path.join(OUTDIR, "zeek_passiverecon.stderr"), "w"),
)
if P0F:
p0f = subprocess.Popen(
["p0f", "-i", f"{IFACE}a", "-o", os.path.join(OUTDIR, "p0f_log.txt")],
stdout=open(os.path.join(OUTDIR, "p0f.stdout"), "w"),
stderr=open(os.path.join(OUTDIR, "p0f.stderr"), "w"),
)
# run masscanned # run masscanned
masscanned = subprocess.Popen( masscanned = subprocess.Popen("RUST_BACKTRACE=1 ./target/debug/masscanned -vvvvv -i {} -f {} -a {} {}".format(conf.iface, ipfile, MAC_ADDR, args), shell=True,
[ stdin=None, stdout=open("test/res/masscanned.stdout", "w"), stderr=open("test/res/masscanned.stderr", "w"), close_fds=True)
"./target/debug/masscanned",
"-vvvvv",
"-i",
f"{IFACE}b",
"--self-ip-file",
ipfile.name,
"-m",
MAC_ADDR,
]
# if args in CLI, they are passed to masscanned
+ sys.argv[1:],
env=dict(os.environ, RUST_BACKTRACE="1"),
stdout=open(os.path.join(OUTDIR, "masscanned.stdout"), "w"),
stderr=open(os.path.join(OUTDIR, "masscanned.stderr"), "w"),
)
sleep(1) sleep(1)
try: try:
result = test_all(masscanned) test_all(tap)
except AssertionError: except AssertionError:
result = -1 pass
# terminate masscanned # terminate masscanned
masscanned.send_signal(SIGINT) masscanned.kill()
masscanned.wait()
# terminate capture # terminate capture
if TCPDUMP: sleep(2)
tcpdump.send_signal(SIGINT) tcpdump.kill()
tcpdump.wait()
if ZEEK_PASSIVERECON:
zeek.send_signal(SIGINT)
zeek.wait()
if P0F:
p0f.send_signal(SIGINT)
p0f.wait()
sys.exit(result)