Categories
Coding

Python Port Scanner inc UDP

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:

  1. Set up a listener so we can “hear” when packets come into our machine.
  2. Send either specially crafted or generic UDP packets depending on the service being tested.
  3. 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(("8.8.8.8", 80))
    HOST = HostIPSocket.getsockname()[0]
    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)[0]
         
        # 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) 
  1. So the first four lines is using a socket to capture our current IP Address rather than having it set as a static variable.
  2. 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.
  3. After this we then bind the new socket to our interface with the IP Address picked up in original four lines of code.
  4. 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.
  5. The new thread bit is actually part of step 2 so we can send a UDP crafted message.
  6. Then the while statement which starts receiving the packets into our interface and analysing them.
    1. This first runs a function to convert the first 20 bytes of the packet into a nicely formatted IP header.
    2. 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.
    3. 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.
    4. 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.
  7. 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.

Additional Thoughts

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.

Categories
Coding Hacking Linux

Stupidity at its best.

So I’ve been in the IT industry for over ten years working with a variety of organisations doing all sorts of cool things. Thoughout my career I have done a variety of stupid things and as I got older I was convinced that these stupid mistakes would become less or would become so obscure that they wouldn’t be considered stupid. This obviously isn’t the case based on the title of this blog post.

About a month ago, I got an email from EC-Council’s training platform: Codered, due to my Certified Ethical Hacker (CEH) certification expiring. Offering a bunch of courses all for $1. Now I know what you’re thinking here, this is a phishing scam but I went directly to the website signed in and it was true. I signed up for Wireshark for Hackers and Black Hat Python. The Wireshark course was for beginners which needless to say it very much was. I picked up a few things but what annoyed me the most was watching the trainer figure out what he was trying to teach on video, not cool. The Python course though was cool, the educator was brilliant. Super detailed but put it all in a way that was really easy to understand. This was also helped by the fact that I’ve done some Python work before so the syntax was familiar.

Now the backstory is done, onto the stupidity part. Within the Python course, there was a module specifically dedicated to Brute Force cracking Linux/Unix passwords from the /etc/shadow or /etc/passwd file. This used the module crypt. I build the script and had it all ready but with me being on a Windows machine it wouldn’t work with a sample shadow/passwd file available on the internet. So after a little bit of time, I built a Linux VM for something else, copied the file across and tried it out.

Fail. The code wouldn’t run and just threw the following error:

AttributeError: ‘module’ object has no attribute ‘crpyt’

So to everyone’s favourite troubleshooter, Google. I put the error into Google and nothing. This is very unusual, I almost always find someone with an error pretty much matching what I have. I’m not a programmer after all. I found a bunch of people with the same error but not against the same attribute. I spent hours, researching into this trying to find if there was a Pip module that I hadn’t installed. For those of you that are unsure Pip is a program to install Python modules onto a system. Everything suggested that I needed to install the cryptography module on Linux but when I tried that it said it was already installed. So I thought maybe it was the distribution of Linux that I was using, Kali. The trainer used Ubuntu so I moved over to one of my servers with Ubuntu on to try that. No dice.

Finally, I decided to give up on searching and went to the line of code in question which was throwing the error:

digest = crypt.crpyt(word, hashed_pass)

I use Visual Studio Code (VSCode) for my programming and the nice thing is in Python on VSCode each piece of code is usually colour coded, so green for a module, yellow for a function, light blue for variable and orange for some text, etc. This was when I realised the word crpyt after crypt. was white. Oh, balls, it’s a typo, changed it to crypt, the word went yellow, re-copied it to my Kali machine and hey presto everything is working.

For all of you out there that are either just starting out or have been in the field for a short while and end up making a mistake like this. Don’t worry, someone who’s got a job as a Senior Engineer makes these mistakes too and I don’t doubt that I won’t stop making these mistakes until I retire. Don’t get disheartened by it, it’s human nature and I hope this post helps you in realising that. For those of you that run into this issue yourself whilst messing about with Python, check your code and make sure you haven’t made a typo as that error can be quite misleading if you don’t read it letter by letter like me.