cover.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #!/usr/bin/python3
  2. # Probably not very useful yet. Just an idea.
  3. # The idea is that your tor client should not sit idle when you're not active, immediately betraying that you're not active. It's not meant to be used all the time & use up resources of the network, but could be handy sometimes.
  4. # It's 3 tasks. If server fails, then program should exit. If server fails with errno 98, then switch to a different port. Set up hidden service only when that port is known. Only client should wait for publication of service.
  5. # Once operational, the program sends random amounts of random traffic to the hidden service at random intervals. Feel free to implement more realistic-looking protocols.
  6. # dependencies
  7. # PySocks
  8. # stem
  9. # pycrypto
  10. import time
  11. import sys
  12. import stem
  13. from stem import connection
  14. from stem.control import Controller
  15. import Crypto
  16. from Crypto import Random
  17. import signal
  18. import multiprocessing
  19. from multiprocessing import Process, Queue
  20. # config settings
  21. CONTROL_PORT = 9151
  22. SOCKS_PORT = 9150
  23. DEFAULT_MEANWAIT = 2.1
  24. DEFAULT_MEANBYTES = 16384
  25. import socket
  26. import socks
  27. import math
  28. import re
  29. import getopt
  30. socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', SOCKS_PORT)
  31. extport = 80 # set any port for the hidden service
  32. # hidden service is public.
  33. # any good? won't they be able to fit the distribution?
  34. class Exp:
  35. def __init__(self, lambd = 1.0):
  36. self.l = lambd
  37. def cdf(self, x):
  38. return (lambda x: 1 - math.exp(- self.l * x))(x)
  39. def sample(self, randomstream, rprecisionbytes = 2, sprecision = 16):
  40. r = randomstream.read(rprecisionbytes) #
  41. val = 0.0
  42. num = 1
  43. while r != b'':
  44. byte = r[0] # python3
  45. r = r[1:]
  46. for i in range(8):
  47. bit = (byte & (1 << i)) >> i
  48. con = bit * 1.0 / (1 << num)
  49. val += con
  50. num += 1
  51. lower = 0.0
  52. upper = None
  53. point = 1.0
  54. for i in range(sprecision):
  55. if self.cdf(point) == val:
  56. return point
  57. if self.cdf(point) > val:
  58. upper = point
  59. point = (upper + lower) / 2.0
  60. if self.cdf(point) < val:
  61. if not upper:
  62. point *= 2
  63. else:
  64. lower = point
  65. point = (upper + lower) / 2.0
  66. return point
  67. def handler(signal, frame):
  68. print ("Exiting thread")
  69. sys.exit(0)
  70. class CoverSender:
  71. def __init__(self, onion):
  72. self.r = Random.new()
  73. self.s = socks.socksocket()
  74. self.address = onion
  75. def run(self, meanwait = DEFAULT_MEANWAIT, meanbytes = DEFAULT_MEANBYTES
  76. ):
  77. """
  78. To be run as a thread / process
  79. Keyword args:
  80. meanwait -- mean waiting time between bursts (in seconds)
  81. meanbytes -- mean #bytes to send per burst
  82. """
  83. signal.signal(signal.SIGINT, handler)
  84. Random.atfork()
  85. # distribution parameters
  86. waitdistr = Exp(1.0 / meanwait) # waiting time
  87. lendistr = Exp(1.0 / meanbytes) # message length
  88. while True:
  89. try:
  90. self.s = socks.socksocket()
  91. self.s.setproxy(socks.PROXY_TYPE_SOCKS5,
  92. "127.0.0.1", SOCKS_PORT, True)
  93. print (" c Connect to %s:%d ..." % (self.address, extport))
  94. self.s.connect((self.address, extport))
  95. msglen = int(math.floor(lendistr.sample(self.r)))
  96. msg = self.r.read(msglen)
  97. print (" c Attempting to send %d bytes ..." % msglen)
  98. numsent = 0
  99. while numsent < msglen:
  100. numsent += self.s.send(msg[numsent:])
  101. print (" c Done, closing socket")
  102. self.s.close()
  103. except Exception as e:
  104. print (" c Client error: %s" % e)
  105. pausea = waitdistr.sample(self.r)
  106. print (" c Sleep %.2fs ..." % pausea)
  107. time.sleep(pausea)
  108. class CoverServer:
  109. def __init__(self, q):
  110. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  111. self.outputq = q
  112. def run(self):
  113. signal.signal(signal.SIGINT, handler)
  114. s = self.socket
  115. while True:
  116. try:
  117. s.bind(('127.0.0.1', 0))
  118. break
  119. except Exception as e:
  120. # this should'nt happen at all for port chosen by OS
  121. if ("%s" % e)[:10] == "[Errno 98]":
  122. f = 1.0
  123. print (" s Bind failure: %s (retry %d)" % (e, f))
  124. time.sleep(f)
  125. else:
  126. print (" s Bind failure: %s" % e)
  127. raise e
  128. self.outputq.put({"myport":s.getsockname()[1]})
  129. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  130. s.listen(600)
  131. while True:
  132. try:
  133. c, addr = s.accept()
  134. print (' s Got connection')
  135. # ... all in the same thread ...
  136. while True:
  137. x = c.recv(1024)
  138. print (" s Recvd bytes %d" % len(x))
  139. # pay attention to str != bytes
  140. if x == b'':
  141. print (' s End of transmission') # for whatever reason
  142. break
  143. c.close()
  144. except Exception as e:
  145. print (" s Server error: %s" % e)
  146. class StemSetup:
  147. def __init__(self, outq, inq):
  148. self.outputq = outq
  149. self.inputq = inq
  150. def run(self, myport, await = True):
  151. signal.signal(signal.SIGINT, handler)
  152. print(' * Connecting to tor controller')
  153. with Controller.from_port(port=CONTROL_PORT) as controller:
  154. try:
  155. controller.authenticate()
  156. print(' * Authenticated, starting hs')
  157. response = controller.create_ephemeral_hidden_service({extport: myport}, await_publication = await)
  158. if await:
  159. print(" * Published service %s.onion" % response.service_id)
  160. else:
  161. print(" * Created service %s.onion" % response.service_id)
  162. address = "%s.onion" % response.service_id
  163. self.outputq.put({"onion":address})
  164. while True:
  165. item = self.inputq.get()
  166. if item == "quit":
  167. print (" * Quit hs controller thread")
  168. return
  169. except stem.connection.MissingPassword:
  170. pw = getpass.getpass("Controller password: ")
  171. try:
  172. controller.authenticate(password = pw)
  173. except stem.connection.PasswordAuthFailed:
  174. print("Unable to authenticate, password is incorrect")
  175. sys.exit(1)
  176. except stem.connection.AuthenticationFailure as exc:
  177. print("Unable to authenticate: %s" % exc)
  178. sys.exit(1)
  179. def main(options, meanwait=DEFAULT_MEANWAIT, meanbytes=DEFAULT_MEANBYTES):
  180. o = options
  181. commq3 = None
  182. onion = ''
  183. processes1 = []
  184. processes2 = [] # exit only after processes1 quit
  185. if 'remote' in o.keys():
  186. onion = o['remote']
  187. if o['server']:
  188. commq1 = Queue()
  189. cS = CoverServer(commq1)
  190. pS = Process(target=CoverServer.run, args=(cS,))
  191. print(" * Starting server listener")
  192. pS.start()
  193. myport = commq1.get()['myport']
  194. print(" * Server up at localport %d" % myport)
  195. commq2 = Queue()
  196. commq3 = Queue() # used to signal end of work
  197. cstem = StemSetup(commq2, commq3)
  198. pstem = Process(target=StemSetup.run, args=(cstem, myport, options['await']))
  199. pstem.start()
  200. onion = commq2.get()['onion']
  201. processes2.append(pstem)
  202. processes1.append(pS)
  203. if o['client'] and len(onion):
  204. print(" * Init cover traffic sender (client to %s)" % onion)
  205. cs = CoverSender(onion)
  206. ps = Process(target=CoverSender.run, args=(cs, meanwait, meanbytes))
  207. print(" * Starting cover traffic sender")
  208. ps.start()
  209. processes1.append(ps)
  210. if o['client'] and not len(onion):
  211. print(" * Won't start cover traffic sender, no service given")
  212. for p1 in processes1:
  213. p1.join()
  214. if o['server']:
  215. commq3.put("quit")
  216. for p in processes2:
  217. p.join()
  218. def usage():
  219. print ("You're using it wrong, see source code")
  220. if __name__ == "__main__":
  221. try:
  222. opts, args = getopt.getopt(sys.argv[1:],
  223. 'w:b:hsaAc:',
  224. ['meanwait=', "meanbytes=", 'help', 'server', "autoclient", "await", 'client=' ])
  225. except getopt.GetoptError as e:
  226. print (e)
  227. usage()
  228. sys.exit(2)
  229. meanwait = DEFAULT_MEANWAIT
  230. meanbytes = DEFAULT_MEANBYTES
  231. o = {'await':False, 'server':False, 'client':False}
  232. for opt, arg in opts:
  233. # print (opt, arg)
  234. if opt in ('-h', '--help'):
  235. usage()
  236. sys.exit(2)
  237. if opt in ('-s', '--server'):
  238. o['server'] = True
  239. if opt in ('-a', '--autoclient'):
  240. o['client'] = True
  241. if opt in ('-A', '--await'):
  242. o['await'] = True
  243. if opt in ('-c', '--client'):
  244. o['client'] = True
  245. o['remote'] = arg
  246. if not re.match("\.onion$", o['remote']):
  247. o['remote'] += '.onion'
  248. if opt in ('-b', '--meanbytes'):
  249. try:
  250. meanbytes = int(arg)
  251. except Exception as e:
  252. usage()
  253. sys.exit(2)
  254. if opt in ('-w', '--meanwait'):
  255. try:
  256. meanwait = float(arg)
  257. except Exception as e:
  258. usage()
  259. sys.exit(2)
  260. try:
  261. main(o, meanwait=meanwait, meanbytes=meanbytes)
  262. except KeyboardInterrupt as e:
  263. print ("Got ctrl-c, exiting more or less gracefully")