Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 47 additions & 21 deletions fakedns.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
#!/usr/bin/env python
"""Fakedns.py: A regular-expression based DNS MITM Server by Crypt0s."""

import pdb
import socket
import re
import sys
import os
import SocketServer
import signal
import argparse
from datetime import datetime


# Use of ANSI escape sequences to output colored text
class bcolors:
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'

file_log = open ("dns_origin.log", "w")


# inspired from DNSChef
class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
Expand All @@ -34,9 +45,11 @@ def __init__(self, data):
lon = ord(data[ini])
while lon != 0:
self.domain += data[ini + 1:ini + lon + 1] + '.'
self.origin = self.domain
self.domain = self.domain.lower()
ini += lon + 1 # you can implement CNAME and PTR
lon = ord(data[ini])
self.type = data[ini:][1:3]
self.type = data[ini:][1:3]
else:
self.type = data[-4:-2]

Expand All @@ -49,9 +62,10 @@ def __init__(self, data):
"\x00\x0c": "PTR",
"\x00\x10": "TXT",
"\x00\x0f": "MX",
"\x00\x06":"SOA"
"\x00\x06": "SOA"
}


# Stolen:
# https://github.com/learningequality/ka-lite/blob/master/python-packages/django/utils/ipv6.py#L209
def _is_shorthand_ip(ip_str):
Expand All @@ -67,6 +81,7 @@ def _is_shorthand_ip(ip_str):
return True
return False


# Stolen:
# https://github.com/learningequality/ka-lite/blob/master/python-packages/django/utils/ipv6.py#L209
def _explode_shorthand_ip_string(ip_str):
Expand Down Expand Up @@ -160,6 +175,7 @@ def make_packet(self):
except (TypeError, ValueError):
pass


# All classes need to set type, length, and data fields of the DNS Response
# Finished
class A(DNSResponse):
Expand All @@ -175,6 +191,7 @@ def get_ip(dns_record):
# Convert to hex
return ''.join(chr(int(x)) for x in ip.split('.'))


# Implemented
class AAAA(DNSResponse):
def __init__(self, query, address):
Expand All @@ -193,12 +210,14 @@ def get_ip_6(host, port=0):
# just returns the first answer and only the address
ip = result[0][4][0]


# Not yet implemented
class CNAME(DNSResponse):
def __init__(self, query):
super(CNAME, self).__init__(query)
self.type = "\x00\x05"


# Implemented
class PTR(DNSResponse):
def __init__(self, query, ptr_entry):
Expand All @@ -214,6 +233,7 @@ def __init__(self, query, ptr_entry):
if self.length < '\xff':
self.length = "\x00" + self.length


# Finished
class TXT(DNSResponse):
def __init__(self, query, txt_record):
Expand All @@ -237,6 +257,7 @@ def __init__(self, query, txt_record):
"\x00\x10": TXT
}


# Technically this is a subclass of A
class NONEFOUND(DNSResponse):
def __init__(self, query):
Expand All @@ -246,7 +267,7 @@ def __init__(self, query):
self.rranswers = "\x00\x00"
self.length = "\x00\x00"
self.data = "\x00"
print ">> Built NONEFOUND response"
print bcolors.WARNING + "[*]" + bcolors.ENDC + " Built NONEFOUND response"


class Rule (object):
Expand Down Expand Up @@ -322,17 +343,20 @@ def match(self, req_type, domain, addr):
# Error classes for handling rule issues
class RuleError_BadRegularExpression(Exception):
def __init__(self,lineno):
print "\n!! Malformed Regular Expression on rulefile line #%d\n\n" % lineno
print "\n" + bcolors.FAIL + "[x]" + bcolors.ENDC + " Malformed Regular Expression on rulefile line #%d\n\n" \
% lineno


class RuleError_BadRuleType(Exception):
def __init__(self,lineno):
print "\n!! Rule type unsupported on rulefile line #%d\n\n" % lineno
print "\n" + bcolors.FAIL + "[x]" + bcolors.ENDC + " Rule type unsupported on rulefile line #%d\n\n" \
% lineno


class RuleError_BadFormat(Exception):
def __init__(self,lineno):
print "\n!! Not Enough Parameters for rule on rulefile line #%d\n\n" % lineno
print "\n" + bcolors.FAIL + "[x]" + bcolors.ENDC + " Not Enough Parameters for rule on rulefile line #%d\n\n" \
% lineno


