Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed if/else blocks and 4 segment limit. #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
117 changes: 54 additions & 63 deletions dns2snort.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,33 @@

#Infile, outfile, and sid arguments via ArgParse are all required.

parser.add_argument('-i', dest="infile", required=True,
help="The name of the file containing a list of Domains, One domain per line.")
parser.add_argument('-o', dest="outfile", required=True, help="The name of the file to output your snort rules to.")
parser.add_argument('-s', dest="sid", type=int, required=True,
help="The snort sid to start numbering incrementally at. This number should be between 1000000 and 2000000.")
parser.add_argument('-w', dest="www", required=False, action='store_true', help="Remove the 'www' subdomain from domains that have it.")
parser.add_argument(
'-i',
dest="infile",
required=True,
help=
"The name of the file containing a list of Domains, One domain per line.")

parser.add_argument('-o',
dest="outfile",
required=True,
help="The name of the file to output your snort rules to.")

parser.add_argument(
'-s',
dest="sid",
type=int,
required=True,
help=
"The snort sid to start numbering incrementally at. This number should be between 1000000 and 2000000.")

parser.add_argument(
'-w',
dest="www",
required=False,
action='store_true',
help="Remove the 'www' subdomain from domains that have it.")

args = parser.parse_args()

#This is a small check to ensure -s is set to a valid value between one and two million - the local rules range.
Expand All @@ -50,7 +71,7 @@
#fout is the file we will be outputting our rules to.
#f is the file we will read a list of domains from.
#This script iterates through each line (via for line loop) and splits on periods (.), creating a list for each line.
#The script calculates the segments of the domain in question (can handle 1-4 segments -- e.g. .ru (1 segments, TLD) all the way to this.is.evil.ru (4 segments))
#The script calculates the segments of the domain in question.
#Each segment of a domain has it's string length calculated and converted to hex.
#If the segment is less than or equal to 0xf, this is converted to "0f" (padded with a zero, since snort rules expect this)
#The hexidecmal letter is converted to upper case, and the rule is written to a file.
Expand All @@ -59,66 +80,36 @@
with open(args.outfile, 'w') as fout:
with open(args.infile, 'r') as f:
for line in f:

domain = line.rstrip()
if args.www == True:

if args.www == True:
domain = re.sub('^www\.', '', domain, flags=re.IGNORECASE)
segment = domain.split('.')
segments = domain.split('.')

#try/except fixes a bug with TLD rule creation where segment has 2 elements and element 0 is '' for some reason.

try:
segment.remove('')
segments.remove('')
except ValueError:
pass
if len(segment) == 1:
sega = (hex(len(segment[0])))[2:]
if int(len(sega)) == 1:
sega = "0%s" % sega
rule = ("alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:\"BLACKLIST DNS domain %s\"; flow:to_server; byte_test:1,!&,0xF8,2; content:\"|%s|%s|00|\"; fast_pattern:only; metadata:service dns; sid:%s; rev:1;)\n" % (domain, sega.upper(), segment[0], args.sid))
fout.write(rule)
print rule
args.sid += 1
elif len(segment) == 2:
sega = (hex(len(segment[0])))[2:]
if int(len(sega)) == 1:
sega = "0%s" % sega
segb = (hex(len(segment[1])))[2:]
if int(len(segb)) == 1:
segb = "0%s" % segb
rule = ("alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:\"BLACKLIST DNS domain %s\"; flow:to_server; byte_test:1,!&,0xF8,2; content:\"|%s|%s|%s|%s|00|\"; fast_pattern:only; metadata:service dns; sid:%s; rev:1;)\n" % (domain, sega.upper(), segment[0], segb.upper(), segment[1], args.sid))
fout.write(rule)
print rule
args.sid += 1
elif len(segment) == 3:
sega = (hex(len(segment[0])))[2:]
if int(len(sega)) == 1:
sega = "0%s" % sega
segb = (hex(len(segment[1])))[2:]
if int(len(segb)) == 1:
segb = "0%s" % segb
segc = (hex(len(segment[2])))[2:]
if int(len(segc)) == 1:
segc = "0%s" % segc
rule = ("alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:\"BLACKLIST DNS domain %s\"; flow:to_server; byte_test:1,!&,0xF8,2; content:\"|%s|%s|%s|%s|%s|%s|00|\"; fast_pattern:only; metadata:service dns; sid:%s; rev:1;)\n" % (domain, sega.upper(), segment[0], segb.upper(), segment[1], segc.upper(), segment[2], args.sid))
fout.write(rule)
print rule
args.sid += 1
elif len(segment) == 4:
sega = (hex(len(segment[0])))[2:]
if int(len(sega)) == 1:
sega = "0%s" % sega
segb = (hex(len(segment[1])))[2:]
if int(len(segb)) == 1:
segb = "0%s" % segb
segc = (hex(len(segment[2])))[2:]
if int(len(segc)) == 1:
segc = "0%s" % segc
segd = (hex(len(segment[3])))[2:]
if int(len(segd)) == 1:
segd = "0%s" % segd
rule = ("alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:\"BLACKLIST DNS domain %s\"; flow:to_server; byte_test:1,!&,0xF8,2; content:\"|%s|%s|%s|%s|%s|%s|%s|%s|00|\"; fast_pattern:only; metadata:service dns; sid:%s; rev:1;)\n" % (domain, sega.upper(), segment[0], segb.upper(), segment[1], segc.upper(), segment[2], segd.upper(), segment[3], args.sid))
print rule
fout.write(rule)
args.sid += 1
else:
print "the number of segments in the domain %s is greater than 4. Skipping." % domain
pass

