2010년 10월 22일 금요일

TCP/IP 프로토콜 제작 / 파싱 및 Auditing 강좌 - 4

지난 번 3번째 강좌에서 해결 해 드리지 못했던 캡쳐파일을 프로그램에 사용하기 위한 코드부분을 표기 해 드리고자 합니다.

코드는 제멋대로 코드이므로 내용을 이해하는 데에 주력하여 주시기 바랍니다.

 

 

이번 강좌의 목적은 wireshark로 캡쳐한 파일을 프로그램에서 디버깅 목적으로 사용하기 함수를 작성하도록 하겠습니다.

단지 이 내용만 채우기에는 다소 황당함을 동반하기 때문에 왜 그렇게 코드를 작성해야 하는지에 대한 부분을 자세히 설명 드리고자 합니다.

 

함수 명은 void ReadDumpFile() 으로 하겠습니다.

 

먼저 코드를 보시기 전에 잠시 링크를 통해 웹페이지를 한 번 구경다녀 오시죠.

 

http://wiki.wireshark.org/Development/LibpcapFileFormat

 

이름만 보아도 대충 무슨 설명인지 감이 오시지 않나요?

바로 wireshark에서 캡쳐하는 libcap 파일에 대한 포맷을 설명하고 있는 문서입니다.

 

사실 전체를 보면 되겠지만, 그래도 영어에 심한 반감을 가지고 계신 분들을 위해서 필요한 내용을 추려서 올려드립니다.

 

저 문서에서 필요한 내용은 다음 두 구조체입니다.

 

·         typedef struct pcap_hdr_s {
·                 guint32 magic_number;   /* magic number */
·                 guint16 version_major;  /* major version number */
·                 guint16 version_minor;  /* minor version number */
·                 gint32  thiszone;       /* GMT to local correction */
·                 guint32 sigfigs;        /* accuracy of timestamps */
·                 guint32 snaplen;        /* max length of captured packets, in octets */
·                 guint32 network;        /* data link type */
·         } pcap_hdr_t;

 

 

·         typedef struct pcaprec_hdr_s {

·                 guint32 ts_sec;         /* timestamp seconds */

·                 guint32 ts_usec;        /* timestamp microseconds */

·                 guint32 incl_len;       /* number of octets of packet saved in file */

·                 guint32 orig_len;       /* actual length of packet */

·         } pcaprec_hdr_t;

 

실제 따져보면 앞의 구조체는 24바이트, 뒤의 구조체는 16바이트가 됩니다.

위에서 표기된 guint32, guint16 등은 비트를 표기함을 이해하시겠죠? 32비트면 4바이트가 되겠죠.그러니까 윈도우에서 활용하시려면 unsigned int, unsigned short 등으로 사용하면 되겠습니다. 줄여서 UINT, USHORT 등으로 표기합니다.

 

윈도우에서 사용되는 형식으로 변경 해 봅니다.

 

typedef struct pcap_hdr_s {

           UINT magic_number;   /* magic number */

           USHORT version_major;  /* major version number */

           USHORT version_minor;  /* minor version number */

           INT  thiszone;       /* GMT to local correction */

           UINT sigfigs;        /* accuracy of timestamps */

           UINT snaplen;        /* max length of captured packets, in octets */

           UINT network;        /* data link type */

} pcap_hdr_t;

ð  24바이트

 

typedef struct pcaprec_hdr_s {

           UINT ts_sec;         /* timestamp seconds */

           UINT ts_usec;        /* timestamp microseconds */

           UINT incl_len;       /* number of octets of packet saved in file */

           UINT orig_len;       /* actual length of packet */

} pcaprec_hdr_t;

ð  16바이트

 

달라지는 내용은 없습니다. 그냥 위 내용을 헤더에 그대로 넣어 사용하여 봅시다.

 

쉽게 예를 들기 위해서 TCP 패킷을 딱 하나만 잡아보시기 바랍니다. Wireshark의 사용법에 대해서는 3번째 강의를 참조하세요.

 

하나의 패킷을 잡아서 저장하신 후 그것을 그냥 VC6.0 화면에 로딩하시기 바랍니다. , 주의하실 것은 꼭 binary 형태로 여시기 바랍니다. 혹시 hex workshop 등의 헥사 데이터를 볼 수 있는 툴이 있으시다면 그것으로 열어도 상관 없습니다. 그럼 헥사 형태와 오른쪽에 아스키 값으로 찍히는 화면이 아래처럼 나올 겁니다. 근데 왜 wireshark로 열면 안되냐 하면 바로 이 파일의 헤더는 wireshark에서 데이터로 사용할 뿐 실제 패킷내용은 보여주지 않기 때문입니다. ( 24바이트와 16바이트가 표기되지 않습니다)

 

 

