mirror of
https://github.com/trustedsec/social-engineer-toolkit
synced 2024-11-25 14:00:18 +00:00
Merge pull request #699 from meitar/dns-server
Revive SET's built-in DNS responder under Python 3
This commit is contained in:
commit
fba5551957
4 changed files with 263 additions and 69 deletions
|
@ -126,7 +126,9 @@ if operating_system == "posix":
|
|||
|
||||
dns = core.check_config("DNS_SERVER=")
|
||||
if dns.lower() == "on":
|
||||
core.start_dns()
|
||||
import src.core.minifakedns
|
||||
from src.core.setcore import detect_public_ip
|
||||
src.core.minifakedns.start_dns_server(detect_public_ip())
|
||||
|
||||
# remove old files
|
||||
for root, dirs, files in os.walk(core.userconfigpath):
|
||||
|
@ -153,7 +155,6 @@ if not os.path.isfile("/etc/setoolkit/set_config.py"):
|
|||
update_config()
|
||||
|
||||
define_version = core.get_version()
|
||||
core.cleanup_routine()
|
||||
|
||||
# create the set.options routine
|
||||
with open(os.path.join(core.userconfigpath, "set.options"), "w") as filewrite:
|
||||
|
|
243
src/core/minifakedns.py
Normal file
243
src/core/minifakedns.py
Normal file
|
@ -0,0 +1,243 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
SET core PyFakeMiniDNS server implementation.
|
||||
|
||||
Slightly modified implementation of Francisco Santos's PyfakeminiDNS
|
||||
script designed to run as a thread and handle various additional
|
||||
system configuration tasks, if necessary in the running environment,
|
||||
along with a few implementation considerations specifically for SET.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
# We need this module variable so the helper functions can be called
|
||||
# from outside of this module, e.g., during SET startup and cleanup.
|
||||
dns_server_thread = None
|
||||
|
||||
def start_dns_server(reply_ip):
|
||||
"""
|
||||
Helper function, intended to be called from other modules.
|
||||
|
||||
Args:
|
||||
reply_ip (string): IPv4 address in dotted quad notation to use in all answers.
|
||||
"""
|
||||
global dns_server_thread
|
||||
dns_server_thread = MiniFakeDNS(kwargs={'port': 53, 'ip': reply_ip})
|
||||
dns_server_thread.start()
|
||||
|
||||
def stop_dns_server():
|
||||
"""
|
||||
Helper function, intended to be called from other modules.
|
||||
"""
|
||||
dns_server_thread.stop()
|
||||
dns_server_thread.join()
|
||||
dns_server_thread.cleanup()
|
||||
|
||||
class DNSQuery:
|
||||
"""
|
||||
A DNS query (that can be parsed as binary data).
|
||||
|
||||
See original for reference, but note there have been changes:
|
||||
https://code.activestate.com/recipes/491264-mini-fake-dns-server/
|
||||
Among the changes are variables names that have been translated
|
||||
to English from their original Spanish.
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
"""
|
||||
Args:
|
||||
data (bytes): The binary data of the DNS packet from the wire.
|
||||
"""
|
||||
self.data = data
|
||||
|
||||
# The domain name the client is querying the DNS for.
|
||||
self.domain = ''
|
||||
|
||||
# Parse DNS packet headers.
|
||||
txn_id = data[:2] # DNS transaction ID, two bytes.
|
||||
flags = data[2:4] # DNS flags, also two bytes.
|
||||
|
||||
# To determine whether or not this DNS packet is a query that
|
||||
# we should respond to, we need to examine the "QR" field and
|
||||
# the "opcode" field. Together, these make up five bits, but
|
||||
# they are the left-most bits (most-significant bits) in the
|
||||
# first byte of the two-byte Flags field. An ASCII diagram:
|
||||
#
|
||||
# X XXXX ...
|
||||
# ^ ^
|
||||
# | \- The opcode bits are here.
|
||||
# |
|
||||
# The QR bit.
|
||||
#
|
||||
# To read them meaningfully, we first discard the three bits
|
||||
# in the rightmost (least significant) position by performing
|
||||
# a 3-place bitwise right shift, which in python is the `>>`
|
||||
# operator. At that point, we have a byte value like this:
|
||||
#
|
||||
# 000 X XXXX
|
||||
# ^ ^
|
||||
# | \- The opcode bits are here.
|
||||
# |
|
||||
# The QR bit.
|
||||
#
|
||||
# Now that the most significant bits are all zero'ed out, we
|
||||
# can test the values of the unknown bits to see if they are
|
||||
# representing a standard query.
|
||||
#
|
||||
# In DNS, a standard query has the opcode field set to zero,
|
||||
# so all the bits in the opcode field should be 0. Meanwhile,
|
||||
# the QR field should also be a 0, representing a DNS query
|
||||
# rather than a DNS reply. So what we are hoping to see is:
|
||||
#
|
||||
# 000 0 0000
|
||||
#
|
||||
# To test for this reliably, we do a bitwise AND with a value
|
||||
# of decimal 31, which is 11111 in binary, exactly five bits:
|
||||
#
|
||||
# 00000000 (Remember, 0 AND 1 equals 0.)
|
||||
# AND 00011111
|
||||
# ------------
|
||||
# 00000000 = decimal 0
|
||||
#
|
||||
# In one line of Python code, we get the following:
|
||||
kind = (flags[0] >> 3) & 31 # Opcode is in bits 4, 5, 6, and 7 of first byte.
|
||||
# QR bit is 8th bit, but it should be 0.
|
||||
# And now, we test to see if the result
|
||||
if 0 == kind: # was a standard query.
|
||||
|
||||
# The header of a DNS packet is exactly twelve bytes long,
|
||||
# meaning that the very start of the first DNS question
|
||||
# will always begin at the same offset.
|
||||
offset = 12 # The first question begins at the 13th byte.
|
||||
|
||||
# The DNS protocol encodes domain names as a series of
|
||||
# labels. Each label is prefixed by a single byte denoting
|
||||
# that label's length.
|
||||
length = data[offset]
|
||||
while 0 != length:
|
||||
self.domain += data[offset + 1 : offset + length + 1].decode() + '.'
|
||||
offset += length + 1
|
||||
length = data[offset]
|
||||
|
||||
def response(self, ip):
|
||||
"""
|
||||
Construct a DNS reply packet with a given IP address.
|
||||
|
||||
TODO: This responds incorrectly to EDNS queries that make use
|
||||
of the OPT pseudo-record type. Specifically, the pointer
|
||||
wrong because we do not check the length of the original
|
||||
query we received. Instead, we should note the length of
|
||||
the original packet until the end of the first question,
|
||||
and truncate (i.e., drop, ignore) the remainder.
|
||||
|
||||
For now, what this actually means is that testing this
|
||||
server using a recent version of `dig(1)` will fail
|
||||
unless you use the `+noedns` query option. For example:
|
||||
|
||||
dig @127.0.0.1 example.com +noedns
|
||||
|
||||
Simpler or older DNS utilities such as `host(1)` are
|
||||
probably going to work.
|
||||
|
||||
Args:
|
||||
ip (string): IP address to respond with.
|
||||
"""
|
||||
packet = b''
|
||||
if self.domain:
|
||||
packet += self.data[:2] + b'\x81\x80'
|
||||
packet += self.data[4:6] + self.data[4:6] + b'\x00\x00\x00\x00' # Questions and Answers Counts
|
||||
packet += self.data[12:] # Original Domain Name Question
|
||||
packet += b'\xc0\x0c' # Pointer to domain name
|
||||
packet += b'\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04' # Response type, ttl and resource data length -> 4 bytes
|
||||
packet += bytes([int(x) for x in ip.split('.')]) # 4 bytes of IP.
|
||||
return packet
|
||||
|
||||
class MiniFakeDNS(threading.Thread):
|
||||
"""
|
||||
The MiniFakeDNS server, written to be run as a Python Thread.
|
||||
"""
|
||||
def __init__(self, group=None, target=None, name=None,
|
||||
args=(), kwargs=None):
|
||||
super(MiniFakeDNS, self).__init__(
|
||||
group=group, target=target, name=name)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
# The IPs address we will respond with.
|
||||
self.ip = kwargs['ip']
|
||||
|
||||
# The port number we will attempt to bind to. Default is 53.
|
||||
self.port = kwargs['port']
|
||||
|
||||
# Remember which configuration we usurped, if any. Used to cleanup.
|
||||
self.cede_configuration = None
|
||||
|
||||
# A flag to indicate that the thread should exit.
|
||||
self.stop_flag = False
|
||||
|
||||
def run(self):
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udps:
|
||||
udps.setblocking(False)
|
||||
try:
|
||||
udps.bind(('', self.port))
|
||||
except OSError as e:
|
||||
if 'Address already in use' == e.strerror and os.path.exists('/etc/resolv.conf'):
|
||||
# We can't listen on port 53 because something else got
|
||||
# there before we did. It's probably systemd-resolved's
|
||||
# DNS stub resolver, but since we are probably running as
|
||||
# the `root` user, we can fix this ourselves.
|
||||
if 'stub-resolv.conf' in os.path.realpath('/etc/resolv.conf'):
|
||||
self.usurp_systemd_resolved()
|
||||
self.cede_configuration = self.cede_to_systemd_resolved
|
||||
# Try binding again, now that the port might be available.
|
||||
udps.bind(('', self.port))
|
||||
while not self.stop_flag:
|
||||
try:
|
||||
data, addr = udps.recvfrom(1024)
|
||||
p = DNSQuery(data)
|
||||
udps.sendto(p.response(self.ip), addr)
|
||||
except BlockingIOError:
|
||||
pass
|
||||
print("Exiting the DNS Server..")
|
||||
sys.exit()
|
||||
|
||||
def cleanup(self):
|
||||
if self.cede_configuration is not None:
|
||||
self.cede_configuration()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Signals to the DNS server thread to stop.
|
||||
"""
|
||||
self.stop_flag = True
|
||||
|
||||
def usurp_systemd_resolved(self):
|
||||
"""
|
||||
Helper function to get systemd-resolved out of the way when it
|
||||
is listening on 127.0.0.1:53 and we are trying to run SET's
|
||||
own DNS server.
|
||||
"""
|
||||
try:
|
||||
os.mkdir('/etc/systemd/resolved.conf.d')
|
||||
except (OSError, FileExistsError):
|
||||
pass
|
||||
with open('/etc/systemd/resolved.conf.d/99-setoolkit-dns.conf', 'w') as f:
|
||||
f.write("[Resolve]\nDNS=9.9.9.9\nDNSStubListener=no")
|
||||
os.rename('/etc/resolv.conf', '/etc/resolv.conf.original')
|
||||
os.symlink('/run/systemd/resolve/resolv.conf', '/etc/resolv.conf')
|
||||
subprocess.call(['systemctl', 'restart', 'systemd-resolved.service'])
|
||||
|
||||
def cede_to_systemd_resolved(self):
|
||||
"""
|
||||
Helper function to cede system configuration back to systemd-resolved
|
||||
after we have usurped control over DNS configuration away from it.
|
||||
"""
|
||||
os.remove('/etc/systemd/resolved.conf.d/99-setoolkit-dns.conf')
|
||||
os.remove('/etc/resolv.conf')
|
||||
os.rename('/etc/resolv.conf.original', '/etc/resolv.conf')
|
||||
subprocess.call(['systemctl', 'restart', 'systemd-resolved.service'])
|
||||
|
|
@ -345,10 +345,7 @@ this is how networking works.
|
|||
""")
|
||||
|
||||
try:
|
||||
rhost = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
rhost.connect(('google.com', 0))
|
||||
rhost.settimeout(2)
|
||||
revipaddr = rhost.getsockname()[0]
|
||||
revipaddr = detect_public_ip()
|
||||
ipaddr = raw_input(setprompt(["2"], "IP address for the POST back in Harvester/Tabnabbing [" + revipaddr + "]"))
|
||||
if ipaddr == "": ipaddr=revipaddr
|
||||
except Exception:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# !/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Centralized core modules for SET
|
||||
#
|
||||
|
@ -16,6 +16,7 @@ import string
|
|||
import inspect
|
||||
import base64
|
||||
from src.core import dictionaries
|
||||
import src.core.minifakedns
|
||||
import io
|
||||
import trace
|
||||
|
||||
|
@ -303,7 +304,19 @@ class create_menu:
|
|||
return
|
||||
|
||||
|
||||
def detect_public_ip():
|
||||
"""
|
||||
Helper function to auto-detect our public IP(v4) address.
|
||||
"""
|
||||
rhost = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
rhost.connect(('google.com', 0))
|
||||
rhost.settimeout(2)
|
||||
return rhost.getsockname()[0]
|
||||
|
||||
def validate_ip(address):
|
||||
"""
|
||||
Validates that a given string is an IPv4 dotted quad.
|
||||
"""
|
||||
try:
|
||||
if socket.inet_aton(address):
|
||||
if len(address.split('.')) == 4:
|
||||
|
@ -428,10 +441,7 @@ def meta_database():
|
|||
#
|
||||
def grab_ipaddress():
|
||||
try:
|
||||
rhost = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
rhost.connect(('google.com', 0))
|
||||
rhost.settimeout(2)
|
||||
revipaddr = rhost.getsockname()[0]
|
||||
revipaddr = detect_public_ip()
|
||||
rhost = raw_input(setprompt("0", "IP address or URL (www.ex.com) for the payload listener (LHOST) [" + revipaddr + "]"))
|
||||
if rhost == "": rhost = revipaddr
|
||||
|
||||
|
@ -480,7 +490,7 @@ def cleanup_routine():
|
|||
os.remove(userconfigpath + "Signed_Update.jar")
|
||||
if os.path.isfile(userconfigpath + "version.lock"):
|
||||
os.remove(userconfigpath + "version.lock")
|
||||
|
||||
src.core.minifakedns.stop_dns_server()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -1702,63 +1712,6 @@ def check_ports(filename, port):
|
|||
else:
|
||||
return False
|
||||
|
||||
# main dns class
|
||||
|
||||
|
||||
class DNSQuery:
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.dominio = ''
|
||||
|
||||
tipo = (ord(data[2]) >> 3) & 15 # Opcode bits
|
||||
if tipo == 0: # Standard query
|
||||
ini = 12
|
||||
lon = ord(data[ini])
|
||||
while lon != 0:
|
||||
self.dominio += data[ini + 1:ini + lon + 1] + '.'
|
||||
ini += lon + 1
|
||||
lon = ord(data[ini])
|
||||
|
||||
def respuesta(self, ip):
|
||||
packet = ''
|
||||
if self.dominio:
|
||||
packet += self.data[:2] + "\x81\x80"
|
||||
packet += self.data[4:6] + self.data[4:6] + \
|
||||
'\x00\x00\x00\x00' # Questions and Answers Counts
|
||||
# Original Domain Name Question
|
||||
packet += self.data[12:]
|
||||
# Pointer to domain name
|
||||
packet += '\xc0\x0c'
|
||||
# Response type, ttl and resource data length -> 4 bytes
|
||||
packet += '\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04'
|
||||
packet += str.join('', [chr(int(x))
|
||||
for x in ip.split('.')]) # 4bytes of IP
|
||||
return packet
|
||||
|
||||
# main dns routine
|
||||
|
||||
|
||||
def dns():
|
||||
udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udps.bind(('', 53))
|
||||
try:
|
||||
while 1:
|
||||
data, addr = udps.recvfrom(1024)
|
||||
p = DNSQuery(data)
|
||||
udps.sendto(p.respuesta(ip), addr)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("Exiting the DNS Server..")
|
||||
sys.exit()
|
||||
udps.close()
|
||||
|
||||
# start dns
|
||||
|
||||
|
||||
def start_dns():
|
||||
thread.start_new_thread(dns, ())
|
||||
|
||||
# the main ~./set path for SET
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue