119. TCP 헤더와 Flag 분석 — 포트·시퀀스·Flag·옵션 필드 읽는 법

changseop lee·3일 전

네트워크 · 패킷 분석

목록 보기
119/300

📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 119편
이전 글: 118. Ping Packet 분석 · 다음 글: 120. UDP Header 분석
참고(01. TCP/IP 네트워크 구조 이해): 40. TCP와 UDP 헤더 비교 — 신뢰성과 속도의 차이 · 41. TCP 3-Way Handshake — 연결이 성립했다는 증거 · 45. TCP 연결 종료 — FIN과 RST의 차이 · 46. 시퀀스 번호·ACK·윈도우 — TCP가 데이터를 빠짐없이 전달하는 방법 — TCP 동작 개념은 이 글들에서 다뤘습니다.

1. 왜 알아야 하는가

관제에서 보는 트래픽의 상당 부분은 TCP 위에서 동작합니다(HTTP, HTTPS, SSH, SMB, DB 등). 방화벽 로그가 "허용/차단"과 포트를 알려 준다면, pcap의 TCP 헤더는 그 연결이 실제로 어떻게 진행되었는지를 알려 줍니다.

  • 이 패킷은 어느 연결(세션)에 속하는가? (tcp.stream)
  • 연결 요청(SYN)에 서버가 응답했는가, 거부(RST)했는가?
  • 실제로 데이터가 오갔는가, 오갔다면 몇 바이트인가? (tcp.len)
  • 일반적인 통신에서는 나오지 않는 Flag 조합이 있는가?
  • 연결 초기에 협상된 옵션(MSS, Window Scale 등)은 무엇인가?

이 글은 TCP 헤더의 각 필드가 Wireshark에서 어떤 이름과 값으로 보이는지, 특히 Wireshark가 계산해서 보여 주는 값과 패킷에 실제로 기록된 값의 차이에 집중합니다. 연결 수립 과정 자체의 패킷 분석은 121편(121. TCP 3-Way Handshake 분석)에서 다룹니다.


2. 핵심 개념

TCP 헤더 필드명
그림 1. TCP 헤더 위치별 Wireshark 필드명

2-1. TCP 헤더 필드와 Wireshark 필드명

헤더 항목Wireshark 필드실제 값 / 계산 값분석 시 의미
Source Porttcp.srcport실제출발지 포트 (tcp.port는 양쪽 중 하나)
Destination Porttcp.dstport실제목적지 포트
(세션 번호)tcp.stream계산Wireshark가 연결마다 붙이는 0부터 시작하는 번호
Sequence Numbertcp.seq계산(상대값)연결 시작 기준 상대 번호
Sequence Number (raw)tcp.seq_raw실제헤더에 기록된 32비트 원래 값
Acknowledgment Numbertcp.ack / tcp.ack_raw계산 / 실제다음에 받기를 기대하는 번호
(다음 시퀀스)tcp.nxtseq계산이 세그먼트 다음에 올 시퀀스 번호
Header Lengthtcp.hdr_len실제(바이트 환산)20이면 옵션 없음, 그 이상은 옵션 포함
Flagstcp.flags, tcp.flags.*, tcp.flags.str실제제어 비트
Windowtcp.window_size_value실제헤더에 기록된 16비트 값
(계산된 윈도우)tcp.window_size계산Window Scale을 적용한 실제 수신 가능 크기
(스케일 배수)tcp.window_size_scalefactor계산-1 = 알 수 없음(핸드셰이크 미캡처), -2 = 스케일 미사용
(데이터 길이)tcp.len계산이 세그먼트의 페이로드 바이트 수
Checksumtcp.checksum실제기본적으로 검증하지 않음(115편 오프로딩 참고)
Urgent Pointertcp.urgent_pointer실제URG가 설정될 때만 의미

2-2. Flag 필드

FlagWireshark 필드비트 값의미
FINtcp.flags.fin0x001보낼 데이터 없음, 정상 종료 요청
SYNtcp.flags.syn0x002연결 시작, 초기 시퀀스 번호 동기화
RSTtcp.flags.reset0x004연결 즉시 중단·거부
PSHtcp.flags.push0x008받은 데이터를 바로 애플리케이션에 전달
ACKtcp.flags.ack0x010tcp.ack 값이 유효함
URGtcp.flags.urg0x020긴급 포인터 유효
ECE / CWRtcp.flags.ece, tcp.flags.cwr0x040 / 0x080ECN 혼잡 알림 관련

