/* 
written by kaspy (kaspyx@gmail.com)
*/ 


windump를 이용하여 tcp 패킷을 분석해 보았다. 

아래와같은 명령어로 실행하였다.

기본적인 사용법과 다운로드는 여기 링크 참조!!  -> Link

(1번 네트워크 인터페이스의 7778번 포트를 감청)


서버쪽에서는 nc -l  -p 7778 을 사용하여 7778번 포트를 열어 접속을 기다리게 하였다.(리눅스 서버)

클라이언트에서는 nc를 사용하여 7778번 포트에 접속한다음 hello 라는 문자열을 보내고 ctr + c 하여 접속을 끊었다.(윈도우 클라이언트)


TCP 통신 패킷 분석

tcp/ip 통신은 안정형 통신 방법으로 데이터를 주고 받을때마다 ACK 및 S/N 번호로 데이터가 오고가는 순서를 보장할수있다. 일명 3 Way Handshake 기법인데 windump 를 통해 패킷을 분석해 보았다.

1) 연결 할때

우선 내컴퓨터의 네트워크 인터페이스 카드에 대한 정보는 아래와 같다.

(mac 주소 및 IP 주소)

nc를 이용하여 접속했을때 나온 패킷 덤프는 아래와 같다.


Ethernet header (빨간색 박스)


Dest addr(6 byte) => 00:09:97:c7:26:07

Src addr(6 byte)  => 00:24:be:66:ef:b8

Type ( 2byte ) => 0x0800 (IP packet)

 

IP header (이어서 빨간색 박스)


     version(4 bit)+header length(4 bit)+type of service(8 bit)+total length(16 bit)

    +identification(16 bit)+flags(3 bit)+fragment offset(13 bit)

    +time to live(8 bit)+protocol(8 bit)+header checksum(16 bit)

    +source IP address(32 bit)

    +dest IP address(32 bit)


version(4 bit)+header length(4 bit) => 0x45

0100 0101(2) 여기서 0100 header version : 4, header length: 0101, 5

type of service => 0

total length => 0x30

idectification(2 byte) => 0x3803

flags (3bit) => 0x04 : 패킷을 분할하지 않겠다는 뜻, fragment offset(13bit)=>패킷 조각 오프셋: 0x0

time to live(1 byte) => 0x80 TTL: 128

protocol( 1byte) => 0x06 : TCP 패킷이라는 뜻

check sum(2byte) = 0x0000

source IP address( 4 byte) => a5 f6 43 d3 => 해당 헥스 값을 10진수로 바꾸면 IP주소가됨

0xaf : 165 0xf6: 246 0x43: 67 0xd3: 211

dest IP address(4 byte) => a5 f6 0c d7

0xaf : 165 0xf6: 246 0x0c :12 0xd7: 215

 

TCP Header (노란색 박스)


source port number(16)+dest port number(16)

 + sequence number(32)

 + acknowledgment number(32)

 + header length(4)+reserved(6)+URG+ACK+PSH+RST+SYN+FIN+window size(16)

 + TCP checksum(16)+urgent pointer(16)

 

sequence number=ISN(Initial Sequence Number)

                  + (# of bytes sent so far in this tcp connection)

acknowledgment number=next sequence number expected from the other part

 

source port number (2 byte) => 0x b6 8b : 46731

dest port number (2 byte) => 0x 1e 62 : 7778

sequence number (4 byte) => 0x f1 01 c7 00 : 4,043,425,536

acknowledgment number (4 byte) => 0x00 00 00 00

length(4)+reserved(6)+URG+ACK+PSH+RST+SYN+FIN =>

 

0x70 02 => 0111 0000 0000 0010 (2)

header length( 4bit ) => 7 (4 바이트 단위 이므로 * 4) : 28


- Control flag bit 정보

 URG

 ACK

PSH 

 RST

SYN

 FIN

flag bit ( 6 bit) => 00 00 10 SYN 패킷

 

window size (2 byte) => 0x2000 ( 8192)

check sum ( 2 byte) => 0x9cb9 ( 40121)

urgent pointer(2 byte) => 0x00

초록색 박스

클라이언트에서 sequence number 0xf101c700로 서버에게 보냈을 때 다시 서버에서는 sequence number +1을 하여 acknowledgment number(0xf101c701)을 보내 주었다.

서버에서는 Code bit 7011 Code bit( 01 00 10)으로 표현할수 있는데 이는 SYN/ACK 를 다시 클라이언트에게 응답을 해줌

주황색 박스

이후 클라이언트는 서버에서 보내준 sequence number(0x46623e05) +1을 하여 acknowledgment number(0x46623e05)을 보내어줘서 잘 받았다고 알려주고 Code bit 01 00 00 패킷(ACK)을 보내주고 이제 통신을 하기 위한 3 Way HandShake의 절차를 마쳤음.


2) 데이터 전송 하고 연결을 끊을때

