diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4838749 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2016 Peter Kacherginsky + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index d71c0d4..991ea0c 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,739 @@ by Andrew Honig and Michael Sikorski. The tool allows you to intercept and redirect all or specific network traffic while simulating legitimate network services. Using FakeNet-NG, malware analysts -can quickly identify malware’s functionality and capture network signatures. -Penetration testers and bug hunters will find FakeNet-NG’s configurable +can quickly identify malware's functionality and capture network signatures. +Penetration testers and bug hunters will find FakeNet-NG's configurable interception engine and modular framework highly useful when testing -application’s specific functionality and prototyping PoCs. +application's specific functionality and prototyping PoCs. -Release -======== +Installation +============ + +You can install FakeNet-NG in a few different ways. + +Stand-alone executable +---------------------- + +It is easiest to simply download the compiled version which can be obtained from +the releases page: + + https://www.github.com/fireeye/flare-fakenet-ng/releases + +Execute FakeNet-NG by running 'fakenet.exe' from the downloaded directory. + +This is the preferred method as it does not require you to install any +additional modules, which is ideal for a malware analysis machine. + +Installing module +----------------- + +Alternatively you can install FakeNet-NG as a python module using pip: + + pip install https://github.com/fireeye/flare-fakenet-ng/zipball/master + +Or by obtaining the latest source code and installing it manually: + + git clone https://www.github.com/fireeye/flare-fakenet-ng/ + +Change directory to the downloaded flare-fakenet-ng and run: + + python setup.py install + +Execute FakeNet-NG by running 'fakenet' in any directory. + +No installation +--------------- + +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 2.7.x for the 64-bit or 32-bit versions + of Windows respectively. + +2) Install Python dependencies: + + pip install pydivert + pip install dnslib + pip install dpkt + + *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: + + pip install requests + +3) Download the FakeNet-NG source code: + + git clone https://github.com/fireeye/flare-fakenet-ng + +Execute FakeNet-NG by running it with a Python interpreter: + + python fakenet.py + +Usage +===== + +The easiest way to run FakeNet-NG is to simply execute the provided +executable as an Administrator. You can provide `--help` command-line +parameter to get simple help: + + C:\tools\fakenet-ng>fakenet.exe --help + ______ _ ________ _ _ ______ _______ _ _ _____ + | ____/\ | |/ / ____| \ | | ____|__ __| | \ | |/ ____| + | |__ / \ | ' /| |__ | \| | |__ | |______| \| | | __ + | __/ /\ \ | < | __| | . ` | __| | |______| . ` | | |_ | + | | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| | + |_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____| + + Version 1.0 + _____________________________________________________________ + Developed by + Peter Kacherginsky + FLARE (FireEye Labs Advanced Reversing Engineering) + _____________________________________________________________ + Usage: fakenet.py [options]: + + Options: + -h, --help show this help message and exit + -c FILE, --config-file=FILE + configuration filename + -v, --verbose print more verbose messages. + -l LOG_FILE, --log-file=LOG_FILE + +As you can see from the simple help above it is possible to configure the +configuration file used to start FakeNet-NG. By default, the tool uses +`configs\default.ini`; however, it can be changed with the `-c` parameter. +There are several example configuration files in the `configs` directory. +Due to the large number of different settings, FakeNet-NG relies on the +configuration files to control its functionality. + +NOTE: FakeNet-NG will attempt to locate the specified configuration file, first +by using the provided absolute or relative path in case you want to store all of +your configurations. If the specified configuration file is not found, +then it will try to look in its `configs` directory. + +The rest of the command-line options allow you to control the amount +of logging output displayed as well as redirecting it to a file as +opposed to dumping it on the screen. + +Simple run +---------- + +Before we dive in and run FakeNet-NG let's go over a few basic concepts. The +tool consists of several modules working together. One such important module is +the diverter which is responsible for redirecting traffic to a collection of +listeners. The diverter forces applications to interact with FakeNet-NG as +opposed to real servers. Listeners are individual services handling incoming +connections and allowing us to examine application's traffic (e.g. malware +signatures). + +Let's launch FakeNet-NG using default settings by running the following command: + + C:\tools\fakenet-ng>fakenet.exe + +Below is the annotated output log illustrating a sample intercepted DNS request +and an HTTP connection: + + ______ _ ________ _ _ ______ _______ _ _ _____ + | ____/\ | |/ / ____| \ | | ____|__ __| | \ | |/ ____| + | |__ / \ | ' /| |__ | \| | |__ | |______| \| | | __ + | __/ /\ \ | < | __| | . ` | __| | |______| . ` | | |_ | + | | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| | + |_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____| + + Version 1.0 + _____________________________________________________________ + Developed by + Peter Kacherginsky + FLARE (FireEye Labs Advanced Reverse Engineering) + _____________________________________________________________ + + 07/06/16 10:20:52 PM [ FakeNet] Loaded configuration file: configs/default.ini + / + default configuration file / + + 07/06/16 10:20:52 PM [ Diverter] Capturing traffic to packets_20160706_222052.pcap + / + PCAP output file / + + 07/06/16 10:20:52 PM [ FakeNet] Anonymous Forwarder listener on TCP port 8080... + \ + \ Anonymous Listener rule + + 07/06/16 10:20:52 PM [ RawTCPListener] Starting... + 07/06/16 10:20:52 PM [ RawUDPListener] Starting... + 07/06/16 10:20:52 PM [ FilteredListener] Starting... + 07/06/16 10:20:52 PM [ DNS Server] Starting... + 07/06/16 10:20:52 PM [ HTTPListener80] Starting... + 07/06/16 10:20:52 PM [ HTTPListener443] Starting... + 07/06/16 10:20:52 PM [ SMTPListener] Starting... + 07/06/16 10:20:52 PM [ Diverter] Starting... + \ + \ Listeners starting up + + 07/06/16 10:20:52 PM [ Diverter] Diverting ports: + 07/06/16 10:20:52 PM [ Diverter] TCP: 1337, 80, 443, 25 + 07/06/16 10:20:52 PM [ Diverter] UDP: 1337, 53 + / + Summary of diverted ports / + + 07/06/16 10:21:03 PM [ Diverter] Modifying outbound external UDP request packet: + 07/06/16 10:21:03 PM [ Diverter] from: 192.168.250.140:49383 -> 4.2.2.1:53 + 07/06/16 10:21:03 PM [ Diverter] to: 192.168.250.140:49383 -> 192.168.250.140:53 + 07/06/16 10:21:03 PM [ Diverter] pid: 456 name: malware.exe + / + Intercepted traffic to the DNS server from malware.exe / + + 07/06/16 10:21:03 PM [ DNS Server] Received A request for domain 'evil.com'. + \ + \ Fake DNS Listener handling the above request + + 07/06/16 10:21:04 PM [ Diverter] Modifying outbound external TCP request packet: + 07/06/16 10:21:04 PM [ Diverter] from: 192.168.250.140:2179 -> 192.0.2.123:80 + 07/06/16 10:21:04 PM [ Diverter] to: 192.168.250.140:2179 -> 192.168.250.140:80 + 07/06/16 10:21:04 PM [ Diverter] pid: 456 name: malware.exe + / + Intercepted traffic to the web server from malware.exe / + + 07/06/16 10:21:08 PM [ HTTPListener80] Received a GET request. + 07/06/16 10:21:08 PM [ HTTPListener80] -------------------------------------------------------------------------------- + 07/06/16 10:21:08 PM [ HTTPListener80] GET / HTTP/1.0 + 07/06/16 10:21:08 PM [ HTTPListener80] + 07/06/16 10:21:08 PM [ HTTPListener80] -------------------------------------------------------------------------------- + \ + \ Fake HTTP Listener handling the above request + +Notice that each log line has a name of the currently running FakeNet-NG +modules. For example, when it is diverting traffic, the logs will be prefixed +with the `Diverter` label: + + 07/06/16 10:21:03 PM [ Diverter] Modifying outbound external UDP request packet: + 07/06/16 10:21:03 PM [ Diverter] from: 192.168.250.140:49383 -> 4.2.2.1:53 + 07/06/16 10:21:03 PM [ Diverter] to: 192.168.250.140:49383 -> 192.168.250.140:53 + 07/06/16 10:21:03 PM [ Diverter] pid: 456 name: malware.exe + +At the same time, whenever individual listeners are handling diverted traffic, +logs will be labeled with the name set in the configuration file: + + 07/06/16 10:21:03 PM [ DNS Server] Received A request for domain 'evil.com'. + +To stop FakeNet-NG and close out the generated PCAP file simply press `CTRL-C`: + + 07/06/16 10:21:41 PM [ FakeNet] Stopping... + 07/06/16 10:21:42 PM [ HTTPListener80] Stopping... + 07/06/16 10:21:42 PM [ HTTPListener443] Stopping... + 07/06/16 10:21:42 PM [ SMTPListener] Stopping... + 07/06/16 10:21:43 PM [ Diverter] Stopping... + +Configuration +------------- + +In order to take full advantage of FakeNet-NG's capabilities we must understand +its configuration file structure and settings. Below is a sample configuration +file: + + ############################################################################### + # Fakenet Configuration + + [FakeNet] + + DivertTraffic: Yes + + ############################################################################### + # Diverter Configuration + + [Diverter] + + DumpPackets: Yes + DumpPacketsFilePrefix: packets + + ModifyLocalDNS: No + StopDNSService: Yes + + ############################################################################### + # Listener Configuration + + [DNS Server] + Enabled: True + Port: 53 + Protocol: UDP + Listener: DNSListener + DNSResponse: 192.0.2.123 + NXDomains: 0 + + [RawTCPListener] + Enabled: True + Port: 1337 + Protocol: TCP + Listener: RawListener + UseSSL: No + Timeout: 10 + +The configuration file is broken up into several sections. + +* **[FakeNet]** - Controls the behavior of the application itself. The only valid +option at this point is `DivertTraffic`. When enabled, it instructs the tool +to launch the appropriate diverter plugin and intercept traffic. If this option +is disabled, FakeNet-NG will still launch listeners, but will rely on another +method to direct traffic to them (e.g. manually change DNS server). + +* **[Diverter]** - Settings for redirecting traffic. Covered in detail below. + +* **[Listener Name]** - A collection of listener configurations. Each listener +has a set of default settings (e.g. port, protocol) as well as listener +specific configurations (e.g. DumpHTTPPosts for the HTTPListener). + +Diverter Configuration +---------------------- + +Considering you have enabled the `DivertTraffic` setting in the `[FakeNet]` +configuration block, the tool will enable its traffic redirection engine to +which we will call `diverter` from now on as a reference to the excellent +`WinDiverter` library used to perform the magic behind the scenes. + +The diverter will examine all of the outgoing packets and match them against +a list of protocols, ports of enabled listeners. If there is a listener +listening on the packet's port and protocol, then the destination address +will be changed to the local machine's IP address where the listener will +handle the request. At the same time, responses coming from the listener +will be changed so that the source IP address would appear as if the packet +is coming from the originally requested host. + +You can optionally enable the `DumpPackets` setting to store all traffic +observed by FakeNet-NG (redirected or forwarded) to a PCAP file. It is possible +to decrypt SSL traffic between an intercepted application and one of the +listeners with SSL support. Use the instructions at the following page: + + https://wiki.wireshark.org/SSL + +The keys `privkey.pem` and `server.pem` used by FakeNet-NG's servers are in the +application's root directory. + +Last but not least, the Windows diverter supports two DNS related settings: + +* **ModifyLocalDNS** - modify local machine's DNS service. +* **StopDNSService** - stops Windows DNS client (Dnscache). This allows FakeNet + to see the actual processes resolving domains as opposed + to the generic 'svchost.exe' process. + +Redirecting All Traffic +----------------------- + +By default the diverter will only intercept traffic that has a dedicated +listener created for it. However, by enabling `RedirectAllTraffic` setting +and configuring the default TCP and UDP handlers with the `DefaultTCPListener` +and `DefaultUDPListener` settings it is possible to dynamically handle traffic +going to ports not explicitly defined in one of the listeners. For example, +let's look at a sample configuration which redirects all traffic to +local TCP and UDP listeners on ports 1234: + + RedirectAllTraffic: Yes + DefaultTCPListener: TCPListener1234 + DefaultUDPListener: UDPListener1234 + +*NOTE*: We are jumping a bit ahead with listener definitions, but just +consider that `TCPListener1234` and `UDPListener1234` will be defined in +the section below. + +With the `RedirectAllTraffic` setting, FakeNet-NG will modify not only the +destination address, but also the destination port so it can be handled +by one of the default listeners. Below is a sample log of traffic destined to +an external host IP address 1.1.1.1 on port 4444 which was redirected to the +default listener on port 1234 instead: + + 07/06/16 01:13:47 AM [ Diverter] Modifying outbound external TCP request packet: + 07/06/16 01:13:47 AM [ Diverter] from: 192.168.66.129:1650 -> 1.1.1.1:4444 + 07/06/16 01:13:47 AM [ Diverter] to: 192.168.66.129:1650 -> 192.168.66.129:1234 + 07/06/16 01:13:47 AM [ Diverter] pid: 3716 name: malware.exe + +It is important to note that traffic destined to the port from one of the +explicitly defined listeners will still be handled by that listener and +not the default listener. For example, default UDP listener will not handle +DNS traffic if a separate UDP port 53 DNS listener is defined. + +One issue when enabling the `RedirectAllTraffic` options is that you may +still want to let some traffic through to ensure normal operation of the +machine. Consider a scenario where you are trying to analyze an application + that still needs to connect to an external DNS server. You can utilize the +`BlackListPortsTCP` and `BlackListPortsUDP` settings to define a list of +ports to which traffic will be ignored and forwarded unaltered: + + BlackListPortsUDP: 53 + +Some other diverter settings that you may consider are `ProcessBlackList` +and `HostBlackList` which allow diverter to ignore and forward traffic +coming from a specific process name or destined for a specific host +respectively. + +Listener Configurations +---------------------- + +Listener configurations define the behavior of individual listeners. Let's +look at a sample listener configuration: + + [TCPListener1234] + Enabled: True + Port: 1234 + Protocol: TCP + Listener: RawListener + UseSSL: Yes + Timeout: 10 + +The configuration above consists of the listener name `TCPListener1234`. It +will be used for logging purposes so you can distinguish between different +listeners handling connections even if they are handling the same protocol. + +The following settings are generic for all listeners: + + * **Enabled** - specify whether or not the listener is enabled. + * **Port** - TCP or UDP port to listen on. + * **Protocol** - TCP or UDP + * **Listener** - Listener name to handle traffic. + * **ProcessWhiteList** - Only traffic from these processes will be modified + and the rest will simply be forwarded. + * **ProcessBlackList** - Traffic from all but these processes will be simply + forwarded and the rest will be modified as needed. + * **HostWhiteList** - Only traffic to these hosts will be modified and + the rest will be simply forwarded. + * **HostBlackList** - Traffic to these hosts will be simply forwarded + and the rest will be modified as needed. + * **ExecuteCmd** - Execute command on the first connection packet. This + feature is useful for extending FakeNet-NG's functionality + (e.g. launch a debugger on the connecting pid to help with + unpacking and decoding.) + +The `Port` and `Protocol` settings are necessary for the listeners to know to +which ports to bind and, if they support multiple protocol (e.g RawListener), +decide which protocol to use. They are also used by the diverter to figure out +which ports and protocols to redirect. + +The `Listener` setting defines one of the available listener plugins to handle +redirected traffic. The current version of FakeNet-NG comes with the following +listeners: + +* **DNSListener** - supports DNS protocol and replies to A records with either + a local machine's IP address or a configurable address in + the `DNSResponse` setting. You can also set the `NXDomains` + attribute to the number of requests the listener should ignore. + This way you may be able to get the malware to request all of + its backup C2 controller names. The listener supports both TCP + and UDP protocols. +* **RawListener** - supports basic TCP and UDP binary protocols. The default + behavior is to simply echo the received packets back to + the client. Supports SSL connections. +* **HTTPListener** - supports HTTP and HTTPS protocols. Responds with different + files in the configurable `Webroot` directory based on the + requested file extension. Optionally dumps POST requests to + a configurable file which can be specified using + `DumpHTTPPosts` and `DumpHTTPPostsFilePrefix` settings. +* **SMTPListener** - supports SMTP protocol. + + +NOTE: FakeNet-NG will attempt to locate the webroot directory, first by using +the provided absolute or relative paths. If the specified webroot path is not +found, then it will try to look in its `defaultFiles` directory. + +As a special case, FakeNet-NG automatically responds to all ICMP requests while +running. So in case a malware attempts to ping a host to test connectivity it +will get a valid response. + +Listener Filtering +------------------ + +FakeNet-NG supports several filtering rules consisting of process and host +blacklists and whitelists. The whitelists are treated as the rules that allow +connections to the listeners while the blacklists are used to ignore the +incoming connections and let them to be simply forwarded. + +For example, consider the configuration below with process and host filters: + + [FilteredListener] + Enabled: True + Port: 31337 + Protocol: TCP + Listener: RawListener + UseSSL: No + Timeout: 10 + ProcessWhiteList: malware.exe, ncat.exe + HostBlackList: 5.5.5.5 + +The `FilteredListener` above will only handle connection coming from the +processes `malware.exe` and `ncat.exe`, but will ignore any connections +destined for the host `5.5.5.5`. Meaning that if a process called `test.exe` +attempted to connect on port 31337 it will not be redirected to the listener +and will be forwarded to wherever it was originally intended if the route +is available. + +At the same time of the process `malware.exe` attempted to connect to port 31337 +on any host other than `5.5.5.5` it will be diverted +to the `FilteredListener`. Any connections from the process `malware.exe` +destined to `5.5.5.5` would be allowed through. + +Listener Command Execution +-------------------------- + +Another powerful configuration setting is `ExecuteCmd`. It essentially +allows you to execute an arbitrary command on the first detected packet +of the connection. The value of `ExecuteCmd` can use several format string +variables: + + * `{pid}` - process id + * `{procname}` - process executable name + * `{src_addr}` - source address + * `{src_port}` - source port + * `{dst_addr}` - destination address + * `{dst_port}` - destination port + +Consider a scenario of a packed malware sample which connects to a configured +C2 server on port 8443 (Use `RedirectAllTraffic` if the port is not known). In +many cases the malware would unpack itself by the time it makes the connection +making that point in execution ideal to attach to the process with a debugger +and dump an unpacked version of it for further analysis. + +Let's see how this can be used to automatically launch a debugger on the +first connection: + + [C2Listener] + Enabled: True + Port: 8443 + Protocol: TCP + Listener: RawListener + UseSSL: Yes + Timeout: 300 + ProcessWhiteList: malware.exe + ExecuteCmd: C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe -p {pid} + +Once FakeNet-NG detects a new connection coming from the whitelisted process +`malware.exe` (this setting is optional), it will automatically launch `windbg` +and attach it to the connecting process. + +*NOTE*: You might want to extend the normal `Timeout` setting in case the malware + needs to further interact with the listener. + +Anonymous Listener +------------------ + +There is a special use case where you can create a new listener configuration +without defining the actual listener to handle it: + + [Forwarder] + Enabled: True + Port: 8080 + Protocol: TCP + ProcessWhiteList: chrome.exe + +Without a listener defined, FakeNet-NG will still divert traffic to the local +machine, but a separate listener must be launched by the user. For example, +you could have an HTTP proxy listening for connections on port 8080 and let +FakeNet-NG intercept all the traffic from applications which do not use system's +proxy server settings or use hard-coded IP addresses. Using anonymous listeners +you can bring FakeNet-NG's advanced traffic and process filtering capabilities +to 3rd party tools. + +You may also want to enable diverter's `ProcessBlackList` setting to allow +the external tool to communicate out to the Internet. For example, to allow +an HTTP proxy to forward proxied traffic add its process name to the process +blacklist. For example, add the following process to let Burp Proxy to +communicate out to the Internet: + + ProcessBlackList: java.exe + +In the scenario where application communicates on an unknown port, but you still +want to redirect it to the anonymous listener on port 8080 you can define the +default listener as follows: + + RedirectAllTraffic: Yes + DefaultTCPListener: ForwarderTCP + DefaultUDPListener: RawUDPListener + +Finally, to allow DNS traffic to still go to the default DNS server on the +Internet, while redirecting all other traffic, add port 53 to the diverter's +UDP port blacklist as follows: + + BlackListPortsUDP: 53 + +Development +=========== + +FakeNet-NG is developed in Python which allows you to rapidly develop new +plugins and extend existing functionality. + +Developing Listeners +-------------------- + +All listeners must implement just two methods: start() and stop(). Below is a +sample listener template: + + + import logging + + import sys + + import threading + import socket + + class MyListener(): + + def __init__(self, config, name = 'MyListener', logging_level = logging.INFO): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging_level) + + self.config = config + self.name = name + + self.logger.info('Starting...') + + self.logger.debug('Initialized with config:') + for key, value in config.iteritems(): + self.logger.debug(' %10s: %s', key, value) + + def start(self): + + # Start listener + # ... + + def stop(self): + + # Stop listener + # ... + +The main listener class `MyListener()` will be provided with a parsed +configuration dictionary containing information such as port to listen on, +protocol, etc. The main listener class will also receive the current listener +instance name and the logging info set by the user. + +The only requirement for listener implementation is that you use threading so +that when FakeNet-NG calls the `start()` method, the listener will not block +but spawn a new thread that handles incoming connections. + +Another requirement is to ensure that the listener can reliably shutdown when +the `stop()` method is called. For example, make use of connection timeouts to +ensure that the listener does not block on some connection for too long. + +The logging convention used by FakeNet-NG's listeners is to use the self.logger +object for the output. That way the logging is uniform across the application. +For example to display an error or warning you would use the following: + + self.logger.error("This is an error") + self.logger.warning("This is a warning") + +Finally, after you finish developing the listener, copy it to the `listeners\` +directory and append you module name to `__all__` varialbe in the `listeners\__init__`: + + __all__ = ['RawListener', 'HTTPListener', 'DNSListener', 'SMTPListener', 'MyListener'] + +At this point you can let the application use the newly created listener by +adding it to the configuration file: + + [MyAwesomeListener] + Enabled: True + Port: 1337 + Protocol: TCP + Listener: MyListener + +Developing Diverters +-------------------- + +FakeNet-NG uses the open source WinDivert library in order to perform the +traffic redirection on Windows Vista+ operating systems. The implementation of +the windows diverter is located in `diverters\windows.py`. + +It is important to note that a lot of the Windows specific functionality is +actually implemented in the `diverters\winutil.py` file using ctypes to call +many of the Windows API functions directly. + +It is planned to expand the support for traffic divertion + +Building standalone executables +------------------------------- + +If you would like to generate a stand-alone executable you would need to +install the PyInstaller module: + + pip install pyinstaller + +To generate the exe file run the pyinstaller as follows: + + pyinstaller fakenet-ng.spec + +The standalone executable will be available in the `dist/` directory. + +Known Issues +============ + +Does not work on VMWare with host-only mode enabled +--------------------------------------------------- + +See "Not Intercepting Traffic" below. + +Not Intercepting Traffic +------------------------ + +In order to for FakeNet-NG to intercept and modify the packet, there must exist +a valid network route for the packet to reach its destination. + +There is an easy way to check whether or not you have routes set up correctly. +Without the tool running attempt to ping the destination host. You should +observe either a valid response or a timeout message. If you receive a +destination not reachable error instead, then you do not have a valid route. + +This is usually caused by your gateway being either not set or not reachable. +For example, on a VMWare machine with host-only mode your machine will not have +the gateway configured thus preventing FakeNet-NG from seeing any traffic. + +To correct this issue, manually configure your primary interface to the gateway +in the same subnet. First check the interface name: + + C:\>netsh interface show interface + + Admin State State Type Interface Name + ------------------------------------------------------------------------- + Enabled Connected Dedicated Local Area Connection + +In this case the interface name is "Local Area Connection" so we will use it for +the rest of the commands. + +Manually configure the interface IP address and gateway as follows: + + C:\>netsh interface ip set address name="Local Area Connection" static 192.168.249.123 255.255.255.0 192.168.249.254 + +Manually set the DNS server IP address + + C:\>netsh interface ip set dns name="Local Area Connection" static 4.2.2.2 + +If you are still having issue ensure that the gateway IP address itself is +routable. + +Error: Could not locate WinDivert DLL or one of its components +-------------------------------------------------------------- + +Please ensure that FakeNet-NG is extracted to the local C: drive to make +sure the WinDivert driver is loaded correctly. + +Limitations +=========== + +* Only Windows Vista+ is supported. Please use the original Fakenet for + Windows XP/2003 operating systems. + +* Local machine only traffic is not intercepted (e.g. if you tried to connect + directly to one of the listeners). + +* Only traffic using TCP, UDP, and ICMP protocols are intercepted. + +Credits +======= + +* FakeNet-NG was designed and developed by Peter Kacherginsky. +* Special thanks to Andrew Honig, Michael Sikorski and others for the + original FakeNet which was the inspiration to develop this tool. + +Contact +======= + +For bugs, crashes, or other comments please contact +peter.kacherginsky@fireeye.com -FakeNet-NG will be released during BlackHat 2016. Come visit our BlackHat -Arsenal booth for a demo and a presentation of using FakeNet-NG for -malware analysis and presentation at the Arsenal Theater on Wednesday, -August 3rd. diff --git a/fakenet.spec b/fakenet.spec new file mode 100644 index 0000000..a6f9055 --- /dev/null +++ b/fakenet.spec @@ -0,0 +1,36 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['fakenet/fakenet.py'], + pathex=['fakenet'], + binaries=None, + datas=None, + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + icon='resources/fakenet.ico', + name='fakenet', + debug=False, + strip=False, + upx=True, + console=True, + uac_admin=True) + +coll = COLLECT(exe, + strip=False, + upx=True, + name='fakenet-dat' +) diff --git a/fakenet/__init__.py b/fakenet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fakenet/configs/burp.ini b/fakenet/configs/burp.ini new file mode 100644 index 0000000..5826cfe --- /dev/null +++ b/fakenet/configs/burp.ini @@ -0,0 +1,51 @@ +############################################################################### +# Fakenet Configuration + +[FakeNet] + +DivertTraffic: Yes + +############################################################################### +# Diverter Configuration + +[Diverter] + +DumpPackets: No +DumpPacketsFilePrefix: packets + +ModifyLocalDNS: No +StopDNSService: Yes + +RedirectAllTraffic: Yes +DefaultTCPListener: ForwarderTCP +DefaultUDPListener: ForwarderUDP + +BlackListPortsTCP: 139 +BlackListPortsUDP: 53, 67, 68, 137, 138, 1900, 5355 + +# Ignore traffic generated by the java.exe process to let +# Burp communicate out. +ProcessBlackList: java.exe + +############################################################################### +# Listener Configuration + +[ForwarderTCP] +Enabled: True +Port: 8080 +Protocol: TCP +ProcessWhiteList: chrome.exe, firefox.exe, ncat.exe + +[ForwarderUDP] +Enabled: True +Port: 1234 +Protocol: UDP +ProcessWhiteList: chrome.exe + +[DNS Server] +Enabled: False +Port: 53 +Protocol: UDP +Listener: DNSListener +DNSResponse: 127.0.0.1 +NXDomains: 0 \ No newline at end of file diff --git a/fakenet/configs/debug.ini b/fakenet/configs/debug.ini new file mode 100644 index 0000000..7375101 --- /dev/null +++ b/fakenet/configs/debug.ini @@ -0,0 +1,55 @@ +############################################################################### +# Fakenet Configuration + +[FakeNet] + +DivertTraffic: Yes + +############################################################################### +# Diverter Configuration + +[Diverter] + +DumpPackets: No +DumpPacketsFilePrefix: packets + +ModifyLocalDNS: No +StopDNSService: Yes + +RedirectAllTraffic: Yes +DefaultTCPListener: RawTCPListener +DefaultUDPListener: RawUDPListener + +BlackListPortsTCP: 139 +BlackListPortsUDP: 67, 68, 137, 138, 1900, 5355 + +ProcessBlackList: windbg.exe + +############################################################################### +# Listener Configuration + +[RawTCPListener] +Enabled: True +Port: 1337 +Protocol: TCP +Listener: RawListener +UseSSL: No +Timeout: 300 +ProcessWhiteList: ncat.exe +ExecuteCmd: C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe -p {pid} + +[RawUDPListener] +Enabled: True +Port: 1337 +Protocol: UDP +Listener: RawListener +UseSSL: No +Timeout: 10 + +[DNS Server] +Enabled: Yes +Port: 53 +Protocol: UDP +Listener: DNSListener +DNSResponse: 192.0.2.123 +NXDomains: 0 \ No newline at end of file diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini new file mode 100644 index 0000000..a6b4ca9 --- /dev/null +++ b/fakenet/configs/default.ini @@ -0,0 +1,177 @@ +############################################################################### +# Fakenet Configuration + +[FakeNet] + +# Specify whether or not FakeNet should divert traffic. Disable if you want to +# just start listeners and direct traffic manually (e.g. modify DNS server) +DivertTraffic: Yes + +############################################################################### +# Diverter Configuration + +[Diverter] + +# Specify whether or not to save captured traffic. You can also change +# the file prefix for the generated PCAPs. +DumpPackets: Yes +DumpPacketsFilePrefix: packets + +# Enable 'ModifyLocalDNS' to statically set DNS server to the local machine. +ModifyLocalDNS: No + +# Enable 'StopDNSService' to stop Windows DNS client to see the actual +# processes resolving domains. +StopDNSService: Yes + +# Enable 'RedirectAllTraffic' to optionally divert traffic going to ports not +# specifically listed in one of the listeners below. 'DefaultTCPListener' and +# '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 +# 'BlackListPortsUDP' below so that system's default DNS server is used instead. + +RedirectAllTraffic: Yes +DefaultTCPListener: RawTCPListener +DefaultUDPListener: RawUDPListener + +# 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 +# function normally +# +# NOTE: This setting is only honored when 'RedirectAllTraffic' is enabled. + +BlackListPortsTCP: 139 +BlackListPortsUDP: 67, 68, 137, 138, 1900, 5355 + +# Specify processes to ignore when diverting traffic. +# ProcessBlackList: java.exe + +# Specify hosts to ignore when diverting traffic. +# HostBlackList: 6.6.6.6 + +############################################################################### +# Listener Configuration +# +# Listener configuration consists of generic settings used by the diverter which +# are the same for all listeners and listener specific settings. +# +# NOTE: Listener section names will be used for logging. +# +# NOTE: Settings labels are not case-sensitive. +# +# The following settings are available for all listeners: +# * Enabled - specify whether or not the listener is enabled. +# * Port - TCP or UDP port to listen on. +# * Protocol - TCP or UDP +# * Listener - Listener name to handle traffic. +# * ProcessWhiteList - Only traffic from these processes will be modified +# and the rest will simply be forwarded. +# * ProcessBlackList - Traffic from all but these processes will be simply forwarded +# and the rest will be modified as needed. +# * HostWhiteList - Only traffic to these hosts will be modified and +# the rest will be simply forwarded. +# * HostBlackList - Traffic to these hosts will be simply forwarded +# and the rest will be modified as needed. +# * ExecuteCmd - Execute command on the first connection packet. This is feature is useful +# for extending FakeNet-NG's functionality (e.g. launch a debugger on the +# connecting pid to help with unpacking and decoding.) +# +# The following format string variables are made available: +# * {pid} - process id +# * {procname} - process executable name +# * {src_addr} - source address +# * {src_port} - source port +# * {dst_addr} - destination address +# * {dst_port} - destination port +# +# Listener entry which does not specify a specific listener service +# will still redirect all packets to the local machine on the specified port and +# subject to all the filters (processes, hosts, etc.). However, you must set-up a +# third party service (e.g. proxy servers) to accept these connections. This feature can be +# used to provide FakeNet-NG's passive traffic diverting and filtering capabilities to other +# applications. +# +# Listener specific settings: +# +# * Timeout - Set connection timeout for any listeners that support +# TCP connections (e.g. RawListener, DNSListener, HTTPListener +# SMTPListener). +# * UseSSL - Enable SSL support on the listener (RawListener, HTTPListener) +# * Webroot - Set webroot path for HTTPListener. +# * DumpHTTPPosts - Store HTTP Post requests for the HTTPListener. +# * DumpHTTPPostsFilePrefix - File prefix for the stored HTTP Post requests used by the HTTPListener. +# * DNSResponse - IP address to respond with for A record DNS queries. (DNSListener) +# * NXDomains - A number of DNS requests to ignore to let the malware cycle through +# all of the backup C2 servers. (DNSListener) + +[Forwarder] +Enabled: False +Port: 8080 +Protocol: TCP +ProcessWhiteList: chrome.exe + +[RawTCPListener] +Enabled: True +Port: 1337 +Protocol: TCP +Listener: RawListener +UseSSL: No +Timeout: 10 + +[RawUDPListener] +Enabled: True +Port: 1337 +Protocol: UDP +Listener: RawListener +UseSSL: No +Timeout: 10 + +[FilteredListener] +Enabled: False +Port: 31337 +Protocol: TCP +Listener: RawListener +UseSSL: No +Timeout: 10 +ProcessWhiteList: ncat.exe, nc.exe +HostBlackList: 5.5.5.5 + +[DNS Server] +Enabled: True +Port: 53 +Protocol: UDP +Listener: DNSListener +DNSResponse: 192.0.2.123 +NXDomains: 0 + +[HTTPListener80] +Enabled: True +Port: 80 +Protocol: TCP +Listener: HTTPListener +UseSSL: No +Webroot: defaultFiles/ +Timeout: 10 +#ProcessBlackList: dmclient.exe, OneDrive.exe, svchost.exe, backgroundTaskHost.exe, GoogleUpdate.exe, chrome.exe +DumpHTTPPosts: Yes +DumpHTTPPostsFilePrefix: http + +[HTTPListener443] +Enabled: True +Port: 443 +Protocol: TCP +Listener: HTTPListener +UseSSL: Yes +Webroot: defaultFiles/ +DumpHTTPPosts: Yes +DumpHTTPPostsFilePrefix: http + +[SMTPListener] +Enabled: True +Port: 25 +Protocol: TCP +Listener: SMTPListener +UseSSL: No \ No newline at end of file diff --git a/fakenet/defaultFiles/FakeNet.gif b/fakenet/defaultFiles/FakeNet.gif new file mode 100644 index 0000000..786bb11 Binary files /dev/null and b/fakenet/defaultFiles/FakeNet.gif differ diff --git a/fakenet/defaultFiles/FakeNet.html b/fakenet/defaultFiles/FakeNet.html new file mode 100644 index 0000000..8f327a0 --- /dev/null +++ b/fakenet/defaultFiles/FakeNet.html @@ -0,0 +1,37 @@ + + +FakeNet-NG + + + +
+     ______      _  ________ _   _ ______ _______     _   _  _____ 
+    |  ____/\   | |/ /  ____| \ | |  ____|__   __|   | \ | |/ ____|
+    | |__ /  \  | ' /| |__  |  \| | |__     | |______|  \| | |  __ 
+    |  __/ /\ \ |  < |  __| | . ` |  __|    | |______| . ` | | |_ |
+    | | / ____ \| . \| |____| |\  | |____   | |      | |\  | |__| |
+    |_|/_/    \_\_|\_\______|_| \_|______|  |_|      |_| \_|\_____|
+
+                       H T T P   L I S T E N E R                   
+
+ +

FakeNet-NG 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.

+ +

The tool allows you to intercept and redirect all or specific network traffic +while simulating legitimate network services. Using FakeNet-NG, malware analysts +can quickly identify malware's functionality and capture network signatures. +Penetration testers and bug hunters will find FakeNet-NG's configurable +interception engine and modular framework highly useful when testing +application's specific functionality and prototyping PoCs.

+ +

FakeNet-NG is based on the excellent Fakenet tool developed +by Andrew Honig and Michael Sikorski.

+ +

Contact

+ +For bugs, crashes, or other comments please contact Peter Kacherginsky by email +peter.kacherginsky@fireeye.com or Twitter @_iphelix. + + \ No newline at end of file diff --git a/fakenet/defaultFiles/FakeNet.ico b/fakenet/defaultFiles/FakeNet.ico new file mode 100644 index 0000000..8431d83 Binary files /dev/null and b/fakenet/defaultFiles/FakeNet.ico differ diff --git a/fakenet/defaultFiles/FakeNet.jpg b/fakenet/defaultFiles/FakeNet.jpg new file mode 100644 index 0000000..f58ce1a Binary files /dev/null and b/fakenet/defaultFiles/FakeNet.jpg differ diff --git a/fakenet/defaultFiles/FakeNet.pdf b/fakenet/defaultFiles/FakeNet.pdf new file mode 100644 index 0000000..9399b8c Binary files /dev/null and b/fakenet/defaultFiles/FakeNet.pdf differ diff --git a/fakenet/defaultFiles/FakeNet.png b/fakenet/defaultFiles/FakeNet.png new file mode 100644 index 0000000..109479c Binary files /dev/null and b/fakenet/defaultFiles/FakeNet.png differ diff --git a/fakenet/defaultFiles/FakeNetMini.exe b/fakenet/defaultFiles/FakeNetMini.exe new file mode 100644 index 0000000..94bb4aa Binary files /dev/null and b/fakenet/defaultFiles/FakeNetMini.exe differ diff --git a/fakenet/diverters/__init__.py b/fakenet/diverters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fakenet/diverters/__init__.pyc b/fakenet/diverters/__init__.pyc new file mode 100644 index 0000000..ca7d9ab Binary files /dev/null and b/fakenet/diverters/__init__.pyc differ diff --git a/fakenet/diverters/windows.py b/fakenet/diverters/windows.py new file mode 100644 index 0000000..16e5f3d --- /dev/null +++ b/fakenet/diverters/windows.py @@ -0,0 +1,663 @@ +# Diverter for Windows implemented using WinDivert library + +import logging + +from pydivert.windivert import * +from pydivert.enum import Direction, Defaults + +import socket + +import os + +import dpkt + +import time +import threading + +import platform + +from winutil import * + +import subprocess + +class Diverter(WinUtilMixin): + + def __init__(self, diverter_config, listeners_config, logging_level = logging.INFO): + + self.logger = logging.getLogger('Diverter') + self.logger.setLevel(logging_level) + + self.diverter_config = diverter_config + self.listeners_config = listeners_config + + # Local IP address + self.external_ip = socket.gethostbyname(socket.gethostname()) + self.loopback_ip = socket.gethostbyname('localhost') + + # Sessions cache + # NOTE: A dictionary of source ports mapped to destination address, port tuples + self.sessions = dict() + + ####################################################################### + # Listener specific configuration + # NOTE: All of these definitions have protocol as the first key + # followed by a list or another nested dict with the actual definitions + + # Diverted ports + self.diverted_ports = dict() + + # Listener Port Process filtering + # TODO: Allow PIDs + self.port_process_whitelist = dict() + self.port_process_blacklist = dict() + + # Listener Port Host filtering + # TODO: Allow domain name resolution + self.port_host_whitelist = dict() + self.port_host_blacklist = dict() + + # Execute command list + self.port_execute = dict() + + # Parse listener configurations + self.parse_listeners_config(listeners_config) + + ####################################################################### + # Diverter settings and filters + + # Intercept filter + # NOTE: All relevant connections are recorded as outbound by WinDivert + # so additional filtering based on destination port will need to be + # performed in order to determine the correct traffic direction. + self.filter = None + + # Default TCP/UDP listeners + self.default_listener_tcp_port = None + self.default_listener_udp_port = None + + # Global TCP/UDP port blacklist + self.blacklist_ports_tcp = [] + self.blacklist_ports_udp = [] + + # Global process blacklist + # TODO: Allow PIDs + self.blacklist_processes = [] + + # Global host blacklist + # TODO: Allow domain resolution + self.blacklist_hosts = [] + + # Parse diverter config + self.parse_diverter_config() + + ####################################################################### + # Network verification + + # Check active interfaces + if not self.check_active_ethernet_adapters(): + self.logger.warning('WARNING: No active ethernet interfaces detected!') + self.logger.warning(' Please enable a network interface.') + + # Check configured gateways + if not self.check_gateways(): + self.logger.warning('WARNING: No gateways configured!') + self.logger.warning(' Please configure a default gateway or route in order to intercept external traffic.') + + # Check configured DNS servers + if not self.check_dns_servers(): + self.logger.warning('WARNING: No DNS servers configured!') + self.logger.warning(' Please configure a DNS server in order to allow network resolution.') + + ####################################################################### + # Initialize WinDivert + + # Locate the WinDivert driver + # NOTE: This is necessary to work in scenarios where the applications is + # executed as a python script, installed as an egg or with the pyinstaller + + dll_arch = "64" if platform.machine() == 'AMD64' else "32" + + dll_path = os.path.join('lib', dll_arch, 'WinDivert.dll') + + if not os.path.exists(dll_path): + + dll_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib', dll_arch, 'WinDivert.dll') + + if not os.path.exists(dll_path): + + self.logger.error('Could not open bundled WinDivert.dll') + sys.exit(1) + + # Divert handle + driver = None + + driver = WinDivert(dll_path = dll_path) + + try: + self.handle = Handle(driver, filter=self.filter) + self.handle.open() + except WindowsError, e: + if e.winerror == 5: + self.logger.error('ERROR: Insufficient privileges to run windows diverter.') + self.logger.error(' Please restart with Administrator privileges.') + sys.exit(1) + elif e.winerror == 3: + self.logger.error('ERROR: Could not locate WinDivert DLL or one of its components.') + self.logger.error(' Please make sure you have copied FakeNet-NG to the C: drive.') + sys.exit(1) + else: + self.logger.error('ERROR: Failed to open a handle to the WinDivert driver: %s', e) + sys.exit(1) + + # Capture packets configuration + self.capture_flag = False + self.dump_packets_file_prefix = "packets" + self.pcap = None + + if self.diverter_config.get('dumppackets') and self.diverter_config['dumppackets'].lower() == 'yes': + self.capture_flag = True + pcap_filename = "%s_%s.pcap" % (diverter_config.get('dumppacketsfileprefix', 'packets'), time.strftime("%Y%m%d_%H%M%S")) + + self.logger.info('Capturing traffic to %s', pcap_filename) + self.pcap = dpkt.pcap.Writer(open(pcap_filename, 'wb'), linktype=dpkt.pcap.DLT_RAW) + + ########################################################################### + # Parse listener specific settings and filters + + def parse_listeners_config(self, listeners_config): + + ####################################################################### + # Populate diverter ports and process filters from the configuration + for listener_name, listener_config in listeners_config.iteritems(): + + if 'port' in listener_config: + + port = int(listener_config['port']) + + if not 'protocol' in listener_config: + self.logger.error('ERROR: Protocol not defined for listener %s', listener_name) + sys.exit(1) + + protocol = listener_config['protocol'].upper() + + if not protocol in ['TCP', 'UDP']: + self.logger.error('ERROR: Invalid protocol %s for listener %s', protocol, listener_name) + sys.exit(1) + + if not protocol in self.diverted_ports: + self.diverted_ports[protocol] = list() + + self.diverted_ports[protocol].append(port) + + ############################################################### + # Process filtering configuration + if 'processwhitelist' in listener_config and 'processblacklist' in listener_config: + self.logger.error('ERROR: Listener can\'t have both process whitelist and blacklist.') + sys.exit(1) + + elif 'processwhitelist' in listener_config: + + self.logger.debug('Process whitelist:') + + if not protocol in self.port_process_whitelist: + self.port_process_whitelist[protocol] = dict() + + self.port_process_whitelist[protocol][port] = [process.strip() for process in listener_config['processwhitelist'].split(',')] + + for port in self.port_process_whitelist[protocol]: + self.logger.debug(' Port: %d (%s) Processes: %s', port, protocol, ', '.join(self.port_process_whitelist[protocol][port])) + + elif 'processblacklist' in listener_config: + self.logger.debug('Process blacklist:') + + if not protocol in self.port_process_blacklist: + self.port_process_blacklist[protocol] = dict() + + self.port_process_blacklist[protocol][port] = [process.strip() for process in listener_config['processblacklist'].split(',')] + + for port in self.port_process_blacklist[protocol]: + self.logger.debug(' Port: %d (%s) Processes: %s', port, protocol, ', '.join(self.port_process_blacklist[protocol][port])) + + ############################################################### + # Host filtering configuration + if 'hostwhitelist' in listener_config and 'hostblacklist' in listener_config: + self.logger.error('ERROR: Listener can\'t have both host whitelist and blacklist.') + sys.exit(1) + + elif 'hostwhitelist' in listener_config: + + self.logger.debug('Host whitelist:') + + if not protocol in self.port_host_whitelist: + self.port_host_whitelist[protocol] = dict() + + self.port_host_whitelist[protocol][port] = [host.strip() for host in listener_config['hostwhitelist'].split(',')] + + for port in self.port_host_whitelist[protocol]: + self.logger.debug(' Port: %d (%s) Hosts: %s', port, protocol, ', '.join(self.port_host_whitelist[protocol][port])) + + elif 'hostblacklist' in listener_config: + self.logger.debug('Host blacklist:') + + if not protocol in self.port_host_blacklist: + self.port_host_blacklist[protocol] = dict() + + self.port_host_blacklist[protocol][port] = [host.strip() for host in listener_config['hostblacklist'].split(',')] + + for port in self.port_host_blacklist[protocol]: + self.logger.debug(' Port: %d (%s) Hosts: %s', port, protocol, ', '.join(self.port_host_blacklist[protocol][port])) + + ############################################################### + # Execute command configuration + if 'executecmd' in listener_config: + + if not protocol in self.port_execute: + self.port_execute[protocol] = dict() + + self.port_execute[protocol][port] = listener_config['executecmd'].strip() + self.logger.debug('Port %d (%s) ExecuteCmd: %s', port, protocol, self.port_execute[protocol][port] ) + + ########################################################################### + # Parse diverter settings and filters + + def parse_diverter_config(self): + + # Do not redirect blacklisted processes + if self.diverter_config.get('processblacklist') != None: + self.blacklist_processes = [process.strip() for process in self.diverter_config.get('processblacklist').split(',')] + self.logger.debug('Blacklisted processes: %s', ', '.join([str(p) for p in self.blacklist_processes])) + + # Do not redirect blacklisted hosts + if self.diverter_config.get('hostblacklist') != None: + self.blacklist_hosts = [host.strip() for host in self.diverter_config.get('hostblacklist').split(',')] + self.logger.debug('Blacklisted hosts: %s', ', '.join([str(p) for p in self.blacklist_hosts])) + + # Redirect all traffic + if self.diverter_config.get('redirectalltraffic') and self.diverter_config['redirectalltraffic'].lower() == 'yes': + self.filter = "outbound and ip and (icmp or tcp or udp)" + + if self.diverter_config.get('defaulttcplistener') == None: + self.logger.error('ERROR: No default TCP listener specified in the configuration.') + sys.exit(1) + + elif self.diverter_config.get('defaultudplistener') == None: + self.logger.error('ERROR: No default UDP listener specified in the configuration.') + sys.exit(1) + + elif not self.diverter_config.get('defaulttcplistener') in self.listeners_config: + self.logger.error('ERROR: No configuration exists for default TCP listener %s', self.diverter_config.get('defaulttcplistener')) + sys.exit(1) + + elif not self.diverter_config.get('defaultudplistener') in self.listeners_config: + self.logger.error('ERROR: No configuration exists for default UDP listener %s', self.diverter_config.get('defaultudplistener')) + sys.exit(1) + + else: + self.default_listener_tcp_port = int( self.listeners_config[ self.diverter_config['defaulttcplistener'] ]['port'] ) + self.logger.error('Using default listener %s on port %d', self.diverter_config['defaulttcplistener'], self.default_listener_tcp_port) + + self.default_listener_udp_port = int( self.listeners_config[ self.diverter_config['defaultudplistener'] ]['port'] ) + self.logger.error('Using default listener %s on port %d', self.diverter_config['defaultudplistener'], self.default_listener_udp_port) + + # Do not redirect blacklisted TCP ports + if self.diverter_config.get('blacklistportstcp') != None: + self.blacklist_ports_tcp = [int(port.strip()) for port in self.diverter_config.get('blacklistportstcp').split(',')] + self.logger.debug('Blacklisted TCP ports: %s', ', '.join([str(p) for p in self.blacklist_ports_tcp])) + + # Do not redirect blacklisted UDP ports + if self.diverter_config.get('blacklistportsudp') != None: + self.blacklist_ports_udp = [int(port.strip()) for port in self.diverter_config.get('blacklistportsudp').split(',')] + self.logger.debug('Blacklisted UDP ports: %s', ', '.join([str(p) for p in self.blacklist_ports_udp])) + + # Redirect only specific traffic, build the filter dynamically + else: + + filter_diverted_ports = list() + + if self.diverted_ports.get('TCP') != None: + for tcp_port in self.diverted_ports.get('TCP'): + filter_diverted_ports.append("tcp.DstPort == %s" % tcp_port) + filter_diverted_ports.append("tcp.SrcPort == %s" % tcp_port) + + if self.diverted_ports.get('UDP') != None: + for udp_port in self.diverted_ports.get('UDP'): + filter_diverted_ports.append("udp.DstPort == %s" % udp_port) + filter_diverted_ports.append("udp.SrcPort == %s" % udp_port) + + if len(filter_diverted_ports) > 0: + self.filter = "outbound and ip and (icmp or %s)" % " or ".join(filter_diverted_ports) + else: + self.filter = "outbound and ip" + + ########################################################################### + # Diverter controller functions + + def start(self): + + self.logger.info('Starting...') + + # Set local DNS server IP address + if self.diverter_config.get('modifylocaldns') and self.diverter_config['modifylocaldns'].lower() == 'yes': + self.set_dns_server(self.loopback_ip) + + # Stop DNS service + if self.diverter_config.get('stopdnsservice') and self.diverter_config['stopdnsservice'].lower() == 'yes': + self.stop_service_helper('Dnscache') + + self.logger.info('Diverting ports: ') + if self.diverted_ports.get('TCP'): self.logger.info('TCP: %s', ', '.join("%d" % port for port in self.diverted_ports['TCP'])) + if self.diverted_ports.get('UDP'): self.logger.info('UDP: %s', ', '.join("%d" % port for port in self.diverted_ports['UDP'])) + + self.flush_dns() + + self.diverter_thread = threading.Thread(target=self.divert_thread) + self.diverter_thread.daemon = True + + self.diverter_thread.start() + + + + def divert_thread(self): + + try: + while True: + packet = self.handle.receive() + self.handle_packet(packet) + + # Handle errors related to shutdown process. + except WindowsError as e: + if e.winerror in [4,6,995]: + return + else: + raise + + def stop(self): + self.logger.info('Stopping...') + if self.pcap: + self.pcap.close() + + self.handle.close() + + # Restore DNS server + if self.diverter_config.get('modifylocaldns') and self.diverter_config['modifylocaldns'].lower() == 'yes': + self.restore_dns_server() + + # Restart DNS service + if self.diverter_config.get('stopdnsservice') and self.diverter_config['stopdnsservice'].lower() == 'yes': + self.start_service_helper('Dnscache') + + self.flush_dns() + + + def handle_icmp_packet(self, packet): + # Modify outgoing ICMP packet to target local Windows host which will reply to the ICMP messages. + # HACK: Can't intercept inbound ICMP server, but still works for now. + + if not ((packet.meta.is_loopback() and packet.src_addr == self.loopback_ip and packet.dst_addr == self.loopback_ip) or \ + (packet.src_addr == self.external_ip and packet.dst_addr == self.external_ip)): + + self.logger.info('Modifying %s ICMP packet:', 'loopback' if packet.meta.is_loopback() else 'external') + self.logger.info(' from: %s -> %s', packet.src_addr, packet.dst_addr) + + # Direct packet to the right interface IP address to avoid routing issues + packet.dst_addr = self.loopback_ip if packet.meta.is_loopback() else self.external_ip + + self.logger.info(' to: %s -> %s', packet.src_addr, packet.dst_addr) + + return packet + + def handle_tcp_udp_packet(self, packet, protocol, default_listener_port, blacklist_ports): + + # Meta strings + interface_string = 'loopback' if packet.meta.is_loopback() else 'external' + direction_string = 'inbound' if packet.meta.is_inbound() else 'outbound' + + # Protocol specific filters + diverted_ports = self.diverted_ports.get(protocol) + port_process_whitelist = self.port_process_whitelist.get(protocol) + port_process_blacklist = self.port_process_blacklist.get(protocol) + port_host_whitelist = self.port_host_whitelist.get(protocol) + port_host_blacklist = self.port_host_blacklist.get(protocol) + port_execute = self.port_execute.get(protocol) + + if packet.src_port in blacklist_ports or packet.dst_port in blacklist_ports: + self.logger.debug('Forwarding blacklisted port %s %s %s packet:', direction_string, interface_string, protocol) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check if a packet must be diverted to a local listener + # Rules: + # 1) Divert outbound packets only + # 2) Make sure we are not diverting response packet based on the source port + # 3) Make sure the destination port is a known diverted port or we have a default listener port defined + elif diverted_ports and (packet.dst_port in diverted_ports or default_listener_port != None) and not packet.src_port in diverted_ports: + + # Find which process ID is sending the request + conn_pid = self.get_pid_port_tcp(packet.src_port) if type(packet.headers[1].hdr) == TcpHeader else self.get_pid_port_udp(packet.src_port) + process_name = self.get_process_image_filename(conn_pid) if conn_pid else None + + # Check process blacklist + if process_name and process_name in self.blacklist_processes: + self.logger.debug('Ignoring %s %s %s request packet from process %s in the process blacklist.', direction_string, interface_string, protocol, process_name) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check host blacklist + if packet.dst_addr in self.blacklist_hosts: + self.logger.debug('Ignoring %s %s %s request packet to %s in the host blacklist.', direction_string, interface_string, protocol, packet.dst_addr) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check the port process whitelist + elif process_name and port_process_whitelist and \ + ((packet.dst_port in port_process_whitelist and not process_name in port_process_whitelist[packet.dst_port]) or\ + (default_listener_port and default_listener_port in port_process_whitelist and not process_name in port_process_whitelist[default_listener_port])) : + self.logger.debug('Ignoring %s %s %s request packet from process %s not in the listener process whitelist.', direction_string, interface_string, protocol, process_name) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check the port process blacklist + elif process_name and port_process_blacklist and \ + ((packet.dst_port in port_process_blacklist and process_name in port_process_blacklist[packet.dst_port]) or\ + (default_listener_port and default_listener_port in port_process_blacklist and process_name in port_process_blacklist[default_listener_port])) : + self.logger.debug('Ignoring %s %s %s request packet from process %s in the listener process blacklist.', direction_string, interface_string, protocol, process_name) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check the port host whitelist + elif packet.dst_addr and port_host_whitelist and \ + ((packet.dst_port in port_host_whitelist and not packet.dst_addr in port_host_whitelist[packet.dst_port]) or\ + (default_listener_port and default_listener_port in port_host_whitelist and not packet.dst_addr in port_host_whitelist[default_listener_port])) : + self.logger.debug('Ignoring %s %s %s request packet to %s not in the listener host whitelist.', direction_string, interface_string, protocol, packet.dst_addr) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Check the port host blacklist + elif packet.dst_addr and port_host_blacklist and \ + ((packet.dst_port in port_host_blacklist and packet.dst_addr in port_host_blacklist[packet.dst_port]) or\ + (default_listener_port and default_listener_port in port_host_blacklist and packet.dst_addr in port_host_blacklist[default_listener_port])) : + self.logger.debug('Ignoring %s %s %s request packet to %s in the listener host blacklist.', direction_string, interface_string, protocol, packet.dst_addr) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Make sure you are not intercepting packets from one of the FakeNet listeners + elif conn_pid and os.getpid() == conn_pid: + self.logger.debug('Skipping %s %s %s listener packet:', direction_string, interface_string, protocol) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Modify the packet + else: + + # Adjustable log level output. Used to display info level logs for first packets of the session and + # debug level for the rest of the communication in order to reduce log output. + logger_level = self.logger.debug + + # First packet in a new session + if not (packet.src_port in self.sessions and self.sessions[packet.src_port] == (packet.dst_addr, packet.dst_port)): + + # Cache original target IP address based on source port + self.sessions[packet.src_port] = (packet.dst_addr, packet.dst_port) + + # Override log level to display all information on info level + logger_level = self.logger.info + + # Execute command + if conn_pid and port_execute and (packet.dst_port in port_execute or (default_listener_port and default_listener_port in port_execute)): + + + execute_cmd = port_execute[packet.dst_port if packet.dst_port in diverted_ports else default_listener_port].format(pid = conn_pid, + procname = process_name, + src_addr = packet.src_addr, + src_port = packet.src_port, + dst_addr = packet.dst_addr, + dst_port = packet.dst_port) + + logger_level('Executing command: %s', execute_cmd) + + self.execute_detached(execute_cmd) + + + logger_level('Modifying %s %s %s request packet:', direction_string, interface_string, protocol) + logger_level(' from: %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Direct packet to the right interface IP address to avoid routing issues + packet.dst_addr = self.loopback_ip if packet.meta.is_loopback() else self.external_ip + + # Direct packet to an existing or a default listener + packet.dst_port = packet.dst_port if packet.dst_port in diverted_ports else default_listener_port + + logger_level(' to: %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + if conn_pid: + logger_level(' pid: %d name: %s', conn_pid, process_name if process_name else 'Unknown') + + + # Restore diverted response from a local listener + # NOTE: The response can come from a legitimate request + elif diverted_ports and packet.src_port in diverted_ports: + + # Find which process ID is sending the request + conn_pid = self.get_pid_port_tcp(packet.dst_port) if type(packet.headers[1].hdr) == TcpHeader else self.get_pid_port_udp(packet.dst_port) + process_name = self.get_process_image_filename(conn_pid) + + if not packet.dst_port in self.sessions: + self.logger.debug('Unknown %s %s %s response packet:', direction_string, interface_string, protocol) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Restore original target IP address from the cache + else: + self.logger.debug('Modifying %s %s %s response packet:', direction_string, interface_string, protocol) + self.logger.debug(' from: %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + # Restore original target IP address based on destination port + packet.src_addr, packet.src_port = self.sessions[packet.dst_port] + + self.logger.debug(' to: %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + if conn_pid: + self.logger.debug(' pid: %d name: %s', conn_pid, process_name if process_name else 'Unknown') + + else: + self.logger.debug('Forwarding %s %s %s packet:', direction_string, interface_string, protocol) + self.logger.debug(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + + return packet + + def handle_packet(self, packet): + + if packet == None: + self.logger.error('ERROR: Can\'t handle packet.') + return + + # Preserve destination address to detect packet being diverted + dst_addr = packet.dst_addr + + ####################################################################### + # Capture packet and store raw packet in the PCAP + if self.capture_flag: + self.pcap.writepkt(packet._raw_packet) + + ########################################################################### + # Verify the IP packet has an additional header + + if len(packet.headers) > 1 and packet.headers[1] and packet.headers[1].hdr: + + ####################################################################### + # Handle ICMP Packets + + if type(packet.headers[1].hdr) in [IcmpHeader, Icmpv6Header]: + packet = self.handle_icmp_packet(packet) + + ####################################################################### + # Handle TCP/UDP Packets + + elif type(packet.headers[1].hdr) == TcpHeader: + protocol = 'TCP' + packet = self.handle_tcp_udp_packet(packet, + protocol, + self.default_listener_tcp_port, + self.blacklist_ports_tcp) + + elif type(packet.headers[1].hdr) == UdpHeader: + protocol = 'UDP' + packet = self.handle_tcp_udp_packet(packet, + protocol, + self.default_listener_udp_port, + self.blacklist_ports_udp) + + else: + self.logger.error('ERROR: Unknown packet header type.') + + ####################################################################### + # Capture modified packet and store raw packet in the PCAP + # NOTE: While this results in potentially duplicate traffic capture, this is necessary + # to properly restore TLS/SSL sessions. + # TODO: Develop logic to record traffic before modification for both requests and + # responses to reduce duplicate captures. + if self.capture_flag and (dst_addr != packet.dst_addr): + self.pcap.writepkt(packet._raw_packet) + + ####################################################################### + # Attempt to send the processed packet + try: + self.handle.send(packet) + except Exception, e: + + protocol = 'Unknown' + + if type(packet.headers[1].hdr) == TcpHeader: + protocol = 'TCP' + elif type(packet.headers[1].hdr) == UdpHeader: + protocol = 'UDP' + elif type(packet.headers[1].hdr) in [IcmpHeader, Icmpv6Header]: + protocol = 'ICMP' + + interface_string = 'loopback' if packet.meta.is_loopback() else 'external' + direction_string = 'inbound' if packet.meta.is_inbound() else 'outbound' + + self.logger.error('ERROR: Failed to send %s %s %s packet', direction_string, interface_string, protocol) + + if packet.src_port and packet.dst_port: + self.logger.error(' %s:%d -> %s:%d', packet.src_addr, packet.src_port, packet.dst_addr, packet.dst_port) + else: + self.logger.error(' %s -> %s', packet.src_addr, packet.dst_addr) + + self.logger.error(' %s', e) + +def main(): + + self.diverter_config = {'redirectalltraffic': 'no', 'defaultlistener': 'DefaultListener', 'dumppackets': 'no'} + listeners_config = {'DefaultListener': {'port': '1337'}} + + diverter = Diverter(diverter_config, listeners_config) + diverter.start() + + ########################################################################### + # Run processing + import time + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + diverter.stop() + + ########################################################################### + # Run tests + # TODO + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fakenet/diverters/windows.pyc b/fakenet/diverters/windows.pyc new file mode 100644 index 0000000..3ccb931 Binary files /dev/null and b/fakenet/diverters/windows.pyc differ diff --git a/fakenet/diverters/winutil.py b/fakenet/diverters/winutil.py new file mode 100644 index 0000000..c8f26a7 --- /dev/null +++ b/fakenet/diverters/winutil.py @@ -0,0 +1,1195 @@ +#!/usr/bin/env python +import logging +logging.basicConfig(format='%(asctime)s [%(name)18s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) + +from ctypes import * +from ctypes.wintypes import * + +import os +import sys +import socket +import struct + +import time + +from _winreg import * + +import subprocess + +NO_ERROR = 0 + +AF_INET = 2 +AF_INET6 = 23 + +ULONG64 = c_uint64 + + +############################################################################## +# Services related functions +############################################################################## + +SC_MANAGER_ALL_ACCESS = 0xF003F + +SERVICE_ALL_ACCESS = 0xF01FF +SERVICE_STOP = 0x0020 +SERVICE_QUERY_STATUS = 0x0004 +SERVICE_ENUMERATE_DEPENDENTS = 0x0008 + +SC_STATUS_PROCESS_INFO = 0x0 + +SERVICE_STOPPED = 0x1 +SERVICE_START_PENDING = 0x2 +SERVICE_STOP_PENDING = 0x3 +SERVICE_RUNNING = 0x4 +SERVICE_CONTINUE_PENDING = 0x5 +SERVICE_PAUSE_PENDING = 0x6 +SERVICE_PAUSED = 0x7 + +SERVICE_CONTROL_STOP = 0x1 +SERVICE_CONTROL_PAUSE = 0x2 +SERVICE_CONTROL_CONTINUE = 0x3 + +SERVICE_NO_CHANGE = 0xffffffff + +SERVICE_AUTO_START = 0x2 +SERVICE_BOOT_START = 0x0 +SERVICE_DEMAND_START = 0x3 +SERVICE_DISABLED = 0x4 +SERVICE_SYSTEM_START = 0x1 + +class SERVICE_STATUS_PROCESS(Structure): + _fields_ = [ + ("dwServiceType", DWORD), + ("dwCurrentState", DWORD), + ("dwControlsAccepted", DWORD), + ("dwWin32ExitCode", DWORD), + ("dwServiceSpecificExitCode", DWORD), + ("dwCheckPoint", DWORD), + ("dwWaitHint", DWORD), + ("dwProcessId", DWORD), + ("dwServiceFlags", DWORD), + ] + +############################################################################## +# Process related functions +############################################################################## + +############################################################################## +# GetExtendedTcpTable constants and structures + +TCP_TABLE_OWNER_PID_ALL = 5 + +class MIB_TCPROW_OWNER_PID(Structure): + _fields_ = [ + ("dwState", DWORD), + ("dwLocalAddr", DWORD), + ("dwLocalPort", DWORD), + ("dwRemoteAddr", DWORD), + ("dwRemotePort", DWORD), + ("dwOwningPid", DWORD) + ] + +class MIB_TCPTABLE_OWNER_PID(Structure): + _fields_ = [ + ("dwNumEntries", DWORD), + ("table", MIB_TCPROW_OWNER_PID * 512) + ] + +############################################################################## +# GetExtendedUdpTable constants and structures + +UDP_TABLE_OWNER_PID = 1 + +class MIB_UDPROW_OWNER_PID(Structure): + _fields_ = [ + ("dwLocalAddr", DWORD), + ("dwLocalPort", DWORD), + ("dwOwningPid", DWORD) + ] + +class MIB_UDPTABLE_OWNER_PID(Structure): + _fields_ = [ + ("dwNumEntries", DWORD), + ("table", MIB_UDPROW_OWNER_PID * 512) + ] + +############################################################################### +# GetProcessImageFileName constants and structures + +MAX_PATH = 260 +PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + + +############################################################################### +# Network interface related functions +############################################################################### + +MIB_IF_TYPE_ETHERNET = 6 +MIB_IF_TYPE_LOOPBACK = 28 +IF_TYPE_IEEE80211 = 71 + +############################################################################### +# GetAdaptersAddresses constants and structures + +MAX_ADAPTER_ADDRESS_LENGTH = 8 +MAX_DHCPV6_DUID_LENGTH = 130 + +IFOPERSTATUSUP = 1 + +class SOCKADDR(Structure): + _fields_ = [ + ("sa_family", c_ushort), + ("sa_data", c_char * 14), + ] + +class SOCKET_ADDRESS(Structure): + _fields_ = [ + ("Sockaddr", POINTER(SOCKADDR)), + ("SockaddrLength", INT), + ] + +class IP_ADAPTER_PREFIX(Structure): + pass +IP_ADAPTER_PREFIX._fields_ = [ + ("Length", ULONG), + ("Flags", DWORD), + ("Next", POINTER(IP_ADAPTER_PREFIX)), + ("Address", SOCKET_ADDRESS), + ("PrefixLength", ULONG), + ] + +class IP_ADAPTER_ADDRESSES(Structure): + pass +IP_ADAPTER_ADDRESSES._fields_ = [ + ("Length", ULONG), + ("IfIndex", DWORD), + ("Next", POINTER(IP_ADAPTER_ADDRESSES)), + ("AdapterName", LPSTR), + ("FirstUnicastAddress", c_void_p), # Not used + ("FirstAnycastAddress", c_void_p), # Not used + ("FirstMulticastAddress", c_void_p), # Not used + ("FirstDnsServerAddress", c_void_p), # Not used + ("DnsSuffix", LPWSTR), + ("Description", LPWSTR), + ("FriendlyName", LPWSTR), + ("PhysicalAddress", BYTE * MAX_ADAPTER_ADDRESS_LENGTH), + ("PhysicalAddressLength", DWORD), + ("Flags", DWORD), + ("Mtu", DWORD), + ("IfType", DWORD), + ("OperStatus", DWORD), + ("Ipv6IfIndex", DWORD), + ("ZoneIndices", DWORD * 16), + ("FirstPrefix", POINTER(IP_ADAPTER_PREFIX)), + ("TransmitLinkSpeed", ULONG64), + ("ReceiveLinkSpeed", ULONG64), + ("FirstWinsServerAddress", c_void_p), # Not used + ("FirstGatewayAddress", c_void_p), # Not used + ("Ipv4Metric", ULONG), + ("Ipv6Metric", ULONG), + ("Luid", ULONG64), + ("Dhcpv4Server", SOCKET_ADDRESS), + ("CompartmentId", DWORD), + ("NetworkGuid", BYTE * 16), + ("ConnectionType", DWORD), + ("TunnelType", DWORD), + ("Dhcpv6Server", SOCKET_ADDRESS), + ("Dhcpv6ClientDuid", BYTE * MAX_DHCPV6_DUID_LENGTH), + ("Dhcpv6ClientDuidLength", ULONG), + ("Dhcpv6Iaid", ULONG), + ("FirstDnsSuffix", c_void_p), # Not used +] + +############################################################################### +# GetAdaptersInfo constants and structures + +MAX_ADAPTER_NAME_LENGTH = 256 +MAX_ADAPTER_DESCRIPTION_LENGTH = 128 +MAX_ADAPTER_LENGTH = 8 + +MIB_IF_TYPE_ETHERNET = 6 +MIB_IF_TYPE_LOOPBACK = 28 +IF_TYPE_IEEE80211 = 71 + +class IP_ADDRESS_STRING(Structure): + _fields_ = [ + ("String", c_char * 16), + ] + +class IP_MASK_STRING(Structure): + _fields_ = [ + ("String", c_char * 16), + ] + +class IP_ADDR_STRING(Structure): + pass +IP_ADDR_STRING._fields_ = [ + ("Next", POINTER(IP_ADDR_STRING)), + ("IpAddress", IP_ADDRESS_STRING), + ("IpMask", IP_MASK_STRING), + ("Context", DWORD), + ] + +class IP_ADAPTER_INFO(Structure): + pass +IP_ADAPTER_INFO._fields_ = [ + ("Next", POINTER(IP_ADAPTER_INFO)), + ("ComboIndex", DWORD), + ("AdapterName", c_char * (MAX_ADAPTER_NAME_LENGTH + 4)), + ("Description", c_char * (MAX_ADAPTER_DESCRIPTION_LENGTH + 4)), + ("AddressLength", UINT), + ("Address", BYTE * MAX_ADAPTER_LENGTH), + ("Index", DWORD), + ("Type", UINT), + ("DhcpEnabled", UINT), + ("CurrentIpAddress", c_void_p), # Not used + ("IpAddressList", IP_ADDR_STRING), + ("GatewayList", IP_ADDR_STRING), + ("DhcpServer", IP_ADDR_STRING), + ("HaveWins", BOOL), + ("PrimaryWinsServer", IP_ADDR_STRING), + ("SecondaryWinsServer", IP_ADDR_STRING), + ("LeaseObtained", c_ulong), + ("LeaseExpires", c_ulong), + + ] + +############################################################################### +# GetNetworkParams constants and structures + +MAX_HOSTNAME_LEN = 128 +MAX_DOMAIN_NAME_LEN = 128 +MAX_SCOPE_ID_LEN = 256 + +############################################################################### +# ConvertInterface constants and structures + +NDIS_IF_MAX_STRING_SIZE = 256 + +class IP_ADDRESS_STRING(Structure): + _fields_ = [ + ("String", c_char * 16), + ] + +class IP_MASK_STRING(Structure): + _fields_ = [ + ("String", c_char * 16), + ] + +class IP_ADDR_STRING(Structure): + pass +IP_ADDR_STRING._fields_ = [ + ("Next", POINTER(IP_ADDR_STRING)), + ("IpAddress", IP_ADDRESS_STRING), + ("IpMask", IP_MASK_STRING), + ("Context", DWORD), + ] + +class FIXED_INFO(Structure): + _fields_ = [ + ("HostName", c_char * (MAX_HOSTNAME_LEN + 4)), + ("DomainName", c_char * (MAX_DOMAIN_NAME_LEN + 4)), + ("CurrentDnsServer", c_void_p), # Not used + ("DnsServerList", IP_ADDR_STRING), + ("NodeType", UINT), + ("ScopeId", c_char * (MAX_SCOPE_ID_LEN + 4)), + ("EnableRouting", UINT), + ("EnableProxy", UINT), + ("EnableDns", UINT), + ] + +class WinUtilMixin(): + + ########################################################################### + # Service related functions + ########################################################################### + + ########################################################################### + # Establishes a connection to the service control manager on the specified computer and opens the specified service control manager database. + # + # SC_HANDLE WINAPI OpenSCManager( + # _In_opt_ LPCTSTR lpMachineName, + # _In_opt_ LPCTSTR lpDatabaseName, + # _In_ DWORD dwDesiredAccess + # ); + + def open_sc_manager(self): + + sc_handle = windll.advapi32.OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS) + if sc_handle == 0: + self.logger.error("Failed to call OpenSCManager") + return + + return sc_handle + + ########################################################################### + # Closes a handle to a service control manager or service object + # + # BOOL WINAPI CloseServiceHandle( + # _In_ SC_HANDLE hSCObject + # ); + + def close_service_handle(self, sc_handle): + + if windll.advapi32.CloseServiceHandle(sc_handle) == 0: + self.logger.error('Failed to call CloseServiceHandle') + return False + + return True + + + + ########################################################################### + # Opens an existing service. + # + # SC_HANDLE WINAPI OpenService( + # _In_ SC_HANDLE hSCManager, + # _In_ LPCTSTR lpServiceName, + # _In_ DWORD dwDesiredAccess + # ); + + def open_service(self, sc_handle, service_name, dwDesiredAccess = SERVICE_ALL_ACCESS): + + if not sc_handle: + return + + service_handle = windll.advapi32.OpenServiceA(sc_handle, service_name, dwDesiredAccess) + + if service_handle == 0: + self.logger.error('Failed to call OpenService') + return + + return service_handle + + ########################################################################### + # Retrieves the current status of the specified service based on the specified information level. + # + # BOOL WINAPI QueryServiceStatusEx( + # _In_ SC_HANDLE hService, + # _In_ SC_STATUS_TYPE InfoLevel, + # _Out_opt_ LPBYTE lpBuffer, + # _In_ DWORD cbBufSize, + # _Out_ LPDWORD pcbBytesNeeded + # ); + + def query_service_status_ex(self, service_handle): + + lpBuffer = SERVICE_STATUS_PROCESS() + cbBufSize = DWORD(sizeof(SERVICE_STATUS_PROCESS)) + pcbBytesNeeded = DWORD() + + if windll.advapi32.QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO, byref(lpBuffer), cbBufSize, byref(pcbBytesNeeded)) == 0: + self.logger.error('Failed to call QueryServiceStatusEx') + return + + return lpBuffer + + ########################################################################### + # Sends a control code to a service. + # + # BOOL WINAPI ControlService( + # _In_ SC_HANDLE hService, + # _In_ DWORD dwControl, + # _Out_ LPSERVICE_STATUS lpServiceStatus + # ); + + def control_service(self, service_handle, dwControl): + + lpServiceStatus = SERVICE_STATUS_PROCESS() + + if windll.advapi32.ControlService(service_handle, dwControl, byref(lpServiceStatus)) == 0: + self.logger.error('Failed to call ControlService') + return + + return lpServiceStatus + + ########################################################################### + # Starts a service + # + # BOOL WINAPI StartService( + # _In_ SC_HANDLE hService, + # _In_ DWORD dwNumServiceArgs, + # _In_opt_ LPCTSTR *lpServiceArgVectors + # ); + + def start_service(self, service_handle): + + if windll.advapi32.StartServiceA(service_handle, 0, 0) == 0: + self.logger.error('Failed to call StartService') + return False + + else: + return True + + ########################################################################### + # Changes the configuration parameters of a service. + # + # BOOL WINAPI ChangeServiceConfig( + # _In_ SC_HANDLE hService, + # _In_ DWORD dwServiceType, + # _In_ DWORD dwStartType, + # _In_ DWORD dwErrorControl, + # _In_opt_ LPCTSTR lpBinaryPathName, + # _In_opt_ LPCTSTR lpLoadOrderGroup, + # _Out_opt_ LPDWORD lpdwTagId, + # _In_opt_ LPCTSTR lpDependencies, + # _In_opt_ LPCTSTR lpServiceStartName, + # _In_opt_ LPCTSTR lpPassword, + # _In_opt_ LPCTSTR lpDisplayName + # ); + + def change_service_config(self, service_handle, dwStartType = SERVICE_DISABLED): + + if windll.advapi32.ChangeServiceConfigA(service_handle, SERVICE_NO_CHANGE, dwStartType, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0) == 0: + self.logger.error('Failed to call ChangeServiceConfig') + raise WinError(get_last_error()) + return False + + else: + return True + + + def start_service_helper(self, service_name = 'Dnscache'): + + sc_handle = None + service_handle = None + + timeout = 5 + + sc_handle = self.open_sc_manager() + + if not sc_handle: + return + + service_handle = self.open_service(sc_handle, service_name) + + if not service_handle: + self.close_service_handle(sc_handle) + return + + # Enable the service + if not self.change_service_config(service_handle, SERVICE_AUTO_START): + + # Backup enable the service + try: + subprocess.check_call("sc config %s start= auto" % service_name, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError, e: + self.logger.error('Failed to enable the service %s. (sc config)', service_name) + else: + self.logger.info('Successfully enabled the service %s. (sc config)', service_name) + + else: + self.logger.info('Successfully enabled the service %s.', service_name) + + service_status = self.query_service_status_ex(service_handle) + + if service_status: + + if not service_status.dwCurrentState in [SERVICE_RUNNING, SERVICE_START_PENDING]: + + # Start service + if self.start_service(service_handle): + + # Wait for the service to start + while timeout: + timeout -= 1 + time.sleep(1) + + service_status = self.query_service_status_ex(service_handle) + if service_status.dwCurrentState == SERVICE_RUNNING: + self.logger.info('Successfully started the service %s.', service_name) + break + else: + self.logger.error('Timed out while trying to start the service %s.', service_name) + else: + self.logger.error('Failed to start the service %s.', service_name) + else: + self.logger.error('Service %s is already running.', service_name) + + # As a backup call net stop + if service_status.dwCurrentState != SERVICE_RUNNING: + + try: + subprocess.check_call("net start %s" % service_name, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError, e: + self.logger.error('Failed to start the service %s. (net stop)', service_name) + else: + self.logger.info('Successfully started the service %s.', service_name) + + + + + self.close_service_handle(service_handle) + self.close_service_handle(sc_handle) + + def stop_service_helper(self, service_name = 'Dnscache'): + + sc_handle = None + service_handle = None + + Control = SERVICE_CONTROL_STOP + dwControl = DWORD(Control) + timeout = 5 + + sc_handle = self.open_sc_manager() + + if not sc_handle: + return + + service_handle = self.open_service(sc_handle, service_name) + + if not service_handle: + self.close_service_handle(sc_handle) + return + + # Disable the service + if not self.change_service_config(service_handle, SERVICE_DISABLED): + + # Backup disable the service + try: + subprocess.check_call("sc config %s start= disabled" % service_name, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError, e: + self.logger.error('Failed to disable the service %s. (sc config)', service_name) + else: + self.logger.info('Successfully disabled the service %s. (sc config)', service_name) + + else: + self.logger.info('Successfully disabled the service %s.', service_name) + + service_status = self.query_service_status_ex(service_handle) + + if service_status: + + if service_status.dwCurrentState != SERVICE_STOPPED: + + # Send a stop code to the service + if self.control_service(service_handle, dwControl): + + # Wait for the service to stop + while timeout: + timeout -= 1 + time.sleep(1) + + service_status = self.query_service_status_ex(service_handle) + if service_status.dwCurrentState == SERVICE_STOPPED: + self.logger.info('Successfully stopped the service %s.', service_name) + break + + else: + self.logger.error('Timed out while trying to stop the service %s.', service_name) + else: + self.logger.error('Failed to stop the service %s.', service_name) + else: + self.logger.error('Service %s is already stopped.', service_name) + + # As a backup call net stop + if service_status.dwCurrentState != SERVICE_STOPPED: + + try: + subprocess.check_call("net stop %s" % service_name, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError, e: + self.logger.error('Failed to stop the service %s. (net stop)', service_name) + else: + self.logger.info('Successfully stopped the service %s.', service_name) + + self.close_service_handle(service_handle) + self.close_service_handle(sc_handle) + + + + + ########################################################################### + # Process related functions + ########################################################################### + + ########################################################################### + # The GetExtendedTcpTable function retrieves a table that contains a list of TCP endpoints available to the application. + # + # DWORD GetExtendedTcpTable( + # _Out_ PVOID pTcpTable, + # _Inout_ PDWORD pdwSize, + # _In_ BOOL bOrder, + # _In_ ULONG ulAf, + # _In_ TCP_TABLE_CLASS TableClass, + # _In_ ULONG Reserved + # ); + + def get_extended_tcp_table(self): + + dwSize = DWORD(sizeof(MIB_TCPROW_OWNER_PID) * 512 + 4) + + TcpTable = MIB_TCPTABLE_OWNER_PID() + + if windll.iphlpapi.GetExtendedTcpTable(byref(TcpTable), byref(dwSize), False, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) != NO_ERROR: + self.logger.error("Failed to call GetExtendedTcpTable") + return + + for item in TcpTable.table[:TcpTable.dwNumEntries]: + yield item + + def get_pid_port_tcp(self, port): + + for item in self.get_extended_tcp_table(): + + lPort = socket.ntohs(item.dwLocalPort) + lAddr = socket.inet_ntoa(struct.pack('L', item.dwLocalAddr)) + pid = item.dwOwningPid + + if lPort == port: + return pid + else: + return None + + ################################################################################# + # The GetExtendedUdpTable function retrieves a table that contains a list of UDP endpoints available to the application. + # + # DWORD GetExtendedUdpTable( + # _Out_ PVOID pUdpTable, + # _Inout_ PDWORD pdwSize, + # _In_ BOOL bOrder, + # _In_ ULONG ulAf, + # _In_ UDP_TABLE_CLASS TableClass, + # _In_ ULONG Reserved + # ); + + def get_extended_udp_table(self): + + dwSize = DWORD(sizeof(MIB_UDPROW_OWNER_PID) * 512 + 4) + + UdpTable = MIB_UDPTABLE_OWNER_PID() + + if windll.iphlpapi.GetExtendedUdpTable(byref(UdpTable), byref(dwSize), False, AF_INET, UDP_TABLE_OWNER_PID, 0) != NO_ERROR: + self.logger.error("Failed to call GetExtendedUdpTable") + return + + for item in UdpTable.table[:UdpTable.dwNumEntries]: + yield item + + def get_pid_port_udp(self, port): + + for item in self.get_extended_udp_table(): + + lPort = socket.ntohs(item.dwLocalPort) + lAddr = socket.inet_ntoa(struct.pack('L', item.dwLocalAddr)) + pid = item.dwOwningPid + + if lPort == port: + return pid + else: + return None + + ############################################################################### + # Retrieves the name of the executable file for the specified process. + # + # DWORD WINAPI GetProcessImageFileName( + # _In_ HANDLE hProcess, + # _Out_ LPTSTR lpImageFileName, + # _In_ DWORD nSize + # ); + + def get_process_image_filename(self, pid): + + process_name = None + + hProcess = windll.kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) + if hProcess: + + lpImageFileName = create_string_buffer(MAX_PATH) + + if windll.psapi.GetProcessImageFileNameA(hProcess, lpImageFileName, MAX_PATH) > 0: + process_name = os.path.basename(lpImageFileName.value) + else: + self.logger.error('Failed to call GetProcessImageFileNameA') + + windll.kernel32.CloseHandle(hProcess) + + return process_name + + + + + ############################################################################### + # The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. + # + # ULONG WINAPI GetAdaptersAddresses( + # _In_ ULONG Family, + # _In_ ULONG Flags, + # _In_ PVOID Reserved, + # _Inout_ PIP_ADAPTER_ADDRESSES AdapterAddresses, + # _Inout_ PULONG SizePointer + # ); + + def get_adapters_addresses(self): + + Size = ULONG(0) + + windll.iphlpapi.GetAdaptersAddresses(AF_INET, 0, None, None, byref(Size)) + + AdapterAddresses = create_string_buffer(Size.value) + pAdapterAddresses = cast(AdapterAddresses, POINTER(IP_ADAPTER_ADDRESSES)) + + if not windll.iphlpapi.GetAdaptersAddresses(AF_INET, 0, None, pAdapterAddresses, byref(Size)) == NO_ERROR: + self.logger.error('Failed calling GetAdaptersAddresses') + return + + while pAdapterAddresses: + + yield pAdapterAddresses.contents + pAdapterAddresses = pAdapterAddresses.contents.Next + + def get_active_ethernet_adapters(self): + + for adapter in self.get_adapters_addresses(): + + if adapter.IfType == MIB_IF_TYPE_ETHERNET and adapter.OperStatus == IFOPERSTATUSUP: + yield adapter + + def check_active_ethernet_adapters(self): + + for adapter in self.get_adapters_addresses(): + + if adapter.IfType == MIB_IF_TYPE_ETHERNET and adapter.OperStatus == IFOPERSTATUSUP: + return True + else: + return False + + + ########################################################################### + # The GetAdaptersInfo function retrieves adapter information for the local computer. + # + # On Windows XP and later: Use the GetAdaptersAddresses function instead of GetAdaptersInfo. + # + # DWORD GetAdaptersInfo( + # _Out_ PIP_ADAPTER_INFO pAdapterInfo, + # _Inout_ PULONG pOutBufLen + # ); + + def get_adapters_info(self): + + OutBufLen = DWORD(0) + + windll.iphlpapi.GetAdaptersInfo(None, byref(OutBufLen)) + + AdapterInfo = create_string_buffer(OutBufLen.value) + pAdapterInfo = cast(AdapterInfo, POINTER(IP_ADAPTER_INFO)) + + if not windll.iphlpapi.GetAdaptersInfo(byref(AdapterInfo), byref(OutBufLen)) == NO_ERROR: + self.logger.error('Failed calling GetAdaptersInfo') + return + + while pAdapterInfo: + + yield pAdapterInfo.contents + pAdapterInfo = pAdapterInfo.contents.Next + + def get_gateways(self): + + for adapter in self.get_adapters_info(): + + gateway = adapter.GatewayList + + while gateway: + + yield gateway.IpAddress.String + gateway = gateway.Next + + def check_gateways(self): + + for gateway in self.get_gateways(): + if gateway != '0.0.0.0': + return True + else: + return False + + + ########################################################################### + # The GetNetworkParams function retrieves network parameters for the local computer. + # + # DWORD GetNetworkParams( + # _Out_ PFIXED_INFO pFixedInfo, + # _In_ PULONG pOutBufLen + # ); + + def get_network_params(self): + OutBufLen = ULONG(sizeof(FIXED_INFO)) + FixedInfo = FIXED_INFO() + + if not windll.iphlpapi.GetNetworkParams(byref(FixedInfo), byref(OutBufLen)) == NO_ERROR: + self.logger.error('Failed calling GetNetworkParams') + return None + + return FixedInfo + + def get_dns_servers(self): + + FixedInfo = self.get_network_params() + + if not FixedInfo: + return + + ip_addr_string = FixedInfo.DnsServerList + + while ip_addr_string: + + yield ip_addr_string.IpAddress.String + ip_addr_string = ip_addr_string.Next + + def check_dns_servers(self): + + FixedInfo = self.get_network_params() + + if not FixedInfo: + return + + ip_addr_string = FixedInfo.DnsServerList + + if ip_addr_string: + return True + + else: + return False + + + ########################################################################### + # The GetBestInterface function retrieves the index of the interface that has the best route to the specified IPv4 address. + # + # DWORD GetBestInterface( + # _In_ IPAddr dwDestAddr, + # _Out_ PDWORD pdwBestIfIndex + # ); + + def get_best_interface(self, ip='8.8.8.8'): + BestIfIndex = DWORD() + DestAddr = socket.inet_aton(ip) + + if not windll.iphlpapi.GetBestInterface(DestAddr, byref(BestIfIndex)) == NO_ERROR: + self.logger.error('Failed calling GetBestInterface') + return None + + return BestIfIndex.value + + def check_best_interface(self, ip='8.8.8.8'): + BestIfIndex = DWORD() + DestAddr = socket.inet_aton(ip) + + if not windll.iphlpapi.GetBestInterface(DestAddr, byref(BestIfIndex)) == NO_ERROR: + return False + + return True + + ########################################################################### + # Convert interface index to name + # + # NETIO_STATUS WINAPI ConvertInterfaceIndexToLuid( + # _In_ NET_IFINDEX InterfaceIndex, + # _Out_ PNET_LUID InterfaceLuid + # ); + # + # NETIO_STATUS WINAPI ConvertInterfaceLuidToNameA( + # _In_ const NET_LUID *InterfaceLuid, + # _Out_ PSTR InterfaceName, + # _In_ SIZE_T Length + # ); + + def convert_interface_index_to_name(self, index): + + InterfaceLuid = ULONG64() + + if not windll.iphlpapi.ConvertInterfaceIndexToLuid(index, byref(InterfaceLuid)) == NO_ERROR: + self.logger.error('Failed calling ConvertInterfaceIndexToLuid') + return None + + InterfaceName = create_string_buffer(NDIS_IF_MAX_STRING_SIZE + 1) + + if not windll.iphlpapi.ConvertInterfaceLuidToNameA(byref(InterfaceLuid), InterfaceName, NDIS_IF_MAX_STRING_SIZE + 1) == NO_ERROR: + self.logger.error('Failed calling ConvertInterfaceLuidToName') + return None + + return InterfaceName.value + + ########################################################################### + # DnsFlushResolverCache + def notify_ip_change(self, adapter_name): + + if windll.dhcpcsvc.DhcpNotifyConfigChange(0, adapter_name, 0, 0, 0, 0) == NO_ERROR: + self.logger.debug('Successfully performed adapter change notification on %s', adapter_name) + else: + self.logger.error('Failed to notify adapter change on %s', adapter_name) + + ########################################################################### + # DnsFlushResolverCache + def flush_dns(self): + if windll.dnsapi.DnsFlushResolverCache(): + self.logger.info('Flushed DNS cache.') + else: + self.logger.error('Failed to flush DNS cache. (DnsFlushResolverCache)') + + # As a backup call ipconfig /flushdns because DnsFlushResolverCache is undocumented. + try: + subprocess.check_call('ipconfig /flushdns', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except subprocess.CalledProcessError, e: + self.logger.error("Failed to flush DNS cache. (ipconfig)") + else: + self.logger.info('Flushed DNS cache. (ipconfig)') + + def get_reg_value(self, key, sub_key, value, sam = KEY_READ): + + try: + handle = OpenKey(key, sub_key, 0, sam) + [data, regtype] = QueryValueEx(handle, value) + CloseKey(handle) + if data == '': + raise WindowsError + + return data + + except WindowsError: + self.logger.error('Failed getting registry value %s.', value) + return None + + def set_reg_value(self, key, sub_key, value, data, type = REG_SZ, sam = KEY_WRITE): + + try: + handle = CreateKeyEx(key, sub_key, 0, sam) + SetValueEx(handle, value, 0, type, data) + CloseKey(handle) + + return True + + except WindowsError: + self.logger.error('Failed setting registry value %s', value) + return False + + ########################################################################### + # Set DNS Server + + def set_dns_server(self, dns_server = '127.0.0.1'): + + key = HKEY_LOCAL_MACHINE + sub_key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s" + value = 'NameServer' + + for adapter in self.get_active_ethernet_adapters(): + + # Preserve existing setting + dns_server_backup = self.get_reg_value(key, sub_key % adapter.AdapterName, value) + + # Restore previous value or a blank string if the key was not present + if dns_server_backup: + self.adapters_dns_server_backup[adapter.AdapterName] = (dns_server_backup, adapter.FriendlyName) + else: + self.adapters_dns_server_backup[adapter.AdapterName] = ('', adapter.FriendlyName) + + # Set new dns server value + if self.set_reg_value(key, sub_key % adapter.AdapterName, value, dns_server): + self.logger.info('Set DNS server %s on the adapter: %s', dns_server, adapter.FriendlyName) + self.notify_ip_change(adapter.AdapterName) + else: + self.logger.error('Failed to set DNS server %s on the adapter: %s', dns_server, adapter.FriendlyName) + + def restore_dns_server(self): + + key = HKEY_LOCAL_MACHINE + sub_key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s" + value = 'NameServer' + + for adapter_name in self.adapters_dns_server_backup: + + (dns_server, adapter_friendlyname) = self.adapters_dns_server_backup[adapter_name] + + # Restore dns server value + if self.set_reg_value(key, sub_key % adapter_name, value, dns_server): + self.logger.info('Restored DNS server %s on the adapter: %s', dns_server, adapter_friendlyname) + else: + self.logger.error('Failed to restore DNS server %s on the adapter: %s', dns_server, adapter_friendlyname) + + ########################################################################### + # Check if user is an Administrator + def is_user_an_admin(self): + return ctypes.windll.shell32.IsUserAnAdmin() + + ########################################################################### + # Execute process and detach + def execute_detached(self, execute_cmd): + DETACHED_PROCESS = 0x00000008 + + # import pdb + # pdb.set_trace() + try: + pid = subprocess.Popen(execute_cmd.split(), creationflags=DETACHED_PROCESS).pid + except Exception, e: + self.logger.error('Error: Failed to execute command: %s', execute_cmd) + self.logger.error(' %s', e) + else: + return pid + +def test_process_list(): + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + pid = self.get_pid_port_tcp(135) + if pid: + self.logger.info('pid: %d name: %s', pid, self.get_process_image_filename(pid)) + else: + self.logger.error('failed to get pid for tcp port 135') + + + pid = self.get_pid_port_udp(123) + if pid: + self.logger.info('pid: %d name: %s', pid, self.get_process_image_filename(pid)) + else: + self.logger.error('failed to get pid for udp port 123') + + pid = self.get_pid_port_tcp(1234) + if not pid: + self.logger.info('successfully returned None for unknown tcp port 1234') + + pid = self.get_pid_port_udp(1234) + if not pid: + self.logger.info('successfully returned None for unknown udp port 1234') + +def test_interfaces_list(): + + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + # for adapter in self.get_adapters_addresses(): + # self.logger.info('ethernet: %s enabled: %s index: %d friendlyname: %s name: %s', adapter.IfType == MIB_IF_TYPE_ETHERNET, adapter.OperStatus == IFOPERSTATUSUP, adapter.IfIndex, adapter.FriendlyName, adapter.AdapterName) + + + for dns_server in self.get_dns_servers(): + self.logger.info('dns: %s', dns_server) + + for gateway in self.get_gateways(): + self.logger.info('gateway: %s', gateway) + + for adapter in self.get_active_ethernet_adapters(): + self.logger.info('active ethernet index: %s friendlyname: %s name: %s', adapter.IfIndex, adapter.FriendlyName, adapter.AdapterName) + + +def test_registry_nameserver(): + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + key = HKEY_LOCAL_MACHINE + sub_key = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{cd17d5b5-bf83-44f5-8de7-d988e3db5451}' + value = 'NameServer' + data = '127.0.0.1' + + + data_tmp = self.get_reg_value(key, sub_key, value) + self.logger.info('NameServer: %s', data_tmp) + + if self.set_reg_value(key, sub_key, value, data): + self.logger.info('Successfully set value %s to data %s', value, data) + + data_tmp = self.get_reg_value(key, sub_key, value) + self.logger.info('Nameserver: %s', data_tmp) + else: + self.logger.info('Failed to set value %s to data %s', value, data) + + self.notify_ip_change('{cd17d5b5-bf83-44f5-8de7-d988e3db5451}') + + self.flush_dns() + + +def test_registry_gateway(): + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + key = HKEY_LOCAL_MACHINE + sub_key = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{cd17d5b5-bf83-44f5-8de7-d988e3db5451}' + #value = 'NameServer' + #data = '127.0.0.1' + + if self.get_reg_value(key, sub_key, 'DhcpDefaultGateway'): + self.logger.info('DefaultGateway is set') + + else: + ip = self.get_reg_value(key, sub_key, 'Dhcp') + #self.logger + + + self.notify_ip_change('{cd17d5b5-bf83-44f5-8de7-d988e3db5451}') + +def test_check_connectivity(): + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + if not self.check_gateways(): + self.logger.warning('No gateways found.') + else: + self.logger.info('Gateways PASS') + + if not self.check_active_ethernet_adapters(): + self.logger.warning('No active ethernet adapters found') + else: + self.logger.info('Active ethernet PASS') + + if not self.get_best_interface(): + self.logger.warning('No routable interface found.') + else: + self.logger.info('Routable interface PASS') + + if not self.check_dns_servers(): + self.logger.warning('No DNS servers configured') + else: + self.logger.info('DNS server PASS') + +def test_stop_service(): + + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + self.stop_service_helper('Dnscache') + + +def test_start_service(): + class Test(WinUtilMixin): + def __init__(self, name = 'WinUtil'): + self.logger = logging.getLogger(name) + + self = Test() + + self.start_service_helper('Dnscache') + +def main(): + pass + + #test_process_list() + + #test_interfaces_list() + + #test_registry_gateway() + + + #test_check_connectivity() + + test_stop_service() + #test_start_service() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fakenet/diverters/winutil.pyc b/fakenet/diverters/winutil.pyc new file mode 100644 index 0000000..ed72773 Binary files /dev/null and b/fakenet/diverters/winutil.pyc differ diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py new file mode 100644 index 0000000..f7c4207 --- /dev/null +++ b/fakenet/fakenet.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# +# FakeNet-NG is a next generation dynamic network analysis tool for malware +# analysts and penetration testers. +# +# Developed by Peter Kacherginsky + +import logging + +import os +import sys +import time + +from collections import OrderedDict + +from optparse import OptionParser,OptionGroup +from ConfigParser import ConfigParser + +import platform + +from optparse import OptionParser + +############################################################################### +# Listener services +import listeners +from listeners import * + +############################################################################### +# FakeNet +############################################################################### + +class Fakenet(): + + def __init__(self, logging_level = logging.INFO): + + self.logger = logging.getLogger('FakeNet') + self.logger.setLevel(logging_level) + + self.logging_level = logging_level + + # Diverter used to intercept and redirect traffic + self.diverter = None + + # FakeNet options and parameters + self.fakenet_config = dict() + + # Diverter options and parameters + self.diverter_config = dict() + + # Listener options and parameters + self.listeners_config = OrderedDict() + + # List of running listener providers + self.running_listener_providers = list() + + def parse_config(self, config_filename): + + if not config_filename: + + config_filename = os.path.join(os.path.dirname(__file__), 'configs', 'default.ini') + + if not os.path.exists(config_filename): + + config_filename = os.path.join(os.path.dirname(__file__), 'configs', config_filename) + + if not os.path.exists(config_filename): + + self.logger.error('Could not open configuration file %s', config_filename) + sys.exit(1) + + config = ConfigParser() + config.read(config_filename) + + self.logger.info('Loaded configuration file: %s', config_filename) + + # Parse configuration + for section in config.sections(): + + if section == 'FakeNet': + self.fakenet_config = dict(config.items(section)) + + elif section == 'Diverter': + self.diverter_config = dict(config.items(section)) + + elif config.getboolean(section, 'enabled'): + self.listeners_config[section] = dict(config.items(section)) + + def start(self): + + if self.fakenet_config.get('diverttraffic') and self.fakenet_config['diverttraffic'].lower() == 'yes': + + # Select platform specific diverter + if platform.system() == 'Windows': + + # Check Windows version + if platform.release() in ['2000', 'XP', '2003Server', 'post2003']: + self.logger.error('Error: FakeNet-NG only supports Windows Vista+.') + self.logger.error(' Please use the original Fakenet for older versions of Windows.') + sys.exit(1) + + # Check architecture + if platform.machine() == 'AMD64' and platform.architecture()[0] != '64bit': + self.logger.error('Error: Please install 64-bit Python interpreter to support diverter functions.') + sys.exit(1) + + + from diverters.windows import Diverter + self.diverter = Diverter(self.diverter_config, self.listeners_config, self.logging_level) + + else: + self.logger.error('Error: Your system %s is currently not supported.', platform.system()) + sys.exit(1) + + + # Start all of the listeners + for listener_name in self.listeners_config: + + listener_config = self.listeners_config[listener_name] + + # Anonymous listener + if not 'listener' in listener_config: + self.logger.info('Anonymous %s listener on %s port %s...', listener_name, listener_config['protocol'], listener_config['port']) + continue + + # Get a specific provider for the listener name + try: + listener_module = getattr(listeners, listener_config['listener']) + listener_provider = getattr(listener_module, listener_config['listener']) + + except AttributeError as e: + self.logger.error('Listener %s is not implemented.', listener_config['listener']) + self.logger.error("%s" % e) + + else: + # Listener provider object + listener_provider_instance = listener_provider(listener_config, listener_name, self.logging_level) + + # Store listener provider object + self.running_listener_providers.append(listener_provider_instance) + + try: + listener_provider_instance.start() + except Exception, e: + self.logger.error('Error starting %s listener:', listener_config['listener']) + self.logger.error(" %s" % e) + + + # Start the diverter + if self.diverter: + self.diverter.start() + + def stop(self): + + self.logger.info("Stopping...") + + for running_listener_provider in self.running_listener_providers: + running_listener_provider.stop() + + if self.diverter: + self.diverter.stop() + + sys.exit(0) + +def main(): + + print """ + ______ _ ________ _ _ ______ _______ _ _ _____ + | ____/\ | |/ / ____| \ | | ____|__ __| | \ | |/ ____| + | |__ / \ | ' /| |__ | \| | |__ | |______| \| | | __ + | __/ /\ \ | < | __| | . ` | __| | |______| . ` | | |_ | + | | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| | + |_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____| + + Version 1.0 + _____________________________________________________________ + Developed by + Peter Kacherginsky + FLARE (FireEye Labs Advanced Reversing Engineering) + _____________________________________________________________ + """ + + # Parse command line arguments + parser = OptionParser(usage = "fakenet.py [options]:") + parser.add_option("-c", "--config-file", action="store", dest="config_file", + help="configuration filename", metavar="FILE") + parser.add_option("-v", "--verbose", + action="store_true", dest="verbose", default=False, + help="print more verbose messages.") + parser.add_option("-l", "--log-file", action="store", dest="log_file") + + (options, args) = parser.parse_args() + + logging_level = logging.DEBUG if options.verbose else logging.INFO + + if options.log_file: + logging.basicConfig(format='%(asctime)s [%(name)18s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.INFO, filename=options.log_file) + else: + logging.basicConfig(format='%(asctime)s [%(name)18s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.INFO) + + fakenet = Fakenet(logging_level) + fakenet.parse_config(options.config_file) + fakenet.start() + + try: + while True: + time.sleep(1) + + except KeyboardInterrupt: + fakenet.stop() + + except: + e = sys.exc_info()[0] + fakenet.logger.error("ERROR: %s" % e) + fakenet.stop() + +if __name__ == '__main__': + main() diff --git a/fakenet/lib/32/WinDivert.dll b/fakenet/lib/32/WinDivert.dll new file mode 100644 index 0000000..5a58851 Binary files /dev/null and b/fakenet/lib/32/WinDivert.dll differ diff --git a/fakenet/lib/32/WinDivert32.sys b/fakenet/lib/32/WinDivert32.sys new file mode 100644 index 0000000..a1f83fb Binary files /dev/null and b/fakenet/lib/32/WinDivert32.sys differ diff --git a/fakenet/lib/64/WinDivert.dll b/fakenet/lib/64/WinDivert.dll new file mode 100644 index 0000000..7b34194 Binary files /dev/null and b/fakenet/lib/64/WinDivert.dll differ diff --git a/fakenet/lib/64/WinDivert64.sys b/fakenet/lib/64/WinDivert64.sys new file mode 100644 index 0000000..245939b Binary files /dev/null and b/fakenet/lib/64/WinDivert64.sys differ diff --git a/fakenet/listeners/DNSListener.py b/fakenet/listeners/DNSListener.py new file mode 100644 index 0000000..407f8a7 --- /dev/null +++ b/fakenet/listeners/DNSListener.py @@ -0,0 +1,222 @@ +import logging + +import threading +import SocketServer +from dnslib import * + +import ssl +import socket + +class DNSListener(): + + def __init__(self, config = {}, name = 'DNSListener', logging_level = logging.INFO): + + self.logger = logging.getLogger(name) + self.logger.setLevel(logging_level) + + self.config = config + self.local_ip = '0.0.0.0' + self.server = None + + self.logger.info('Starting...') + + self.logger.debug('Initialized with config:') + for key, value in config.iteritems(): + self.logger.debug(' %10s: %s', key, value) + + def start(self): + + # Start UDP listener + if self.config['protocol'].lower() == 'udp': + self.logger.debug('Starting UDP ...') + self.server = ThreadedUDPServer((self.local_ip, int(self.config.get('port', 53))), self.config, self.logger, UDPHandler) + + # Start TCP listener + elif self.config['protocol'].lower() == 'tcp': + self.logger.debug('Starting TCP ...') + self.server = ThreadedTCPServer((self.local_ip, int(self.config.get('port', 53))), self.config, self.logger, TCPHandler) + + self.server.nxdomains = int(self.config.get('nxdomains', 0)) + + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + + def stop(self): + self.logger.debug('Stopping...') + + # Stop listener + if self.server: + self.server.shutdown() + self.server.server_close() + + +class DNSHandler(): + + def parse(self,data): + response = "" + + try: + # Parse data as DNS + d = DNSRecord.parse(data) + + except Exception, e: + self.server.logger.error('Error: Invalid DNS Request') + self.server.logger.info('%s', '-'*80) + for line in hexdump_table(data): + self.server.logger.info(line) + self.server.logger.info('%s', '-'*80,) + + else: + # Only Process DNS Queries + if QR[d.header.qr] == "QUERY": + + # Gather query parameters + # NOTE: Do not lowercase qname here, because we want to see + # any case request weirdness in the logs. + qname = str(d.q.qname) + + # Chop off the last period + if qname[-1] == '.': qname = qname[:-1] + + qtype = QTYPE[d.q.qtype] + + self.server.logger.info('Received %s request for domain \'%s\'.', qtype, qname) + + # 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) + + # Get fake record from the configuration or use the external address + fake_record = self.server.config.get('dnsresponse', socket.gethostbyname(socket.gethostname())) + + if qtype == 'A': + + if self.server.nxdomains > 0: + self.server.logger.info('Ignoring query. NXDomains: %d', self.server.nxdomains) + self.server.nxdomains -= 1 + else: + # dnslib doesn't like trailing dots + if fake_record[-1] == ".": fake_record = fake_record[:-1] + + self.server.logger.info('Responding with \'%s\'', fake_record) + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record))) + + response = response.pack() + + return response + +class UDPHandler(DNSHandler, SocketServer.BaseRequestHandler): + + def handle(self): + + try: + (data,socket) = self.request + response = self.parse(data) + + if response: + socket.sendto(response, self.client_address) + + except socket.error as msg: + self.server.logger.error('Error: %s', msg.strerror or msg) + + except Exception, e: + self.server.logger.error('Error: %s', e) + +class TCPHandler(DNSHandler, SocketServer.BaseRequestHandler): + + def handle(self): + + # Timeout connection to prevent hanging + self.request.settimeout(int(self.server.config.get('timeout', 5))) + + try: + data = self.request.recv(1024) + + # Remove the addition "length" parameter used in the + # TCP DNS protocol + data = data[2:] + response = self.parse(data) + + if response: + # Calculate and add the additional "length" parameter + # used in TCP DNS protocol + length = binascii.unhexlify("%04x" % len(response)) + self.request.sendall(length+response) + + except socket.timeout: + self.server.logger.warning('Connection timeout.') + + except socket.error as msg: + self.server.logger.error('Error: %s', msg.strerror) + + except Exception, e: + self.server.logger.error('Error: %s', e) + +class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): + + # Override SocketServer.UDPServer to add extra parameters + def __init__(self, server_address, config, logger, RequestHandlerClass): + self.config = config + self.logger = logger + SocketServer.UDPServer.__init__(self, server_address, RequestHandlerClass) + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + + # Override default value + allow_reuse_address = True + + # Override SocketServer.TCPServer to add extra parameters + def __init__(self, server_address, config, logger, RequestHandlerClass): + self.config = config + self.logger = logger + SocketServer.TCPServer.__init__(self,server_address,RequestHandlerClass) + +def hexdump_table(data, length=16): + + hexdump_lines = [] + for i in range(0, len(data), 16): + chunk = data[i:i+16] + hex_line = ' '.join(["%02X" % ord(b) for b in chunk ] ) + ascii_line = ''.join([b if ord(b) > 31 and ord(b) < 127 else '.' for b in chunk ] ) + hexdump_lines.append("%04X: %-*s %s" % (i, length*3, hex_line, ascii_line )) + return hexdump_lines + +############################################################################### +# Testing code +def test(config): + + print "\t[DNSListener] Testing 'google.com' A record." + query = DNSRecord(q=DNSQuestion('google.com',getattr(QTYPE,'A'))) + answer_pkt = query.send('localhost', int(config.get('port', 53))) + answer = DNSRecord.parse(answer_pkt) + + print '-'*80 + print answer + print '-'*80 + + +def main(): + logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) + + config = {'port': '53', 'protocol': 'UDP', 'dnsresponse': '127.0.0.1', 'nxdomains': 3 } + + listener = DNSListener(config, logging_level = logging.DEBUG) + listener.start() + + + ########################################################################### + # Run processing + import time + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + ########################################################################### + # Run tests + test(config) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fakenet/listeners/DNSListener.pyc b/fakenet/listeners/DNSListener.pyc new file mode 100644 index 0000000..e2b79a7 Binary files /dev/null and b/fakenet/listeners/DNSListener.pyc differ diff --git a/fakenet/listeners/HTTPListener.py b/fakenet/listeners/HTTPListener.py new file mode 100644 index 0000000..b058801 --- /dev/null +++ b/fakenet/listeners/HTTPListener.py @@ -0,0 +1,292 @@ +import logging + +import os +import sys + +import threading +import SocketServer +import BaseHTTPServer + +import ssl +import socket + +import posixpath +import mimetypes + +import time + +MIME_FILE_RESPONSE = { + 'text/html': 'FakeNet.html', + 'image/png': 'FakeNet.png', + 'image/ico': 'FakeNet.ico', + 'image/jpeg': 'FakeNet.jpg', + 'application/octet-stream': 'FakeNetMini.exe', + 'application/x-msdownload': 'FakeNetMini.exe', + 'application/pdf': 'FakeNet.pdf', + 'application/xml': 'FakeNet.html' +} + +class HTTPListener(): + + if not mimetypes.inited: + mimetypes.init() # try to read system mime.types + extensions_map = mimetypes.types_map.copy() + extensions_map.update({ + '': 'text/html', # Default + }) + + def __init__(self, config = {}, name = 'HTTPListener', logging_level = logging.DEBUG): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging_level) + + self.config = config + self.name = name + self.local_ip = '0.0.0.0' + self.server = None + + self.logger.info('Starting...') + + self.logger.debug('Initialized with config:') + for key, value in config.iteritems(): + self.logger.debug(' %10s: %s', key, value) + + # Initialize webroot directory + self.webroot_path = self.config.get('webroot','defaultFiles') + + # Try absolute path first + if not os.path.exists(self.webroot_path): + + # Try to locate the webroot directory relative to application path + self.webroot_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), self.webroot_path) + + if not os.path.exists(self.webroot_path): + self.logger.error('Could not locate webroot directory: %s', self.webroot_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.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 = 'privkey.pem' + if not os.path.exists(keyfile_path): + keyfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), keyfile_path) + + if not os.path.exists(keyfile_path): + self.logger.error('Could not locate privkey.pem') + sys.exit(1) + + certfile_path = 'server.pem' + if not os.path.exists(certfile_path): + certfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), certfile_path) + + if not os.path.exists(certfile_path): + self.logger.error('Could not locate certfile.pem') + sys.exit(1) + + self.server.socket = ssl.wrap_socket(self.server.socket, keyfile=keyfile_path, certfile=certfile_path, server_side=True, ciphers='RSA') + + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + + def stop(self): + self.logger.info('Stopping...') + if self.server: + self.server.shutdown() + self.server.server_close() + +class ThreadedHTTPServer(BaseHTTPServer.HTTPServer): + + def handle_error(self, request, client_address): + exctype, value = sys.exc_info()[:2] + self.logger.error('Error: %s', value) + +class ThreadedHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + + def __init__(self, *args): + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args) + + def setup(self): + self.request.settimeout(int(self.server.config.get('timeout', 10))) + BaseHTTPServer.BaseHTTPRequestHandler.setup(self) + + def do_HEAD(self): + self.server.logger.info('Received HEAD request') + + # Process request + self.server.logger.info('%s', '-'*80) + self.server.logger.info(self.requestline) + for line in str(self.headers).split("\n"): + self.server.logger.info(line) + self.server.logger.info('%s', '-'*80) + + # Prepare response + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + + def do_GET(self): + + self.server.logger.info('Received a GET request.') + + # Process request + self.server.logger.info('%s', '-'*80) + self.server.logger.info(self.requestline) + for line in str(self.headers).split("\n"): + self.server.logger.info(line) + self.server.logger.info('%s', '-'*80) + + # Get response type based on the requested path + response, response_type = self.get_response(self.path) + + # Prepare response + self.send_response(200) + self.send_header("Content-Type", response_type) + self.send_header("Content-Length", len(response)) + self.end_headers() + + self.wfile.write(response) + + def do_POST(self): + self.server.logger.info('Received a POST request') + + post_body = '' + + content_len = int(self.headers.get('content-length', 0)) + post_body = self.rfile.read(content_len) + + # Process request + self.server.logger.info('%s', '-'*80) + self.server.logger.info(self.requestline) + for line in str(self.headers).split("\n"): + self.server.logger.info(line) + for line in post_body.split("\n"): + self.server.logger.info(line) + self.server.logger.info('%s', '-'*80) + + # Store HTTP Posts + if self.server.config.get('dumphttpposts') and self.server.config['dumphttpposts'].lower() == 'yes': + http_filename = "%s_%s.txt" % (self.server.config.get('dumphttppostsfileprefix', 'http'), time.strftime("%Y%m%d_%H%M%S")) + + self.server.logger.info('Storing HTTP POST headers and data to %s.', http_filename) + http_f = open(http_filename, 'wb') + + if http_f: + http_f.write(self.requestline + "\r\n") + http_f.write(str(self.headers) + "\r\n") + http_f.write(post_body) + + http_f.close() + else: + self.server.logger.error('Failed to write HTTP POST headers and data to %s.', http_filename) + + # Get response type based on the requested path + response, response_type = self.get_response(self.path) + + # Prepare response + self.send_response(200) + self.send_header("Content-Type", response_type) + self.send_header("Content-Length", len(response)) + self.end_headers() + + self.wfile.write(response) + + def get_response(self, path): + response = "FakeNet

FakeNet

" + response_type = 'text/html' + + if path[-1] == '/': + response_type = 'text/html' + path += 'index.html' + else: + _, ext = posixpath.splitext(path) + response_type = self.server.extensions_map.get(ext, 'text/html') + + response_filename = os.path.join(self.server.webroot_path, path[1:]) + + # Check the requested path exists + if not os.path.exists(response_filename): + + self.server.logger.debug('Could not find path: %s', response_filename) + + # Try default MIME file + response_filename = os.path.join(self.server.webroot_path, MIME_FILE_RESPONSE.get(response_type, 'FakeNet.html')) + + # Check default MIME file exists + if not os.path.exists(response_filename): + self.server.logger.debug('Could not find path: %s', response_filename) + self.server.logger.error('Could not locate requested file or default handler.') + return (response, response_type) + + self.server.logger.info('Responding with mime type: %s file: %s', response_type, response_filename) + + try: + f = open(response_filename, 'rb') + except Exception, e: + self.server.logger.error('Failed to open response file: %s', response_filename) + response_type = 'text/html' + else: + response = f.read() + f.close() + + return (response, response_type) + + def log_message(self, format, *args): + return + + +############################################################################### +# Testing code +def test(config): + + import requests + + url = "%s://localhost:%s" % ('http' if config.get('usessl') == 'No' else 'https', int(config.get('port', 8080))) + + print "\t[HTTPListener] Testing HEAD request." + print '-'*80 + print requests.head(url, verify=False, stream=True).text + print '-'*80 + + print "\t[HTTPListener] Testing GET request." + print '-'*80 + print requests.get(url, verify=False, stream=True).text + print '-'*80 + + print "\t[HTTPListener] Testing POST request." + print '-'*80 + print requests.post(url, {'param1':'A'*80, 'param2':'B'*80}, verify=False, stream=True).text + print '-'*80 + +def main(): + logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) + + config = {'port': '8443', 'usessl': 'Yes', 'webroot': '../defaultFiles' } + + listener = HTTPListener(config) + listener.start() + + ########################################################################### + # Run processing + import time + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + ########################################################################### + # Run tests + test(config) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fakenet/listeners/HTTPListener.pyc b/fakenet/listeners/HTTPListener.pyc new file mode 100644 index 0000000..d3fdb97 Binary files /dev/null and b/fakenet/listeners/HTTPListener.pyc differ diff --git a/fakenet/listeners/RawListener.py b/fakenet/listeners/RawListener.py new file mode 100644 index 0000000..0927cfa --- /dev/null +++ b/fakenet/listeners/RawListener.py @@ -0,0 +1,199 @@ +import logging + +import os +import sys + +import threading +import SocketServer + +import ssl +import socket + +class RawListener(): + + def __init__(self, config, name = 'RawListener', logging_level = logging.INFO): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging_level) + + self.config = config + self.name = name + self.local_ip = '0.0.0.0' + self.server = None + + self.logger.info('Starting...') + + self.logger.debug('Initialized with config:') + for key, value in config.iteritems(): + self.logger.debug(' %10s: %s', key, value) + + def start(self): + + # Start listener + if self.config.get('protocol') != None: + + if self.config['protocol'].lower() == 'tcp': + self.logger.debug('Starting TCP ...') + self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) + + elif self.config['protocol'].lower() == 'udp': + self.logger.debug('Starting UDP ...') + self.server = ThreadedUDPServer((self.local_ip, int(self.config['port'])), ThreadedUDPRequestHandler) + + else: + self.logger.error('Unknown protocol %s', self.config['protocol']) + return + else: + self.logger.error('Protocol is not defined.') + return + + self.server.logger = self.logger + self.server.config = self.config + + if self.config.get('usessl') == 'Yes': + self.logger.debug('Using SSL socket.') + + keyfile_path = 'privkey.pem' + if not os.path.exists(keyfile_path): + keyfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), keyfile_path) + + if not os.path.exists(keyfile_path): + self.logger.error('Could not locate privkey.pem') + sys.exit(1) + + certfile_path = 'server.pem' + if not os.path.exists(certfile_path): + certfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), certfile_path) + + if not os.path.exists(certfile_path): + self.logger.error('Could not locate certfile.pem') + sys.exit(1) + + self.server.socket = ssl.wrap_socket(self.server.socket, keyfile=keyfile_path, certfile=certfile_path, server_side=True, ciphers='RSA') + + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + + def stop(self): + self.logger.debug('Stopping...') + if self.server: + self.server.shutdown() + self.server.server_close() + +class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): + + def handle(self): + + # Timeout connection to prevent hanging + self.request.settimeout(int(self.server.config.get('timeout', 5))) + + try: + + while True: + + data = self.request.recv(1024) + + if not data: + break + + self.server.logger.info('Received %d bytes.', len(data)) + self.server.logger.info('%s', '-'*80) + for line in hexdump_table(data): + self.server.logger.info(line) + self.server.logger.info('%s', '-'*80,) + + self.request.sendall(data) + + except socket.timeout: + self.server.logger.warning('Connection timeout') + + except socket.error as msg: + self.server.logger.error('Error: %s', msg.strerror or msg) + + except Exception, e: + self.server.logger.error('Error: %s', e) + +class ThreadedUDPRequestHandler(SocketServer.BaseRequestHandler): + + def handle(self): + + try: + (data,socket) = self.request + + if not data: + return + + self.server.logger.info('Received %d bytes.', len(data)) + self.server.logger.debug('%s', '-'*80,) + for line in hexdump_table(data): + self.server.logger.debug(line) + self.server.logger.debug('%s', '-'*80,) + + socket.sendto(data, self.client_address) + + except socket.error as msg: + self.server.logger.error('Error: %s', msg.strerror or msg) + + except Exception, e: + self.server.logger.error('Error: %s', e) + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + pass + +class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): + pass + +def hexdump_table(data, length=16): + + hexdump_lines = [] + for i in range(0, len(data), 16): + chunk = data[i:i+16] + hex_line = ' '.join(["%02X" % ord(b) for b in chunk ] ) + ascii_line = ''.join([b if ord(b) > 31 and ord(b) < 127 else '.' for b in chunk ] ) + hexdump_lines.append("%04X: %-*s %s" % (i, length*3, hex_line, ascii_line )) + return hexdump_lines + +############################################################################### +# Testing code +def test(config): + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + print "\t[RawListener] Sending request:\n%s" % "HELO\n" + try: + # Connect to server and send data + sock.connect(('localhost', int(config.get('port', 23)))) + sock.sendall("HELO\n") + + # Receive data from the server and shut down + received = sock.recv(1024) + finally: + sock.close() + +def main(): + logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) + + config = {'port': '1337', 'usessl': 'No', 'protocol': 'tcp'} + + listener = RawListener(config) + listener.start() + + + ########################################################################### + # Run processing + import time + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + ########################################################################### + # Run tests + #test(config) + + listener.stop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/fakenet/listeners/RawListener.pyc b/fakenet/listeners/RawListener.pyc new file mode 100644 index 0000000..4409d20 Binary files /dev/null and b/fakenet/listeners/RawListener.pyc differ diff --git a/fakenet/listeners/SMTPListener.py b/fakenet/listeners/SMTPListener.py new file mode 100644 index 0000000..7ada194 --- /dev/null +++ b/fakenet/listeners/SMTPListener.py @@ -0,0 +1,176 @@ +import logging + +import sys +import os + +import threading +import SocketServer + +import ssl +import socket + +class SMTPListener(): + + def __init__(self, config, name = 'SMTPListener', logging_level = logging.INFO): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging_level) + + self.config = config + self.name = name + self.local_ip = '0.0.0.0' + self.server = None + + self.logger.info('Starting...') + + self.logger.debug('Initialized with config:') + for key, value in config.iteritems(): + self.logger.debug(' %10s: %s', key, value) + + def start(self): + self.logger.debug('Starting...') + + self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) + + if self.config.get('usessl') == 'Yes': + self.logger.debug('Using SSL socket') + + keyfile_path = 'privkey.pem' + if not os.path.exists(keyfile_path): + keyfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), keyfile_path) + + if not os.path.exists(keyfile_path): + self.logger.error('Could not locate privkey.pem') + sys.exit(1) + + certfile_path = 'server.pem' + if not os.path.exists(certfile_path): + certfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), certfile_path) + + if not os.path.exists(certfile_path): + self.logger.error('Could not locate certfile.pem') + sys.exit(1) + + self.server.socket = ssl.wrap_socket(self.server.socket, keyfile='privkey.pem', certfile='server.pem', server_side=True, ciphers='RSA') + + self.server.logger = self.logger + self.server.config = self.config + + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + + def stop(self): + self.logger.info('Stopping...') + if self.server: + self.server.shutdown() + self.server.server_close() + +class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): + + def handle(self): + + # Timeout connection to prevent hanging + self.request.settimeout(int(self.server.config.get('timeout', 5))) + + try: + + self.request.sendall("220 PracticalMalwareAnalysis.COM STMP Service Ready\r\n") + while True: + data = self.request.recv(4096) + self.server.logger.info('Recieved Data.') + for line in data.split("\n"): + self.server.logger.debug(line) + + command = data[:4].upper() + + if command == '': + break + + elif command == 'HELO': + self.request.sendall("250 PracticalMalwareAnalysis.com\r\n") + + elif command in ['MAIL', 'RCPT', 'NOOP', 'RSET']: + self.request.sendall("250 OK\r\n") + + elif command == 'QUIT': + self.request.sendall("221 PracticalMalwareAnalysis.com bye\r\n") + + elif command == "DATA": + self.request.sendall("354 start mail input, end with .\r\n") + + mail_data = "" + while True: + mail_data_chunk = self.request.recv(4096) + + if not mail_data_chunk: + break + + mail_data += mail_data_chunk + + if "\r\n.\r\n" in mail_data: + break + + self.server.logger.info('Received mail data.') + for line in mail_data.split("\n"): + self.server.logger.debug(line) + + self.request.sendall("250 OK\r\n") + + else: + self.request.sendall("503 Command not supported\r\n") + + except socket.timeout: + self.server.logger.warning('Connection timeout') + + except socket.error as msg: + self.server.logger.error('Error: %s', msg.strerror or msg) + + except Exception, e: + self.server.logger.error('Error: %s', e) + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + pass + +############################################################################### +# Testing code +def test(config): + + import smtplib + + logger = logging.getLogger('SMTPListenerTest') + + server = smtplib.SMTP_SSL('localhost', config.get('port', 25)) + + message = "From: test@test.com\r\nTo: test@test.com\r\n\r\nTest message\r\n" + + logger.info('Testing email request.') + logger.info('-'*80) + server.set_debuglevel(1) + server.sendmail('test@test.com','test@test.com', message) + server.quit() + logger.info('-'*80) + +def main(): + logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) + + config = {'port': '25', 'usessl': 'Yes', 'timeout': 10 } + + listener = SMTPListener(config) + listener.start() + + ########################################################################### + # Run processing + import time + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + ########################################################################### + # Run tests + test(config) + +if __name__ == '__main__': + main() diff --git a/fakenet/listeners/SMTPListener.pyc b/fakenet/listeners/SMTPListener.pyc new file mode 100644 index 0000000..a078132 Binary files /dev/null and b/fakenet/listeners/SMTPListener.pyc differ diff --git a/fakenet/listeners/__init__.py b/fakenet/listeners/__init__.py new file mode 100644 index 0000000..a4458cc --- /dev/null +++ b/fakenet/listeners/__init__.py @@ -0,0 +1,7 @@ +import RawListener +import HTTPListener +import DNSListener +import SMTPListener + +__all__ = ['RawListener', 'HTTPListener', 'DNSListener', 'SMTPListener'] + diff --git a/fakenet/listeners/__init__.pyc b/fakenet/listeners/__init__.pyc new file mode 100644 index 0000000..50c46a1 Binary files /dev/null and b/fakenet/listeners/__init__.pyc differ diff --git a/fakenet/listeners/privkey.pem b/fakenet/listeners/privkey.pem new file mode 100644 index 0000000..1636941 --- /dev/null +++ b/fakenet/listeners/privkey.pem @@ -0,0 +1,47 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDzcX3h/MmqeYoj +t3qVhgcV8NwApbBYr6iLmmx384ayykau/0lfcN2NGovofwMSGyqFom42d6GgFLEJ +uaPiQ//RZK6NQl2w1X2wP/Un4SET5Id4sMBShR2zS/5H7GgSlgqwUqKegnel348b +/A2YcdAd4iQ97yBxINxbkciN/d8L2omI4etreD/yghqX5WN2UH+NoSg7FdpvgBIh +aMKsqC9vEqbgXaIFr3jDJVjyEy4wBfvFKTgEAIvHhE2B6B1/ij3eJk0ZKpub3Du2 +gekZTpqajQL4viE1wjvydjJq/ixL2dr3q3+Ko/4xl0bLU8r6+/0CkkFnMxMP2+mp +4xVT9uM3AgMBAAECggEBAJpM7hAGDMCbxp361pzdVbJndtqGKm8b74WEvImO9mpu +YTzcHGJ9BEBCejlD/+tDAsGvAZJOY0g2tTvHyYNJvGS5HxXz4bSKrN7Aux+qxy93 +oxIxXcUwEHIrkaF+yzw0k9PMnLxBT5r4Rxniua9NPC8kvfnvji2GOYVksqylI/G7 +NwBLrYjFOJLXqikVZveyG0uQPXxwtRwaVgDSU7QX8kxXfpwsaZGdnm7yk80hrNte +U4U/A7kv63tE0+qFu9jQVatYwcGAR7kNr9qsFOHvxPbEQkvS6yOUHMdNF8ZpwFbA +vv6O1pt4jV7kAo5TLFoGXgnONBifzZCTD127laH1j1kCgYEA/Te6CsWbZm/6pPFJ +QpVyzDlSam7hCb5rmOneoOeIjo2CLn22DEeu9ZqETj2xLMfA0diV1r23Eia+kHwO +3XdNKLg3/Ue22FpQuV4iCqxDXkwdM/hMeyeLssQmNlGE0+iBaXL8UZCu5zWnw2TD +pkjeS6KGPCMN1MvYdhGIzYxt/zUCgYEA9h5FVLxBA0s40jgILpPvLbIF84cVDF46 +xnFs7c7jQSH+DYK9C7GFYKyDw3UTI5+UtwalxsBYWSLZuTSzdpUocKzT31TmbY2M +nOYb3s7VHrJUXWnCl5m6EW1oqRUVMZuseECg7IABWUaI8qUQAbU2JAD1VeoslpVo +s7C9TzuxCjsCgYEA395l3+Im6uDzkuIz5+cjEEVZhPm0gZ3VmOKjTlSFGasoPhws +WB0EJOXQNTA7tSCBa8V1a95cvXJ9plXX+prgH7EG5ymBETSAC/KaXB9CjFr0sp7C +V1t2Gb1rHzjhG0yDJYxgYWhuCcIZKRmsFBZ2Wh49WWuQbeMu4+vKrBeMpEUCgYEA +4+Wh01XCaZdk1Ru3T9ICHnEDG75QmjRC/nAXKplxS2V7hu0xujs2Qw9br0igYVMq +dNP9+20uIdOogduv9jUlzjfqtJk8CeORedOFqSoipBsDDcCZxKE/b1W9HRv9lQMO +kAdgO75IAW/T7cM0cDBpIquUgWqmwQU2f3U/xreCNvMCfxQQiFTTSyhnZu195hjJ +fgNZ3SODPMkQoh8Pah1E5Hb83umGBPvt4MN2bfm8wWmLEjpU93xiJa2uN8Jzx5Mb +K3hs4JcPtjJ2m2x0aDt1RGFhllEl0l5tOQg+cKy9jnqA/O1wmMOXsFgYS5Fhg1Me +o70kQ6YRH+pBgnP7Pgcu4X0= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAPHrk7xP1wNXMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAxMjEyMjM3MDdaFw0xNjAyMjAyMjM3MDdaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAPNxfeH8yap5iiO3epWGBxXw3AClsFivqIuabHfzhrLKRq7/SV9w3Y0ai+h/ +AxIbKoWibjZ3oaAUsQm5o+JD/9Fkro1CXbDVfbA/9SfhIRPkh3iwwFKFHbNL/kfs +aBKWCrBSop6Cd6Xfjxv8DZhx0B3iJD3vIHEg3FuRyI393wvaiYjh62t4P/KCGpfl +Y3ZQf42hKDsV2m+AEiFowqyoL28SpuBdogWveMMlWPITLjAF+8UpOAQAi8eETYHo +HX+KPd4mTRkqm5vcO7aB6RlOmpqNAvi+ITXCO/J2Mmr+LEvZ2verf4qj/jGXRstT +yvr7/QKSQWczEw/b6anjFVP24zcCAwEAAaNQME4wHQYDVR0OBBYEFHB5zTo/9cg+ +8kTzB3WDwATror8YMB8GA1UdIwQYMBaAFHB5zTo/9cg+8kTzB3WDwATror8YMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACH3jlYfLBR0kGALkJSq9tRI +fB6sjjVOFrZtCt5sOqxh9We3wvMsr2TPAuEVEhiPQ1w1U4x4DTQU6A65N1KOj9Le +anjjlyjNSTqXvNUbxp0wh8x4LJ8DtmsfdXXYp3LsColBmh7JQn/2TL687B9tBXiq +p3P6wkuIOHX45UOQ0kvD4wRf3t+8mL4TmrCH4YHqzbcjI1KXGDfmVQ7i64nYrl3z +v7UCA6Xh0MShv3c0AtX1ccsxObEwjJzWA30zYUQtIWxXpRtLlkiflFU8ak1HzmBU +AHsg1T1KI1012+a1j/LWkKqb91EpUjC/DlaHz8zDOH/S1IEWM6UFjwKYnHLPxoM= +-----END CERTIFICATE----- + diff --git a/fakenet/listeners/server.pem b/fakenet/listeners/server.pem new file mode 100644 index 0000000..aff78bd --- /dev/null +++ b/fakenet/listeners/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAPHrk7xP1wNXMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAxMjEyMjM3MDdaFw0xNjAyMjAyMjM3MDdaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAPNxfeH8yap5iiO3epWGBxXw3AClsFivqIuabHfzhrLKRq7/SV9w3Y0ai+h/ +AxIbKoWibjZ3oaAUsQm5o+JD/9Fkro1CXbDVfbA/9SfhIRPkh3iwwFKFHbNL/kfs +aBKWCrBSop6Cd6Xfjxv8DZhx0B3iJD3vIHEg3FuRyI393wvaiYjh62t4P/KCGpfl +Y3ZQf42hKDsV2m+AEiFowqyoL28SpuBdogWveMMlWPITLjAF+8UpOAQAi8eETYHo +HX+KPd4mTRkqm5vcO7aB6RlOmpqNAvi+ITXCO/J2Mmr+LEvZ2verf4qj/jGXRstT +yvr7/QKSQWczEw/b6anjFVP24zcCAwEAAaNQME4wHQYDVR0OBBYEFHB5zTo/9cg+ +8kTzB3WDwATror8YMB8GA1UdIwQYMBaAFHB5zTo/9cg+8kTzB3WDwATror8YMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACH3jlYfLBR0kGALkJSq9tRI +fB6sjjVOFrZtCt5sOqxh9We3wvMsr2TPAuEVEhiPQ1w1U4x4DTQU6A65N1KOj9Le +anjjlyjNSTqXvNUbxp0wh8x4LJ8DtmsfdXXYp3LsColBmh7JQn/2TL687B9tBXiq +p3P6wkuIOHX45UOQ0kvD4wRf3t+8mL4TmrCH4YHqzbcjI1KXGDfmVQ7i64nYrl3z +v7UCA6Xh0MShv3c0AtX1ccsxObEwjJzWA30zYUQtIWxXpRtLlkiflFU8ak1HzmBU +AHsg1T1KI1012+a1j/LWkKqb91EpUjC/DlaHz8zDOH/S1IEWM6UFjwKYnHLPxoM= +-----END CERTIFICATE----- diff --git a/fakenet/privkey.pem b/fakenet/privkey.pem new file mode 100644 index 0000000..1636941 --- /dev/null +++ b/fakenet/privkey.pem @@ -0,0 +1,47 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDzcX3h/MmqeYoj +t3qVhgcV8NwApbBYr6iLmmx384ayykau/0lfcN2NGovofwMSGyqFom42d6GgFLEJ +uaPiQ//RZK6NQl2w1X2wP/Un4SET5Id4sMBShR2zS/5H7GgSlgqwUqKegnel348b +/A2YcdAd4iQ97yBxINxbkciN/d8L2omI4etreD/yghqX5WN2UH+NoSg7FdpvgBIh +aMKsqC9vEqbgXaIFr3jDJVjyEy4wBfvFKTgEAIvHhE2B6B1/ij3eJk0ZKpub3Du2 +gekZTpqajQL4viE1wjvydjJq/ixL2dr3q3+Ko/4xl0bLU8r6+/0CkkFnMxMP2+mp +4xVT9uM3AgMBAAECggEBAJpM7hAGDMCbxp361pzdVbJndtqGKm8b74WEvImO9mpu +YTzcHGJ9BEBCejlD/+tDAsGvAZJOY0g2tTvHyYNJvGS5HxXz4bSKrN7Aux+qxy93 +oxIxXcUwEHIrkaF+yzw0k9PMnLxBT5r4Rxniua9NPC8kvfnvji2GOYVksqylI/G7 +NwBLrYjFOJLXqikVZveyG0uQPXxwtRwaVgDSU7QX8kxXfpwsaZGdnm7yk80hrNte +U4U/A7kv63tE0+qFu9jQVatYwcGAR7kNr9qsFOHvxPbEQkvS6yOUHMdNF8ZpwFbA +vv6O1pt4jV7kAo5TLFoGXgnONBifzZCTD127laH1j1kCgYEA/Te6CsWbZm/6pPFJ +QpVyzDlSam7hCb5rmOneoOeIjo2CLn22DEeu9ZqETj2xLMfA0diV1r23Eia+kHwO +3XdNKLg3/Ue22FpQuV4iCqxDXkwdM/hMeyeLssQmNlGE0+iBaXL8UZCu5zWnw2TD +pkjeS6KGPCMN1MvYdhGIzYxt/zUCgYEA9h5FVLxBA0s40jgILpPvLbIF84cVDF46 +xnFs7c7jQSH+DYK9C7GFYKyDw3UTI5+UtwalxsBYWSLZuTSzdpUocKzT31TmbY2M +nOYb3s7VHrJUXWnCl5m6EW1oqRUVMZuseECg7IABWUaI8qUQAbU2JAD1VeoslpVo +s7C9TzuxCjsCgYEA395l3+Im6uDzkuIz5+cjEEVZhPm0gZ3VmOKjTlSFGasoPhws +WB0EJOXQNTA7tSCBa8V1a95cvXJ9plXX+prgH7EG5ymBETSAC/KaXB9CjFr0sp7C +V1t2Gb1rHzjhG0yDJYxgYWhuCcIZKRmsFBZ2Wh49WWuQbeMu4+vKrBeMpEUCgYEA +4+Wh01XCaZdk1Ru3T9ICHnEDG75QmjRC/nAXKplxS2V7hu0xujs2Qw9br0igYVMq +dNP9+20uIdOogduv9jUlzjfqtJk8CeORedOFqSoipBsDDcCZxKE/b1W9HRv9lQMO +kAdgO75IAW/T7cM0cDBpIquUgWqmwQU2f3U/xreCNvMCfxQQiFTTSyhnZu195hjJ +fgNZ3SODPMkQoh8Pah1E5Hb83umGBPvt4MN2bfm8wWmLEjpU93xiJa2uN8Jzx5Mb +K3hs4JcPtjJ2m2x0aDt1RGFhllEl0l5tOQg+cKy9jnqA/O1wmMOXsFgYS5Fhg1Me +o70kQ6YRH+pBgnP7Pgcu4X0= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAPHrk7xP1wNXMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAxMjEyMjM3MDdaFw0xNjAyMjAyMjM3MDdaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAPNxfeH8yap5iiO3epWGBxXw3AClsFivqIuabHfzhrLKRq7/SV9w3Y0ai+h/ +AxIbKoWibjZ3oaAUsQm5o+JD/9Fkro1CXbDVfbA/9SfhIRPkh3iwwFKFHbNL/kfs +aBKWCrBSop6Cd6Xfjxv8DZhx0B3iJD3vIHEg3FuRyI393wvaiYjh62t4P/KCGpfl +Y3ZQf42hKDsV2m+AEiFowqyoL28SpuBdogWveMMlWPITLjAF+8UpOAQAi8eETYHo +HX+KPd4mTRkqm5vcO7aB6RlOmpqNAvi+ITXCO/J2Mmr+LEvZ2verf4qj/jGXRstT +yvr7/QKSQWczEw/b6anjFVP24zcCAwEAAaNQME4wHQYDVR0OBBYEFHB5zTo/9cg+ +8kTzB3WDwATror8YMB8GA1UdIwQYMBaAFHB5zTo/9cg+8kTzB3WDwATror8YMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACH3jlYfLBR0kGALkJSq9tRI +fB6sjjVOFrZtCt5sOqxh9We3wvMsr2TPAuEVEhiPQ1w1U4x4DTQU6A65N1KOj9Le +anjjlyjNSTqXvNUbxp0wh8x4LJ8DtmsfdXXYp3LsColBmh7JQn/2TL687B9tBXiq +p3P6wkuIOHX45UOQ0kvD4wRf3t+8mL4TmrCH4YHqzbcjI1KXGDfmVQ7i64nYrl3z +v7UCA6Xh0MShv3c0AtX1ccsxObEwjJzWA30zYUQtIWxXpRtLlkiflFU8ak1HzmBU +AHsg1T1KI1012+a1j/LWkKqb91EpUjC/DlaHz8zDOH/S1IEWM6UFjwKYnHLPxoM= +-----END CERTIFICATE----- + diff --git a/fakenet/server.pem b/fakenet/server.pem new file mode 100644 index 0000000..aff78bd --- /dev/null +++ b/fakenet/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAPHrk7xP1wNXMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjAxMjEyMjM3MDdaFw0xNjAyMjAyMjM3MDdaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAPNxfeH8yap5iiO3epWGBxXw3AClsFivqIuabHfzhrLKRq7/SV9w3Y0ai+h/ +AxIbKoWibjZ3oaAUsQm5o+JD/9Fkro1CXbDVfbA/9SfhIRPkh3iwwFKFHbNL/kfs +aBKWCrBSop6Cd6Xfjxv8DZhx0B3iJD3vIHEg3FuRyI393wvaiYjh62t4P/KCGpfl +Y3ZQf42hKDsV2m+AEiFowqyoL28SpuBdogWveMMlWPITLjAF+8UpOAQAi8eETYHo +HX+KPd4mTRkqm5vcO7aB6RlOmpqNAvi+ITXCO/J2Mmr+LEvZ2verf4qj/jGXRstT +yvr7/QKSQWczEw/b6anjFVP24zcCAwEAAaNQME4wHQYDVR0OBBYEFHB5zTo/9cg+ +8kTzB3WDwATror8YMB8GA1UdIwQYMBaAFHB5zTo/9cg+8kTzB3WDwATror8YMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACH3jlYfLBR0kGALkJSq9tRI +fB6sjjVOFrZtCt5sOqxh9We3wvMsr2TPAuEVEhiPQ1w1U4x4DTQU6A65N1KOj9Le +anjjlyjNSTqXvNUbxp0wh8x4LJ8DtmsfdXXYp3LsColBmh7JQn/2TL687B9tBXiq +p3P6wkuIOHX45UOQ0kvD4wRf3t+8mL4TmrCH4YHqzbcjI1KXGDfmVQ7i64nYrl3z +v7UCA6Xh0MShv3c0AtX1ccsxObEwjJzWA30zYUQtIWxXpRtLlkiflFU8ak1HzmBU +AHsg1T1KI1012+a1j/LWkKqb91EpUjC/DlaHz8zDOH/S1IEWM6UFjwKYnHLPxoM= +-----END CERTIFICATE----- diff --git a/resources/fakenet.ico b/resources/fakenet.ico new file mode 100644 index 0000000..8431d83 Binary files /dev/null and b/resources/fakenet.ico differ diff --git a/resources/fakenet.png b/resources/fakenet.png new file mode 100644 index 0000000..5b9c52b Binary files /dev/null and b/resources/fakenet.png differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..07e2386 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +import os + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +requirements = [ + "pydivert", + "dpkt", + "dnslib", +] + +setup( + name='FakeNet NG', + version='1.0', + description="", + long_description="", + author="Peter Kacherginsky", + author_email='peter.kacherginsky@fireeye.com', + url='https://www.github.com/fireeye/flare-fakenet-ng', + packages=[ + 'fakenet', + ], + package_dir={'fakenet': 'fakenet'}, + package_data={'fakenet': ['*.pem','diverters/*.py', 'listeners/*.py', 'configs/*.ini', 'defaultFiles/*', 'lib/64/*', 'lib/32/*']}, + entry_points={ + "console_scripts": [ + "fakenet=fakenet.fakenet:main", + ] + }, + include_package_data=True, + install_requires=requirements, + zip_safe=False, + keywords='fakenet-ng', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + ], +) \ No newline at end of file