IRCP

- information gathering tool for irc servers
git clone git://git.acid.vegas/IRCP.git
Log | Files | Refs | Archive | README | LICENSE

commit 2b5efa8bf00f227862057cc3a3b355745eff5115
parent 3f66438ebebcbe4c619c8f4486324ea78ed19e6d
Author: acidvegas <acid.vegas@acid.vegas>
Date: Tue, 6 Jun 2023 01:17:01 -0400

Added support for IPv6 & non-standard ports

Diffstat:
MREADME.md | 50+++++++++++++-------------------------------------
Mircp.py | 143+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------

2 files changed, 99 insertions(+), 94 deletions(-)

diff --git a/README.md b/README.md
@@ -6,12 +6,17 @@
 
 A robust information gathering tool for large scale reconnaissance on [Internet Relay Chat](https://en.wikipedia.org/wiki/Internet_Relay_Chat) servers, made for future usage with [internetrelaychat.org](https://internetrelaychat.org) for public statistics on the protocol.
 
-Meant to be used in combination with [masscan](https://github.com/robertdavidgraham/masscan) checking **0.0.0.0/0** *(the entire IPv4 range)* for port **6667**.
+Meant to be used in combination with [masscan](https://github.com/robertdavidgraham/masscan) checking **0.0.0.0/0** *(the entire IPv4 range)* for ports **6660-6669**, **6697**, **7000**, & other common IRC ports.
 
-The idea is to create a *proof-of-concept* documenting how large-scale information gathering on the IRC protocol can be malicious & invasive to privacy.
+The idea is to create a *proof-of-concept* documenting how large-scale information gathering on the IRC protocol can be malicious & invasive to privacy, while also yielding deep-dive look at the IRC protocol & it's internal statistics & commonalities.
+
+## Usage
+The only required arguement to pass is a direct path to the targets list, which should be a text file containing a new-line seperated list of targets. Targets must be a valid IPv4 or IPv6 address & can optionally be suffixed with a port.
+
+Edit [ircp.py](https://github.com/internet-relay-chat/IRCP/blob/master/ircp.py) & tweak the settings to your favor, though they rest with sane defaults.
 
 ## Order of Operations
-First, an attempt to connect using SSL/TLS on port 6697 is made, which will fall back to a standard connection on port 6667 if it fails. The **RPL_ISUPPORT** *(005)* response is checked for the `SSL=` option to try & locate secure ports.
+First, an attempt to connect using SSL/TLS is made, which will fall back to a standard connection if it fails. If a non-standard port was given, both standatd & secure connection attempts are made on the port as-well. The **RPL_ISUPPORT** *(005)* response is checked for the `SSL=` option to try & locate secure ports.
 
 Once connected, server information is gathered from `ADMIN`, `CAP LS`, `MODULES -all`, `VERSION`, `IRCOPS`, `MAP`, `INFO`, `LINKS`, `STATS p`, & `LIST` replies. An attempt to register a nickname is then made by trying to contact NickServ.
 
@@ -21,37 +26,6 @@ Once we have finishing scanning a server, the information found is saved to a JS
 
 Everything is done in a *carefully* throttled manner for stealth to avoid detection. An extensive amount research on IRC daemons, services, & common practices used by network administrators was done & has fine tuned this project to be able to evade common triggers that thwart what we are doing.
 
-## Opt-out
-The IRC networks we scanned are PUBLIC networks...any person can freely connect & parse the same information. Send your hate mail to [scan@internetrelaychat.org](mailto://scan@internetrelaychat.org)
-
-## Config
-###### Settings
-| Setting       | Default Value                  | Description                                           |
-| ------------- | ------------------------------ | ----------------------------------------------------- |
-| `errors`      | `True`                         | Show errors in console                                |
-| `errors_conn` | `False`                        | Show connection errors in console                     |
-| `log_max`     | `5000000`                      | Maximum log size *(in bytes)* before starting another |
-| `nickname`    | `"IRCP"`                       | IRC nickname *(`None` = random)*                      |
-| `username`    | `"ircp"`                       | IRC username *(`None` = random)*                      |
-| `realname`    | `"internetrelaychat.org"`      | IRC realname *(`None` = random)*                      |
-| `ns_mail`     | `"scan@internetrelaychat.org"` | NickServ email address *(`None` = random)*            |
-| `ns_pass`     | `"changeme"`                   | NickServ password *(`None` = random)*                 |
-| `vhost`       | `None`                         | Bind to a specific IP address                         |
-
-###### Throttle
-| Setting    | Default Value | Description                                                   |
-| ---------- | ------------- | ------------------------------------------------------------- |
-| `channels` | `3`           | Maximum number of channels to scan at once                    |
-| `delay`    | `300`         | Delay before registering nick *(if enabled)* & sending `LIST` |
-| `join`     | `10`          | Delay between channel `JOIN`                                  |
-| `nick`     | `300`         | Delay between every random `NICK` change                      |
-| `part`     | `10`          | Delay before `PART` from channel                              |
-| `seconds`  | `300`         | Maximum seconds to wait when throttled for `JOIN`             |
-| `threads`  | `100`         | Maximum number of threads running                             |
-| `timeout`  | `30`          | Timeout for all sockets                                       |
-| `whois`    | `5`           | Delay between `WHOIS` requests                                |
-| `ztimeout` | `200`         | Timeout for zero data from server                             |
-
 ## Preview
 ![](.screens/preview.png)
 
@@ -77,10 +51,12 @@ Mass scanning *default* ports of services is nothing new & though port 6667 is n
 * Create a seperate log for failed connections
 * Ability to link multiple IRCP instances running in daemon mode together for balancing
 * Remote syncing the logs to another server
-* Support for handling a target list that contains host:port:ssl for networks on non-standard ports
 * Give props to [bwall](https://github.com/bwall) for giving me the idea with his [ircsnapshot](https://github.com/bwall/ircsnapshot) repository
-* Confirm nick registered *(most likely through MODE +r)*
-* Confirm SSL/TLS connections *(most likely through "You are connected using SSL cipher" NOTICE message)*
+* Confirm nick registered *(most likely through MODE +r)* *(Log nick & password)*
+* Support for hostnames in targets list *(Attempt IPv6 & fallback to IPv4)*
+
+## Opt-out
+The IRC networks we scanned are PUBLIC networks...any person can freely connect & parse the same information. Send your hate mail to [scan@internetrelaychat.org](mailto://scan@internetrelaychat.org)
 
 ## Mirrors
 - [acid.vegas](https://git.acid.vegas/ircp)
diff --git a/ircp.py b/ircp.py
@@ -24,6 +24,7 @@ class settings:
 
 class throttle:
 	channels = 3   if not settings.daemon else 2   # Maximum number of channels to scan at once
+	connect  = 15  if not settings.daemon else 60  # Delay between each connection attempt on a diffferent port
 	delay    = 300 if not settings.daemon else 600 # Delay before registering nick (if enabled) & sending /LIST
 	join     = 10  if not settings.daemon else 30  # Delay between channel JOINs
 	nick     = 300 if not settings.daemon else 600 # Delay between every random NICK change
@@ -34,44 +35,43 @@ class throttle:
 	whois    = 5   if not settings.daemon else 15  # Delay between WHOIS requests
 	ztimeout = 200 if not settings.daemon else 300 # Timeout for zero data from server
 
-donotscan = (
-	'irc.dronebl.org',       'irc.alphachat.net',
-	'5.9.164.48',            '45.32.74.177',          '104.238.146.46',               '149.248.55.130',
-	'2001:19f0:6001:1dc::1', '2001:19f0:b001:ce3::1', '2a01:4f8:160:2501:48:164:9:5', '2001:19f0:6401:17c::1'
-)
-
-badchan = {
-	'403' : 'ERR_NOSUCHCHANNEL',    '405' : 'ERR_TOOMANYCHANNELS',
-	'435' : 'ERR_BANONCHAN',        '442' : 'ERR_NOTONCHANNEL',
-	'448' : 'ERR_FORBIDDENCHANNEL', '470' : 'ERR_LINKCHANNEL',
-	'471' : 'ERR_CHANNELISFULL',    '473' : 'ERR_INVITEONLYCHAN',
-	'474' : 'ERR_BANNEDFROMCHAN',   '475' : 'ERR_BADCHANNELKEY',
-	'476' : 'ERR_BADCHANMASK',      '477' : 'ERR_NEEDREGGEDNICK',
-	'479' : 'ERR_BADCHANNAME',      '480' : 'ERR_THROTTLE',
-	'485' : 'ERR_CHANBANREASON',    '488' : 'ERR_NOSSL',
-	'489' : 'ERR_SECUREONLYCHAN',   '519' : 'ERR_TOOMANYUSERS',
-	'520' : 'ERR_OPERONLY',         '926' : 'ERR_BADCHANNEL'
-}
-
-badserver = {
-	'install identd'                              : 'Identd required',
-	'trying to reconnect too fast'                : 'Throttled',
-	'your host is trying to (re)connect too fast' : 'Throttled',
-	'reconnecting too fast'                       : 'Throttled',
-	'access denied'                               : 'Access denied',
-	'not authorized to'                           : 'Not authorized',
-	'not authorised to'                           : 'Not authorized',
-	'password mismatch'                           : 'Password mismatch',
-	'dronebl'                                     : 'DroneBL',
-	'dnsbl'                                       : 'DNSBL',
-	'g:lined'                                     : 'G:Lined',
-	'z:lined'                                     : 'Z:Lined',
-	'timeout'                                     : 'Timeout',
-	'closing link'                                : 'Banned',
-	'banned'                                      : 'Banned',
-	'client exited'                               : 'QUIT',
-	'quit'                                        : 'QUIT'
-}
+class bad:
+	donotscan = (
+		'irc.dronebl.org',       'irc.alphachat.net',
+		'5.9.164.48',            '45.32.74.177',          '104.238.146.46',               '149.248.55.130',
+		'2001:19f0:6001:1dc::1', '2001:19f0:b001:ce3::1', '2a01:4f8:160:2501:48:164:9:5', '2001:19f0:6401:17c::1'
+	)
+	chan = {
+		'403' : 'ERR_NOSUCHCHANNEL',    '405' : 'ERR_TOOMANYCHANNELS',
+		'435' : 'ERR_BANONCHAN',        '442' : 'ERR_NOTONCHANNEL',
+		'448' : 'ERR_FORBIDDENCHANNEL', '470' : 'ERR_LINKCHANNEL',
+		'471' : 'ERR_CHANNELISFULL',    '473' : 'ERR_INVITEONLYCHAN',
+		'474' : 'ERR_BANNEDFROMCHAN',   '475' : 'ERR_BADCHANNELKEY',
+		'476' : 'ERR_BADCHANMASK',      '477' : 'ERR_NEEDREGGEDNICK',
+		'479' : 'ERR_BADCHANNAME',      '480' : 'ERR_THROTTLE',
+		'485' : 'ERR_CHANBANREASON',    '488' : 'ERR_NOSSL',
+		'489' : 'ERR_SECUREONLYCHAN',   '519' : 'ERR_TOOMANYUSERS',
+		'520' : 'ERR_OPERONLY',         '926' : 'ERR_BADCHANNEL'
+	}
+	error = {
+		'install identd'                 : 'Identd required',
+		'trying to reconnect too fast'   : 'Throttled',
+		'trying to (re)connect too fast' : 'Throttled',
+		'reconnecting too fast'          : 'Throttled',
+		'access denied'                  : 'Access denied',
+		'not authorized to'              : 'Not authorized',
+		'not authorised to'              : 'Not authorized',
+		'password mismatch'              : 'Password mismatch',
+		'dronebl'                        : 'DroneBL',
+		'dnsbl'                          : 'DNSBL',
+		'g:lined'                        : 'G:Lined',
+		'z:lined'                        : 'Z:Lined',
+		'timeout'                        : 'Timeout',
+		'closing link'                   : 'Banned',
+		'banned'                         : 'Banned',
+		'client exited'                  : 'QUIT',
+		'quit'                           : 'QUIT'
+	}
 
 def backup(name):
 	try:
@@ -106,7 +106,8 @@ def ssl_ctx():
 class probe:
 	def __init__(self, semaphore, server, port, family=2):
 		self.server    = server
-		self.port      = port
+		self.port      = 6697
+		self.oport     = port
 		self.display   = server.ljust(18)+' \033[30m|\033[0m unknown network           \033[30m|\033[0m '
 		self.semaphore = semaphore
 		self.nickname  = None
@@ -122,15 +123,40 @@ class probe:
 	async def run(self):
 		async with self.semaphore:
 			try:
-				await self.connect()
+				await self.connect() # 6697
 			except Exception as ex:
 				if settings.errors_conn:
-					error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS', ex)
-				try:
-					await self.connect(True)
-				except Exception as ex:
-					if settings.errors_conn:
-						error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect', ex)
+					error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS on port ' + str(self.port), ex)
+				if self.oport not in (6667,6697):
+					self.port = self.oport
+					await asyncio.sleep(throttle.connect)
+					try:
+						await self.connect() # Non-standard
+					except Exception as ex:
+						if settings.errors_conn:
+							error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS on port ' + str(self.port), ex)
+						self.port = 6667
+						await asyncio.sleep(throttle.connect)
+						try:
+							await self.connect(True) # 6667
+						except Exception as ex:
+							if settings.errors_conn:
+								error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
+							self.port = self.oport
+							await asyncio.sleep(throttle.connect)
+							try:
+								await self.connect(True) # Non-standard
+							except Exception as ex:
+								if settings.errors_conn:
+									error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
+				else:
+					self.port = 6667
+					await asyncio.sleep(throttle.connect)
+					try:
+						await self.connect(True) # 6667
+					except Exception as ex:
+						if settings.errors_conn:
+							error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
 
 	async def raw(self, data):
 		self.writer.write(data[:510].encode('utf-8') + b'\r\n')
@@ -139,7 +165,7 @@ class probe:
 	async def connect(self, fallback=False):
 		options = {
 			'host'       : self.server,
-			'port'       : 6667 if fallback else 6697,
+			'port'       : self.port,
 			'limit'      : 1024,
 			'ssl'        : None if fallback else ssl_ctx(),
 			'family'     : self.family,
@@ -152,7 +178,7 @@ class probe:
 		}
 		self.nickname = identity['nick']
 		self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), throttle.timeout)
-		self.snapshot['port'] = option['ports']
+		self.snapshot['port'] = option['port']
 		del options
 		if not fallback:
 			self.snapshot['ssl'] = True
@@ -267,17 +293,17 @@ class probe:
 					self.snapshot['RAW'] = self.snapshot['RAW']+[line,] if 'RAW' in self.snapshot else [line,]
 				else:
 					self.snapshot[event] = self.snapshot[event]+[line,] if event in self.snapshot else [line,]
-				if event in badchan and len(args) >= 4:
+				if event in bad.chan and len(args) >= 4:
 					chan = args[3]
 					if chan in self.channels['users']:
 						del self.channels['users'][chan]
-					error(f'{self.display}\033[31merror\033[0m - {chan}', badchan[event])
+					error(f'{self.display}\033[31merror\033[0m - {chan}', bad.chan[event])
 				elif line.startswith('ERROR :'):
-					check = [check for check in badserver if check in line.lower()]
+					check = [check for check in bad.error if check in line.lower()]
 					if check:
 						if check[0] in ('dronebl','dnsbl'):
 							self.snapshot['proxy'] = True
-						raise Exception(badserver[check[0]])
+						raise Exception(bad.error[check[0]])
 				elif args[0] == 'PING':
 					await self.raw('PONG ' + args[1][1:])
 				elif event == '001': #RPL_WELCOME
@@ -350,11 +376,11 @@ class probe:
 							self.jthrottle = throttle.seconds if seconds > throttle.seconds else seconds
 					error(self.display + '\033[31merror\033[0m - delay found', msg)
 				elif event == '465': # ERR_YOUREBANNEDCREEP
-					check = [check for check in badserver if check in line.lower()]
+					check = [check for check in bad.error if check in line.lower()]
 					if check:
 						if check[0] in ('dronebl','dnsbl'):
 							self.snapshot['proxy'] = True
-						raise Exception(badserver[check[0]])
+						raise Exception(bad.error[check[0]])
 				elif event == '464': # ERR_PASSWDMISMATCH
 					raise Exception('Network has a password')
 				elif event == '487': # ERR_MSGSERVICES
@@ -418,7 +444,10 @@ async def main(targets):
 	jobs = list()
 	for target in targets:
 		server = ':'.join(target.split(':')[-1:])
-		port   = ':'.join(target.split(':')[:-1])
+		if ':' not in target: # TODO: IPv6 addresses without a port wont get :6667 appeneded to it like this
+			port = 6697
+		else:
+			port  = int(':'.join(target.split(':')[:-1]))
 		try:
 			ipaddress.IPv4Address(server)
 			jobs.append(asyncio.ensure_future(probe(sema, server, port, 2).run()))
@@ -427,7 +456,7 @@ async def main(targets):
 				ipaddress.IPv6Address(server)
 				jobs.append(asyncio.ensure_future(probe(sema, server, port, 10).run()))
 			except:
-				error('failed to scan '+target, 'invalid ip address')
+				error('invalid ip address', server)
 	await asyncio.gather(*jobs)
 
 # Main
@@ -449,7 +478,7 @@ else:
 		os.mkdir('logs')
 	except FileExistsError:
 		pass
-	targets = [line.rstrip() for line in open(targets_file).readlines() if line and line not in donotscan]
+	targets = [line.rstrip() for line in open(targets_file).readlines() if line and line not in bad.donotscan]
 	found   = len(targets)
 	debug(f'loaded {found:,} targets')
 	if settings.daemon: