Skip to content

Commit

Permalink
Major update with modularity support
Browse files Browse the repository at this point in the history
- added support for standalone feature modules in the form of `.toml` files
- moved the majority of existing features to a modular format
- major cleanup of the polardns codebase
- major review of the variables scope and naming conventions in the code
- adopting a more object-oriented approach for DNS request and DNS response variables
  • Loading branch information
ivan-jedek committed May 8, 2024
1 parent 5e880c8 commit 91254d0
Show file tree
Hide file tree
Showing 76 changed files with 2,878 additions and 0 deletions.
35 changes: 35 additions & 0 deletions modules/afuzz1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
def main(labels, *args, **kwargs):
"""
name = "afuzz1"
info = "Single A record with arbitrary byte"
desc = "Respond with A record containing arbitrary byte in the middle of the name in the ANSWER section, essentially giving an incorrect answer."
type = "feature"
author = "[email protected]"
"""
if labels[0].startswith("afuzz1"):
# todo: Send A record with a slightly distorted name.
byte = 65
if req.subdomains[1].isnumeric():
byte = int(req.subdomains[1])
if byte > 255: byte = 255
### DNS header #######
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
newip = "6.6.6." + str(byte)
tmpdom = convDom2Bin(req.full_domain) # first convert to dns name notation
newdom = tmpdom[0:3] # \
newdom += struct.pack(">B", byte) # > replace the 3rd char with chosen byte
newdom += tmpdom[4:] # /
# A
buffer += newdom + getTypeBin("A") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
buffer += struct.pack(">H", 4) ## Data length
buffer += socket.inet_aton(newip) ## IP
# log and send
strdom = req.full_domain[0:2]
strdom += "\\x%0.2x" % byte
strdom += req.full_domain[3:]
log("A %s -> %s" % (strdom, newip))
send_buf(self, buffer)
36 changes: 36 additions & 0 deletions modules/afuzz1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[module]
name = "afuzz1"
info = "Single A record with arbitrary byte"
desc = "Respond with A record containing arbitrary byte in the middle of the name in the ANSWER section, essentially giving an incorrect answer."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("afuzz1"):
# todo: Send A record with a slightly distorted name.
byte = 65
if req.subdomains[1].isnumeric():
byte = int(req.subdomains[1])
if byte > 255: byte = 255
### DNS header #######
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
newip = "6.6.6." + str(byte)
tmpdom = convDom2Bin(req.full_domain) # first convert to dns name notation
newdom = tmpdom[0:3] # \
newdom += struct.pack(">B", byte) # > replace the 3rd char with chosen byte
newdom += tmpdom[4:] # /
# A
buffer += newdom + getTypeBin("A") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
buffer += struct.pack(">H", 4) ## Data length
buffer += socket.inet_aton(newip) ## IP
# log and send
strdom = req.full_domain[0:2]
strdom += "\\x%0.2x" % byte
strdom += req.full_domain[3:]
log("A %s -> %s" % (strdom, newip))
send_buf(self, buffer)
'''
48 changes: 48 additions & 0 deletions modules/afuzz2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[module]
name = "afuzz2"
info = "Many bogus A records and legit A record"
desc = "Respond with many bogus A records containing byte values starting from 0 up to 255 max, followed by a legitimate answer (proper A record) in the end."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("afuzz2"):
# Send many A records with a slightly distorted name. In the end, provide the correct one also
# af<01>zz2.dnslabtest1.com A 6.6.6.1
# af<02>zz2.dnslabtest1.com A 6.6.6.2
# af<03>zz2.dnslabtest1.com A 6.6.6.3
# ...
# af<fe>zz2.dnslabtest1.com A 6.6.6.254
# af<ff>zz2.dnslabtest1.com A 6.6.6.255
# afuzz2.dnslabtest1.com A 1.2.3.4
answers = 1
if req.subdomains[1].isnumeric():
answers = int(req.subdomains[1])
if answers > 256: answers = 256
### DNS header #######
buffer = prep_dns_header(b'\x84\x00', req.QURR, answers+1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
#for b in range(1, 255):
tmpdom = convDom2Bin(req.full_domain)
# multiple bad A
for b in range(1, answers+1):
newip = "6.6.6." + str(b-1)
newdom = tmpdom[0:3]
newdom += struct.pack(">B", b-1)
newdom += tmpdom[4:]
buffer += newdom + getTypeBin("A") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
buffer += struct.pack(">H", 4) ## Data length
buffer += socket.inet_aton(newip) ## IP
# good A
buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
buffer += struct.pack(">L", resp.TTL) ## TTL
buffer += struct.pack(">H", 4) ## Data length
buffer += socket.inet_aton("2.3.4.5") ## IP
# log and send
log("%d bogus A records + legit A record" % (answers))
send_buf(self, buffer)
#####################################################################
'''
30 changes: 30 additions & 0 deletions modules/badcompress1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[module]
name = "badcompress1"
info = "Custom offset in Name field"
desc = "Respond with CNAME (always&lt;RANDOM>.yourdomain.com), where you can specify an arbitrary offset in the answer Name field compression pointer."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompress1"):
# Send answer with arbitrary compression pointer in the ANSWER section in the query name
offset = 12 # default offset is 12, which points to the domain name in the question
if req.subdomains[1].isnumeric():
offset = int(req.subdomains[1])
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
badcomp = b"\xc0" + struct.pack(">B", offset) ## arbitrary offset in the answer in the Name
buffer += badcomp + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = "always" + str(random.randint(1,100000)) + "." + req.sld_tld_domain
buffer += struct.pack(">H", len(dom)+2) ## Data length
buffer += convDom2Bin(dom) ## CNAME value
# log and send
log("CNAME %s (badcomp 1, answer Name, offset %d)" % (dom, offset))
send_buf(self, buffer)
#####################################################################
'''
29 changes: 29 additions & 0 deletions modules/badcompress2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[module]
name = "badcompress2"
info = "Custom offset in CNAME field"
desc = "Respond with CNAME (abc.badcompress2.yourdomain.com), where you can specify an arbitrary offset in the answer CNAME field compression pointer."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompress2"):
# Send answer with arbitrary compression pointer in the ANSWER section in the CNAME name
offset = 12 # default offset is 12, which points to the domain name in the question
if req.subdomains[1].isnumeric():
offset = int(req.subdomains[1])
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
buffer += b"\xc0\x0c" + getTypeBin("CNAME") + getClassBin("IN") ## using compression here, no problem
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = b"\x03abc" + b"\xc0" + struct.pack(">B", offset) ## arbitrary offset in the answer in the CNAME
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value
# log and send
log("CNAME abc.%s (badcomp 2, CNAME, offset %d)" % (req.full_domain, offset))
send_buf(self, buffer)
#####################################################################
'''
27 changes: 27 additions & 0 deletions modules/badcompressfwptr1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[module]
name = "badcompressfwptr1"
info = "Forward pointer in Name field"
desc = "Respond with CNAME (abc.badcompressfwptr1.yourdomain.com) and use a forward pointer in the answer Name field to point to the end (the actual CNAME), where there is \"abc\" + a pointer to the beginning (to the domain name in the query)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressfwptr1"):
# Send answer with a forward compression pointer pointing to another pointer - variant 1
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
offset_to_last_cname = len(req.full_domain) + 12 + 18 ## forward pointer to the CNAME in the end
buffer += b"\xc0" + struct.pack(">B", offset_to_last_cname) + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = b"\x03abc" + b"\xc0\x0c" ## "abc" + pointer to the first domain name (in the query)
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value
# log and send
log("CNAME abc.%s (badcomp with forward pointer 1)" % (req.full_domain))
send_buf(self, buffer)
#####################################################################
'''
28 changes: 28 additions & 0 deletions modules/badcompressfwptr2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[module]
name = "badcompressfwptr2"
info = "Double compression pointer"
desc = "Respond with CNAME (abc.badcompressfwptr2.yourdomain.com) and use a forward pointer in the answer Name field to point to the end (the actual CNAME). But, skip the \"abc\" portion so that it will point directly to another pointer pointing to the beginning (to the domain name in the query)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressfwptr2"):
# Send answer with a forward compression pointer pointing to another pointer - variant 2
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
offset_to_last_cname = len(req.full_domain) + 12 + 18 + 4 ## forward pointer to the CNAME in the end, but also
## skipping the "abc" portion, so it's like a small chain
buffer += b"\xc0" + struct.pack(">B", offset_to_last_cname) + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = b"\x03abc" + b"\xc0\x0c" ## "abc" + pointer to the first domain name (in the query)
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value
# log and send
log("CNAME abc.%s (badcomp with forward pointer 2)" % (req.full_domain))
send_buf(self, buffer)
#####################################################################
'''
28 changes: 28 additions & 0 deletions modules/badcompressloop1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[module]
name = "badcompressloop1"
info = "Pointer loop in Name field 1"
desc = "Respond with CNAME, where the answer Name field only contains a pointer to itself (=> a loop)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressloop1"):
# Send answer with a compression pointer loop in the Answer name - variant 1
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
offset_to_middle_name = len(req.full_domain) + 18 ## forward pointer to the name in the middle
baddom = b"\xc0" + struct.pack(">B", offset_to_middle_name)
buffer += baddom + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = "always" + str(random.randint(1,100000)) + "." + req.sld_tld_domain
buffer += struct.pack(">H", len(dom)+2) ## Data length
buffer += convDom2Bin(dom) ## CNAME value
# log and send
log("CNAME %s (badcomp loop 1, answer Name, <LOOP>)" % (dom))
send_buf(self, buffer)
#####################################################################
'''
28 changes: 28 additions & 0 deletions modules/badcompressloop2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[module]
name = "badcompressloop2"
info = "Pointer loop in Name field 2"
desc = "Respond with CNAME, where the answer Name field only contains \"abc\" and a pointer to the beginning of the \"abc\" (=> a loop)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressloop2"):
# Send answer with a compression pointer loop in the Answer name - variant 2
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
offset_to_middle_name = len(req.full_domain) + 18 ## forward pointer to the name in the middle
baddom = b"\x03abc" + b"\xc0" + struct.pack(">B", offset_to_middle_name)
buffer += baddom + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
dom = "always" + str(random.randint(1,100000)) + "." + req.sld_tld_domain
buffer += struct.pack(">H", len(dom)+2) ## Data length
buffer += convDom2Bin(dom) ## CNAME value
# log and send
log("CNAME %s (badcomp loop 2, answer Name, abc<LOOP>)" % (dom))
send_buf(self, buffer)
#####################################################################
'''
28 changes: 28 additions & 0 deletions modules/badcompressloop3.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[module]
name = "badcompressloop3"
info = "Double pointer loop"
desc = "Respond with CNAME, use a forward pointer in the answer Name field pointing to the end (the actual CNAME). The actual CNAME points to the answer Name field, effectively creating a loop."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressloop3"):
# Send answer with a compression pointer loop involving a forward pointer and a backward pointer
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
offset_to_last_cname = len(req.full_domain) + 12 + 18 ## forward pointer to the CNAME in the end
buffer += b"\xc0" + struct.pack(">B", offset_to_last_cname) + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
offset_to_middle_name = len(req.full_domain) + 18 ## backward pointer to the name in the middle
dom = b"\x03abc" + b"\xc0" + struct.pack(">B", offset_to_middle_name)
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value
# log and send
log("CNAME abc.<LOOP> (badcomp loop 3 in the answer Name and CNAME)")
send_buf(self, buffer)
#####################################################################
'''
27 changes: 27 additions & 0 deletions modules/badcompressloop4.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[module]
name = "badcompressloop4"
info = "Pointer loop in CNAME field 1"
desc = "Respond with CNAME, where the CNAME only contains a pointer to itself (=> a loop)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressloop4"):
# Send answer with a compression pointer loop in the CNAME - variant 1
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
buffer += b"\xc0\x0c" + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
offset_to_last_cname = len(req.full_domain) + 12 + 18 ## forward pointer to the CNAME in the end
dom = b"\xc0" + struct.pack(">B", offset_to_last_cname)
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value with pointer to itself
# log and send
log("CNAME <LOOP> (badcomp loop 4 in CNAME)")
send_buf(self, buffer)
#####################################################################
'''
27 changes: 27 additions & 0 deletions modules/badcompressloop5.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[module]
name = "badcompressloop5"
info = "Pointer loop in CNAME field 2"
desc = "Respond with CNAME, where the CNAME only contains \"abc\" + a pointer to the beginning of the \"abc\" (=> a loop)."
type = "feature"
author = "[email protected]"

code = '''
if req.first_subdomain.startswith("badcompressloop5"):
# Send answer with a compression pointer loop in the CNAME - variant 2
### DNS header ########
buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0)
### QUESTION SECTION ########
if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin
### ANSWER SECTION ########
# CNAME
buffer += b"\xc0\x0c" + getTypeBin("CNAME") + getClassBin("IN")
buffer += struct.pack(">L", resp.TTL) ## TTL
offset_to_last_cname = len(req.full_domain) + 12 + 18 ## forward pointer to the CNAME in the end
dom = b"\x03abc" + b"\xc0" + struct.pack(">B", offset_to_last_cname)
buffer += struct.pack(">H", len(dom)) ## Data length
buffer += dom ## CNAME value with pointer to itself
# log and send
log("CNAME abc<LOOP> (badcomp loop 5 in CNAME)")
send_buf(self, buffer)
#####################################################################
'''
Loading

0 comments on commit 91254d0

Please sign in to comment.