본문 바로가기

해킹 공격

[NOOBHACK] 윈도우, 리눅스 패킷 캡쳐 코드

내가 오늘 pacp으로 window에서 wireshark 같은 패킷 캡쳐 기능을 하게 하는 코드를 구현 하였다.

리눅스는 개발 환경이 꽁으로 되어 있는데 윈도우는 npcap을 집적 연결해야 한다.

 

https://npcap.com/dist/npcap-1.76.exe

여기에서 installer를 다운 받고 실행해 준다

 

https://npcap.com/dist/npcap-sdk-1.13.zip

여기서는 zip파일을 다운 받아 편한 곳에 압축 해제 해준다

 

visual studio에서 프로젝트 파일 속성에 들어가 다음과 같이 설정해 준다

먼저 추가 포함 디렉터리에 압축해제한 sdk 파일의 경로 중 Include 폴더가 있는 경로를 넣는다 ;도 구분을 위해 넣어줘야 한다.

링커 설정에서는 추가 라이브러리 디렉터리에는 x64 폴더가 있는 경로를 지정해 준다 ;도 역시 붙여준다.

그리고 라이브러리 경로를 지정해 줬으면 그 경로에 있는 무슨 .lib 파일을 사용할건지 지정해야 한다.

압축해제한 경로에 x64 폴더를 보면 두개의 라이브러리 파일이 존재하는데 이 둘을 사진과 같이 설정해 주면 된다.

 

이렇게 Packet.lib;와 pcap.lib;을 추가해 준다 그럼 환경 준비는 끝났다.

 

근데 또 웃긴건 ip 헤더나 TCP 헤더가 정의 되어 있지 않다. 그러면 우리가 집적 구조체를 정의하여 사용해야 한다...

 

 

#include <stdio.h>
#include <pcap.h>


typedef struct ip_hdr {
    unsigned char ip_hl : 4;
    unsigned char ip_v : 4; 
    unsigned char ip_tos;
    unsigned short ip_len;
    unsigned short ip_id;
    unsigned short ip_off;
    unsigned char ip_ttl; 
    unsigned char ip_p;  
    unsigned short ip_sum;
    BYTE ip_src[4]; 
    BYTE ip_dst[4];  
} IPV4_HDR;

typedef struct ethernet_hdr {
    BYTE h_source[ETH_ALEN];
    BYTE h_dest[ETH_ALEN];
    unsigned short h_proto;
} ETHERNET_HDR;


typedef struct _TCP_HDR {
    unsigned short source_port;
    unsigned short dest_port;
    unsigned int sequence_number;
    unsigned int ack_number;
    unsigned char data_offset : 4;
    unsigned char reserved : 3;
    unsigned short flags : 9; 
    unsigned short window;
    unsigned short checksum;
    unsigned short urgent_pointer;
} TCP_HDR;

typedef struct _tcp_packet {
    BYTE data[];
} TCP_DATA;

typedef struct ipv4_packet {
    ETHERNET_HDR eth_hdr;
    IPV4_HDR ip_hdr;
    TCP_HDR tcp_hdr;
    TCP_DATA tcp_data;
} TCP_PACKET;

이렇게 만들어진 구조체를 결론적으론 TCP_PACKET을 사용할거다 근데 또 귀찮은 점은 패딩 문제로 TCP_PACKET이 갖는 구조체 크기는 실제 크기보다 더 크다 이는 패딩 이라는 과정 때문에 구조체 크기가 늘어난 것이다.

전처리기를 이용해서 실제 구조체 크기로 맞춰 준다.

 

#include <stdio.h>
#include <pcap.h>

#pragma pack(push, 1)


typedef struct ip_hdr {
    unsigned char ip_hl : 4;
    unsigned char ip_v : 4; 
    unsigned char ip_tos;
    unsigned short ip_len;
    unsigned short ip_id;
    unsigned short ip_off;
    unsigned char ip_ttl; 
    unsigned char ip_p;  
    unsigned short ip_sum;
    BYTE ip_src[4]; 
    BYTE ip_dst[4];  
} IPV4_HDR;

