WinSock で簡易パケットキャプチャを作ろう
ネットワークの管理やトラブルシューティングなどをしていると、 ネットワークにどのようなデータが流れているかみなければいけない (あるいは見てみたい) 場合がよくあります。
Windows の環境だとそのような場合に使えるツールとして、何と言っても Network Monitor が挙げられます。 昔は NT4 Server や Windows 2000 Server などのサーバー OS の管理ツールとして OS に同梱されていましたが、 今はマイクロソフトのダウンロードセンターからダウンロード可能です。
その他、有名どころとしては、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セットくらいあってもいいんじゃないでしょうか。