4. 패킷 분석기 예광탄 - IPv4 프로토콜 스택 분석
안녕하세요. 언제나 휴일, 언휴예요.
지난 게시글에서는 pcap 포멧 파일을 로딩하여 ethernet 프로토콜 스택을 분석하는 예광탄을 만들어 보았어요.
더보기
이번에는 IPv4 프로토콜 스택 분석하는 부분을 작성할 거예요.
더보기
빨간색으로 테두리 안에 있는 내용이 이번에 추가한 기능에 의해 출력하는 부분이예요.
현재 작성하는 것이 예광탄이긴 하지만 이미 작성한 부분만 보더라도 헤더 파일없이 소스 코드에만 작성하는 것은 장기적으로 보았을 때 효율이 떨어질 것 같아요. 이번에는 이제까지 작성한 것을 다시 한 번 살펴보면서 헤더 파일과 소스 파일로 구분하기로 할게요. 물론 아직까지는 프로그램 설계는 생략하며 소스 파일을 분할하지는 않을 거예요.
먼저 예광탄에서 사용자 정의 형식 부분은 ehpacket.h 파일에 작성할게요.
헤더 파일 코드 보기 접기
#pragma once
#pragma warning ( disable :4996)
#include <WinSock2.h>
#pragma comment ( lib , "ws2_32" )
#pragma pack (1)
typedef unsigned short ushort ;
typedef unsigned char uchar ;
typedef unsigned int uint ;
// pcap fileheader
// _______________________________________________________________________
// | 4 | 2 | 2 | 4 |
// _______________________________________________________________________
// | magic(0xa1b2c3d4) | maj.ver | min.ver | gmt to localcorrection |
// _______________________________________________________________________
// | 4 | 4 | 4 |
// _______________________________________________________________________
// | 캡쳐한 시각 | snap 의 최대 길이 | datalink type |
// ________________________________________________________________________
typedef struct _pcap_file_header
{
#define MAGIC 0xa1b2c3d4 //pcap magic
int magic;
unsigned short version_major;
unsigned short version_minor;
int thiszone; /* gmt to localcorrection */
unsigned sigfigs; /* accuracy oftimestamps */
unsigned snaplen; /* maxlength savedportion of each pkt */
unsigned linktype; /* datalink type (LINKTYPE_*) */
} pcap_file_header ;
typedef struct _timeval
{
long tv_sec; /* seconds*/
long tv_usec; /*andmicroseconds */
} timeval ;
// pcapheader
// _______________________________________________________________________________
// | 8 | 4 | 4 |
// ______________________________________________________________________________
// | seconds(4) | micro seconds(4) | 캡쳐한 길이 | 패킷 길이 |
// _______________________________________________________________________________
typedef struct _pcap_header
{
timeval ts; /*time stamp */
unsigned caplen; /* length of portionpresent */
unsigned len; /*length thispacket (off wire) */
} pcap_header ;
// ethernet protocol stack
// ____________________________________________________________________________
// | 6 | 6 | 2 |
// ____________________________________________________________________________
// | dest mac address | src mac address | type |
// ____________________________________________________________________________
#define MAC_ADDR_LEN 6
typedef struct _ethernet
{
unsigned char dest_mac[ MAC_ADDR_LEN ];
unsigned char src_mac[ MAC_ADDR_LEN ];
unsigned short type;
} ethernet ;
// IPv4 protocol stack
// _____________________________________________________________________________________
// | 4 | 4 | 8 | 16 |
// _____________________________________________________________________________________
// | version | HLEN | service type | total length |
// _____________________________________________________________________________________
// | 16 | 3 | 13 |
// _____________________________________________________________________________________
// | identification | Flags | Fragment offset |
// _____________________________________________________________________________________
// | 8 | 8 | 16 |
// _____________________________________________________________________________________
// | Time To Live | Protocol | Header Checksum |
// ____________________________________________________________________________________
// | 32 |
// ____________________________________________________________________________________
// | Source IPv4 Address |
// ____________________________________________________________________________________
// | 32 |
// ____________________________________________________________________________________
// | Destination IPv4 Address |
// ____________________________________________________________________________________
// | 0~320(40 바이트) |
// ____________________________________________________________________________________
// | Options and Padding |
// ____________________________________________________________________________________
typedef struct _iphdr
{
uchar hlen : 4; //header length , 1 per 4bytes
uchar version : 4; //version , 4
uchar service;
ushort tlen; //total length
ushort id; //identification
ushort frag;
#define DONT_FRAG (frag) (frag & 0x40)
#define MORE_FRAG (frag) (frag & 0x20)
#define FRAG_OFFSET (frag) (ntohs(frag) & (~0x6000))
uchar ttl; //time to live
uchar protocol;
ushort checksum;
uint src_address;
uint dst_address;
} iphdr ;
접기
이번에 추가한 부분은 IPv4 헤더 부분이예요.
typedef struct _iphdr
{
버전 정보와 헤더 길이가 4비트씩으로 구성하고 있으며 host byte order가 little endian이라는 가정에서 구조체에 두 개의 멤버의 순서를 바꾸었어요.
uchar hlen : 4; //header length , 1 per 4bytes
uchar version : 4; //version , 4
그리고 TOS(Type of Service)라 불렀던 Service 멤버와 전체 길이와 패킷을 구분하기 위한 ID 멤버가 있습니다.
uchar service;
ushort tlen; //total length
ushort id; //identification
이어서 단편화 패킷인지 구분하고 단편화 패킷일 때 단편화 offsets인 멤버가 있어요.
ushort frag;
#define DONT_FRAG (frag) (frag & 0x40)
#define MORE_FRAG (frag) (frag & 0x20)
#define FRAG_OFFSET (frag) (ntohs(frag) & (~0x6000))
그리고 경유할 수 있는 최대 라우터 수를 의미하는 TTL과 프로토콜(ICMP, IGMP, TCP, UDP, OSPF)와 체크섬 이 있죠.
uchar ttl; //time to live
uchar protocol;
ushort checksum;
마지막으로 발신지 주소와 목적지 주소가 있습니다.
uint src_address;
uint dst_address;
} iphdr ;
이제 소스 코드를 살펴보아요.
소스 파일 코드 보기 접기
// 패킷 분석기 예광탄 - IPv4 프로토콜 스택 분석
// 언제나 휴일, http://ehclub.co.kr
// 코드 변경없이 사용 및 배포할 수 있습니다.
#include "ehpacket.h"
#include <stdio.h>
#define MAX_PACKET 10
pcap_header headers[ MAX_PACKET ]; // 패킷 헤더들을 보관할배열
int pcnt; // 패킷 개수
int Parsing( FILE *fp);
int main()
{
char fname[256];
FILE *fp = 0;
printf( " 파일 명:" );
gets_s(fname, sizeof (fname));
fopen_s(&fp, fname, "rb" ); // 전달받은 파일을 읽기/ 바이너리모드로 열기
Parsing(fp); // 분석하기
fclose(fp); // 파일 닫기
return 0;
}
void ParsingEthernet( FILE *fp);
int Parsing( FILE * fp )
{
pcap_file_header pfh;
fread(&pfh, sizeof (pfh), 1, fp ); //pcap 파일 헤더 읽기
if (pfh.magic != MAGIC ) // 매직이 다르면
{
printf( "this file format is not correct \n" );
return -1;
}
printf( "version:%d.%d\n" , pfh.version_major, pfh.version_minor); //pcap 헤더 정보 출력
switch (pfh.linktype) // 링크 타입에 따라
{
case 1:ParsingEthernet( fp ); break ; //Ethernet 방식으로 분석
case 6:printf( "Not support Token Ring\n" ); break ;
case 10:printf( "Not support FDDI\n" ); break ;
case 0:printf( "Not support Loopback\n" ); break ;
default :printf( "Unknown\n" ); break ;
}
return 0;
}
void ViewPacketHeader( pcap_header *ph);
void ViewEthernet( char *buf);
void ParsingEthernet( FILE * fp )
{
char buf[65536];
pcap_header *ph = headers; //ph 를 패킷 헤더의 시작 위치로 초기화
int i = 0;
while (feof( fp ) == 0) // 파일의 끝이 아니면 반복
{
if (fread(ph, sizeof ( pcap_header ), 1, fp ) != 1) // 패킷 헤더 읽기를 실패하면
{
break ; // 루프 탈출
}
if (pcnt == MAX_PACKET ) // 예광탄에서 분석할 수 있게 설정한 패킷 수에 도달
{
break ; // 루프 탈출
}
ViewPacketHeader(ph); // 패킷 헤더 정보 출력
fread(buf, 1, ph->caplen, fp ); // 패킷 읽기
ViewEthernet(buf); // 이더넷 정보 출력
ph++; //ph 를 다음 위치로 이동
}
}
void ViewPacketHeader( pcap_header * ph )
{
pcnt++; // 패킷 개수를 1 증가
printf( "\nNo:%dtime:%08d:%06d caplen:%u length:%u \n" ,
pcnt, ph ->ts.tv_sec, ph ->ts.tv_usec, ph ->caplen, ph ->len);
}
void ViewMac( unsigned char *mac);
void ViewIP( char *buf);
void ViewARP( char *buf);
void ViewEthernet( char * buf )
{
ethernet *ph = ( ethernet *) buf ; // 패킷 버퍼를 ethernet 구조체 포인터로 형 변환
printf( "===========ETHERNET Header==============\n" );
printf( "dest mac:0x" );
ViewMac(ph->dest_mac); //MAC 주소 출력
printf( " src mac:0x" );
ViewMac(ph->src_mac); //MAC 주소 출력
printf( " type:%#x\n" , ntohs(ph->type));
//Link 타입을 출력(2 바이트 이상데이터는 network byte order -> host byte order 로 변환해야 함
switch (ntohs(ph->type))
{
case 0x800:ViewIP( buf + sizeof ( ethernet )); /*IP 정보 출력 */ break ;
case 0x806:ViewARP( buf + sizeof ( ethernet )); /*ARP 정보 출력*/ break ;
default :printf( "Not support Protocol\n" ); break ;
}
}
void ViewMac( unsigned char * mac )
{
int i;
for (i = 0;i < MAC_ADDR_LEN ; ++i)
{
printf( "%02x " , mac [i]);
}
}
ushort ip_checksum( ushort *base, int len);
void ViewIP( char * buf )
{
IN_ADDR addr;
iphdr *ip = ( iphdr *) buf ; // 패킷 버퍼를 ethernet 구조체 포인터로 형변환
printf( "\n=========== IPv4 Header ==============\n" );
addr. s_addr = ip->src_address;
printf( "src:%s, " , inet_ntoa(addr));
addr. s_addr = ip->dst_address;
printf( "dst:%s\n" , inet_ntoa(addr));
printf( "header length:%d bytes, " , ip->hlen * 4);
printf( "version:%d, " , ip->version);
printf( "total length:%d bytes\n" , ntohs(ip->tlen));
printf( "id:%d, " , ntohs(ip->id));
if ( DONT_FRAG (ip->frag))
{
printf( "Don't Fragment\n" );
}
else
{
if ( MORE_FRAG (ip->frag) == 0)
{
printf( "last fragment, " );
}
printf( "offset:%d " , FRAG_OFFSET (ip->frag));
}
if (ip_checksum(( ushort *) buf , sizeof ( iphdr )) == 0)
{
printf( "checksum is correct, " );
}
else
{
printf( "checksum is not correct, " );
}
printf( "TTL:%d\n" , ip->ttl);
switch (ip->protocol)
{
case 1: printf( "ICMP\n" ); break ;
case 2: printf( "IGMP\n" ); break ;
case 6: printf( "TCP\n" ); break ;
case 17: printf( "UDP\n" ); break ;
case 89: printf( "OSPF\n" ); break ;
default : printf( "Not support\n" ); break ;
}
}
void ViewARP( char * buf )
{
//to be defined
}
ushort ip_checksum( ushort * base , int len )
{
int nleft = len ;
int sum = 0;
u_short *w = base ;
u_short answer = 0;
while (nleft>1)
{
sum += *w;
w++;
nleft -= 2;
}
if (nleft == 1)
{
*( ushort *)(&answer) = *( uchar *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
접기
IPv4 프로토콜을 분석하여 출력하는 ViewIP 부분을 추가합시다.
IPv4 프로토콜 스택에서는 2바이트 단위 영역들의 체크섬을 통해 유효한 패킷임을 증명하는 필드가 있어요. 이를 위한 함수를 만들기로 해요.
ushort ip_checksum( ushort *base, int len);
void ViewIP( char * buf )
{
IN_ADDR addr;
iphdr *ip = ( iphdr *) buf ; // 패킷 버퍼를 ethernet 구조체 포인터로 형변환
printf( "\n=========== IPv4 Header ==============\n" );
먼저 발신지와 목적지 IPv4 주소를 출력하세요.
addr. s_addr = ip->src_address;
printf( "src:%s, " , inet_ntoa(addr));
addr. s_addr = ip->dst_address;
printf( "dst:%s\n" , inet_ntoa(addr));
헤더 길이는 단위가 4바이트입니다. 헤더 길이 필드의 값에 4를 곱하여 바이트 단위로 변환하여 출력합시다.
printf( "header length:%d bytes, " , ip->hlen * 4);
버전 정보, 전체 길이, 패킷 id를 출력하세요.
printf( "version:%d, " , ip->version);
printf( "total length:%d bytes\n" , ntohs(ip->tlen));
printf( "id:%d, " , ntohs(ip->id));
단편화 정보는 먼저 단편화 하지 말아야 한다는 DF 값을 먼저 확인하여 출력합시다.
if ( DONT_FRAG (ip->frag))
{
printf( "Don't Fragment\n" );
}
else
{
DF 값이 1일 때는 MF 값으로 같은 패킷 ID의 마지막 패킷인지 출력하세요. MF 값이 1이면 마지막 조각이 아니라는 의미이며 0이면 마지막 조각이라는 의미예요. 물론 단편화하지 않은 패킷도 MF값은 0입니다.
if ( MORE_FRAG (ip->frag) == 0)
{
printf( "last fragment, " );
}
단편화 offset도 출력하세요.
printf( "offset:%d " , FRAG_OFFSET (ip->frag));
}
체크섬을 계산하여 체크섬이 정확한지 오류가 있는지 출력하세요.
if (ip_checksum(( ushort *) buf , sizeof ( iphdr )) == 0)
{
printf( "checksum is correct, " );
}
else
{
printf( "checksum is not correct, " );
}
ttl값과 프로토콜을 출력하세요.
printf( "TTL:%d\n" , ip->ttl);
switch (ip->protocol)
{
case 1: printf( "ICMP\n" ); break ;
case 2: printf( "IGMP\n" ); break ;
case 6: printf( "TCP\n" ); break ;
case 17: printf( "UDP\n" ); break ;
case 89: printf( "OSPF\n" ); break ;
default : printf( "Not support\n" ); break ;
}
}
void ViewARP( char * buf )
{
//to be defined
}
체크섬 계산은 다음과 같습니다.
ushort ip_checksum( ushort * base , int len )
{
int nleft = len ;
int sum = 0;
u_short *w = base ;
u_short answer = 0;
while (nleft>1)
{
sum += *w;
w++;
nleft -= 2;
}
if (nleft == 1)
{
*( ushort *)(&answer) = *( uchar *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
이제 실행해 보시고 잘못 작성한 부분은 수정하세요.
그럼 다음에 봐요.
모두 행복한 하루~
실습에 사용할 pcap 파일(다른 pcap 파일을 사용하셔도 무관합니다.)
demo.pcap
헤더 파일 및 소스 파일
ehpacket.h
Program.c