개요
와이어 샤크의 기능중 Statistics>endpoints 에 들어가면 다음과같이 endpoints에 대한 내용을 알려주는 기능이 존재한다.
이와같은 분석 프로그램을 C++을 이용해서 구현해 볼것이다.
코드 구현
순서
Step1: pcap 프로그램 작성
Step2: IP,Ethernet 구조체 구성
Step3: map활용 코드 작성
Step4: 출력 코드작성
[ STEP 0 ] - include
#include <stdio.h>
#include <string.h> // memcpy 사용
#include <pcap.h> // pcap 활용
#include <map> // STL map 사용
#include <netinet/ip.h> // ntoh 사용
#include "header.h" // ip, ethernet 구조체 설정
[ STEP 1 ] - pcap 프로그램 작성
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* handle = pcap_open_offline("/home/ubuntu/STL/test.pcap", errbuf);
if (handle == NULL) {
fprintf(stderr, "couldn't open file %s: %s\n", "/home/ubuntu/STL/test.pcap", errbuf);
return -1;
}
struct pcap_pkthdr* header;
const u_char* data;
while(handle != NULL){
int res = pcap_next_ex(handle, &header, &data);
if (res == 0) continue;
if (res == -2) break;
}
-errbuf: 에러를 담을 버퍼생성
-pcap_open_offline: 기존의 pcap파일을 불러올때 사용한다.
(실시간 패킷을 잡으려면 pacap_open_live를 사용한다.)
-struct pcap_pkthdr* header: pcap 헤더 구조체 선언 (include pcap에서 가져온다)
-const u_char* data: 패킷 데이터를 담을 data선언
-pcap_next_ex:패킷을 하나씩 읽어오는 역할을 한다.
- 패킷을 문제없이 읽은 경우 1을 반환
- 시간 초과가 만료 된 경우 0을 반환
- 패킷을 읽는 동안 오류가 발생하면 -1을 반환
- 패킷이 EOF면 -2을 반환
[ STEP 2 ] - IP,Ethernet 구조체 구성
struct ETHER{
uint8_t dst_MAC[6];
uint8_t src_MAC[6];
uint16_t ether_type;
};
struct IP{
uint8_t v_l;
uint8_t tos;
uint16_t total_len;
uint16_t id;
uint16_t flag;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
};
위와 같이 구성한 이유를 알기 위해서는 패킷 구조를 알아야하는데 모른다면 여기서 확인해보자
2020/06/26 - [네트워크] - network패킷 구조
간단히 설명하자면 ETHER 구조체는 MAC의 주소와 TYPE을 얻기 위해 위와 같은 구조체로 설정하였고
IP 구조체는 다음과 같이 구성되어있어 위와같은 구조체로 설정했다.
struct ETHER * mac_key = (struct ETHER *)data;
struct IP * ip_key = (struct IP *)(data+14); //ether header => 14
따라서 mac의 주소와 ip의 주소를 가져오기위해 다음과 같이 구조체 변수를 선언해줬다.
IP에서 +14를 해준 이유는 이더넷 해더의 14바이트뒤의 값을 가져오기위해 연산을 해줬다.
사용방법은 다음과 같이 가져올 수 있다.
//ex
ip_key->dst_ip
ip_key->src_ip
mac_key->des_MAC
+inet 활용+
필자는 구조체를 직접 선언해줬는데 libpcap을 사용하면 편하게 사용할수도 있다. netinet에는 네트워크 구조체들이 미리 선언되어있어 가져다 쓰기만 하면 되는데 밑의 코드와 참고 사이트를 활용하면 이해가 더 쉬울것이다.
#include <netinet/in.h>
#include <netinet/ip.h>
~
pcap_offline등 추가 코드
~
struct ip * IPheader;
IPheader = (ip*)(date+sizeof(struct ether_header));
IPheader->ip_src
IPheader->ip_dst
참고: screwsliding.tistory.com/entry/Pcap-IP
[ STEP 3 ] - map활용 코드 작성
이제 map을 활용해서 dst와 src를 잘 정리하는 과정이 필요하다.
STL map을 잘 모른다면 아래의 글을 먼저 읽고 와도 좋다.
2021/01/22 - [c++/STL] - [STL]MAP(기본 개념 및 예시)
2021/01/23 - [c++/STL] - [STL]MAP(구조체 활용)
우선 기본적으로 map의 key는 주소로 value는 packet수 나 byte수를 담는 구조로 설정할것이다.
구조체 VALUES의 구성은 다음과 같다.
struct VALUES{
unsigned int Tx_packets;
unsigned int Tx_bytes;
unsigned int Rx_packets;
unsigned int Rx_bytes;
};
IP endpoint
map<uint32_t,VALUES> ip_;
위와같이 map을 선언해준다.
이어서 다음 코드는 map에 각 ip별로 rx,tx,packet수를 연산해주는 코드이다.
if (ip_.find(ip_key->dst_ip) == ip_.end()){
VALUES val;
val.Tx_bytes=0;
val.Rx_bytes=header->len;
val.Tx_packets=0;
val.Rx_packets=1;
ip_.insert(pair<uint32_t,VALUES>((ip_key->dst_ip),val));
}
else{
ip_[ip_key->dst_ip].Rx_packets += 1;
ip_[ip_key->dst_ip].Rx_bytes += header->len;
}
if (ip_.find(ip_key->src_ip) == ip_.end()){
VALUES val;
val.Tx_bytes=header->len;
val.Rx_bytes=0;
val.Tx_packets=1;
val.Rx_packets=0;
ip_.insert(pair<uint32_t,VALUES>((ip_key->src_ip),val));
}
else{
ip_[ip_key->src_ip].Tx_packets += 1;
ip_[ip_key->src_ip].Tx_bytes += header->len;
}
만약 ip가 존재하지 않다면 새롭게 insert를 사용하여 넣어주고 만약 존재한다면 기존 값에 +해주는 방식이다.
Ethernet endpoint
mac 주소는 uint32_t 값에 한번에 안담기기때문에 key로 구조체안의 배열을 사용해서 구해줬다.
struct MAC{
uint8_t MAC_a[6];
bool operator <(const MAC &var) const
{
return memcmp(MAC_a, var.MAC_a, sizeof(MAC)) < 0;
}
};
여기서 연산자 오버라이딩 한이유는 map은 이진트리여서 key값에 구조체가오면 오버라이딩을 통해 탐색할수 있게 만들어줘야하기 때문이다.
map<MAC,VALUES> mac_;
이어서 다음과 같이 map을 선언한뒤
MAC mac_a_r,mac_a_t;
memcpy(mac_a_r.MAC_a,mac_key->dst_MAC,sizeof(mac_a_r));
memcpy(mac_a_t.MAC_a,mac_key->src_MAC,sizeof(mac_a_t));
구조체 변수를 생성해 배열이니 memcpy를 통해 값을 넣어주고
ip와 동일하게 연산해준다.
if (mac_.find(mac_a_r) == mac_.end()){
VALUES val;
val.Tx_bytes=0;
val.Rx_bytes=header->len;
val.Tx_packets=0;
val.Rx_packets=1;
mac_.insert(pair<MAC,VALUES>((mac_a_r),val));
}
else{
mac_[mac_a_r].Rx_packets += 1;
mac_[mac_a_r].Rx_bytes += header->len;
}
if (mac_.find(mac_a_t) == mac_.end()){
VALUES val;
val.Tx_bytes=header->len;
val.Rx_bytes=0;
val.Tx_packets=1;
val.Rx_packets=0;
mac_.insert(pair<MAC,VALUES>((mac_a_t),val));
}
else{
mac_[mac_a_t].Tx_packets += 1;
mac_[mac_a_t].Tx_bytes += header->len;
}
[ STEP 4 ] - 출력 코드작성
IP endpoint
void ntoa(uint32_t ip, char * dst){
sprintf(dst, "%d.%d.%d.%d", ip&0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF);
}
위 코드는 data의 ip들은 정수값으로 표현되는데 우리가 알고있는 ip모양으로 변환시키는 함수이다.
//print ip endpoint
printf("<IPv4 Endpoints>\n");
printf("------------------------------------------------------------------------------------\n");
printf("| Address | Tx Packets | Tx Bytes | Rx Packets | Rx Bytes |\n");
printf("------------------------------------------------------------------------------------\n");
map<uint32_t, VALUES>::iterator iter;
for(iter = ip_.begin(); iter != ip_.end(); ++iter){
char addr[18];
ntoa(iter->first, addr);
printf("|%18s|%15d|%15d|%15d|%15d|\n", addr,
iter->second.Tx_packets,iter->second.Tx_bytes, iter->second.Rx_packets, iter->second.Rx_bytes);
printf("------------------------------------------------------------------------------------\n");
}
결론적으로 차례대로 출력을 위해 iterator을 이용해서 다음과 같이 출력 시켰다.
Ethernet endpoint
//print mac endpoint
printf("<Ethernet Endpoints>\n");
printf("------------------------------------------------------------------------------------\n");
printf("| Address | Tx Packets | Tx Bytes | Rx Packets | Rx Bytes |\n");
printf("------------------------------------------------------------------------------------\n");
map<MAC, VALUES>::iterator iter_;
for(iter_ = mac_.begin(); iter_ != mac_.end(); ++iter_){
//because array
printf("|%02X:%02X:%02X:%02X:%02X:%02X |%15d|%15d|%15d|%15d|\n",
iter_->first.MAC_a[0],
iter_->first.MAC_a[1],
iter_->first.MAC_a[2],
iter_->first.MAC_a[3],
iter_->first.MAC_a[4],
iter_->first.MAC_a[5],
iter_->second.Tx_packets,iter_->second.Tx_bytes, iter_->second.Rx_packets, iter_->second.Rx_bytes);
printf("------------------------------------------------------------------------------------\n");
}
mac 주소는 우리가 배열에 저장을 해뒀음으로 정수인 mac주소를 16진수로 출력시키기 위해 위와같이 코드를 짰다.
출력 모습
전체 소스코드
github 주소:github.com/wpgur/packet-state
'c++ > 네트워크 프로그래밍' 카테고리의 다른 글
deauth-attack 구현 (0) | 2021.02.13 |
---|