secdev / scapy

Scapy: the Python-based interactive packet manipulation program & library.
https://scapy.net
GNU General Public License v2.0
10.6k stars 2.01k forks source link

Netbios query response is missing an answer() #4443

Closed lawndoc closed 3 months ago

lawndoc commented 3 months ago

Brief description

I am using sr1 to send UDP packets for protocols like LLMNR and NBNS. Using wireshark and tcpdump, I am able to see the packets being sent and the responses being received, but the Scapy library is not capturing the response after it hits the interface on the host.

Scapy version

2.6.0-dev (main)

Python version

3.12.3

Operating system

Ubuntu 24.04 LTS (Linux 6.8.0-35-generic)

Additional environment information

Both hosts in the below pcap are in 172.19.0.0/24. The pcaps were captured on the interface of the host running Scapy using tcpdump.

nbns pcap: nbns.zip llmnr pcap: llmnr.zip

The host sending responses is running Responder to ensure that all LLMNR and NBNS requests have a response sent back.

How to reproduce

Code to reproduce:

from scapy.all import *
from scapy.layers.inet import IP, UDP
from scapy.layers.netbios import NBNSQueryRequest, NBNSQueryResponse, NBNSHeader

# change IP(dst= to your local broadcast IP
packet = IP(dst="172.19.0.255")/UDP(sport=137, dport=137)/NBNSHeader(OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1)/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME="Loremipsumdolorsitamet", QUESTION_TYPE="NB")
response = sr1(packet, timeout=3, verbose=0)
if response is not None and response.haslayer(NBNSQueryResponse):
    # Print all resolved IP addresses
    for i in range(response[NBNSQueryResponse].RDLENGTH):
        print(f"Got a response from: {response[NBNSQueryResponse].ADDR_ENTRY[i].NB_ADDRESS}")

Have Responder running on the same subnet or query a valid hostname of a Windows host on that subnet to get a response.

Actual result

No response

Expected result

No response

Related resources

No response

lawndoc commented 3 months ago

CC: #4275

lawndoc commented 3 months ago

There might be a race condition happening here.

As a workaround for this issue, we were able to use AsyncSniffer to capture packets rather than relying on the data returned from sr1. That code is below:

from scapy.all import *
from scapy.layers.inet import IP, UDP
from scapy.layers.netbios import NBNSQueryRequest, NBNSQueryResponse, NBNSHeader

# change IP(dst= to your local broadcast IP
packet = IP(dst="172.19.0.255")/UDP(sport=137, dport=137)/NBNSHeader(OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1)/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME="Loremipsumdolorsitamet", QUESTION_TYPE="NB")
sniffer = AsyncSniffer(filter="udp dst port 137", store=True)
sniffer.start()
sleep(0.5)  # !!! needed to add a delay before or we would sometimes miss the packets
sr1(packet, timeout=self.timeout, verbose=0)
sleep(self.timeout)
response = sniffer.stop()
if not response:
    if self.verbosity >= 1:
        print("No response (NBNS -> {self.hostname})")
    return
if self.verbosity >=1:
    for p in response:
        print(p)
# Print all resolved IP addresses
for sniffed_packet in response:
    if sniffed_packet is not None and sniffed_packet.haslayer(NBNSQueryResponse):
        for answer in sniffed_packet[NBNSQueryResponse].ADDR_ENTRY:
            print(f"Got a response from: {response[NBNSQueryResponse].ADDR_ENTRY[i].NB_ADDRESS}")

Originally, we didn't have the sleep(0.5) call between sniffer.start() and sending the query with sr1. We were running into a strange issue where it would inconsistently not capture any packets. After adding the delay between sniffer.start() and sr1, we were consistently capturing all requests and responses.

I wonder if this issue we are having with sr1 not capturing responses is due to the same reason, it's not starting the sniffer fast enough to capture the responses?

gpotter2 commented 3 months ago

There might be a race condition happening here.

No.

You're missing conf.checkIPaddr = False.

Matching Netbios Query answers was however not implemented. This is fixed by https://github.com/secdev/scapy/pull/4445. Thanks for reporting that

lawndoc commented 3 months ago

I see now that config flag was called out in the usage documentation in the DHCP section. I wish you wouldn't say it like it was obvious, though. I just started using Scapy 2 weeks ago. I was just trying to be helpful and provide ideas. 😄

Thank you for discovering the Netbios Query answer bug, that would have been the next thing I ran into. I appreciate the work you do as an open source maintainer. If there's anything I can do better when reporting or troubleshooting issues, let me know.

gpotter2 commented 3 months ago

I wish you wouldn't say it like it was obvious, though

Sorry about that :P this flag is honestly hard to guess, it's alright.

I've added a doc example to #4445 for Netbios to make this a bit easier to find.

I appreciate the work you do as an open source maintainer.

You're welcome :)

lawndoc commented 3 months ago

Sorry to pester, but I tested again after the merge (scapy-2.6.0rc1.dev44) using the code below and am still not getting the netbios response captured by Scapy.

from scapy.all import *
from scapy.layers.inet import IP, UDP
from scapy.layers.netbios import NBNSQueryRequest, NBNSQueryResponse, NBNSHeader

# required for broadcast requests
conf.checkIPaddr = False

hostname = "Loremipsumdolorsitamet"

# change IP(dst= to your local broadcast IP
packet = IP(dst="172.19.0.255")/UDP(sport=137, dport=137)/NBNSHeader(OPCODE=0x0, NM_FLAGS=0x11, QDCOUNT=1)/NBNSQueryRequest(SUFFIX="file server service", QUESTION_NAME="Loremipsumdolorsitamet", QUESTION_TYPE="NB")
response = sr1(packet, timeout=1, verbose=0)
if not response:
    print("No response (NBNS -> {hostname})")
    exit()
for p in response:
    print(p)
# Print all resolved IP addresses
for sniffed_packet in response:
    if sniffed_packet is not None and sniffed_packet.haslayer(NBNSQueryResponse):
        for answer in sniffed_packet[NBNSQueryResponse].ADDR_ENTRY:
            print(f"Got a response from: {response[NBNSQueryResponse].ADDR_ENTRY[i].NB_ADDRESS}")

pcap from host running the above code: netbios.zip

Also, this code will actually work unlike the code I sent originally which still had self references. Sorry about that 😄

I'll do some of the troubleshooting steps you suggested here and report back.

lawndoc commented 3 months ago

Looks like we are getting the following results in the debug object:

<Sent: TCP:0 UDP:1 ICMP:0 Other:0>
<Received: TCP:0 UDP:1 ICMP:0 Other:6>
<Results: TCP:0 UDP:0 ICMP:0 Other:0>

I'll also take a look around the code that was changed in the PR, but I'm not as well versed at the byte level implementation of the packets.

lawndoc commented 3 months ago

Based on the fact that your unit tests are passing, I'm guessing there's something else I'm doing wrong lol.

lawndoc commented 3 months ago

I figured out the problem! The hostname we were querying was longer than the max length 15 of the netbios host field.

I added logic to my code to slice the string to [:15] before sending the request. Interestingly, it looks like Scapy already handles the slicing when it sends the packets, but the value of self.RR_NAME in answers contains the full-length hostname.

I might open a PR to handle that gracefully in the answers function. It would prevent future noobs like me from making the same mistake, and I don't think it would hurt anything to have Scapy handle that.