서론
이전에 pcap-test로 작성한 코드를 리뷰해보면,
어떤 기능들인지는 세세히는 모르지만 대충 개발할 정도로는 이해가 가능할 것이다.
#include<pcap.h>
#include<stdbool.h>
#include<stdio.h>
void usage() {
printf("systax: pcap-test <interface>\n");
printf("sample: pcap-test wlan0\n");
} //use error message
int main(int argc, char *argv[]) {
if(argc != 2)
{
usage();
return -1;
}// 인자 잘못 입력시
char *interface = argv[1]; //interface를 인자로 준다.
char errbuf[PCAP_ERRBUF_SIZE]; //error buffer
pcap_t *pcap = pcap_open_live(interface, BUFSIZ, 1, 1000, errbuf);
if(pcap == NULL) {
fprintf(stderr, "pcap_open_live(%s) return null - %s\n",interface, errbuf);
return -1;
} // error
while (true) {
struct pcap_pkthdr *header;
const u_char = packet;
int res = pcap_next_ex(pcap, &header, &packet);
if(res == 0) continue;
if(res == PCAP_ERROR || res == PCAP_ERROR_BREAK) {
printf("pcap_next_ex return %d(%s)\n",res,pcap_geterr(pcap));
break;
} // error
//header->caplen은 byte 단위
for(int i=0; i<header->caplen; i++) {
printf("0x%02X ",packet[i]);
}
printf("%u bytes captured\n", header->caplen);
}
pcap_close(pcap);
}
간단히 주석을 달았는데,
저기 header->caplen은 byte 단위 뒷 부분쯤에 코드를 넣어서 조작한 뒤 출력해야 과제를 수행할 것 같다.
pcap_open_live()
pd = pcap_open_live(device, snaplen, PROMISCUOUS, 1000, ebuf);
위 함수는 실제 기기를 열어주는 기능을 하는 것으로 snaplen는 패킷당 저장할 바이스 수, 실제 datalink계층부터 패킷의 크기를 계산하여 원하는 부분만을 얻어오면 되는 것입니다. 헤더정보만을 보고싶은데 쓸데없이 데이타까지 받을 필요는 없겠죠. 데이터까지 보고싶으면 snaplen를 크게 하면 됩니다. PROMISCUOUS는 1이며 네트웍 디바이스에 오는 모든 패킷을 받겠다는 의미입니다. 이 모드를 자세하게 설명하면 Ethernet은 모든 패킷이 broadcasting되며 일단 모든 네트웍 디바이스는 동일 네트웍내의 다른 호스트의 패킷도 일단 접하게 됩니다. 그러나, 네트웍 디바이스는 기본적으로 자신의 패킷만을 받게끔 되어있습니다. 그러므로 다른 호스트의 패킷은 버리게 되는 것입니다. 그러나 promiscuous모드로 디바이스 모드를 바꾸게 되면 모든 패킷을 받아들이게 되는 것입니다. 모든 네트워크 모니터링 프로그램들은 모두 이 모드를 사용하게 됩니다. 세 번째 인자는 패킷이 버퍼로 전달될 때 바로 전달되는 것이 아니라 위에서 명시한 시간을 넘겼을 때나 버퍼가 다 채워졌을 때 응용프로그램으로 전달되는 것입니다.
pcap 간단한 함수들에 관한 사이트(https://wiki.kldp.org/KoreanDoc/html/Libpcap-KLDP/function.html)
송수신되는 packet을 capture하여 중요 정보를 출력하는 프로그램을 작성하라.
Ethernet Header의 src mac / dst mac
IP Header의 src ip / dst ip
TCP Header의 src port / dst port
Payload(Data)의 hexadecimal value(최대 8바이트까지만)
이것이 과제.
이것을 출력해본 것과 wireshark를 이용하여 캡처한 패킷을 비교하니 공부가 꽤 된듯 하다.
아 그리고 패킷을 비교하여 찾을 때 길이 가지고 찾았는데, filter로 frame.len == ~~ 한다는 것도 알았다.
개념적 확인
우선 헤더의 정보를 출력하기 위해선 헤더의 형식과 그 의미를 알아야한다.
프로토콜의 3요소로는 구문(Syntax), 의미(Semantic), 타이밍(Timing)이 있는데
구문은 형식을 의미하고, 의미는 그 정보에 대한 의미를 뜻한다. ->네트워크 관리사 딸 때 배웠다.
그래서 헤더의 정보부터 알아본다.
모두 과제할 때 참고했던 이미지들이다.
Ehternet, IP, TCP 순으로 이미지를 올려뒀다.
우리에게 보이는 패킷은
OSI 7Layer에서 2.Datalink layer 부분에서 전달되는 패킷을 보는 것이라고 생각이 드는데,
분석을 했을 때,
Ethernet header | IP header | TCP header | DATA
이런 꼴을 보였기 때문이다. 이는 아마
수업 내용에서 pcap이 layer 2까지 컨트롤 가능하다는 점에서 미루어 생각 가능했다.
다시 돌아와서, 잡아둔 패킷을 가지고 분석해보겠다.
<Ethernet header>
0x00 0x0C 0x29 0xAF 0xE6 0x58 -> Destination MAC address (6byte)
0x00 0x50 0x56 0xC0 0x00 0x08 -> Source MAC address (6byte)
0x08 0x00 -> Ehter Type : 상위 계층의 네트워크 프로토콜 데이터 형식, 종류 식별 (0x08 0x00이면 IPv4) (2byte)
14byte
--------------------------------------------------------
<IPv4 header>
0x45 = 0100(version : 4이므로 IPv4, 1 nibble = 4bit) 0101(Header length : 일반적으로 0101 = 5가 들어가는데, 이는 4byte(32bit) 단위이므로 4byte*5 = 20 byte 1nibble = 4bit) (1byte)
0x00 ->TOS(Type Of Service : 0이면 보통 데이터로 취급, 1은 비용최소화, 2는 신뢰성 최대, 4는 처리량 최소화... 등) (1byte)
0x00 0x84 -> Total Length (0x00 0x84 = 0x0084? == 132 아마 위 Ethernet header로 14byte는 빠져있는듯.) (2byte)
0x03 0x7A -> Identificaion (2byte)
0x40 0x00 -> IP Flags(3bit) + Fragment Offset (2byte)
0x80 -> TTL(Time To Live : hop 수가 들어감) (1byte)
0x06 -> Protocol : 상위 계층 프로토콜 6이면 TCP. (1byte)
0x71 0x08 -> Header checksum
0xC0 0xA8 0x82 0x01 -> Source IP Address (32bit) (4byte)
0xC0 0xA8 0x82 0x9F -> Destination IP Address (32bit) (4byte)
Soruce Address 인 0xC0 0xA8 0x82 0x01을 예로 들면
1100 0000 1010 1000 1000 0010 0000 0001인데 이를 4개로 자르므로
1100 0000 / 1010 1000/ 1000 0010/ 0000 0001
= 192.168.130.1
20byte
-----------------------------------------------------------------------------
<TCP header>
0xF7 0x3E -> Source Port (2byte)
0x00 0x16 -> Destination Port (2byte)
0xf7 0x3e로 예를 들면, 0xf73e = 63294 이므로 port number은 63294
0x1E 0x5E 0x3A 0x3B -> Sequence Number (4byte)
0x6F 0xF4 0x1A 0xE7 -> Acknowledge Number (4byte)
0x50 0x18 -> Offset : headerlength 여기도 보통 0101로 들어가서 ip header length와 같은 방법으로 계산됨 (4bit : 1nibble) + reserved (6bit) TCP Flags (6bit) (2byte)
0x10 0x0A -> Window : Sliding window와 관련있는듯. (2byte)
0xFF 0xCC -> check sum (2byte)
0x00 0x00 -> urgent pointer (2byte)
20byte
======================================================
여기부터는 데이터 같은데.
다음과 같이 암호화되어있는 경우에도.. 그냥 출력하면 되는건지 모르겠다.
0x7A 0xDA 0x2F 0x9D 0xAD 0x80 0xF4 0x3E (8byte) 문제에서 8byte까지만이라고 했으니 여기까지만 출력할 것이다.
0xD8 0x54 0x37 0xC5 0x49 0x5D 0xF8 0xFF
0x3E 0x3E 0x43 0x57 0xB8 0x04 0xBB 0xC4
0xAB 0x90 0xF7 0x0C 0x9C 0xBF 0xDE 0x56
0x26 0x89 0xD3 0xDD 0xCA 0xC6 0x88 0x51
0x95 0xA5 0x16 0x8A 0xC3 0xAE 0x43 0x3A
0x4F 0x4D 0xAE 0x0A 0x05 0x50 0x30 0x82
0xC6 0xFF 0x36 0xB5 0xCF 0x26 0xB5 0xE4
0x2F 0x47 0x3F 0x1B 0xF4 0xF3 0xEE 0xC6
0x80 0x8A 0xE6 0x11 0xA2 0x02 0xB2 0x26
0x78 0x06 0xCE 0x01 0xE3 0x8B 0x07 0xB8
0x65 0xCE 0x32 0x9C
146 bytes captured
이렇게 한 패킷이 분석된다.
이 정보들을 대충 알았으니 이를 가지고 만들 수 있을 것이다.
풀이
- 네트워크 패킷의 헤더를 구조체로 선언하여 사용하면 이후 작업이 편해지므로 libnet library를 검색하여 사용하도록 한다.
- libnet-headers.h 안에 있는, 사용하기 좋은 구조체들 :
- struct libnet_ethernet_hdr
- struct libnet_ipv4_hdr
- struct libnet_tcp_hdr
- libnet-headers.h 안에 있는, 사용하기 좋은 구조체들 :
이를 한 번 이용해보고자 한다.
http://manual.freeshell.org/libnet11/html/libnet-headers_8h-source.html
여기에서 소스를 확인해볼 수 있고
sudo apt-get install libnet1
하여 libnet을 사용할 것이다.
헤더파일을 보면 어떻게 사용할 지 알 수 있을 것이다.
MAC address는 배열에 저장하므로 그대로 인덱스를 돌려서 출력하도록 하고
ip address는
구조체에서 다음과 같이 생겼다.
그래서
들어가서 보면 in_add_t s_addr;
처럼 되어있어서
ip의 값을 보려면
ipv4 -> ip_src.s_addr처럼 써야했다. 이는 C에 대한 이해가 부족해서 왜 .으로 쓰는지.. s_addr을 왜 넣어야 하는지 잘 모르겠다.
그렇게 ip주소를 변환해서 출력하면 거꾸로 출력된다. mac에서는 1byte씩 배열에 넣어서 잘 출력되었는데 여기서는 여러 byte로 읽어들여서 little endian으로 변환된건지.. 잘 모르겠으나, ntohl 함수를 사용해서 거꾸로 변환했다.
tcp도 비슷한 방식으로 진행했으나 tcp port는 2byte이므로 ntohs함수를 사용해야 했다.
마지막으로 data를 읽어들이기 위해 packet의 시작점으로 header의 총 길이를 알아야 했다.
이렇게 해보면 5 5가 출력되어서 ip_hl과 th_off는 거꾸로 되어있지 않다는 걸 알 수 있었다.
ip_hl, th_off에 각각 4를 곱해서 byte수를 알아내도록 한다. (단위가 32bit 였으므로)
#include<pcap.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<libnet.h>
void usage() {
printf("systax: pcap-test <interface>\n");
printf("sample: pcap-test wlan0\n");
} //use error message
int main(int argc, char *argv[]) {
if(argc != 2)
{
usage();
return -1;
}// 인자 잘못 입력시
char *interface = argv[1]; //interface를 인자로 준다.
char errbuf[PCAP_ERRBUF_SIZE]; //error buffer
pcap_t *pcap = pcap_open_live(interface, BUFSIZ, 1, 1000, errbuf);
if(pcap == NULL) {
fprintf(stderr, "pcap_open_live(%s) return null - %s\n",interface, errbuf);
return -1;
} // error
while (true) {
struct pcap_pkthdr *header;
struct libnet_ethernet_hdr *ethernet;
struct libnet_ipv4_hdr *ipv4;
struct libnet_tcp_hdr *tcp;
const u_char *packet;
int res = pcap_next_ex(pcap, &header, &packet);
if(res == 0) continue;
if(res == PCAP_ERROR || res == PCAP_ERROR_BREAK) {
printf("pcap_next_ex return %d(%s)\n",res,pcap_geterr(pcap));
break;
} // error 처리
//header->caplen은 byte 단위
ethernet = (struct libnet_ethernet_hdr *)packet;
ipv4 = (struct libnet_ipv4_hdr *) (packet+sizeof(*ethernet));
tcp = (struct libnet_tcp_hdr *) (packet+sizeof(*ethernet)+sizeof(*ipv4));
//print scr mac, dst mac
printf("<Ethernet>\n");
printf("Soruce MAC address : ");
for(int i=0; i<ETHER_ADDR_LEN; i++) {
if(i == ETHER_ADDR_LEN-1) {
printf("%02X\n",ethernet->ether_shost[i]);
}
else {
printf("%02X:",ethernet->ether_shost[i]);
}
}
printf("Destination MAC address : ");
for(int i=0; i<ETHER_ADDR_LEN; i++) {
if(i == ETHER_ADDR_LEN-1) {
printf("%02X\n",ethernet->ether_dhost[i]);
}
else {
printf("%02X:",ethernet->ether_dhost[i]);
}
}
// if(ethernet->ether_type == 0x0800) // if next is ip protocol
printf("<IPv4>\n");
u_int8_t ip1, ip2, ip3, ip4;
uint32_t sip = ntohl(ipv4->ip_src.s_addr);
uint32_t dip = ntohl(ipv4->ip_dst.s_addr);
printf("Source IP address : ");
ip1 = (sip & 0xff000000) >> 24;
ip2 = (sip & 0x00ff0000) >> 16;
ip3 = (sip & 0x0000ff00) >> 8;
ip4 = (sip & 0x000000ff);
printf("%d.%d.%d.%d\n",ip1,ip2,ip3,ip4);
printf("Destination IP address : ");
ip1 = (dip & 0xff000000) >> 24;
ip2 = (dip & 0x00ff0000) >> 16;
ip3 = (dip & 0x0000ff00) >> 8;
ip4 = (dip & 0x000000ff);
printf("%d.%d.%d.%d\n",ip1,ip2,ip3,ip4);
printf("<TCP>\n");
printf("Source PORT : ");
printf("%d\n",ntohs(tcp->th_sport));
printf("Destination PORT : ");
printf("%d\n",ntohs(tcp->th_dport));
uint32_t hsize = 14+(ipv4->ip_hl)*4+(tcp->th_off)*4;
printf("Payload(Data) : ");
for(int i=hsize; i<hsize+8 && i<header->caplen; i++) {
printf("0x%02X ",packet[i]);
}
printf("\n\n");
}
pcap_close(pcap);
}
그리하여 완성했다.
https://github.com/63um3um/SF_Network_Study/tree/master/pcap_test
아직도 Makefile 만드는 법은 잘 모르겠다.