i = 0
rulefragment = ""

for segment in segments:
seghex = (hex(len(segments[i])))[2:]
if int(len(seghex)) == 1:
seghex = "0%s" % seghex
rulefragment += seghex.upper() + "|" + segment + "|"
i += 1

rule = (
"alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:\"BLACKLIST DNS domain %s\"; flow:to_server; byte_test:1,!&,0xF8,2; content:\"|%s00|\"; fast_pattern:only; metadata:service dns; sid:%s; rev:1;)\n"
% (domain, rulefragment, args.sid))

fout.write(rule)
print rule

args.sid += 1
exit()
8 changes: 3 additions & 5 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ Options (INFILE, OUTFILE, and SID arguments are all required!):
-w : Remove the 'www' subdomain from domains that have it.
-h : prints out the most badass help message you've ever seen.

This script supports TLDS, and FQDNS up to four subdomains deep - see the example below, in addition to the sampledns.txt and sample.rules files provided with this script.

Example input:

.pw
Expand All @@ -33,7 +31,7 @@ alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:"BLACKLIST DNS known malware do

These are all properly formatted snort rules ready to be pushed to a sensor.

Notes:
Notes:
- Please note that none of these sample domains are malicious. They are samples for testing only.
- The domain list input file should not have ANY trailing spaces on any of the individual user-agent lines. Additionally, there should be ZERO blank lines in the domain list file that will be used to generate the snort rules. If the script encounters any FQDNs greater than 4 subdomains deep (such as "lol.wut.you.doin.it [5-level subdomain]") the script will skip over them, notify the user, and print it to the terminal.
- If you think this is a concern, the Mandiant APT1 report has over 2000 domains as IOCs associated with the campaign. dns2snort only encountered two FQDNs with 5+ subdomains; dns2snort successfully created rules for every other domain in the list of IOCs. Maybe you can look over my code and implement a way to support FQDNs of varying length? I'm not good enough at python to do this currently without a huge CF of if/then statements. Help is welcome.
- The domain list input file should not have ANY trailing spaces on any of the individual user-agent lines. Additionally, there should be ZERO blank lines in the domain list file that will be used to generate the snort rules.
- If you think this is a concern, the Mandiant APT1 report has over 2000 domains as IOCs associated with the campaign. dns2snort only encountered two FQDNs with 5+ subdomains; dns2snort successfully created rules for every other domain in the list of IOCs. Maybe you can look over my code and implement a way to support FQDNs of varying length? I'm not good enough at python to do this currently without a huge CF of if/then statements. Help is welcome.