IMCAFS

Home

principle and implementation of port scanning

Posted by tzul at 2020-03-24
all

National Day holiday is also idle, let's review the port scanning technology. This paper first gives the specific principle of each kind of scanning, and then gives the specific code using scapy.

0x01 basic knowledge

TCP header structure

Because most port scanning technologies are based on TCP, let's review the TCP protocol briefly. The following are from Wikipedia:

ICMP message type

Why should we talk about ICMP message types here? This is mainly to understand the ICMP response returned after the firewall filters and scans data packets. In fact, there is nothing to talk about. Just finish with the following picture:

Generally, if the packet is filtered by the firewall, an ICMP packet will be received, which is of type 3 and code 1, 2, 3, 9, 10 or 13.

Scapy Foundation

This article focuses on how to construct packets and how to send packets.

Construct packet

The packet creation of scapy is based on our four layer reference model of TCP / IP, including link layer, network layer, transport layer and application layer. Scapy has written classes for each layer. What we need to do is instantiate these classes. Some operations on the packet are to call the class's methods or change the class's parameter values. Each layer has its own creation functions, such as ip(), tcp(), udp(), and so on. Different layers are connected by "/".

Message template

A large number of message templates are built in scapy, which can be viewed through LS under the Python interpreter

>>> ls() ARP : ARP ASN1_Packet : None BOOTP : BOOTP CookedLinux : cooked linux DHCP : DHCP options DHCP6 : DHCPv6 Generic Message) DHCP6OptAuth : DHCP6 Option - Authentication DHCP6OptBCMCSDomains : DHCP6 Option - BCMCS Domain Name List DHCP6OptBCMCSServers : DHCP6 Option - BCMCS Addresses List DHCP6OptClientFQDN : DHCP6 Option - Client FQDN DHCP6OptClientId : DHCP6 Client Identifier Option DHCP6OptDNSDomains : DHCP6 Option - Domain Search List option DHCP6OptDNSServers : DHCP6 Option - DNS Recursive Name Server DHCP6OptElapsedTime : DHCP6 Elapsed Time Option DHCP6OptGeoConf : DHCP6OptIAAddress : DHCP6 IA Address Option (IA_TA or IA_NA suboption) DHCP6OptIAPrefix : DHCP6 Option - IA_PD Prefix option DHCP6OptIA_NA : DHCP6 Identity Association for Non-temporary Addresses Option DHCP6OptIA_PD : DHCP6 Option - Identity Association for Prefix Delegation DHCP6OptIA_TA : DHCP6 Identity Association for Temporary Addresses Option DHCP6OptIfaceId : DHCP6 Interface-Id Option DHCP6OptInfoRefreshTime : DHCP6 Option - Information Refresh Time ...

The commonly used templates include: ether, IP, TCP, UDP and other message templates. Here we focus on the following TCP templates:

>>> ls(TCP) sport : ShortEnumField = (20) dport : ShortEnumField = (80) seq : IntField = (0) ack : IntField = (0) dataofs : BitField (4 bits) = (None) reserved : BitField (4 bits) = (0) flags : FlagsField (8 bits) = (2) window : ShortField = (8192) chksum : XShortField = (None) urgptr : ShortField = (0) options : TCPOptionsField = ({})

Now let's build a data package by ourselves:

>>> p=sr1(p1,timeout=10) Begin emission: Finished to send 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >>> p1=IP(src="192.168.199.100",dst="192.168.199.1")/TCP(sport=RandShort(),dport=80,flags='S') >>> p1.show() ###[ IP ]### version= 4 ihl= None tos= 0x0 len= None id= 1 flags= frag= 0 ttl= 64 proto= tcp chksum= None src= 192.168.199.100 dst= 192.168.199.1 \options\ ###[ TCP ]### sport= <RandShort> dport= www seq= 0 ack= 0 dataofs= None reserved= 0 flags= S window= 8192 chksum= None urgptr= 0 options= {}

For the TCP packet constructed above, we use the random function to randomly select the port when sending the packet. The destination port is 80. Flags = s means that we will identify syn position 1 in the bit, that is to say, we construct a syn packet.

flags=S SYN

Send and receive packets

The SR () function is used to send packets and receive replies. This function returns a pair of packets and their replies, as well as packets with or without replies. The sr1() function is a variant that returns an answer packet. Now let's try to send the data packet constructed above directly:

