📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 117편
이전 글: 116. IPv6 Packet 분석 · 다음 글: 118. Ping Packet 분석
참고(01. TCP/IP 네트워크 구조 이해): 34. ICMP와 ping — 오류 보고 프로토콜 읽는 법 — ICMP의 역할과 ping·traceroute의 원리는 이 글에서 다뤘습니다.
ICMP는 데이터를 나르는 프로토콜이 아니라 네트워크 상태를 알려 주는 프로토콜입니다. 그래서 ICMP 패킷은 관제 분석에서 "다른 통신에 무슨 일이 있었는가"를 알려 주는 간접 증거가 됩니다.
특히 ICMP 오류 메시지 안에는 오류를 일으킨 원본 패킷의 IP 헤더와 그 뒤 일부 바이트가 들어 있습니다. 이 구조를 모르면 필터 결과를 잘못 해석하기 쉽습니다. 이번 글은 ICMP 필드를 읽는 방법과 이 "패킷 속 패킷"을 다루는 방법에 집중합니다.

그림 1. ICMP 오류 패킷은 '어떤 패킷이 왜 실패했는지'를 원본 헤더와 함께 알려 줍니다
| 항목 | Wireshark 필드 | 의미 |
|---|---|---|
| Type | icmp.type | 메시지 종류 |
| Code | icmp.code | Type 안의 세부 사유 |
| Checksum | icmp.checksum | ICMP 메시지 전체의 오류 검출값 |
| Identifier | icmp.ident (icmp.ident_le) | Echo 요청·응답을 묶는 값. 보통 ping 프로세스별로 다름 |
| Sequence | icmp.seq (icmp.seq_le) | Echo 요청 순번 |
| 응답 프레임 번호 | icmp.resp_in | 이 요청에 대한 응답이 몇 번 프레임인지 (Wireshark 계산) |
| 요청 프레임 번호 | icmp.resp_to | 이 응답이 몇 번 프레임의 요청에 대한 것인지 |
| 응답 시간 | icmp.resptime | 요청~응답 사이 시간(ms) |
| 응답 없음 | icmp.no_resp | 캡처 안에서 응답을 찾지 못한 요청 |
| 데이터 길이 | data.len | Echo 데이터 부분의 바이트 수 |
| 다음 홉 MTU | icmp.mtu | Type 3 / Code 4에서 알려 주는 MTU |
Info 열의 id=0x0005, seq=1/256에서 1/256은 같은 2바이트를 빅엔디언(1)과 리틀엔디언(256)으로 각각 읽은 값입니다. OS마다 이 값을 기록하는 방식이 달라 Wireshark가 두 해석을 모두 보여 주는 것이며, 필드로는 icmp.seq와 icmp.seq_le에 해당합니다.
| Type / Code | 이름 | 관제에서의 의미 |
|---|---|---|
| 8 / 0 | Echo Request | ping 요청 |
| 0 / 0 | Echo Reply | ping 응답 |
| 3 / 0 | Network Unreachable | 목적지 네트워크로 가는 경로 없음 |
| 3 / 1 | Host Unreachable | 목적지 호스트에 도달 불가 (ARP 응답 없음 등) |
| 3 / 3 | Port Unreachable | 목적지 호스트에 도달했지만 해당 UDP 포트가 닫혀 있음 |
| 3 / 4 | Fragmentation Needed and DF set | DF 때문에 조각낼 수 없음 → icmp.mtu 확인 |
| 3 / 13 | Communication Administratively Prohibited | 필터링 장비가 차단했음을 알림 |
| 5 / x | Redirect | 더 나은 게이트웨이를 알려 줌 (일반 LAN에서는 드묾) |
| 11 / 0 | Time-to-live exceeded in transit | 전달 중 TTL 만료 (traceroute에서 활용) |
| 11 / 1 | Fragment reassembly time exceeded | 조각 재조립 시간 초과 |
Type 3, 5, 11 같은 오류 메시지는 오류를 일으킨 원본 패킷의 IP 헤더와 그 뒤의 최소 8바이트(UDP라면 헤더 전체, TCP라면 포트·시퀀스 번호)를 담고 돌아옵니다. Wireshark는 이 부분을 다시 IP·UDP·TCP로 해석해 상세 트리 안에 두 번째 IP 헤더로 보여 줍니다.
그 결과 한 프레임 안에 ip.src, ip.dst, ip.ttl 같은 필드가 두 번 존재하게 됩니다.
| 위치 | ip.src / ip.dst | 의미 |
|---|---|---|
| 바깥 IP 헤더 (1번째) | 오류를 보낸 장비 → 원래 송신자 | "누가 오류를 알려 왔는가" |
| 안쪽 IP 헤더 (2번째) | 원래 송신자 → 원래 목적지 | "어떤 패킷이 문제였는가" |
UDP로 닫힌 포트에 보냈을 때 Port Unreachable이 만들어지는 과정을 필드로 따라가 보면 다음과 같습니다.
[원본 패킷]
192.168.10.20:51515 ──UDP──→ 192.168.10.30:33434 (닫힌 포트)
ip.id=0x4444, ip.ttl=64
↓
192.168.10.30 커널: 해당 UDP 포트에서 기다리는 프로세스 없음
↓
[ICMP 오류 패킷]
바깥 IP : 192.168.10.30 → 192.168.10.20 (ip.src#1, ip.dst#1)
ICMP : icmp.type=3, icmp.code=3 (Port unreachable)
안쪽 IP : 192.168.10.20 → 192.168.10.30 (ip.src#2, ip.dst#2), ip.id=0x4444
안쪽 UDP: 51515 → 33434 (udp.srcport, udp.dstport)
↓
분석자: 안쪽 헤더를 보고 "어떤 요청이 거부되었는지" 원본 패킷과 매칭
이 구조 때문에 tshark -T fields -e ip.src로 출력하면 ICMP 오류 프레임에서는 192.168.10.30,192.168.10.20처럼 두 값이 쉼표로 함께 나옵니다. 또한 ip.src == 192.168.10.20이라는 필터는 바깥 헤더가 아니라 안쪽 헤더에서 일치해도 해당 프레임을 보여 줍니다. 원하는 계층을 명확히 지정하는 방법은 다음과 같습니다.
| 목적 | 방법 |
|---|---|
| 바깥 헤더 값만 출력 | -T fields -E occurrence=f -e ip.src |
| 안쪽(마지막) 헤더 값만 출력 | -T fields -E occurrence=l -e ip.dst |
| 바깥 헤더 기준 필터 | ip.src#1 == 192.168.10.30 (레이어 연산자, Wireshark 4.2 기준 지원) |
| 안쪽 헤더 기준 필터 | ip.dst#2 == 192.168.10.30 |
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 이름은 ens33을 예시로 사용합니다. tshark 설치는 112편과 같습니다(Rocky: sudo dnf install -y wireshark-cli, Ubuntu: sudo apt install -y tshark).
# traceroute (기본 동작: UDP 사용)
sudo dnf install -y traceroute # Rocky Linux
sudo apt install -y traceroute # Ubuntu
# 터미널 1: ICMP와 traceroute의 UDP를 함께 캡처
sudo tshark -i ens33 -f "icmp or udp portrange 33434-33534" -w /tmp/pcap10-icmp.pcapng
# 터미널 2
ping -c 4 <게이트웨이 IP> # Echo Request / Reply
traceroute -n -m 5 example.com # TTL 초과(11/0) 메시지 발생
traceroute는 목적지 포트 33434부터 올라가는 UDP 패킷을 TTL 1부터 보내며, 중간 라우터가 Time-to-live exceeded를, 목적지 호스트가 Port unreachable을 돌려주는 방식으로 동작합니다. 방화벽이 ICMP나 UDP를 막는 환경에서는 * * *만 보이고 ICMP가 캡처되지 않을 수 있습니다. 이것도 "그 구간에서 ICMP가 돌아오지 않았다"는 관찰 결과입니다.
📷 [실습 화면 삽입] traceroute 실행 결과와, Wireshark에서 Time-to-live exceeded 패킷을 선택한 화면
F=/tmp/pcap10-icmp.pcapng
# (1) Echo 요청·응답 매칭 (-2: 두 번 읽어 응답 프레임 번호를 채움)
tshark -2 -r $F -Y "icmp.type == 8 || icmp.type == 0" -T fields -E header=y \
-e frame.number -e ip.src -e ip.dst -e icmp.type -e icmp.ident -e icmp.seq \
-e icmp.resp_in -e icmp.resp_to -e icmp.resptime -e data.len
# (2) 응답 없는 요청
tshark -2 -r $F -Y "icmp.no_resp"
# (3) 오류 메시지: 누가 보냈고, 어떤 패킷이 원인인가
tshark -r $F -Y "icmp.type == 3 || icmp.type == 11" -T fields -E header=y \
-e frame.number -e ip.src -e ip.dst -e icmp.type -e icmp.code \
-e udp.srcport -e udp.dstport
# (4) 바깥 헤더(보낸 장비)만 정리
tshark -r $F -Y "icmp.type == 11" -T fields -E occurrence=f -e ip.src | sort | uniq -c
# (5) Type/Code 분포
tshark -r $F -Y icmp -T fields -e icmp.type -e icmp.code | sort | uniq -c | sort -rn
icmp.resp_in처럼 뒤에 나오는 패킷을 참조하는 필드는 tshark가 파일을 한 번만 읽으면 비어 있을 수 있어, -2(2-pass 분석) 옵션을 붙였습니다. Wireshark GUI에서는 파일을 연 뒤 자동으로 채워집니다.
📷 [실습 화면 삽입] Destination unreachable 또는 Time-to-live exceeded 패킷의 상세 트리에서 안쪽 Internet Protocol·User Datagram Protocol을 펼친 화면
아래는 형식 예시(값은 환경마다 다름) 입니다.
frame ip.src ip.dst type ident seq resp_in resp_to resptime data.len
1 192.168.10.20 192.168.10.1 8 5 1 2 56
2 192.168.10.1 192.168.10.20 0 5 1 1 0.512 56
frame ip.src ip.dst type code udp.srcport udp.dstport
9 192.168.10.1,192.168.10.20 192.168.10.20,203.0.113.5 11 0 40000 33435
12 203.0.113.5,192.168.10.20 192.168.10.20,203.0.113.5 3 3 40011 33446
192.168.10.1(첫 번째 홉)이 "내가 보낸 203.0.113.5 행 UDP(목적지 포트 33435)의 TTL이 만료되었다"고 알려 온 것203.0.113.5가 "해당 UDP 포트는 닫혀 있다"고 알려 온 것 → traceroute가 목적지에 도달했다는 뜻확인 체크리스트
icmp.ident·icmp.seq로 짝지어지는 것을 확인했다-2 옵션 유무에 따라 icmp.resp_in이 채워지는 차이를 확인했다ip.src가 두 값으로 출력되는 이유를 설명할 수 있다| 관찰 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
| 응답 없는 Echo Request 다수 | 목적지가 ICMP 차단, 호스트 꺼짐 | 한 출발지가 대역 내 여러 IP로 순차 요청 (호스트 탐색, 05. 네트워크 스캔 · 공격 징후 분석 시리즈에서 다룸) |
큰 data.len, 일정하지 않은 데이터 | MTU 점검용 ping, 일부 모니터링 도구 | 일반적인 ping 크기와 다른 요청이 장시간 반복, 데이터 내용이 매번 다름 (ICMP 터널링 가능성) |
| Port Unreachable (3/3) 다수 수신 | DNS 서버 장애, traceroute | 내부 호스트가 여러 UDP 포트에 대해 3/3을 연속으로 돌려줌 (UDP 포트 스캔 흔적) |
| Admin Prohibited (3/13) | 방화벽 정책에 따른 차단 | 허용되어야 할 업무 통신이 차단됨 (정책 확인 필요) |
| Redirect (5) | 일부 라우터 구성 | 일반 호스트가 Redirect를 보냄 (경로 조작 시도 가능성) |
| TTL exceeded (11/0) | traceroute, 일시적 라우팅 루프 | 대량 발생 → 라우팅 루프 또는 경로 탐색 활동 |
확인 명령 예시입니다.
# 출발지별 Echo Request 목적지 수 (여러 IP로 퍼지는지)
tshark -r $F -Y "icmp.type == 8" -T fields -e ip.src -e ip.dst | sort -u \
| awk '{c[$1]++} END {for (s in c) print c[s], s}' | sort -rn
# Echo 데이터 길이 분포
tshark -r $F -Y "icmp.type == 8" -T fields -e data.len | sort | uniq -c | sort -rn
# Port Unreachable을 보낸 호스트와 원인이 된 목적지 포트
tshark -r $F -Y "icmp.type == 3 && icmp.code == 3" -T fields \
-E occurrence=f -e ip.src -e udp.dstport | sort | uniq -c | sort -rn
# Redirect 존재 여부
tshark -r $F -Y "icmp.type == 5" -T fields -e ip.src -e icmp.redir_gw
일반적으로 Linux의 ping은 기본 56바이트 데이터(앞부분에 타임스탬프 포함, Wireshark에서 icmp.data_time으로 해석)를, Windows의 ping은 32바이트 데이터를 보냅니다. 이 기준과 다른 크기나 내용이 보인다고 곧바로 악성은 아니지만, 어떤 도구가 보낸 것인지 설명이 필요한 패킷이 됩니다.
흔적이 남는 곳
한계와 오탐 주의
ip.src, ip.dst, udp.dstport 필터는 오류 메시지의 안쪽 헤더에도 걸립니다. "이 IP가 보낸 패킷"을 찾으려다 "이 IP가 원인이 된 오류 메시지"까지 섞여 나올 수 있으므로, 계층 지정(#1, occurrence=f)을 습관화합니다.ip.id, 포트)로 대조하는 것이 좋습니다.icmp.type·icmp.code 조합이며, 특히 3/3(포트 닫힘), 3/4(MTU), 3/13(관리적 차단), 11/0(TTL 만료)을 구분할 수 있어야 합니다.icmp.ident·icmp.seq로 요청·응답이 짝지어지고, Wireshark의 icmp.resp_in·icmp.resptime·icmp.no_resp로 응답 여부를 빠르게 확인합니다(tshark는 -2 필요).ip.src 등이 두 번 나타납니다.-E occurrence=f/l과 레이어 연산자(ip.src#1)로 명확히 합니다.