tcp.flags.str은 설정된 Flag를 ·······A··S·처럼 문자로 보여 주는 필드라 사람이 읽기 좋고, tcp.flags는 0x012 같은 숫자라 조합 비교에 편합니다. 예약 비트 영역의 필드 이름은 Wireshark 버전에 따라 바뀐 적이 있으므로(4.2 기준 tcp.flags.ae, tcp.flags.res), 사용하는 버전의 상세 트리에서 확인 후 씁니다.

2-3. 자주 보는 옵션 (주로 SYN, SYN/ACK에 포함)

옵션KindWireshark 필드의미
MSS2tcp.options.mss, tcp.options.mss_val한 세그먼트에 받을 수 있는 최대 데이터 크기
Window Scale3tcp.options.wscale, tcp.options.wscale.shift, tcp.options.wscale.multiplier윈도우 값에 곱할 배수(2의 shift 제곱)
SACK Permitted4tcp.options.sack_perm선택적 확인응답 사용 가능
SACK5tcp.options.sack_le, tcp.options.sack_re받은 데이터 구간(왼쪽·오른쪽 끝)
Timestamps8tcp.options.timestamp.tsval, tcp.options.timestamp.tsecr송신 시각 값 / 상대 값 되돌려주기
NOP1tcp.option_kind == 1옵션 정렬용 패딩

3. 동작 원리

TCP 플래그
그림 2. 플래그 비트 배치와 자주 보는 조합의 16진 값

3-1. 상대 시퀀스 번호는 Wireshark가 만든 값

TCP의 초기 시퀀스 번호(ISN)는 무작위에 가까운 32비트 값입니다. 사람이 읽기 어렵기 때문에 Wireshark는 기본적으로 연결에서 처음 본 값을 0으로 두고 상대값을 보여 줍니다(tcp.relative_sequence_numbers: TRUE).

패킷에 실제 기록된 값              Wireshark 기본 표시
tcp.seq_raw = 1000000 (SYN)   →   tcp.seq = 0
tcp.seq_raw = 1000001         →   tcp.seq = 1
tcp.seq_raw = 1000001, len=21 →   tcp.seq = 1, tcp.nxtseq = 22
                ↓
다른 캡처 파일(센서 pcap vs 서버 pcap)과 비교할 때
→ 상대값은 파일마다 기준점이 달라 비교 불가
→ tcp.seq_raw 로 비교해야 같은 패킷인지 확인 가능

캡처가 연결 중간부터 시작되면 Wireshark는 SYN을 보지 못했으므로 처음 본 패킷을 기준으로 상대값을 만들고, Window Scale도 알 수 없어 tcp.window_size_scalefactor가 -1로 표시됩니다. 이 경우 tcp.window_size는 실제 수신 가능 크기가 아니라 헤더 값 그대로입니다.

3-2. tcp.stream으로 연결 단위 묶기

Wireshark는 (출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트) 조합이 같은 패킷을 하나의 연결로 보고 tcp.stream 번호를 붙입니다. 분석할 때는 개별 패킷보다 tcp.stream 단위로 "무슨 일이 있었는지"를 요약하는 것이 효율적입니다. 같은 포트 조합이 짧은 시간 안에 재사용되면 Wireshark가 새 연결로 나누기도 하고(tcp.analysis.reused_ports 표시), 반대로 오래된 연결과 섞여 보일 수도 있습니다.


4. 실제 명령어 / 실습

실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 이름은 ens33을 예시로 사용합니다. tshark 설치는 112편과 같습니다(Rocky: sudo dnf install -y wireshark-cli, Ubuntu: sudo apt install -y tshark).

4-1. 캡처와 트래픽 발생

# 터미널 1: TCP만 캡처
sudo tshark -i ens33 -f "tcp" -w /tmp/pcap11-tcp.pcapng

# 터미널 2 (본인 VM 또는 본인 소유 실습 서버 대상)
curl -sI http://example.com > /dev/null                            # 정상 연결 + 데이터
timeout 2 bash -c '</dev/tcp/<실습 서버 IP>/8081' ; echo "rc=$?"    # 닫힌 포트 연결 시도

두 번째 명령은 bash의 /dev/tcp 기능으로 TCP 연결만 시도하는 방법입니다. 대상 포트가 닫혀 있으면 SYN에 대해 RST가 돌아오고, 방화벽이 조용히 버리면 응답 없이 시간이 초과됩니다. 호스트 PC에서 VM으로 SSH 접속을 하고 있다면 그 트래픽도 함께 캡처됩니다.

📷 [실습 화면 삽입] Wireshark 상세 창의 Transmission Control Protocol 트리 (Flags와 Options를 펼친 상태)

4-2. 필드 단위로 읽기