>>> p=sr1(p1,timeout=10) Begin emission: .Finished to send 1 packets. * Received 2 packets, got 1 answers, remaining 0 packets >>> p.show() ###[ IP ]### version= 4L ihl= 5L tos= 0x0 len= 44 id= 0 flags= DF frag= 0L ttl= 64 proto= tcp chksum= 0x2b15 src= 192.168.199.1 dst= 192.168.199.100 \options\ ###[ TCP ]### sport= www dport= 64694 seq= 3511850663 ack= 1 dataofs= 6L reserved= 0L flags= SA window= 14600 chksum= 0xea54 urgptr= 0 options= [('MSS', 1460)] ###[ Padding ]### load= '\x00\x00' >>> hexdump(p) 0000 45 00 00 2C 00 00 40 00 40 06 2B 15 C0 A8 C7 01 E..,[email protected]@.+..... 0010 C0 A8 C7 64 00 50 FC B6 D1 52 96 A7 00 00 00 01 ...d.P...R...... 0020 60 12 39 08 EA 54 00 00 02 04 05 B4 00 00 `.9..T........

Note that the middle identification bit of the reply packet we received above is SA, which means that we have received the syn + ACK packet, and the corresponding hex below is 0x12.

SA SYN+ACK 0x12

0x02 port scan

Port scan type

Common port scan types are:

Because there are too many illustrations, I don't want to steal them directly. The original drawings are all from infosec Institute.

TCP connection scan

The principle is very simple, that is to use the client and the server to establish a TCP connection, we must have three handshakes. If we can successfully complete one TCP three handshake, then the target port is open.

Normally, when the port is open, the server will return a syn + ack after receiving the syn as shown in the figure above. At this time, we only need to return an ACK + RST to complete the three handshakes and reset the connection. If the target port is closed, the server will return to RST to reset the connection as shown in the figure below.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_connect_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='S'),timeout=10) # 判断是否收到应答包 if type(tcp_connect_scan) == type(None): print "[-] Port is closed." # 判断收到的应答包是否具有TCP层 elif tcp_connect_scan.haslayer(TCP): # 判断是否为SYN+ACK数据包 if tcp_connect_scan.getlayer(TCP).flags == 0x12: send(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='AR')) print "[+] Port is open." # 判断是否为RST数据包 elif tcp_connect_scan.getlayer(TCP).flags == 0x14: print "[-] Port is closed."

TCP syn scan

The principle of TCP syn scanning is basically the same as the previous TCP connection scanning. The difference lies in that if the target port is open, only RST packets are returned instead of ACK + rst. Then you may have to ask why you do this, which is to avoid the detection of firewall.

Similarly, if the target returns an RST packet directly, it indicates that the port is down.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_syn_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='S'),timeout=10) # 判断是否收到应答包 if type(tcp_syn_scan) == type(None): print "[-] Port is closed." # 判断收到的应答包是否具有TCP层 elif tcp_syn_scan.haslayer(TCP): # 判断是否为SYN+ACK数据包 if tcp_syn_scan.getlayer(TCP).flags == 0x12: send(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='R')) print "[+] Port is open." # 判断是否为RST数据包 elif tcp_syn_scan.getlayer(TCP).flags == 0x14: print "[-] Port is closed." # 判断数据包是否具有ICMP层 elif tcp_syn_scan.haslayer(ICMP): # 判断是否被防火墙过滤 if tcp_syn_scan.getlayer(TCP).type == 3 and tcp_syn_scan.getlayer(TCP).code in [1,2,3,9,10,13]: print "[-] Filtered"

TCP Xmas tree scan

The principle of Christmas tree scanning is that the client sends packets with PSH, fin, urg identification and port number to the server. If the port is open, the server will not respond.

If the server returns an RST packet, it indicates that the port is closed.

Similarly, if the returned ICMP packet is received, it indicates that the packet is filtered.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_xmas_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='PFU'),timeout=10) # 判断是否收到应答包 if type(tcp_xmas_scan) == type(None): print "[+] Port is open|filtered." # 判断收到的应答包是否具有TCP层 elif tcp_xmas_scan.haslayer(TCP): # 判断是否为RST数据包 if tcp_xmas_scan.getlayer(TCP).flags == 0x14: print "[-] Port is closed." # 判断数据包是否具有ICMP层 elif tcp_xmas_scan.haslayer(ICMP): # 判断是否被防火墙过滤 if tcp_xmas_scan.getlayer(TCP).type == 3 and tcp_xmas_scan.getlayer(TCP).code in [1,2,3,9,10,13]: print "[-] Filtered."

