WinSock で簡易パケットキャプチャを作ろう

ネットワークの管理やトラブルシューティングなどをしていると、 ネットワークにどのようなデータが流れているかみなければいけない (あるいは見てみたい) 場合がよくあります。

Windows の環境だとそのような場合に使えるツールとして、何と言っても Network Monitor が挙げられます。 昔は NT4 Server や Windows 2000 Server などのサーバー OS の管理ツールとして OS に同梱されていましたが、 今はマイクロソフトのダウンロードセンターからダウンロード可能です。

Network Monitor 3.4 のダウンロード

その他、有名どころとしては、Wireshark (と WinPCap) があります。

どちらも大変優れたソフトウェアで、ネットワークの解析に非常に役に立ちます。

しかし、何かあった時に 「ちょっとネットワークを見たい」 という時もあります。 その時にこうしたツールがもともとインストールされていれば問題ないのですが、インストールされていないとちょっと厄介なことになります。

そこで意外と役立つのが今回ご紹介するツールです。

このプログラムは WinSock の生ソケット (Raw Socket) にて、WSAIoctl 関数で SIO_RCVALL を指定することによって、 指定したインターフェイスの IP パケットを全て受け取れるようになります。 NIC がプロミスキャスモードをサポートしている場合、 プロミスキャスモードを有効にするというわけです。

プログラムの作成

コードは各自適当に読んで頂くことにして、ビルドの仕方を説明します。(手抜きですいません(苦笑))

次のコードを pcap.cpp として保存します。

#include <winsock2.h>
#include <mstcpip.h>
#include <stdio.h>
#include "printutils.h"


//////////////////////////////////////////////////////////////////////////


WSADATA g_wsaData;


//////////////////////////////////////////////////////////////////////////


VOID PrintTCPHeader(PTCP_HEADER tcp_header) {

	if (!tcp_header) {
		SetLastError(ERROR_INVALID_PARAMETER);
		return;
	}

	printf("Src Port: %u\n", ntohs(tcp_header->th_sport));
	printf("Dst Port: %u\n", ntohs(tcp_header->th_dport));
	printf("Sequence number: %u\n", ntohl(tcp_header->th_seq));
	printf("Acknowledgement: %u\n", ntohl(tcp_header->th_ack));
	printf("Header Length: %u\n", tcp_header->th_hl);
	// Flags
	printf("Flags:\n");
	printf("\tURG: %u\n", tcp_header->th_flags & 0x20 ? 1 : 0);
	printf("\tACK: %u\n", tcp_header->th_flags & 0x10 ? 1 : 0);
	printf("\tPSH: %u\n", tcp_header->th_flags & 0x8 ? 1 : 0);
	printf("\tRST: %u\n", tcp_header->th_flags & 0x4 ? 1 : 0);
	printf("\tSYN: %u\n", tcp_header->th_flags & 0x2 ? 1 : 0);
	printf("\tFIN: %u\n", tcp_header->th_flags & 0x1 ? 1 : 0);
	printf("Window size: %u\n", ntohs(tcp_header->th_win));
	printf("Checksum: %u\n", ntohs(tcp_header->th_sum));
	printf("Urgent offset: %u\n", ntohs(tcp_header->th_urp));

}


//////////////////////////////////////////////////////////////////////////
//
// main function
//