F=/tmp/pcap11-tcp.pcapng

# (1) 헤더 핵심 필드
tshark -r $F -T fields -E header=y \
  -e frame.number -e tcp.stream -e tcp.srcport -e tcp.dstport \
  -e tcp.seq -e tcp.seq_raw -e tcp.ack -e tcp.len -e tcp.flags -e tcp.flags.str \
  -e tcp.window_size_value -e tcp.window_size -e tcp.window_size_scalefactor | head -20

# (2) SYN, SYN/ACK에 실린 옵션
tshark -r $F -Y "tcp.flags.syn == 1" -T fields -E header=y \
  -e frame.number -e ip.src -e tcp.flags.str -e tcp.options.mss_val \
  -e tcp.options.wscale.shift -e tcp.options.timestamp.tsval -e tcp.option_kind

# (3) 상대 시퀀스 번호 끄고 보기 (실제 헤더 값)
tshark -r $F -o tcp.relative_sequence_numbers:FALSE -T fields \
  -e frame.number -e tcp.seq -e tcp.ack | head

# (4) 연결별 데이터 양 요약 (페이로드 합계)
tshark -r $F -Y "tcp.len > 0" -T fields -e tcp.stream -e tcp.len \
  | awk '{b[$1]+=$2} END {for (s in b) print s, b[s]}' | sort -n

# (5) Flag 조합 분포
tshark -r $F -T fields -e tcp.flags.str | sort | uniq -c | sort -rn

📷 [실습 화면 삽입] 닫힌 포트 연결 시도에서 [SYN] → [RST, ACK]가 보이는 패킷 목록


5. 결과 확인

아래는 형식 예시(값은 환경마다 다름) 입니다.

frame stream srcport dstport seq seq_raw  ack len flags  flags.str      win_value win   scale
13    0      51000   22      0   1000000  0   0   0x0002 ··········S·   64240     64240
14    0      22      51000   0   5000000  1   0   0x0012 ·······A··S·   65160     65160
15    0      51000   22      1   1000001  1   0   0x0010 ·······A····   502       64256 128
16    0      51000   22      1   1000001  1   21  0x0018 ·······AP···   502       64256 128
17    1      51001   8081    0   777      0   0   0x0002 ··········S·   64240     64240
18    1      8081    51001   1   0        1   0   0x0014 ·······A·R··   0         0     -2
  • 13~16번(stream 0): SYN → SYN/ACK → ACK 후 21바이트 데이터(PSH/ACK). 15번부터 Window Scale 128배가 적용되어 502 × 128 = 64256
  • 17~18번(stream 1): SYN에 대해 RST/ACK → 포트가 닫혀 있음. SYN에 Window Scale 옵션이 없었으므로 scale은 -2

옵션 형식 예시:

frame  ip.src         flags.str      mss_val  wscale.shift  tsval  option_kind
13     192.168.10.20  ··········S·   1460     7             12345  2,4,8,1,3

확인 체크리스트

  • tcp.stream 번호로 연결 단위를 구분했다
  • tcp.seq(상대값)와 tcp.seq_raw(실제값)의 차이를 확인했다
  • tcp.len > 0인 패킷으로 실제 데이터가 오간 연결을 구분했다
  • SYN에 실린 MSS·Window Scale·SACK Permitted·Timestamps 옵션을 읽었다
  • tcp.window_size_value와 tcp.window_size의 차이와 scale 값(-1, -2)의 의미를 설명할 수 있다
  • 닫힌 포트에 대한 SYN → RST/ACK 흐름을 확인했다

6. 패킷 / 로그 분석

관찰정상일 수 있는 경우의심해야 하는 경우
SYN 후 RST/ACK서비스 중지, 잘못된 포트 설정한 출발지가 여러 포트에 SYN → 대부분 RST/ACK (포트 스캔, 05. 네트워크 스캔 · 공격 징후 분석 시리즈에서 다룸)
SYN 후 응답 없음방화벽의 조용한 차단(drop), 패킷 손실대량의 응답 없는 SYN이 한 서버로 집중
데이터 없는 연결 (tcp.len이 모두 0)헬스체크, 연결 확인짧은 연결이 여러 포트·호스트로 반복
SYN과 FIN이 동시에 설정(정상 통신에서는 사용되지 않음)조작된 패킷 → 스캔·회피 시도 가능성
Flag가 하나도 없는 패킷 (tcp.flags == 0x000)(정상 통신에서는 사용되지 않음)NULL 스캔 등 조작 패킷
FIN·PSH·URG 동시 설정 (0x029)(정상 통신에서는 사용되지 않음)Xmas 스캔 등 조작 패킷
MSS 옵션 없는 SYN일부 특수 장비·스택옵션이 거의 없는 SYN이 대량 발생 (도구가 만든 패킷 가능성)
Window 0 (tcp.analysis.zero_window)수신 측 처리 지연장시간 지속 → 서비스 성능 문제 확인