Dest addr(6 byte) => 00:09:97:c7:26:07 (서버)

Src addr(6 byte)  => 00:24:be:66:ef:b8 (클라이언트)

Type ( 2byte ) => 0x0800 (IP packet)

 Control flag bit 정보

 URG

 ACK

PSH 

 RST

SYN

 FIN

 화면에서 유심히 봐야 할 내용을 박스로 쳤는데, 파란 박스는 SequenceNumber Acknowledeg Number 그리고 빨간 박스는 데이터, Flag 비트는 노란색으로 표시 하였다.

1 패킷을 보내는 것은 클라이언트 hello라는 내용을 gets로 입력받아서 서버에게 보내주는 패킷 내용입니다. SequenceNumber 0xf10f7c01로 보내 주었고, 플래그 비트는 011000 으로 표시 될수 있는데 이전 패킷을 잘 받았다는 표시 ACKPSH : Push.  Buffer를 따로 플래그로 두워 보내는데, 채워지는것을 기다리지 않고 데이터를 전달한다는 뜻으로 해석된다.

2 패킷은 1번 패킷을 잘받았다는 플래그비트 ACK을 클라이언트에게 보내주며 Acknowledeg Number는 클라이언트에서 보내어준 SequenceNumber 0xf10f7c01에서 클라이언트에게 받은 데이터의 개수(5)를 더해서 0xf10f7c06을 보내어 주었다.

3 패킷은 클라이언트가 이제 접속을 종료하겠다는 패킷을 보내준 내용플래그 비트가 010001으로 ACK FIN 플래그 비트가 셋팅 되어 있습니다.

  4 패킷은 서버가 이제 클라이언트의 종료를 잘받았다고 ACK 플래그를 보내주고 접속을 서로 끊음.



신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

Winpcap 라이브러리 중에 pcap_open_live, pcap_compile , pcap_setfilter, pcap_next_ex 함수가 있다.

각각의 함수를 활용하면 패킷을 로우레벨에서 분석할수 있다. 물론 다른 함수들도 사용되긴 하지만 주로 사용되는 함수는 4개라고 보면 될것같다.

함수 레퍼런스 : http://www.winpcap.org/docs/docs_40_2/html/group__wpcapfunc.html#gae6abe06e15c87b803f69773822beca8


1. TCP 패킷 감청 프로그램 만들기

1). pcap_open_live 

 현재 네트워크에서 사용하는 네트워크 디바이스의 패킷을 캡처하는 함수이다. 아래는 함수에대한 프로토타입이다. 첫번째는 오픈한 네트워크 어뎁터에 대한 장치드라이버에 대한 이름이고, 두번째 snaplen은 캡처할 패킷의 크기이다. 세번째가 중요한데 캡처 모드인데 보통 1 (모든 패킷을 캡처)을 준다. to_ms는 타임아웃 설정 delay이고, ebuf는 캡처에 실패했을때 에러메시지를 저장해주는 버퍼이다. 성공시 pcap_t* 를 리턴해주고 실패시 NULL을 리턴한다.

  1. pcap_t* pcap_open_live(const char *device,
  2. int snaplen,
  3. int promisc
  4. int to_ms
  5. char *  ebuf)

2). pcap_compile

 캡처한 패킷에 대한 구조체를 인자로 받아서 필터룰을 적용해준다. 매개변수로 앞서 설명한 pcap_open_live 에서 리턴한 pcap_t* 구조체를 매개변수로 받는다. 여기서 bpf_program *fp는 날아댕기는 패킷에대한 필터를 만들어주고 이걸 기반으로 패킷을 잡게된다. 3번째 인자 char *str은 필터에 대한 룰을 정해주는데 룰에대한 문자열 정의는 아래와 같이 정해줬다. 

 #define FILTER_RULE "host 165.246.12.215 and port 7778"

룰에 대한 정의에 대한 설명은 http://www.winpcap.org/docs/docs_40_2/html/group__language.html 을 참고해 주기바란다.optimize 옵션은 보통 1을 주고. 마지막 인자인 netmask는 ipv4에서 broadcast를 감청할때 쓰일수있는 인자이다. 실패시 -1 을 리턴

  1. int pcap_compile(pcap_t *p,
  2. struct bpf_program *fp,
  3. char *str,
  4. int optimize,
  5. bpf_u_int32 netmask  
  6. )


