Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for SNI and dynamic certificate #198

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions fakenet/configs/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ NetworkMode: Auto
# DebugLevel: specify fine-grained debug print flags to enable. Enabling all
# logging when verbose mode is selected results in overwhelming output, hence
# this setting. Valid values (comma-separated) are:
#
#
# GENPKT Generic packet information
# GENPKTV Packet analysis, displays IP, TCP, UDP fields, very wide output
# CB Diverter packet handler callback start/finish logging
Expand Down Expand Up @@ -88,7 +88,7 @@ FixDNS: Yes
# ephemeral change.
ModifyLocalDNS: Yes

# Enable 'StopDNSService' to stop Windows DNS client to see the actual
# Enable 'StopDNSService' to stop Windows DNS client to see the actual
# processes resolving domains. This is a no-op on Linux, until such time as DNS
# caching is observed to interfere with finding the pid associated with a DNS
# request.
Expand All @@ -99,16 +99,16 @@ StopDNSService: Yes
# 'DefaultUDPListener' will handle TCP and UDP traffic going to unspecified ports.
#
# NOTE: Setting default UDP listener will intercept all DNS traffic unless you
# enable a dedicated UDP port 53 DNS listener or add UDP port 53 to the
# enable a dedicated UDP port 53 DNS listener or add UDP port 53 to the
# 'BlackListPortsUDP' below so that system's default DNS server is used instead.

RedirectAllTraffic: Yes
DefaultTCPListener: ProxyTCPListener
DefaultUDPListener: ProxyUDPListener

# Specify TCP and UDP ports to ignore when diverting packets.
# Specify TCP and UDP ports to ignore when diverting packets.
# For example, you may want to avoid diverting UDP port 53 (DNS) traffic
# when trying to intercept a specific process while allowing the rest to
# when trying to intercept a specific process while allowing the rest to
# function normally
#
# NOTE: This setting is only honored when 'RedirectAllTraffic' is enabled.
Expand All @@ -131,7 +131,7 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
# Listener Configuration
#
# Listener configuration consists of generic settings used by the diverter which
# are the same for all listeners and listener specific settings.
# are the same for all listeners and listener specific settings.
#
# NOTE: Listener section names will be used for logging.
#
Expand Down Expand Up @@ -199,6 +199,11 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
# hostname string, !hostname to insert the actual hostname
# of the system, or !random to generate a random hostname
# between 1 and 15 characters (inclusive).
# * Static_CA - Set FakeNet to use user provided CA certificate to sign generated certificates.
# * CA_Cert - CA certificate in PEM format to be used when Static_CA config is set. Manually
# add this certificate to Windows trust store before executing FakeNet.
# * CA_Key - CA private key in PEM format to be used when Static_CA config is set.


[ProxyTCPListener]
Enabled: True
Expand All @@ -207,6 +212,9 @@ Listener: ProxyListener
Port: 38926
Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener
Hidden: False
Static_CA: No
CA_Cert: configs/fakenet_ca.crt
CA_Key: configs/fakenet_ca.key

