├── .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 |
--------------------------------------------------------------------------------