카테고리 없음

wincap IA32

컴공 2013. 6. 18. 15:13
반응형
Winpcap이란 리눅스/유닉스에서 이미 유명한 Libpcap을 윈도우 개발 환경에
맞게 포팅한 것이다. Winpcap의 사용 방법들은 Libpcap과 거의 동일하다.
이 문서는 Winpcap을 설치하는 방법과 몇가지 샘플 예제를 설명한다.
참고로 구현 환경은 Windows32-API이며, 아마도 Windows32-Console mode에서도
동일하게 적용될 것이다.

1. Winpcap 개발 패키지를 다운로드받자.
http://winpcap.polito.it/default.htm <- 요기서 Download 메뉴.
참고로 Winpcap auto-installer를 받는 것이 아니다. 이것은 우리가 만든
프로그램을 사용할 사람들이 설치해야하는 드라이버와 DLL 파일들이다.
개발자는 각종 헤더파일과 LIB 파일이 들어있는 "Developer's pack"을 받아야한다.
설치 과정은 간단하다. 다운받은 파일의 압축을 푼 후, LIB과 INCLUDE 디렉토리
내의 파일들을 각각 Visual Studio가 설치된 폴더의 VC98\LIB과 VC98\INCLUDE에
복사해 넣으면 된다.

2. PROJECT-SETTINGS-LINK 탭에 wpcap.lib을 추가한다.
3. 프로그램의 헤더 부분에 #include <pcap.h>을 추가한다.

이정도는 개발 준비는 완료된다. 이제 간단간단한 예제들을 설명해 나가겠다.
먼저, 현재 호스트의 네트워크 정보를 가져오는 예제이다. 이 문서의 대부분의
소스 코드는 http://doit.ajou.ac.kr/~kagi/pub/using_libpcap/에서 가져온 것이다.

◎ 내 컴퓨터의 네트워크 정보 가져오기.

============================================================
char *dev; // 네트워크 디바이스의 이름을 가리킬 변수.
char errbuf[PCAP_ERRBUF_SIZE]; // 에러 메시지를 담을 변수.

bpf_u_int32 net; // 네트워크 주소 정보
bpf_u_int32 mask; // 넷마스크 정보
struct in_addr net_addr, mask_addr;

