Skip to content

Commit 29d7360

Browse files
authored
Add nbns_request() util (#4853)
1 parent c776b9f commit 29d7360

File tree

4 files changed

+107
-3
lines changed

4 files changed

+107
-3
lines changed

scapy/layers/dns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,7 @@ def dns_resolve(qname, qtype="A", raw=False, tcp=False, verbose=1, timeout=3, **
14051405
:param timeout: seconds until timeout (per server)
14061406
:raise TimeoutError: if no DNS servers were reached in time.
14071407
"""
1408-
# Unify types
1408+
# Unify types (for caching)
14091409
qtype = DNSQR.qtype.any2i_one(None, qtype)
14101410
qname = DNSQR.qname.any2i(None, qname)
14111411
# Check cache

scapy/layers/netbios.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,17 @@
3434
XShortField,
3535
XStrFixedLenField
3636
)
37+
from scapy.interfaces import _GlobInterfaceType
38+
from scapy.sendrecv import sr1
3739
from scapy.layers.inet import IP, UDP, TCP
3840
from scapy.layers.l2 import Ether, SourceMACField
3941

42+
# Typing imports
43+
from typing import (
44+
List,
45+
Union,
46+
)
47+
4048

4149
class NetBIOS_DS(Packet):
4250
name = "NetBIOS datagram service"
@@ -398,6 +406,93 @@ def tcp_reassemble(cls, data, *args, **kwargs):
398406
bind_layers(TCP, NBTSession, dport=139, sport=139)
399407

400408

409+
_nbns_cache = conf.netcache.new_cache("nbns_cache", 300)
410+
411+
412+
@conf.commands.register
413+
def nbns_resolve(
414+
qname: str,
415+
iface: Union[_GlobInterfaceType, List[_GlobInterfaceType]] = None,
416+
raw: bool = False,
417+
timeout: int = 3,
418+
**kwargs,
419+
) -> List[str]:
420+
"""
421+
Perform a simple NBNS (NetBios Name Services) resolution with caching
422+
423+
:param qname: the name to query
424+
:param iface: the interfaces to use. (default: all)
425+
:param raw: return the whole netbios packet (default False)
426+
:param timeout: seconds until timeout (per server)
427+
:raise TimeoutError: if no DNS servers were reached in time.
428+
"""
429+
kwargs.setdefault("verbose", 0)
430+
431+
# Unify types (for caching)
432+
qname = NBNSQueryRequest.QUESTION_NAME.any2i(None, qname)
433+
434+
# Check cache
435+
cache_ident = qname + b"raw" if raw else b""
436+
result = _nbns_cache.get(cache_ident)
437+
if result:
438+
return result
439+
440+
if iface is None:
441+
ifaces = [
442+
x
443+
for name, x in conf.ifaces.items()
444+
if x.is_valid() and name != conf.loopback_name
445+
]
446+
elif isinstance(iface, list):
447+
ifaces = iface
448+
else:
449+
ifaces = [iface]
450+
451+
# Builds a request for each broadcast address of each interface
452+
requests = []
453+
for iface in ifaces:
454+
for bdcst in conf.route.get_if_bcast(iface):
455+
if bdcst == "255.255.255.255":
456+
continue
457+
requests.append(
458+
IP(dst=bdcst) /
459+
UDP() /
460+
NBNSHeader() /
461+
NBNSQueryRequest(QUESTION_NAME=qname)
462+
)
463+
464+
if not requests:
465+
return None
466+
467+
# Perform requests, get the first response
468+
try:
469+
old_checkIPAddr = conf.checkIPaddr
470+
conf.checkIPaddr = False
471+
472+
res = sr1(
473+
requests,
474+
timeout=timeout,
475+
first=True,
476+
**kwargs,
477+
)
478+
finally:
479+
conf.checkIPaddr = old_checkIPAddr
480+
481+
if res is not None:
482+
if raw:
483+
# Raw
484+
result = res
485+
else:
486+
# Get IP
487+
result = [x.NB_ADDRESS for x in res.ADDR_ENTRY]
488+
if result:
489+
# Cache it
490+
_nbns_cache[cache_ident] = result
491+
return result
492+
else:
493+
raise TimeoutError
494+
495+
401496
class NBNS_am(AnsweringMachine):
402497
function_name = "nbnsd"
403498
filter = "udp port 137"

scapy/route.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,11 @@ def route(self, dst=None, dev=None, verbose=conf.verb, _internal=False):
229229

230230
def get_if_bcast(self, iff):
231231
# type: (str) -> List[str]
232+
"""
233+
Return the list of broadcast addresses of an interface.
234+
"""
232235
bcast_list = []
233-
for net, msk, gw, iface, addr, metric in self.routes:
236+
for net, msk, _, iface, _, _ in self.routes:
234237
if net == 0:
235238
continue # Ignore default route "0.0.0.0"
236239
elif msk == 0xffffffff:

scapy/sendrecv.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class debug:
8686
if negative, how many times to retry when no more packets
8787
are answered
8888
:param multi: whether to accept multiple answers for the same stimulus
89+
:param first: stop after receiving the first response of any sent packet
8990
:param rcv_pks: if set, will be used instead of pks to receive packets.
9091
packets will still be sent through pks
9192
:param prebuild: pre-build the packets before starting to send them.
@@ -125,6 +126,7 @@ def __init__(self,
125126
chainCC=False, # type: bool
126127
retry=0, # type: int
127128
multi=False, # type: bool
129+
first=False, # type: bool
128130
rcv_pks=None, # type: Optional[SuperSocket]
129131
prebuild=False, # type: bool
130132
_flood=None, # type: Optional[_FloodGenerator]
@@ -150,6 +152,7 @@ def __init__(self,
150152
self.chainCC = chainCC
151153
self.multi = multi
152154
self.timeout = timeout
155+
self.first = first
153156
self.session = session
154157
self.chainEX = chainEX
155158
self.stop_filter = stop_filter
@@ -254,7 +257,10 @@ def results(self):
254257

255258
def _stop_sniffer_if_done(self) -> None:
256259
"""Close the sniffer if all expected answers have been received"""
257-
if self._send_done and self.noans >= self.notans and not self.multi:
260+
if (
261+
self._send_done and self.noans >= self.notans and not self.multi or
262+
self.first and self.noans
263+
):
258264
if self.sniffer and self.sniffer.running:
259265
self.sniffer.stop(join=False)
260266

0 commit comments

Comments
 (0)