3). pcap_setfilter

컴파일한 필터를 실제 패킷 핸들에 적용해주는 함수이다. 앞서 설명한 pcap_compile 에서 나온 인자들 2개를 넣어주면 된다.

  1. int pcap_setfilter(pcap_t *p,
  2. struct bpf_program *fp       
  3. )      

4). pcap_next_ex

마지막으로 캡처한 패킷 데이터를 실제 가져오는 함수이다. 실제는 packet_handler라는 콜백 함수가 데이터들을 채워주는데. 이중 포인터를 인자로 받는다. 

  1. int pcap_next_ex(pcap_t *p,
  2. struct pcap_pkthdr ** pkt_header,
  3. const u_char ** pkt_data         
  4. )

실제는 사용하는 방식은 아래와 같다.

  1. int res;
  2. struct pcap_pkthdr *header;
  3. const unsigned char *pkt_data;
  4. while((res=pcap_next_ex(fp, &header,&pkt_data))>=0){
  5.         if (res==0) continue;
  6.         /* blah~~ */
  7. }

아래와 같이 패킷을 감청하는 프로그램을 짜서 TCP/IP 통신시에 처음 연결시 Three way handshake 패킷을 감청해보았다.

기본적인 패킷 스니핑 프로그램의 기본 틀은 같으니 파싱부분만 추가하면 될것이다.

  1. #include "pcap.h"
  2.  
  3. #include <stdio.h>
  4. #include <winsock2.h>
  5.  
  6. #pragma comment (lib, "wpcap.lib")
  7. #pragma comment (lib, "ws2_32.lib" )
  8.  
  9. #define FILTER_RULE "host 165.246.12.215 and port 7778"
  10.  
  11. struct ether_addr
  12. {
  13.         unsigned char ether_addr_octet[6];
  14. };
  15.  
  16. struct ether_header
  17. {
  18.         struct  ether_addr ether_dhost;
  19.         struct  ether_addr ether_shost;
  20.         unsigned short ether_type;
  21. };
  22.  
  23. struct ip_header
  24. {
  25.         unsigned char ip_header_len:4;
  26.         unsigned char ip_version:4;
  27.         unsigned char ip_tos;
  28.         unsigned short ip_total_length;
  29.         unsigned short ip_id;
  30.         unsigned char ip_frag_offset:5;
  31.         unsigned char ip_more_fragment:1;
  32.         unsigned char ip_dont_fragment:1;
  33.         unsigned char ip_reserved_zero:1;
  34.         unsigned char ip_frag_offset1;
  35.         unsigned char ip_ttl;
  36.         unsigned char ip_protocol;
  37.         unsigned short ip_checksum;
  38.         struct in_addr ip_srcaddr;
  39.         struct in_addr ip_destaddr;
  40. };
  41.  
  42. struct tcp_header
  43. {
  44.         unsigned short source_port;
  45.         unsigned short dest_port;
  46.         unsigned int sequence;
  47.         unsigned int acknowledge;
  48.         unsigned char ns:1;
  49.         unsigned char reserved_part1:3;
  50.         unsigned char data_offset:4;
  51.         unsigned char fin:1;
  52.         unsigned char syn:1;
  53.         unsigned char rst:1;
  54.         unsigned char psh:1;
  55.         unsigned char ack:1;
  56.         unsigned char urg:1;
  57.         unsigned char ecn:1;
  58.         unsigned char cwr:1;
  59.         unsigned short window;
  60.         unsigned short checksum;
  61.         unsigned short urgent_pointer;
  62. };
  63.  
  64. void print_ether_header(const unsigned char *data);
  65. int print_ip_header(const unsigned char *data);
  66. int print_tcp_header(const unsigned char *data);
  67. void print_data(const unsigned char *data);
  68.  
  69. int main(){
  70.         pcap_if_t *alldevs=NULL;
  71.         char errbuf[PCAP_ERRBUF_SIZE];
  72.  
  73.         int offset=0;
  74.        
  75.         // find all network adapters
  76.         if (pcap_findalldevs(&alldevs, errbuf)==-1){
  77.                 printf("dev find failed\n");
  78.                 return -1;
  79.         }
  80.         if (alldevs==NULL){
  81.                 printf("no devs found\n");
  82.                 return -1;
  83.         }
  84.         // print them
  85.         pcap_if_t *d; int i;
  86.         for(d=alldevs,i=0; d!=NULL; d=d->next){
  87.                 printf("%d-th dev: %s ", ++i, d->name);
  88.                 if (d->description)
  89.                         printf(" (%s)\n", d->description);
  90.                 else
  91.                         printf(" (No description available)\n");
  92.         }
  93.  
  94.         int inum;
  95.  
  96.         printf("enter the interface number: ");
  97.     scanf("%d", &inum);
  98.     for(d=alldevs, i=0; i<inum-1; d=d->next, i++); // jump to the i-th dev
  99.  
  100.     // open
  101.     pcap_t  *fp;
  102.     if ((fp = pcap_open_live(d->name,      // name of the device
  103.                65536,                   // capture size
  104.                1,  // promiscuous mode
  105.                20,                    // read timeout
  106.                errbuf
  107.                ))==NULL){
  108.         printf("pcap open failed\n");
  109.         pcap_freealldevs(alldevs);
  110.         return -1;
  111.         }
  112.  
  113.         printf("pcap open successful\n");
  114.  
  115.         struct bpf_program  fcode;
  116.     if (pcap_compile(fp,  // pcap handle
  117.                 &fcode,  // compiled rule
  118.                 FILTER_RULE,  // filter rule
  119.                 1,            // optimize
  120.                 NULL) < 0){
  121.         printf("pcap compile failed\n");
  122.         pcap_freealldevs(alldevs);
  123.         return -1;
  124.     }
  125.     if (pcap_setfilter(fp, &fcode) <0 ){
  126.         printf("pcap compile failed\n");
  127.         pcap_freealldevs(alldevs);
  128.         return -1;
  129.     }
  130.  
  131.         pcap_freealldevs(alldevs); // we don't need this anymore
  132.  
  133.         struct pcap_pkthdr *header;
  134.        
  135.         const unsigned char *pkt_data;
  136.         int res;
  137.        
  138.                 while((res=pcap_next_ex(fp, &header,&pkt_data))>=0){
  139.                         if (res==0) continue;
  140.  
  141.                 print_ether_header(pkt_data);
  142.                 pkt_data = pkt_data + 14;       // raw_pkt_data의 14번지까지 이더넷
  143.                 offset = print_ip_header(pkt_data);
  144.                 pkt_data = pkt_data + offset;           // ip_header의 길이만큼 오프셋
  145.                 offset = print_tcp_header(pkt_data);
  146.                 pkt_data = pkt_data + offset;           //print_tcp_header *4 데이터 위치로 오프셋
  147.                 print_data(pkt_data);
  148.                 }
  149.  
  150.  
  151.         return 0;
  152.  
  153. }
  154.  
  155. void print_ether_header(const unsigned char *data)
  156. {
  157.         struct  ether_header *eh;               // 이더넷 헤더 구조체
  158.         unsigned short ether_type;                     
  159.         eh = (struct ether_header *)data;       // 받아온 로우 데이터를 이더넷 헤더구조체 형태로 사용
  160.         ether_type=ntohs(eh->ether_type);       // 숫자는 네트워크 바이트 순서에서 호스트 바이트 순서로 바꿔야함
  161.        
  162.         if (ether_type!=0x0800)
  163.         {
  164.                 printf("ether type wrong\n");
  165.                 return ;
  166.         }
  167.         // 이더넷 헤더 출력
  168.         printf("\n============ETHERNET HEADER==========\n");
  169.         printf("Dst MAC Addr [%02x:%02x:%02x:%02x:%02x:%02x]\n", // 6 byte for dest
  170.                     eh->ether_dhost.ether_addr_octet[0],
  171.                     eh->ether_dhost.ether_addr_octet[1],
  172.                     eh->ether_dhost.ether_addr_octet[2],
  173.                     eh->ether_dhost.ether_addr_octet[3],
  174.                     eh->ether_dhost.ether_addr_octet[4],
  175.                     eh->ether_dhost.ether_addr_octet[5]);
  176.         printf("Src MAC Addr [%02x:%02x:%02x:%02x:%02x:%02x]\n", // 6 byte for src
  177.                     eh->ether_shost.ether_addr_octet[0],
  178.                     eh->ether_shost.ether_addr_octet[1],
  179.                     eh->ether_shost.ether_addr_octet[2],
  180.                     eh->ether_shost.ether_addr_octet[3],
  181.                     eh->ether_shost.ether_addr_octet[4],
  182.                     eh->ether_shost.ether_addr_octet[5]);
  183. }
  184.  
  185. int print_ip_header(const unsigned char *data)
  186. {
  187.         struct  ip_header *ih;         
  188.         ih = (struct ip_header *)data;  // 마찬가지로 ip_header의 구조체 형태로 변환
  189.  
  190.         printf("\n============IP HEADER============\n");
  191.         printf("IPv%d ver \n", ih->ip_version);
  192.         // Total packet length (Headers + data)
  193.         printf("Packet Length : %d\n", ntohs(ih->ip_total_length)+14);
  194.         printf("TTL : %d\n", ih->ip_ttl);
  195.         if(ih->ip_protocol == 0x06)
  196.         {
  197.                 printf("Protocol : TCP\n");
  198.         }
  199.         printf("Src IP Addr : %s\n", inet_ntoa(ih->ip_srcaddr) );
  200.         printf("Dst IP Addr : %s\n", inet_ntoa(ih->ip_destaddr) );
  201.        
  202.         // return to ip header size
  203.         return ih->ip_header_len*4;
  204. }
  205.  
  206. int print_tcp_header(const unsigned char *data)
  207. {
  208.         struct  tcp_header *th;
  209.         th = (struct tcp_header *)data;
  210.  
  211.         printf("\n============TCP HEADER============\n");
  212.         printf("Src Port Num : %d\n", ntohs(th->source_port) );
  213.         printf("Dest Port Num : %d\n", ntohs(th->dest_port) );
  214.         printf("Flag :");
  215.         if(ntohs(th->cwr))
  216.         {
  217.                 printf(" CWR ");
  218.         }
  219.         if(ntohs(th->ecn))
  220.         {
  221.                 printf(" ENC ");
  222.         }
  223.         if(ntohs(th->urg))
  224.         {
  225.                 printf(" URG ");
  226.         }
  227.         if(ntohs(th->ack))
  228.         {
  229.                 printf(" ACK ");
  230.         }
  231.         if(ntohs(th->psh))
  232.         {
  233.                 printf(" PUSH ");
  234.         }
  235.         if(ntohs(th->rst))
  236.         {
  237.                 printf(" RST ");
  238.         }
  239.         if(ntohs(th->syn))
  240.         {
  241.                 printf(" SYN ");
  242.         }
  243.         if(ntohs(th->fin))
  244.         {
  245.                 printf(" FIN ");
  246.         }
  247.        
  248.         printf("\n");
  249.  
  250.         // return to tcp header size
  251.         return th->data_offset*4;
  252. }
  253.  
  254. void print_data(const unsigned char *data)
  255. {
  256.         printf("\n============DATA============\n");
  257.         printf("%s\n", data);
  258. }


