jupiter- efnet irc botnet |
git clone git://git.acid.vegas/jupiter.git |
Log | Files | Refs | Archive | README | LICENSE |
jupiter.py (18858B)
1 #!/usr/bin/env python 2 # jupiter: internet relay chat botnet for efnet - developed by acidvegas in python (https://git.acid.vegas/jupiter) 3 4 ''' A M P L I S S I M U S M A C H I N A ''' 5 6 import argparse 7 import asyncio 8 import copy 9 import os 10 import random 11 import re 12 import socket 13 import ssl 14 import time 15 16 try: 17 import aiosocks 18 except ImportError: 19 raise SystemExit('Error: aiosocks module not installed! (pip install aiosocks)') 20 21 # Connection 22 servers = ( 23 {'server':'efnet.deic.eu', 'ssl':6697, 'ipv6': True}, 24 {'server':'efnet.port80.se', 'ssl':6697, 'ipv6': True}, 25 #{'server':'efnet.portlane.se', 'ssl':6697, 'ipv6': True}, # Removed (efnet.portlane.se is an alias for irc.efnet.org) 26 {'server':'irc.choopa.net', 'ssl':9000, 'ipv6': True}, 27 # {'server':'irc.colosolutions.net', 'ssl':None, 'ipv6':False}, # error: SSL handshake failed: unsafe legacy renegotiation disabled 28 #{'server':'irc.deft.com', 'ssl':None, 'ipv6':False}, # Removed (irc.deft.com points to irc.servercentral.net) 29 {'server':'irc.du.se', 'ssl':None, 'ipv6':False}, # error: handshake failed: dh key too small 30 #{'server':'irc.efnet.fr', 'ssl':6697, 'ipv6': True}, # Removed (irc.efnet.fr is an alias for irc.efnet.nl) 31 {'server':'irc.efnet.nl', 'ssl':6697, 'ipv6': True}, 32 {'server':'irc.homelien.no', 'ssl':6697, 'ipv6': True}, 33 {'server':'irc.mzima.net', 'ssl':6697, 'ipv6': True}, 34 #{'server':'irc.nordunet.se', 'ssl':6697, 'ipv6': True}, # Removed (irc.nordunet.se is an alias for irc.swepipe.se) 35 # {'server':'irc.prison.net', 'ssl':None, 'ipv6':False}, 36 {'server':'irc.swepipe.se', 'ssl':6697, 'ipv6': True}, 37 {'server':'irc.underworld.no', 'ssl':6697, 'ipv6': True}, 38 {'server':'irc.servercentral.net', 'ssl':9999, 'ipv6':False} 39 ) 40 ipv6 = False #True # Set to False if your system does not have an IPv6 address 41 channel = '#jupiter2' 42 backup = '#jupiter2-' + str(random.randint(1000,9999)) # Use /list -re #jupiter-* on weechat to find your bots 43 key = 'xChangeMex' 44 45 # Settings 46 admin = 'acidvegas!*@*' #'nick!user@host' # Can use wildcards (Must be in nick!user@host format) 47 connect_delay = False #True # Random delay between 5-15 minutes before connecting a clone to a server 48 id = 'TEST' # Unique ID so you can tell which bots belong what server 49 50 # Formatting Control Characters / Color Codes 51 bold = '\x02' 52 reset = '\x0f' 53 green = '03' 54 red = '04' 55 purple = '06' 56 orange = '07' 57 yellow = '08' 58 light_green = '09' 59 cyan = '10' 60 light_cyan = '11' 61 light_blue = '12' 62 pink = '13' 63 grey = '14' 64 65 # Globals 66 bots = list() 67 callerid = list() 68 69 def botcontrol(action, data, ci=False): 70 global bots, callerid 71 if action == '+': 72 if ci: 73 if data not in callerid: 74 callerid.append(data) 75 else: 76 if data not in bots: 77 bots.append(data) 78 elif action == '-': 79 if ci: 80 if data in callerid: 81 callerid.remove(data) 82 else: 83 if data in bots: 84 bots.remove(data) 85 86 def color(msg, foreground, background=None): 87 return f'\x03{foreground},{background}{msg}{reset}' if background else f'\x03{foreground}{msg}{reset}' 88 89 def debug(data): 90 print('{0} | [~] - {1}'.format(time.strftime('%I:%M:%S'), data)) 91 92 def error(data, reason=None): 93 print('{0} | [!] - {1} ({2})'.format(time.strftime('%I:%M:%S'), data, str(repr(reason)))) if reason else print('{0} | [!] - {1}'.format(time.strftime('%I:%M:%S'), data)) 94 95 def is_admin(ident): 96 return re.compile(admin.replace('*','.*')).search(ident) 97 98 def rndnick(): 99 prefix = random.choice(['sl','st','sn','cr','pl','pr','fr','fl','qu','br','gr','sh','sk','tr','kl','wr','bl']+list('bcdfgklmnprstvwz')) 100 midfix = random.choice(('aeiou'))+random.choice(('aeiou'))+random.choice(('bcdfgklmnprstvwz')) 101 suffix = random.choice(['ed','est','er','ered','le','ly','y','ies','iest','ian','ion','est','ing','led','inger']+list('abcdfgklmnprstvwz')) 102 endpix = str(random.randint(1960,2025)) if random.choice([True,False]) else str(random.randint(1,99)) if random.choice([True,False]) else '' 103 return prefix+midfix+suffix+endpix 104 105 def ssl_ctx(): 106 ctx = ssl.create_default_context() 107 ctx.check_hostname = False 108 ctx.verify_mode = ssl.CERT_NONE 109 return ctx 110 111 def unicode(): 112 msg='\u202e\u0007\x03' + str(random.randint(2,14)) 113 for i in range(random.randint(150, 200)): 114 msg += chr(random.randint(0x1000,0x3000)) 115 return msg 116 117 class clone(): 118 def __init__(self, server, vhost=None, proxy=None, use_ipv6=False): 119 self.server = server 120 self.vhost = vhost 121 self.proxy = proxy 122 self.use_ipv6 = use_ipv6 123 self.ssl_status = True 124 self.nickname = rndnick() 125 self.host = self.nickname + '!*@*' 126 self.monlist = list() 127 self.landmine = None 128 self.relay = None 129 self.loop = None 130 self.reader = None 131 self.writer = None 132 133 async def connect(self): 134 while True: 135 try: 136 if connect_delay: 137 await asyncio.sleep(random.randint(30,120)) 138 if self.proxy: 139 self.ssl_status = False 140 auth = self.proxy.split('@')[0].split(':') if '@' in self.proxy else None 141 proxy_ip, proxy_port = self.proxy.split('@')[1].split(':') if '@' in self.proxy else self.proxy.split(':') 142 options = { 143 'proxy' : aiosocks.Socks5Addr(proxy_ip, proxy_port), 144 'proxy_auth' : aiosocks.Socks5Auth(*auth) if auth else None, 145 'dst' : (self.server['server'], self.server['ssl'] if self.server['ssl'] and self.ssl_status else 6667), 146 'limit' : 1024, 147 'ssl' : ssl_ctx() if self.server['ssl'] and self.ssl_status else None, 148 'family' : 2 149 } 150 self.reader, self.writer = await asyncio.wait_for(aiosocks.open_connection(**options), 30) 151 else: 152 options = { 153 'host' : self.server['server'], 154 'port' : self.server['ssl'] if self.server['ssl'] and self.ssl_status else 6667, 155 'limit' : 1024, 156 'ssl' : ssl_ctx() if self.server['ssl'] and self.ssl_status else None, 157 'family' : socket.AF_INET6 if self.use_ipv6 else socket.AF_INET, 158 'local_addr' : (self.vhost, random.randint(5000,60000)) if self.vhost else None 159 } 160 self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), 30) 161 await self.raw(f'USER {rndnick()} 0 * :{rndnick()}') 162 await self.raw('NICK ' + self.nickname) 163 except Exception as ex: 164 v6 = 'using IPv6 ' if self.use_ipv6 else '' 165 if self.ssl_status and self.server['ssl']: 166 self.ssl_status = False 167 print(options) 168 error('Failed to connect to \'{0}\' IRC server {1}on port {2} using SSL/TLS'.format(self.server['server'], v6, str(self.server['ssl'])), ex) 169 else: 170 if not self.ssl_status and self.server['ssl']: 171 self.ssl_status = True 172 error('Failed to connect to \'{0}\' IRC server {1}'.format(self.server['server'], v6), ex) 173 else: 174 await self.listen() 175 finally: 176 await asyncio.sleep(10) #await asyncio.sleep(86400+random.randint(1800,3600)) 177 178 async def event_message(self, ident, nick, target, msg): 179 if target == self.relay: 180 await asyncio.sleep(0.5) 181 await self.sendmsg(channel, '[{0}] <{1}>: {2}'.format(color(target, cyan), color(nick[:15].ljust(15), purple), msg)) 182 if is_admin(ident): 183 args = msg.split() 184 if args[0] in ('@all',self.nickname) and len(args) >= 2: 185 if len(args) == 2: 186 if args[1] == 'id': 187 await self.sendmsg(target, id) 188 elif args[1] == 'sync' and args[0] == self.nickname: # NOTE: Do we need sync? Seems to work without it... (sync adds admin to botlist and wont +o swarm everyone if you +o a bot) 189 await self.raw('WHO ' + channel) 190 elif len(args) == 3: 191 if args[1] == '5000': 192 chan = args[2] 193 if chan == 'stop': 194 self.landmine = None 195 await self.sendmsg(channel, '5000 mode turned off') 196 elif chan[:1] == '#': 197 self.landmine = chan 198 await self.sendmsg(channel, '5000 mode actived on ' + color(chan, cyan)) 199 elif args[1] == 'monitor': 200 if args[2] == 'list' and self.monlist: 201 await self.sendmsg(target, '[{0}] {1}'.format(color('Monitor', light_blue), ', '.join(self.monlist))) 202 elif args[2] == 'reset' and self.monlist: 203 await self.monitor('C') 204 self.monlist = list() 205 await self.sendmsg(target, '{0} nick(s) have been {1} from the monitor list.'.format(color(str(len(self.monlist)), cyan), color('removed', red))) 206 elif args[2][:1] == '+': 207 nicks = [mon_nick for mon_nick in set(args[2][1:].split(',')) if mon_nick not in self.monlist] 208 if nicks: 209 await self.monitor('+', nicks) 210 self.monlist += nicks 211 await self.sendmsg(target, '{0} nick(s) have been {1} to the monitor list.'.format(color(str(len(nicks)), cyan), color('added', green))) 212 elif args[2][:1] == '-': 213 nicks = [mon_nick for mon_nick in set(args[2][1:].split(',')) if mon_nick in self.monlist] 214 if nicks: 215 await self.monitor('-', nicks) 216 for mon_nick in nicks: 217 self.monlist.remove(mon_nick) 218 await self.sendmsg(target, '{0} nick(s) have been {1} from the monitor list.'.format(color(str(len(nicks)), cyan), color('removed', red))) 219 elif args[1] == 'relay' and args[0] == self.nickname: 220 chan = args[2] 221 if chan == 'stop': 222 self.relay = None 223 await self.sendmsg(channel, 'Relay turned off') 224 elif chan[:1] == '#': 225 self.relay = chan 226 await self.sendmsg(channel, 'Monitoring ' + color(chan, cyan)) 227 elif len(args) >= 4 and args[1] == 'raw': 228 if args[2] == '-d': 229 data = ' '.join(args[3:]) 230 self.loops = asyncio.create_task(self.raw(data,True)) 231 else: 232 data = ' '.join(args[2:]) 233 await self.raw(data) 234 elif target == self.nickname: 235 if msg.startswith('\x01ACTION'): 236 await self.sendmsg(channel, '[{0}] {1}{2}{3} * {4}'.format(color('PM', red), color('<', grey), color(nick, yellow), color('>', grey), msg[8:][:-1])) 237 else: 238 await self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('PM', red), color('<', grey), color(nick, yellow), color('>', grey), msg)) 239 240 async def event_mode(self, nick, chan, modes): 241 if chan == backup and modes == '+nt' and key: 242 await self.mode(backup, '+mk' + key) 243 elif ('e' in modes or 'I' in modes) and self.host in modes: 244 if nick not in bots: 245 await self.mode(chan, f'+eI *!*@{self.host} *!*@{self.host}') # Quick and dirty +eI recovery 246 else: 247 nicks = modes.split()[1:] 248 modes = modes.split()[0] 249 if 'o' in modes: 250 state = None 251 op = False 252 lostop = list() 253 for item in modes: 254 if item in ('+-'): 255 state = item 256 else: 257 if nicks: 258 current = nicks.pop(0) 259 if current == self.nickname and item == 'o': 260 op = True if state == '+' else False 261 elif current in bots and item == 'o' and state == '-': 262 lostop.append(current) 263 if op: 264 if nick not in bots: 265 _bots = copy.deepcopy(bots) 266 random.shuffle(_bots) 267 _bots = [_bots[i:i+4] for i in range(0, len(_bots), 4)] 268 for clones in _bots: 269 await self.mode(chan, '+oooo ' + ' '.join(clones)) 270 await self.mode(chan, f'+eI *!*@{self.host} *!*@{self.host}') 271 await self.mode(chan, f'+eI {unicode()[:10]}!{unicode()[:10]}@{unicode()[:10]} {unicode()[:10]}!{unicode()[:10]}@{unicode()[:10]}') 272 elif lostop: 273 await self.mode(chan, '+' + 'o'*len(lostop) + ' ' + ' '.join(lostop)) 274 await self.raw(f'KICK {chan} {nick} {unicode()}') 275 await self.mode(chan, f'+b {nick}!*@*') 276 await self.sendmsg(chan, f'{unicode()} oh god what is happening {unicode()}') 277 278 async def listen(self): 279 while not self.reader.at_eof(): 280 try: 281 data = await asyncio.wait_for(self.reader.readuntil(b'\r\n'), 600) 282 line = data.decode('utf-8').strip() 283 args = line.split() 284 if line.startswith('ERROR :Closing Link:'): 285 print(line) 286 raise Exception('Banned') 287 elif line.startswith('ERROR :Reconnecting too fast'): 288 raise Exception('Throttled') 289 elif args[0] == 'PING': 290 await self.raw('PONG ' + args[1][1:]) 291 elif args[1] == '001': # RPL_WELCOME 292 if self.monlist: 293 await self.monitor('+', self.monlist) 294 await self.raw(f'JOIN {channel} {key}') if key else await self.raw('JOIN ' + channel) 295 await self.raw(f'JOIN {backup} {key}') if key else await self.raw('JOIN ' + backup) 296 elif args[1] == '315': # RPL_ENDOFWHO 297 await self.sendmsg(channel, 'Sync complete') 298 elif args[1] == '352' and len(args) >= 8: # RPL_WHOREPLY 299 nick = args[7] 300 botcontrol('+',nick) 301 elif args[1] == '433' and len(args) >= 4: # ERR_NICKNAMEINUSE 302 nick = args[2] 303 target_nick = args[3] 304 if nick == '*': 305 self.nickname = rndnick() 306 await self.nick(self.nickname) 307 elif args[1] == '465': # ERR_YOUREBANNEDCREEP 308 error('K-Lined', self.server) 309 elif args[1] in ('716','717'): # RPL_TARGNOTIFY 310 nick = args[2] #TODO: verify this is the correct arguement 311 botcontrol(nick, '+', True) 312 elif args[1] == '731' and len(args) >= 4: # RPL_MONOFFLINE 313 nick = args[3][1:] 314 await self.nick(nick) 315 elif args[1] == 'JOIN' and len(args) == 3: 316 nick = args[0].split('!')[0][1:] 317 host = args[0].split('@')[1] 318 chan = args[2][1:] 319 if chan == self.landmine: 320 await self.sendmsg(chan, f'{unicode()} oh god {nick} what is happening {unicode()}') 321 await self.sendmsg(nick, f'{unicode()} oh god {nick} what is happening {unicode()}') 322 elif chan == channel: 323 botcontrol('+', nick) 324 if nick == self.nickname: 325 self.host = host 326 elif args[1] == 'KICK' and len(args) >= 4: 327 chan = args[2] 328 nick = args[3] 329 if nick == self.nickname: 330 await asyncio.sleep(3) 331 await self.raw('JOIN ' + chan) 332 elif args[1] == 'MODE' and len(args) >= 4: 333 nick = args[0].split('!')[0][1:] 334 chan = args[2] 335 modes = ' '.join(args[3:]) 336 await self.event_mode(nick, chan, modes) 337 elif args[1] == 'NICK' and len(args) == 3: 338 nick = args[0].split('!')[0][1:] 339 new_nick = args[2][1:] 340 if nick == self.nickname: 341 botcontrol('-', nick) 342 botcontrol('+', new_nick) 343 self.nickname = new_nick 344 if self.nickname in self.monlist: 345 await self.monitor('C') 346 self.monlist = list() 347 elif nick in self.monlist: 348 await self.nick(nick) 349 elif nick in bots: 350 botcontrol('-', nick) 351 botcontrol('+', new_nick) 352 elif args[1] == 'NOTICE': 353 nick = args[0].split('!')[0][1:] 354 target = args[2] 355 msg = ' '.join(args[3:])[1:] 356 if target == self.nickname: 357 if '!' not in args[0] and 'Blacklisted Proxy found' in line: 358 error('Blacklisted IP', line) 359 elif 'You are now being scanned for open proxies' in line: 360 pass # We can ignore these & not relay them into the channel 361 else: 362 await self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('NOTICE', purple), color('<', grey), color(nick, yellow), color('>', grey), msg)) 363 elif args[1] == 'PRIVMSG' and len(args) >= 4: 364 ident = args[0][1:] 365 nick = args[0].split('!')[0][1:] 366 target = args[2] 367 msg = ' '.join(args[3:])[1:] 368 if msg[:1] == '\001': 369 msg = msg[1:-1] 370 if target == self.nickname: 371 if msg == 'VERSION': 372 version = random.choice(['http://www.mibbit.com ajax IRC Client','mIRC v6.35 Khaled Mardam-Bey','xchat 0.24.1 Linux 2.6.27-8-eeepc i686','rZNC Version 1.0 [02/01/11] - Built from ZNC','thelounge v3.0.0 -- https://thelounge.chat/']) 373 await self.raw(f'NOTICE {nick} \001VERSION {version}\001') 374 else: 375 await self.sendmsg(channel, '[{0}] {1}{2}{3} {4}'.format(color('CTCP', green), color('<', grey), color(nick, yellow), color('>', grey), msg)) 376 else: 377 await self.event_message(ident, nick, target, msg) 378 elif args[1] == 'QUIT': 379 nick = args[0].split('!')[0][1:] 380 if nick in self.monlist: 381 await self.nick(nick) 382 elif nick in bots: 383 botcontrol('-', nick) 384 except (UnicodeDecodeError,UnicodeEncodeError): 385 pass 386 except Exception as ex: 387 error('Unexpected error occured on \'{0}\' server.'.format(self.server['server']), ex) 388 try: 389 self.loop.cancel() 390 except: 391 pass 392 break 393 394 async def mode(self, target, mode): 395 await self.raw(f'MODE {target} {mode}') 396 397 async def monitor(self, action, nicks=list()): 398 await self.raw(f'MONITOR {action} ' + ','.join(nicks)) 399 400 async def nick(self, nick): 401 await self.raw('NICK ' + nick) 402 403 async def raw(self, data, delay=False): 404 try: 405 if delay: 406 await asyncio.sleep(random.randint(60,300)) 407 self.writer.write(data[:510].encode('utf-8') + b'\r\n') 408 await self.writer.drain() 409 except asyncio.CancelledError: 410 pass 411 412 async def sendmsg(self, target, msg): 413 await self.raw(f'PRIVMSG {target} :{msg}') 414 415 async def main(input_data=None): 416 jobs = list() 417 for i in range(args.clones): 418 for server in servers: 419 if input_data: 420 for item in input_data: 421 if args.proxies: 422 jobs.append(asyncio.ensure_future(clone(server, proxy=item).connect())) 423 elif args.vhosts: 424 if ':' in item: 425 if ipv6 and server['ipv6']: 426 jobs.append(asyncio.ensure_future(clone(server, vhost=item, use_ipv6=True).connect())) 427 else: 428 jobs.append(asyncio.ensure_future(clone(server, vhost=item).connect())) 429 430 else: 431 jobs.append(asyncio.ensure_future(clone(server).connect())) 432 if ipv6 and server['ipv6']: 433 jobs.append(asyncio.ensure_future(clone(server, use_ipv6=True).connect())) 434 await asyncio.gather(*jobs) 435 436 # Main 437 if __name__ == '__main__': 438 print('#'*56) 439 print('#{:^54}#'.format('')) 440 print('#{:^54}#'.format('Jupiter IRC botnet for EFnet')) 441 print('#{:^54}#'.format('Developed by acidvegas in Python')) 442 print('#{:^54}#'.format('https://git.acid.vegas/jupiter')) 443 print('#{:^54}#'.format('')) 444 print('#'*56) 445 parser = argparse.ArgumentParser(usage='%(prog)s [options]') 446 parser.add_argument('-p', '--proxies', type=str, help="Path to file containing proxies.") 447 parser.add_argument('-v', '--vhosts', type=str, help="Path to file containing vhosts.") 448 parser.add_argument('-c', '--clones', type=int, default=3, help="Number to define the concurrency to use. Default is 3.") 449 args = parser.parse_args() 450 if args.proxies and args.vhosts: 451 raise SystemExit('Cannot use both proxies & vhosts at the same time!') 452 elif (input_file := args.proxies if args.proxies else args.vhosts if args.vhosts else None): 453 if os.path.exists(input_file): 454 data = set([item.rstrip() for item in open(input_file, 'r').read().splitlines() if item]) 455 if data: 456 print('Loaded {0:,} items from {1}'.format(len(data), input_file)) 457 asyncio.run(main(data)) 458 else: 459 raise SystemExit(f'Error: {input_file} is empty!') 460 else: 461 raise SystemExit(f'Error: {input_file} does not exist!') 462 else: 463 print('Loading raw clones...') 464 asyncio.run(main())