[ProxyUDPListener]
Enabled: True
Expand Down
19 changes: 19 additions & 0 deletions fakenet/configs/fakenet_ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHzCCAgegAwIBAgIUdwSkHdOM2mMDw094Kha+9Z9/w60wDQYJKoZIhvcNAQEL
BQAwHzELMAkGA1UEBhMCVVMxEDAOBgNVBAMMB2Zha2VuZXQwHhcNMjMxMjE0MDAx
NDUxWhcNMjQwMTEzMDAxNDUxWjAfMQswCQYDVQQGEwJVUzEQMA4GA1UEAwwHZmFr
ZW5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7pGzS8bX8M3SSQ
mk79puvqGBHwWVpDK82T44N/8mXHJ1R/7jMDq2wpkSjliiAE0mxPpkzMbr9mkeP/
/31GKAszbJYnurrxxYbLyOdRst2VqoXkWTia61lrsRIGcjwzKe2zyMCcuiyRTLcP
BmYd/ie5AzyHxitlS49cub+QkIODUAKTiZT3mPu6Yw2XvYkg+up69NzC0a/XexUv
PvgbBizquKj/YzMSp5X7ieYGv0xHf8Dhf3mqh9oLk35X/qV3LqdnVPjweCR8X9ze
yhfBbDr1VoBnOe2Nb5hlU9MB//A0hgDYj5TrHa4JrbNkv3lYMd4uv/CBW6o18Ba+
/zjEvyECAwEAAaNTMFEwHQYDVR0OBBYEFD3wRGMPQdWtBCKRy5c9N5YWnki2MB8G
A1UdIwQYMBaAFD3wRGMPQdWtBCKRy5c9N5YWnki2MA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAGT5rmafrlv8VIXAgc0iazNd7A6rT7xNLkuF2JGK
7NV3yvsWM6SA+DlG7y70om+eKjd+qxzinxnSt6uFJhcdCqot1LU1u3OZDifTTJmk
31yEYp/+A93qjwe1Ag2rsVcztRl88KtsKrKNohv24iiWfIVDnHo0joerXoaGQwo6
zXl4GVJBEEAhf2GQRgyXcoWkSrsq8UKtVV9dI5QgIS6vZ65oNQEeoAXH56ihFUBX
hS+4Ko2FfUtxbfbw7tpDaNtqhAzJ+LE4RoDUepyCDXPha0Wb4giGOd5EEubYrFKi
DOdAMiiQT1WLK3/UnMlCOV4lne+g9JwBCXL8C0F0W1fFZY8=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions fakenet/configs/fakenet_ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDO6Rs0vG1/DN0k
kJpO/abr6hgR8FlaQyvNk+ODf/JlxydUf+4zA6tsKZEo5YogBNJsT6ZMzG6/ZpHj
//99RigLM2yWJ7q68cWGy8jnUbLdlaqF5Fk4mutZa7ESBnI8Mynts8jAnLoskUy3
DwZmHf4nuQM8h8YrZUuPXLm/kJCDg1ACk4mU95j7umMNl72JIPrqevTcwtGv13sV
Lz74GwYs6rio/2MzEqeV+4nmBr9MR3/A4X95qofaC5N+V/6ldy6nZ1T48HgkfF/c
3soXwWw69VaAZzntjW+YZVPTAf/wNIYA2I+U6x2uCa2zZL95WDHeLr/wgVuqNfAW
vv84xL8hAgMBAAECggEAAyp6KDPcHCjH5XU4hXGPeGYvkhldQtxqsw2Rr7xpVWrl
dw1q8/dR2kVGVFSlBWe7tIPk0Ew8fD14+xtXG4xhmECAoElTHY+b7VgxkVJPem9h
RD4XOLsP4ba4rlNes+DiUCHKbyHTOox1RXytQZxNbgbVr7tOVNU1nf0rVmzt9Zbw
uPltpCcL+yvnaWmBUgdCLIhIT/HDrp4+uZP+zW7pVm/KMQL0I9OMnm6dCimP88pU
meepDRdxHkHVb347jx0+nWSH2V63wxH6WR8lAb4oAXeQXUgS8Q0lXh70EW/x0Ut3
neB/mfuaXLVrBcBTbhPTU7PKd/Sc+dkkqhEpNRR3dQKBgQDyLNPyWEJVXwQ6iOom
xxyqm61DZw8IpBYVmfYrOisTUWVC+oNlpsNPzWsgwmWzpcJ8s6ykupWFcWZeJv2h
XPlq+Ai6Ky0v+yESiUtNb+lKuXGvHqTpTxvPgL/20ZMfRIkWs9YBLA+aTh5RC8Vs
i12fRW9JTZTKgyf7PEkj+DjCCwKBgQDauOszqtFs6d36AMVJJ7OlmZnEWaELv9bW
ns7kdeBgr35gzGbDOkyQGBII5SCwpgWbXyxT39T9xuNoWCWXSZdHDco/UlAG47qI
Pq/KmtbrKxvg6yKsPIDV+cexuBrLIvzsaz8qPbfE/W7nxf+FtvDhvnGyqoiYd3DS
XSD5HcwLAwKBgQDdQpG+mF66qx4s8Myl80NAqQ1LSMyWg3xd7hXYdsPGWZaf9Eu6
wvstXSvkeVf8I5Um4+33bzWO/wWdPhh6pnyG++jVVv9pGBOmYOP48yd9iyLP8bqQ
IyPwmNxKgD3f0nlB0brTxVLYE0llmNCelFJMY18C5SvtPpl31COrBm2s8wKBgG8D
zN28pe+SBIkQOxKWhChZfiKbG5LLHFBy6rAq5GguqwaWuNH+lT3N+dlp8t22ZsIl
3Gn2AjWM7X/Yvbu8LnxyE2Vwcg4NKHBe4PsE/HEAwHW44zBoxTvWO/WIbJEOgTG+
faEDEnN57wDVDozf/gOWlj8JL6uzdCBSBJps9VPhAoGAWQUkdpxRxEARhL7wYR7g
EidDWKnULiULjlrP7VPMJ5hrZf5PmWyZLWW3SkEUhcf25CnJoUoq1OOB1GjL9lBL
0+tXalLnA/mRxO5ILzwJivHyJKnljuPKyXmpKt8H4KRUXV3Uk2may58Jwn8InuRE
aW956i0O1tgrDOj3tSAT8KI=
-----END PRIVATE KEY-----
4 changes: 2 additions & 2 deletions fakenet/diverters/diverterbase.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2016-2023 Mandiant, Inc. All rights reserved.
# Copyright (C) 2016-2024 Mandiant, Inc. All rights reserved.