// 네트워크 디바이스를 찾는다. 리턴 값은 디바이스의 이름이다.
if(!(dev = pcap_lookupdev(errbuf))){
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// 해당 디바이스의 정보를 가져온다.
// net은 네트워크 주소, mask는 넷마스크를 의미한다.
if(pcap_lookupnet(dev, &net, &mask, errbuf) < 0){
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// in_addr 구조체 형식에 맞게 바꾸어주자.
// 이유는 대다수의 소켓 관련 함수가 in_addr 구조체를 사용하기 때문이다.
// 이 예제에선 inet_ntoa() 함수를 위한 것이다.
net_addr.s_addr = net;
mask_addr.s_addr = mask;

// 디바이스 이름을 출력한다.
MessageBox(g_hWnd, dev, "Device Name", 0);

// 네트워크 주소를 출력한다.
MessageBox(g_hWnd, inet_ntoa(net_addr), "Network Address", 0);

// 넷마스크를 출력한다.
MessageBox(g_hWnd, inet_ntoa(mask_addr), "Netmask Info", 0);
============================================================

이상 디바이스 이름과 네트워크 주소(IP), 그리고 NETMASK 정보를
출력하는 샘플 소스 코드였다.
이번엔 내 컴퓨터로 들어오는 패킷들을 캡쳐하여 16진수 형태로 출력하는
샘플 코드를 설명한다.

◎ 실시간으로 패킷 캡쳐하기.

===========================================================================
char *dev; // 디바이스 이름을 가리킨다.
char errbuf[PCAP_ERRBUF_SIZE]; // 에러 메시지를 저장한다.
bpf_u_int32 net, netmask; // net address, netmask 정보를 저장한다.
pcap_t *pd; // *중요 - 캡쳐할 패킷의 디스크립터이다.

// 네트워크 디바이스를 찾는다. 리턴 값은 디바이스의 이름이다.
if(!(dev = pcap_lookupdev(errbuf))){
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// dev : 패킷을 받는 디바이스의 이름이다.
// 1024 : 캡쳐할 패킷의 최대 값이다. NIC의 헤더가 14바이트, TCP/IP의 헤더가
// 40바이트임으로 최소 54바이트에서, 추가 DATA를 저장할 패킷 길이을 결정하여
// 적어주면 된다.
// 1 : promiscuous mode로 작동시킬지를 결정한다. dummy 허브에서 유효하다.
// 3000 : 응답이 없을 경우 TIMEOUT시킬 초이다. ms 단위.
// 그리고 이 값을 적용시키려면 pcap_loop() 함수 대신 pcap_dispatch을 사용한다.
// errbuf : 에러메시지가 저장될 변수이다.
if(!(pd = pcap_open_live(dev, 1024, 1, 3000, errbuf))){
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// pd : 패킷 디스크립터이다.
// 10 : 받을 패킷의 최대 수이다. 즉, 10번만 loop를 돌고, 그 후엔 함수가
// 종료된다.
// packet_view : 받은 패킷을 처리할 함수이다.
// 0 : 사용자 데이터이다.
if(pcap_loop(pd, 10, packet_view, 0) < 0) {
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// 패킷 디스크립터를 닫는다.
pcap_close(pd);
===========================================================================

다음은 받은 패킷을 처리하는 함수이다. 앞서 pcap_loop의 인자로 사용하였다.
pcap_loop의 세 번째 인자는 다음의 함수 원형을 따라야한다.
void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);

===========================================================================
void packet_view(unsigned char *user, const struct pcap_pkthdr *h, const unsigned char *p)
{
int len=0;
char packet_string[1024];
char temp[10];

strcpy(packet_string, "PACKET\n");
while(len < h->len) {
sprintf(temp, "%02x ", *(p++));
strcat(packet_string, temp);
if(!(++len % 16)){
sprintf(temp, "\n");
strcat(packet_string, temp);
}
}
MessageBox(g_hWnd, packet_string, "received!", 0);
}
===========================================================================
이상. 패킷 받기 샘플 예제 끝. 결과를 보면, 내 컴퓨터로 도착한 패킷 1개의
NIC 헤더, TCP/IP 헤더, 그리고 패킷의 DATA가 16진수 형태로 출력될 것이다.
또, 앞서 pcap_loop의 두 번째 인자로 10을 주었기 때문에 단 10개의 패킷만을
캡쳐한 후 루프를 종료한다. 만약 계속해서 패킷을 받고자하다면 0을 인자로
주면 된다. 이제 원하는 조건의 패킷만 출력되도록 수정해보자.

◎ 패킷 필터링하기.

===========================================================================
앞서 본 예제 소스의 pcap_open_live() 함수와 pcap_loop() 함수의 사이에 다음의
두 함수만 추가한다.

struct bpf_program fcode; // 필터링 Rule이 저장될 변수.

// pcap_complie : 필터링 Rule을 설정하는 함수
// pd : 캡쳐할 패킷의 디스크립터
// fcode : 필터링 Rule이 저장될 변수
// dst host 210.10x.19x.xx and tcp port 80 : 필터링 Rule(조건) 정의
// 0 : 결과 코드를 수행할 때의 최적화 여부
// netmask : netmask 정보. netmask 얻는 방법 가장 처음 소스에 있음.

if(pcap_compile(pd, &fcode, "dst host 1.1.1.1 and tcp port 80", 0, netmask) < 0) {
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}

// pcap_compile()에 의해 정의된 Rule을 실제로 적용.
if(pcap_setfilter(pd, &fcode) < 0) {
MessageBox(g_hWnd, errbuf, "Error", 0);
ExitProcess(1);
}
===========================================================================

◎ 패킷 헤더의 값 출력하기.
===========================================================================
* packet_view() 함수 내부에 추가해주면 된다.

struct ip *iph;
struct tcphdr *tcph;

// iph의 포인터를 전체 패킷에서 ether_header의 바로 다음 부분으로 설정.
iph = (struct ip *)(p + sizeof(struct ether_header));

// tcph의 포인터는 전체 패킷에서 ether_header과 ip header의 바로 다음 부분.
tcph = (struct tcphdr *)(p + sizeof(struct ether_header) + sizeof(struct ip));

// 상대방 IP
strcpy(target_ip, inet_ntoa(iph->ip_src);
// 나의 IP
strcpy(my_ip, inet_ntoa(iph->ip_dst);

// 상대방 Port
target_port = ntohs(tcph->sport);
// 나의 Port
my_port = ntohs(tcph->dport);
===========================================================================

이상~ 초간단 강좌를 마칩니다. 위 4가지 샘플만 응용하면 raw socket이 필요한
왠만한 프로그램들은 모두 구현할 수 있을겁니다. 단, Winpcap(Libpcap)은 패킷을
보내는건 안되고 오직 받는것만 됩니다. 그게 가장 큰 단점같구요. 아마도 패킷을
보내는 것은 libnet 라이브러리를 사용해야할 겁니다. 물론 직접 raw socket을
구현해도 되구요. 윈도우 상에서 raw socket을 보내는 것은 잘 되는데 받는게
안되서 Libpcap을 사용하게 되었습니다. Libpcap에 패킷을 보내는함수들도 있다면
참 좋았을텐데 아쉽네요.

* 참고
raw socket으로 패킷 발송한 후, pcap_dispatch나 loop로 받으려면, sendto하기
전에 thread로 pcap_dispatch를 실행시키고, sendto하기 전에 sleep()으로 약
0.1초 정도 간격을 주어야됩니다. 그렇지 않으면 너무 빨리 패킷이 와버려서
pcap_dispatch가 받지 못함.

* 참고2
pcap_dispatch의 마지막 인자는 packet_view의 첫번째 인자로 전달됩니다
반응형