typedef struct ethernet_hdr {
    BYTE h_source[ETH_ALEN];
    BYTE h_dest[ETH_ALEN];
    unsigned short h_proto;
} ETHERNET_HDR;


typedef struct _TCP_HDR {
    unsigned short source_port;
    unsigned short dest_port;
    unsigned int sequence_number;
    unsigned int ack_number;
    unsigned char data_offset : 4;
    unsigned char reserved : 3;
    unsigned short flags : 9; 
    unsigned short window;
    unsigned short checksum;
    unsigned short urgent_pointer;
} TCP_HDR;

typedef struct _tcp_packet {
    BYTE data[];
} TCP_DATA;

typedef struct ipv4_packet {
    ETHERNET_HDR eth_hdr;
    IPV4_HDR ip_hdr;
    TCP_HDR tcp_hdr;
    TCP_DATA tcp_data;
} TCP_PACKET;

#pragma pack(pop)

 

정말 귀찮게 한다.

이제 실제로 모든 패킷을 받아오는 코드를 작성하기 전에 네트워크 인터페이스를 선택해야한다.

네트워크 인터페이스는 현재 통신하고 있는 네트워크의 종류를 뜻한다.

 

우리 컴퓨터에서는 다양한 네트워크가 있다. 공유기에서 통신되고 있는 네트워크, 가상머신에서 통신되고 있는 네트워크, 로컬 호스트에서만 통신되고 있는 네트워크 등, 어떤 네트워크를 캡쳐 할건지 결정해야 한다.

 

int main() {

    pcap_t* handle;
    char errbuf[400];

    int c=1;
    pcap_if_t* alldevs;
    pcap_if_t* d;
    if (pcap_findalldevs(&alldevs, errbuf) == -1) {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }

    for (d = alldevs; d != NULL; d = d->next)
    {
        printf("%d. %s ", c, d->name);
        if (d->description)
            printf("(%s)\n",d->description);
        else
            printf(" (No description available)\n");
        c += 1;
    }
    printf("\n\nChoice interface network: ");
    scanf_s("%d", &c);

    pcap_findalldevs(&alldevs, errbuf);

    d = alldevs;
    for (int i = 1; i < c; i++) {
        d = d->next;
    }
    char* device = d->name;

    if (device == NULL) {
        printf("Error finding device: %s\n", errbuf);
        return 1;
    }
}

 

이렇게 모든 네트워크 인터페이스를 출력하여 선택하는 코드를 작성하였다. 여기서 모르는 함수를 리뷰 하자면

 

pcap_findalldevs(pcap_if_t **, char *)

이 함수는 사용 가능한 모든 네트워크 인터페이스를 담아준다

pcap_if_t는 이 네트워크 인터페이스를 담기위해 사용되는 구조체이다.

pcap_if_t 구조체는 다음과 같다.

struct pcap_if {
    struct pcap_if *next;       // 다음 인터페이스에 대한 포인터
    char *name;                 // 인터페이스의 이름 (예: "eth0")
    char *description;          // 인터페이스에 대한 설명 (이 설명은 없을 수도 있습니다)
    struct pcap_addr *addresses;// 인터페이스의 주소 목록에 대한 포인터
    bpf_u_int32 flags;          // PCAP_IF_로 시작하는 여러 플래그
};

 

먼저 모든 네트워크 인터페이스를 alldevs라는 변수에 담는다

    if (pcap_findalldevs(&alldevs, errbuf) == -1) {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }

 

 

그리고 for문에 모든 네트워크 인터페이스를 돌면서 인터페이스 이름을 출력해준다

    for (d = alldevs; d != NULL; d = d->next)
    {
        printf("%d. %s ", c, d->name);
        if (d->description)
            printf("(%s)\n",d->description);
        else
            printf(" (No description available)\n");
        c += 1;
    }

 

 

 

이제 무슨 인터페이스를 선택하는지 번호로 입력을 받을 것이기 때문에 다음과 같이 코드를 짜야한다.

    printf("\n\nChoice interface network: ");
    scanf_s("%d", &c);

    printf("\Ptint data len: ");
    scanf_s("%d", &bytes);

    pcap_findalldevs(&alldevs, errbuf);

    d = alldevs;
    for (int i = 1; i < c; i++) {
        d = d->next;
    }
    char* device = d->name;

    if (device == NULL) {
        printf("Error finding device: %s\n", errbuf);
        return 1;
    }

