mirror of
git://git.acid.vegas/IRCP.git
synced 2024-12-27 08:56:37 +00:00
Added support for IPv6 & non-standard ports
This commit is contained in:
parent
3f66438ebe
commit
2b5efa8bf0
50
README.md
50
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)
|
||||
|
143
ircp.py
143
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:
|
||||
|
Loading…
Reference in New Issue
Block a user