Skip to content

Commit 58b9c99

Browse files
authored
Modify logging in DNS listener, Diverter for blacklisted processes (#192)
* 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
1 parent 2e3e99e commit 58b9c99

9 files changed

+125
-27
lines changed

CHANGELOG.txt

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
Version 3.3
2+
-----------
3+
* Hide logging in DNS listener and Diverter for blacklisted processes
4+
when not in verbose mode
5+
* Use binary location instead of current directory when getting config
6+
files in Pyinstaller bundles
7+
18
Version 3.2
29
-----------
310
* Use .1 for default gateway instead of .254 because this is the default Virtual

README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

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

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

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

8383
2) Install Python dependencies:
8484

85-
pip install pydivert dnslib dpkt pyopenssl pyftpdlib netifaces
85+
pip install pydivert dnslib dpkt pyopenssl pyftpdlib netifaces jinja2
8686

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

91-
2b) Optionally, you can install the following module used for testing:
91+
Optionally, you can install the following module used for testing:
9292

9393
pip install requests
9494

@@ -734,6 +734,15 @@ plugins and extend existing functionality. For details, see
734734
Known Issues
735735
============
736736

737+
[WinError 87] The parameter is incorrect
738+
----------------------------------------
739+
As of this wriring, the default buffer size in pydivert is 1500. If FakeNet-NG
740+
encounters a packet larger than the default buffer size, you may observe this error.
741+
A workaround is to specify the desired buffer size in self.handle.recv(bufsize=<your_bufsize>)
742+
in fakenet/diverters/windows.
743+
See [here](https://github.com/ffalcinelli/pydivert/issues/42#issuecomment-495036124)
744+
745+
737746
Does not work on VMWare with host-only mode enabled
738747
---------------------------------------------------
739748

docs/developing.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ utilities (i.e. `pip`). Use an administrative command prompt where applicable
181181
for installing Python modules for all users.
182182

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

188188
Before installing `pyinstaller`, you may wish to take the following steps to
189189
prevent the error `ImportError: No module named PyInstaller`:

fakenet/diverters/diverterbase.py

+59-6
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,8 @@ def parse_diverter_config(self):
10541054
self.getconfigval('processblacklist').split(',')]
10551055
self.logger.debug('Blacklisted processes: %s', ', '.join(
10561056
[str(p) for p in self.blacklist_processes]))
1057+
if self.logger.level == logging.INFO:
1058+
self.logger.info('Hiding logs from blacklisted processes')
10571059

10581060
# Only redirect whitelisted processes
10591061
if self.is_configured('processwhitelist'):
@@ -1202,7 +1204,18 @@ def handle_pkt(self, pkt, callbacks3, callbacks4):
12021204
pc = PidCommDest(pid, comm, pkt.proto, pkt.dst_ip0, pkt.dport0)
12031205
if pc.isDistinct(self.last_conn, self.ip_addrs[pkt.ipver]):
12041206
self.last_conn = pc
1205-
self.logger.info('%s' % (str(pc)))
1207+
# As a user may not wish to see any logs from a blacklisted
1208+
# process, messages are logged with level DEBUG. Executing
1209+
# FakeNet in the verbose mode will print these logs
1210+
is_process_blacklisted, _, _ = self.isProcessBlackListed(
1211+
pkt.proto,
1212+
process_name=comm,
1213+
dport=pkt.dport0
1214+
)
1215+
if is_process_blacklisted:
1216+
self.logger.debug('%s' % (str(pc)))
1217+
else:
1218+
self.logger.info('%s' % (str(pc)))
12061219

12071220
# 2: Call layer 3 (network) callbacks
12081221
for cb in callbacks3:
@@ -1825,9 +1838,8 @@ def logNbi(self, sport, nbi, proto, application_layer_proto,
18251838
is_ssl_encrypted):
18261839
"""Collects the NBIs from all listeners into a dictionary.
18271840
1828-
All listeners (currently only HTTPListener) use this
1829-
method to notify the diverter about any NBI captured
1830-
within their scope.
1841+
All listeners use this method to notify the diverter about any NBI
1842+
captured within their scope.
18311843
18321844
Args:
18331845
sport: int port bound by listener
@@ -1956,7 +1968,7 @@ def generate_html_report(self):
19561968
"""
19571969
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
19581970
# Inside a Pyinstaller bundle
1959-
fakenet_dir_path = os.getcwd()
1971+
fakenet_dir_path = os.path.dirname(sys.executable)
19601972
else:
19611973
fakenet_dir_path = os.fspath(Path(__file__).parents[1])
19621974

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

19741986
self.logger.info(f"Generated new HTML report: {output_filename}")
1975-
1987+
1988+
def isProcessBlackListed(self, proto, sport=None, process_name=None, dport=None):
1989+
"""Checks if a process is blacklisted.
1990+
Expected arguments are either:
1991+
- process_name and dport, or
1992+
- sport
1993+
"""
1994+
pid = None
1995+
1996+
if self.single_host_mode and proto is not None:
1997+
if process_name is None or dport is None:
1998+
if sport is None:
1999+
return False, process_name, pid
2000+
2001+
orig_sport = self.proxy_sport_to_orig_sport_map.get((proto, sport), sport)
2002+
session = self.sessions.get(orig_sport)
2003+
if session:
2004+
pid = session.pid
2005+
process_name = session.comm
2006+
dport = session.dport0
2007+
else:
2008+
return False, process_name, pid
2009+
2010+
# Check process blacklist
2011+
if process_name in self.blacklist_processes:
2012+
self.pdebug(DIGN, ('Ignoring %s packet from process %s ' +
2013+
'in the process blacklist.') % (proto,
2014+
process_name))
2015+
return True, process_name, pid
2016+
2017+
# Check per-listener blacklisted process list
2018+
if self.listener_ports.isProcessBlackListHit(
2019+
proto, dport, process_name):
2020+
self.pdebug(DIGN, ('Ignoring %s request packet from ' +
2021+
'process %s in the listener process ' +
2022+
'blacklist.') % (proto, process_name))
2023+
return True, process_name, pid
2024+
return False, process_name, pid
19762025

