📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 135편
이전 글: 134. Follow TCP Stream · 다음 글: 136. DHCP Packet 분석
DNS의 해석 과정은 47. DNS 이름 해석 흐름 — 도메인이 IP가 되기까지, 레코드 타입과 응답 코드의 의미는 70. DNS 레코드와 질의 유형, 터널링 같은 악용 징후는 144. DNS 악용 유형 — 터널링·DGA·비정상 질의 탐지에서 다룹니다. 이 글은 Wireshark에서 DNS 패킷 하나를 필드 단위로 읽고, 질의와 응답을 짝지어 검증하는 방법에 집중합니다.
| 영역 | Wireshark 필드 | 읽는 내용 |
|---|---|---|
| 식별 | dns.id | Transaction ID, 질의·응답 짝 맞추기 |
| 방향 | dns.flags.response | 0 질의, 1 응답 |
| 플래그 | dns.flags.rd, dns.flags.ra, dns.flags.aa, dns.flags.truncated | 재귀 요청·가능, 권한 응답, 잘림 |
| 결과 | dns.flags.rcode | 0 NoError, 2 ServFail, 3 NXDomain, 5 Refused |
| 질의 | dns.qry.name, dns.qry.type, dns.qry.name.len | 이름, 타입 번호, 이름 길이 |
| 개수 | dns.count.queries, dns.count.answers | 질의·응답 레코드 수 |
| 응답 값 | dns.a, dns.aaaa, dns.cname, dns.resp.ttl | 주소, 별칭, TTL |
Wireshark 계산 필드도 함께 봅니다.
| 계산 필드 | 의미 |
|---|---|
dns.response_in | (질의 패킷에) 이 질의의 응답이 있는 프레임 |
dns.response_to | (응답 패킷에) 원래 질의 프레임 |
dns.time | 질의부터 응답까지 걸린 시간 |
Wireshark가 DNS 질의와 응답을 연결하는 방식입니다.
UDP 53 (또는 TCP 53) 패킷
↓ DNS 해석
dns.flags.response == 0 → 질의 목록에 (주소·포트 쌍, dns.id) 등록
↓
dns.flags.response == 1 → 같은 쌍 + 같은 dns.id 질의 검색
├─ 찾음 → response_to / response_in / dns.time 기록
└─ 없음 → 짝 없는 응답 (요청 없이 온 응답 후보)
↓
TC 플래그 = 1 → 클라이언트가 TCP 53으로 재질의 (2바이트 길이 필드 + DNS 메시지)
dns.* 필드로 해석되지 않습니다.정상 질의·응답 판단 기준
| 확인 항목 | 정상 | 이상 신호 |
|---|---|---|
| 목적지 | 조직이 지정한 내부 리졸버 | 임의의 외부 DNS 서버로 직접 질의 |
| 짝 | 대부분 response_in 존재 | 응답 없는 질의 다수, 질의 없는 응답 다수 |
dns.time | 캐시 적중 시 매우 짧고, 재귀 시 더 김 | 특정 도메인만 지속적으로 느림·타임아웃 |
| RCODE | 대부분 0, NXDomain 소량 | NXDomain 비율 급증 (무작위 이름 질의) |
| 질의 타입 | A·AAAA·HTTPS 등 위주 | TXT·NULL 등이 한 도메인에 반복 |
| TC 플래그 | 큰 응답(DNSSEC 등)에서 가끔 | 작은 질의에 TC 응답 반복 |
자주 쓰는 Display Filter
| 필터 | 용도 |
|---|---|
dns.flags.response == 0 && !dns.response_in | 응답을 받지 못한 질의 |
dns.flags.response == 1 && !dns.response_to | 질의 없이 도착한 응답 |
dns.flags.rcode == 3 | NXDomain 응답 |
dns.time > 1 | 1초 이상 걸린 응답 |
dns && !(ip.addr == 192.168.10.1) | 지정 리졸버(예시 주소) 외 DNS 통신 |
dns.qry.name contains "example" | 특정 이름 포함 질의 |
실습 예시 — 본인 소유 VM에서 존재하는 이름과 존재하지 않는 이름을 질의해 캡처합니다. 명령과 출력은 예시(값은 환경마다 다름)입니다.
sudo tcpdump -i ens33 -nn -w /tmp/dns.pcapng port 53 &
dig @192.168.10.1 www.example.com A # Rocky: bind-utils, Ubuntu: dnsutils 패키지
dig @192.168.10.1 nohost-test.example.com A
sudo pkill -INT tcpdump
# 질의·응답 짝, 응답 시간, 결과
tshark -r /tmp/dns.pcapng -Y 'dns.flags.response == 1' -T fields -E header=y \
-e frame.number -e dns.response_to -e dns.id -e dns.qry.name -e dns.qry.type \
-e dns.flags.rcode -e dns.count.answers -e dns.a -e dns.time
# 질의 이름 상위 빈도
tshark -r /tmp/dns.pcapng -Y 'dns.flags.response == 0' -T fields -e dns.qry.name \
| sort | uniq -c | sort -rn | head
# 질의 타입·RCODE 통계
tshark -r /tmp/dns.pcapng -q -z dns,tree
첫 번째 명령의 형식 예시입니다.
frame.number dns.response_to dns.id dns.qry.name qry.type rcode answers dns.a dns.time
2 1 0x3f2a www.example.com 1 0 1 203.0.113.10 0.021
4 3 0x81c4 nohost-test.example.com 1 3 0 0.034
| 확인 포인트 | 읽는 법 |
|---|---|
response_to 1, dns.id 0x3f2a | 프레임 1 질의에 대한 응답 |
| rcode 0, answers 1 | 정상 해석, 주소 1개 |
| rcode 3, answers 0 | NXDomain, 존재하지 않는 이름 |
📷 [실습 화면 삽입 위치] DNS 응답 패킷 Details에서 Flags(Response, Recursion desired/available, Reply code)와 Answers,
[Request In],[Time]을 펼친 화면
📷 [실습 화면 삽입 위치]
dns.flags.response == 0 && !dns.response_in필터로 응답 없는 질의만 남긴 화면
| 관찰 | 가능한 해석 | 확인할 것 |
|---|---|---|
| 내부 호스트가 외부 DNS 서버로 직접 질의 | 정책 우회, 악성코드의 자체 리졸버 사용 | 해당 호스트 프로세스, 방화벽 53 출구 정책 |
| 질의 없는 응답이 대량 유입 | 반사 공격의 피해 측, 응답 위조 시도 | 출발지, dns.id 분포 |
| 한 도메인 하위의 긴 무작위 이름 반복 | 터널링·DGA 가능성 (상세 판단은 144. DNS 악용 유형 — 터널링·DGA·비정상 질의 탐지) | dns.qry.name.len, 고유 이름 수 |
| 같은 질의에 서로 다른 응답 IP가 짧은 간격으로 | 짧은 TTL의 빠른 변경(fast-flux) 또는 CDN | dns.resp.ttl, 응답 IP의 소속 |
| 흔적 위치 | 확인할 수 있는 것 |
|---|---|
| 패킷 캡처 | 질의·응답 전체 필드, 짝 여부, 응답 시간 |
| 리졸버 질의 로그 | 누가 어떤 이름을 질의했는지 (기본 비활성인 경우 많음) |
| 방화벽 로그 | 내부 리졸버 외 53·853 출구 통신 |
| IDS | 알려진 악성 도메인 질의 이벤트 |
관제자가 확인할 질문
오탐 주의: 브라우저·OS의 사전 조회, 보안 제품의 평판 조회, CDN의 짧은 TTL은 모두 정상 트래픽이지만 대량·무작위처럼 보일 수 있습니다. 또 내부 리졸버 앞에서 캡처하면 클라이언트 질의만, 뒤에서 캡처하면 리졸버의 외부 질의만 보이므로 캡처 위치를 먼저 확인합니다.
dns.id·dns.flags.*·dns.qry.*·dns.count.*·응답 레코드 필드 순서로 읽습니다.response_in·response_to·dns.time을 계산합니다.!dns.response_in(응답 없는 질의), !dns.response_to(요청 없는 응답) 필터로 짝이 깨진 트래픽을 찾습니다.dns.*로 보이지 않으므로 목적지 IP·포트·SNI로 따로 확인합니다.