mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-07-13 16:43:59 -07:00
Initial commit for v1.0 using mitmproxy instead of twisted
Added a plugin system to Net-Creds so you can now add your own parsers, api hook names might change between now and the offcial release (will submit a PR to the original repo once completed) The main MITM HTTP Proxy now uses mitmproxy which is a big deal, cuts the code down by an insane amount, no more twisted! yay! Basic plugin have been re-wrote for the new proxy engine Since we are using mitmproxy we have out of the box support for SSL/TLS!
This commit is contained in:
commit
eea5f53be2
50 changed files with 5525 additions and 0 deletions
428
core/servers/dns.py
Normal file
428
core/servers/dns.py
Normal file
|
@ -0,0 +1,428 @@
|
|||
# DNSChef is a highly configurable DNS Proxy for Penetration Testers
|
||||
# and Malware Analysts. Please visit http://thesprawl.org/projects/dnschef/
|
||||
# for the latest version and documentation. Please forward all issues and
|
||||
# concerns to iphelix [at] thesprawl.org.
|
||||
|
||||
# Copyright (C) 2015 Peter Kacherginsky, Marcello Salvati
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import threading, random, operator, time
|
||||
import SocketServer, socket, sys, os
|
||||
import binascii
|
||||
import string
|
||||
import base64
|
||||
import time
|
||||
import logging
|
||||
|
||||
from configobj import ConfigObj
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.utils import shutdown
|
||||
from core.logger import logger
|
||||
|
||||
from dnslib import *
|
||||
from IPy import IP
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s %(clientip)s [DNS] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("DNSChef", formatter)
|
||||
|
||||
dnslog = logging.getLogger('dnslog')
|
||||
handler = logging.FileHandler('./logs/dns/dns.log',)
|
||||
handler.setFormatter(formatter)
|
||||
dnslog.addHandler(handler)
|
||||
dnslog.setLevel(logging.INFO)
|
||||
|
||||
# DNSHandler Mixin. The class contains generic functions to parse DNS requests and
|
||||
# calculate an appropriate response based on user parameters.
|
||||
class DNSHandler():
|
||||
|
||||
def parse(self,data):
|
||||
|
||||
nametodns = DNSChef().nametodns
|
||||
nameservers = DNSChef().nameservers
|
||||
hsts = DNSChef().hsts
|
||||
hstsconfig = DNSChef().real_records
|
||||
server_address = DNSChef().server_address
|
||||
clientip = {"clientip": self.client_address[0]}
|
||||
|
||||
response = ""
|
||||
|
||||
try:
|
||||
# Parse data as DNS
|
||||
d = DNSRecord.parse(data)
|
||||
|
||||
except Exception as e:
|
||||
log.info("Error: invalid DNS request", extra=clientip)
|
||||
dnslog.info("Error: invalid DNS request", extra=clientip)
|
||||
|
||||
else:
|
||||
# Only Process DNS Queries
|
||||
if QR[d.header.qr] == "QUERY":
|
||||
|
||||
# Gather query parameters
|
||||
# NOTE: Do not lowercase qname here, because we want to see
|
||||
# any case request weirdness in the logs.
|
||||
qname = str(d.q.qname)
|
||||
|
||||
# Chop off the last period
|
||||
if qname[-1] == '.': qname = qname[:-1]
|
||||
|
||||
qtype = QTYPE[d.q.qtype]
|
||||
|
||||
# Find all matching fake DNS records for the query name or get False
|
||||
fake_records = dict()
|
||||
|
||||
for record in nametodns:
|
||||
|
||||
fake_records[record] = self.findnametodns(qname, nametodns[record])
|
||||
|
||||
if hsts:
|
||||
if qname in hstsconfig:
|
||||
response = self.hstsbypass(hstsconfig[qname], qname, nameservers, d)
|
||||
return response
|
||||
|
||||
elif qname[:4] == 'wwww':
|
||||
response = self.hstsbypass(qname[1:], qname, nameservers, d)
|
||||
return response
|
||||
|
||||
elif qname[:3] == 'web':
|
||||
response = self.hstsbypass(qname[3:], qname, nameservers, d)
|
||||
return response
|
||||
|
||||
# Check if there is a fake record for the current request qtype
|
||||
if qtype in fake_records and fake_records[qtype]:
|
||||
|
||||
fake_record = fake_records[qtype]
|
||||
|
||||
# Create a custom response to the query
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
log.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip)
|
||||
dnslog.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip)
|
||||
|
||||
# IPv6 needs additional work before inclusion:
|
||||
if qtype == "AAAA":
|
||||
ipv6 = IP(fake_record)
|
||||
ipv6_bin = ipv6.strBin()
|
||||
ipv6_hex_tuple = [int(ipv6_bin[i:i+8],2) for i in xrange(0,len(ipv6_bin),8)]
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](ipv6_hex_tuple)))
|
||||
|
||||
elif qtype == "SOA":
|
||||
mname,rname,t1,t2,t3,t4,t5 = fake_record.split(" ")
|
||||
times = tuple([int(t) for t in [t1,t2,t3,t4,t5]])
|
||||
|
||||
# dnslib doesn't like trailing dots
|
||||
if mname[-1] == ".": mname = mname[:-1]
|
||||
if rname[-1] == ".": rname = rname[:-1]
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](mname,rname,times)))
|
||||
|
||||
elif qtype == "NAPTR":
|
||||
order,preference,flags,service,regexp,replacement = fake_record.split(" ")
|
||||
order = int(order)
|
||||
preference = int(preference)
|
||||
|
||||
# dnslib doesn't like trailing dots
|
||||
if replacement[-1] == ".": replacement = replacement[:-1]
|
||||
|
||||
response.add_answer( RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](order,preference,flags,service,regexp,DNSLabel(replacement))) )
|
||||
|
||||
elif qtype == "SRV":
|
||||
priority, weight, port, target = fake_record.split(" ")
|
||||
priority = int(priority)
|
||||
weight = int(weight)
|
||||
port = int(port)
|
||||
if target[-1] == ".": target = target[:-1]
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](priority, weight, port, target) ))
|
||||
|
||||
elif qtype == "DNSKEY":
|
||||
flags, protocol, algorithm, key = fake_record.split(" ")
|
||||
flags = int(flags)
|
||||
protocol = int(protocol)
|
||||
algorithm = int(algorithm)
|
||||
key = base64.b64decode(("".join(key)).encode('ascii'))
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](flags, protocol, algorithm, key) ))
|
||||
|
||||
elif qtype == "RRSIG":
|
||||
covered, algorithm, labels, orig_ttl, sig_exp, sig_inc, key_tag, name, sig = fake_record.split(" ")
|
||||
covered = getattr(QTYPE,covered) # NOTE: Covered QTYPE
|
||||
algorithm = int(algorithm)
|
||||
labels = int(labels)
|
||||
orig_ttl = int(orig_ttl)
|
||||
sig_exp = int(time.mktime(time.strptime(sig_exp +'GMT',"%Y%m%d%H%M%S%Z")))
|
||||
sig_inc = int(time.mktime(time.strptime(sig_inc +'GMT',"%Y%m%d%H%M%S%Z")))
|
||||
key_tag = int(key_tag)
|
||||
if name[-1] == '.': name = name[:-1]
|
||||
sig = base64.b64decode(("".join(sig)).encode('ascii'))
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](covered, algorithm, labels,orig_ttl, sig_exp, sig_inc, key_tag, name, sig)))
|
||||
|
||||
else:
|
||||
# dnslib doesn't like trailing dots
|
||||
if fake_record[-1] == ".": fake_record = fake_record[:-1]
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))
|
||||
|
||||
response = response.pack()
|
||||
|
||||
elif qtype == "*" and not None in fake_records.values():
|
||||
log.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip)
|
||||
dnslog.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip)
|
||||
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
for qtype,fake_record in fake_records.items():
|
||||
if fake_record:
|
||||
|
||||
# NOTE: RDMAP is a dictionary map of qtype strings to handling classses
|
||||
# IPv6 needs additional work before inclusion:
|
||||
if qtype == "AAAA":
|
||||
ipv6 = IP(fake_record)
|
||||
ipv6_bin = ipv6.strBin()
|
||||
fake_record = [int(ipv6_bin[i:i+8],2) for i in xrange(0,len(ipv6_bin),8)]
|
||||
|
||||
elif qtype == "SOA":
|
||||
mname,rname,t1,t2,t3,t4,t5 = fake_record.split(" ")
|
||||
times = tuple([int(t) for t in [t1,t2,t3,t4,t5]])
|
||||
|
||||
# dnslib doesn't like trailing dots
|
||||
if mname[-1] == ".": mname = mname[:-1]
|
||||
if rname[-1] == ".": rname = rname[:-1]
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](mname,rname,times)))
|
||||
|
||||
elif qtype == "NAPTR":
|
||||
order,preference,flags,service,regexp,replacement = fake_record.split(" ")
|
||||
order = int(order)
|
||||
preference = int(preference)
|
||||
|
||||
# dnslib doesn't like trailing dots
|
||||
if replacement and replacement[-1] == ".": replacement = replacement[:-1]
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](order,preference,flags,service,regexp,replacement)))
|
||||
|
||||
elif qtype == "SRV":
|
||||
priority, weight, port, target = fake_record.split(" ")
|
||||
priority = int(priority)
|
||||
weight = int(weight)
|
||||
port = int(port)
|
||||
if target[-1] == ".": target = target[:-1]
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](priority, weight, port, target) ))
|
||||
|
||||
elif qtype == "DNSKEY":
|
||||
flags, protocol, algorithm, key = fake_record.split(" ")
|
||||
flags = int(flags)
|
||||
protocol = int(protocol)
|
||||
algorithm = int(algorithm)
|
||||
key = base64.b64decode(("".join(key)).encode('ascii'))
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](flags, protocol, algorithm, key) ))
|
||||
|
||||
elif qtype == "RRSIG":
|
||||
covered, algorithm, labels, orig_ttl, sig_exp, sig_inc, key_tag, name, sig = fake_record.split(" ")
|
||||
covered = getattr(QTYPE,covered) # NOTE: Covered QTYPE
|
||||
algorithm = int(algorithm)
|
||||
labels = int(labels)
|
||||
orig_ttl = int(orig_ttl)
|
||||
sig_exp = int(time.mktime(time.strptime(sig_exp +'GMT',"%Y%m%d%H%M%S%Z")))
|
||||
sig_inc = int(time.mktime(time.strptime(sig_inc +'GMT',"%Y%m%d%H%M%S%Z")))
|
||||
key_tag = int(key_tag)
|
||||
if name[-1] == '.': name = name[:-1]
|
||||
sig = base64.b64decode(("".join(sig)).encode('ascii'))
|
||||
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](covered, algorithm, labels,orig_ttl, sig_exp, sig_inc, key_tag, name, sig) ))
|
||||
|
||||
else:
|
||||
# dnslib doesn't like trailing dots
|
||||
if fake_record[-1] == ".": fake_record = fake_record[:-1]
|
||||
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))
|
||||
|
||||
response = response.pack()
|
||||
|
||||
# Proxy the request
|
||||
else:
|
||||
log.debug("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip)
|
||||
dnslog.info("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip)
|
||||
|
||||
nameserver_tuple = random.choice(nameservers).split('#')
|
||||
response = self.proxyrequest(data, *nameserver_tuple)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# Find appropriate ip address to use for a queried name. The function can
|
||||
def findnametodns(self,qname,nametodns):
|
||||
|
||||
# Make qname case insensitive
|
||||
qname = qname.lower()
|
||||
|
||||
# Split and reverse qname into components for matching.
|
||||
qnamelist = qname.split('.')
|
||||
qnamelist.reverse()
|
||||
|
||||
# HACK: It is important to search the nametodns dictionary before iterating it so that
|
||||
# global matching ['*.*.*.*.*.*.*.*.*.*'] will match last. Use sorting for that.
|
||||
for domain,host in sorted(nametodns.iteritems(), key=operator.itemgetter(1)):
|
||||
|
||||
# NOTE: It is assumed that domain name was already lowercased
|
||||
# when it was loaded through --file, --fakedomains or --truedomains
|
||||
# don't want to waste time lowercasing domains on every request.
|
||||
|
||||
# Split and reverse domain into components for matching
|
||||
domain = domain.split('.')
|
||||
domain.reverse()
|
||||
|
||||
# Compare domains in reverse.
|
||||
for a,b in map(None,qnamelist,domain):
|
||||
if a != b and b != "*":
|
||||
break
|
||||
else:
|
||||
# Could be a real IP or False if we are doing reverse matching with 'truedomains'
|
||||
return host
|
||||
else:
|
||||
return False
|
||||
|
||||
# Obtain a response from a real DNS server.
|
||||
def proxyrequest(self, request, host, port="53", protocol="udp"):
|
||||
clientip = {'clientip': self.client_address[0]}
|
||||
|
||||
reply = None
|
||||
try:
|
||||
if DNSChef().ipv6:
|
||||
|
||||
if protocol == "udp":
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
elif protocol == "tcp":
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
|
||||
else:
|
||||
if protocol == "udp":
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
elif protocol == "tcp":
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
sock.settimeout(3.0)
|
||||
|
||||
# Send the proxy request to a randomly chosen DNS server
|
||||
|
||||
if protocol == "udp":
|
||||
sock.sendto(request, (host, int(port)))
|
||||
reply = sock.recv(1024)
|
||||
sock.close()
|
||||
|
||||
elif protocol == "tcp":
|
||||
sock.connect((host, int(port)))
|
||||
|
||||
# Add length for the TCP request
|
||||
length = binascii.unhexlify("%04x" % len(request))
|
||||
sock.sendall(length+request)
|
||||
|
||||
# Strip length from the response
|
||||
reply = sock.recv(1024)
|
||||
reply = reply[2:]
|
||||
|
||||
sock.close()
|
||||
|
||||
except Exception as e:
|
||||
log.warning("Could not proxy request: {}".format(e), extra=clientip)
|
||||
dnslog.info("Could not proxy request: {}".format(e), extra=clientip)
|
||||
else:
|
||||
return reply
|
||||
|
||||
def hstsbypass(self, real_domain, fake_domain, nameservers, d):
|
||||
clientip = {'clientip': self.client_address[0]}
|
||||
|
||||
log.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip)
|
||||
dnslog.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip)
|
||||
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
nameserver_tuple = random.choice(nameservers).split('#')
|
||||
|
||||
#First proxy the request with the real domain
|
||||
q = DNSRecord.question(real_domain).pack()
|
||||
r = self.proxyrequest(q, *nameserver_tuple)
|
||||
if r is None: return None
|
||||
|
||||
#Parse the answer
|
||||
dns_rr = DNSRecord.parse(r).rr
|
||||
|
||||
#Create the DNS response
|
||||
for res in dns_rr:
|
||||
if res.get_rname() == real_domain:
|
||||
res.set_rname(fake_domain)
|
||||
response.add_answer(res)
|
||||
else:
|
||||
response.add_answer(res)
|
||||
|
||||
return response.pack()
|
||||
|
||||
# UDP DNS Handler for incoming requests
|
||||
class UDPHandler(DNSHandler, SocketServer.BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
(data,socket) = self.request
|
||||
response = self.parse(data)
|
||||
|
||||
if response:
|
||||
socket.sendto(response, self.client_address)
|
||||
|
||||
# TCP DNS Handler for incoming requests
|
||||
class TCPHandler(DNSHandler, SocketServer.BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
data = self.request.recv(1024)
|
||||
|
||||
# Remove the addition "length" parameter used in the
|
||||
# TCP DNS protocol
|
||||
data = data[2:]
|
||||
response = self.parse(data)
|
||||
|
||||
if response:
|
||||
# Calculate and add the additional "length" parameter
|
||||
# used in TCP DNS protocol
|
||||
length = binascii.unhexlify("%04x" % len(response))
|
||||
self.request.sendall(length+response)
|
||||
|
||||
class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
|
||||
|
||||
# Override SocketServer.UDPServer to add extra parameters
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
self.address_family = socket.AF_INET6 if DNSChef().ipv6 else socket.AF_INET
|
||||
|
||||
SocketServer.UDPServer.__init__(self,server_address,RequestHandlerClass)
|
||||
|
||||
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
||||
|
||||
# Override default value
|
||||
allow_reuse_address = True
|
||||
|
||||
# Override SocketServer.TCPServer to add extra parameters
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
self.address_family = socket.AF_INET6 if DNSChef().ipv6 else socket.AF_INET
|
||||
|
||||
SocketServer.TCPServer.__init__(self,server_address,RequestHandlerClass)
|
Loading…
Add table
Add a link
Reference in a new issue