void main(int argc, char* argv[]) {

	INT i, nRet, nNIC;
	char szBuff[4096], *lpNIC;
	DWORD dwBytesReturned;
	SOCKET_ADDRESS_LIST* sock_addr_list;
	SOCKADDR_IN *nic_addr, addr;
	ULONG uRCVALL_OPTION = RCVALL_ON;

	IP_HEADER ip_header;
	TCP_HEADER tcp_header;
	DWORD dwTotalHeaderLen;
	char* lpBuffer;

	// WSAStartup
	WSAStartup(MAKEWORD(2, 2), &g_wsaData);

	// Create Raw Socket
	SOCKET hSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
	if (INVALID_SOCKET == hSocket) {
		printf("socket Failed.\n");
		return;
	}

	// Enumerate Interfaces and Prompt to Select (if any)
	nRet = WSAIoctl(
		hSocket,
		SIO_ADDRESS_LIST_QUERY, NULL, 0,
		szBuff, sizeof(szBuff), &dwBytesReturned,
		NULL, NULL);

	if (WSAEFAULT == nRet) {
		printf("ERROR: WSAIoctl Failed.\n");
		return;
	}

	sock_addr_list = (SOCKET_ADDRESS_LIST*)szBuff;

	if (sock_addr_list->iAddressCount > 1) {
		for (i = 0; i < sock_addr_list->iAddressCount; i++) {
			nic_addr = (SOCKADDR_IN*)sock_addr_list->Address[i].lpSockaddr;
			lpNIC = inet_ntoa(nic_addr->sin_addr);
			printf("NIC [%02d]: %s\n", i, lpNIC);
		}
		printf("Enter NIC Number you want to monitor --> ");
		scanf_s("%d", &nNIC);
		printf("\n");
		if (nNIC < 0 || sock_addr_list->iAddressCount <= nNIC) {
			printf("Invalid number.\n");
			return;
		}
	}
	else if (1 == sock_addr_list->iAddressCount) {
		nNIC = 0;
	}
	else {
		printf("No Network Available.\n");
		return;
	}

	nic_addr = (SOCKADDR_IN*)sock_addr_list->Address[nNIC].lpSockaddr;
	printf("[INFO] %s\n", inet_ntoa(nic_addr->sin_addr));

	// Bind
	addr.sin_addr.s_addr = nic_addr->sin_addr.s_addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(0);
	nRet = bind(hSocket, (SOCKADDR*)&addr, sizeof(addr));
	if (SOCKET_ERROR == nRet) {
		printf("bind Failed. %d\n", WSAGetLastError());
		return;
	}

	// Set SIO_RCVALL Option
	nRet = WSAIoctl(
		hSocket, SIO_RCVALL, &uRCVALL_OPTION, sizeof(ULONG),
		NULL, 0, &dwBytesReturned, NULL, NULL);

	if (0 != nRet) {
		printf("WSAIoctl Failed. SIO_RCVALL mode setting.\n");
		return;
	}

	// Capture Packets
	while (1) {

		dwBytesReturned = recv(hSocket, szBuff, sizeof(szBuff), 0);

		if (SOCKET_ERROR == dwBytesReturned || 0 == dwBytesReturned) {
			printf("recv returned SOCKET_ERROR or closed.\n");
			break;
		}
		memmove(&ip_header, szBuff, sizeof(IP_HEADER));

		printf("---------------------------------------------------------\n");
		printf("* IP HEADER\n");
		PrintHexDump(((ip_header.ip_hl) * 4), (PBYTE)szBuff);

		if (6 == ip_header.ip_p) { // TCP Packet

			lpBuffer = szBuff;
			lpBuffer += ((ip_header.ip_hl) * 4);
			memmove(&tcp_header, lpBuffer, sizeof(TCP_HEADER));

			printf("* TCP HEADER\n");
			PrintHexDump(sizeof(TCP_HEADER), (PBYTE)&tcp_header);
			PrintTCPHeader(&tcp_header);

			printf("* TCP Payload\n");
			dwTotalHeaderLen = ((ip_header.ip_hl) * 4) + ((tcp_header.th_hl) * 4);
			lpBuffer = szBuff;
			lpBuffer += dwTotalHeaderLen;
			PrintHexDump(
				dwBytesReturned - dwTotalHeaderLen,
				(PBYTE)lpBuffer);
		}
		printf("* RAW DATA\n");
		PrintHexDump(dwBytesReturned, (PBYTE)szBuff);

	}

	// Clean up
	closesocket(hSocket);
	WSACleanup();

}

次のコードを printutils.h として保存します。

#pragma once

#include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>

//
// IP structure.
// see. Gary R. Wright et al,
//  "TCP/IP Illustrated, Vol 2" p.211, Addison Wesley, 1995
//
typedef struct IP_HEADER {
	unsigned char ip_vhl; // version and header length
#define ip_v ip_vhl>>4
#define ip_hl ip_vhl&0x0F
	unsigned char ip_tos; // type of service
	unsigned short ip_len; // total length
	unsigned short ip_id; // identification
	unsigned short ip_off; //fragment offset field
#define IP_DF 0x4000 //dont fragment flag
#define IP_MF 0x2000 //more fragment flag
#define IP_OFFMASK 0x1fff //mask for fragmenting bits
	unsigned char ip_ttl; // time to live
	unsigned char ip_p; //protocol
	unsigned short ip_sum; // checksum
	struct in_addr ip_src, ip_dst; // source and dest address
} IP_HEADER, *PIP_HEADER;

typedef struct TCP_HEADER {
	unsigned short th_sport; // 16-bit source port number
	unsigned short th_dport; // 16-bit destination port number
	unsigned long th_seq; // 32-bit sequence number
	unsigned long th_ack; // 32-bit acknowledgement number
	unsigned char th_hlr; // header length and reserved
	unsigned char th_rfl; // reserved and flags
#define th_hl th_hlr>>4
#define th_flags th_rfl&0x3F
	unsigned short th_win; // 16-bit window size
	unsigned short th_sum; // 16-bit TCP checksum
	unsigned short th_urp; // 16-bit urgent offset
} TCP_HEADER, *PTCP_HEADER;

typedef struct MY_TCPROW {
	DWORD dwState;
	in_addr LocalAddr;
	SHORT sLocalPort;
	in_addr RemoteAddr;
	SHORT sRemotePort;
} MY_TCPROW, *PMY_TCPROW;

void PrintHexDump(DWORD length, PBYTE buffer);
VOID PrintTCPRow(PMIB_TCPROW pTcpRow);
VOID PrintAddrInfo(addrinfo* pAddrInfo);
VOID PrintIPHeader(IP_HEADER ip_header);

次のコードを printutils.cpp として保存します。

#include "printutils.h"
#include <stdio.h>

void PrintHexDump(DWORD length, PBYTE buffer) {

	DWORD i,count,index;
	CHAR rgbDigits[]="0123456789abcdef";
	CHAR rgbLine[100];
	char cbLine;

	for(index = 0; length; length -= count, buffer += count, index += count) {
		count = (length > 16) ? 16:length;

		sprintf_s(rgbLine, sizeof(rgbLine), "%4.4x ",index);
		cbLine = 5;

		for(i=0;i<count;i++) {
			rgbLine[cbLine++] = rgbDigits[buffer[i] >> 4];
			rgbLine[cbLine++] = rgbDigits[buffer[i] & 0x0f];
			if(i == 7) {
				rgbLine[cbLine++] = ':';
			}
			else {
				rgbLine[cbLine++] = ' ';
			}
		}

		for(; i < 16; i++) {
			rgbLine[cbLine++] = ' ';
			rgbLine[cbLine++] = ' ';
			rgbLine[cbLine++] = ' ';
		}

		rgbLine[cbLine++] = ' ';

		for(i = 0; i < count; i++) {
			if(buffer[i] < 32 || buffer[i] > 126) {
				rgbLine[cbLine++] = '.';
			}
			else {
				rgbLine[cbLine++] = buffer[i];
			}
		}
		rgbLine[cbLine++] = 0;
		printf("%s\n", rgbLine);
	}
} // end PrintHexDump


//////////////////////////////////////////////////////////////////////


