#!/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:])