19772026

19782027
class DiverterListenerCallbacks():
@@ -2011,3 +2060,7 @@ def mapProxySportToOrigSport(self, proto, orig_sport, proxy_sport,
20112060
self.__diverter.mapProxySportToOrigSport(proto, orig_sport, proxy_sport,
20122061
is_ssl_encrypted)
20132062

2063+
def isProcessBlackListed(self, proto, sport):
2064+
"""Check if the process is blacklisted.
2065+
"""
2066+
return self.__diverter.isProcessBlackListed(proto, sport=sport)

fakenet/fakenet.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self, logging_level = logging.INFO):
6464
def parse_config(self, config_filename):
6565
# Handling Pyinstaller bundle scenario: https://pyinstaller.org/en/stable/runtime-information.html
6666
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
67-
dir_path = os.getcwd()
67+
dir_path = os.path.dirname(sys.executable)
6868
else:
6969
dir_path = os.path.dirname(__file__)
7070

@@ -349,7 +349,7 @@ def main():
349349
| | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| |
350350
|_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____|
351351
352-
Version 3.2
352+
Version 3.3
353353
_____________________________________________________________
354354
Developed by FLARE Team
355355
Copyright (C) 2016-2024 Mandiant, Inc. All rights reserved.

fakenet/listeners/DNSListener.py

+38-9
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def __init__(
4949
self.logger.debug('Initialized with config:')
5050
for key, value in config.items():
5151
self.logger.debug(' %10s: %s', key, value)
52+
if self.logger.level == logging.INFO:
53+
self.logger.info('Hiding logs from blacklisted processes')
5254

5355
def start(self):
5456

@@ -81,18 +83,38 @@ def acceptDiverterListenerCallbacks(self, diverterListenerCallbacks):
8183

8284

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

85-
def parse(self,data):
101+
def parse(self, data):
86102
response = ""
87-
103+
proto = 'TCP' if self.server.socket_type == socket.SOCK_STREAM else 'UDP'
104+
is_process_blacklisted, process_name, pid = self.server \
105+
.diverterListenerCallbacks \
106+
.isProcessBlackListed(
107+
proto,
108+
sport=self.client_address[1])
109+
88110
try:
89111
# Parse data as DNS
90112
d = DNSRecord.parse(data)
91113

92114
except Exception as e:
93-
self.server.logger.error('Error: Invalid DNS Request')
115+
self.log_message(logging.ERROR, is_process_blacklisted, 'Error: Invalid DNS Request')
94116
for line in hexdump_table(data):
95-
self.server.logger.warning(INDENT + line)
117+
self.log_message(logging.WARNING, is_process_blacklisted, INDENT + line)
96118

97119
else:
98120
# Only Process DNS Queries
@@ -110,7 +132,14 @@ def parse(self,data):
110132
self.qname = qname
111133
self.qtype = qtype
112134

113-
self.server.logger.info('Received %s request for domain \'%s\'.', qtype, qname)
135+
if process_name is None or pid is None:
136+
self.log_message(logging.INFO, is_process_blacklisted,
137+
'Received %s request for domain \'%s\'.',
138+
qtype, qname)
139+
else:
140+
self.log_message(logging.INFO, is_process_blacklisted,
141+
'Received %s request for domain \'%s\' from %s (%s)',
142+
qtype, qname, process_name, pid)
114143

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

167196
if self.server.nxdomains > 0:
168-
self.server.logger.info('Ignoring query. NXDomains: %d',
197+
self.log_message(logging.INFO, is_process_blacklisted, 'Ignoring query. NXDomains: %d',
169198
self.server.nxdomains)
170199
self.server.nxdomains -= 1
171200
else:
172-
self.server.logger.debug('Responding with \'%s\'',
201+
self.log_message(logging.DEBUG, is_process_blacklisted, 'Responding with \'%s\'',
173202
fake_record)
174203
response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record)))
175204

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

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

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

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

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

fakenet/listeners/ListenerBase.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def abs_config_path(path):
3434
return abspath
3535

3636
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
37-
relpath = os.path.join(os.getcwd(), path)
37+
relpath = os.path.join(os.path.dirname(sys.executable), path)
3838
else:
3939

4040
# Try to locate the location relative to application path

fakenet/listeners/ProxyListener.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def run(self):
141141
self.listener_q.put(data)
142142
else:
143143
self.sock.close()
144-
exit(1)
144+
sys.exit(1)
145145
except Exception as e:
146146
self.logger.debug('Listener socket exception %s' % e.message)
147147

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
setup(
2828
name='FakeNet NG',
29-
version='3.2',
29+
version='3.3',
3030
description="",
3131
long_description="",
3232
author="Mandiant FLARE Team with credit to Peter Kacherginsky as the original developer",

0 commit comments

Comments
 (0)