학사 나부랭이

Network - Internet Control Message Protocol 본문

自習/Network

Network - Internet Control Message Protocol

태양왕 해킹 (14세) 2021. 7. 1. 04:36

ICMP

 3계층에서 동작하며, 네트워크 통신의 테스트나 오류 메시지 응답을 전송할 때 사용돼요. 통신 상태를 확인할 때 사용하는 Ping, Tracert 명령어도 ICMP를 사용하죠.

 

ICMP 헤더 구조

  • Type: ICMP 패킷의 종류를 나타내요.
  • Code: Type에 대한 상세 항목이에요.
  • Checksum: 오류를 검출하는 필드예요.
  • Other message specific information: ICMP 헤더의 추가 메시지 필드예요. ICMP 헤더는 Type과 Code에 따라 내용이 바뀌죠.

다음은 ICMP 프로토콜의 Type과 Code의 주요 필드에 대한 설명이에요.

Type Code 설명
0 0 Echo Reply, Echo Request (Type 8)에 대한 응답
3   Destination Unreachable, 목적지에 도달 불가
0 Network Unreachable, 네트워크에 접근 불가
1 Host Unreachable, 호스트 목적지에 도달 불가
2 Protocol Unreachable, 프로토콜 도달 불가
3 Port Unreachable, 포트에 도달 불가 (닫힌 포트에 UDP를 요청)
4 Fragmentation Required, and DF set, 패킷의 분할이 필요하지만, Do not Fragment 비트가 세팅되어 있음
5   Redirect, 라우터 경로 재설정
8   Echo Request, 응답을 바라는 요청
11   Time Exceeded, 시간 초과
0 TTL Exceeded, TTL 필드가 0이 되어서 메시지를 반환
1 Fragment Reassembly Time Exceeded, 시간 초과

ICMP Sniffer

 네트워크 패킷을 킁킁거리며 훔쳐보는 프로그램을 스니퍼라고 해요.

# icmp sniffer
from socket import *
import os
import struct

# 패킷을 byte 형태로 받으면 헤더와 나머지를 리턴
def parseIPHeader(ip_header):
    # struct 모듈로 byte를 편하게 다룸, unpack의 첫 번째 인자의 알파벳에 따라 앞에서부터 byte를 끊어 튜플 형태로 반환, 두 번째 인자는 언팩 할 byte를 받음
    # B: 정수-1byte, H: 정수-2bytes, s: bytes-1byte, L: 정수-4bytes, Q: 정수-8bytes
    # byte 배열에서 'BBHHHBBH4s4s'는 '2B3H2BH4s4s'로 표현
    ip_headers = struct.unpack("!2B3H2BH4s4s", ip_header[:20])
    ip_payloads = ip_header[20:]
    return ip_headers, ip_payloads


# ICMP 헤더와 메시지 내용을 튜플로 반환
def parseICMPHeader(icmp_data):
    icmp_headers = struct.unpack("!2B3H", icmp_data[:8])
    icmp_payloads = icmp_data[8:]
    return icmp_headers, icmp_payloads


def parsing(host):
    # NT계열 운영체제면(Windows)
    if os.name == "nt":
        sock_protocol = IPPROTO_IP
    # 리눅스나 유닉스 계열이면
    else:
        sock_protocol = IPPROTO_ICMP
    # AF_INET: IPv4 주소(AF_INET6: IPv6), Raw 소켓을 사용하겠다, 프로토콜 지정|생략(IP방식 = 0, ICMP방식 = 1)
    sock = socket(AF_INET, SOCK_RAW, sock_protocol)
    # 호스트의 IP주소와 포트를 연결(인자: ('호스트', '포트 번호')<=튜플 형태|자동 지정)
    sock.bind((host, 0))

    # 소켓에 옵션 추가(대상 소켓, 옵션(여기서는 IP헤더를 포함하는 옵션), 옵션의 설정 값(여기서는 True, if 0: 커널이 자동으로 IP헤더 작성))
    sock.setsockopt(IPPROTO_IP, IP_HDRINCL, 1)

    # Promiscuous mode, 윈도우에서 필요, 목적지 Network Interface Card의 MAC 주소가 자신이 아니더라도 패킷을 수신
    if os.name == "nt":
        sock.ioctl(SIO_RCVALL, RCVALL_ON)

    try:
        while True:
            data = sock.recvfrom(65535)
            # IP 헤더 20bytes의 ip_headers와 나머지 데이터인 ip_payloads를 튜플 형태로 반환 받음
            ip_headers, ip_payloads = parseIPHeader(data[0])
            if ip_headers[6] == 1:  # IP 헤더에서 프로토콜, 1==ICMP
                ip_source_address = inet_ntoa(ip_headers[8])
                ip_destination_address = inet_ntoa(ip_headers[9])
                print(f"from.{ip_source_address} -> to.{ip_destination_address}")
                icmp_headers, icmp_payloads = parseICMPHeader(ip_payloads)
                if icmp_headers[0] == 0:  # ICMP 헤더의 type, ping 요청
                    print("Echo reply")
                elif icmp_headers[0] == 8:  # ping 응답
                    print("Echo request")
                print(f"icmp_headers: {icmp_headers}")
                print(f"icmp_payloads: {icmp_payloads}")
                print("=" * 50)
    except KeyboardInterrupt:  # Ctrl+C
        # Off Promiscuous mode
        if os.name == "nt":
            sock.ioctl(SIO_RCVALL, RCVALL_OFF)
        sock.close()


if __name__ == "__main__":
    host = "192.168.0.9"
    print(f"START SNIFFING at {host}")
    parsing(host)

'自習 > Network' 카테고리의 다른 글

Network - TCP/UDP  (0) 2021.07.13
Network - Ping sweep scaner  (0) 2021.07.12
Network - Internet Protocol  (0) 2021.07.01
Network - IDS, IPS, Snort, UTM  (0) 2021.05.23
Network - Firewall  (0) 2021.05.23
Comments