2. TCP 패킷 분석하기

 서버(165.246.12.215)에서는 nc로 7778번을 열어 listening 하였고
클라이언트(192.168.0.9)에서는 nc로 7778번 포트에 접속하여 Hi there!!를 보내고 클라이언트에서 연결을 끊는과정을 감청해 보았다.

 아래는 TCP 방식이 연결을 하고 데이터를 보내고받고 연결을 끊을때의 과정이다.
서버의 받는 포트번호는 7778번으로 고정인데 클라이언트 포트번호는 유동적일수 있다.

1) 연결할때 과정

-  SYN

위의 화면은 클라이언트가 서버에 접속 요청을 했을 때 날아간 패킷이다

주요 패킷 정보를 판별할 수 있고 SYN 플래그를 통해 접속 요청을 확인할 수있다.


- ACK/SYN

위의 화면은 서버로부터 접속 승낙을 받았을때  패킷이다.


- ACK

클라이언트도 ACK를 보냈다. 여기까지의 과정이 Three way handshake 이다.

이를 통해 connect가 성공적으로 이루어졌음을 확인할수 있다.


2) 데이터 전송

 -ACK/PUSH

클라이언트에서 데이터를 보낸 패킷 내용

3) 접속 종료할떄 과정

- ACK/FIN

데이터를 보내고 클라이언트에서 소켓을 닫게 된다. ACK 플래그와 함께 FIN 플래그를 클라이언트가 보내준다.

(클라이언트가 접속을 끊으므로 FIN을 날렸다.)


- ACK

서버도 FIN 플래그에 동의한다고 ACK를 보내준다.

- ACK/FIN


이제는 서버도 FIN을 날려줘서 연결을 끊자고 패킷을 보냄

- ACK

마지막으로 클라이언트도 ACK를 날리고 연결을 끊는다.

* 관련 링크참조

http://kaspyx.tistory.com/entry/WinPcap-개발-환경-구축하기

신고
Posted by 캐스피


티스토리 툴바