일요일, 12월 22
Shadow

#001 libpcap 을 통한 Ethernet frame capture, 패킷 분석

– 연결된 장치 검색
char * pcap_lookupdev(char * errbuf);
– Open Device
pcap_t * pcap_open_live(char * device, int snaplen, int promisc, int to_ms, char * ebuf);
– 열린 장치로 부터 패킷 Listening
char * pcap_next(pcap_t * p,struct pcap_pkthdr * h)
– 이더넷 구조체를 이용해서 이더넷 정보를 읽어 낸다.
struct ether_header * header;
– 장치 ID를 사용해서 열린 장치의 닫는다.
pcap_close(pcap_t * p); 

pcap_next()를 통해 패킷을 캡쳐함. header의 마지막 값인 ether_type을 통해 다음에 있을 프로토콜의 타입을 알수 있다.

struct ether_header
{

u_int8_t  ether_dhost[ETH_ALEN]; //Destination, ETH_ALEN = 6

u_int8_t  ether_shost[ETH_ALEN]; //Source,

u_int16_6 ether_type;            //Protocol ID

}

프로토콜의 타입은 /usr/include/net/ethernet.h 에  이더넷 구조체와 함께 정의 되어 있으며 다음과 같다.

ETHERNET_PUP     0x0200   /*Xerox PUP*/

ETHERNET_IP      0x0800   /*IP*/

ETHERNET_ARP     0x0806   /*Address resolution*/

ETHERNET_REVARP  0x8035   /*Reverse ARP*/

이더넷 구조체를 header라 가정 하면 header->ether_type의 값과 위의 값을 비교 해서 프로토콜의 타입을 알아 낼수 있다. 여기서 주의 할점은 header->ether_type의 값은 네트워크 바이트인 빅인디안 방식이기 때문에 리틀엔디안 방식을 사용하는 인텔 CUP에서는 리틀 엔디안 방식으로 변형후 비교를 해야한다. (ntohs() 함수 사용)

Ex)
switch( ntohs( header->ether_type ) )

{

case ETHERNET_PUP:

break;

case ETHERNET_IP:

break;

중략

}

– 아이피 헤더 정보 분석

이더넷 헤더의 프로토콜 타입이 IP 인 경우라면 패킷의 이더넷 다음부분에는 IP 헤더정보가 오게 된다.IP 헤더는 이더넷과 마찬가지로 헤더 구조체가 존재한다. 구조체와 프로토콜 타입에 대한 정의는 /usr/include/netinet/in.h 에 프로토콜에 타입에 대한 정의가 되어 있으며 /usr/include/netinet/ip.h 에 IP구조체가 정의 되어 있다. ip 구조체의 내용은 다음과 같으며 리틀엔디안과 빅 엔디안의 구조체 맴버가 다르게 설정 되어 있다는 것을 볼수 있다.

struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ip_hl:4;    /* header length */
unsigned int ip_v:4;    /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
unsigned int ip_v:4;    /* version */
unsigned int ip_hl:4;    /* header length */
#endif
u_int8_t ip_tos;      /* type of service */
u_short ip_len;      /* total length */
u_short ip_id;      /* identification */
u_short ip_off;      /* fragment offset field */
#define  IP_RF 0x8000      /* reserved fragment flag */
#define  IP_DF 0x4000      /* dont fragment flag */
#define  IP_MF 0x2000      /* more fragments flag */
#define  IP_OFFMASK 0x1fff    /* mask for fragmenting bits */
u_int8_t ip_ttl;      /* time to live */
u_int8_t ip_p;      /* protocol */
u_short ip_sum;      /* checksum */
struct in_addr ip_src, ip_dst;  /* source and dest address */
};

읽어 들이 패킷정보를 IP구조체가 가리키게 하기 위해선 이더넷 헤더 크기만큼을 이동 해야 한다. 사용예는 다음과 같다.

pcap_next를 통해 구한 패킷의 시작 주소에서 이더넷 해더 만큼을 이동한다.

struct ip * IPheader;

IPheader = (IP*)(date+sizeof(struct ether_header));

이후부터 IPheader 의 맴버를 통해 IP헤더의 내용을 구한다.

IP헤더는 다음과 같은 내용으로 구성 되어 있다.

VER(버전)                  구조체맴버 =>    IPheader->ip_v

버전을 나타내는 필드로서 IPv4 패킷일 경우에는 4가 들어가고 IPv6일 경우에는 6이 들어가게 된다.

HLEN (헤더 길이)          구조체맴버 =>    IPheader->ip_hl

IP헤더 길이를 나타내는 필드며 길이는 보통 옵션의 유무에 따라서 20~60바이트 까지 가변적이다.
이러한 헤더의 길이는 워드 단위로(4바이트) 나누어 나타낸다.
Ex) 헤더의 길이가 20바이트라면 20/4 = 5 이므로 HLEN 의 값은 5가 된다.

Total length(전체 길이)   구조체맴버 =>    IPheader->ip_len

