[C 프로젝트] 패킷 분석기 Part 2. PCAP 파일 구조 분석기

demo.pcap 파일 다운로드

 안녕하세요. 언제나 휴일, 언휴예요.

 이번 강의는 “패킷 분석기 Part 2. PCAP 파일 구조 분석”입니다.

  먼저 PCAP 파일 구조를 알아본 후에 구현하기로 할게요.

PCAP 파일 구조

 PCAP 파일은 TCPDump 프로그램으로 패킷을 캡쳐한 파일을 말합니다. 와이어 샤크에서도 PCAP 파일로 저장하기를 제공하고 있어요.

와이어 샤크에서 Save sas

와이어샤크에서 PCAP 파일로 저장

 다음은 PCAP 파일을 설명하기 위한 화면입니다.

PCAP 파일 분석

 PCAP 파일은 PCAP 파일 헤더와 Packet 목록으로 구성합니다.

PCAP 파일 헤더

 PCAP 파일의 시작 부분 24바이트가 파일 헤더입니다.

PCAP 파일 헤더

 PCAP 파일 헤더는 MAGIC(4바이트, A1B2C3D4), Major 버전(2바이트), Minor 버전(2바이트), Time Zone(4바이트), Time Stamps(4바이트), Snap Length(4바이트), Link Type(4바이트)로 총 24바이트입니다.

 MAGIC – PACP 파일을 나타내는 값으로 언제나 16진수로 A1B2C3D4 값을 가짐

 Major 버전, Minor 버전 – 패킷 캡쳐에 사용한 TcpDump 버전

 Time Zone – 캡쳐한 지역과 표준시와의 시간 차

 Time Stamp – 시간 스탬프

 Snap Length – 캡쳐할 때 설정한 하나의 패킷에서 캡쳐할 수 있는 최대 크기

 Link Type – Link 계층 유형(캡쳐한 장치 유형으로 ethernet은 장치는 1)

PACKET 헤더

 PCAP 파일 헤더 뒤에는 패킷 목록들이 옵니다. 그리고 각 패킷에는 패킷 헤더 정보와 캡쳐한 패킷(2계층 캡쳐 정보이기 때문에 프레임이 더 옳은 표현) 정보로 구성합니다.

PACKET 헤더

 PACKET 헤더는 Time Stamp(8바이트), Capture Length(4바이트), Packet Length(4바이트)로 총 16바이트입니다.

 Time Stamp – 패킷을 캡쳐한 타임 스템프로 초(4바이트)와 마이크로 초(4바이트)

 Capture Length – 실제 캡쳐한 길이

 Packet Length – 실제 패킷 길이

 네트워크 트래픽을 캡쳐할 때 캡쳐 길이의 상한을 설정하지 않았다면 Packet Length와 Capture Length는 같습니다.

PCAP 파일 분석기 구현

 이제 Visual Studio에서 PCAP 파일 분석기 프로그램을 작성해 봅시다. 이 강의에서는 C언어로 구현할게요. 

 다음은이번에 실습할  PCAP 파일 분석기를  실행하였을 때의  모습입니다.PCAP 파일 분석기 실행

 제일 먼저 PcapFile 헤더 파일(PcapFile.h)과 소스 파일(PcapFile.c)을 추가하세요.

PcapFile.h

 헤더 파일에 PCAP 헤더 구조체와 PACKET 헤더 구조체를 정의하세요.

 그리고 PCAP 파일을 분석하는 함수와 Ethernet 프레임을 분석하는 함수를 선언합시다.  

#pragma once
#include <stdio.h>
typedef unsigned int uint;
typedef unsigned short ushort;
typedef unsigned char uchar;

typedef struct _PFHeader PFHeader; 
struct _PFHeader //패킷 파일 헤더
{
	uint magic;//0xA1B2C3D4
	ushort major;
	ushort minor;
	uint gmt_to_local;
	uint timestamp;
	uint max_caplen;
	uint linktype;
};

typedef struct _PACKETHEADER PackHeader;
struct _PACKETHEADER //패킷 헤더
{
	uint captime;//second
	uint caputime;//u second
	uint caplen;
	uint packlen;
};

#define PF_MAGIC 0xA1B2C3D4
#define LT_ETHER   0x01
void ParseEther(FILE *fp);
int ParsePCapFile(FILE *fp, PFHeader *pfh);

main 함수 구현

 이번에는 main 함수를 구현합시다.

 먼저 분석할 PCAP 파일명을 입력받고 읽기 전용으로 파일 열기를 시도합니다.

int main()
{
	char fname[256 + 1] = "";
	printf("분석할 pcap 파일명:");
	scanf_s("%s", fname, sizeof(fname));

	FILE *fp = 0;
	fopen_s(&fp, fname, "rb");
	if (fp == 0)
	{
		perror("파일 열기 실패");
		system("pause");
		return 0;
	}
}

 PCAP 파일을 분석하는 함수를 호출합니다. 만약 PCAP 파일 포멧이 아니면 메시지를 출력한 후에 종료합니다.

	PFHeader pfh = { 0, };
	if (ParsePCapFile(fp, &pfh) == 0)
	{
		printf("PCAP 파일이 아닙니다\n");
		fclose(fp);
		system("pause");
		return 0;
	}

 PCAP 헤더의 linktype 값이 ethernet일 때는 Ethernet 프레임을 분석하는 함수를 호출하고 그 외의 값일 때는 지원하지 않음을 출력합니다. 

 그리고 열려져 있는 파일을 닫습니다.

	switch (pfh.linktype)
	{
	case LT_ETHER: ParseEther(fp); break;
	default: printf("Not Support\n"); break;
	}
	fclose(fp);

 다음은 Program.c 파일의 소스 코드입니다.