이 코드는 어떤 네트워크 인터페이스를 선택하면 device 변수에 네트워크 인터페이스를 넣는다

 

 

이제 선택한 네트워크 인터페이스를 기반으로 모든 패킷을 캡쳐하는 코드를 작성한다.

    handle = pcap_open_live(device, BUFSIZ, 1, timeout_limit, errbuf);
    if (handle == NULL) {
        fprintf(stderr, "Could not open device %s: %s\n", device, errbuf);
        return 2;
    }

    pcap_loop(handle, 0, packet_handler, NULL);


    pcap_close(handle);
    pcap_freealldevs(alldevs);

 

pcap_open_live 함수는 네트워크 패킷을 캡쳐하기 위해 해당 디바이스를 오픈하는 함수이다.

원형은 다음과 같다

 

pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf);

device: 패킷을 캡쳐할 디바이스 이름

snaplen: 캡처할 패킷의 최대 바이트 수 패킷이 설정한 값보다 더 길면, 패킷이 잘린다.

promisc: 프로미스큐어스 모드를 설정하는 인자다 1로 설정되며, 그게 아니면 0 프로미스큐어스 모드는 모든 패킷을 수신하며 그렇지 않으면 해당 인터페이스의 MAC주수로 지정된 패킷만 수신.

to_ms: 읽기 시간 초과 값(밀리초). 이 시간 초과 이후로 패킷 데이터가 없으면 pcap_next()와 같은 함수의 호출이 반환된다

errbuf: 오류가 발생되면 해당 변수에 에러 메시지를 저장한다.

 

반환 값은 성공한다면 패킷 캡쳐의 핸들이 반환되며

실패하면 NULL이 반환되어 errbuf에 에러 메시지가 저장된다.

 

 

 

그리고 pcap_loop 함수는 지정된 횟수만큼 패킷을 캡쳐하고 캡쳐 될 때 마다 지정된 함수를 실행한다

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);

 

p: pcap_open_live 또는 pcap_open_offline에 의해 반환된 패킷 캡쳐 핸들 포인터다

cnt: 캡쳐할 패킷의 수다 0, -1이 넣어지면 무한으로 패킷을 캡쳐한다.

callback: 패킷이 캡쳐 될때 마다 실행되는 함수다. 이 함수에는 전달되는 인자 값이 따로 있다

예를 들자면

void callback(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *packet);

패킷이 캡쳐 되면 여러 인자 값들을 callback 함수에 넘겨준다. 

user 인자 값은 pcap_loop 함수에서 user에서 따로 지정한 데이터를 넘겨줄 수 있다 필요없다면 0으로 설정 가능

pcap_pkthdr는 캡쳐한 패킷의 헤더 정보를 담고 있으며 pcaket은 실질적으로 패킷 데이터를 가지고 있다

 

user: callback 인자에서 설명하듯 패킷이 캡쳐되면 전달 될 사용자 지정 값이다 필요없다면 NULL도 가능하다.

 

    pcap_close(handle);
    pcap_freealldevs(alldevs);

 

이 pcap_close는 패킷 핸들을 해제 하여 주고

pcap_freealldevs는 우리가 네트워크 인터페이스를 검색 할 때 사용된 변수에 할당된 메모리를 해제해 준다.

 

이제 이 함수에서 작성된 패킷 캡쳐를 할 때 마다 수행되는 callback 함수를 작성해 준다.

 

