5. 패킷 분석기 예광탄 - ARP 프로토콜 스택 분석
안녕하세요. 언제나 휴일, 언휴예요.
이전 게시글에서는 pcap 파일의 IPv4 프로토콜 스택 분석하는 부분을 추가했어요. 현재까지 PCAP 파일 포멧 분석 및 ethernet 프로토콜 스택과 IPv4 프로토콜 분석하였습니다.
이번에는 ARP 프로토콜 스택 헤더를 분석하여 콘솔 화면에 출력하는 부분을 추가합시다.
빨간색 테두리 안에 출력 내용이 ARP 프로토콜 스택 정보입니다.
먼저 ARP 프로토콜 스택 헤더를 구조체로 정의하는 부분을 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;
// ARP
// ____________________________________________________________________________
// | Hardware Type (16) | Protocol Type (16) |
// ____________________________________________________________________________
// | H/W length(8) | Pro length(8) | OP code (16) |
// ____________________________________________________________________________
// | Sender H/W Address (가변 , Ethernet 48bits, 6Bytes) |
// ____________________________________________________________________________
// | Sender Protocol Address (가변 , IP 32bits, 4Bytes) |
// ____________________________________________________________________________
// | Target H/W Address (가변 , Ethernet 48bits, 6Bytes) |
// ____________________________________________________________________________
// | Target Protocol Address (가변 , IP 32bits, 4Bytes) |
// ____________________________________________________________________________
typedef struct _arphdr
{
ushort hwtype; //hardware type
ushort prototype;//protocol type
uchar hlen;//h/w length
uchar plen;//protocol length
ushort opcode; //operation code
uchar sender_mac[MAC_ADDR_LEN];
uint sender_address;
uchar target_mac[MAC_ADDR_LEN];
uint target_address;
}arphdr;
typedef struct _arphdr
{
하드웨어 타입(16비트)와 프로토콜 타입(16비트)와 하드웨어 길이(8비트), 프로토콜 길이(8비트), operation code(16비트)로 시작합니다.
ushort hwtype; //hardware type
ushort prototype;//protocol type
uchar hlen;//h/w length
uchar plen;//protocol length
ushort opcode; //operation code
송신자의 H/W 주소와 프로토콜 주소가 오는데 이 부분은 가변적인 길이를 갖습니다. 여기에서는 ethernet과 IPv4일 때에만 분석을 제공할 것이며 이 때는 ethernet 주소는 6바이트, IPv4 주소 길이 4바이트입니다.
uchar sender_mac[MAC_ADDR_LEN];
uint sender_address;
목적지의 H/W주소와 프로토콜 주소도 마찬가지입니다.
uchar target_mac[MAC_ADDR_LEN];
uint target_address;
}arphdr;
소스 파일에 ARP 프로토콜 스택 헤더를 분석하여 콘솔 화면에 출력하는 ViewARP 함수를 구현합시다.
//패킷 분석기 예광탄 - 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)
{
arphdr *arphd = (arphdr *)buf;
printf("\n=========== ARP Header ==============\n");
printf("h/w type: %u", ntohs(arphd->hwtype));
if (ntohs(arphd->hwtype) == 1)
{
printf(" Ethernet");
}
printf("\nprotocol type:%#x", ntohs(arphd->prototype));
if (ntohs(arphd->prototype) == 0x800)
{
IN_ADDR addr;
printf(" IP\n");
printf("\n opcode: %d ", ntohs(arphd->opcode));
printf("sender mac:0x");
ViewMac(arphd->sender_mac);
addr.s_addr = arphd->sender_address;
printf("\nsender ip: %s\n", inet_ntoa(addr));
printf("target mac:0x");
ViewMac(arphd->target_mac);
addr.s_addr = arphd->target_address;
printf("\ntarget ip: %s\n", inet_ntoa(addr));
}
}
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);
}
void ViewARP(char *buf)
{
arphdr *arphd = (arphdr *)buf;
printf("\n=========== ARP Header ==============\n");
먼저 하드웨어 타입을 출력합니다. network byte order를 host byte order로 변경하여 출력하세요.
printf("h/w type:
%u",
ntohs(arphd->hwtype));
ethernet은 하드웨어 타입이 1입니다.
if (ntohs(arphd->hwtype) == 1)
{
printf(" Ethernet");
}
프로토콜 타입을 출력합니다. byte order를 변환하여 출력하세요.
printf("\nprotocol
type:%#x",
ntohs(arphd->prototype));
IPv4는 프로토콜 타입이 0x800입니다.
if (ntohs(arphd->prototype) == 0x800)
{
IN_ADDR addr;
printf(" IP\n");
OP 코드를 출력하세요.
printf("\n opcode: %d
",
ntohs(arphd->opcode));
발신지 MAC 주소와 IPv4 주소를 출력하세요.
printf("sender
mac:0x");
ViewMac(arphd->sender_mac);
addr.s_addr =
arphd->sender_address;
printf("\nsender ip:
%s\n",
inet_ntoa(addr));
목적지 MAC 주소와 IPv4 주소를 출력하세요.
printf("target
mac:0x");
ViewMac(arphd->target_mac);
addr.s_addr =
arphd->target_address;
printf("\ntarget ip:
%s\n",
inet_ntoa(addr));
}
}
앞으로 남은 프로토콜 스택 헤더를 분석하는 것도 결국 이미 약속한 프로토콜 스택에 맞게 데이터 값을 확인하여 사용자가 보기 편하게 보여주는 작업이 기본입니다.
물론 네트워크 보안을 위해 이러한 기본적인 분석 이외에 수상한 트래픽을 빠르게 파악할 수 있게 효과적인 분석 기능을 추가하는 작업을 통해 기능 강화 및 품질 수준을 높여야겠죠.
다음에는 ICMPv4 프로토콜 스택 헤더를 분석하는 작업을 할게요. 그럼 다음에 봐요.
오늘도 모두 행복한 하루~
테스트에 사용한 PCAP 파일(ARP 트래픽이 있는 pcap 파일이면 다른 파일이어도 상관없습니다.)
demoarp.pcap
헤더 파일 및 소스 파일
ehpacket.h
Program.c