본문 바로가기

IT/WinpcapProgramming

Winpcap 프로그래밍 - 패킷 스니핑 (감청/분석) 하기

/* 
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-개발-환경-구축하기