import os
import abc
Expand Down Expand Up @@ -958,7 +958,7 @@ def _build_cmd(self, tmpl, pid, comm, src_ip, sport, dst_ip, dport):
except KeyError as e:
self.logger.error(('Failed to build ExecuteCmd for port %d due ' +
'to erroneous format key: %s') %
(dport, e.message))
(dport, e))

return cmd

Expand Down
28 changes: 14 additions & 14 deletions fakenet/diverters/linutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ def start(self, timeout_sec=0.5):
self._bound = True
except OSError as e:
self.logger.error('Failed to start queue for %s: %s' %
(str(self), e.message))
(str(self), e))
except RuntimeWarning as e:
self.logger.error('Failed to start queue for %s: %s' %
(str(self), e.message))
(str(self), e))

if not self._bound:
return False
Expand All @@ -166,7 +166,7 @@ def start(self, timeout_sec=0.5):
self._thread.start()
self._started = True
except RuntimeError as e:
self.logger.error('Failed to start queue thread: %s' % (e.message))
self.logger.error('Failed to start queue thread: %s' % (e))

return self._started

Expand Down Expand Up @@ -251,7 +251,7 @@ def parse(self, multi=False, max_col=None):
retval = cb_retval
break
except IOError as e:
self.logger.error('Failed accessing %s: %s' % (path, e.message))
self.logger.error('Failed accessing %s: %s' % (path, e))
# All or nothing
retval = [] if multi else None

Expand Down Expand Up @@ -313,7 +313,7 @@ def linux_capture_iptables(self):
ret = p.wait()
except OSError as e:
self.logger.error('Error executing iptables-save: %s' %
(e.message))
(e))

return ret

Expand All @@ -328,7 +328,7 @@ def linux_restore_iptables(self):
ret = p.wait()
except OSError as e:
self.logger.error('Error executing iptables-restore: %s' %
(e.message))
(e))

return ret

Expand All @@ -351,7 +351,7 @@ def linux_flush_iptables(self):
self.logger.error('Received return code %d from %s' +
(ret, cmd))
except OSError as e:
self.logger.error('Error executing %s: %s' % (cmd, e.message))
self.logger.error('Error executing %s: %s' % (cmd, e))

return rets

Expand Down Expand Up @@ -388,7 +388,7 @@ def linux_get_current_nfnlq_bindings(self):
self.logger.debug(('Failed to open %s to enumerate netfilter '
'netlink queues, caller may proceed as if '
'none are in use: %s') %
(procfs_path, e.message))
(procfs_path, e))

return qnos

Expand Down Expand Up @@ -445,7 +445,7 @@ def _linux_get_ifaces(self):
ifaces.append(fields[0].strip())
except IOError as e:
self.logger.error('Failed to open %s to enumerate interfaces: %s' %
(procfs_path, e.message))
(procfs_path, e))

return ifaces

Expand All @@ -472,7 +472,7 @@ def linux_modifylocaldns_ephemeral(self):
except IOError as e:
self.logger.error(('Failed to open %s to save DNS ' +
'configuration: %s') % (resolvconf_path,
e.message))
e))

if self.old_dns:
try:
Expand All @@ -484,7 +484,7 @@ def linux_modifylocaldns_ephemeral(self):
except IOError as e:
self.logger.error(('Failed to open %s to modify DNS ' +
'configuration: %s') % (resolvconf_path,
e.message))
e))

def linux_restore_local_dns(self):
resolvconf_path = '/etc/resolv.conf'
Expand All @@ -496,7 +496,7 @@ def linux_restore_local_dns(self):
except IOError as e:
self.logger.error(('Failed to open %s to restore DNS ' +
'configuration: %s') % (resolvconf_path,
e.message))
e))

