안 쓰던 블로그

pcap 프로그래밍-ARP 패킷 분석 ARP reader 본문

Network

pcap 프로그래밍-ARP 패킷 분석 ARP reader

proqk 2020. 9. 25. 17:23
반응형

이전 글

헤더들 정보: foxtrotin.tistory.com/324

ARP 패킷 만들고 전송하기: foxtrotin.tistory.com/323

 

pcap 함수

pcap_findalldevs(&alldevs, errbuf)

네트워크 이더넷들을 찾는 함수

 

alldevs에 찾은 이더넷 정보 저장하며, 에러 시 errbuf에 에러를 저장한다


pcap_freealldevs(alldevs)

해당 네트워크를 제외한 다른 네트워크를 해제하기 위한 함수

 

pcap_open_live(name, 65536, 1, 1000, errbuf)

실제 네트워크 이더넷의 패킷을 캡처 하는 함수

 

name: 장치이름

65536: 패킷의 길이

1: 모든 패킷을 잡을 수 있는 promisc모드로 설정

1000: 패킷을 읽는 시간

errbuf: 에러버퍼

pcap_next_ex(adhandle, &header, &pkt_data)

캡처된 패킷의 데이터를 불러오는 함수


arp.h 구조

#ifndef _NET_IF_ARP_H
#define _NET_IF_ARP_H 1

#include <sys/types.h>
#include <sys/socket.h>
#include <stdint.h>

__BEGIN_DECLS

/* Some internals from deep down in the kernel.  */
#define MAX_ADDR_LEN    7

/* ARP protocol opcodes. */
#define ARPOP_REQUEST   1               /* ARP request.  */
#define ARPOP_REPLY     2               /* ARP reply.  */
#define ARPOP_RREQUEST  3               /* RARP request.  */
#define ARPOP_RREPLY    4               /* RARP reply.  */
#define ARPOP_InREQUEST 8               /* InARP request.  */
#define ARPOP_InREPLY   9               /* InARP reply.  */
#define ARPOP_NAK       10              /* (ATM)ARP NAK.  */

struct arphdr
  {
    unsigned short int ar_hrd;          /* Format of hardware address.  */
    unsigned short int ar_pro;          /* Format of protocol address.  */
    unsigned char ar_hln;               /* Length of hardware address.  */
    unsigned char ar_pln;               /* Length of protocol address.  */
    unsigned short int ar_op;           /* ARP opcode (command).  */
#if 0
    /* Ethernet looks like this : This bit is variable sized
       however...  */
    unsigned char __ar_sha[ETH_ALEN];   /* Sender hardware address.  */
    unsigned char __ar_sip[4];          /* Sender IP address.  */
    unsigned char __ar_tha[ETH_ALEN];   /* Target hardware address.  */
    unsigned char __ar_tip[4];          /* Target IP address.  */
#endif
  };


/* ARP ioctl request.  */
struct arpreq
  {
    struct sockaddr arp_pa;             /* Protocol address.  */
    struct sockaddr arp_ha;             /* Hardware address.  */
    int arp_flags;                      /* Flags.  */
    struct sockaddr arp_netmask;        /* Netmask (only for proxy arps).  */
    char arp_dev[16];
  };

__END_DECLS

#endif  /* net/if_arp.h */

arp.h에서 arphdr 구조체 부분을 사용한다


동작과정

1. 이더넷 헤더 확인

arp패킷을 캡쳐하기 위해서는 일단 이더넷 헤더 구조를 봐야 한다

이더넷 헤더에서 눈 여겨 봐야 할 부분은 Destination Address, Source Address, Type이다

목적지 주소와 출발지 주소는 말 그대로 목적지, 출발지의 MAC주소를 담고 있다

Type은 DATA 부분에 담겨 있는 데이터가 어느 프로토콜에 해당하는지를 나타낸다

0x600이상의 값을 가져야 프로토콜 종류를 의미하며, 0x600 이하의 값을 가진다면 프레임의 길이를 나타내는 값으로 변경된다

 

종류로는 다음과 같다

 

2. 이더넷 패킷 헤더의 Type이 ARP인지 확인

ARP패킷을 스니핑하기 위해서는 해당 패킷이 ARP인지 확인하는 과정이 제일 먼저 필요하다

그래서 코드 맨 처음에 이더넷 패킷을 받아와 헤더에서 Type값이 arp인지를 확인하는 부분이 있다

arp패킷이 아니면 패킷을 스루한다

 

3. arphdr구조체에 패킷을 담고 정보를 확인

리눅스 libpcap 기준으로 if_arp.h에 있는 arphdr구조체 구조에 맞게 패킷을 담아 온다

원하는 정보를 하나씩 출력한다


전체 코드

#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <cstdio>
#include <iostream>
#include <cstring>
#include <pcap.h>
#include <net/if_arp.h>

using namespace std;

