Skip to content

Commit

Permalink
Modify logging in DNS listener, Diverter for blacklisted processes (#192
Browse files Browse the repository at this point in the history
)

* Fix documentation

* Minor code fix

* Modify logging in DNS listener, Diverter for blacklisted processes

* Code cleanup

* Get config from binary location for Pyinstaller bundles

* Update version and add logs

* Fix text length in CHANGELOG

* Update documentation
  • Loading branch information
tinajohnson authored Nov 21, 2024
1 parent 2e3e99e commit 58b9c99
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 27 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
Version 3.3
-----------
* Hide logging in DNS listener and Diverter for blacklisted processes
when not in verbose mode
* Use binary location instead of current directory when getting config
files in Pyinstaller bundles

Version 3.2
-----------
* Use .1 for default gateway instead of .254 because this is the default Virtual
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

D O C U M E N T A T I O N

FakeNet-NG 3.2 is a next generation dynamic network analysis tool for malware
FakeNet-NG 3.3 is a next generation dynamic network analysis tool for malware
analysts and penetration testers. It is open source and designed for the latest
versions of Windows (and Linux, for certain modes of operation). FakeNet-NG is
based on the excellent Fakenet tool developed by Andrew Honig and Michael
Expand Down Expand Up @@ -77,18 +77,18 @@ Finally if you would like to avoid installing FakeNet-NG and just want to run it
as-is (e.g. for development), then you would need to obtain the source code and
install dependencies as follows:

1) Install 64-bit or 32-bit Python 3.7.x for the 64-bit or 32-bit versions
1) Install 64-bit or 32-bit Python 3.10.11 for the 64-bit or 32-bit versions
of Windows respectively.

2) Install Python dependencies:

pip install pydivert dnslib dpkt pyopenssl pyftpdlib netifaces
pip install pydivert dnslib dpkt pyopenssl pyftpdlib netifaces jinja2

*NOTE*: pydivert will also download and install WinDivert library and
driver in the `%PYTHONHOME%\DLLs` directory. FakeNet-NG bundles those
files so they are not necessary for normal use.

2b) Optionally, you can install the following module used for testing:
Optionally, you can install the following module used for testing:

pip install requests

Expand Down Expand Up @@ -734,6 +734,15 @@ plugins and extend existing functionality. For details, see
Known Issues
============

[WinError 87] The parameter is incorrect
----------------------------------------
As of this wriring, the default buffer size in pydivert is 1500. If FakeNet-NG
encounters a packet larger than the default buffer size, you may observe this error.
A workaround is to specify the desired buffer size in self.handle.recv(bufsize=<your_bufsize>)
in fakenet/diverters/windows.
See [here](https://github.com/ffalcinelli/pydivert/issues/42#issuecomment-495036124)


Does not work on VMWare with host-only mode enabled
---------------------------------------------------

Expand Down
6 changes: 3 additions & 3 deletions docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ utilities (i.e. `pip`). Use an administrative command prompt where applicable
for installing Python modules for all users.

Pre-requisites:
* Python 2.7 x86 with `pip`
* Visual C++ for Python 2.7 development, available at:
<https://aka.ms/vcpython27>
* Python 3.10.11 x86 with `pip`
* Visual C++ for Python development, available at:
<https://visualstudio.microsoft.com/visual-cpp-build-tools/>

Before installing `pyinstaller`, you may wish to take the following steps to
prevent the error `ImportError: No module named PyInstaller`:
Expand Down
65 changes: 59 additions & 6 deletions fakenet/diverters/diverterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,8 @@ def parse_diverter_config(self):
self.getconfigval('processblacklist').split(',')]
self.logger.debug('Blacklisted processes: %s', ', '.join(
[str(p) for p in self.blacklist_processes]))
if self.logger.level == logging.INFO:
self.logger.info('Hiding logs from blacklisted processes')