확인 필터 예시입니다.

# 비정상 Flag 조합
tshark -r $F -Y "(tcp.flags.syn == 1 && tcp.flags.fin == 1) || tcp.flags == 0x000 || tcp.flags == 0x029"

# SYN에 RST로 응답한 연결 (출발지·목적지 포트 요약)
tshark -r $F -Y "tcp.flags.reset == 1 && tcp.flags.ack == 1 && tcp.seq == 1" \
  -T fields -e ip.src -e tcp.srcport -e ip.dst | sort | uniq -c | sort -rn

# 출발지별로 SYN을 보낸 목적지 포트 수
tshark -r $F -Y "tcp.flags.syn == 1 && tcp.flags.ack == 0" -T fields \
  -e ip.src -e tcp.dstport | sort -u | awk '{c[$1]++} END {for (s in c) print c[s], s}' | sort -rn

# MSS 옵션이 없는 SYN (Wireshark 전문가 정보)
tshark -r $F -Y "tcp.options.mss.absent"

두 번째 필터의 tcp.seq == 1 조건은 상대 시퀀스 번호를 전제로 한 것이라, 캡처가 연결 도중부터 시작되었거나 상대값 표시를 끈 경우에는 결과가 달라질 수 있습니다. 필터 결과는 반드시 해당 tcp.stream을 열어 앞뒤 패킷과 함께 확인합니다.


7. 보안관제 관점

흔적이 남는 곳

  • 방화벽 세션 로그: 연결 허용·차단, 세션 종료 사유, 바이트 수. 다만 개별 Flag나 시퀀스 값은 보통 남지 않습니다(방화벽 로그는 06. 방화벽 · IDS/IPS 분석 시리즈에서 다룸).
  • IDS: 비정상 Flag 조합, 스캔 패턴, 세션 재조립 후의 페이로드 탐지.
  • 호스트: 서버 측의 연결 상태는 ss 명령으로 확인할 수 있습니다(ss와 ip 명령어, 16. 프로세스와 네트워크 소켓 참고).

한계와 오탐 주의

  • tcp.seq, tcp.ack, tcp.stream, tcp.window_size, tcp.len은 Wireshark가 계산한 값입니다. 캡처가 불완전하면(연결 중간부터 시작, 패킷 유실) 계산 결과도 부정확해질 수 있으므로, 증거로 인용할 때는 tcp.seq_raw 같은 실제 값을 함께 적습니다.
  • 여러 지점에서 캡처한 pcap을 비교할 때는 상대 시퀀스 번호가 파일마다 달라지므로 반드시 raw 값으로 매칭합니다.
  • 비정상 Flag 조합은 일반 통신에서 거의 없지만, 네트워크 장비의 오동작이나 손상된 패킷에서도 나타날 수 있습니다. 반복성과 출발지 패턴을 함께 봅니다.
  • TCP 체크섬은 송신 호스트 캡처에서 오프로딩 때문에 틀린 값으로 보일 수 있습니다(115. IP 헤더 분석 — TTL·Flags·Fragment·Checksum 필드 읽는 법 참고).

8. 핵심 정리

  • TCP 분석의 기본 단위는 tcp.stream(연결)이며, 포트(tcp.srcport, tcp.dstport)와 Flag로 연결의 성격을 파악합니다.
  • tcp.seq·tcp.ack는 Wireshark가 만든 상대값이고, 헤더의 실제 값은 tcp.seq_raw·tcp.ack_raw입니다. 여러 캡처를 비교할 때는 raw 값을 씁니다.
  • tcp.len은 페이로드 크기로, 실제 데이터가 오갔는지 판단하는 가장 간단한 기준입니다.
  • tcp.window_size는 Window Scale을 적용한 계산값이며, 핸드셰이크가 캡처되지 않으면 scale이 -1(알 수 없음)로 표시됩니다.
  • SYN/ACK에 실린 MSS·SACK Permitted·Window Scale·Timestamps 옵션은 tcp.options.* 필드로 읽습니다.
  • SYN+FIN, Flag 없음, FIN+PSH+URG 같은 조합은 정상 통신에서 쓰이지 않으므로 출발지와 반복성을 함께 확인합니다.

다음 글: 12. 3-Way Handshake 패킷 분석 (예정)

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글