FPS/FPS.py (6306B)
1 import os # 운영 체제와 상호작용하여 파일 경로를 설정하는 데 사용
2 import time # 스캔 시작 및 종료 시간을 기록하고 경과 시간을 계산하는 데 사용
3 from scapy.all import IP, TCP, sr # 네트워크 패킷을 만들고 전송하는 데 필요한 Scapy의 모듈들
4 import argparse # 명령줄 인자를 쉽게 처리할 수 있도록 돕는 라이브러리
5
6 # Nmap 서비스 매핑 로드 함수: nmap-services 파일에서 포트 번호와 서비스 이름 매핑 정보를 읽어오는 함수
7 def load_nmap_services(filename):
8 service_map = {} # 서비스 정보를 저장할 빈 딕셔너리 생성
9 # 파일 열기
10 with open(filename, "r") as file:
11 # 파일의 각 줄을 읽습니다
12 for line in file:
13 # 각 줄이 주석('#')으로 시작하는 경우, 해당 줄은 무시하고 넘어갑니다.
14 if not line.startswith("#"):
15 parts = line.split() # 각 줄의 정보를 공백으로 분리
16 # 분리한 정보에서 포트와 서비스 이름을 가져옵니다
17 if len(parts) >= 2: # 최소한 2개의 요소가 있을 때만 처리
18 service = parts[0] # 서비스 이름
19 port, proto = parts[1].split("/") # 포트 번호와 프로토콜(tcp/udp)을 분리
20 # 딕셔너리에 포트 번호와 프로토콜을 키로, 서비스 이름을 값으로 저장
21 service_map[(int(port), proto)] = service
22 return service_map # 서비스 매핑 딕셔너리를 반환
23
24 # 서비스 매핑 파일 경로 설정 및 로드
25 # 현재 스크립트가 위치한 폴더에 있는 nmap-services 파일 경로를 설정합니다.
26 # 만약 해당 파일이 존재한다면, 로드하여 서비스 매핑 정보를 가져옵니다.
27 service_file_path = os.path.join(os.path.dirname(__file__), "nmap-services")
28 nmap_service_map = load_nmap_services(service_file_path) if os.path.exists(service_file_path) else {}
29
30 # 포트 범위 해석 함수: 입력받은 포트 문자열을 숫자 범위 또는 리스트로 변환
31 def parse_ports(port_str):
32 # 포트가 범위(예: "1-1000") 형식일 경우
33 if "-" in port_str:
34 # 시작 포트와 끝 포트를 분리하여 숫자로 변환
35 start, end = map(int, port_str.split("-"))
36 return range(start, end + 1) # 범위를 반환 (1부터 1000까지)
37 # 포트가 단일 숫자들로 나열된 경우 (예: "80,443")
38 return [int(p) for p in port_str.split(",")] # 각 포트를 리스트에 담아 반환
39
40 # 일괄 포트 스캔 함수: 여러 포트를 동시에 스캔하여 빠르게 상태를 확인
41 def batch_scan(ip, ports):
42 open_ports_info = [] # 열린 포트 정보를 저장할 리스트
43 closed, filtered = 0, 0 # 닫힌 포트와 필터링된 포트의 개수를 초기화
44
45 # 스캔할 패킷을 한 번에 생성하여 리스트에 저장합니다.
46 # IP와 TCP 패킷을 결합하여 지정된 각 포트에 대해 SYN 패킷을 생성합니다.
47 packets = [IP(dst=ip) / TCP(dport=port, flags="S") for port in ports]
48 # sr 함수로 일괄 전송을 실행하여 응답을 수집합니다.
49 # timeout은 0.5초로 설정하여 빠르게 응답을 처리합니다.
50 answered, unanswered = sr(packets, timeout=0.5, verbose=0)
51
52 # 응답이 있는 패킷을 분석하여 열린 포트와 닫힌 포트를 구분합니다.
53 for snd, rcv in answered:
54 port = snd.dport # 전송된 패킷의 목적지 포트 번호
55 # 응답이 SYN-ACK 패킷(SYN 요청에 대한 승낙)일 경우 해당 포트를 'open'으로 간주
56 if rcv.haslayer(TCP) and rcv.getlayer(TCP).flags == 0x12:
57 service = nmap_service_map.get((port, "tcp"), "unknown") # 서비스 이름 가져오기
58 ttl = rcv.ttl # 응답 패킷의 TTL(Time to Live) 값을 가져옵니다.
59 # 열린 포트 정보를 딕셔너리로 저장하여 리스트에 추가
60 open_ports_info.append({"port": port, "state": "open", "service": service, "ttl": ttl})
61 else:
62 closed += 1 # SYN-ACK가 아닌 다른 응답은 닫힌 포트로 간주하여 개수 증가
63
64 # 응답이 없는 패킷(필터링된 포트)은 unanswered에 저장되어 있으므로, 필터링된 포트 수로 처리
65 filtered = len(unanswered)
66
67 return open_ports_info, closed, filtered # 열린 포트 정보와 닫힌/필터링된 포트 수 반환
68
69 # 메인 함수: 프로그램의 진입점
70 if __name__ == "__main__":
71 # 명령줄 인자 파서 설정
72 parser = argparse.ArgumentParser(description="Optimized Batch Python Port Scanner")
73 parser.add_argument("ip", help="IP address to scan") # 스캔할 IP 주소
74 parser.add_argument("-p", "--ports", type=str, default="1-1000", help="List of ports or range (default: 1-1000)")
75 args = parser.parse_args() # 인자 파싱
76
77 # 포트 목록을 파싱하여 숫자형 리스트로 변환
78 ports = parse_ports(args.ports)
79
80 # 스캔 시작 시간 기록
81 start_time = time.time()
82 print(f"Starting scan on {args.ip} at {time.strftime('%Y-%m-%d %H:%M:%S')}")
83
84 # 일괄 포트 스캔을 수행하여 열린 포트, 닫힌 포트, 필터링된 포트를 확인
85 open_ports_info, closed_ports, filtered_ports = batch_scan(args.ip, ports)
86
87 # 스캔 종료 시간 기록 및 경과 시간 계산
88 elapsed_time = round(time.time() - start_time, 2)
89
90 # 스캔 결과 출력
91 print(f"\nScan report for {args.ip}")
92 print(f"Host is up. Scanned in {elapsed_time} seconds.")
93 print(f"{closed_ports} closed ports and {filtered_ports} filtered ports not shown.")
94
95 # 열린 포트를 테이블 형식으로 출력
96 if open_ports_info:
97 # 헤더 출력
98 print("\nPORT STATE SERVICE TTL")
99 print("-" * 45)
100 # 열린 포트 정보를 반복하면서 각 열을 정렬하여 출력
101 for info in open_ports_info:
102 # 각 열린 포트의 정보를 일정한 열 너비로 맞춰 출력
103 print(f"{str(info['port']) + '/tcp':<10}{info['state']:<9}{info['service']:<16}{info['ttl']}")
104 else:
105 print("No open ports found.") # 열린 포트가 없을 경우