# Only redirect whitelisted processes
if self.is_configured('processwhitelist'):
Expand Down Expand Up @@ -1202,7 +1204,18 @@ def handle_pkt(self, pkt, callbacks3, callbacks4):
pc = PidCommDest(pid, comm, pkt.proto, pkt.dst_ip0, pkt.dport0)
if pc.isDistinct(self.last_conn, self.ip_addrs[pkt.ipver]):
self.last_conn = pc
self.logger.info('%s' % (str(pc)))
# As a user may not wish to see any logs from a blacklisted
# process, messages are logged with level DEBUG. Executing
# FakeNet in the verbose mode will print these logs
is_process_blacklisted, _, _ = self.isProcessBlackListed(
pkt.proto,
process_name=comm,
dport=pkt.dport0
)
if is_process_blacklisted:
self.logger.debug('%s' % (str(pc)))
else:
self.logger.info('%s' % (str(pc)))

# 2: Call layer 3 (network) callbacks
for cb in callbacks3:
Expand Down Expand Up @@ -1825,9 +1838,8 @@ def logNbi(self, sport, nbi, proto, application_layer_proto,
is_ssl_encrypted):
"""Collects the NBIs from all listeners into a dictionary.
All listeners (currently only HTTPListener) use this
method to notify the diverter about any NBI captured
within their scope.
All listeners use this method to notify the diverter about any NBI
captured within their scope.
Args:
sport: int port bound by listener
Expand Down Expand Up @@ -1956,7 +1968,7 @@ def generate_html_report(self):
"""
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
# Inside a Pyinstaller bundle
fakenet_dir_path = os.getcwd()
fakenet_dir_path = os.path.dirname(sys.executable)
else:
fakenet_dir_path = os.fspath(Path(__file__).parents[1])

Expand All @@ -1972,7 +1984,44 @@ def generate_html_report(self):
output_file.write(template.render(nbis=self.nbis))

self.logger.info(f"Generated new HTML report: {output_filename}")


def isProcessBlackListed(self, proto, sport=None, process_name=None, dport=None):
"""Checks if a process is blacklisted.
Expected arguments are either:
- process_name and dport, or
- sport
"""
pid = None

if self.single_host_mode and proto is not None:
if process_name is None or dport is None:
if sport is None:
return False, process_name, pid

orig_sport = self.proxy_sport_to_orig_sport_map.get((proto, sport), sport)
session = self.sessions.get(orig_sport)
if session:
pid = session.pid
process_name = session.comm
dport = session.dport0
else:
return False, process_name, pid

# Check process blacklist
if process_name in self.blacklist_processes:
self.pdebug(DIGN, ('Ignoring %s packet from process %s ' +
'in the process blacklist.') % (proto,
process_name))
return True, process_name, pid

# Check per-listener blacklisted process list
if self.listener_ports.isProcessBlackListHit(
proto, dport, process_name):
self.pdebug(DIGN, ('Ignoring %s request packet from ' +
'process %s in the listener process ' +
'blacklist.') % (proto, process_name))
return True, process_name, pid
return False, process_name, pid


