If for whatever reason you end up needing to do a port scan against a target but you cannot install NMAP then you may be able to use a Python script. If you do some googling you will find a variety of sources available that show you a script to perform a port scan using Python. From my searching, however, all of these are for TCP. If you search for UDP port scanning in Python you’ll likely become very disappointed. I know I have.
Fortunately, it is possible with some caveats, the first is the way in which UDP testing works, if you perform a scan against a target of a specific port e.g. DNS then the best way to get an absolute yes or no is to send a service-specific request to the target. If DNS is an open service then the target will respond with a DNS response. However, let’s say the target doesn’t support DNS then what will happen is you will receive an ICMP Unreachable. All of these are pretty common knowledge if you have some basic IT experience.
So, to Python then, to be able to ascertain the information outlined in the paragraph above we need to perform the following steps:
- Set up a listener so we can “hear” when packets come into our machine.
- Send either specially crafted or generic UDP packets depending on the service being tested.
- Check to see what response we have from the target to help ascertain whether the port is open, filtered or closed.
Setting up a Listener and checking the response
So setting up a listener is quite simple really. Here is how we’re going to do it:
StartTime = time.time() #get the current IP Address of default routed interface HostIPSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) HostIPSocket.connect(("184.108.40.206", 80)) HOST = HostIPSocket.getsockname() HostIPSocket.close() #check if the application is running on windows or not. if os.name == 'nt': socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP SocketListener = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) #bind the new listener to the IP address on the interface. SocketListener.bind((HOST, 0)) SocketListener.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) #if windows turn on a bit so on the driver so it converts the interface to promiscuous mode if os.name == 'nt': SocketListener.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) #listen for returned packet on new thread after sending the request. newthread = threading.Thread(target=udp_sender, args=(target_ip, target_port)) newthread.start() count = 0 while True: returnedPackets = SocketListener.recvfrom(65535) # create an IP header from the first 20 bytes ip_header = IP(returnedPackets[0:20]) if (str(ip_header.src_address) == str(target_ip) and ip_header.protocol == "ICMP"): offset = ip_header.ihl * 4 buf = returnedPackets[offset:offset + 8] icmp_header = ICMP(buf) # check for TYPE 3 and CODE if icmp_header.code == 3 and icmp_header.type == 3: # make sure it has our magic message buf = returnedPackets[48:56] udp_header = UDP(buf) if returnedPackets[len(returnedPackets) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'): ClosedPorts.append(udp_header.dstport) break #check if UDP response has been received. elif (str(ip_header.src_address) == str(target_ip) and ip_header.protocol =="UDP"): offset = ip_header.ihl * 4 buf = returnedPackets[offset:offset + 8] udp_header = UDP(buf) if udp_header.srcport == target_port: OpenPorts.append(target_port) break else: EndTime = time.time() if EndTime - StartTime >= 3: OpenFilteredPorts.append(target_port) break #turn back off promiscuous mode after the operation if os.name == 'nt': SocketListener.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
- So the first four lines is using a socket to capture our current IP Address rather than having it set as a static variable.
- Then we have an IF statement which checks whether we are using a Windows NT based system or not as that will determine what protocol our socket will connect on.
- After this we then bind the new socket to our interface with the IP Address picked up in original four lines of code.
- We then check again if were using Windows as if so we need to force promisicous mode. Because of this we will also need to run this as administrator when using Windows.
- The new thread bit is actually part of step 2 so we can send a UDP crafted message.
- Then the while statement which starts receiving the packets into our interface and analysing them.
- This first runs a function to convert the first 20 bytes of the packet into a nicely formatted IP header.
- Then we check if the packet received it an ICMP packet and is from the target if so, we perform some addtional functions to convert part of the packet to get the ICMP header portion. Then we check to see if it’s ICMP code equal 3 and whether the ICMP message is the same as the one we sent. If so then we can consider the port to be closed as we’ve received an ICMP unreachable.
- If we don’t receive an ICMP response then we check whether the target has sent us a UDP response. If they have then we perform a function to get the UDP headers so we can check if the source port is from the target port we sent the request to. If so, then we can assume that the port is open.
- Finally, if nothing matches then we wait for up to 50 packets to be recieved and if nothing matches our tests then we assume it’s refusing likely because it’s open but filtered.
- The last IF statement in this code snippet turns off promisicous mode if running on Windows.
Sending a UDP Message
Sending a UDP message in Python is quite possibly the easiest thing I’ve ever done:
def udp_sender(target_ip, target_port): #run UDP port check now. connection_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: if(str(target_port) in UDP_sig): connection_socket.sendto(UDP_sig[str(target_port)], (target_ip,target_port)) else: connection_socket.sendto(bytes(MESSAGE, 'utf8'), (target_ip,target_port)) finally: connection_socket.close()
This code snippet is super simple. First, we create a UDP socket using the SOCK_DGRAM function. Next, we check to see if we have any protocol-specific messages for the port we are targeting, if so we pull that byte array, if not we use a string variable called MESSAGE and convert it to a byte array. Then we close the connection. This function is called in the thread in the previous step.
As you can see this is a bit dirty, especially with the whole wait for 3-second thing at the end of the loop. However, it does work at scale, to make this work faster you could implement byte string variables for each specific service of a UDP request. That way when you send a request for say DNS you should get a response rather than nothing as the service you’re attempting to access doesn’t know how to respond.
Note: if you’re running this on Windows you will need to disable your firewall as it blocked ICMP Unreachable packets from being received by the OS. You can do this either in the Python script or just as and when you run it.