mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-01 22:28:20 +00:00
Add DNS to supported protocols + functionnal tests
This commit is contained in:
parent
c6be16382f
commit
2e296d7546
3 changed files with 251 additions and 2 deletions
|
@ -17,11 +17,15 @@
|
|||
use lazy_static::lazy_static;
|
||||
use log::*;
|
||||
use pnet::packet::ip::IpNextHeaderProtocols;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::client::ClientInfo;
|
||||
use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE};
|
||||
use crate::Masscanned;
|
||||
|
||||
mod dns;
|
||||
use dns::DNSPacket;
|
||||
|
||||
mod http;
|
||||
use http::HTTP_VERBS;
|
||||
|
||||
|
@ -40,6 +44,12 @@ 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};
|
||||
|
||||
|
@ -145,6 +155,16 @@ pub fn repl<'a>(
|
|||
if id == NO_MATCH {
|
||||
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) */
|
||||
match id {
|
||||
|
@ -160,7 +180,6 @@ pub fn repl<'a>(
|
|||
if let Some(t) = &mut tcb {
|
||||
t.proto_id = PROTO_NONE;
|
||||
}
|
||||
debug!("id: {}", id);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +328,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_proto_repl_http() {
|
||||
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();
|
||||
|
@ -338,4 +357,31 @@ mod tests {
|
|||
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,
|
||||
ip_addresses: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ from .core import test_all # noqa: F401
|
|||
|
||||
DEFAULT_TESTS = [
|
||||
"arp",
|
||||
"dns",
|
||||
"ghost",
|
||||
"http",
|
||||
"icmpv4",
|
||||
|
|
202
test/src/tests/dns.py
Normal file
202
test/src/tests/dns.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
# 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 socket import AF_INET6
|
||||
import struct
|
||||
|
||||
from scapy.layers.dns import DNS, DNSQR
|
||||
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_dns_a():
|
||||
sports = [13274] # [53, 13274, 12198, 888, 0]
|
||||
dports = [80] # [53, 5353, 80, 161, 24732]
|
||||
payload = DNS()
|
||||
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")
|
||||
req = (
|
||||
Ether(dst=MAC_ADDR)
|
||||
/ IP(dst=IPV4_ADDR)
|
||||
/ UDP(sport=sport, dport=dport)
|
||||
/ DNS(id=1234, rd=False, opcode=0, qd=qd))
|
||||
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 not DNS in udp:
|
||||
try:
|
||||
rr = DNS(udp.load)
|
||||
except Exception:
|
||||
raise AssertionError("no DNS layer found")
|
||||
else:
|
||||
rr = udp[DNS]
|
||||
assert(rr.id == 1234), f"unexpected id value: {rr.id}"
|
||||
assert(rr.qr == True), f"unexpected qr value"
|
||||
assert(rr.opcode == 0), f"unexpected opcode value"
|
||||
assert(rr.aa == True), f"unexpected aa value"
|
||||
assert(rr.tc == False), f"unexpected tc value"
|
||||
assert(rr.rd == False), f"unexpected rd value"
|
||||
assert(rr.ra == False), f"unexpected ra value"
|
||||
assert(rr.z == 0), f"unexpected z value"
|
||||
assert(rr.rcode == 0), f"unexpected rcode value"
|
||||
assert(rr.qdcount == 1), f"unexpected qdcount value"
|
||||
assert(rr.ancount == 1), f"unexpected ancount value"
|
||||
assert(rr.nscount == 0), f"unexpected nscount value"
|
||||
assert(rr.arcount == 0), f"unexpected arcount value"
|
||||
|
||||
"""
|
||||
|
||||
@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}"
|
||||
"""
|
Loading…
Add table
Add a link
Reference in a new issue