📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 107편
이전 글: 106. Packet Bytes 구조 · 다음 글: 108. Display Filter
pcap 파일에는 보통 수만~수백만 개의 패킷이 있습니다. 필터 없이 스크롤하는 것은 분석이 아닙니다. 그렇다고 필터 목록을 외우는 것도 답이 아닙니다. 필터는 분석 질문을 기계가 이해하는 문장으로 바꾼 것이기 때문입니다.
질문: "192.168.10.20이 외부로 TCP 연결을 시도했는가?"
→ 필터:ip.src == 192.168.10.20 && tcp.flags.syn == 1 && tcp.flags.ack == 0
또 하나 중요한 점은 필터가 두 종류라는 것입니다. 캡처 필터를 잘못 쓰면 증거가 처음부터 수집되지 않고, 디스플레이 필터를 잘못 쓰면 있는 증거를 못 보고 지나칩니다. 두 필터의 차이를 이해하는 것이 이 글의 목표입니다.
| 구분 | Capture Filter | Display Filter |
|---|---|---|
| 적용 시점 | 캡처 중, 커널(BPF) 단계 | 캡처 후, 해석(dissect)된 결과에 |
| 문법 | BPF (pcap-filter) | Wireshark 필드 문법 |
| 걸러진 패킷 | 버려짐 (복구 불가) | 숨겨질 뿐 파일에 그대로 있음 |
| 사용 도구 | tcpdump, dumpcap -f, tshark -f, Wireshark 캡처 옵션 | Wireshark 필터 바, tshark -Y |
| 표현력 | 주소·포트·프로토콜·오프셋 수준 | 모든 해석 필드(HTTP 메서드, DNS 이름, TCP 분석 플래그 등) |
| 목적 | 수집량 줄이기 | 분석 질문에 답하기 |
| 의도 | Capture Filter (BPF) | Display Filter |
|---|---|---|
| 특정 호스트 | host 192.168.10.20 | ip.addr == 192.168.10.20 |
| 출발지만 | src host 192.168.10.20 | ip.src == 192.168.10.20 |
| 대역 | net 192.168.10.0/24 | ip.addr == 192.168.10.0/24 |
| TCP 443 | tcp port 443 | tcp.port == 443 |
| 제외 | not port 22 | !(tcp.port == 22) |
| SYN만 | tcp[tcpflags] == tcp-syn | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
BPF는 host, port, net 같은 키워드 문장, 디스플레이 필터는 프로토콜.필드 == 값 형식의 필드 비교식입니다. Wireshark 필터 바에 BPF 문법을 입력하면 빨간색으로 표시되어 오류를 알려 줍니다.