VOID PrintTCPRow(PMIB_TCPROW pTcpRow) {

	char* lpState = NULL;
	MY_TCPROW MyTcpRow;

	MyTcpRow.dwState = pTcpRow->dwState;
	MyTcpRow.LocalAddr.S_un.S_addr = pTcpRow->dwLocalAddr;
	MyTcpRow.sLocalPort = ntohs(pTcpRow->dwLocalPort & 0xFF00);
	MyTcpRow.RemoteAddr.S_un.S_addr =
		MIB_TCP_STATE_LISTEN == MyTcpRow.dwState ?
		0 : pTcpRow->dwRemoteAddr;
	MyTcpRow.sRemotePort =
	    MIB_TCP_STATE_LISTEN == MyTcpRow.dwState ?
	    0 : ntohs(pTcpRow->dwRemotePort & 0xFF00);
	printf("%s:%d\t\t%s:%d ***",
		inet_ntoa(MyTcpRow.LocalAddr), MyTcpRow.sLocalPort,
		inet_ntoa(MyTcpRow.RemoteAddr), MyTcpRow.sRemotePort);

	// TCP State
	switch(pTcpRow->dwState) {
	case MIB_TCP_STATE_CLOSED: lpState = "CLOSED"; break;
	case MIB_TCP_STATE_LISTEN: lpState = "LISTENING"; break;
	case MIB_TCP_STATE_SYN_SENT: lpState = "SIN_SENT"; break;
	case MIB_TCP_STATE_SYN_RCVD: lpState = "SYN_RCVD"; break;
	case MIB_TCP_STATE_ESTAB: lpState = "ESTABLISHED"; break;
	case MIB_TCP_STATE_FIN_WAIT1: lpState = "FIN_WAIT1"; break;
	case MIB_TCP_STATE_FIN_WAIT2: lpState = "FIN_WAIT2"; break;
	case MIB_TCP_STATE_CLOSE_WAIT: lpState = "CLOSE_WAIT"; break;
	case MIB_TCP_STATE_CLOSING: lpState = "CLOSING"; break;
	case MIB_TCP_STATE_LAST_ACK: lpState = "LAST_ACK"; break;
	case MIB_TCP_STATE_TIME_WAIT: lpState = "TIME_WAIT"; break;
	case MIB_TCP_STATE_DELETE_TCB: lpState = "DELETE_TCP"; break;
	default: lpState = "Unknown";
		break;
	}
	printf("\t%s\n", lpState);
}


//////////////////////////////////////////////////////////////////////////


VOID PrintIPHeader(IP_HEADER ip_header){

	char *lpIP;

	printf("Version: %u\n", ip_header.ip_v); //version. This is always 4.
	printf("Header Length: %u * 4\n", ip_header.ip_hl);
	// header length is (ip_hl * 4) bytes
	printf("Type of Service: %u\n", ip_header.ip_tos); // type of service
	printf("Total Length: %u\n", ntohs(ip_header.ip_len)); // Total length
	printf("Identification: %u\n", ntohs(ip_header.ip_id)); // Identification
	printf("Flags: %u\n", ntohs(ip_header.ip_off));
	printf("Time To Live: %u\n", ip_header.ip_ttl);
	printf("Protocol: %u\n", ip_header.ip_p);
	printf("Checksum: %u\n", ntohs(ip_header.ip_sum));
	lpIP = inet_ntoa(ip_header.ip_src);
	printf("IP src: %s\n", lpIP);
	lpIP = inet_ntoa(ip_header.ip_dst);
	printf("IP dst: %s\n", lpIP);
}

makefile は以下の通りです。

TARGETNAME=pcap
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

"$(OUTDIR)" :
 @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

CPP_PROJ=\
 /nologo\
 /MT\
 /W3\
 /Fo"$(OUTDIR)\\"\
 /Fd"$(OUTDIR)\\"\
 /c\
 /Zi

LINK32_FLAGS=\
 ws2_32.lib\
 user32.lib\
 /subsystem:console\
 /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
 /machine:I386\
 /out:"$(OUTDIR)\$(TARGETNAME).exe"\
 /DEBUG\
 /RELEASE\
 /nologo

LINK32_OBJS= \
 "$(OUTDIR)\$(TARGETNAME).obj"\
 "$(OUTDIR)\printutils.obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
 $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

.cpp{$(OUTDIR)}.obj::
 $(CPP) $(CPP_PROJ) $<

以上をひとつのディレクトリに入れて、 Windows SDK のコマンドプロンプトにて nmake すれば出来上がりです。

chk サブディレクトリに pcap.exe というのが出来上がりますから、それを実行してください。 次のようにデータが次々と表示されるはずです。