void dump_pkt(const u_char *pkt_data, struct pcap_pkthdr* header);

void usage(){ //사용법 출력
    printf("syntax: pcap-test <interface>\n");
    printf("sample: pcap-test wlan0\n");
}

int main(int argc, char* argv[]){
    if (argc != 2){ //인자값이 2가 아니면 빠꾸
        usage();
        return -1; //0이 아니면 다 정상종료x 프로그램에서 리턴하는 경우 이렇게 아무 값이나 주는 게 좋다
    }

    char* dev = argv[1]; //argv[0]은 파일 이름, argv[1]이 데이터
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t* handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf); //BUFSIZ는 기본값
    if(handle==nullptr){
        fprintf(stderr, "pcap_open_live(%s) return nullptr - %s\n", dev, errbuf); //handle이 없으면 종료
        return -1;
    }

    while(true){ //종료 때까지
        struct pcap_pkthdr* header;
        const u_char* packet;
        int res = pcap_next_ex(handle, &header, &packet);
        if(res==0) continue;
        if(res==-1 || res==-2){
            printf("pcap_next_ex return %d(%s)\n", res, pcap_geterr(handle));
            break;
        }
        dump_pkt(packet, header);
    }
    pcap_close(handle);    
}

void dump_pkt(const u_char *pkt_data, struct pcap_pkthdr* header){
    struct ether_header *eth_hdr; //이더넷 헤더 구조체
    eth_hdr = (struct ether_header *)pkt_data; //패킷 데이터에서 이더넷 헤더 가져옴
    u_int16_t eth_type = ntohs(eth_hdr->ether_type); //이더넷 헤더에서 이더넷 타입 값을 가져와 리틀 엔디안으로 변환

    //if type is not arp, return function 이더넷 타입이 arp가 아니면 리턴
    if(eth_type!=ETHERTYPE_ARP) return;

    struct arphdr *arp_hdr = (struct arphdr *)(pkt_data+sizeof(ether_header)); //arphdr구조체 가져옴

    u_int8_t hardware_format = arp_hdr->ar_hrd; //하드웨어 포맷
    u_int8_t protocol_format = arp_hdr->ar_pro; //프로토콜 포맷

    printf("\nARP Packet Info====================================\n");

    //print pkt length
    printf("%u bytes captured. Actual length: %u\n", header->caplen, header->len); //헤더에서 캡쳐된 패킷 크기 가져와서 출력

    //print mac addr
    u_int8_t *dst_mac = eth_hdr->ether_dhost; //이더넷 헤더에서 목적지 mac주소 가져옴
    u_int8_t *src_mac = eth_hdr->ether_shost; //이더넷 헤더에서 출발지 mac주소 가져옴

    printf("Dst MAC : %02x:%02x:%02x:%02x:%02x:%02x\n",
        dst_mac[0],dst_mac[1], dst_mac[2], dst_mac[3], dst_mac[4], dst_mac[5]); //mac주소 출력

    printf("Src MAC : %02x:%02x:%02x:%02x:%02x:%02x\n",
        src_mac[0],src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]); //mac주소 출력
    
    //print length
    printf("Length of hardware address : %d\n", arp_hdr->ar_hln);
    printf("Length of protocol address : %d\n", arp_hdr->ar_pln);
    
    //print format
    printf("Format of hardware address : ");
    if(ntohs(arp_hdr->ar_hrd) == ARPHRD_ETHER) printf("Ethernet 10/100Mbps\n");  //Ethernet 10/100Mbps
    else printf("%x\n", ntohs(arp_hdr->ar_hrd)); //another format

    printf("ARP opcode: ");
    if(arp_hdr->ar_op == ARPOP_REQUEST) printf("ARP request\n"); //op코드가 1이면 request 패킷
    else if(arp_hdr->ar_op== ARPOP_REPLY) printf("ARP request\n"); //op코드가 2면 reply 패킷
    else if(arp_hdr->ar_op== ARPOP_RREQUEST) printf("RARP request\n"); //op코드가 3이면 rarp request패킷
    else if(arp_hdr->ar_op== ARPOP_RREPLY) printf("RARP request\n"); //op코드가 4면 rarp reply 패킷
    else printf("%x\n\n", ntohs(arp_hdr->ar_op)); //another OP code
}

실행

sudo wireshark
와이어 샤크 실행

g++ -o pcap_read_arp pcap_read_arp.cpp -lpcap
컴파일

sudo ./pcap_send_arp eth0
eth0으로 전송

 

와이어샤크에 ARP 패킷이 잡히면, 프로그램에서도 잡히는 것을 볼 수 있다

출발지와 목적지의 MAC주소와 패킷의 크기, 주소 등을 알 수 있다

처음 ARP 패킷은 request였으니까 opcode가 1이고, 두번째 ARP 패킷은 reply니까 opcode가 2인 것도 확인할 수 있다

 

 

반응형
Comments