├── .gitignore ├── img └── logo.png ├── .gitattributes ├── demon_hill ├── reload.py ├── utils.py ├── __init__.py ├── filters.py ├── settings.py ├── tables.py ├── log.py ├── client2server.py ├── http.py └── tcp_proxy.py ├── TODO ├── README.md ├── test_server.py ├── ca-cert.pem ├── test_client.py ├── server-key.pem ├── server-cert.pem ├── main.py ├── LICENSE └── demon_hill_cc_final.py /.gitignore: -------------------------------------------------------------------------------- 1 | # dirs 2 | __pycache__ 3 | beta 4 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tde-nico/demon_hill/HEAD/img/logo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /demon_hill/reload.py: -------------------------------------------------------------------------------- 1 | from .log import * 2 | 3 | RELOAD = False 4 | 5 | # Log 6 | logger.debug(__file__) 7 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | code cleaning 2 | 3 | argumet parser: 4 | ports 5 | addresses 6 | iptables changes 7 | 8 | 9 | pcap sniff: 10 | sock 11 | ssl sock 12 | 13 | -------------------------------------------------------------------------------- /demon_hill/utils.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from .log import * 3 | 4 | 5 | def random_flag(length:int=FLAG_LEN-1): 6 | return "".join(random.choices(string.ascii_uppercase + string.digits, k=length)) + "=" 7 | 8 | 9 | def replace_flag(logger: logging.Logger, data: bytes, id: int) -> bytes: 10 | def callback(match_obj): 11 | new_flag = random_flag() 12 | logger.warning(f"{match_obj.group().decode()} -> {new_flag}") 13 | return new_flag.encode() 14 | 15 | logger.warning(f"Reciving Attack from {id}") 16 | search = re.search(FLAG_REGEX, data) 17 | if search: 18 | data = re.sub(FLAG_REGEX, callback, data) 19 | #else: 20 | # data = b"HTTP/2 404 Not Found\n" 21 | return data 22 | 23 | 24 | # Log 25 | logger.debug(__file__) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # demon_hill 2 | 3 | 16 | 17 |
18 | 19 |
20 | 21 | ## Info 22 | 23 | Demon Hill is a cutsom proxy used in AD-style CTF's. 24 | It recives packets from clients and send the to the server applyng "filters" between the connection; it can also reload itself via command. 25 | 26 | ## Filters 27 | 28 | The filters are two lists (one for client data and one for server data) that contains functions that can operate on the data. 29 | 30 | ## Reload 31 | 32 | It is initialized as only a main that imports himself like a module, and with the command "r" in the cmd when running, can reimport itself and reload new patches and filters to new and old connections without disconnect none. 33 | -------------------------------------------------------------------------------- /demon_hill/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module, reload 2 | 3 | ''' 4 | from .log import * 5 | from .settings import * 6 | from .utils import * 7 | from .tables import * 8 | from .submitter import * 9 | from .http import * 10 | from .filters import * 11 | from .tcp_proxy import * 12 | ''' 13 | 14 | PACKAGE = 'demon_hill' 15 | 16 | def load_module(name): 17 | mdl = import_module(name, package=PACKAGE) 18 | try: 19 | if RELOAD: 20 | reload(mdl) 21 | except Exception as e: 22 | if str(e) != "name 'RELOAD' is not defined": 23 | print(e) 24 | if "__all__" in mdl.__dict__: 25 | names = mdl.__dict__["__all__"] 26 | else: 27 | names = [x for x in mdl.__dict__ if not x.startswith("_")] 28 | globals().update({k: getattr(mdl, k) for k in names}) 29 | 30 | 31 | MODULES = [ 32 | 'log', 33 | 'settings', 34 | 'utils', 35 | 'tables', 36 | 'http', 37 | 'filters', 38 | 'tcp_proxy', 39 | 'reload', 40 | ] 41 | 42 | for module in MODULES: 43 | load_module('.' + module) 44 | 45 | 46 | -------------------------------------------------------------------------------- /demon_hill/filters.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from .utils import * 3 | from .log import * 4 | from .http import * 5 | 6 | 7 | 8 | def custom_filter(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 9 | 10 | # write here 11 | 12 | return data 13 | 14 | 15 | def info_filter(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 16 | logger.info(data) 17 | return data 18 | 19 | 20 | def regex_filter(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 21 | for exclusion in REGEX_MASKS: 22 | if re.search(exclusion, client_history): 23 | data = replace_flag(logger, data, id) 24 | break 25 | return data 26 | 27 | 28 | 29 | 30 | 31 | SERVER_FILTERS = [ 32 | regex_filter, 33 | # http_response, 34 | # custom_filter, 35 | # info_filter, 36 | ] 37 | 38 | CLIENT_FILTERS = [ 39 | # custom_filter, 40 | # info_filter, 41 | ] 42 | 43 | 44 | # Log 45 | logger.debug(__file__) 46 | -------------------------------------------------------------------------------- /test_server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import ssl 3 | 4 | 5 | HOST = "::1" 6 | PORT = 1337 7 | 8 | IPV6 = 1 9 | SSL = 0 10 | 11 | 12 | 13 | if __name__ == "__main__": 14 | if IPV6: 15 | server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 16 | else: 17 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | 19 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 20 | 21 | if SSL: 22 | server = ssl.wrap_socket( 23 | server, 24 | server_side=True, 25 | keyfile="server-key.pem", 26 | certfile="./server-cert.pem", 27 | do_handshake_on_connect=True, 28 | ) 29 | 30 | server.bind((HOST, PORT)) 31 | print(f'IPv6: {IPV6}, SSL: {SSL}') 32 | print(f'Listening on {HOST} {PORT}') 33 | server.listen(0) 34 | 35 | 36 | while True: 37 | #ssl.SSLContext.num_tickets = 0 38 | connection, client_address = server.accept() 39 | 40 | while True: 41 | #ssl.SSLContext.num_tickets = 0 42 | data = connection.recv(1024) 43 | 44 | if not data: 45 | break 46 | 47 | print(f"Received: {data.decode('utf-8')}") 48 | connection.sendall(b'ok') 49 | -------------------------------------------------------------------------------- /ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDbTCCAlWgAwIBAgIUelxMuLRzY0DtdseAhF8leK5pQwcwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCSVQxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA2MjUyMjA5MzVaGA8zMDIy 5 | MTAyNjIyMDkzNVowRTELMAkGA1UEBhMCSVQxEzARBgNVBAgMClNvbWUtU3RhdGUx 6 | ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN 7 | AQEBBQADggEPADCCAQoCggEBAL1x5DlB8LD0ITiCKHNYHdcPXare4vR73lAKYhtV 8 | ijMxfVDOdjb4XO4bDGRe58F+eq28NEu6l8WeB8ENozDkEUJp/m2vwL9m4eMmTWBN 9 | cJOhLdXFr21MkhpjqDAsK84cC01DhW11moo8UUB8oV8Gw8SpTlWiWLMtAwWT8zMx 10 | gnNAox7QvMJ9QuznYFdNfcX51EqoXLnK4gGosxP8CanFL1LFFUMaHB67mbZxL4zR 11 | y1r6HaRD1CTf/7VHi4efBDOjxXiAXbPrJpEa99ojVdbb+LqSnH6YmFeSwGqESt84 12 | ZUNHdgKEmSlM51VMSa9R4yj9nqCjdc7RGGG/BXJPaBY6yZ0CAwEAAaNTMFEwHQYD 13 | VR0OBBYEFL35yYQEy7AQGzz52wfuXvlUY+c5MB8GA1UdIwQYMBaAFL35yYQEy7AQ 14 | Gzz52wfuXvlUY+c5MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB 15 | AGr0VHIwRWSNAncktpvqyMYBrdxaTfPRG5CmG0fKLgb3Lrhsypn9UPlvOS3E3f+g 16 | LAATmHhYVXaK1yFZ8/njRH96NLLvCjppRMxtcGlk0neWZODd2NFjPLPxFzeGyzEn 17 | FbaY27lDUqt5Gnq52EooUrbznfNVzXPcUf6mlCzJ59zhQ1A/ILjrJl9HfjygWWNT 18 | BDnol9fkITVvxojpCiTws5uO/jnYiA33R+qIzmIHRUoMS09FL9vmaR8AzlAN+jQP 19 | b8IIYlYZQvLUTL6wy/bWhcARcQ075HhvC3xrmPWNXy46e27Jx6uJF4W26BrZSObI 20 | c/ZW8wOeT+5CB8G2JkkSAjI= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import ssl 3 | import time 4 | import sys 5 | 6 | 7 | 8 | SERVER_HOST = "fe80::215:5dff:fed9:2922" 9 | if (len(sys.argv) > 1 and sys.argv[1] == '-l') \ 10 | or (len(sys.argv) > 2 and sys.argv[2] == '-l'): 11 | SERVER_HOST = "::1" 12 | 13 | 14 | SERVER_PORT = 1337 15 | if (len(sys.argv) > 1 and sys.argv[1] == '-f') \ 16 | or (len(sys.argv) > 2 and sys.argv[2] == '-f'): 17 | SERVER_PORT -= 1 18 | 19 | 20 | IPV6 = 1 21 | SSL = 0 22 | 23 | 24 | if __name__ == "__main__": 25 | if IPV6: 26 | client = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 27 | else: 28 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 29 | 30 | if SSL: 31 | client = ssl.wrap_socket(client, ca_certs="./ca-cert.pem", do_handshake_on_connect=True) 32 | 33 | print(f'IPv6: {IPV6}, SSL: {SSL}') 34 | print(f'Connecting to {SERVER_HOST} {SERVER_PORT}') 35 | client.connect((SERVER_HOST, SERVER_PORT)) 36 | 37 | i = 0 38 | while True: 39 | print(f'sending... {i}') 40 | client.sendall(f"Hello World! {i}".encode("utf-8")) 41 | print(client.recv(1024)) 42 | i += 1 43 | time.sleep(1) 44 | 45 | 46 | ''' 47 | hostname = 'www.python.org' 48 | 49 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 50 | context.load_verify_locations('./ca-cert.pem') 51 | 52 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: 53 | with context.wrap_socket(sock, server_hostname=hostname) as ssock: 54 | print(ssock.version()) 55 | ''' 56 | -------------------------------------------------------------------------------- /demon_hill/settings.py: -------------------------------------------------------------------------------- 1 | import socket, select, threading 2 | import os, sys, re 3 | import logging 4 | import random, string 5 | import requests, datetime 6 | import ssl 7 | 8 | LOG_LEVEL = 'debug' 9 | 10 | # IPv6 11 | IPV6 = False 12 | if IPV6: 13 | INADDR_ANY = '::' 14 | LOCALHOST = '::1' 15 | else: 16 | INADDR_ANY = '0.0.0.0' 17 | LOCALHOST = '127.0.0.1' 18 | 19 | 20 | # Addrs 21 | FROM_ADDR = INADDR_ANY 22 | TO_ADDR = LOCALHOST 23 | 24 | # Ports 25 | FROM_PORT = 1336 26 | TO_PORT = 1337 27 | 28 | # SSL 29 | SSL = False 30 | SSL_KEYFILE = "./server-key.pem" 31 | SSL_CERTFILE = "./server-cert.pem" 32 | SSL_CA_CERT = "./ca-cert.pem" 33 | 34 | # Auto forwarding 35 | AUTO_SET_TABLES = True 36 | 37 | 38 | # Max history 39 | SERVER_HISTORY_SIZE = 1024 * 1024 40 | CLIENT_HISTORY_SIZE = 1024 * 1024 41 | 42 | 43 | # Flag 44 | FLAG_LEN = 32 45 | FLAG_REGEX = rb'[A-Z0-9]{31}=' 46 | 47 | 48 | # Masks 49 | REGEX_MASKS = [ 50 | ] 51 | 52 | REGEX_MASKS_2 = [ 53 | ] 54 | 55 | 56 | ############################## COLORS ############################## 57 | 58 | 59 | 60 | END = "\033[0m" 61 | 62 | BLACK = "\033[30m" 63 | RED = "\033[31m" 64 | GREEN = "\033[32m" 65 | YELLOW = "\033[33m" 66 | BLUE = "\033[34m" 67 | PURPLE = "\033[35m" 68 | CYAN = "\033[36m" 69 | GREY = "\033[90m" 70 | 71 | HIGH_RED = "\033[91m" 72 | HIGH_GREEN = "\033[92m" 73 | HIGH_YELLOW = "\033[93m" 74 | HIGH_BLUE = "\033[94m" 75 | HIGH_PURPLE = "\033[95m" 76 | HIGH_CYAN = "\033[96m" 77 | 78 | -------------------------------------------------------------------------------- /server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCp6WlMveDjEV/s 3 | lhgXbpUz8lZC3GBznwkTtHgM5QIDQusZPr4UjtHWkZ61QqofIkkBkJNxyAdgHvc3 4 | MQHO1JRwE8Riz/+h3aNEcVMoMJGUUa0BF9XsOOGoJgaCTe2wLmdxfTkOCDLBMx5M 5 | Jp2idRg5yvylrvPAGllnQtbYRpE44W5xvaJwyAt2bz5BVQCn0vocUKmN0iF2yx1Y 6 | Aw1V0RZuob62eIBVWX8plbkpXztPMyLWSTWpS6hsl5KfNXutHLHNNswa5uy8bdt3 7 | mUmgK9A7MXxGkNUe6N64Xasn2drVnwyvz33cWJGeJrA0LV6rF6bbX+qF2Z+sqT1/ 8 | LFv/pc21AgMBAAECggEAF1yBDpzdxoKdTH9fjRs2dve3HgbEWSlQ2215Axbalb21 9 | 6tjqVBPF7KlFZ7kkgFbbsFQjPaXpw7DGP0ek7r3iMd255vcft+7sE408SH6TCZBI 10 | R+nxzwKMHi0qQHC8ATkEHoG/3jrI1bD0VbaKfmt4mvuajKI3H97Pk4fIV+Ct92Ko 11 | DL/tq8PgdkV50JtasALkDPN9tpTe42rxw3NCTIrTtFEapdVXDTLCVkZoPNry88Pl 12 | TT3A4+ZyoFfUBY4dDp/2Bxyd7LsRZ9sGEa3OMIPY0AoD6wqC7CggFbs0sR9nZoPm 13 | 4RW8JOYUQ0JmxSH1OpV8Z6+r4YItp6J27s6FfZXE2QKBgQDOS7Po3uJpaxHsdelf 14 | 1vL68IfgjOUSDWzSjjlWjb9y4MbvHdRTQGhqYaqAUhRQVxVDbatZ+DSEtM7vRCtr 15 | v0AQ7PB3U7ZbN/6R0CdZfxBiCWYg34szNnZg1nLS515JFSSUqG6FwJML5pYXfy6x 16 | qqw5DU8MyrJnSZlZVYrtj2te3QKBgQDS2Yzn0uYd2mo8mulqGeFzd4LnpG0V/qCw 17 | gDfiA4V7mtgBotOq47nULnse85kxu9OpnAYdKM9NhAVl19Qg9/uI43A6gLByhgyM 18 | k43eiLvDUM1eSNSrAG5zWKTLMBvBFgkAEqsxNbYMWEdGdvfcd6GSk9RLA5r9Cu+2 19 | 5Z/NtcNAuQKBgH6ije/3UfjwVvJWd0nzwvgzytUW6E8qmpCt6ZiuLsWwIa4LG5l3 20 | QiF7jpRyzjF7I3xex/7UlDiaXSUXLahYoFVDfWFiq+BXARg1NNKFY2Mq7dxkHSj0 21 | 17oeKiI5EPG/3tK6ig3k5t85Tw1hbyJ06H+lIc9yZg1taosiLcQidACpAoGAGs5l 22 | x6NcdjcqgoH/Yunfa9qp3eFdrQouS8JBWI6yDcDl7W9SEcvZ+Evgg1LdCyGiC1I2 23 | xMZHdMdwGD6UTYy5gfHIduedhzHbrpOj2cLwfChts6r2vrbTU/7VFLANF0NB6ax1 24 | 70+w0Wj3xtGhOXpMJGJ+/vp6XeliCJKw+mo9ZKECgYAeQy9iidtu+W5c476v20kC 25 | E9veZEed9m46K3/K5iqdX6z+Q6qQAhG/CDIiZ+C7DsHNFBKSoZcHl/znFxe9aLnq 26 | /jdd16jy2/frQjU0YXU5cSonwDpm0+gM0prb9GA1izfP51/ifYUctPmJudflq/3H 27 | fV5OubLsDJDIcKTfmxExgw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEwzCCA6ugAwIBAgIUTSctKlYu62WM3LL99HGxoKxbKPgwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCSVQxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA2MjcwMTE0NTNaFw0yNTA2 5 | MjYwMTE0NTNaMHkxCzAJBgNVBAYTAlhYMQwwCgYDVQQIDANOL0ExDDAKBgNVBAcM 6 | A04vQTEgMB4GA1UECgwXU2VsZi1zaWduZWQgY2VydGlmaWNhdGUxLDAqBgNVBAMM 7 | I2NoZWF0c19hcGk6IFNlbGYtc2lnbmVkIGNlcnRpZmljYXRlMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqelpTL3g4xFf7JYYF26VM/JWQtxgc58JE7R4 9 | DOUCA0LrGT6+FI7R1pGetUKqHyJJAZCTccgHYB73NzEBztSUcBPEYs//od2jRHFT 10 | KDCRlFGtARfV7DjhqCYGgk3tsC5ncX05DggywTMeTCadonUYOcr8pa7zwBpZZ0LW 11 | 2EaROOFucb2icMgLdm8+QVUAp9L6HFCpjdIhdssdWAMNVdEWbqG+tniAVVl/KZW5 12 | KV87TzMi1kk1qUuobJeSnzV7rRyxzTbMGubsvG3bd5lJoCvQOzF8RpDVHujeuF2r 13 | J9na1Z8Mr8993FiRniawNC1eqxem21/qhdmfrKk9fyxb/6XNtQIDAQABo4IBdTCC 14 | AXEwggEtBgNVHREEggEkMIIBIIcECjwBAYcECjwCAYcECjwDAYcECjwEAYcECjwF 15 | AYcECjwGAYcECjwHAYcECjwIAYcECjwJAYcECjwKAYcECjwLAYcECjwMAYcECjwN 16 | AYcECjwOAYcECjwPAYcECjwQAYcECjwRAYcECjwSAYcECjwTAYcECjwUAYcECjwV 17 | AYcECjwWAYcECjwXAYcECjwYAYcECjwZAYcECjwaAYcECjwbAYcECjwcAYcECjwd 18 | AYcECjweAYcECjwfAYcECjwgAYcECjwhAYcECjwiAYcECjwjAYcECjwkAYcECjwl 19 | AYcECjwmAYcECjwnAYcECjwoAYcECjwpAYcECjwqAYcECjwrAYcECjwAAYcEfwAA 20 | AYcErBQAAoIKY2hlYXRzX2FwaTAdBgNVHQ4EFgQUrqgHfCT8xlXOWoVa7+uEcPsC 21 | UTcwHwYDVR0jBBgwFoAUvfnJhATLsBAbPPnbB+5e+VRj5zkwDQYJKoZIhvcNAQEL 22 | BQADggEBALcS+Ty9MAHQ7J2PhmnQVuNwdj45WVCn5I5KhwMF24E6Az8zpqHf57NY 23 | gLi2GXCt/U6UKZmDDBB37Nf/IDOfVHgUwRmv1J6GL6Tc/fOe5LT8rX/iaXSGCSLn 24 | 1/AA/B4B0s094NTiCYiAqFC6eB5sT18bVXJLLJF5ui309YIDITLjXx1PFCcn/D5P 25 | A9FIH+fWJcu+NpeiRl73AZ/6jwjYgt9+vnCRGtujqbG9goDmresSBuiJ310isYW0 26 | SwxR4h5N/1bylBcgh6FshZslSHi2LNa0nNN7KSlGkWFlCrp7cAfDje12PJs86VwT 27 | QaUQfyGMiabCB5oM3xg0KcTF1Pl/3QU= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from os import _exit, getpid 3 | from importlib import import_module, reload 4 | from threading import enumerate 5 | 6 | 7 | 8 | 9 | if __name__ == '__main__': 10 | dh = import_module('demon_hill') 11 | 12 | if len(argv) == 3: 13 | dh.FROM_PORT = int(argv[1]) 14 | dh.TO_PORT = int(argv[2]) 15 | 16 | reload_string = dh.to_rainbow('Reloading Proxy') 17 | 18 | proxy = dh.TCPProxy(dh.logger, dh.FROM_ADDR, dh.TO_ADDR, dh.FROM_PORT, dh.TO_PORT) 19 | proxy.start() 20 | 21 | proxy.lock.acquire() 22 | if not proxy.sock: 23 | _exit(0) 24 | proxy.lock.release() 25 | 26 | if dh.AUTO_SET_TABLES: 27 | dh.enable_forwarding(dh.logger) 28 | 29 | while True: 30 | try: 31 | cmd = input() 32 | 33 | if cmd[:1] == 'q': # Quit 34 | proxy.exit() 35 | 36 | elif cmd[:1] == 'r': # Reload 37 | dh.logger.info(reload_string) 38 | dh.RELOAD = True 39 | reload(dh) 40 | tmp_sock = proxy.sock 41 | proxy.lock.acquire() 42 | proxy.is_running = False 43 | proxy.sample_connection() 44 | proxy.lock.acquire() 45 | proxy.lock.release() 46 | proxy = dh.TCPProxy(dh.logger, dh.FROM_ADDR, dh.TO_ADDR, dh.FROM_PORT, dh.TO_PORT, tmp_sock) 47 | proxy.start() 48 | 49 | elif cmd[:1] == 'i': # Info 50 | dh.logger.info(f'{dh.HIGH_CYAN}PID{dh.END}: {dh.GREEN}{getpid()}{dh.END}') 51 | dh.logger.info(f'{dh.HIGH_CYAN}Threads{dh.END}: {dh.GREEN}{len(enumerate())}{dh.END}') 52 | 53 | elif cmd[:1] == 'f': # Forwarding 54 | dh.enable_forwarding(dh.logger) 55 | elif cmd[:1] == 'd': # Disable Forwarding 56 | dh.disable_forwarding(dh.logger) 57 | 58 | elif cmd[:1] == '+': # increases logs 59 | dh.loglevel_up() 60 | elif cmd[:1] == '-': # decreases logs 61 | dh.loglevel_down() 62 | 63 | except KeyboardInterrupt: 64 | proxy.exit() 65 | except Exception as e: 66 | dh.logger.error(str(e)) 67 | 68 | -------------------------------------------------------------------------------- /demon_hill/tables.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from .log import * 3 | from os import system, getuid 4 | 5 | 6 | IP_INTERFACE = "eth0" 7 | OUT_BLOCK = " 1>/dev/null" 8 | ERROR_BLOCK = " 2>/dev/null" 9 | if IPV6: 10 | TABLES = "ip6tables" 11 | else: 12 | TABLES = "iptables" 13 | IP_PREROUTING = f"{TABLES}" + " {} " + f"PREROUTING -t nat -i {IP_INTERFACE} -p tcp --dport {TO_PORT} -j DNAT --to-destination :{FROM_PORT}" 14 | IP_FORWARD = f"{TABLES}" + " {} " + f"FORWARD -i {IP_INTERFACE} -p tcp --dport {FROM_PORT} -j ACCEPT" 15 | 16 | IP_PREROUTING += ERROR_BLOCK + OUT_BLOCK 17 | IP_FORWARD += ERROR_BLOCK + OUT_BLOCK 18 | 19 | 20 | def is_routing(): 21 | return system(IP_PREROUTING.format("-C")) 22 | 23 | 24 | def modify_forwarding(cmd:str, logger:logging.Logger=None) -> bool: 25 | if getuid() != 0: 26 | logger.error(f"Need Root to Modify Routing") 27 | return True 28 | ret = system(IP_PREROUTING.format(cmd)) 29 | if ret and logger: 30 | logger.error(f"Prerouting Error {HIGH_RED}{ret}{END}") 31 | return True 32 | ret = system(IP_FORWARD.format(cmd)) 33 | if ret and logger: 34 | logger.error(f"Forward Error {HIGH_RED}{ret}{END}") 35 | return True 36 | return False 37 | 38 | 39 | def enable_forwarding(logger:logging.Logger=None): 40 | if is_routing(): 41 | if modify_forwarding("-A", logger): 42 | return 43 | logger.info(f"{HIGH_GREEN}Routing Enabled {HIGH_CYAN}{TO_PORT} -> {FROM_PORT}{END}") 44 | else: 45 | logger.info(f"{HIGH_CYAN}Routing Already Enabled {HIGH_GREEN}{TO_PORT} -> {FROM_PORT}{END}") 46 | 47 | 48 | def disable_forwarding(logger:logging.Logger=None): 49 | if not is_routing(): 50 | if modify_forwarding("-D", logger): 51 | return 52 | logger.info(f"{HIGH_RED}Routing Disabled {YELLOW}{TO_PORT} -> {FROM_PORT}{END}") 53 | else: 54 | logger.info(f"{YELLOW}Routing Already Disabled {HIGH_RED}{TO_PORT} -> {FROM_PORT}{END}") 55 | 56 | 57 | # Log 58 | logger.debug(__file__) 59 | -------------------------------------------------------------------------------- /demon_hill/log.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | 3 | 4 | class CustomFormatter(logging.Formatter): 5 | fmt = "[%(asctime)s] %(levelname)s: %(message)s" 6 | FORMATS = { 7 | logging.DEBUG: GREY + fmt + END, 8 | logging.INFO: GREY + "[%(asctime)s] " + END + "%(levelname)s: %(message)s", 9 | logging.WARNING: YELLOW + "[%(asctime)s] %(levelname)s: " + HIGH_YELLOW + "%(message)s" + END, 10 | logging.ERROR: RED + fmt + END, 11 | logging.CRITICAL: HIGH_RED + fmt + END, 12 | } 13 | 14 | def format(self, record): 15 | log_fmt = self.FORMATS.get(record.levelno, self.fmt) 16 | formatter = logging.Formatter(log_fmt) 17 | return formatter.format(record) 18 | 19 | 20 | def to_rainbow(s: str) -> str: 21 | rainbow = [HIGH_PURPLE, HIGH_BLUE, HIGH_CYAN, HIGH_GREEN, HIGH_YELLOW, YELLOW, RED] 22 | colors = len(rainbow) 23 | i = 0 24 | res = '' 25 | for char in s: 26 | res += rainbow[i] + char 27 | i = (i + 1) % colors 28 | res += END 29 | return res 30 | 31 | 32 | levels = { 33 | 'debug': logging.DEBUG, 34 | 'info': logging.INFO, 35 | 'warning': logging.WARNING, 36 | 'error': logging.ERROR, 37 | 'critical': logging.CRITICAL, 38 | } 39 | log_level = levels.get(LOG_LEVEL.lower(), logging.INFO) 40 | 41 | 42 | logger = logging.getLogger('demologger') 43 | logger.handlers.clear() 44 | custom_handler = logging.StreamHandler() 45 | custom_handler.setLevel(log_level) 46 | custom_handler.setFormatter(CustomFormatter()) 47 | logger.addHandler(custom_handler) 48 | logger.setLevel(log_level) 49 | 50 | 51 | def change_loglevel(direction): 52 | global levels, log_level, logger 53 | levels_values = list(levels.values()) 54 | levels_labels = list(levels.keys()) 55 | current_level_index = levels_values.index(log_level) 56 | current_level = levels_labels[current_level_index] 57 | if direction > 0: 58 | if current_level_index < len(levels_values) - 1: 59 | current_level_index += direction 60 | else: 61 | if current_level_index > 0: 62 | current_level_index += direction 63 | new_level = levels_values[current_level_index] 64 | new_level_label = levels_labels[current_level_index] 65 | logger.critical(f"{HIGH_CYAN}{current_level}{END} -> {GREEN}{new_level_label}{END}") 66 | log_level = new_level 67 | logger.setLevel(log_level) 68 | 69 | 70 | def loglevel_up(): 71 | change_loglevel(-1) 72 | 73 | 74 | def loglevel_down(): 75 | change_loglevel(+1) 76 | 77 | 78 | # Log 79 | logger.debug(__file__) 80 | -------------------------------------------------------------------------------- /demon_hill/client2server.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from .filters import * 3 | from .log import * 4 | 5 | 6 | class Client2Server(threading.Thread): 7 | def __init__(self, logger:logging.Logger, to_host:str, to_port:int, client_sock:socket.socket, client_id:str): 8 | super().__init__() 9 | self.logger = logger 10 | self.client = client_sock 11 | self.id = client_id 12 | self.client_history = b"" 13 | self.server_history = b"" 14 | self.error = None 15 | try: 16 | if IPV6: 17 | self.server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 18 | else: 19 | self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 20 | if SSL: 21 | self.server = ssl.wrap_socket( 22 | self.server, 23 | ca_certs=SSL_CA_CERT, 24 | do_handshake_on_connect=True, 25 | ) 26 | self.server.connect((to_host, to_port)) 27 | 28 | self.client.setblocking(False) 29 | self.server.setblocking(False) 30 | except ConnectionRefusedError as e: 31 | self.error = f'{e}' 32 | self.logger.warning(self.error) 33 | except Exception as e: 34 | self.error = f'{e}' 35 | self.logger.critical(self.error) 36 | 37 | 38 | def exit(self, msg): 39 | msg = f'{CYAN}{self.id}{END} ' + msg 40 | if self.client.fileno() != -1: 41 | self.client.close() 42 | msg = f'client ' + msg 43 | if self.server.fileno() != -1: 44 | msg = 'and ' + msg 45 | if self.server.fileno() != -1: 46 | self.server.close() 47 | msg = f'server ' + msg 48 | self.logger.info(f"{msg}") 49 | sys.exit() 50 | 51 | 52 | def send(self, read:socket.socket, write:socket.socket, is_client:bool): 53 | try: 54 | if SSL: 55 | data = read.recv() 56 | else: 57 | data = read.recv(1024) 58 | 59 | except ssl.SSLError as e: 60 | if e.errno == ssl.SSL_ERROR_WANT_READ: 61 | pass 62 | return 63 | except Exception as e: 64 | self.exit(f'{e}') 65 | 66 | if not data: 67 | self.exit("closed") 68 | 69 | # TODO better history 70 | if is_client: 71 | if len(self.client_history + data) <= CLIENT_HISTORY_SIZE: 72 | self.client_history += data 73 | filters = CLIENT_FILTERS 74 | else: 75 | if len(self.server_history + data) <= SERVER_HISTORY_SIZE: 76 | self.server_history += data 77 | filters = SERVER_FILTERS 78 | 79 | try: 80 | for f in filters: 81 | data = f(self.logger, data, self.server_history, self.client_history, self.id) 82 | write.sendall(data) 83 | 84 | except Exception as e: 85 | self.exit(f"{e}") 86 | 87 | 88 | def run(self): 89 | socket_list = [self.client, self.server] 90 | while True: 91 | read_sockets, write_sockets, error_sockets = select.select(socket_list, [], []) 92 | self.logger.debug(f'{read_sockets} {write_sockets} {error_sockets}') 93 | 94 | if self.client.fileno() == -1 or self.server.fileno() == -1: 95 | self.exit(f'closed during select') 96 | 97 | for sock in read_sockets: 98 | if sock == self.client: 99 | self.send(self.client, self.server, True) 100 | elif sock == self.server: 101 | self.send(self.server, self.client, False) 102 | 103 | 104 | # Log 105 | logger.debug(__file__) 106 | -------------------------------------------------------------------------------- /demon_hill/http.py: -------------------------------------------------------------------------------- 1 | from collections import UserDict 2 | from re import compile 3 | from .log import * 4 | from .utils import * 5 | 6 | 7 | HEADER = compile(r'([^:]+):\s+(.+)') 8 | REQUEST = compile(r"(\w+)\s+(.+)\s+HTTP\/(\d\.\d|\d)") 9 | RESPONSE = compile(r"HTTP\/(\d\.\d|\d)\s+(\d+)\s+(.+)") 10 | 11 | 12 | 13 | class MalformedHeaderException(Exception): 14 | def __init__(self, message): 15 | custom_msg = f'MalformedHeaderException: {message}' 16 | super().__init__(custom_msg) 17 | 18 | 19 | class MalformedRequestLineException(Exception): 20 | def __init__(self, message): 21 | custom_msg = f'MalformedRequestLineException: {message}' 22 | super().__init__(custom_msg) 23 | 24 | 25 | class MalformedResponseLineException(Exception): 26 | def __init__(self, message): 27 | custom_msg = f'MalformedResponseLineException: {message}' 28 | super().__init__(custom_msg) 29 | 30 | 31 | class HTTPHeaders(UserDict): 32 | def __init__(self, data: bytes) -> None: 33 | self.data: dict = {} 34 | for line in data.split(b'\n'): 35 | line = line.strip().decode() 36 | if not line: 37 | break 38 | match = HEADER.match(line) 39 | if not match: 40 | raise MalformedHeaderException(line) 41 | key: str = match.group(1) 42 | value: str = match.group(2) 43 | self[key] = value 44 | 45 | def __getitem__(self, item: str) -> str: 46 | return self.data[item.lower()] 47 | 48 | def __setitem__(self, item: str, value: str) -> None: 49 | self.data[item.lower()] = value 50 | 51 | 52 | 53 | class HTTPPayload: 54 | def __init__(self, data: bytes) -> None: 55 | self.headers: HTTPHeaders = HTTPHeaders(data) 56 | self.body: bytes = b'' 57 | if "content-length" in self.headers: 58 | self.body = data.split(b'\r\n\r\n', 1)[1] 59 | 60 | def __bytes__(self) -> bytes: 61 | result = bytearray() 62 | for key, value in self.headers.items(): 63 | result.extend(f"{key}: {value}\r\n".encode()) 64 | result.extend(b"\r\n") 65 | if self.body: 66 | result.extend(self.body) 67 | return bytes(result) 68 | 69 | 70 | class HTTPRequest: 71 | def __init__(self, data: bytes) -> None: 72 | if b'\n' not in data: 73 | raise MalformedRequestLineException(data) 74 | request, data = data.split(b'\n', 1) 75 | request = request.strip().decode() 76 | if not request: 77 | return None 78 | match = REQUEST.match(request) 79 | if match is None: 80 | raise MalformedRequestLineException(request) 81 | 82 | self.method: str = match.group(1) 83 | self.path: str = match.group(2) 84 | self.version: str = match.group(3) 85 | self.payload: HTTPPayload = HTTPPayload(data) 86 | 87 | def __bytes__(self) -> bytes: 88 | return ( 89 | f"{self.method} {self.path} HTTP/{self.version}\r\n".encode() 90 | + bytes(self.payload) 91 | ) 92 | 93 | 94 | class HTTPResponse: 95 | def __init__(self, data: bytes) -> None: 96 | if b'\n' not in data: 97 | raise MalformedResponseLineException(data) 98 | request, data = data.split(b'\n', 1) 99 | request = request.strip().decode() 100 | if not request: 101 | return None 102 | match = RESPONSE.match(request) 103 | if not match: 104 | raise MalformedResponseLineException(request) 105 | 106 | self.version: str = match.group(1) 107 | self.code: str = match.group(2) 108 | self.message: str = match.group(3) 109 | self.payload: HTTPPayload = HTTPPayload(data) 110 | 111 | def __bytes__(self) -> bytes: 112 | return ( 113 | f"HTTP/{self.version} {self.code} {self.message}\r\n".encode() 114 | + bytes(self.payload) 115 | ) 116 | 117 | 118 | ############################## SAMPLE FILTERS ############################## 119 | 120 | 121 | def http_response(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 122 | 123 | try: 124 | req = HTTPResponse(data) 125 | 126 | 127 | req.payload.body = replace_flag(logger, req.payload.body, id) 128 | 129 | return bytes(req) 130 | except Exception as e: 131 | logger.error(f'{e}') 132 | 133 | return data 134 | 135 | def http_request(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 136 | try: 137 | req = HTTPRequest(data) 138 | 139 | return bytes(req) 140 | except Exception as e: 141 | logger.error(f'{e}') 142 | 143 | return data 144 | 145 | 146 | # Log 147 | logger.debug(__file__) 148 | -------------------------------------------------------------------------------- /demon_hill/tcp_proxy.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from .log import * 3 | from .client2server import * 4 | from .tables import * 5 | 6 | from os import _exit 7 | from importlib import import_module, reload 8 | 9 | PACKAGE = 'demon_hill' 10 | 11 | def load_module(name): 12 | mdl = import_module(name, package=PACKAGE) 13 | reload(mdl) 14 | if "__all__" in mdl.__dict__: 15 | names = mdl.__dict__["__all__"] 16 | else: 17 | names = [x for x in mdl.__dict__ if not x.startswith("_")] 18 | globals().update({k: getattr(mdl, k) for k in names}) 19 | 20 | load_module('.client2server') 21 | 22 | 23 | class TCPProxy(threading.Thread): 24 | def __init__(self, logger:logging.Logger, from_host:str, to_host:str, from_port:int, to_port:int, sock:socket.socket=None): 25 | super().__init__() 26 | self.logger = logger 27 | self.from_host = from_host 28 | self.to_host = to_host 29 | self.from_port = from_port 30 | self.to_port = to_port 31 | self.sock = sock 32 | self.is_running = True 33 | self.lock = threading.Lock() 34 | self.lock.acquire() 35 | 36 | 37 | def run(self): 38 | try: 39 | if not self.sock: 40 | if IPV6: 41 | self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 42 | else: 43 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 44 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 45 | if SSL: 46 | self.sock = ssl.wrap_socket( 47 | self.sock, 48 | server_side=True, 49 | keyfile=SSL_KEYFILE, 50 | certfile=SSL_CERTFILE, 51 | do_handshake_on_connect=True, 52 | ) 53 | self.sock.bind((self.from_host, self.from_port)) 54 | self.sock.listen(1) 55 | self.logger.info(f"Serving {BLUE}{self.from_host}{END}:{GREEN}{self.from_port}{END}" +\ 56 | f" -> {BLUE}{self.to_host}{END}:{GREEN}{self.to_port}{END}") 57 | else: 58 | self.logger.info(f"{BLUE}{self.from_host}{END}:{GREEN}{self.from_port}{END}" +\ 59 | f" -> {BLUE}{self.to_host}{END}:{GREEN}{self.to_port}{END}") 60 | self.logger.info(f"{to_rainbow('Proxy Successfully Reloaded')}") 61 | self.lock.release() 62 | 63 | except Exception as e: 64 | self.logger.critical('Error while opening Main Socket') 65 | self.logger.critical(f'{e}') 66 | self.sock.close() 67 | self.sock = None 68 | self.lock.release() 69 | return 70 | 71 | 72 | while True: 73 | 74 | socket_list = [self.sock] 75 | read_sockets, write_sockets, error_sockets = select.select(socket_list, [], []) 76 | self.logger.debug(f'{read_sockets} {write_sockets} {error_sockets}') 77 | 78 | if not self.is_running: 79 | break 80 | 81 | if self.sock.fileno() == -1: 82 | self.logger.info(f"Shutting {HIGH_RED}{self.from_port}{END} -> {HIGH_RED}{self.to_port}{END}") 83 | break 84 | 85 | if not read_sockets: 86 | continue 87 | 88 | for sock in read_sockets: 89 | if sock == self.sock: 90 | try: 91 | client_sock, addr = sock.accept() 92 | client_ip, client_port = addr[0], addr[1] 93 | client_id = f"{client_ip}:{client_port}" 94 | self.logger.info(f"client {CYAN}{client_id}{END} connected") 95 | except ssl.SSLError as e: 96 | if e.errno == ssl.SSL_ERROR_EOF: 97 | pass 98 | continue 99 | except OSError as e: 100 | self.logger.error(f'{e}') 101 | break 102 | else: 103 | continue 104 | 105 | middleware = Client2Server( 106 | self.logger, 107 | self.to_host, 108 | self.to_port, 109 | client_sock, 110 | client_id 111 | ) 112 | if not middleware.error: 113 | middleware.start() 114 | 115 | self.logger.info(f"{to_rainbow('Proxy Closed')}") 116 | self.lock.release() 117 | 118 | 119 | def sample_connection(self): 120 | try: 121 | if IPV6: 122 | server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 123 | else: 124 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 125 | server.connect((LOCALHOST, self.from_port)) 126 | server.close() 127 | except ConnectionRefusedError as e: 128 | self.logger.warning(f'{e}') 129 | except Exception as e: 130 | self.logger.critical(f'{e}') 131 | 132 | 133 | def close(self): 134 | if self.sock: 135 | self.sock.close() 136 | self.sample_connection() 137 | 138 | 139 | def exit(self): 140 | if AUTO_SET_TABLES: 141 | disable_forwarding(self.logger) 142 | self.lock.acquire() 143 | self.close() 144 | self.lock.acquire() 145 | _exit(0) 146 | 147 | 148 | # Log 149 | logger.debug(__file__) 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /demon_hill_cc_final.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import socket, threading 3 | import os, sys, re 4 | import logging 5 | import random, string 6 | 7 | ############################## CONFIG ############################## 8 | 9 | 10 | LOG_LEVEL = 'info' 11 | 12 | FROM_PORT = 1338 13 | TO_PORT = 1337 14 | 15 | SERVER_HISTORY_SIZE = 1024 * 1024 16 | CLIENT_HISTORY_SIZE = 1024 * 1024 17 | 18 | 19 | FLAG_LEN = 32 20 | FLAG_REGEX = rb'[A-Z0-9]{31}=' 21 | 22 | REGEX_MASKS = [ 23 | rb'1\n[a-zA-Z0-9]{3}\n\n5\n2\n[a-zA-Z0-9]*\n3\n0\n2\n', 24 | ] 25 | 26 | REGEX_MASKS_2 = [ 27 | #rb'filename=".*"', 28 | ] 29 | 30 | 31 | ############################## MAIN ############################## 32 | 33 | if __name__ == '__main__': 34 | module = __file__.split('/')[-1][:-3] 35 | this = importlib.import_module(module) 36 | 37 | if len(sys.argv) == 3: 38 | this.FROM_PORT = int(sys.argv[1]) 39 | this.TO_PORT = int(sys.argv[2]) 40 | 41 | proxy = this.TCPProxy(this.logger, '0.0.0.0', '127.0.0.1', this.FROM_PORT, this.TO_PORT) 42 | proxy.start() 43 | 44 | reload_string = this.to_rainbow('Reloading Proxy') 45 | 46 | while True: 47 | try: 48 | cmd = input() 49 | 50 | if cmd[:1] == 'q': 51 | os._exit(0) 52 | 53 | if cmd[:1] == 'r': 54 | this.logger.info(reload_string) 55 | importlib.reload(this) 56 | proxy.close() 57 | proxy.lock.acquire() 58 | proxy = this.TCPProxy(this.logger, '0.0.0.0', '127.0.0.1', this.FROM_PORT, this.TO_PORT) 59 | proxy.start() 60 | 61 | except KeyboardInterrupt: 62 | proxy.close() 63 | proxy.lock.acquire() 64 | os._exit(0) 65 | except Exception as e: 66 | this.logger.error(str(e)) 67 | 68 | 69 | 70 | 71 | ############################## UTILS ############################## 72 | 73 | 74 | def random_flag(length:int=FLAG_LEN-1): 75 | return "".join(random.choices(string.ascii_uppercase + string.digits, k=length)) + "=" 76 | 77 | 78 | def replace_flag(logger: logging.Logger, data: bytes, id: int) -> bytes: 79 | def callback(match_obj): 80 | new_flag = random_flag() 81 | logger.warning(f"{match_obj.group().decode()} -> {new_flag}") 82 | return new_flag.encode() 83 | 84 | logger.warning(f"Reciving Attack from {id}") 85 | search = re.search(FLAG_REGEX, data) 86 | if search: 87 | data = re.sub(FLAG_REGEX, callback, data) 88 | #else: 89 | # data = b"HTTP/2 404 Not Found\n" 90 | return data 91 | 92 | 93 | ############################## FILTERS ############################## 94 | 95 | 96 | def custom_filter(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 97 | 98 | # write here 99 | 100 | return data 101 | 102 | 103 | def regex_filter(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 104 | for exclusion in REGEX_MASKS: 105 | if re.search(exclusion, client_history): 106 | data = replace_flag(logger, data, id) 107 | break 108 | return data 109 | 110 | 111 | def regex_filter_2(logger:logging.Logger, data:bytes, server_history:bytes, client_history:bytes, id:int) -> bytes: 112 | for exclusion in REGEX_MASKS_2: 113 | res = re.search(exclusion, client_history) 114 | if res: 115 | filename = client_history[res.start():].split(b'"')[1] 116 | logger.critical(f"{filename}") 117 | if filename != b'program': 118 | data = replace_flag(logger, data, id) 119 | break 120 | return data 121 | 122 | SERVER_FILTERS = [ 123 | regex_filter, 124 | ] 125 | 126 | CLIENT_FILTERS = [ 127 | ] 128 | 129 | ############################## LOGGER ############################## 130 | 131 | END = "\033[0m" 132 | 133 | BLACK = "\033[30m" 134 | RED = "\033[31m" 135 | GREEN = "\033[32m" 136 | YELLOW = "\033[33m" 137 | BLUE = "\033[34m" 138 | PURPLE = "\033[35m" 139 | CYAN = "\033[36m" 140 | GREY = "\033[90m" 141 | 142 | HIGH_RED = "\033[91m" 143 | HIGH_GREEN = "\033[92m" 144 | HIGH_YELLOW = "\033[93m" 145 | HIGH_BLUE = "\033[94m" 146 | HIGH_PURPLE = "\033[95m" 147 | HIGH_CYAN = "\033[96m" 148 | 149 | def to_rainbow(s: str) -> str: 150 | rainbow = [HIGH_PURPLE, HIGH_BLUE, HIGH_CYAN, HIGH_GREEN, HIGH_YELLOW, YELLOW, RED] 151 | colors = len(rainbow) 152 | i = 0 153 | res = '' 154 | for char in s: 155 | res += rainbow[i] + char 156 | i = (i + 1) % colors 157 | res += END 158 | return res 159 | 160 | class CustomFormatter(logging.Formatter): 161 | fmt = "[%(asctime)s] %(levelname)s: %(message)s" 162 | FORMATS = { 163 | logging.DEBUG: GREY + fmt + END, 164 | logging.INFO: GREY + "[%(asctime)s] " + END + "%(levelname)s: %(message)s", 165 | logging.WARNING: YELLOW + "[%(asctime)s] %(levelname)s: " + HIGH_YELLOW + "%(message)s" + END, 166 | logging.ERROR: RED + fmt + END, 167 | logging.CRITICAL: HIGH_RED + fmt + END, 168 | } 169 | 170 | def format(self, record): 171 | log_fmt = self.FORMATS.get(record.levelno, self.fmt) 172 | formatter = logging.Formatter(log_fmt) 173 | return formatter.format(record) 174 | 175 | 176 | 177 | levels = { 178 | 'debug': logging.DEBUG, 179 | 'info': logging.INFO, 180 | 'warning': logging.WARNING, 181 | 'error': logging.ERROR, 182 | 'critical': logging.CRITICAL, 183 | } 184 | log_level = levels.get(LOG_LEVEL.lower(), logging.INFO) 185 | 186 | 187 | logger = logging.getLogger('demologger') 188 | logger.handlers.clear() 189 | custom_handler = logging.StreamHandler() 190 | custom_handler.setLevel(log_level) 191 | custom_handler.setFormatter(CustomFormatter()) 192 | logger.addHandler(custom_handler) 193 | logger.setLevel(log_level) 194 | 195 | 196 | ############################## SERVER ############################## 197 | 198 | 199 | class Proxy2Server(threading.Thread): 200 | def __init__(self, logger:logging.Logger, host:str, port:int): 201 | super(Proxy2Server, self).__init__() 202 | self.logger = logger 203 | self.port = port 204 | self.host = host 205 | self.error = None 206 | self.client = None 207 | self.c2p = None 208 | self.history = b"" 209 | self.lock = threading.Lock() 210 | try: 211 | self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 212 | self.server.connect((host, port)) 213 | except ConnectionRefusedError as e: 214 | self.error = f'{e}' 215 | self.logger.warning(f'{e}') 216 | except Exception as e: 217 | self.error = f'{e}' 218 | self.logger.critical(f'{e}') 219 | 220 | 221 | def run(self): 222 | while True: 223 | try: 224 | data = b'' 225 | if self.server: 226 | data = self.server.recv(4096) 227 | 228 | except OSError: 229 | self.close() 230 | self.logger.info(f"server {CYAN}{self.c2p.id}{END} exit: recive") 231 | sys.exit() 232 | 233 | if not data: 234 | if self.client: 235 | self.c2p.close() 236 | self.logger.info(f"server {CYAN}{self.c2p.id}{END} exit: closed") 237 | sys.exit() 238 | 239 | try: 240 | if len(self.history) + len(data) < SERVER_HISTORY_SIZE: 241 | self.history += data 242 | self.logger.debug(data) 243 | for f in SERVER_FILTERS: 244 | data = f(self.logger, data, self.history, self.c2p.history, self.c2p.id) 245 | self.c2p.lock.acquire() 246 | if self.client and self.server: 247 | self.client.sendall(data) 248 | self.c2p.lock.release() 249 | 250 | except Exception as e: 251 | self.logger.error(f'server[{self.port} {self.c2p.id}]: {e}') 252 | 253 | 254 | def close(self): 255 | self.logger.info(f"server {CYAN}{self.c2p.id}{END} closed") 256 | self.lock.acquire() 257 | try: 258 | self.server.close() 259 | except AttributeError: 260 | pass 261 | self.server = None 262 | self.lock.release() 263 | 264 | 265 | ############################## CLIENT ############################## 266 | 267 | 268 | class Client2Proxy(threading.Thread): 269 | def __init__(self, logger:logging.Logger, host:str, port:int, sock:socket.socket): 270 | super(Client2Proxy, self).__init__() 271 | self.logger = logger 272 | self.port = port 273 | self.host = host 274 | self.error = None 275 | self.server = None 276 | self.p2s = None 277 | self.history = b"" 278 | self.lock = threading.Lock() 279 | try: 280 | self.client, addr = sock.accept() 281 | client_ip, client_port = addr 282 | self.id = client_port 283 | self.logger.info(f"client {CYAN}{self.id}{END} connected") 284 | except OSError as e: 285 | self.error = f'{e}' 286 | 287 | 288 | def run(self): 289 | while True: 290 | try: 291 | data = b'' 292 | if self.client: 293 | data = self.client.recv(4096) 294 | except OSError: 295 | self.close() 296 | self.logger.info(f"client {CYAN}{self.id}{END} exit: recive") 297 | sys.exit() 298 | 299 | if not data: 300 | if self.server: 301 | self.p2s.close() 302 | self.logger.info(f"client {CYAN}{self.id}{END} exit: closed") 303 | sys.exit() 304 | 305 | try: 306 | if len(self.history) + len(data) < CLIENT_HISTORY_SIZE: 307 | self.history += data 308 | self.logger.debug(data) 309 | for f in CLIENT_FILTERS: 310 | data = f(self.logger, data, self.p2s.history, self.history, self.id) 311 | self.p2s.lock.acquire() 312 | if self.server and self.client: 313 | self.server.sendall(data) 314 | self.p2s.lock.release() 315 | 316 | except Exception as e: 317 | self.logger.error(f'client[{self.port} {self.id}]: {e}') 318 | 319 | 320 | def close(self): 321 | self.logger.info(f"client {CYAN}{self.id}{END} closed") 322 | self.lock.acquire() 323 | try: 324 | self.client.close() 325 | except AttributeError: 326 | pass 327 | self.client = None 328 | self.lock.release() 329 | 330 | 331 | ############################## PROXY ############################## 332 | 333 | 334 | class TCPProxy(threading.Thread): 335 | def __init__(self, logger:logging.Logger, from_host:str, to_host:str, from_port:int, to_port:int): 336 | super(TCPProxy, self).__init__() 337 | self.logger = logger 338 | self.from_host = from_host 339 | self.to_host = to_host 340 | self.from_port = from_port 341 | self.to_port = to_port 342 | self.lock = threading.Lock() 343 | 344 | 345 | def run(self): 346 | try: 347 | self.lock.acquire() 348 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 349 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 350 | self.sock.bind((self.from_host, self.from_port)) 351 | self.sock.listen(1) 352 | self.logger.info(f"Serving {GREEN}{self.from_port}{END} -> {GREEN}{self.to_port}{END}") 353 | except Exception as e: 354 | self.logger.critical('Error while opening Main Proxy Socket') 355 | self.logger.critical(f'{e}') 356 | 357 | while True: 358 | c2p = Client2Proxy(self.logger, self.from_host, self.from_port, self.sock) 359 | if c2p.error: 360 | break 361 | 362 | p2s = Proxy2Server(self.logger, self.to_host, self.to_port) 363 | if p2s.error: 364 | c2p.close() 365 | continue 366 | 367 | c2p.server = p2s.server 368 | p2s.client = c2p.client 369 | p2s.c2p = c2p 370 | c2p.p2s = p2s 371 | 372 | c2p.start() 373 | p2s.start() 374 | 375 | self.logger.info(f"Shutting {HIGH_RED}{self.from_port}{END} -> {HIGH_RED}{self.to_port}{END}") 376 | self.lock.release() 377 | 378 | 379 | def close(self): 380 | self.sock.close() 381 | self.sample_connection() 382 | 383 | 384 | def sample_connection(self): 385 | try: 386 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 387 | server.connect(('127.0.0.1', self.from_port)) 388 | server.close() 389 | except ConnectionRefusedError as e: 390 | self.logger.warning(f'{e}') 391 | except Exception as e: 392 | self.logger.critical(f'{e}') 393 | --------------------------------------------------------------------------------