class DiverterListenerCallbacks():
Expand Down Expand Up @@ -2011,3 +2060,7 @@ def mapProxySportToOrigSport(self, proto, orig_sport, proxy_sport,
self.__diverter.mapProxySportToOrigSport(proto, orig_sport, proxy_sport,
is_ssl_encrypted)

def isProcessBlackListed(self, proto, sport):
"""Check if the process is blacklisted.
"""
return self.__diverter.isProcessBlackListed(proto, sport=sport)
4 changes: 2 additions & 2 deletions fakenet/fakenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __init__(self, logging_level = logging.INFO):
def parse_config(self, config_filename):
# Handling Pyinstaller bundle scenario: https://pyinstaller.org/en/stable/runtime-information.html
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
dir_path = os.getcwd()
dir_path = os.path.dirname(sys.executable)
else:
dir_path = os.path.dirname(__file__)

Expand Down Expand Up @@ -349,7 +349,7 @@ def main():
| | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| |
|_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____|
Version 3.2
Version 3.3
_____________________________________________________________
Developed by FLARE Team
Copyright (C) 2016-2024 Mandiant, Inc. All rights reserved.
Expand Down
47 changes: 38 additions & 9 deletions fakenet/listeners/DNSListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def __init__(
self.logger.debug('Initialized with config:')
for key, value in config.items():
self.logger.debug(' %10s: %s', key, value)
if self.logger.level == logging.INFO:
self.logger.info('Hiding logs from blacklisted processes')

def start(self):

Expand Down Expand Up @@ -81,18 +83,38 @@ def acceptDiverterListenerCallbacks(self, diverterListenerCallbacks):


class DNSHandler():
def log_message(self, log_level, is_process_blacklisted, message, *args):
"""The primary objective of this method is to control the log messages
generated for requests from blacklisted processes.
In a case where the DNS server is same as the local machine, the DNS
requests from a blacklisted process will reach the DNS listener (which
listens on port 53 locally) nevertheless. As a user may not wish to see
logs from a blacklisted process, messages are logged with level DEBUG.
Executing FakeNet in the verbose mode will print these logs.
"""
if is_process_blacklisted:
self.server.logger.log(logging.DEBUG, message, *args)
else:
self.server.logger.log(log_level, message, *args)

def parse(self,data):
def parse(self, data):
response = ""

proto = 'TCP' if self.server.socket_type == socket.SOCK_STREAM else 'UDP'
is_process_blacklisted, process_name, pid = self.server \
.diverterListenerCallbacks \
.isProcessBlackListed(
proto,
sport=self.client_address[1])

try:
# Parse data as DNS
d = DNSRecord.parse(data)

except Exception as e:
self.server.logger.error('Error: Invalid DNS Request')
self.log_message(logging.ERROR, is_process_blacklisted, 'Error: Invalid DNS Request')
for line in hexdump_table(data):
self.server.logger.warning(INDENT + line)
self.log_message(logging.WARNING, is_process_blacklisted, INDENT + line)

else:
# Only Process DNS Queries
Expand All @@ -110,7 +132,14 @@ def parse(self,data):
self.qname = qname
self.qtype = qtype

self.server.logger.info('Received %s request for domain \'%s\'.', qtype, qname)
if process_name is None or pid is None:
self.log_message(logging.INFO, is_process_blacklisted,
'Received %s request for domain \'%s\'.',
qtype, qname)
else:
self.log_message(logging.INFO, is_process_blacklisted,
'Received %s request for domain \'%s\' from %s (%s)',
qtype, qname, process_name, pid)

# Create a custom response to the query
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
Expand Down Expand Up @@ -165,11 +194,11 @@ def parse(self,data):
fake_record = socket.gethostbyname(socket.gethostname())

if self.server.nxdomains > 0:
self.server.logger.info('Ignoring query. NXDomains: %d',
self.log_message(logging.INFO, is_process_blacklisted, 'Ignoring query. NXDomains: %d',
self.server.nxdomains)
self.server.nxdomains -= 1
else:
self.server.logger.debug('Responding with \'%s\'',
self.log_message(logging.DEBUG, is_process_blacklisted, 'Responding with \'%s\'',
fake_record)
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))

Expand All @@ -180,7 +209,7 @@ def parse(self,data):
# dnslib doesn't like trailing dots
if fake_record[-1] == ".": fake_record = fake_record[:-1]

self.server.logger.debug('Responding with \'%s\'',
self.log_message(logging.DEBUG, is_process_blacklisted, 'Responding with \'%s\'',
fake_record)
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))

Expand All @@ -189,7 +218,7 @@ def parse(self,data):

fake_record = self.server.config.get('responsetxt', 'FAKENET')

self.server.logger.debug('Responding with \'%s\'',
self.log_message(logging.DEBUG, is_process_blacklisted, 'Responding with \'%s\'',
fake_record)
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))

Expand Down
2 changes: 1 addition & 1 deletion fakenet/listeners/ListenerBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def abs_config_path(path):
return abspath

if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
relpath = os.path.join(os.getcwd(), path)
relpath = os.path.join(os.path.dirname(sys.executable), path)
else:

# Try to locate the location relative to application path
Expand Down
2 changes: 1 addition & 1 deletion fakenet/listeners/ProxyListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def run(self):
self.listener_q.put(data)
else:
self.sock.close()
exit(1)
sys.exit(1)
except Exception as e:
self.logger.debug('Listener socket exception %s' % e.message)

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

setup(
name='FakeNet NG',
version='3.2',
version='3.3',
description="",
long_description="",
author="Mandiant FLARE Team with credit to Peter Kacherginsky as the original developer",
Expand Down

0 comments on commit 58b9c99

Please sign in to comment.