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:
| |