1
mirror of git://git.acid.vegas/IRCP.git synced 2024-11-23 00:16:41 +00:00

Added support for IPv6 & non-standard ports

This commit is contained in:
Dionysus 2023-06-06 01:17:01 -04:00
parent 3f66438ebe
commit 2b5efa8bf0
Signed by: acidvegas
GPG Key ID: EF4B922DB85DC9DE
2 changed files with 99 additions and 94 deletions

View File

@ -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. 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 ## 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. 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. 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 ## Preview
![](.screens/preview.png) ![](.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 * Create a seperate log for failed connections
* Ability to link multiple IRCP instances running in daemon mode together for balancing * Ability to link multiple IRCP instances running in daemon mode together for balancing
* Remote syncing the logs to another server * 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 * 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 nick registered *(most likely through MODE +r)* *(Log nick & password)*
* Confirm SSL/TLS connections *(most likely through "You are connected using SSL cipher" NOTICE message)* * 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 ## Mirrors
- [acid.vegas](https://git.acid.vegas/ircp) - [acid.vegas](https://git.acid.vegas/ircp)

71
ircp.py
View File

@ -24,6 +24,7 @@ class settings:
class throttle: class throttle:
channels = 3 if not settings.daemon else 2 # Maximum number of channels to scan at once 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 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 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 nick = 300 if not settings.daemon else 600 # Delay between every random NICK change
@ -34,13 +35,13 @@ class throttle:
whois = 5 if not settings.daemon else 15 # Delay between WHOIS requests 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 ztimeout = 200 if not settings.daemon else 300 # Timeout for zero data from server
class bad:
donotscan = ( donotscan = (
'irc.dronebl.org', 'irc.alphachat.net', 'irc.dronebl.org', 'irc.alphachat.net',
'5.9.164.48', '45.32.74.177', '104.238.146.46', '149.248.55.130', '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' '2001:19f0:6001:1dc::1', '2001:19f0:b001:ce3::1', '2a01:4f8:160:2501:48:164:9:5', '2001:19f0:6401:17c::1'
) )
chan = {
badchan = {
'403' : 'ERR_NOSUCHCHANNEL', '405' : 'ERR_TOOMANYCHANNELS', '403' : 'ERR_NOSUCHCHANNEL', '405' : 'ERR_TOOMANYCHANNELS',
'435' : 'ERR_BANONCHAN', '442' : 'ERR_NOTONCHANNEL', '435' : 'ERR_BANONCHAN', '442' : 'ERR_NOTONCHANNEL',
'448' : 'ERR_FORBIDDENCHANNEL', '470' : 'ERR_LINKCHANNEL', '448' : 'ERR_FORBIDDENCHANNEL', '470' : 'ERR_LINKCHANNEL',
@ -52,11 +53,10 @@ badchan = {
'489' : 'ERR_SECUREONLYCHAN', '519' : 'ERR_TOOMANYUSERS', '489' : 'ERR_SECUREONLYCHAN', '519' : 'ERR_TOOMANYUSERS',
'520' : 'ERR_OPERONLY', '926' : 'ERR_BADCHANNEL' '520' : 'ERR_OPERONLY', '926' : 'ERR_BADCHANNEL'
} }
error = {
badserver = {
'install identd' : 'Identd required', 'install identd' : 'Identd required',
'trying to reconnect too fast' : 'Throttled', 'trying to reconnect too fast' : 'Throttled',
'your host is trying to (re)connect too fast' : 'Throttled', 'trying to (re)connect too fast' : 'Throttled',
'reconnecting too fast' : 'Throttled', 'reconnecting too fast' : 'Throttled',
'access denied' : 'Access denied', 'access denied' : 'Access denied',
'not authorized to' : 'Not authorized', 'not authorized to' : 'Not authorized',
@ -106,7 +106,8 @@ def ssl_ctx():
class probe: class probe:
def __init__(self, semaphore, server, port, family=2): def __init__(self, semaphore, server, port, family=2):
self.server = server 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.display = server.ljust(18)+' \033[30m|\033[0m unknown network \033[30m|\033[0m '
self.semaphore = semaphore self.semaphore = semaphore
self.nickname = None self.nickname = None
@ -122,15 +123,40 @@ class probe:
async def run(self): async def run(self):
async with self.semaphore: async with self.semaphore:
try: try:
await self.connect() await self.connect() # 6697
except Exception as ex: except Exception as ex:
if settings.errors_conn: if settings.errors_conn:
error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS', 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: try:
await self.connect(True) await self.connect() # Non-standard
except Exception as ex: except Exception as ex:
if settings.errors_conn: 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)
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): async def raw(self, data):
self.writer.write(data[:510].encode('utf-8') + b'\r\n') self.writer.write(data[:510].encode('utf-8') + b'\r\n')
@ -139,7 +165,7 @@ class probe:
async def connect(self, fallback=False): async def connect(self, fallback=False):
options = { options = {
'host' : self.server, 'host' : self.server,
'port' : 6667 if fallback else 6697, 'port' : self.port,
'limit' : 1024, 'limit' : 1024,
'ssl' : None if fallback else ssl_ctx(), 'ssl' : None if fallback else ssl_ctx(),
'family' : self.family, 'family' : self.family,
@ -152,7 +178,7 @@ class probe:
} }
self.nickname = identity['nick'] self.nickname = identity['nick']
self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), throttle.timeout) 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 del options
if not fallback: if not fallback:
self.snapshot['ssl'] = True self.snapshot['ssl'] = True
@ -267,17 +293,17 @@ class probe:
self.snapshot['RAW'] = self.snapshot['RAW']+[line,] if 'RAW' in self.snapshot else [line,] self.snapshot['RAW'] = self.snapshot['RAW']+[line,] if 'RAW' in self.snapshot else [line,]
else: else:
self.snapshot[event] = self.snapshot[event]+[line,] if event in self.snapshot else [line,] 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] chan = args[3]
if chan in self.channels['users']: if chan in self.channels['users']:
del self.channels['users'][chan] 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 :'): 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:
if check[0] in ('dronebl','dnsbl'): if check[0] in ('dronebl','dnsbl'):
self.snapshot['proxy'] = True self.snapshot['proxy'] = True
raise Exception(badserver[check[0]]) raise Exception(bad.error[check[0]])
elif args[0] == 'PING': elif args[0] == 'PING':
await self.raw('PONG ' + args[1][1:]) await self.raw('PONG ' + args[1][1:])
elif event == '001': #RPL_WELCOME elif event == '001': #RPL_WELCOME
@ -350,11 +376,11 @@ class probe:
self.jthrottle = throttle.seconds if seconds > throttle.seconds else seconds self.jthrottle = throttle.seconds if seconds > throttle.seconds else seconds
error(self.display + '\033[31merror\033[0m - delay found', msg) error(self.display + '\033[31merror\033[0m - delay found', msg)
elif event == '465': # ERR_YOUREBANNEDCREEP 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:
if check[0] in ('dronebl','dnsbl'): if check[0] in ('dronebl','dnsbl'):
self.snapshot['proxy'] = True self.snapshot['proxy'] = True
raise Exception(badserver[check[0]]) raise Exception(bad.error[check[0]])
elif event == '464': # ERR_PASSWDMISMATCH elif event == '464': # ERR_PASSWDMISMATCH
raise Exception('Network has a password') raise Exception('Network has a password')
elif event == '487': # ERR_MSGSERVICES elif event == '487': # ERR_MSGSERVICES
@ -418,7 +444,10 @@ async def main(targets):
jobs = list() jobs = list()
for target in targets: for target in targets:
server = ':'.join(target.split(':')[-1:]) 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: try:
ipaddress.IPv4Address(server) ipaddress.IPv4Address(server)
jobs.append(asyncio.ensure_future(probe(sema, server, port, 2).run())) jobs.append(asyncio.ensure_future(probe(sema, server, port, 2).run()))
@ -427,7 +456,7 @@ async def main(targets):
ipaddress.IPv6Address(server) ipaddress.IPv6Address(server)
jobs.append(asyncio.ensure_future(probe(sema, server, port, 10).run())) jobs.append(asyncio.ensure_future(probe(sema, server, port, 10).run()))
except: except:
error('failed to scan '+target, 'invalid ip address') error('invalid ip address', server)
await asyncio.gather(*jobs) await asyncio.gather(*jobs)
# Main # Main
@ -449,7 +478,7 @@ else:
os.mkdir('logs') os.mkdir('logs')
except FileExistsError: except FileExistsError:
pass 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) found = len(targets)
debug(f'loaded {found:,} targets') debug(f'loaded {found:,} targets')
if settings.daemon: if settings.daemon: