mirror of
git://git.acid.vegas/IRCP.git
synced 2024-11-26 09:56:42 +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.
|
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)
|
||||||
|
79
ircp.py
79
ircp.py
@ -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
|
||||||
|
|
||||||
donotscan = (
|
class bad:
|
||||||
|
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',
|
||||||
@ -51,12 +52,11 @@ badchan = {
|
|||||||
'485' : 'ERR_CHANBANREASON', '488' : 'ERR_NOSSL',
|
'485' : 'ERR_CHANBANREASON', '488' : 'ERR_NOSSL',
|
||||||
'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',
|
||||||
@ -71,7 +71,7 @@ badserver = {
|
|||||||
'banned' : 'Banned',
|
'banned' : 'Banned',
|
||||||
'client exited' : 'QUIT',
|
'client exited' : 'QUIT',
|
||||||
'quit' : 'QUIT'
|
'quit' : 'QUIT'
|
||||||
}
|
}
|
||||||
|
|
||||||
def backup(name):
|
def backup(name):
|
||||||
try:
|
try:
|
||||||
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user