학사 나부랭이
Network - Internet Control Message Protocol 본문
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