123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-"
- """
- This file is part of the UFONet project, https://ufonet.03c8.net
- Copyright (c) 2013/2020 | psy <epsylon@riseup.net>
- You should have received a copy of the GNU General Public License along
- with UFONet; if not, write to the Free Software Foundation, Inc., 51
- Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- """
- import socket, threading, logging, datetime, sys, os, re, time
- from . import zombie
- # zombie tracking class
- class Herd(object):
- # basic constructor
- def __init__(self,ufonet):
- super(Herd, self).__init__()
- self.ufonet=ufonet
- self.reset()
- self.total_connections=0
- self.total_hits=0
- self.total_fails=0
- self.total_time=0
- self.total_size=0
- self.total_connection_fails=0
- self.living=threading.active_count()
- self.stats={}
- self.zombies_ready = []
- # property setup
- def reset(self):
- self.active = []
- self.done = []
- self.lock = threading.Lock()
- self.result={}
- self.connection={}
- # clean temporary statistic files
- def cleanup(self):
- try:
- if os.path.exists("/tmp/ufonet.html"):
- os.remove("/tmp/ufonet.html")
- if os.path.exists("/tmp/ufonet.html.tmp"):
- os.remove("/tmp/ufonet.html.tmp")
- except:
- pass
- # got a new one!
- def new_zombie(self, zombie):
- self.total_connections+=1
- if zombie not in self.stats:
- self.stats[zombie]=[]
- with self.lock:
- self.active.append(zombie)
- # give me your report & byebye
- def kill_zombie(self, zombie, result, connection_failed):
- with self.lock:
- try:
- self.result[zombie]=str(result)
- self.connection[zombie]=connection_failed
- self.done.append(zombie)
- if result[0]==200 :
- self.total_hits+=1
- else:
- self.total_fails+=1
- if connection_failed:
- self.total_connection_fails+=1
- self.active.remove(zombie)
- self.total_time+=result[1]
- self.total_size+=result[2]
- if zombie in self.stats:
- self.stats[zombie].append(result)
- else:
- pass
- except:
- pass
- # head count (+/- headless zombies)
- # active thread count = 1 principal + 1/zombie
- def no_more_zombies(self):
- ac=threading.active_count()-1
- options = self.ufonet.options
- if options.verbose == True:
- if ac>self.living:
- if ac-self.living not in self.ufonet.ac_control:
- print("[Info] [AI] [Control] Number of Active [ARMY] returning from battle front: "+ str(ac-self.living))
- self.ufonet.ac_control.append(ac-self.living)
- with self.lock:
- return ac==self.living
- # retrieve result by zombie name
- def get_result(self,zombie):
- return self.result[zombie] or False
- # retrieve connection status by zombie name
- def connection_failed(self,zombie):
- return self.connection[zombie] or False
- # retrieve size on correct format
- def sizeof_fmt(self, size, suffix='B'):
- for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
- if abs(size) < 1024.0:
- return "%3.1f%s%s" % (size, unit, suffix)
- size /= 1024.0
- return "%.1f%s%s" % (size, 'Yi', suffix)
- # generate html statistics
- def dump_html(self,final=False):
- buf=""
- out=self.get_stat()
- if os.path.exists("/tmp/ufonet.html.tmp"):
- try:
- self.cleanup()
- except:
- print('[Info] Previous tmp file found... html content will not be updated.')
- pass
- buf += "<div>" + os.linesep
- if out['err'] is not None:
- buf += "<div>Errors : <br/>"+str(out['err'])+'</div>'+os.linesep
- buf += "<h2>Conn: " +str(self.total_connections)+" - Zombies: "+str(len(self.stats))+" -> Hits: "+str(self.total_hits)+" - Fails: "+str(self.total_fails)+"</h2>"+os.linesep
- buf += "<div id='zombie_list'><h3>Zombies: </h3>"
- if len(out['data'])==0:
- buf += "waiting..."
- for l in out["data"] :
- hits='<font color="green">'+str(out['data'][l]['hits'])+'</font>'
- fails=str(out['data'][l]['fails'])
- buf += "<button href='#' title='"+l+"' onclick=\"zombie_detail("+str(out['data'][l])+")\">"+fails+"/"+hits+"</button>"+os.linesep
- buf += "</div>"
- if out['max_hits'] > 0:
- buf += "<hr/>"+os.linesep
- buf += "<div>Zombie 0day: "+str( out['max_hitz'])+ " with "+str( out['max_hits'])+ " hits</div>"+os.linesep
- if out['max_fails'] > 0:
- buf += "<hr/>"+os.linesep
- buf += "<div>Worst zombie: "+str(out['max_failz'])+ " with "+str(out['max_fails'])+" fails</div>"+os.linesep
- buf += "<hr/>"+os.linesep
- try:
- buf += "<div>Total time:" +str(out['total_time'])+ " | Avg time:"+str(out['avg_time'])+"</div>"+os.linesep
- buf += "<div>Total size:"+str(out['total_size'])+" | Avg size:"+str(out['avg_size'])+"</div>"+os.linesep
- buf += "<hr/>"+os.linesep
- except:
- pass
- buf += "<div><h3>Troops: </h3></div>"+os.linesep
- buf += "<div>Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail)+"</div>" + os.linesep
- buf += "<div>Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail)+"</div>" + os.linesep
- buf += "<div>X-RPCs: " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail)+"</div>" + os.linesep
- buf += "<div>UCAVs: " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail)+"</div>" + os.linesep
- f = open("/tmp/ufonet.html.tmp", "w")
- f.write(buf)
- if(final):
- f.write("<script>hdone=true</script>")
- f.close()
- try:
- os.rename("/tmp/ufonet.html.tmp","/tmp/ufonet.html")
- except:
- pass
- # generate statistics for stdout
- def format(self, out):
- if len(out['data'])==0:
- print("[Info] Not any feedback data to show. Exiting...")
- return
- print('='*42)
- print("Herd statistics")
- print("="*42)
- for zo in out['data']:
- z=out['data'][zo]
- print('Zombie :', z['name'], " | ", z['hits'], " hits ", z['fails'] ," fails ", z['retries'], " retries ")
- print(" Times:", z['time'], " total ", z['min_time'], " min ", z['avg_time'] ," avg ", z['max_time'], " max ")
- print(" Sizes:", z['size'], " total ", z['min_size'], " min ", z['size'] ," avg ", z['max_size'], " max ")
- print("-"*21)
- if out['max_hits'] > 0:
- print("="*80)
- print("Zombie 0day: ", out['max_hitz'], " with ", out['max_hits'], " hits")
- if out['max_fails'] > 0:
- print("="*80)
- print("Worst zombie: ", out['max_failz'], " with ", out['max_fails'], " fails")
- print("="*80)
- print("Total invocations:", self.total_connections,"| Zombies:", str(self.ufonet.total_zombie),"| Hits:", self.total_hits,"| Fails:", self.total_fails)
- print("Total time:", out['total_time'], "| Avg time:", out['avg_time'])
- print("Total size:", out['total_size'],"| Avg size:", out['avg_size'])
- print("-"*21)
- print("="*42)
- print("Troops statistics")
- print("="*42)
- print("Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail))
- print("Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail))
- print("X-RPCs: " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail))
- print("UCAVs : " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail))
- print("-"*21)
- print("\n") # gui related
- print('='*21)
- # show what we have
- def get_stat(self):
- data={}
- out={'err':None,"header":"","data":{},"total":{},"footer":"",'max_fails':0,'max_failz':"",'max_hits':0,'max_hitz':""}
- if os.path.exists("html.tmp"):
- out['err']= "\n[Info] Previous tmp file found... html content will not be updated."
- return out
- if self.total_connections==0:
- out['err']= "\n[Error] No herd without zombies..."
- return out
- if len(self.stats)==0:
- out['err']= "\n[Error] No statistics available..."
- return out
- self.zero_fails = 0
- for zombie_stat in self.stats:
- zs=self.stats[zombie_stat]
- try:
- entry={'name':zombie_stat,"hits":0,"fails":0,"retries":0,"time":0,"max_time":0,"min_time":zs[0][1],"avg_time":0,"size":0,"max_size":0,"min_size":zs[0][2],"avg_size":0}
- except:
- out['err']= "\n[Error] No statistics available...\n"
- return out
- if len(zs)==0:
- continue
- for line in zs:
- if line[0]==200:
- entry['hits']+=1
- else:
- entry['fails']+=1
- try:
- if self.connection[zombie_stat]:
- entry['retries']+=1
- except:
- entry['retries']=entry['retries'] # black magic!
- entry['time']+=line[1]
- if line[1]>entry['max_time']:
- entry['max_time']=line[1]
- if line[1]<entry['min_time']:
- entry['min_time']=line[1]
- entry['size']+=line[2]
- if line[2]>entry['max_size']:
- entry['max_size']=line[2]
- if line[2]<entry['min_size']:
- entry['min_size']=line[2]
- if entry['fails'] == 0:
- self.zero_fails +=1
- entry['min_time'] = str(datetime.timedelta(seconds=entry['min_time']))
- entry['avg_time'] = str(datetime.timedelta(seconds=entry['time']/len(zs)))
- entry['max_time'] = str(datetime.timedelta(seconds=entry['max_time']))
- entry['time'] = str(datetime.timedelta(seconds=entry['time']))
- entry['min_size'] = self.sizeof_fmt(int(entry['min_size']))
- entry['avg_size'] = self.sizeof_fmt(int(entry['size']/len(zs)))
- entry['max_size'] = self.sizeof_fmt(int(entry['max_size']))
- entry['size']=self.sizeof_fmt(int(entry['size']))
- if entry['fails'] > out['max_fails']:
- out['max_fails'] = entry['fails']
- out['max_failz'] = zombie_stat
- if entry['hits'] > out['max_hits']:
- out['max_hits'] = entry['hits']
- out['max_hitz'] = zombie_stat
- if entry['fails'] == 0:
- if zombie_stat not in self.zombies_ready: # parse for repetitions
- self.zombies_ready.append(zombie_stat)
- data[entry['name']] = entry
- out['total_time'] = str(datetime.timedelta(seconds=self.total_time))
- out['avg_time_calc'] = self.total_time/self.total_connections
- out['avg_time'] = str(datetime.timedelta(seconds=out['avg_time_calc']))
- out['total_size'] = self.sizeof_fmt(int(self.total_size))
- out['avg_size'] = self.sizeof_fmt(int(self.total_size/self.total_connections))
- out['data']=data
- return out
- # wrapper
- def dump(self):
- out=self.get_stat()
- self.format(out)
- def list_fails(self):
- options = self.ufonet.options
- if self.total_connections==0:
- return
- if self.zombies_ready == None: # if not herd return
- return
- if not options.forceyes:
- print('-'*25)
- update_reply = input("Do you want to update your army (Y/n)")
- print('-'*25)
- else:
- update_reply = "Y"
- if update_reply == "n" or update_reply == "N":
- print("\nBye!\n")
- return
- else:
- self.ufonet.update_zombies(self.zombies_ready)
- print("\n[Info] - Botnet updated! ;-)\n")
- if os.path.exists('mothership') == True:
- os.remove('mothership') # remove mothership stream
- if os.path.exists('alien') == True:
- os.remove('alien') # remove random alien worker
|