본문 바로가기

C언어/프로젝트

리눅스 pcap 활용한 패킷 아날라이져

pcap 사용법
#include <stdio.h>
#include <pcap/pcap.h>

int main()
{
  char *NIC;
  char errbuf[PCAP_ERRBUF_SIZE];
  
  NIC = pcap_lookupdev(errbuf);
  if(0 != NIC)
  {
    printf("NETWORK CADR : [%s]\n", NIC);
  }
  else
  {
    printf("ERROR        : [%s]\n", errbuf);
  }

  return 0;
}

pcap 라이브러리 사용하기 위해서는 컴파일 할때 마지막에 -lpcap를 적어주어야 한다. 
패킷 아날라이져 소스
#include <stdio.h>
#include <stdlib.h>
#include <pcap/pcap.h>

void PrintHexaNAscii(const unsigned char *buffer, int size);

int const PROMISC = 1;
int const TIME_DELAY = 0;
int const READ_PACKET_Length = 1500;

int main()
{
  char *IName;
  const unsigned char *RBuf;
  char EBuf[PCAP_ERRBUF_SIZE];
  struct pcap_pkthdr CInfo;
  int counter = 0;
  pcap_t *CD;

  
  write(1"\033[1;1H\033[2J"10);  // 화면 클리어

  IName = pcap_lookupdev(EBuf); // 장치 이름 확인
  if(NULL == IName)
  {
    perror(EBuf);
    exit(100);
  }
  else
  {
    printf("### Interface              : [%s]\n", IName);
  }

  CD = pcap_open_live(IName, READ_PACKET_Length, PROMISC, TIME_DELAY, EBuf);
  // 장치 열기(device, 패킷최대길이, 수신모드, 대기시간, 에러)
  if(NULL == CD)
  {
    perror(EBuf);
    exit(200);
  }
  else
  {  
    printf("### Max Read Packet Length : [%d]Bytes\n", READ_PACKET_Length);
    printf("### Promiscous Mode        : [%s]\n", PROMISC ?  "YES" : "NO");
    printf("### Capture Time Delay     : [%d]Sec\n", TIME_DELAY);
  }

  while(1)
  {
    if(100 <= counter)
    {
      break;
    }
    else
    {
      ++counter;
    }

    RBuf = pcap_next(CD, &CInfo);  // 패킷 정보를 갖고있음(장치, 구조체주소)
    printf("### Captured Packet Length = [%d]\n", CInfo.caplen);

    PrintHexaNAscii(RBuf, CInfo.caplen); // 실제 패킷의 길이
  }
  pcap_close(CD);
  
  return 0;
}

void PrintHexaNAscii(const unsigned char *buffer, int size)
{
  int count1;
  int count2;
  
  const unsigned char *temp=buffer;
  
  printf("Address\t");        
  for(count1=0; count1<16; count1++)
  {// 메뉴 구분
    printf("%02X ", count1);    
  }                  
  for(count1=0; count1<16; count1++)  
  {                  
    printf("%01X", count1);      
  }                  
  printf("\n");            

  for(count1=0; count1<=size; count1+=16)
  {// 메뉴 주소
    printf("0x%04X\t", count1);
    for(count2=0; count2<16; count2++)
    {// 번지 주소
      printf("%02X ", *buffer);
      buffer++;
    }
    
    for(count2=0; count2<16; count2++)
    {// 문자 판별65~90 97~122
      printf("%c", ((*temp>=48)&&(*temp<=57)||
              (*temp>=65)&&(*temp<=90)||
              (*temp>=97)&&(*temp<=122)? *temp : '.'));
      temp++;
    }
    printf("\n");
  }
  printf("\n");
  return;
}

 Ethernet Handle 의 패킷을 보면 LAN카드 번호가 나와 있는 것을 확인 할수 있다. 뒤에 보라색 네모칸안의 45에서 4는 IPv4를 뜻하고 5는 5*4Byte로 20Byte를 갖는다는 것을 알수 있다.
컴파일을 할때 라이브러리 사용을 하기때문에 역시 -lpcap를 붙여주어야 제대로 된다.
(gcc -o packet main.c -lpcap)
char pcap_lookupdev(char *errbuf);
리눅스 머신의 네트웍 디바이스를 가져오는 함수. 
가능한 다비이스중 가장 번호가 낮은 디바이스를 가져옴. 
eth0 <- 으로 표시됨 만약 여러게인경우 숫자만 바뀜 eth1, eth2 ....
i 옵션으로 디바이스를 수동으로 지정할 수 있다.
pcap_t pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
	
위 함수는 실제 기기를 열어주는 기능을 하는 것으로 snaplen는 패킷당 저장할
바이스 수(1500), 실제 datalink계층부터 패킷의 크기를 계산하여 원하는 부분만을
얻어오면 되는 것입니다. 헤더정보만을 보고싶은데 쓸데없이 데이타까지 받을
필요는 없겠죠. 데이터까지 보고싶으면 이를 늘리면 됩니다. 
PROMISCUOUS는 1이며 네트웍 디바이스에 오는 모든 패킷을 받겠다는
의미입니다. 이 모드를 자세하게 설명하면 lan은 모든 패킷이 broadcasting되며
일단 모든 네트웍 디바이스는 동일 네트웍내의 다른 호스트의 패킷도 일단 접하게
됩니다.  그러나, 네트웍 디바이스는 기본적으로 자신의 패킷만을
받게끔 되어있습니다. 그러므로 다른 호스트의 패킷은 버리게 되는 것입니다.
그러나 promiscuous모드로 디바이스 모드를 바꾸게 되면 모든 패킷을 받아들이게
되는 것입니다. 스니퍼링 프로그램은 모두 이 모드를 사용하게 됩니다.
세 번째 인자는 패킷이 버퍼로 전달될 때 바로 전달되는 것이 아니라
위에서 명시한 시간을 넘겼을 때나 버퍼가 다 채워졌을 때 응용프로그램으로
전달되는 것입니다. 

u_char pcap_next(pcap_t *p, struct pcap_pkthdr *h)
 이 함수는 패킷의 정보를 갖고 있습니다. 반환형이 unsigned char형이고
첫 번째 인자는 열어둔 장치를 적어주면되고, 두 번째 인자는 구조체의 주소를
넘겨 주면 됩니다.