...
* IP HEADER
0000  45 00 00 b1 0e 85 00 00:34 06 0f d7 4a 7d 13 68  E.......4...J}.h
0010  0a 00 00 07                                      ....
* TCP HEADER
0000  00 50 ed 22 cd 78 46 6d:a8 09 22 68 50 18 00 8c  .P.".xFm.."hP...
0010  19 b5 00 00                                      ....
Src Port: 80
Dst Port: 60706
Sequence number: 3447211629
Acknowledgement: 2819170920
Header Length: 5
Flags:
	URG: 0
	ACK: 1
	PSH: 1
	RST: 0
	SYN: 0
	FIN: 0
Window size: 140
Checksum: 6581
Urgent offset: 0
* TCP Payload
0000  48 54 54 50 2f 31 2e 31:20 33 30 34 20 4e 6f 74  HTTP/1.1 304 Not
0010  20 4d 6f 64 69 66 69 65:64 0d 0a 41 67 65 3a 20   Modified..Age:
0020  36 0d 0a 4c 61 73 74 2d:4d 6f 64 69 66 69 65 64  6..Last-Modified
0030  3a 20 57 65 64 2c 20 30:37 20 4a 75 6e 20 32 30  : Wed, 07 Jun 20
0040  30 36 20 31 39 3a 33 38:3a 32 34 20 47 4d 54 0d  06 19:38:24 GMT.
0050  0a 44 61 74 65 3a 20 46:72 69 2c 20 31 33 20 4d  .Date: Fri, 13 M
0060  61 72 20 32 30 30 39 20:30 32 3a 34 34 3a 35 30  ar 2009 02:44:50
0070  20 47 4d 54 0d 0a 53 65:72 76 65 72 3a 20 47 46   GMT..Server: GF
0080  45 2f 32 2e 30 0d 0a 0d:0a                       E/2.0....
* RAW DATA
0000  45 00 00 b1 0e 85 00 00:34 06 0f d7 4a 7d 13 68  E.......4...J}.h
0010  0a 00 00 07 00 50 ed 22:cd 78 46 6d a8 09 22 68  .....P.".xFm.."h
0020  50 18 00 8c 19 b5 00 00:48 54 54 50 2f 31 2e 31  P.......HTTP/1.1
0030  20 33 30 34 20 4e 6f 74:20 4d 6f 64 69 66 69 65   304 Not Modifie
0040  64 0d 0a 41 67 65 3a 20:36 0d 0a 4c 61 73 74 2d  d..Age: 6..Last-
0050  4d 6f 64 69 66 69 65 64:3a 20 57 65 64 2c 20 30  Modified: Wed, 0
0060  37 20 4a 75 6e 20 32 30:30 36 20 31 39 3a 33 38  7 Jun 2006 19:38
0070  3a 32 34 20 47 4d 54 0d:0a 44 61 74 65 3a 20 46  :24 GMT..Date: F
0080  72 69 2c 20 31 33 20 4d:61 72 20 32 30 30 39 20  ri, 13 Mar 2009
0090  30 32 3a 34 34 3a 35 30:20 47 4d 54 0d 0a 53 65  02:44:50 GMT..Se
00a0  72 76 65 72 3a 20 47 46:45 2f 32 2e 30 0d 0a 0d  rver: GFE/2.0...
00b0  0a                                               .
-----------------------------------------------------------------------
* IP HEADER
0000  45 00 00 28 71 9e 00 00:34 06 ad 46 4a 7d 13 68  E..(q...4..FJ}.h
...

いかがでしょうか?意外と簡単ですよね?

ちなみに、コードの中にも書きましたが、IP ヘッダなどの構造については TCP/IP Illustrated の第2巻を参考にしています。

こちらです。真面目にネットワークプログラミングに取り組みたい人にはとても参考になります。

コードを書くのに役立つのは2巻だけですが、1巻は TCP に関する説明も詳しくとても実践的で役に立ちます。 これを2冊買うとなると、3巻セットを買ったほうが割安なので紹介しておきますね。

私はこのボックスのセットを買いました。高いので個人で買うのはちょっとためらっても、会社に1セットくらいあってもいいんじゃないでしょうか。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説