void packet_handler(u_char* user_data, const struct pcap_pkthdr* pkthdr, BYTE* packet) {

    TCP_PACKET* packet_header = (TCP_PACKET*)(packet);



    if (packet_header->ip_hdr.ip_p == IPPROTO_TCP) {

        printf("-------------------------TCP------------------------\n");
        printf("Ethernet dst mac: %x.%x.%x.%x.%x.%x\n", packet_header->eth_hdr.h_source[0], packet_header->eth_hdr.h_source[1], packet_header->eth_hdr.h_source[2], packet_header->eth_hdr.h_source[3], packet_header->eth_hdr.h_source[4], packet_header->eth_hdr.h_source[5]);
        printf("Ethernet src mac: %x.%x.%x.%x.%x.%x\n\n", packet_header->eth_hdr.h_dest[0], packet_header->eth_hdr.h_dest[1], packet_header->eth_hdr.h_dest[2], packet_header->eth_hdr.h_dest[3], packet_header->eth_hdr.h_dest[4], packet_header->eth_hdr.h_dest[5]);
        printf("Src ip: %d.%d.%d.%d\n", packet_header->ip_hdr.ip_src[0], packet_header->ip_hdr.ip_src[1], packet_header->ip_hdr.ip_src[2], packet_header->ip_hdr.ip_src[3]);
        printf("Dst ip: %d.%d.%d.%d\n\n", packet_header->ip_hdr.ip_dst[0], packet_header->ip_hdr.ip_dst[1], packet_header->ip_hdr.ip_dst[2], packet_header->ip_hdr.ip_dst[3]);
        printf("TCP src port: %d\n", packet_header->tcp_hdr.source_port);
        printf("TCP dst port: %d\n", packet_header->tcp_hdr.dest_port);
        


        printf("\nPacket data (%d byte):\n", bytes);
        for (int i = 0; i < bytes  && packet_header->tcp_data.data[i]!='\0'; i++) {
            printf("%02X ", packet_header->tcp_data.data[i]);

            if ((i + 1) % 8 == 0)
                printf(" ");

            if ((i + 1) % 16 == 0) 
                printf("\n");
        }

        printf("\n----------------------------------------------------\n\n");
    }


}

우리가 위에서 정의한 구조체로 pcaket에 적용하면 생각보다 쉽게 작성이 가능하다 근데 여기서 TCP 데이터를 출력을 해야 하는데 너무 길면 보기 불편하므로 main 함수에 따로 출력되는 바이트 길이를 설정 할 수 있게 코드를 추가 해 주자

 

    printf("\Ptint data len: ");
    scanf_s("%d", &bytes);

 

이러면 패킷 캡쳐 코드가 완성 된다.

 


#include <stdio.h>
#include <pcap.h>

#define ETH_ALEN 6
#pragma pack(push, 1)


typedef struct ip_hdr {
    unsigned char ip_hl : 4;
    unsigned char ip_v : 4; 
    unsigned char ip_tos;
    unsigned short ip_len;
    unsigned short ip_id;
    unsigned short ip_off;
    unsigned char ip_ttl; 
    unsigned char ip_p;  
    unsigned short ip_sum;
    BYTE ip_src[4]; 
    BYTE ip_dst[4];  
} IPV4_HDR;

typedef struct ethernet_hdr {
    BYTE h_source[ETH_ALEN];
    BYTE h_dest[ETH_ALEN];
    unsigned short h_proto;
} ETHERNET_HDR;


typedef struct _TCP_HDR {
    unsigned short source_port;
    unsigned short dest_port;
    unsigned int sequence_number;
    unsigned int ack_number;
    unsigned char data_offset : 4;
    unsigned char reserved : 3;
    unsigned short flags : 9; 
    unsigned short window;
    unsigned short checksum;
    unsigned short urgent_pointer;
} TCP_HDR;

typedef struct _tcp_packet {
    BYTE data[];
} TCP_DATA;

typedef struct ipv4_packet {
    ETHERNET_HDR eth_hdr;
    IPV4_HDR ip_hdr;
    TCP_HDR tcp_hdr;
    TCP_DATA tcp_data;
} TCP_PACKET;

#pragma pack(pop)


