#! /usr/bin/env python import sys,string import getopt import dns.resolver import DNS #print help message def printUsage(): print "Fast-flux Analyser" print "" print "Usage: ffanalyse address [options]" print "" print "Valid options:" print " -h, --help : display this help" print " -v, --verbose : verbose mode, print everything" print " -a, --adr : address to check" print "" verbose = False defaults= { 'protocol':'udp', 'port':53, 'opcode':'query', 'qtype':'A', 'rd':1, 'timing':1, 'timeout': 30, 'server_rotate': 0 } defaults['server']=[] #get commandline options and set env values def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'hva', ['help','adr=']) except getopt.error, msg: print msg print 'for help use --help or -h' sys.exit(2) global verbose if len(sys.argv) > 2: # process options for o, a in opts: if o in ('-h', '--help'): printUsage() return if o in ('-v','--verbose'): verbose = True if o in ('-a','--adr'): adr = a elif len(sys.argv) == 2: adr = sys.argv[1] else: print 'error, please supply a domain to check' sys.exit(2) resolve_nameservers() get_dns(adr) def resolve_nameservers(): import sys global nameserver #check if windows system if sys.platform in ('win32', 'nt'): import win32dns defaults['server']=win32dns.RegistryResolve() else: ParseResolvConf() #get nameservers for UNIX type systems def ParseResolvConf(resolv_path="/etc/resolv.conf"): "parses the /etc/resolv.conf file and sets defaults for name servers" global defaults lines=open(resolv_path).readlines() for line in lines: line = string.strip(line) if not line or line[0]==';' or line[0]=='#': continue fields=string.split(line) if len(fields) < 2: continue if fields[0]=='domain' and len(fields) > 1: defaults['domain']=fields[1] if fields[0]=='search': pass if fields[0]=='options': pass if fields[0]=='sortlist': pass if fields[0]=='nameserver': defaults['server'].append(fields[1]) def get_dns(qname): global verbose, defaults rdtype=dns.rdatatype.A rdclass=dns.rdataclass.IN request = dns.message.make_query(qname, rdtype, rdclass) response = dns.query.udp(request,defaults['server'][0]) if verbose: print 'QUERY RESPONSE:\n%s'%str(response) analyse(response) def analyse(response): qname = str(response.question).split()[1] network_ranges = [] nameserver_net_ranges = [] a_count = 0 ns_count = 2 #fix this diff_count = 0 ns_diff_count = 0 a_ttl = 86400 ns_ttl = 86400 ar_count = -1 country_count = 1 countries = '' #check answers if response.rcode != 'NONE': answers = [] for rdata in response.answer: ans = str(rdata).split('\n') for a in ans: answers.append(a) for ans in answers: rdata = ans.split() if rdata[3] == 'A': #check if A-record a_count += 1 if '.' in rdata[4] and str.isdigit(rdata[4][0:2]): network_ranges.append(rdata[4]) if int(rdata[1]) < a_ttl: a_ttl = int(rdata[1]) #check for multiple ip ranges if len(network_ranges) > 0: first_range = network_ranges[0] for ip in network_ranges: st = ip[0:ip.rfind('.')] i = st.rfind('.') #print st for c in range(ip.count('.')-2): st = st[0:i] i = st.rfind('.') #print st if st not in first_range: diff_count += 1 asn = '' asn_count = 1 if a_count>=2 and ns_count>1 and diff_count>0: for ip in network_ranges: st = get_asn(ip) st = st.split(' | ') asn_t = st[0] country = st[2] if asn == '': asn = asn_t elif asn_t != asn: asn_count += 1 if countries == '': countries = country elif country != countries: country_count += 1 ttl_score = 0 #seems to be a trend with fast-flux domains if ns_ttl == a_ttl: ttl_score = 1 if a_ttl <= 300: ttl_score = 1 #calculate score according to Thorsten #=================================================== t_score = (1.32*a_count+18.54*asn_count+0*ns_count+ttl_score*5)-50 #=================================================== #print asn_count,a_count,ns_count,diff_count,ttl_score #calculate Jaroslaw/Patrycja score #=================================================== jp_score = a_count*1+ns_count*1+diff_count*1.5+asn_count*1.5+ttl_score*1+country_count*2 #=================================================== #print qname,jp_score,t_score #Combine to get Fast-Flux score #=================================================== match_count = 0 #print a_count,ns_count,diff_count,asn_count,a_ttl #print ar_count,pkt.qd.qname if t_score > 0: match_count = 1 #print 'Fast-Flux according to Thorsten: %i -> %s'%(t_score,pkt.qd.qname) if jp_score >= 18 and (a_count < 6 and diff_count <2): match_count = match_count+1 #print 'Fast-Flux according to Jaroslow/Patrycja: %i -> %s'%(jp_score,pkt.qd.qname) if diff_count!=0 and a_count>=2 and ns_count>1 and ((diff_count>=1 and asn_count>1)or ttl_score == 1) : match_count = match_count+1 #print 'Fast-Flux according to Metrics: %s'%(pkt.qd.qname) #check Whitelist? #print match_count #match_count += doNaiveBayes(a_count,ns_count,diff_count,asn_count,a_ttl,pkt.qd.qname,ns_diff_count) if match_count >= 2: print '\n%s :: Fast-Flux score: %d'%(qname,match_count) #match_count += doNaiveBayes(a_count,ns_count,diff_count,asn_count,a_ttl,pkt.qd.qname,ns_diff_count) if match_count >= 3: print '\n%s :: Fast-Flux score: %d'%(qname,match_count) #out.write('%i,%i,%i,%i,%i\n'%(a_count,ns_count,diff_count,asn_count,a_ttl)) elif match_count == 1: #match_count += doNaiveBayes(a_count,ns_count,diff_count,asn_count,a_ttl,pkt.qd.qname,ns_diff_count) if match_count == 2: print '\n%s :: Suspicious: Fast-Flux score: %d'%(qname,match_count) def get_asn(ip): global verbose #reverse ip address and query via team cymru if ip[0:ip.find('.')].isdigit(): start_i = 0 end_i = 0 rev = '' for i in range(ip.count('.')): end_i = ip.find('.',start_i) st = ip[start_i:end_i] start_i = end_i+1 rev = st+'.'+rev rev = ip[start_i:len(ip)]+'.'+rev #query team cymru to get ASN answers = dns.resolver.query(rev+'origin.asn.cymru.com','TXT') rdata = str(answers[0]) if verbose: print rdata #st = rdata[0:rdata.find('|')] return rdata #return the ASN value def futurework(): """FUTURE #check nameserver details if len(response.authority) != 0: for ns in response.authority: if ns[1] < ns_ttl: ns_ttl = ns[1] #FUTURE do check to see if nameserver is in list of malicious nameservers #check additional section (nameserver checks) if len(response.additional) != 0: ar_ = pkt.ar[0].rdata for ar in range(pkt.arcount): nameserver_net_ranges.append(pkt.ar[ar].rdata) if ar == pkt.ar[ar].rdata: ar_count += 1 #check for multiple ip ranges if len(nameserver_net_ranges) > 0: first_range = nameserver_net_ranges[0] for ip in nameserver_net_ranges: st = ip[0:ip.rfind('.')] i = st.rfind('.') for c in range(ip.count('.')-1): st = st[0:i] i = st.rfind('.') if st not in first_range: ns_diff_count += 1 """ pass if __name__ == "__main__": main()