그림 1. Capture Filter는 저장 단계, Display Filter는 표시 단계에서 동작합니다
[NIC] → [커널 BPF: Capture Filter] ──불일치──→ 폐기 (영구 소실)
│ 일치
↓
[pcap 파일 / 메모리]
↓
[Wireshark 해석기(dissector)]
frame → eth → ip → tcp → http ... 필드 트리 생성
↓
[Display Filter] ──불일치──→ 목록에서 숨김 (파일엔 존재)
│ 일치
↓
[Packet List에 표시]
디스플레이 필터가 HTTP 메서드나 DNS 질의 이름까지 조건으로 쓸 수 있는 이유는 해석이 끝난 필드 트리를 대상으로 하기 때문입니다. 반대로 BPF는 해석 전 바이트를 빠르게 비교하므로 표현력은 낮지만 커널에서 대량 트래픽을 처리할 수 있습니다.
그래서 일반적인 원칙은 다음과 같습니다.
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스는 ens33을 예시로 사용합니다. 설치는 2편을 참고하세요.
질문: "실습 중 DNS와 웹 트래픽만 필요하고, 내 SSH 세션은 필요 없다."
sudo tcpdump -i ens33 -nn -w /tmp/web_dns.pcap \
'not port 22 and (udp port 53 or tcp port 80 or tcp port 443)'
다른 터미널에서 트래픽을 발생시킵니다.
curl -s -o /dev/null http://example.com
dig example.com
BPF 식은 셸이 괄호를 해석하지 않도록 작은따옴표로 감쌉니다. 문법이 맞는지는 -d 옵션으로 컴파일 결과를 보면 확인할 수 있습니다.
sudo tcpdump -i ens33 -d 'tcp port 443'
아래 필터는 Wireshark 필터 바에 그대로 넣거나, tshark -r 파일 -Y '필터'로 사용할 수 있습니다.
| 분석 질문 | Display Filter |
|---|---|
| 누가 어떤 도메인을 질의했나? | dns.flags.response == 0 (열에 dns.qry.name 추가) |
| 존재하지 않는 도메인 응답이 있었나? | dns.flags.rcode == 3 |
| 웹 요청은 어떤 메서드·URI였나? | http.request |
| 연결 시도(SYN)가 거부(RST)되었나? | tcp.flags.reset == 1 |
| 재전송이 많아 통신 품질이 나빴나? | tcp.analysis.retransmission |
| 특정 세션 하나만 보고 싶다 | tcp.stream eq 3 |
| 여러 포트 중 하나 | tcp.port in {80 443 8080} |
tshark -r /tmp/web_dns.pcap -Y 'dns.flags.response == 0' -T fields -e ip.src -e dns.qry.name
tshark -r /tmp/web_dns.pcap -Y 'http.request' -T fields -e ip.src -e http.request.method -e http.host -e http.request.uri
📷 [실습 화면 삽입] Wireshark 필터 바에 BPF 문법(
host 192.168.10.20)을 넣어 빨간색이 되는 화면과 올바른 디스플레이 필터(초록색) 비교
| 실수 | 결과 | 올바른 방법 |
|---|---|---|
필터 바에 port 80 입력 | 문법 오류(BPF 문법) | tcp.port == 80 |
ip.addr != 192.168.10.20 | Wireshark 3.6 미만에서는 "출발지나 목적지 중 하나라도 다르면" 참이 되어 거의 모든 패킷이 남음 | !(ip.addr == 192.168.10.20) — 버전과 무관하게 의도가 명확 |
http 필터로 웹 트래픽 전체를 봤다고 판단 | HTTP 해석기 기본 포트 목록에 없는 비표준 포트(예: 8081), HTTPS는 http로 해석되지 않음 | tcp.port로 먼저 범위 확인, 비표준 포트는 Decode As 검토 |
캡처 필터로 tcp port 80만 수집 | 같은 시각의 DNS 질의, 비표준 포트 통신 증거 소실 | 캡처는 넓게, 분석은 디스플레이 필터로 |
tcp.flags.syn == 1만으로 스캔 판단 | SYN/ACK 응답까지 포함 | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
and·or 혼용 시 괄호 누락 | 의도와 다른 결합 순서 | 조건 묶음마다 괄호 사용 |
📷 [실습 화면 삽입]
ip.addr != x와!(ip.addr == x)의 표시 패킷 수 비교(상태 표시줄 Displayed 값)
형식 예시(값은 환경마다 다름):
$ tshark -r /tmp/web_dns.pcap -Y 'dns.flags.response == 0' -T fields -e ip.src -e dns.qry.name
192.168.10.20 example.com
192.168.10.20 example.com
$ tshark -r /tmp/web_dns.pcap -Y 'http.request' -T fields -e ip.src -e http.request.method -e http.host -e http.request.uri
192.168.10.20 GET example.com /
(curl이 A·AAAA 레코드를 각각 질의하는 등, 환경에 따라 같은 이름이 여러 번 보일 수 있습니다. 로컬 DNS 캐시가 있으면 반대로 질의가 보이지 않을 수도 있습니다.)
ip.addr != x 대신 !(ip.addr == x)를 사용했다필터는 "정상/의심"을 판단하는 도구가 아니라 판단할 후보를 좁히는 도구입니다. 좁힌 결과를 어떻게 해석하는지가 중요합니다.
| 필터 결과 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
dns.flags.rcode == 3 다수 | 오타, 만료 도메인, 검색 도메인 접미사 자동 추가 | 한 호스트가 무작위 문자열 도메인을 짧은 간격으로 반복 질의 (08. DNS 악용 유형) |
| SYN만 있고 응답 없음 | 방화벽 차단, 대상 서버 다운 | 한 출발지가 여러 포트·여러 IP로 SYN 반복 |
tcp.flags.reset == 1 다수 | 닫힌 포트 접근, 애플리케이션의 정상 종료 방식 | 내부 서버가 다수 외부 IP로부터 받은 SYN에 RST 응답 |
http.request 중 POST 다수 | 로그인, 폼 제출, API 호출 | 로그인 URI로 짧은 간격 반복 POST |
단계적으로 좁히는 예입니다.
질문: "192.168.10.20에서 이상한 외부 통신이 있었나?"
1) ip.addr == 192.168.10.20 → 해당 호스트만
2) ip.src == 192.168.10.20 && !(ip.dst == 192.168.10.0/24) → 외부로 나간 것만
3) ... && tcp.flags.syn == 1 && tcp.flags.ack == 0 → 새 연결 시도만
4) 목적지 포트·IP별로 정리 → Statistics(5편)로 넘김
http 필드로 필터링되지 않습니다. 이때는 tls.handshake.extensions_server_name처럼 암호화 전 노출되는 필드를 활용합니다. (06. HTTPS와 TLS Handshake)host, port, net), 디스플레이 필터는 필드 비교식(ip.addr == x)입니다.!(ip.addr == x) 형태로 써야 버전과 무관하게 의도대로 동작합니다.