int bytes;
void packet_handler(u_char* user_data, const struct pcap_pkthdr* pkthdr, BYTE* packet) {

    TCP_PACKET* packet_header = (TCP_PACKET*)(packet);



    if (packet_header->ip_hdr.ip_p == IPPROTO_TCP) {

        printf("-------------------------TCP------------------------\n");
        printf("Ethernet dst mac: %x.%x.%x.%x.%x.%x\n", packet_header->eth_hdr.h_source[0], packet_header->eth_hdr.h_source[1], packet_header->eth_hdr.h_source[2], packet_header->eth_hdr.h_source[3], packet_header->eth_hdr.h_source[4], packet_header->eth_hdr.h_source[5]);
        printf("Ethernet src mac: %x.%x.%x.%x.%x.%x\n\n", packet_header->eth_hdr.h_dest[0], packet_header->eth_hdr.h_dest[1], packet_header->eth_hdr.h_dest[2], packet_header->eth_hdr.h_dest[3], packet_header->eth_hdr.h_dest[4], packet_header->eth_hdr.h_dest[5]);
        printf("Src ip: %d.%d.%d.%d\n", packet_header->ip_hdr.ip_src[0], packet_header->ip_hdr.ip_src[1], packet_header->ip_hdr.ip_src[2], packet_header->ip_hdr.ip_src[3]);
        printf("Dst ip: %d.%d.%d.%d\n\n", packet_header->ip_hdr.ip_dst[0], packet_header->ip_hdr.ip_dst[1], packet_header->ip_hdr.ip_dst[2], packet_header->ip_hdr.ip_dst[3]);
        printf("TCP src port: %d\n", packet_header->tcp_hdr.source_port);
        printf("TCP dst port: %d\n", packet_header->tcp_hdr.dest_port);
        


        printf("\nPacket data (%d byte):\n", bytes);
        for (int i = 0; i < bytes && packet_header->tcp_data.data[i]!='\0'; i++) {
            printf("%02X ", packet_header->tcp_data.data[i]);

            if ((i + 1) % 8 == 0)
                printf(" ");

            if ((i + 1) % 16 == 0) 
                printf("\n");
        }

        printf("\n----------------------------------------------------\n\n");
    }


}

int main() {

    pcap_t* handle;
    char errbuf[400];

    int timeout_limit = 1000;
    int c=1;
    pcap_if_t* alldevs;
    pcap_if_t* d;
    if (pcap_findalldevs(&alldevs, errbuf) == -1) {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }

    for (d = alldevs; d != NULL; d = d->next)
    {
        printf("%d. %s ", c, d->name);
        if (d->description)
            printf("(%s)\n",d->description);
        else
            printf(" (No description available)\n");
        c += 1;
    }
    printf("\n\nChoice interface network: ");
    scanf_s("%d", &c);

    printf("\Ptint data len: ");
    scanf_s("%d", &bytes);

    pcap_findalldevs(&alldevs, errbuf);

    d = alldevs;
    for (int i = 1; i < c; i++) {
        d = d->next;
    }
    char* device = d->name;

    if (device == NULL) {
        printf("Error finding device: %s\n", errbuf);
        return 1;
    }

    handle = pcap_open_live(device, BUFSIZ, 1, timeout_limit, errbuf);
    if (handle == NULL) {
        fprintf(stderr, "Could not open device %s: %s\n", device, errbuf);
        return 2;
    }

    pcap_loop(handle, 0, packet_handler, NULL);


    pcap_close(handle);
    pcap_freealldevs(alldevs);



    return 0;
}

생각보다 헤더 구조체 정의를 자세하게 배워야 하기 때문에 생각보다 난이도가 있었다. 하지만 좋은 경험이었다. 꼭 필요한 지식이라고 생각된다.

그리고 이 코드는 리눅스 버전이다.

 

//gcc -o capture capture.c -lpcap
#include <stdio.h>
#include <pcap.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#define ETH_ALEN 6

#pragma pack(push, 1)
typedef struct ip_hdr {
    unsigned char ip_hl : 4;
    unsigned char ip_v : 4; 
    unsigned char ip_tos;
    unsigned short ip_len;
    unsigned short ip_id;
    unsigned short ip_off;
    unsigned char ip_ttl; 
    unsigned char ip_p;  
    unsigned short ip_sum;
    unsigned char ip_src[4]; 
    unsigned char ip_dst[4];  
} IPV4_HDR;

typedef struct ethernet_hdr {
    unsigned char h_source[ETH_ALEN];
    unsigned char h_dest[ETH_ALEN];
    unsigned short h_proto;
} ETHERNET_HDR;