맥어드레스 지운다고 화내시는 분은 안계시겠죠? 여러분도 wireshark로 덤프시킨 파일을 노출시킬 때에는 되도록 주의 해 주시기 바랍니다. (실제로 서버들은 더 숨겨야 하는 경우가 많습니다. 아이피와 포트 등도 숨겨야 하는 대상이되겠죠)

 

위와 같이 헤더부분만 나오는 데이터가 추출 될 수도 있고, 저거보다 훠얼 씬 더 긴 1500 바이트가 잡히는 경우도 있을 것입니다.

어쨌거나 중요한 것은 위에 데이터를 어떻게 읽어내고 디버깅 코드에서 사용할 수 있는가가 중요한 것이겠죠.

 

그림에서 보면 알 수 있듯이 24바이트와 16바이트는 위에 헤더에서 표기한 내용에 맞춰봐야 합니다. 즉 빨간색 24바이트는 pcap_hdr_t 구조체에 매핑시키고 파란색 16바이트는 pcaprec_hdr_t 구조체에 매핑시키면 된다는 이야기이죠.

(완전 별개의 얘기입니다만, 요새 3d로 자동차 만드느라 zmodeler에 푹 빠져있는데 UV Mapping 작업을 하면서 매핑이라는 말이 정말 현실적으로 다가오더군요. 그나저나 누구 3dmax 같이 공부하실 분 없으세요?)

 

일단 위 데이터를 보면서 잠시 데이터를 분석 해 보도록 합니다.

 

전반 헤더 24바이트입니다.

D4 C3 B2 A1 02 00 04 00 00 00 00 00 00 00 00 00 FF FF 00 00 01 00 00 00

후반 헤더 16바이트입니다.

19 CB D0 47 11 CE 0A 00 3E 00 00 00 3E 00 00 00

 

, 이 데이터를 위의 구조체에 대입시켜 보겠습니다.

후반 헤더 데이터입니다.

 

typedef struct pcaprec_hdr_s {

           UINT ts_sec;                19 CB D0 47

           UINT ts_usec;              11 CE 0A 00

           UINT incl_len;              3E 00 00 00

           UINT orig_len;              3E 00 00 00

} pcaprec_hdr_t;

 

 

대충 이해가 가시죠? UINT 4바이트이기 때문에 4바이트씩 매핑시키면 됩니다.

, 그럼 위의 값을 계산해 보지요.

윈도우 계산기를 사용하시면 되겠습니다. (공학용 계산기로 하셔도 됩니다)

 

일단 윈도우 계산기를 띄우고 일반용에서 공학용으로 바꿔 주시기 바랍니다. ‘보기메뉴에서 바꿔주시면 됩니다.

 

  

 

 

좌측의 상단의 동그라미에 표기된 Dec가 바로 10진수임을 의미하는 것이고 그 옆의 Hex 16진수를 의미하고 있습니다. 우측 하단의 ABCDEF 버튼은 Hex모드로 변경하면 활성화 됩니다.

 

, 그럼 Hex 모드로 변경해서 위의 구조체 중의 incl_len 항목의 값을 확인 해 보도록 하지요.

값은 3E 00 00 00 입니다만, 근데 뭐가 이상하죠? 일단 변경 해 보도록 합니다.

Hex 모드에서 3E 00 00 00 값을 차례로 입력하고 바로 Dec 모드로 변경 해 보세요. 어떤 값이 나오게되나요?

1040187392 라는 값이 나옵니다. ? 이건 아닐텐데.. 저장된 길이 치고는 너무 길지 않나요? 1장에서 말씀 드렸듯이 하나의 패킷 길이는 1500을 넘길 수가 없을텐데 말이지요..

 

전에 설명에서 빼먹었던 Big Endian Little Endian의 차이 때문에 생기는 문제입니다.

사실 이 부분을 자세하게 설명 드리고 싶었는데 솔직히 타이핑 하기가 너무 귀찮습니다 흑흑..

그냥 웹사이트 링크 걸어두겠습니다. 프로그래밍 하시는 분들이라면 winapi 사이트는 다들 알고계시지 않나 생각합니다.

 

http://www.winapi.co.kr/clec/cpp2/18-1-3.htm

 

그다지 어려운 내용이 아니고, 전공이 이쪽이라면 학부 과정 때 배우는 항목이므로 일단 넘어가겠습니다.

 

