From 7629bc28e5c4b770f720acae6de4af133248faa4 Mon Sep 17 00:00:00 2001 From: firefalc0n Date: Wed, 23 Aug 2017 13:41:35 +0100 Subject: [PATCH 1/3] Use of ANSI escape sequences to print colored text in the terminal. Helpful for reading huge outputs. --- fakedns.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/fakedns.py b/fakedns.py index b3aeafc..f49aea6 100755 --- a/fakedns.py +++ b/fakedns.py @@ -10,6 +10,13 @@ import signal import argparse +# Use of ANSI escape sequences to output colored text +class bcolors: + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + # inspired from DNSChef class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): def __init__(self, server_address, request_handler): @@ -246,7 +253,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): @@ -322,17 +329,17 @@ 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: @@ -345,7 +352,7 @@ 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 @@ -433,7 +440,7 @@ def __init__(self, file_): # 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): @@ -456,7 +463,7 @@ def match(self, query, addr): response = CASE[query.type](query, response_data) - print ">> Matched Request - " + query.domain + print bcolors.OKGREEN + "[+]" + bcolors.ENDC + " Matched Request - " + query.domain return response.make_packet() # if we got here, we didn't match. @@ -464,7 +471,7 @@ def match(self, query, addr): # 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) @@ -473,12 +480,12 @@ def match(self, query, addr): s.sendto(query.data, addr) data = s.recv(1024) s.close() - print "Unmatched Request " + query.domain + print bcolors.FAIL + "[x]" + bcolors.ENDC + " Unmatched Request " + query.domain 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() @@ -526,7 +533,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() @@ -539,7 +546,7 @@ 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 @@ -547,4 +554,4 @@ def signal_handler(signal, frame): # Tell python what happens if someone presses ctrl-C signal.signal(signal.SIGINT, signal_handler) server.serve_forever() - server_thread.join() + server_thread.join() \ No newline at end of file From 2430a56d17a82134a99de7c3b8cc0ae81a073a6f Mon Sep 17 00:00:00 2001 From: firefalc0n Date: Wed, 23 Aug 2017 18:06:50 +0100 Subject: [PATCH 2/3] Following some PEP rules. Adding time of request answer in output. Show original request for random upper & lower case requests. --- fakedns.py | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/fakedns.py b/fakedns.py index f49aea6..70fffb0 100755 --- a/fakedns.py +++ b/fakedns.py @@ -1,7 +1,6 @@ #!/usr/bin/env python """Fakedns.py: A regular-expression based DNS MITM Server by Crypt0s.""" -import pdb import socket import re import sys @@ -9,6 +8,8 @@ import SocketServer import signal import argparse +from datetime import datetime + # Use of ANSI escape sequences to output colored text class bcolors: @@ -17,6 +18,7 @@ class bcolors: FAIL = '\033[91m' ENDC = '\033[0m' + # inspired from DNSChef class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): def __init__(self, server_address, request_handler): @@ -41,9 +43,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] @@ -56,9 +60,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): @@ -74,6 +79,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): @@ -167,6 +173,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): @@ -182,6 +189,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): @@ -200,12 +208,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): @@ -221,6 +231,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): @@ -244,6 +255,7 @@ def __init__(self, query, txt_record): "\x00\x10": TXT } + # Technically this is a subclass of A class NONEFOUND(DNSResponse): def __init__(self, query): @@ -329,17 +341,20 @@ def match(self, req_type, domain, addr): # Error classes for handling rule issues class RuleError_BadRegularExpression(Exception): def __init__(self,lineno): - print "\n" + bcolors.FAIL + "[x]" + bcolors.ENDC + " 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" + bcolors.FAIL + "[x]" + bcolors.ENDC + " 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" + bcolors.FAIL + "[x]" + bcolors.ENDC + " 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: @@ -358,7 +373,6 @@ def _replace_self(self, ips): ips[ips.index(ip)] = self_ip return ips - def __init__(self, file_): """ Parses the DNS Rulefile, validates the rules, replaces keywords @@ -432,8 +446,6 @@ 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)) @@ -442,7 +454,6 @@ def __init__(self, file_): print bcolors.OKGREEN + "[*]" + bcolors.ENDC + " Parsed %d rules from %s" % (len(self.rule_list),file_) - def match(self, query, addr): """ See if the request matches any rules in the rule list by calling the @@ -463,7 +474,8 @@ def match(self, query, addr): response = CASE[query.type](query, response_data) - print bcolors.OKGREEN + "[+]" + bcolors.ENDC + " Matched Request - " + query.domain + print "( "+str(datetime.now())+ ") " +bcolors.OKGREEN + "[+]" + bcolors.ENDC + " Matched Request - " + \ + query.domain + " original query " + bcolors.WARNING + query.origin + bcolors.ENDC return response.make_packet() # if we got here, we didn't match. @@ -476,11 +488,12 @@ def match(self, query, addr): 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 bcolors.FAIL + "[x]" + bcolors.ENDC + " 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 return data except socket.error, e: # We shouldn't wind up here but if we do, don't drop the request @@ -497,6 +510,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...' @@ -546,7 +560,8 @@ def signal_handler(signal, frame): try: server = ThreadedUDPServer((interface, int(port)), UDPHandler) except socket.error: - print bcolors.FAIL + "[x]" + bcolors.ENDC + " 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 From 7daa691c1c83fb08d00624f479575a4a1f3cc97f Mon Sep 17 00:00:00 2001 From: firefalc0n Date: Thu, 24 Aug 2017 15:33:58 +0100 Subject: [PATCH 3/3] Write dns query origin url in text file. --- fakedns.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fakedns.py b/fakedns.py index 70fffb0..8c13c35 100755 --- a/fakedns.py +++ b/fakedns.py @@ -18,6 +18,8 @@ class bcolors: FAIL = '\033[91m' ENDC = '\033[0m' +file_log = open ("dns_origin.log", "w") + # inspired from DNSChef class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): @@ -476,6 +478,7 @@ def match(self, query, addr): 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. @@ -494,6 +497,7 @@ def match(self, query, addr): s.close() 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