헤더와 데이터 길이를 포함한 전체 길이를 바이트 단위로 나타낸다. 16비트 크기이기 때문에 65535바이트 까지 표현할 수 있지만 네트워크의 물리적 NIC에 의해 제한 받는다. 일반적으로 사용하는 이더넷은 1500 바이트가 최대 이며 46바이트가 최소이다. 참고로 물리 매체에 따라 최대로 보낼수 있는 데이터그램 크기를 MTU(Maximum Transfer Unit)라 부르는데 각 물리 매체에 따른 MTU는 아래와 같다.

네트워크                      MTU

hyperchannel                  65535

16Mbps token ring(IBM)        17914

4Mbps token ring(IBM)          464

ATM                           9180

FDDI                          4352

Ethernet                      1500

X.25                           576

Identification(식별자)        구조체맴버 =>    IPheader->ip_id

Identification필드는 패킷의 유일한 식별자로서 각각의 IP패킷을 유일하게 구분해 준다. 특히 패킷이 단편화 되었을때 사용되는 부분으로써 어떤 원본의 일부임을 나타내는지를 알리는 부분이기도 하다. 단편화란 물리 매체마다 전송 패킷의 길이가 달라 해당 매체에 맞게 자르는 것을 말하는데 다음에 있을 Flags비트를 이용해서 이 단편화를 하지 못하게 설정할수 있다. 이렇게 설정되어 단편화 되지 못하게 한다면 이패킷은 그곳에서 폐기되며 폐기되었다는 정보를 출발지로 보내게 된다.

Flags(플래그)        구조체맴버 =>    IPheader->ip_off

IPheader->ip_off의 상위 3비트가 Flags 비트이며 3비트중 제일 처음오는 비트는 사용되지 않으며 두번째 오는 비트(Do not fragment)가 1이라면 해당 패킷에 대해서는 단편화할 수 없다는 표시다. 마지막 비트는 More fragment 비트로 이비트가 1로 설정되었다면 패킷이 단편화 되었는데 아직 끝이 아니라는 의미다.

Fragmentation offset(단편화 오프셋)      구조체맴버 =>    IPheader->ip_off

IPheader->ip_off의 상위 3비트를 제외한 나머지 13비트가 단편화 오프셋에 해당된다.

단편화 오프셋은 패킷이 단편화 되었을 때 단편화 순서를 설정하는 부분이다. 1400바이트 짜리 패킷이 최대 데이타 크기 512바이트의 세 개의 패킷으로 단편화 되었다면 단편화 오프셋의 값은 데이터 시작부분의 값에 8을 나눈 값이 된다. 예를 들어 첫번째 단편화 패킷은 데이터의 0바이트 부터 시작하기 때문에 0/8 = 0의 결과에 따라 단편화 오프셋 필드의 값은 0이되고 두번째 패킷은 512 바이트 부터 시작하기 때문에 512/8 = 64의 결과에 따라 64가 된다.

Time to live(수명)           구조체맴버 =>    IPheader->ip_ttl

Time to live는 줄여서 TTL이라 부르는데 , TTL 값은 패킷이 네트워크 내에서 영원히 떠돌아 다니지 않도록 하기위해 패킷의 생존 기간을 설정하기 위한 필드이다. 처름 설정된 TTL값이 라우터또는 호스트를 하나씩 지나갈때마다 1씩 감소하여 0이되면 해당 패킷을 받은 라우터는 패킷을 버리고 발신지로 ICMP패킷을 보내준다.

Protocol(프로토콜)             구조체맴버 =>    IPheader->ip_p

IP패킷이 담고 있는 데이터가 어떤 프로토콜인지 나타내는 필드다. 해당 프로토콜 분석을 위해서는

/usr/include/netinet/in.h 내에 정의되어 있는 값을 비교 하면 된다.

Header checksum(헤더 체크섬)   구조체맴버 =>    IPheader->ip_sum

IP 패킷의 헤더가 정상적인지 검사하는 데 사용되는 체크섬 값이 설정된 필드다. 패킷을 받는 목적지 호스트는 체크섬을 확인하여 결과가 다르다면 패킷을 버린다.

Source IP Adderss와 Destination IP Address(발신지 IP와 목적지 IP)

구조체맴버 =>    IPheader->ip_src, IPheader->ip_dst

목적지와 출발지의 IP가 설정되어 있으며 헤더 분석을 위해 값을 보기 위해선 inet_ntoa(IPheader->ip_src)
를 사용해서 네트워크 바이트 오더인 아이피를 .표기 방식으로 바꾸어 문자로 변형해 준후 문자열로 출력하면 확인 할수 있다.

– 패킷 분석을 위한 구조체 사용

패킷의 내용을 분석 하기위해선 구조체의 맴버를 이용해야 하는데 패킷이 네트워크 바이트 오더인 빅엔디안 방식이기 때문에 호스트 오더형인 리틀엔디안으로 변형후 출력하거나 비교 해야 한다. 2바이트 형인 맴버들은 ntohs함수를 사용해 변형해 주어야 하며 IP같은 경우는 inet_ntoa함수를 사용해 변형해 주어야 한다.

변형해 주어야 하는 맴버로는 ip_src,ip_dstip_len,ip_id,ip_off,ip_sum 이 있다.

ip_off 같은 경우 상위 3비트와 하위 13비트를 분리해야 하므로 /usr/include/netinet/ip.h 에 정의 되어 있는 마스크 값을이용해서 &연산을 해서 원하는 값을 비교 할수 있다. 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.