결론은 00 00 00 3E 으로 값을 변경해야 하므로 그냥 3E의 값을 보면 되겠죠. 이렇게 값을 변경 해 보면 62라는 값이 나오게 됩니다. 실제 이 값은 ‘number of octets of packet saved in file’ 라고 설명된 값이라는 것이죠. 이 내용이 의미하는 것은 현재 잡은 패킷의 길이가 62byte가 된다는 것이고, 이것이 지금으로써는 가장 중요한 의미를 가지는 값이 될 겁니다. 추후 헤더길이와 데이터의 길이를 판가름하는 중요한 자료일테니까요. 그치만 실제 패킷을 잡을 때는 또 다르겠죠.

 

말이 길었습니다. 이제 코드를 볼까요?

하나 하나 설명드리기에는 너무 번거로우므로 대충만 주석을 달겠습니다.

 

void ReadDumpFile()

{   

int FileLength=0, ReadSize=0, done=0, FileScanPoint=0;

USHORT PackSize;

// 위에서 표기 된 구조체를 헤더 파일에 포함 시키셨죠?

pcap_hdr_t CaputerHeaderBlock;

pcaprec_hdr_t CaptureRecBlock;

char packetbuf[1520] = {0};

 

    // 덤프 한 파일을 열어줍니다

FILE* fp_DumpFile = fopen(“패킷파일”, "rb");

 

if(fp_DumpFile )

{

    // 파일 길이를 알아내서 저장 해 둡니다

fseek(fp_DumpFile, 0, SEEK_END);

FileLength = ftell(fp_DumpFile);

fseek(fp_DumpFile, 0, SEEK_SET);

 

        // 첫 번째 24바이트를 매핑시켜 둡니다

ReadSize = fread(&CaputerHeaderBlock, 1, sizeof(pcap_hdr_t), fp_DumpFile);

if(ReadSize == 0)

{

    // 파일 읽기를 실패했을 경우

fclose(fp_DumpFile);

fp_DumpFile = NULL;

return;

}

else

{

FileScanPoint = ReadSize;

}

}

else

{

printf("파일을 열 수 없습니다!!\n");

return;

}

 

while(1)

{

     // 두 번째 libcap 헤더의 16바이트를 읽어들입니다

ReadSize = fread(&CaptureRecBlock, 1, 16, fp_DumpFile);

if(!ReadSize)

break;

 

FileScanPoint += ReadSize;

// 위에서 설명 된 incl_len 값을 PackSize에 저장 해 둡니다

memcpy(&PackSize, &CaptureRecBlock.incl_len, 4);

 

           // 파일 길이보다 현재 읽어들이는 사이즈가 클 경우 당근 에러

if(FileLength < FileScanPoint+ PackSize)

break;

 

fseek(fp_DumpFile, FileScanPoint, SEEK_SET);

memset(&packetbuf, 0xFF, sizeof(packetbuf));

 

           // 길이만큼만 읽어들일 것

ReadSize = fread(&packetbuf, 1, PackSize, fp_DumpFile);

if(!ReadSize)

break;

else

FileScanPoint += ReadSize;      

}

 

if(fp_DumpFile)

fclose(fp_DumpFile);

}

 

, 이제 향후에 사용 할 함수 하나를 만들어 냈습니다.

위의 함수에서 나온 CaputerHeaderBlock CaptureRecBlock  값을 통해서 원하는 값을 추출할 수도 있겠지요.

위 함수는 여러모로 유용하게 사용할 수 있습니다. , 어떠한 작업을 처리하지 못할 경우 실제 패킷을 TCP/IP에서 계속 쏘면서 테스트할 수는 없지 않겠습니까.. 이럴 때 덤프를 받아 반복적인 작업으로 문제점을 찾아볼 수도 있겠지요.

 

대충 앞으로의 코드도 위의 방향을 따라가게 됩니다. , 신규로 생성하는 프로토콜의 내용을 구조체로 만들고 그것에 실제 데이터를 매핑시켜서 프로그래밍을 하게 되면 무척 편하게 값을 볼 수 있습니다. 추후 강좌에서 다루겠지만 이러한 이유로 인해서 프로토콜을 작성할 때 되도록이면 variable 한 길이의 값을 주기 보다는 고정값을 주는 것을 권장하고 있으며 만약 이러한 내용을 피할 수 없을 경우 어떤 식으로 구조체를 설정하는 지 등에 대해서 알려드리겠습니다.

 

근데 제가 이 강좌를 마무리 지을 수 있을 것인지.. 두둥

아차, 틀린 내용은 언제든 지적 부탁드립니다.

댓글 없음:

댓글 쓰기