//https://ehpub.co.kr
//[언제나 프로젝트] C 패킷 분석기 Part 2. PCAP 파일 구조 분석하기 구현
#include <stdio.h>
#include <stdlib.h>
#include "PcapFile.h"
int main()
{
	char fname[256 + 1] = "";
	printf("분석할 pcap 파일명:");
	scanf_s("%s", fname, sizeof(fname));

	FILE *fp = 0;
	fopen_s(&fp, fname, "rb");
	if (fp == 0)
	{
		perror("파일 열기 실패");
		system("pause");
		return 0;
	}

	PFHeader pfh = { 0, };
	if (ParsePCapFile(fp, &pfh) == 0)
	{
		printf("PCAP 파일이 아닙니다\n");
		fclose(fp);
		system("pause");
		return 0;
	}

	switch (pfh.linktype)
	{
	case LT_ETHER: ParseEther(fp); break;
	default: printf("Not Support\n"); break;
	}
	fclose(fp);
	system("pause");
	return 0;
}

PcapFile.c 구현

 이제 PcapFile.c 파일을 구현합시다.

 ParsePcapFile에서는 PCAP 헤더 구조체 크기만큼 파일에서 읽어온 후에 MAGIC 값이 같은지 체크합니다.

 MAGIC값이 다르면 0을 반환합니다. MAGIC 값이 일치하면 PCAP 파일 정보를 출력합니다.

void ViewPCapFile(PFHeader *pfh);
int ParsePCapFile(FILE *fp, PFHeader *pfh)
{
	fread(pfh, sizeof(PFHeader), 1, fp);
	if (pfh->magic != PF_MAGIC)
	{
		return 0;
	}
	ViewPCapFile(pfh);
	return 1;
}
void ViewPCapFile(PFHeader *pfh)
{
	printf("=========== PCAP File 헤더 정보 ============\n");
	printf("\t 버전:%d.%d\n", pfh->major, pfh->minor);
	printf("\t최대 캡쳐 길이:%d bytes\n", pfh->max_caplen);
}

 Ehternet 프레임 정보를 분석하는 함수를 작성합시다.

 PCAP 파일 헤더 뒤에는 패킷 목록이 옵니다. 여기에서는 하나의 패킷 정보를 얻어와서 PACKET헤더 정보를  출력하는 것을 반복할 거예요.

 이를 위해서는 PACKET 헤더 정보를 먼저 읽고 난 후에 캡쳐한 길이만큼 다시 읽는 작업을 요구합니다. 물론, 읽어온 PACKET 헤더 정보는 출력해 주세요.

char buffer[0x100000];
void ViewPacketHeaderInfo(PackHeader *ph,int pno);

void ParseEther(FILE *fp)
{
	PackHeader ph = { 0 };
	int pno=0;
	while (fread(&ph,sizeof(PackHeader),1,fp)==1)
	{
		pno++;
		ViewPacketHeaderInfo(&ph, pno);
		fread(buffer, sizeof(uchar), ph.caplen, fp);
	}
}
void ViewPacketHeaderInfo(PackHeader *ph, int pno)
{
	printf("!!! <%4d th> 프레임 !!!\n", pno);
	printf("패킷:%6d bytes 캡쳐:%6d\n", ph->packlen, ph->caplen);
}

 다음은 PcapFile.c 파일의 소스 코드입니다.

#include "PcapFile.h"

char buffer[0x100000];
void ViewPCapFile(PFHeader *pfh);
void ViewPacketHeaderInfo(PackHeader *ph,int pno);

void ParseEther(FILE *fp)
{
	PackHeader ph = { 0 };
	int pno=0;
	while (fread(&ph,sizeof(PackHeader),1,fp)==1)
	{
		pno++;
		ViewPacketHeaderInfo(&ph, pno);
		fread(buffer, sizeof(uchar), ph.caplen, fp);
	}
}

int ParsePCapFile(FILE *fp, PFHeader *pfh)
{
	fread(pfh, sizeof(PFHeader), 1, fp);
	if (pfh->magic != PF_MAGIC)
	{
		return 0;
	}
	ViewPCapFile(pfh);
	return 1;
}
void ViewPCapFile(PFHeader *pfh)
{
	printf("=========== PCAP File 헤더 정보 ============\n");
	printf("\t 버전:%d.%d\n", pfh->major, pfh->minor);
	printf("\t최대 캡쳐 길이:%d bytes\n", pfh->max_caplen);
}
void ViewPacketHeaderInfo(PackHeader *ph, int pno)
{
	printf("!!! <%4d th> 프레임 !!!\n", pno);
	printf("패킷:%6d bytes 캡쳐:%6d\n", ph->packlen, ph->caplen);
}

 이상으로 패킷 분석기 Part2 강의를 마칠게요. 다음 강의는 “Ethernet 프로토콜 분석기”입니다.