[Assignment-4] added tcpproxy

This commit is contained in:
Sascha Tommasone 2024-05-25 10:25:40 +02:00
parent ac334954e6
commit 4c97dbb963
Signed by: saschato
GPG key ID: 751068A86FCAA217
20 changed files with 1443 additions and 0 deletions

View file

@ -0,0 +1,216 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

View file

@ -0,0 +1,2 @@
# Systemsicherheit

View file

@ -0,0 +1,83 @@
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIJALovM7ADVGykMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwMTI2MTEyMTE1WhcNMjgwMTI0MTEyMTE1WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAusz4JoOtp7PTNhCrkmBL5niiqnQgnODBwp/cd6ZzQdmxY9gRrZ/JOoDg
gxaJ0fLpF+S+XCIb6H3Hw+zmks15uZVg/EEEgc9cKvwrQw+7z5szIlGQ+OvXvHgO
ijDPE3pOeMss+/fm7zrfZPy3V9tROym/gVlhduyqCy+gLhQpdJ5Q6Qp20uLUdknK
6siO9ovXLggZ7GbFdscV1tkDMx7WFVXl2hYWL3Hw0fQ/yFBpORIBuRG+HizgYnEq
BQaZL66TdZ4MIH35PW/2Ox9q+szjTV4ATxnEZgJSn/xkb9OrRWcPPc+DUDRwNLvF
f5tJbsn3W9pZibzr6vAGhTsH0EY0fj9unJex4QWnS8C2dWiudJRuh1+FiK3R1mG9
JLuVctRrbCApsp0XrquQD68Ts7NF6w6wNqXhB4mNFujNm3AFbhF4mByU39UL7AG2
iiNoV7ydJmXvhoERcxVFzz/mNq5kDUoM79VgIuqyxz1CRnEx0LWIvqpReme2ElcW
WuB0oZKY/IPb1haoouBzBJTu6W9sYxABBM0pohUz/snZ/dfBu/XFhrhR80gtVjh8
Q5OFne2lS7hs/Qz4FZkY27VGctzMsOy17vqdxwBSMnKy6Xnkanvau5PzShiEeoiC
dJvG19nKH07Jg8sQRaHCaoFWXjExgeDo4qHF2ODWXAfBXUpRhMUCAwEAAaNTMFEw
HQYDVR0OBBYEFH/7mpljxuqRaro1y9gXEIKFNz8CMB8GA1UdIwQYMBaAFH/7mplj
xuqRaro1y9gXEIKFNz8CMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggIBAFQo/ZwtS3pno8pcPKooMBcvy+KyzFfwvQgtg65O4ltSmXKjfBKeB9IBasG1
irHINcHMNK3u1C9gO/uufKiNOq5p5vxgU0EumaetXVh/ZmOxrgt5FLkmwxXkwaq7
wrfQvO9Z4skhTNQ3SIOQwqSDtVKUJSHnaKlUgF/lZyFh1FW+DehWsWK0bdgDtdFh
f+Tfj9hBKaZSqnP0vv1x4tTL17bPTarrHMsEZWmEOtOv4/MNuUAhMzrcJkcpoQtl
GVMT2axVAjqATL9Liwy0UvRJIbK0nn8uO2R+8KGy2wdtCwHsrTq0Nq7JIcYlDClY
1MIUPGKMXFUlM84DsSzDItjCTL9Ugf1Nunruumdpo/+Sv3VVeOp1IX/nP44Bp7XU
gqpUvi7qF2n5o1OdXJmxfuTb8Qs1zB8SDPmhpsuJ9E/Ch1v4KUa2SJOhGSBPf02n
dj9zYXuloyRKMuPUFbnTxOI9YIxyfNUZT32D3s4k6MQP3rz2At6wfOVR/SQvbk+e
+IAMnxVWv34RkJzCBB4opE867T33XdpjzSbSj7qiFMC7szxdmE5rpKa6nZuEGz8q
HtkDWipeaRG9HAxOX/NJlac1aP8hQxJ9cIQwVSY2KqAFHIE5MtSpH4XXuoXOvkzU
NEAjtiKuJ8khbl+FrGZ7V3VbNZbzb5hHYcfXgb3LuiwehQ1E
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC6zPgmg62ns9M2
EKuSYEvmeKKqdCCc4MHCn9x3pnNB2bFj2BGtn8k6gOCDFonR8ukX5L5cIhvofcfD
7OaSzXm5lWD8QQSBz1wq/CtDD7vPmzMiUZD469e8eA6KMM8Tek54yyz79+bvOt9k
/LdX21E7Kb+BWWF27KoLL6AuFCl0nlDpCnbS4tR2ScrqyI72i9cuCBnsZsV2xxXW
2QMzHtYVVeXaFhYvcfDR9D/IUGk5EgG5Eb4eLOBicSoFBpkvrpN1ngwgffk9b/Y7
H2r6zONNXgBPGcRmAlKf/GRv06tFZw89z4NQNHA0u8V/m0luyfdb2lmJvOvq8AaF
OwfQRjR+P26cl7HhBadLwLZ1aK50lG6HX4WIrdHWYb0ku5Vy1GtsICmynReuq5AP
rxOzs0XrDrA2peEHiY0W6M2bcAVuEXiYHJTf1QvsAbaKI2hXvJ0mZe+GgRFzFUXP
P+Y2rmQNSgzv1WAi6rLHPUJGcTHQtYi+qlF6Z7YSVxZa4HShkpj8g9vWFqii4HME
lO7pb2xjEAEEzSmiFTP+ydn918G79cWGuFHzSC1WOHxDk4Wd7aVLuGz9DPgVmRjb
tUZy3Myw7LXu+p3HAFIycrLpeeRqe9q7k/NKGIR6iIJ0m8bX2cofTsmDyxBFocJq
gVZeMTGB4OjiocXY4NZcB8FdSlGExQIDAQABAoICAG8jpEDF93vfsbppEKt2P7JP
8/gWP5EW6DEzi6hkkA6NxszwsRPsDX2RUAKuVjFjpOtiXR/T62bX7xLS0BxnxBR2
m815oYTaKqwofFTZ95P9ct7oSKjRKPopM/1kLNAZ5LZZq9n+FJghHuimsy7CfgIF
RLtgwmxPQpyFKXhA5qlLyDfe0fOGoYH/RYuK6AQoD06D42iTfMi+im/Zjd3MavMm
uCqZGXoBAJbqC0jTDse1vvCtbb/mU1o+mhGDa4DDDVjdP7nVOYUkKAvlFXFClbpi
QyzM190ZZK9rKxadiTkxqA/OdwIxMNEvJsJVUctovpMXxk386SBOzpJWHL/+BRxT
Jw66ue13U5BKpcdXiFOz0WNlsFA3E1iv0govMexwBiyIrUts7bS1kKVtWqNpbAq9
7xLjnT/tqu/N+52gIIpcSbN/rFFsJ0fT42ZmHj/ZKlzvz1ID0TDoXuEqwD7ObvH7
yWOePWOfr/9PHUguhLMNxXVeOHcPWhW/iPcdOr2nJS8ugDUvms0GnKXUeb+oH6ei
6cBTosOwlnFy2az9CxDo/3yw1zoiYpxNkMrKvOZ5wW0Lq3xdJgfdKNRANjdMLKPy
Zhfk92FpQCFOc1l8Dymgq4j7EI/0QIl1ziQ1s9j4Zus2h8kp+SRjEtV44s75cY3M
EFlF6KR5jXhZRqfaSufBAoIBAQDqgeL59Icx2AAtQVRxakESSjY6CBWSsd4OD4p0
OqRj26apETgf/9vv9wsK+A5DtNU16YS93Z/H+i227uh6KUeIAmkO+oWgif9xAQ4Z
ovUHEwCy+dFZuDchJVW+uO3sZfn+oxjHCE2F1aGknLN8ADEwf5/CyY+yzyigWXC2
m5irjUfcGFuh4WGO4cz0INHDnC6KeTBQ/il5Yg6JPVsNeXiunz344JKHglbceZHq
jQyXG5GtafciT0mwAaDdcT7HQ/YvVNl9fA0CNxCJAFioN9rtXvKNejFfDZvrRXbD
ApNdXxyqiaYsj4oFsaWu9aZjnE9g6NpCqfi+2fddyclbh+RLAoIBAQDL68RsoDLz
od1kq/NJuwp10WMrCH5MKJXgedqPO4fws7hXhFXCkwhj9AWZf2+f/0Cj+l9tNlR1
+T5UWv+sO+J8uWpX31x15Q//dcIlrt3GmGTEAIP4lN62x9tzTSfi/fezpo+tzFGU
N2OUd57bDry04Zo0pliI4TT4MNfYNsU9YDolZ27MEpiagvRJF+nbuCajdb2aFoTF
qtj515GEsCr8P5AtgbF4hZv7zm4/xKqcV637TcOTPo+XrLPnNL9BheRzJmZoVlA0
uGyBdcvcBFfHEfB6zXtv7ZaCnITOoRXeo0q4gP85AxtwANnBGH+7gt8bsTZXUqRY
s+Xux5Ba3PEvAoIBAAkAu4oFDTuoozkZjPhdr+nX14Ua0lkzYub/Sb10kuMSh69t
7c2ssPDhdxcQttt6kcTkFiiD3aJ7xE2Fln86HnjmPspIa+Dh62CXPcdWLjn7TMeS
N6tOGy+2kzgjOV8d+x7/e/AILZG5xd7f9TQJfdnyzFtaCZ4/vbuKM32PM6lCX0Pf
24S3dltZ59hneiYcVN0UEfrKByWV0iEKrfgydaOekW6AkJ+LLXKBaEys5ZLXiBw0
OTyj9pw/M8HMmzBjN4xRoZfjr0wqeQQJc13h5xG912n/Cu4vQ5EgtZJ/AtFO2Xbi
mfKUACR/0XCKFb01PwblaZutktMg4xJCsOxGp0kCggEBAL86n38GVAGo30cTARk5
b7vA2fB3DIk63iId41nCh96visV3cjz/STUCl2W03eb6pZGgr3BpLJddXpgYpf7M
Qb6Y2iMBcWGVp4T211QjQhKEwqoTma65XImnriHYTvlNFMbCAacIHdCSiK2n566h
iVFO5x9Mh2YFW3kLxL4bzqeZ361H690v6y+qco9A/6tua72KIn2ndGcxqjvRbcMy
uXzH1tr17omJMhfXJAhk02G9z4gFCszANEQWTrcY/eniN7PMZOifWKO39vkIkF4J
LI+gQRXIMGNsOGLPiLOE2E9qbh3LyouaYFaOVaYA5XfgaH09mCoXc8tDGPLs7nBn
FT0CggEABDVHQn/QFsWgU3AP06sGNLv8PEqN6ydqiBbPuUZJAHDQ1Z+mi93I4F0m
s9qGRJYeUVnlhpAj8OYm4nNozxzNKdxAsLL3fQQ1XONdplxTGBJY/FsCt+o7ysLj
fDh9TBAI37D3KGQC5T1QqLlJNAcS0IKplEPRY3tJTDdW0G7GZofyD5CFKGsMx4qg
W4gEpsMlyGrXObCBGcL0OnzYOWv4pzPxhQ4ubYL2DT+/lW4+XLclWe76h1i03l00
5Qw+BY2Hj3ksco7qQvYesEoGibpDoJu91SAQejwRNGxWprT7Iu6teKNseTRQdnS2
s1vWHzIABKW/htxysnONHEUPla0d0A==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = os.path.splitext(os.path.basename(__file__))[0]
self.description = 'Find HTTP Digest Authentication and replace it with a Basic Auth'
self.verbose = verbose
self.realm = 'tcpproxy'
if options is not None:
if 'realm' in options.keys():
self.realm = bytes(options['realm'], 'ascii')
def detect_linebreak(self, data):
line = data.split(b'\n', 1)[0]
if line.endswith(b'\r'):
return b'\r\n'
else:
return b'\n'
def execute(self, data):
delimiter = self.detect_linebreak(data)
lines = data.split(delimiter)
for index, line in enumerate(lines):
if line.lower().startswith(b'www-authenticate: digest'):
lines[index] = b'WWW-Authenticate: Basic realm="%s"' % self.realm
return delimiter.join(lines)
def help(self):
h = '\trealm: use this instead of the default "tcpproxy"\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Print a hexdump of the received data'
self.incoming = incoming # incoming means module is on -im chain
self.len = 16
if options is not None:
if 'length' in options.keys():
self.len = int(options['length'])
def help(self):
return '\tlength: bytes per line (int)'
def execute(self, data):
# this is a pretty hex dumping function directly taken from
# http://code.activestate.com/recipes/142812-hex-dumper/
result = []
digits = 2
for i in range(0, len(data), self.len):
s = data[i:i + self.len]
hexa = ' '.join(['%0*X' % (digits, x) for x in s])
text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
result.append("%04X %-*s %s" % (i, self.len * (digits + 1), hexa, text))
print("\n".join(result))
return data
if __name__ == '__main__':
print ('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Prepend HTTP response header'
self.server = None
if options is not None:
if 'server' in options.keys():
self.server = bytes(options['server'], 'ascii')
# source will be set by the proxy thread later on
self.source = None
def execute(self, data):
if self.server is None:
self.server = bytes(self.source[0], 'ascii')
http = b"HTTP/1.1 200 OK\r\n"
http += b"Server: %s\r\n" % self.server
http += b"Connection: keep-alive\r\n"
http += b"Content-Length: %d\r\n" % len(data)
return http + b"\r\n" + data
def help(self):
h = '\tserver: remote source, used in response Server header\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Prepend HTTP header'
self.incoming = incoming # incoming means module is on -im chain
self.targethost = None
self.targetport = None
if options is not None:
if 'host' in options.keys():
self.targethost = bytes(options['host'], 'ascii')
if 'port' in options.keys():
self.targetport = bytes(options['port'], 'ascii')
# destination will be set by the proxy thread later on
self.destination = None
def execute(self, data):
if self.targethost is None:
self.targethost = bytes(self.destination[0], 'ascii')
if self.targetport is None:
self.targetport = bytes(str(self.destination[1]), 'ascii')
http = b"POST /to/%s/%s HTTP/1.1\r\n" % (self.targethost, self.targetport)
http += b"Host: %s\r\n" % self.targethost
http += b"Connection: keep-alive\r\n"
http += b"Content-Length: %d\r\n" % len(data)
return http + b"\r\n" + str(data)
def help(self):
h = '\thost: remote target, used in request URL and Host header\n'
h += '\tport: remote target port, used in request URL\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Remove HTTP header from data'
self.incoming = incoming # incoming means module is on -im chain
def detect_linebreak(self, data):
line = data.split(b'\n', 1)[0]
if line.endswith(b'\r'):
return b'\r\n' * 2
else:
return b'\n' * 2
def execute(self, data):
delimiter = self.detect_linebreak(data)
if delimiter in data:
data = data.split(delimiter, 1)[1]
return data
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# THIS MODULE DOES NOT WORK AND WILL BE REPLACED, DO NOT USE
import os.path as path
import platform
if 'java' in platform.system().lower():
import java.io as io
from com.thoughtworks.xstream import XStream
from java.lang import Exception
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
self.is_jython = 'java' in platform.system().lower()
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Serialization or deserialization of Java objects' if self.is_jython else \
'Serialization or deserialization of Java objects (needs jython)'
self.incoming = incoming # incoming means module is on -im chain
self.execute = self.error
if options is not None:
if 'mode' in options.keys():
if 'deserial' in options['mode']:
self.execute = self.deserial
elif 'serial' in options['mode']:
self.execute = self.serial
def help(self):
return '\tmode: [serial|deserial] select deserialization (to XML) or serialization (to Java object)'
def deserial(self, data):
if not self.is_jython:
print ('[!] This module can only be used in jython!')
return data
try:
# turn data into a Java object
bis = io.ByteArrayInputStream(data)
ois = io.ObjectInputStream(bis)
obj = ois.readObject()
# converting Java object to XML structure
xs = XStream()
xml = xs.toXML(obj)
return xml
except Exception as e:
print ('[!] Caught Exception. Could not convert.\n')
return data
def serial(self, data):
if not self.is_jython:
print ('[!] This module can only be used in jython!')
return data
try:
# Creating XStream object and creating Java object from XML structure
xs = XStream()
serial = xs.fromXML(data)
# writing created Java object to and serializing it with ObjectOutputStream
bos = io.ByteArrayOutputStream()
oos = io.ObjectOutputStream(bos)
oos.writeObject(serial)
# I had a problem with signed vs. unsigned bytes, hence the & 0xff
return "".join([chr(x & 0xff) for x in bos.toByteArray().tolist()])
except Exception as e:
print ('[!] Caught Exception. Could not convert.\n')
return data
def error(self, data):
print ('[!] Unknown mode. Please specify mode=[serial|deserial].')
return data
if __name__ == '__main__':
print ('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import os.path as path
import time
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Log data in the module chain. Use in addition to general logging (-l/--log).'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
# file: the file name, format is (in|out)-20160601-112233.13413
self.file = ('in-' if incoming else 'out-') + \
time.strftime('%Y%m%d-%H%M%S.') + str(time.time()).split('.')[1]
if options is not None:
if 'file' in options.keys():
self.file = options['file']
self.handle = None
def __del__(self):
if self.handle is not None:
self.handle.close()
def execute(self, data):
if self.handle is None:
self.handle = open(self.file, 'wb', 0) # unbuffered
print('Logging to file', self.file)
logentry = bytes(time.strftime('%Y%m%d-%H%M%S') + ' ' + str(time.time()) + '\n', 'ascii')
logentry += data
logentry += b'-' * 20 + b'\n'
self.handle.write(logentry)
return data
def help(self):
h = '\tfile: name of logfile'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os.path as path
import paho.mqtt.client as mqtt
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Publish the data to an MQTT server'
self.incoming = incoming # incoming means module is on -im chain
self.client_id = ''
self.username = None
self.password = None
self.server = None
self.port = 1883
self.topic = ''
self.hex = False
if options is not None:
if 'clientid' in options.keys():
self.client_id = options['clientid']
if 'server' in options.keys():
self.server = options['server']
if 'username' in options.keys():
self.username = options['username']
if 'password' in options.keys():
self.password = options['password']
if 'port' in options.keys():
try:
self.port = int(options['port'])
if self.port not in range(1, 65536):
raise ValueError
except ValueError:
print(f'port: invalid port {options["port"]}, using default {self.port}')
if 'topic' in options.keys():
self.topic = options['topic'].strip()
if 'hex' in options.keys():
try:
self.hex = bool(strtobool(options['hex']))
except ValueError:
print(f'hex: {options["hex"]} is not a bool value, falling back to default value {self.hex}.')
if self.server is not None:
self.mqtt = mqtt.Client(self.client_id)
if self.username is not None or self.password is not None:
self.mqtt.username_pw_set(self.username, self.password)
self.mqtt.connect(self.server, self.port)
else:
self.mqtt = None
def execute(self, data):
if self.mqtt is not None:
if self.hex is True:
self.mqtt.publish(self.topic, data.hex())
else:
self.mqtt.publish(self.topic, data)
return data
def help(self):
h = '\tserver: server to connect to, required\n'
h += ('\tclientid: what to use as client_id, default is empty\n'
'\tusername: username\n'
'\tpassword: password\n'
'\tport: port to connect to, default 1883\n'
'\ttopic: topic to publish to, default is empty\n'
'\thex: encode data as hex before sending it. AAAA becomes 41414141.')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Replace gzip in the list of accepted encodings ' \
'in a HTTP request with booo.'
self.incoming = incoming # incoming means module is on -im chain
# I chose to replace gzip instead of removing it to keep the parsing
# logic as simple as possible.
def execute(self, data):
try:
# split at \r\n\r\n to split the request into header and body
header, body = data.split(b'\r\n\r\n', 1)
except ValueError:
# no \r\n\r\n, so probably not HTTP, we can go now
return data
# now split the header string into its lines
headers = header.split(b'\r\n')
for h in headers:
if h.lower().startswith(b'accept-encoding:') and b'gzip' in h:
headers[headers.index(h)] = h.replace(b'gzip', b'booo')
break
return b'\r\n'.join(headers) + b'\r\n\r\n' + body
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
import os
import re
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = os.path.splitext(os.path.basename(__file__))[0]
self.description = 'Replace text on the fly by using regular expressions in a file or as module parameters'
self.verbose = verbose
self.search = None
self.replace = None
self.filename = None
self.separator = ':'
if options is not None:
if 'search' in options.keys():
self.search = bytes(options['search'], 'ascii')
if 'replace' in options.keys():
self.replace = bytes(options['replace'], 'ascii')
if 'file' in options.keys():
self.filename = options['file']
try:
open(self.filename)
except IOError as ioe:
print("Error opening %s: %s" % (self.filename, ioe.strerror))
self.filename = None
if 'separator' in options.keys():
self.separator = options['separator']
def execute(self, data):
pairs = [] # list of (search, replace) tuples
if self.search is not None and self.replace is not None:
pairs.append((self.search, self.replace))
if self.filename is not None:
for line in open(self.filename).readlines():
try:
search, replace = line.split(self.separator, 1)
pairs.append((bytes(search.strip(), 'ascii'), bytes(replace.strip(), 'ascii')))
except ValueError:
# line does not contain separator and will be ignored
pass
for search, replace in pairs:
# TODO: verbosity
data = re.sub(search, replace, data)
return data
def help(self):
h = '\tsearch: string or regular expression to search for\n'
h += ('\treplace: string the search string should be replaced with\n')
h += ('\tfile: file containing search:replace pairs, one per line\n')
h += ('\tseparator: define a custom search:replace separator in the file, e.g. search#replace\n')
h += ('\n\tUse at least file or search and replace (or both).\n')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Print the size of the data passed to the module'
self.verbose = verbose
self.source = None
self.destination = None
self.incoming = incoming
if options is not None:
if 'verbose' in options.keys():
self.verbose = bool(strtobool(options['verbose']))
def execute(self, data):
size = len(data)
msg = "Received %d bytes" % size
if self.verbose:
msg += " from %s:%d" % self.source
msg += " for %s:%d" % self.destination
print(msg)
return data
def help(self):
h = '\tverbose: override the global verbosity setting'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python2
import os.path as path
import time
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Change HTTP responses of a certain size to 404.'
self.incoming = incoming # incoming means module is on -im chain
self.size = 2392 # if a response has this value as content-length, it will become a 404
self.verbose = False
self.custom = False
self.rewriteall = False # will we block the first occurence?
self.firstfound = False # have we found the first occurence yet?
self.resetinterval = None # if we haven't found a fitting response in this many seconds, reset the state and set first to False again
self.timer = time.time()
if options is not None:
if 'size' in options.keys():
try:
self.size = int(options['size'])
except ValueError:
pass # use the default if you can't parse the parameter
if 'verbose' in options.keys():
self.verbose = bool(strtobool(options['verbose']))
if 'custom' in options.keys():
try:
with open(options['custom'], 'rb') as handle:
self.custom = handle.read()
except Exception:
print('Can\'t open custom error file, not using it.')
self.custom = False
if 'rewriteall' in options.keys():
self.rewriteall = bool(strtobool(options['rewriteall']))
if 'reset' in options.keys():
try:
self.resetinterval = float(options['reset'])
except ValueError:
pass # use the default if you can't parse the parameter
def execute(self, data):
contentlength = b'content-length: ' + bytes(str(self.size), 'ascii')
if data.startswith(b'HTTP/1.1 200 OK') and contentlength in data.lower():
if self.resetinterval is not None:
t = time.time()
if t - self.timer >= self.resetinterval:
if self.verbose:
print('Timer elapsed')
self.firstfound = False
self.timer = t
if self.rewriteall is False and self.firstfound is False:
# we have seen this response size for the first time and are not blocking the first one
self.firstfound = True
if self.verbose:
print('Letting this response through')
return data
if self.custom is not False:
data = self.custom
if self.verbose:
print('Replaced response with custom response')
else:
data = data.replace(b'200 OK', b'404 Not Found', 1)
if self.verbose:
print('Edited return code')
return data
def help(self):
h = '\tsize: if a response has this value as content-length, it will become a 404\n'
h += ('\tverbose: print a message if a string is replaced\n'
'\tcustom: path to a file containing a custom response, will replace the received response\n'
'\trewriteall: if set, it will rewrite all responses. Default is to let the first on through'
'\treset: number of seconds after which we will reset the state and will let the next response through.')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,28 @@
import os.path as path
import json
import socket
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Simply print the received data as text'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
def execute(self, data):
print(f"Incoming data: {data}")
### Work with data here ###
# data_json = json.loads(data)
# data_json["content"] = "Blablabla"
# data = json.dumps(data_json)
# print(f"Outgoing data: {data}")
# return data + "\n"
print(f"Outgoing data: {data}")
return data
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,46 @@
#!/usr/bin/env python3
import os.path as path
from codecs import decode, lookup
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Simply print the received data as text'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
self.codec = 'latin_1'
if options is not None:
if 'find' in options.keys():
self.find = bytes(options['find'], 'ascii') # text to highlight
if 'color' in options.keys():
self.color = bytes('\033[' + options['color'] + 'm', 'ascii') # highlight color
else:
self.color = b'\033[31;1m'
if 'codec' in options.keys():
codec = options['codec']
try:
lookup(codec)
self.codec = codec
except LookupError:
print(f"{self.name}: {options['codec']} is not a valid codec, using {self.codec}")
def execute(self, data):
if self.find is None:
print(decode(data, self.codec))
else:
pdata = data.replace(self.find, self.color + self.find + b'\033[0m')
print(decode(pdata, self.codec))
return data
def help(self):
h = '\tfind: string that should be highlighted\n'
h += ('\tcolor: ANSI color code. Will be wrapped with \\033[ and m, so'
' passing 32;1 will result in \\033[32;1m (bright green)')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,2 @@
paho-mqtt
PySocks

View file

@ -0,0 +1,489 @@
#!/usr/bin/env python3
import argparse
import pkgutil
import os
import sys
import threading
import socket
import socks
import ssl
import time
import select
import errno
# TODO: implement verbose output
# some code snippets, as well as the original idea, from Black Hat Python
def is_valid_ip4(ip):
# some rudimentary checks if ip is actually a valid IP
octets = ip.split('.')
if len(octets) != 4:
return False
return octets[0] != 0 and all(0 <= int(octet) <= 255 for octet in octets)
def parse_args():
parser = argparse.ArgumentParser(description='Simple TCP proxy for data ' +
'interception and ' +
'modification. ' +
'Select modules to handle ' +
'the intercepted traffic.')
parser.add_argument('-ti', '--targetip', dest='target_ip',
help='remote target IP or host name')
parser.add_argument('-tp', '--targetport', dest='target_port', type=int,
help='remote target port')
parser.add_argument('-li', '--listenip', dest='listen_ip',
default='0.0.0.0', help='IP address/host name to listen for ' +
'incoming data')
parser.add_argument('-lp', '--listenport', dest='listen_port', type=int,
default=8080, help='port to listen on')
parser.add_argument('-pi', '--proxy-ip', dest='proxy_ip', default=None,
help='IP address/host name of proxy')
parser.add_argument('-pp', '--proxy-port', dest='proxy_port', type=int,
default=1080, help='proxy port', )
parser.add_argument('-pt', '--proxy-type', dest='proxy_type', default='SOCKS5', choices=['SOCKS4', 'SOCKS5', 'HTTP'],
type = str.upper, help='proxy type. Options are SOCKS5 (default), SOCKS4, HTTP')
parser.add_argument('-om', '--outmodules', dest='out_modules',
help='comma-separated list of modules to modify data' +
' before sending to remote target.')
parser.add_argument('-im', '--inmodules', dest='in_modules',
help='comma-separated list of modules to modify data' +
' received from the remote target.')
parser.add_argument('-v', '--verbose', dest='verbose', default=False,
action='store_true',
help='More verbose output of status information')
parser.add_argument('-n', '--no-chain', dest='no_chain_modules',
action='store_true', default=False,
help='Don\'t send output from one module to the ' +
'next one')
parser.add_argument('-l', '--log', dest='logfile', default=None,
help='Log all data to a file before modules are run.')
parser.add_argument('--list', dest='list', action='store_true',
help='list available modules')
parser.add_argument('-lo', '--list-options', dest='help_modules', default=None,
help='Print help of selected module')
parser.add_argument('-s', '--ssl', dest='use_ssl', action='store_true',
default=False, help='detect SSL/TLS as well as STARTTLS')
parser.add_argument('-sc', '--server-certificate', default='mitm.pem',
help='server certificate in PEM format (default: %(default)s)')
parser.add_argument('-sk', '--server-key', default='mitm.pem',
help='server key in PEM format (default: %(default)s)')
parser.add_argument('-cc', '--client-certificate', default=None,
help='client certificate in PEM format in case client authentication is required by the target')
parser.add_argument('-ck', '--client-key', default=None,
help='client key in PEM format in case client authentication is required by the target')
return parser.parse_args()
def generate_module_list(modstring, incoming=False, verbose=False):
# This method receives the comma-separated module list, imports the modules
# and creates a Module instance for each module. A list of these instances
# is then returned.
# The incoming parameter is True when the modules belong to the incoming
# chain (-im)
# modstring looks like mod1,mod2:key=val,mod3:key=val:key2=val2,mod4 ...
modlist = []
namelist = modstring.split(',')
for n in namelist:
name, options = parse_module_options(n)
try:
__import__('proxymodules.' + name)
modlist.append(sys.modules['proxymodules.' + name].Module(incoming, verbose, options))
except ImportError:
print('Module %s not found' % name)
sys.exit(3)
return modlist
def parse_module_options(n):
# n is of the form module_name:key1=val1:key2=val2 ...
# this method returns the module name and a dict with the options
n = n.split(':', 1)
if len(n) == 1:
# no module options present
return n[0], None
name = n[0]
optionlist = n[1].split(':')
options = {}
for op in optionlist:
try:
k, v = op.split('=')
options[k] = v
except ValueError:
print(op, ' is not valid!')
sys.exit(23)
return name, options
def list_modules():
# show all available proxy modules
cwd = os.getcwd()
module_path = cwd + os.sep + 'proxymodules'
for _, module, _ in pkgutil.iter_modules([module_path]):
__import__('proxymodules.' + module)
m = sys.modules['proxymodules.' + module].Module()
print(f'{m.name} - {m.description}')
def print_module_help(modlist):
# parse comma-separated list of module names, print module help text
modules = generate_module_list(modlist)
for m in modules:
try:
print(f'{m.name} - {m.description}')
print(m.help())
except AttributeError:
print('\tNo options or missing help() function.')
def update_module_hosts(modules, source, destination):
# set source and destination IP/port for each module
# source and destination are ('IP', port) tuples
# this can only be done once local and remote connections have been established
if modules is not None:
for m in modules:
if hasattr(m, 'source'):
m.source = source
if hasattr(m, 'destination'):
m.destination = destination
def receive_from(s):
# receive data from a socket until no more data is there
b = b""
while True:
data = s.recv(4096)
b += data
if not data or len(data) < 4096:
break
return b
def handle_data(data, modules, dont_chain, incoming, verbose):
# execute each active module on the data. If dont_chain is set, feed the
# output of one plugin to the following plugin. Not every plugin will
# necessarily modify the data, though.
for m in modules:
vprint(("> > > > in: " if incoming else "< < < < out: ") + m.name, verbose)
if dont_chain:
m.execute(data)
else:
data = m.execute(data)
return data
def is_client_hello(sock):
firstbytes = sock.recv(128, socket.MSG_PEEK)
return (len(firstbytes) >= 3 and
firstbytes[0] == 0x16 and
firstbytes[1:3] in [b"\x03\x00",
b"\x03\x01",
b"\x03\x02",
b"\x03\x03",
b"\x02\x00"]
)
def enable_ssl(args, remote_socket, local_socket):
sni = None
def sni_callback(sock, name, ctx):
nonlocal sni
sni = name
try:
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.sni_callback = sni_callback
ctx.load_cert_chain(certfile=args.server_certificate,
keyfile=args.server_key,
)
local_socket = ctx.wrap_socket(local_socket,
server_side=True,
)
except ssl.SSLError as e:
print("SSL handshake failed for listening socket", str(e))
raise
try:
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
if args.client_certificate and args.client_key:
ctx.load_cert_chain(certfile=args.client_certificate,
keyfile=args.client_key,
)
remote_socket = ctx.wrap_socket(remote_socket,
server_hostname=sni,
)
except ssl.SSLError as e:
print("SSL handshake failed for remote socket", str(e))
raise
return [remote_socket, local_socket]
def starttls(args, local_socket, read_sockets):
return (args.use_ssl and
local_socket in read_sockets and
not isinstance(local_socket, ssl.SSLSocket) and
is_client_hello(local_socket)
)
def start_proxy_thread(local_socket, args, in_modules, out_modules):
# This method is executed in a thread. It will relay data between the local
# host and the remote host, while letting modules work on the data before
# passing it on.
remote_socket = socks.socksocket()
if args.proxy_ip:
proxy_types = {'SOCKS5': socks.SOCKS5, 'SOCKS4': socks.SOCKS4, 'HTTP': socks.HTTP}
remote_socket.set_proxy(proxy_types[args.proxy_type], args.proxy_ip, args.proxy_port)
try:
remote_socket.connect((args.target_ip, args.target_port))
vprint('Connected to %s:%d' % remote_socket.getpeername(), args.verbose)
log(args.logfile, 'Connected to %s:%d' % remote_socket.getpeername())
except socket.error as serr:
if serr.errno == errno.ECONNREFUSED:
for s in [remote_socket, local_socket]:
s.close()
print(f'{time.strftime("%Y%m%d-%H%M%S")}, {args.target_ip}:{args.target_port}- Connection refused')
log(args.logfile, f'{time.strftime("%Y%m%d-%H%M%S")}, {args.target_ip}:{args.target_port}- Connection refused')
return None
elif serr.errno == errno.ETIMEDOUT:
for s in [remote_socket, local_socket]:
s.close()
print(f'{time.strftime("%Y%m%d-%H%M%S")}, {args.target_ip}:{args.target_port}- Connection timed out')
log(args.logfile, f'{time.strftime("%Y%m%d-%H%M%S")}, {args.target_ip}:{args.target_port}- Connection timed out')
return None
else:
for s in [remote_socket, local_socket]:
s.close()
raise serr
try:
update_module_hosts(out_modules, local_socket.getpeername(), remote_socket.getpeername())
update_module_hosts(in_modules, remote_socket.getpeername(), local_socket.getpeername())
except socket.error as serr:
if serr.errno == errno.ENOTCONN:
# kind of a blind shot at fixing issue #15
# I don't yet understand how this error can happen, but if it happens I'll just shut down the thread
# the connection is not in a useful state anymore
for s in [remote_socket, local_socket]:
s.close()
return None
else:
for s in [remote_socket, local_socket]:
s.close()
print(f"{time.strftime('%Y%m%d-%H%M%S')}: Socket exception in start_proxy_thread")
raise serr
# This loop ends when no more data is received on either the local or the
# remote socket
running = True
while running:
read_sockets, _, _ = select.select([remote_socket, local_socket], [], [])
if starttls(args, local_socket, read_sockets):
try:
ssl_sockets = enable_ssl(args, remote_socket, local_socket)
remote_socket, local_socket = ssl_sockets
vprint("SSL enabled", args.verbose)
log(args.logfile, "SSL enabled")
except ssl.SSLError as e:
print("SSL handshake failed", str(e))
log(args.logfile, "SSL handshake failed", str(e))
break
read_sockets, _, _ = select.select(ssl_sockets, [], [])
for sock in read_sockets:
try:
peer = sock.getpeername()
except socket.error as serr:
if serr.errno == errno.ENOTCONN:
# kind of a blind shot at fixing issue #15
# I don't yet understand how this error can happen, but if it happens I'll just shut down the thread
# the connection is not in a useful state anymore
for s in [remote_socket, local_socket]:
s.close()
running = False
break
else:
print(f"{time.strftime('%Y%m%d-%H%M%S')}: Socket exception in start_proxy_thread")
raise serr
data = receive_from(sock)
log(args.logfile, 'Received %d bytes' % len(data))
if sock == local_socket:
if len(data):
log(args.logfile, b'< < < out\n' + data)
if out_modules is not None:
data = handle_data(data, out_modules,
args.no_chain_modules,
False, # incoming data?
args.verbose)
remote_socket.send(data.encode() if isinstance(data, str) else data)
else:
vprint("Connection from local client %s:%d closed" % peer, args.verbose)
log(args.logfile, "Connection from local client %s:%d closed" % peer)
remote_socket.close()
running = False
break
elif sock == remote_socket:
if len(data):
log(args.logfile, b'> > > in\n' + data)
if in_modules is not None:
data = handle_data(data, in_modules,
args.no_chain_modules,
True, # incoming data?
args.verbose)
local_socket.send(data)
else:
vprint("Connection to remote server %s:%d closed" % peer, args.verbose)
log(args.logfile, "Connection to remote server %s:%d closed" % peer)
local_socket.close()
running = False
break
def log(handle, message, message_only=False):
# if message_only is True, only the message will be logged
# otherwise the message will be prefixed with a timestamp and a line is
# written after the message to make the log file easier to read
if not isinstance(message, bytes):
message = bytes(message, 'ascii')
if handle is None:
return
if not message_only:
logentry = bytes("%s %s\n" % (time.strftime('%Y%m%d-%H%M%S'), str(time.time())), 'ascii')
else:
logentry = b''
logentry += message
if not message_only:
logentry += b'\n' + b'-' * 20 + b'\n'
handle.write(logentry)
def vprint(msg, is_verbose):
# this will print msg, but only if is_verbose is True
if is_verbose:
print(msg)
def main():
args = parse_args()
if args.list is False and args.help_modules is None:
if not args.target_ip:
print('Target IP is required: -ti')
sys.exit(6)
if not args.target_port:
print('Target port is required: -tp')
sys.exit(7)
if ((args.client_key is None) ^ (args.client_certificate is None)):
print("You must either specify both the client certificate and client key or leave both empty")
sys.exit(8)
if args.logfile is not None:
try:
args.logfile = open(args.logfile, 'ab', 0) # unbuffered
except Exception as ex:
print('Error opening logfile')
print(ex)
sys.exit(4)
if args.list:
list_modules()
sys.exit(0)
if args.help_modules is not None:
print_module_help(args.help_modules)
sys.exit(0)
if args.listen_ip != '0.0.0.0' and not is_valid_ip4(args.listen_ip):
try:
ip = socket.gethostbyname(args.listen_ip)
except socket.gaierror:
ip = False
if ip is False:
print('%s is not a valid IP address or host name' % args.listen_ip)
sys.exit(1)
else:
args.listen_ip = ip
if not is_valid_ip4(args.target_ip):
try:
ip = socket.gethostbyname(args.target_ip)
except socket.gaierror:
ip = False
if ip is False:
print('%s is not a valid IP address or host name' % args.target_ip)
sys.exit(2)
else:
args.target_ip = ip
if args.in_modules is not None:
in_modules = generate_module_list(args.in_modules, incoming=True, verbose=args.verbose)
else:
in_modules = None
if args.out_modules is not None:
out_modules = generate_module_list(args.out_modules, incoming=False, verbose=args.verbose)
else:
out_modules = None
# this is the socket we will listen on for incoming connections
proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxy_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
proxy_socket.bind((args.listen_ip, args.listen_port))
except socket.error as e:
print(e.strerror)
sys.exit(5)
proxy_socket.listen(100)
log(args.logfile, str(args))
# endless loop until ctrl+c
try:
while True:
in_socket, in_addrinfo = proxy_socket.accept()
vprint('Connection from %s:%d' % in_addrinfo, args.verbose)
log(args.logfile, 'Connection from %s:%d' % in_addrinfo)
proxy_thread = threading.Thread(target=start_proxy_thread,
args=(in_socket, args, in_modules,
out_modules))
log(args.logfile, "Starting proxy thread " + proxy_thread.name)
proxy_thread.start()
except KeyboardInterrupt:
log(args.logfile, 'Ctrl+C detected, exiting...')
print('\nCtrl+C detected, exiting...')
sys.exit(0)
if __name__ == '__main__':
main()