herd.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-"
  3. """
  4. This file is part of the UFONet project, https://ufonet.03c8.net
  5. Copyright (c) 2013/2020 | psy <epsylon@riseup.net>
  6. You should have received a copy of the GNU General Public License along
  7. with UFONet; if not, write to the Free Software Foundation, Inc., 51
  8. Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  9. """
  10. import socket, threading, logging, datetime, sys, os, re, time
  11. from . import zombie
  12. # zombie tracking class
  13. class Herd(object):
  14. # basic constructor
  15. def __init__(self,ufonet):
  16. super(Herd, self).__init__()
  17. self.ufonet=ufonet
  18. self.reset()
  19. self.total_connections=0
  20. self.total_hits=0
  21. self.total_fails=0
  22. self.total_time=0
  23. self.total_size=0
  24. self.total_connection_fails=0
  25. self.living=threading.active_count()
  26. self.stats={}
  27. self.zombies_ready = []
  28. # property setup
  29. def reset(self):
  30. self.active = []
  31. self.done = []
  32. self.lock = threading.Lock()
  33. self.result={}
  34. self.connection={}
  35. # clean temporary statistic files
  36. def cleanup(self):
  37. try:
  38. if os.path.exists("/tmp/ufonet.html"):
  39. os.remove("/tmp/ufonet.html")
  40. if os.path.exists("/tmp/ufonet.html.tmp"):
  41. os.remove("/tmp/ufonet.html.tmp")
  42. except:
  43. pass
  44. # got a new one!
  45. def new_zombie(self, zombie):
  46. self.total_connections+=1
  47. if zombie not in self.stats:
  48. self.stats[zombie]=[]
  49. with self.lock:
  50. self.active.append(zombie)
  51. # give me your report & byebye
  52. def kill_zombie(self, zombie, result, connection_failed):
  53. with self.lock:
  54. try:
  55. self.result[zombie]=str(result)
  56. self.connection[zombie]=connection_failed
  57. self.done.append(zombie)
  58. if result[0]==200 :
  59. self.total_hits+=1
  60. else:
  61. self.total_fails+=1
  62. if connection_failed:
  63. self.total_connection_fails+=1
  64. self.active.remove(zombie)
  65. self.total_time+=result[1]
  66. self.total_size+=result[2]
  67. if zombie in self.stats:
  68. self.stats[zombie].append(result)
  69. else:
  70. pass
  71. except:
  72. pass
  73. # head count (+/- headless zombies)
  74. # active thread count = 1 principal + 1/zombie
  75. def no_more_zombies(self):
  76. ac=threading.active_count()
  77. options = self.ufonet.options
  78. if options.verbose == True:
  79. if ac>self.living:
  80. if ac-self.living not in self.ufonet.ac_control:
  81. print("[Info] [AI] [Control] Active [ARMY] returning from the combat front: "+ str(ac-self.living))
  82. self.ufonet.ac_control.append(ac-self.living)
  83. with self.lock:
  84. return ac==self.living
  85. # retrieve result by zombie name
  86. def get_result(self,zombie):
  87. return self.result[zombie] or False
  88. # retrieve connection status by zombie name
  89. def connection_failed(self,zombie):
  90. return self.connection[zombie] or False
  91. # retrieve size on correct format
  92. def sizeof_fmt(self, size, suffix='B'):
  93. for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
  94. if abs(size) < 1024.0:
  95. return "%3.1f%s%s" % (size, unit, suffix)
  96. size /= 1024.0
  97. return "%.1f%s%s" % (size, 'Yi', suffix)
  98. # generate html statistics
  99. def dump_html(self,final=False):
  100. buf=""
  101. out=self.get_stat()
  102. if os.path.exists("/tmp/ufonet.html.tmp"):
  103. try:
  104. self.cleanup()
  105. except:
  106. print('[Info] Previous tmp file found... html content will not be updated.')
  107. pass
  108. buf += "<div>" + os.linesep
  109. if out['err'] is not None:
  110. buf += "<div>Errors : <br/>"+str(out['err'])+'</div>'+os.linesep
  111. 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
  112. buf += "<div id='zombie_list'><h3>Zombies: </h3>"
  113. if len(out['data'])==0:
  114. buf += "waiting..."
  115. for l in out["data"] :
  116. hits='<font color="green">'+str(out['data'][l]['hits'])+'</font>'
  117. fails=str(out['data'][l]['fails'])
  118. buf += "<button href='#' title='"+l+"' onclick=\"zombie_detail("+str(out['data'][l])+")\">"+fails+"/"+hits+"</button>"+os.linesep
  119. buf += "</div>"
  120. if out['max_hits'] > 0:
  121. buf += "<hr/>"+os.linesep
  122. buf += "<div>Zombie 0day: "+str( out['max_hitz'])+ " with "+str( out['max_hits'])+ " hits</div>"+os.linesep
  123. if out['max_fails'] > 0:
  124. buf += "<hr/>"+os.linesep
  125. buf += "<div>Worst zombie: "+str(out['max_failz'])+ " with "+str(out['max_fails'])+" fails</div>"+os.linesep
  126. buf += "<hr/>"+os.linesep
  127. try:
  128. buf += "<div>Total time:" +str(out['total_time'])+ " | Avg time:"+str(out['avg_time'])+"</div>"+os.linesep
  129. buf += "<div>Total size:"+str(out['total_size'])+" | Avg size:"+str(out['avg_size'])+"</div>"+os.linesep
  130. buf += "<hr/>"+os.linesep
  131. except:
  132. pass
  133. buf += "<div><h3>Troops: </h3></div>"+os.linesep
  134. buf += "<div>Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail)+"</div>" + os.linesep
  135. buf += "<div>Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail)+"</div>" + os.linesep
  136. buf += "<div>X-RPCs: " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail)+"</div>" + os.linesep
  137. buf += "<div>UCAVs: " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail)+"</div>" + os.linesep
  138. f = open("/tmp/ufonet.html.tmp", "w")
  139. f.write(buf)
  140. if(final):
  141. f.write("<script>hdone=true</script>")
  142. f.close()
  143. try:
  144. os.rename("/tmp/ufonet.html.tmp","/tmp/ufonet.html")
  145. except:
  146. pass
  147. # generate statistics for stdout
  148. def format(self, out):
  149. if len(out['data'])==0:
  150. print("[Info] Not any feedback data to show. Exiting...")
  151. return
  152. print('='*42)
  153. print("Herd statistics")
  154. print("="*42)
  155. for zo in out['data']:
  156. z=out['data'][zo]
  157. print('Zombie :', z['name'], " | ", z['hits'], " hits ", z['fails'] ," fails ", z['retries'], " retries ")
  158. print(" Times:", z['time'], " total ", z['min_time'], " min ", z['avg_time'] ," avg ", z['max_time'], " max ")
  159. print(" Sizes:", z['size'], " total ", z['min_size'], " min ", z['size'] ," avg ", z['max_size'], " max ")
  160. print("-"*21)
  161. if out['max_hits'] > 0:
  162. print("="*80)
  163. print("Zombie 0day: ", out['max_hitz'], " with ", out['max_hits'], " hits")
  164. if out['max_fails'] > 0:
  165. print("="*80)
  166. print("Worst zombie: ", out['max_failz'], " with ", out['max_fails'], " fails")
  167. print("="*80)
  168. print("Total invocations:", self.total_connections,"| Zombies:", str(self.ufonet.total_zombie),"| Hits:", self.total_hits,"| Fails:", self.total_fails)
  169. print("Total time:", out['total_time'], "| Avg time:", out['avg_time'])
  170. print("Total size:", out['total_size'],"| Avg size:", out['avg_size'])
  171. print("-"*21)
  172. print("="*42)
  173. print("Troops statistics")
  174. print("="*42)
  175. print("Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail))
  176. print("Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail))
  177. print("X-RPCs: " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail))
  178. print("UCAVs : " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail))
  179. print("-"*21)
  180. print("\n") # gui related
  181. print('='*21)
  182. # show what we have
  183. def get_stat(self):
  184. data={}
  185. out={'err':None,"header":"","data":{},"total":{},"footer":"",'max_fails':0,'max_failz':"",'max_hits':0,'max_hitz':""}
  186. if os.path.exists("html.tmp"):
  187. out['err']= "\n[Info] Previous tmp file found... html content will not be updated."
  188. return out
  189. if self.total_connections==0:
  190. out['err']= "\n[Error] No herd without zombies..."
  191. return out
  192. if len(self.stats)==0:
  193. out['err']= "\n[Error] No statistics available..."
  194. return out
  195. self.zero_fails = 0
  196. for zombie_stat in self.stats:
  197. zs=self.stats[zombie_stat]
  198. try:
  199. 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}
  200. except:
  201. out['err']= "\n[Error] No statistics available...\n"
  202. return out
  203. if len(zs)==0:
  204. continue
  205. for line in zs:
  206. if line[0]==200:
  207. entry['hits']+=1
  208. else:
  209. entry['fails']+=1
  210. try:
  211. if self.connection[zombie_stat]:
  212. entry['retries']+=1
  213. except:
  214. entry['retries']=entry['retries'] # black magic!
  215. entry['time']+=line[1]
  216. if line[1]>entry['max_time']:
  217. entry['max_time']=line[1]
  218. if line[1]<entry['min_time']:
  219. entry['min_time']=line[1]
  220. entry['size']+=line[2]
  221. if line[2]>entry['max_size']:
  222. entry['max_size']=line[2]
  223. if line[2]<entry['min_size']:
  224. entry['min_size']=line[2]
  225. if entry['fails'] == 0:
  226. self.zero_fails +=1
  227. entry['min_time'] = str(datetime.timedelta(seconds=entry['min_time']))
  228. entry['avg_time'] = str(datetime.timedelta(seconds=entry['time']/len(zs)))
  229. entry['max_time'] = str(datetime.timedelta(seconds=entry['max_time']))
  230. entry['time'] = str(datetime.timedelta(seconds=entry['time']))
  231. entry['min_size'] = self.sizeof_fmt(int(entry['min_size']))
  232. entry['avg_size'] = self.sizeof_fmt(int(entry['size']/len(zs)))
  233. entry['max_size'] = self.sizeof_fmt(int(entry['max_size']))
  234. entry['size']=self.sizeof_fmt(int(entry['size']))
  235. if entry['fails'] > out['max_fails']:
  236. out['max_fails'] = entry['fails']
  237. out['max_failz'] = zombie_stat
  238. if entry['hits'] > out['max_hits']:
  239. out['max_hits'] = entry['hits']
  240. out['max_hitz'] = zombie_stat
  241. if entry['fails'] == 0:
  242. if zombie_stat not in self.zombies_ready: # parse for repetitions
  243. self.zombies_ready.append(zombie_stat)
  244. data[entry['name']] = entry
  245. out['total_time'] = str(datetime.timedelta(seconds=self.total_time))
  246. out['avg_time_calc'] = self.total_time/self.total_connections
  247. out['avg_time'] = str(datetime.timedelta(seconds=out['avg_time_calc']))
  248. out['total_size'] = self.sizeof_fmt(int(self.total_size))
  249. out['avg_size'] = self.sizeof_fmt(int(self.total_size/self.total_connections))
  250. out['data']=data
  251. return out
  252. # wrapper
  253. def dump(self):
  254. out=self.get_stat()
  255. self.format(out)
  256. def list_fails(self):
  257. options = self.ufonet.options
  258. if self.total_connections==0:
  259. return
  260. if self.zombies_ready == None: # if not herd return
  261. return
  262. if not options.forceyes:
  263. print('-'*25)
  264. update_reply = input("Do you want to update your army (Y/n)")
  265. print('-'*25)
  266. else:
  267. update_reply = "Y"
  268. if update_reply == "n" or update_reply == "N":
  269. print("\nBye!\n")
  270. return
  271. else:
  272. self.ufonet.update_zombies(self.zombies_ready)
  273. print("\n[Info] - Botnet updated! ;-)\n")
  274. if os.path.exists('mothership') == True:
  275. os.remove('mothership') # remove mothership stream
  276. if os.path.exists('alien') == True:
  277. os.remove('alien') # remove random alien worker