TCP fin scan

TCP fin scanning is similar to Christmas tree scanning, except that packets with fin identification and port number are sent to the server. Similarly, if the server does not respond, the port is in an open state (whether it is filtered by the firewall or not).

If the server returns an RST packet, the port is closed.

Similarly, if the returned ICMP packet is received, it indicates that the packet is filtered.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_fin_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='F'),timeout=10) # 判断是否收到应答包 if type(tcp_fin_scan) == type(None): print "[+] Port is open|filtered." # 判断收到的应答包是否具有TCP层 elif tcp_fin_scan.haslayer(TCP): # 判断是否为RST数据包 if tcp_fin_scan.getlayer(TCP).flags == 0x14: print "[-] Port is closed." # 判断数据包是否具有ICMP层 elif tcp_xmas_scan.haslayer(ICMP): # 判断是否被防火墙过滤 if tcp_fin_scan.getlayer(TCP).type == 3 and tcp_fin_scan.getlayer(TCP).code in [1,2,3,9,10,13]: print "[-] Filtered."

TCP empty scan (null)

TCP null scan is also similar to the previous Christmas tree scan and fin scan. It sends a packet to the server. If the server does not respond, it indicates the port method, but the packet sent by empty scan does not set any identification bit.

If the server returns an RST packet, the port is closed.

Similarly, if the returned ICMP packet is received, it indicates that the packet is filtered.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_null_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags=''),timeout=10) # 判断是否收到应答包 if type(tcp_null_scan) == type(None): print "[+] Port is open|filtered." # 判断收到的应答包是否具有TCP层 elif tcp_null_scan.haslayer(TCP): # 判断是否为RST数据包 if tcp_null_scan.getlayer(TCP).flags == 0x14: print "[-] Port is closed." # 判断数据包是否具有ICMP层 elif tcp_null_scan.haslayer(ICMP): # 判断是否被防火墙过滤 if tcp_null_scan.getlayer(TCP).type == 3 and tcp_null_scan.getlayer(TCP).code in [1,2,3,9,10,13]: print "[-] Filtered."

TCP ack scan

Using TCP ack scan can not determine whether the port is closed or open, because when sending a TCP message with ack representation to the other party, it will return a message with RST flag, no matter whether the port is open or closed. Therefore, TCP ack scanning cannot be used to determine whether the port is open or closed. But you can use it to scan the firewall configuration, use it to discover firewall rules, determine whether they are stateful or stateless, and which ports are filtered.

Send a packet with ack identification to the server. If the response with RST identification is received, it means that the server has no filtering and there is no state firewall.

If the server returns an ICMP error packet, the target has a state firewall and our packet is filtered.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port=80 tcp_ack_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='A'),timeout=10) # 判断是否收到应答包 if type(tcp_ack_scan) == type(None): print "[-] Filtered." # 判断收到的应答包是否具有TCP层 elif tcp_ack_scan.haslayer(TCP): # 判断是否为RST数据包 if tcp_ack_scan.getlayer(TCP).flags == 0x14: print "[+] Unfiltered." # 判断数据包是否具有ICMP层 elif tcp_ack_scan.haslayer(ICMP): # 判断是否被防火墙过滤 if tcp_ack_scan.getlayer(TCP).type == 3 and tcp_ack_scan.getlayer(TCP).code in [1,2,3,9,10,13]: print "[-] Filtered."

TCP window scan

The process of TCP window scanning is similar to ack scanning, which is to send packets with ack identification to the server. The difference is that TCP window scanning will check the size of the window in the received RST packets. If the size of the window in the rst packets is not zero, the target port is open.

If the window size in the rst packet is zero, the target port is closed.

Code:

#!/usr/bin/env python2 # -*- coding:utf-8 -*- from scapy.all import * dst_ip = "192.168.199.1" src_port = RandShort() dst_port = 80 tcp_window_scan = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags='A'),timeout=10) # 判断是否收到应答包 if type(tcp_window_scan) == type(None): print "[-] Filtered." # 判断收到的应答包是否具有TCP层 elif tcp_window_scan.haslayer(TCP): # 判断窗口大小是否为0 if tcp_window_scan.getlayer(TCP).window == 0: print "[-] Port is closed." elif tcp_window_scan.getlayer(TCP).window > 0: print "[+] Port is open."