typedef struct _TCP_HDR {
    unsigned short source_port;
    unsigned short dest_port;
    unsigned int sequence_number;
    unsigned int ack_number;
    unsigned char reserved : 4;
    unsigned char data_offset : 4;
    unsigned short flags;
    unsigned short window;
    unsigned short checksum;
    unsigned short urgent_pointer;
} TCP_HDR;

typedef struct _tcp_packet {
    unsigned char data[];
} TCP_DATA;

typedef struct ipv4_packet {
    ETHERNET_HDR eth_hdr;
    IPV4_HDR ip_hdr;
    TCP_HDR tcp_hdr;
    TCP_DATA tcp_data;
} TCP_PACKET;

#pragma pack(pop)
int bytes;
void packet_handler(u_char* user_data, const struct pcap_pkthdr* pkthdr, const u_char* packet) {

    TCP_PACKET* packet_header = (TCP_PACKET*)(packet);

    if (packet_header->ip_hdr.ip_p == IPPROTO_TCP) {
        printf("-------------------------TCP------------------------\n");
        printf("Ethernet dst mac: %x.%x.%x.%x.%x.%x\n", packet_header->eth_hdr.h_dest[0], packet_header->eth_hdr.h_dest[1], packet_header->eth_hdr.h_dest[2], packet_header->eth_hdr.h_dest[3], packet_header->eth_hdr.h_dest[4], packet_header->eth_hdr.h_dest[5]);
        printf("Ethernet src mac: %x.%x.%x.%x.%x.%x\n\n", packet_header->eth_hdr.h_source[0], packet_header->eth_hdr.h_source[1], packet_header->eth_hdr.h_source[2], packet_header->eth_hdr.h_source[3], packet_header->eth_hdr.h_source[4], packet_header->eth_hdr.h_source[5]);
        printf("Src ip: %d.%d.%d.%d\n", packet_header->ip_hdr.ip_src[0], packet_header->ip_hdr.ip_src[1], packet_header->ip_hdr.ip_src[2], packet_header->ip_hdr.ip_src[3]);
        printf("Dst ip: %d.%d.%d.%d\n\n", packet_header->ip_hdr.ip_dst[0], packet_header->ip_hdr.ip_dst[1], packet_header->ip_hdr.ip_dst[2], packet_header->ip_hdr.ip_dst[3]);
        printf("TCP src port: %d\n", ntohs(packet_header->tcp_hdr.source_port));
        printf("TCP dst port: %d\n", ntohs(packet_header->tcp_hdr.dest_port));

        printf("\nPacket data (%d byte):\n", bytes);
        for (int i = 0; i < bytes  && packet_header->tcp_data.data[i]!='\0'; i++) {
            printf("%02X ", packet_header->tcp_data.data[i]);

            if ((i + 1) % 8 == 0) printf(" ");
            if ((i + 1) % 16 == 0) printf("\n");
        }

        printf("\n----------------------------------------------------\n\n");
    }
}

int main() {

    pcap_t* handle;
    char errbuf[PCAP_ERRBUF_SIZE];
    int timeout_limit = 1000;
    int c=1;
    pcap_if_t* alldevs;
    pcap_if_t* d;

    if (pcap_findalldevs(&alldevs, errbuf) == -1) {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        return 1;
    }

    for (d = alldevs; d != NULL; d = d->next) {
        printf("%d. %s ", c, d->name);
        if (d->description) printf("(%s)\n", d->description);
        else printf(" (No description available)\n");
        c++;
    }

    printf("\n\nChoice interface network: ");
    scanf("%d", &c);

    printf("Print data len: ");
    scanf("%d", &bytes);

    d = alldevs;
    for (int i = 1; i < c; i++) {
        d = d->next;
    }
    char* device = d->name;

    if (!device) {
        fprintf(stderr, "Error finding device: %s\n", errbuf);
        return 1;
    }

    handle = pcap_open_live(device, BUFSIZ, 1, timeout_limit, errbuf);
    if (!handle) {
        fprintf(stderr, "Could not open device %s: %s\n", device, errbuf);
        return 2;
    }

    pcap_loop(handle, 0, packet_handler, NULL);

    pcap_close(handle);
    pcap_freealldevs(alldevs);
    return 0;
}

패킷 헤더 공부에 도움이 되었다고 생각하며 C언어를 포함하여 배운 것이 많았다.