def linux_find_processes(self, names):
"""But what if a blacklisted process spawns after we call
Expand Down Expand Up @@ -618,7 +618,7 @@ def _linux_find_sock_by_endpoint_unsafe(self, ipver, proto_name, ip, port,
(line.strip()))
except IOError as e:
self.logger.error('No such protocol/IP ver (%s) or error: %s' %
(procfs_path, e.message))
(procfs_path, e))

return inode

Expand Down Expand Up @@ -701,7 +701,7 @@ def linux_get_comm_by_pid(self, pid):
comm = f.read().strip()
except IOError as e:
self.pdebug(DPROCFS, 'Failed to open %s: %s' %
(procfs_path, e.message))
(procfs_path, e))
return comm

def linux_get_pid_comm_by_endpoint(self, ipver, proto_name, ip, port):
Expand Down
2 changes: 1 addition & 1 deletion fakenet/fakenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def start(self):
self.logger.error("%s" % e)

else:

listener_config['networkmode'] = self.diverter_config['networkmode']
listener_provider_instance = listener_provider(
listener_config, listener_name, self.logging_level)

Expand Down
2 changes: 1 addition & 1 deletion fakenet/listeners/DNSListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DNSListener(object):

def taste(self, data, dport):

confidence = 1 if dport is 53 else 0
confidence = 1 if dport == 53 else 0

try:
d = DNSRecord.parse(data)
Expand Down
54 changes: 36 additions & 18 deletions fakenet/listeners/HTTPListener.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Copyright (C) 2016-2023 Mandiant, Inc. All rights reserved.
# Copyright (C) 2016-2024 Mandiant, Inc. All rights reserved.

import logging
from configparser import ConfigParser

import os
import sys
import imp
import importlib.util
import importlib.machinery

import threading
import socketserver
Expand All @@ -19,6 +20,7 @@

import time

from .ssl_utils import SSLWrapper
from . import *

MIME_FILE_RESPONSE = {
Expand Down Expand Up @@ -46,6 +48,16 @@ def qualify_file_path(filename, fallbackdir):

return path

def load_source(modname, filename):
loader = importlib.machinery.SourceFileLoader(modname, filename)
spec = importlib.util.spec_from_file_location(modname, filename, loader=loader)
module = importlib.util.module_from_spec(spec)
# The module is always executed and not cached in sys.modules.
# Uncomment the following line to cache the module.
# sys.modules[module.__name__] = module
loader.exec_module(module)
return module


class CustomResponse(object):
def __init__(self, name, conf, configroot):
Expand Down Expand Up @@ -83,7 +95,7 @@ def __init__(self, name, conf, configroot):
self.handler = None
pymod_path = qualify_file_path(conf.get('httpdynamic'), configroot)
if pymod_path:
pymod = imp.load_source('cr_' + self.name, pymod_path)
pymod = load_source('cr_' + self.name, pymod_path)
funcname = 'HandleHttp'
funcname_legacy = 'HandleRequest'
if hasattr(pymod, funcname):
Expand Down Expand Up @@ -137,6 +149,10 @@ def respond(self, req, meth, postdata=None):


class HTTPListener(object):
SSL_UTILS = os.path.join("listeners", "ssl_utils")
CA_CERT = os.path.join(SSL_UTILS, "server.pem")
CA_KEY = os.path.join(SSL_UTILS, "privkey.pem")
NOT_AFTER_DELTA_SECONDS = 300 * 24 * 60 * 60

def taste(self, data, dport):

Expand Down Expand Up @@ -168,11 +184,13 @@ def __init__(

self.logger = logging.getLogger(name)
self.logger.setLevel(logging_level)

self.config = config
self.name = name
self.local_ip = config.get('ipaddr')
self.server = None
self.port = self.config.get('port', 80)
self.sslwrapper = None

self.logger.debug('Initialized with config:')
for key, value in config.items():
Expand All @@ -185,29 +203,29 @@ def __init__(
self.logger.error('Could not locate webroot directory: %s', path)
sys.exit(1)


def start(self):
self.logger.debug('Starting...')
self.server = ThreadedHTTPServer((self.local_ip, int(self.config.get('port'))), ThreadedHTTPRequestHandler)

self.server = ThreadedHTTPServer((self.local_ip,
int(self.config.get('port'))), ThreadedHTTPRequestHandler)
self.server.logger = self.logger
self.server.config = self.config
self.server.webroot_path = self.webroot_path
self.server.extensions_map = self.extensions_map

if self.config.get('usessl') == 'Yes':
self.logger.debug('Using SSL socket.')

keyfile_path = 'listeners/ssl_utils/privkey.pem'
keyfile_path = ListenerBase.abs_config_path(keyfile_path)
if keyfile_path is None:
raise RuntimeError('Could not locate %s' % (keyfile_path))

certfile_path = 'listeners/ssl_utils/server.pem'
certfile_path = ListenerBase.abs_config_path(certfile_path)
if certfile_path is None:
raise RuntimeError('Could not locate %s' % (certfile_path))

self.server.socket = ssl.wrap_socket(self.server.socket, keyfile=keyfile_path, certfile=certfile_path, server_side=True, ciphers='RSA')
self.logger.debug("HTTP Listener starting with SSL")
config = {
'cert_dir': self.config.get('cert_dir', 'configs/temp_certs'),
'networkmode': self.config.get('networkmode', None),
'static_ca': self.config.get('static_ca', "No"),
'ca_cert': self.config.get('ca_cert'),
'ca_key': self.config.get('ca_key')
}
self.sslwrapper = SSLWrapper(config)
self.server.sslwrapper = self.sslwrapper
self.server.socket = self.server.sslwrapper.wrap_socket(
self.server.socket)

self.server.custom_responses = []
custom = self.config.get('custom')
Expand Down
Loading