class RuleEngine2:
Expand All @@ -345,13 +369,12 @@ def _replace_self(self, ips):
try:
self_ip = socket.gethostbyname(socket.gethostname())
except socket.error:
print ">> Could not get your IP address from your " \
print bcolors.FAIL + "[x]" + bcolors.ENDC + " Could not get your IP address from your " \
"DNS Server."
self_ip = '127.0.0.1'
ips[ips.index(ip)] = self_ip
return ips


def __init__(self, file_):
"""
Parses the DNS Rulefile, validates the rules, replaces keywords
Expand Down Expand Up @@ -425,16 +448,13 @@ def __init__(self, file_):
ip = ip.replace(":", "").decode('hex')
tmp_ip_array.append(ip)
ips = tmp_ip_array


# add the validated and parsed rule into our list of rules
self.rule_list.append(Rule(rule_type, domain, ips, rebinds, rebind_threshold))

# increment the line number
lineno += 1

print ">> Parsed %d rules from %s" % (len(self.rule_list),file_)

print bcolors.OKGREEN + "[*]" + bcolors.ENDC + " Parsed %d rules from %s" % (len(self.rule_list),file_)

def match(self, query, addr):
"""
Expand All @@ -456,29 +476,33 @@ def match(self, query, addr):

response = CASE[query.type](query, response_data)

print ">> Matched Request - " + query.domain
print "( "+str(datetime.now())+ ") " +bcolors.OKGREEN + "[+]" + bcolors.ENDC + " Matched Request - " + \
query.domain + " original query " + bcolors.WARNING + query.origin + bcolors.ENDC
file_log.write(query.origin+"\n")
return response.make_packet()

# if we got here, we didn't match.
# Forward a request that we didnt have a rule for to someone else

# if the user said not to forward requests, and we are here, it's time to send a NONEFOUND
if args.noforward:
print ">> Don't Forward %s" % query.domain
print bcolors.WARNING + "[*]" + bcolors.ENDC + " Don't Forward %s" % query.domain
return NONEFOUND(query).make_packet()
try:
s = socket.socket(type=socket.SOCK_DGRAM)
s.settimeout(3.0)
addr = ('%s' % (args.dns), 53)
addr = ('%s' % args.dns, 53)
s.sendto(query.data, addr)
data = s.recv(1024)
s.close()
print "Unmatched Request " + query.domain
print "( "+str(datetime.now())+ ") " + bcolors.FAIL + "[x]" + bcolors.ENDC + " Unmatched Request " + \
query.domain + " Original query " + bcolors.FAIL + query.origin + bcolors.ENDC
file_log.write(query.origin+"\n")
return data
except socket.error, e:
# We shouldn't wind up here but if we do, don't drop the request
# send the client *something*
print ">> Error was handled by sending NONEFOUND"
print bcolors.FAIL + "[x]" + bcolors.ENDC + " Error was handled by sending NONEFOUND"
print e
return NONEFOUND(query).make_packet()

Expand All @@ -490,6 +514,7 @@ def respond(data, addr, s):
s.sendto(response, addr)
return response


# Capture Control-C and handle here
def signal_handler(signal, frame):
print 'Exiting...'
Expand Down Expand Up @@ -526,7 +551,7 @@ def signal_handler(signal, frame):
# Default config file path.
path = args.path
if not os.path.isfile(path):
print '>> Please create a "dns.conf" file or specify a config path: ' \
print bcolors.WARNING + "[*]" + bcolors.ENDC + ' Please create a "dns.conf" file or specify a config path: ' \
'./fakedns.py [configfile]'
exit()

Expand All @@ -539,12 +564,13 @@ def signal_handler(signal, frame):
try:
server = ThreadedUDPServer((interface, int(port)), UDPHandler)
except socket.error:
print ">> Could not start server -- is another program on udp:{0}?".format(port)
print bcolors.FAIL + "[x]" + bcolors.ENDC + " Could not start server -- is another program on " \
"udp:{0}?".format(port)
exit(1)

server.daemon = True

# Tell python what happens if someone presses ctrl-C
signal.signal(signal.SIGINT, signal_handler)
server.serve_forever()
server_thread.join()
server_thread.join()