diff --git a/src/proto/mod.rs b/src/proto/mod.rs index 9be5c02..795c564 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -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); + } + } + } } diff --git a/test/src/all.py b/test/src/all.py index 44a82b3..561993e 100644 --- a/test/src/all.py +++ b/test/src/all.py @@ -22,6 +22,7 @@ from .core import test_all # noqa: F401 DEFAULT_TESTS = [ "arp", + "dns", "ghost", "http", "icmpv4", diff --git a/test/src/tests/dns.py b/test/src/tests/dns.py new file mode 100644 index 0000000..2b265ef --- /dev/null +++ b/test/src/tests/dns.py @@ -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 . + +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}" +"""