Members of Beist Research Group : beist and anonymous people
Members of Beist Study Group : beist, dars21, obhacker, passion, p-jackpot, jacaranda, cina
요약: 본 문서는 유저 레벨에서 패킷 헤더를 분석하는 방법과 특정 프로세스를 지정하여 볼 수 있는 패킷 스니핑 프로그램을 구현하는 방법을 소개합니다.
1. 개요
이 문서는 리눅스 환경을 대상으로 작성되었습니다. 본 문서에서 다룰, 특정 프로세스를 지정하여 패킷을 스니핑할 수 있는 프로그램은 현재 실행 중인 프로세스와 실행 시킬 프로그램에서 발생하는 패킷을 스니핑하는데 사용될 수 있습니다. 특히 본 문서에서 다루는 프로그램은 포트 기반으로 수행하는 스니핑이 아니라 프로세스 기반이기 때문에 한 프로세스에서 수시로 다른 포트를 사용할 경우에도 패킷 스니핑을 하기 유용합니다.
본 문서에서는 특정 프로세스에서 사용하고 있는 Port 번호를 알아내기 위하여 proc file system의 프로세스 정보 디렉토리, 네트워킹 정보 분석 방법, 데이터링크 액세스 인터페이스 시스템 콜을 이용하여 패킷의 헤더 정보를 알아내는 방법에 대해 다룰 것입니다.
2. 기술적인 내용
/proc 디렉토리에서 프로세스 id와 사용중인 포트번호를 추출한 후 해당 프로세스에서 발생하는 패킷의 헤더와 데이터를 분석하는 방법에 대해 알아보겠습니다. 본 문서에서 설명하는 프로그램은 2가지 방식으로 작동되는데, 프로그램을 실행할 때, 패킷을 감시할 프로세스 이름이나 프로세스 ID를 입력하는 방식으로 이루어집니다. (예: ./proc_sniff 880 or ./proc_sniff program_name) 다음은 프로세스가 사용중인 Port 번호를 알아내는데 필요한 /proc 디렉토리에 대한 구체적인 내용입니다. (본 문서에서는 주로 TCP에 대해서 설명합니다.)
(1) /proc 디렉토리 분석
/proc 디렉토리 내에는 프로세스 정보, 커널 정보, 장치 정보, 네트워킹 정보 등과 같이 다양한 시스템 정보를 포함하고 있지만, 우리가 관심 있게 봐야 할 부분은 /proc/PID/stat, /proc/net/tcp 파일과 /proc/PID/fd 디렉토리 입니다. 텍스트 에디터를 사용하여 파일을 읽어보면 프로세스 정보와 네트워킹 정보를 확인할 수 있습니다.
1) /proc/PID/stat 파일 읽기
프로그램 이름을 실행 인자로 받았을 경우 실행된 프로그램의 프로세스 id 값을 추출하기 위해서 /proc/PID/stat 파일을 사용해야 합니다.여기서 PID는 /proc 디렉토리 내에 있는 여러 프로세스 ID를 말합니다. 해당 PID(예:1872) 디렉토리 내의 stat파일은 다음과 같이 프로세스 상태에 관련된 내용을 가지고 있습니다.
[root@localhost root]# cat /proc/1872/stat 1872 (sshd) S 1742 1872 1872 0 -1 320 140 48 149 23 2153 982 5 0 15 0 0 0 16721 7065600 535 4294967295 134512640 1347876 80 3221219360 3221216780 3076439090 0 0 4096 73728 3222450524 0 0 17 0 0 0 2153 982 5 0 |
이 정보에서 bold체로 표시된 2번째 필드를 보면 프로세스 이름이 기록되어 있는 것을 확인할 수 있습니다. 이 정보를 토대로 프로그램 이름과 /proc 디렉토리 내에 있는 모든 PID 디렉토리의 stat 파일을 비교하여 프로세스 이름과 일치하는 파일을 찾아 프로세스 id를 추출할 수 있습니다.
2) /proc/PID/fd 디렉토리 내 파일 디스크립터 읽기
/proc/PID/fd 디렉토리 내에는 해당 PID(예:1872) 프로세스에서 사용하고 있는 파일 디스크립터의 정보가 있습니다. 아래 예시는/proc/1872/fd 디렉토리의 내용입니다.
[root@localhost fd]# ls –l 합계 0 lrwx------ 1 root root 64 3월 7 19:06 0 -> /dev/null lrwx------ 1 root root 64 3월 7 19:06 1 -> /dev/null lrwx------ 1 root root 64 3월 7 19:06 2 -> /dev/null lrwx------ 1 root root 64 3월 7 19:06 3 -> socket:[2177] |
위의 정보를 보면 1872 프로세스의 0(표준입력), 1(표준출력), 2(표준에러) 파일 디스크립터는 /dev/null로 링크되어 있습니다. 3번 파일 디스크립터는 소켓 파일 디스크립터이고 해당 디스크립터에 링크되어있는 socket:[2177]은 커널에서 관리하고 있는 소켓 번호인 것을 알 수 있습니다. 이 번호를 이용하여 다음에 설명할 /proc/net 디렉토리 정보와 함께 포트 번호를 추출하는데 사용할 수 있습니다. 1872 프로세스는 위의 1)에서 /proc/1872/stat 파일 정보를 통해 ssh 데몬인 것을 파악했으므로 3번 파일 디스크립터는 ssh 통신을 위한 파일 디스크립터로 예상할 수 있습니다.
3) /proc/net 정보 얻기
IPv4에 관련된 /proc/net/tcp와 IPv6에 관련된 /proc/net/tcp6 파일을 통하여 현재 커널에서 관리 중인 소켓의 정보를 파악할 수 있습니다. (본 문서는 IPv4를 대상으로 설명합니다.) 다음의 /proc/net/tcp 파일의 정보인데 ssh 데몬(소켓번호:2177)에서 사용하는 소켓의 정보만 표현하고 다른 소켓 정보는 생략하였습니다.
[root@localhost net]# cat tcp 0: 0100007F:0016 0100007F:0BC9 01 00000000:00000000 02:0008A351 00000000 0 02177 2 c2253280 43 4 1 3 -1 |
위의 정보에서 진하게 설정된 부분이 주의 깊게 볼 부분입니다.
0100007F:0016 – 서버 측 네트워크 정보를 나타냅니다.
(hex 값을 10진수로 변환하면 127.0.0.1:22)
0100007F:0BC9 – 클라이언트 측 네트워크 주소를 나타냅니다.
(hex 값을 10진수로 변환하면 127.0.0.1:3017)
2177 – socket 번호 입니다.
(local network에서 local network으로 연결했기 때문에 서버와 클라이언트의 주소가 같습니다.)
4) Port 번호 알아내기 과정
프로그램 이름을 실행 인자로 받았을 경우 해당 프로그램을 실행시킨 후에, 실행된 프로세스가 사용하는 Port 번호를 알아내기 위한 작업의 과정을 종합하면 다음과 같습니다.
1. 실행 인자(프로그램 이름)와 /proc 디렉토리에 모든 /proc/PID/stat 파일의 프로세스 이름 정보를 비교하여 PID (프로세스 id) 정보를 알아냅니다..
2. 알아낸 PID(예:1872)를 통해 /proc/1872/fd 의 소켓 파일 디스크립터에 링크된 소켓 번호(예:2177)를 알아냅니다.
3. /proc/net/tcp 파일 정보에서 링크된 소켓 번호(2177)을 검색하면 해당 소켓번호의 소켓 정보를 통해 port번호를 알아낼 수 있습니다.0100007F:0016 에서 “0016”이 port 번호이고, 10진수로 변환하면 “0022”가 port번호임을 알 수 있습니다.
(2) Packet 헤더 정보 얻기
위의 방법을 통해 Port번호를 알아냈으면 이제 해당 Port 번호에 대한 패킷을 스니핑할 수 있습니다. 패킷 헤더 정보를 스니핑하기 위해서pcap 라이브러리를 사용하여 프로그램을 작성할 수 있지만, 본 문서에서는 공개된 라이브러리를 사용하지 않고 직접 시스템 콜을 이용하여 패킷의 헤더를 수신하는 방법에 대하여 알아보겠습니다.
1) Ethernet 헤더 열기
TCP, UDP packet의 간단한 구조는 Ethernet 헤더 - IP 헤더 – TCP 혹은 UDP 헤더 순으로 이루어집니다. 먼저 소켓 시스템 콜을 이용하여 네트워크를 통해 들어온 모든 패킷을 수신하는 방법에 대해 구체적으로 알아보겠습니다.
Ethernet 헤더 데이터부터 수신하기 위해서는 소켓을 다음과 같은 설정으로 오픈합니다.
sock = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL)); |
리눅스에서 지원하는 모든 프로토콜을 처리하기 위해 socket 시스템 콜을 호출할 때 1번째 PF_PACKET을 줍니다. 다음은 리눅스에서 지원하는 프로토콜 패밀리에 대한 안내입니다.
프로토콜 체계 | 정의 |
PF_INET | IPv4 인터넷 프로토콜 |
PF_INET6 | IPv6 인터넷 프로토콜 |
PF_LOCAL | Local 통신을 위한 Unix 프로토콜 |
PF_PACKET | Low level socket을 위한 인터페이스 |
PF_IPX | IPX 노벨 프로토콜 |
2번째 인자인 소켓 타입에는 SOCK_PACKET을 줍니다. 이 타입은 모든 패킷을 수신할 수 있도록 하는 소켓의 데이터 전송 타입입니다. (참고로 TCP는 SOCK_STREAM, UDP는 SOCK_DGRAM을 사용합니다.)
3번째 인자인 프로토콜에는 ETH_P_ALL 을 줍니다. 이 프로토콜은 Ethernet 프로토콜을 통째로 다룰 수 있도록 하는 프로토콜입니다. 위의 소켓을 통하여 데이터를 수신할 경우 Ethernet 헤더 데이터도 수신할 수 있게 됩니다.
2) IP 헤더, TCP 헤더 열기
1)에서 설정한 socket 함수를 통해 수신된 데이터는 Ethernet 헤더와 IP 헤더, TCP 헤더 등 내용까지 포함되어 있습니다. Ethernet 헤더 바로 뒤에 IP 헤더가 붙기 때문에 IP헤더를 읽어오기 위해서는 수신된 데이터에서 Ethernet 헤더의 크기만큼 포인터를 이동해야 IP 헤더정보를 읽어올 수 있습니다.
/* packetFirst 변수는 수신한 패킷의 선두 포인터 값을 가지고 있음 */ packetPointer = packetFirst; /* Ethernet 헤더 크기만큼 이동 */ packetPointer = packetPointer + sizeof (struct ether_header); struct ip *ip_header = (struct ip *) packetPointer; |
2-1) ip_header 포인터 변수를 통해 다음과 같은 ip 구조체의 멤버변수 데이터를 얻어 올 수 있습니다.
/* 헤더의 위치는 “/usr/include/netinet/” 입니다 */ #include <netinet/ip.h> ip_header->ip_hl /* 헤더길이 */ ip_header->ip_v; /* 버전 */ ip_header->ip_tos; /* type of service */ ip_header->ip_len; /* 전체 길이 */ ip_header->ip_id; /* 식별id */ ip_header->ip_off; /* fragment 옵셋 필드 */ ip_header->ip_ttl; /* time to live */ ip_header->ip_p; /* protocol */ ip_header->ip_sum; /* checksum */ ip_header->ip_src /* 출발 ip주소 */ ip_header->ip_dst; /* 도착 ip주소 */ |
IP 헤더의 크기만큼 포인터를 이동하면 TCP 헤더 정보를 읽어올 수 있습니다. IP 헤더의 크기는 ip_header->ip_hl을 통해 알아낼 수 있는데 이 필드는 4비트 길이를 갖지만 단위가 4바이트 단위로 되어 있기 때문에 ip_header->ip_hl 값에 4를 곱해준 값이 IP 헤더의 크기가 됩니다. 일반적으로 특별한 옵션이 없는 한 ip_header->ip_hl의 값은 5입니다. 즉 이 값에 4를 곱하면 IP 헤더의 크기는 20바이트 크기인 것을 알 수 있습니다.
/* packetPointer 변수는 현재 IP 헤더정보를 가리키는 포인터 값을 가지고 있음 */ /* IP 헤더 크기만큼 이동 */ packetPointer = packetPointer + ((int)(ip->ip_hl) << 2); /* 곱을 쉬프트 연산으로 표현 */ tcp_header = (struct tcphdr *) packetPointer; |
2-2) tcp_header 포인터 변수를 통해 다음과 같은 tcphdr 구조체의 멤버 변수 데이터를 얻어 올 수 있습니다.
/* 헤더의 위치는 “/usr/include/netinet/” 입니다 */ #include <netinet/tcp.h> tcp_header->th_sport; /* 출발지 port */ tcp_header->th_dport; /* 목적지 port */ tcp_header->th_seq; /* sequence 번호 */ tcp_header->th_ack; /* acknowledgement 번호 */ tcp_header->th_x2; /* (unused) */ tcp_header->th_off; /* 데이터 옵셋*/ tcp_header->th_flags; /* 제어 플래그 */ tcp_header->th_win; /* window 값*/ tcp_header->th_sum; /* checksum 값*/ tcp_header->th_urp; /* 긴급 포인터 */ |
TCP 헤더 이후에 오는 내용은 실제 데이터 부분이 됩니다.
2-3) UDP 헤더일 경우 udp_header 포인터 변수를 사용하여 다음과 같은 udphdr 구조체의 멤버변수 데이터를 얻어 올 수 있습니다.
/* 헤더의 위치는 “/usr/include/netinet/” 입니다 */ #include <netinet/udp.h> udp_header->uh_sport; /* 출발지 port */ udp_header->uh_dport; /* 목적지 port */ udp_header->uh_ulen; /* udp 길이*/ udp_header->uh_sum; /* udp 체크섬*/ |
2-4) 다음은 TCP 헤더, UDP 헤더 뒤에 붙는 실제 데이터 포인터 위치를 가져오는 부분에 대한 설명입니다.
TCP 헤더일 경우 TCP 헤더의 크기를 구하기 위해서 tcphdr 구조체의 th_off 구조체 변수를 사용합니다. 이 변수 값의 수치 단위는 4바이트이므로 4를 곱하여 tcp 헤더의 크기를 구합니다. 다음과 같이 현재 packetPointer 포인터의 위치를 tcp헤더의 크기만큼 옮기면packetPointer 포인터 변수는 실제 데이터의 선두 위치를 가리키게 됩니다.
packetPointer = packetPointer + ((int)(tcp->th_off) << 2); |
UDP 헤더는 tcp헤더와 다르게 현재 packetPointer 포인터의 위치를 udphdr헤더의 사이즈만큼 옮기면 packetPointer 포인터 변수는 실제 데이터의 선두 위치를 가리키게 됩니다.
다음과 같이 현재 packetPointer 포인터의 위치를 UDP헤더의 크기만큼 옮기면 packetPointer 포인터 변수는 실제 데이터의 선두 위치를 가리키게 됩니다.
packetPointer = packetPointer + sizeof(struct udphdr); |
(3) 프로세스가 사용 중인 Port 번호를 추출하는 소스 설명
다음 소스는 “(1) /proc 디렉토리 분석” 에서 언급하였던 /proc 디렉토리 분석을 통하여 프로세스에서 사용하는 port 번호를 추출하는 부분에 대한 소스코드입니다.
1 /* 2 /proc/net/tcp 정보에서 소켓 device 값과 비교하여 해당 소켓의 port번호를 3 얻어온다 4 */ 5 int SockMapPort(char* ProcTcp, char* sockdevnum, unsigned short* processPortNum) 6 { 7 DIR *directory; 8 struct dirent *entry = NULL; 9 char buffer[256]; 10 char *parsing; 11 char hexPortNum[40]; 12 int culcount; 13 int linecount; 14 int find; 15 FILE *fp; 16 17 culcount = linecount = find = 0; 18 fp = fopen(ProcTcp, "r"); 19 if (fp == NULL) 20 { 21 perror("/proc/net/tcp(6) error"); 22 exit(0); 23 } 24 25 linecount = 0; 26 while(fgets(buffer,256, fp) != NULL) 27 { 28 if(linecount == 0) 29 { 30 linecount++; 31 continue; 32 } 33 culcount = 0; 34 parsing = strtok(buffer, ": "); 35 while((parsing = strtok(NULL, ": ")) != NULL) 36 { 37 if(culcount == 1) 38 { 39 strcpy(hexPortNum, parsing); 40 } 41 42 if(!strncmp(parsing, sockdevnum, strlen(sockdevnum))) 43 { 44 /*puts("동일"); 45 printf("p %s s %s\n", parsing, sockdevnum);*/ 46 find = 1; 47 break; 48 } 49 else 50 { 51 } 52 culcount++; 53 } 54 if(find) break; 55 } 56 57 if(find){ 58 *processPortNum = HexToDecimal(hexPortNum); 59 return 1; 60 } 61 else 62 return 0; 63 } 64 65 unsigned short FindPortNum(char* processID) 66 { 67 DIR *directory; 68 struct dirent *entry = NULL; 69 char procBuff[40]; 70 char sockBuff[40]; 71 char retBuff[40]; 72 char *sockdev; 73 char *sockdevnum; 74 char ProcTcp[20]; 75 int ret; 76 unsigned short processPortNum; 77 78 79 sprintf(procBuff,"/proc/%s/fd", processID); 80 81 if ((directory = opendir(procBuff)) == NULL) 82 { 83 perror("/proc opendir error"); 84 exit(0); 85 } 86 87 while((entry = readdir(directory)) != NULL) 88 { 89 if (strcmp(entry->d_name, ".") !=0 && strcmp(entry->d_name, "..") != 0 && 90 strcmp(entry->d_name, "0") != 0 && strcmp(entry->d_name, "1") !=0 && strcmp(entry->d_name, "2") != 0 91 { 92 sprintf(sockBuff,"/proc/%s/fd/%s", processID, entry->d_name); 93 readlink(sockBuff, retBuff, sizeof(retBuff)); 94 sockdev = strtok(retBuff, ":["); 95 if(strcmp(sockdev, "socket")) 96 continue; 97 98 sockdevnum = strtok(NULL, ":[]"); 99 break; 100 } 101 } 102 103 sprintf(ProcTcp, "/proc/net/tcp"); 104 ret = SockMapPort(ProcTcp, sockdevnum, &processPortNum); 105 if(ret == 0) // "/proc/net/tcp"에 정보가 없을 경우 106 { 107 sprintf(ProcTcp, "/proc/net/tcp6"); 108 SockMapPort(ProcTcp, sockdevnum, &processPortNum); 109 } 110 111 closedir(directory); 112 return processPortNum; 113 } 114 115 /* 116 * 프로세스 정보를 가져온다. 117 * 그러기 위해서 /proc/[PID]/stat파일을 읽어들이고 이를 118 * 필드별로 파싱한다. 파싱은 " "문자를 기준으로 한다. 119 * 또한 프로세스를 생성한 유저 이름도 얻어온다. 120 */ 121 unsigned short FindProcInfo(char* process) 122 { 123 DIR *directory; 124 struct dirent *entry = NULL; 125 char proc_file[40]; 126 char proc_name[20]; 127 int processFlag = 0; 128 unsigned short port; 129 130 if (IsDigit(process)) //process가 숫자이면 131 { 132 processFlag = 1; 133 } 134 else 135 { 136 sprintf(proc_name, "(%s)", process); 137 } 138 139 140 if(processFlag == 0) //실행인자가 프로세스명일 경우 141 { 142 system(process); //실행인자 프로그램을 실행 시킴 143 144 // proc 디렉토리를 열어서 파일(디렉토리포함)의 리스트를 145 // 얻어온다. 146 if ((directory = opendir("/proc")) == NULL) 147 { 148 perror("/proc opendir error"); 149 exit(0); 150 } 151 152 while((entry = readdir(directory)) != NULL) 153 { 154 if (strcmp(entry->d_name, ".") !=0 && strcmp(entry->d_name, "..") != 0) 155 { 156 sprintf(proc_file,"/proc/%s/stat", entry->d_name); 157 // stat 파일이 존재하는지 확인하고 확인하고 158 if (access(proc_file, F_OK) != 0) 159 { 160 continue; 161 } 162 163 // 디렉토리 이름이 숫자인지 확인한다. 164 // 디렉토리 이름이 숫자라면 이를 파싱한다. 165 if (IsDigit(entry->d_name)) 166 { 167 if(ProcParser(proc_file, proc_name)) //디렉토리가 프로세스의 디렉토리 정보인지를 확인 168 { 169 port = FindPortNum(entry->d_name); 170 printf("processName : %s\nport : %d\n", process, port); 171 break; 172 } 173 } 174 else 175 { 176 } 177 } 178 } 179 180 closedir(directory); 181 } 182 else //실행인자가 프로세스id일 경우 183 { 184 port = FindPortNum(process); 185 printf("processID : %s\nport : %d\n", process, port); 186 } 187 188 return port; 189 } 190 191 /* 192 * 파일이름이 숫자인지 확인한다. 193 */ 194 int IsDigit(char *str) 195 { 196 int i; 197 for (i = 0; i < strlen(str); i++) 198 { 199 if (isdigit(str[i])==0) 200 return 0; 201 } 202 return 1; 203 } 204 205 /* 206 "/proc/pid/fd/stat"에서 프로세스명 정보를 비교함 207 */ 208 int ProcParser(char *proc_file, char *proc_name) 209 { 210 FILE *fp; 211 char buf[512] = {0x00,}; 212 int pid; 213 char *pname; 214 215 fp = fopen(proc_file, "r"); 216 if (fp == NULL) 217 { 218 perror("error : "); 219 exit(0); 220 } 221 222 fgets(buf, 511, fp); 223 pid = atoi(strtok(buf, " ")); 224 pname = (char*)strtok(NULL, " "); 225 fclose(fp); 226 if(strncmp(pname, proc_name, strlen(proc_name))) 227 { 228 return 0; //프로세스이름과 일치하지 않음 229 } 230 else 231 { 232 return 1; //프로세스이름과 일치 233 } 234 235 } |
1) 함수 요약 설명
- FindProcInfo : 프로세스 id를 매개변수로 받았을 경우에는 바로 FindPortNum 함수에 프로세스 id를 넘겨 포트번호를 추출합니다.프로그램 명을 매개변수로 받았을 경우에는 프로그램을 실행시키고, /proc 디렉토리를 검색하여 ProcParser 함수로 프로세스 id를 알아냅니다. 이 프로세스 id를 FindPortNum 함수에 대입하여 port 번호를 추출합니다.
- FindPortNum : 인수로 받은 프로세스 id를 토대로 /proc/pid/fd 디렉토리를 읽어들여 0, 1, 2 기본 파일 디스크립터를 제외한 파일 디스크립터 중에 심볼릭 링크된 값이 socket으로 링크된 디바이스 번호를 SockMapPort 함수에 매개변수로 넘겨 소켓의 포트 번호를 추출합니다.
- SockMapPort : /proc/net/tcp나 /proc/net/tcp6 파일을 한 라인씩 읽어 들여 인자로 받은 디바이스 번호와 일치한 라인을 찾습니다. 찾은 라인의 3번째 필드 값이 포트 번호입니다. 이 값은 16진수로 되어 있기 때문에 10진수로 변환 후 포트번호를 리턴합니다.
- ProcParser : 인수로 받은 /proc/pid/fd/stat 파일의 데이터에 프로세스 이름이 일치하는 확인하여 프로세스 id를 찾습니다.
- IsDigit : 인수로 받은 str이 숫자인지 확인합니다.
2) 소스 해설
5행 : SockMapPort 함수는 “/proc/net/tcp”나 “/proc/net/tcp6”을 가리키는 문자열과 소켓 디바이스 번호, 저장될 프로세스 포트 번호 변수를 인수로 받습니다.
18행 : “/proc/net/tcp” 파일을 오픈합니다.
26행 ~ 55행 : “/proc/net/tcp” 의 데이터를 한 라인씩 읽어들여 “:”로 토큰한 값 중에 소켓 디바이스 값과 일치하는지 비교하고, 일치하는 값을 찾으면 저장해 놓은 hexPortNum 값이 16진수로 표현된 포트번호입니다.
57행 ~ 60행 : 16진수로 표현된 포트번호를 10진수로 변경합니다.
65행 : FindPortNum 함수는 문자열로 표현된 프로세스 id를 인수로 받습니다.
79행 : 프로세스id 문자열을 추가하여 “/proc/pid/fd” 문자열 값인 procBuf를 만듭니다.
81행 : opendir 함수로 procBuff 디렉토리를 오픈하여 디렉토리 포인터를 directory 변수에 저장합니다.
87행 ~ 101행 : readdir 함수로 directory 변수의 디렉토리 내에 있는 파일을 읽어 들입니다. 파일이름이 “.”, “..”, “0”, “1”, “2”를 제외한 파일을 readlink 함수를 사용하여 심볼릭 링크된 값을 retBuff에 저장합니다. retBuff에 저장된 값이 소켓 디바이스면 “socket:[802831]”로 표현되기 때문에 “:[]”로 토큰하여 중괄호 안에 있는 소켓 디바이스 값을 sockdevnum 변수에 저장합니다.
103행 : “/proc/net/tcp” 문자열을 ProcTcp 변수에 저장합니다.
104행 : 프로세스가 사용중인 포트번호를 추출하기 위해 SockMapPort 함수를 호출합니다.
105행 ~ 109행 : 소켓 디바이스 번호와 맵핑 된 포트번호가 없으면 “/proc/net/tcp6”를 첫번째 매개변수로 SockMapPort를 재호출합니다.
111행 : 오픈한 디렉토리를 닫습니다.
112행 : 프로세스가 사용 중인 포트 번호를 리턴합니다.
121행 : FindProcInfo 함수는 프로그램 명이나 process id를 인자로 받습니다.
130행 ~ 137행 : IsDigit 함수를 사용하여 인수로 받은 process 값이 숫자인지 체크합니다. 숫자이면 프로세스 id이기 때문에processFlag 값을 1로 설정한다. 숫자가 아니면 프로그램 명이므로 나중에 proc/pid/stat와 비교 과정에서 프로세스 id를 알아내기 위해 프로그램 명에 “()”를 추가합니다.
142행 : 인자로 받은 process 변수의 데이터가 프로그램 명인 경우에는 프로그램을 실행 시킵니다.
146행 ~ 149행 : opendir함수를 사용하여 “/proc” 디렉토리를 오픈합니다.
152행 ~ 178행 : “/proc” 디렉토리를 읽어 들여 ProcParser 함수에 “/proc” 디렉토리 내에
“/proc/pid/stat” 파일과 프로세스 이름을 매개변수로 넘겨 프로세스 id를 찾습니다. 프로세스
Id 를 찾으면 FindPortNum 함수에 프로세스 id를 매개변수로 넘겨 포트번호를 추출합니다.
180행 : 오픈한 “/proc” 디렉토리를 닫습니다.
182행 ~ 186행 : 인자로 받은 process 변수의 데이터가 프로세스 id 이므로 바로
FindPortNum 함수에 프로세스 id를 매개변수로 넘겨 포트번호를 추출합니다.
188행 : 포트번호를 반환합니다.
194행 ~ 203행 : 인자로 받은 str 변수의 데이터가 숫자 문자열인지를 체크합니다.
208행 : ProcParser 함수는 “/proc/pid/fd/stat” 문자열을 가리키는 변수와 프로그램 명을 인자로 받습니다.
215행 : “/proc/pid/fd/stat” 파일을 오픈합니다.
222행 ~ 224행 : 오픈한 파일 포인터로부터 데이터를 읽어 들여 공백을 구분자로 2번 파싱합니다. 첫 번째 파싱하여 얻은 데이터는 pid이고, 두 번째 파싱하여 얻은 데이터는 프로세스 이름입니다.
226행 ~ 233행 : 파싱하여 얻은 프로세스명(pname)과 프로그램 명(proc_name)이 일치하는지 확인하여 일치 여부를 반환합니다.
(4) Packet Header 추출하는 부분 소스 설명
다음 소스는 “(2) Packet 헤더 정보 얻기” 에서 언급하였던 소켓을 통해 ethernet 헤더부터 수신한 데이터를 분석하는 부분에 대한 소스코드입니다.
1 void packetCapture(unsigned short portnum, FILE *wfp) 2 { 3 struct ether_header *eth; 4 struct ether_arp *arp; 5 struct ip *ip; 6 struct icmp *icmp; 7 struct tcphdr *tcp; 8 struct udphdr *udp; 9 int s; 10 int len; 11 int c; // getopt()에서 취득한 문자 12 char buff[MAXSIZE]; // 데이터 수신 버퍼 13 char *packetPointer; // 헤더의 선두를 나타내는 작업용 포인터 14 char *packetFirst; // 패킷의 선두를 나타내는 포인터 15 char buf[BUFSIZE]; 16 int i; 17 char data[BUFSIZE]; 18 19 if ((s = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) { 20 perror("socket"); 21 exit(1); 22 } 23 24 while (1){ 25 if ((len = read(s, buff, MAXSIZE)) < 0) { 26 perror("read"); 27 exit(1); 28 } 29 /* Ethernet 헤더선두에 포인터를 세트 */ 30 packetPointer = packetFirst = buff; 31 32 eth = (struct ether_header *) packetPointer; 33 packetPointer = packetPointer + sizeof (struct ether_header); 34 35 36 if (ntohs(eth->ether_type) == ETHERTYPE_IP) { 37 ip = (struct ip *) packetPointer; 38 39 packetPointer = packetPointer + ((int)(ip->ip_hl) << 2); 40 41 switch (ip->ip_p) { 42 case IPPROTO_TCP: 43 tcp = (struct tcphdr *) packetPointer; 44 if((portnum == ntohs(tcp->th_sport)) || (portnum == ntohs(tcp->th_dport))){ 45 46 print_ip(ip, wfp); 47 print_tcp(tcp, wfp); 48 dump_packet(packetFirst, len, wfp); 49 printf("\n"); 50 sprintf(buf, "\n"); 51 fputs(buf, wfp); 52 fflush(stdout); 53 } 54 break; 55 case IPPROTO_UDP: 56 udp = (struct udphdr *) packetPointer; 57 packetPointer = packetPointer + sizeof(struct udphdr); 58 if((portnum == ntohs(udp->uh_sport)) || (portnum == ntohs(udp->uh_dport))){ 59 print_ip(ip, wfp); 60 print_udp(udp); 61 dump_packet(packetFirst, len, wfp); 62 printf("\n"); 63 sprintf(buf, "\n"); 64 fputs(buf, wfp); 65 fflush(stdout); 66 } 67 break; 68 } 69 } 70 } 71 72 } 73 74 75 76 void dump_packet(unsigned char *buff, int len, FILE *fp) 77 { 78 int i, j; 79 char buf[BUFSIZE]; 80 81 printf("Packet Dump:\n"); 82 sprintf(buf, "Packet Dump\n"); 83 fputs(buf, fp); 84 for (i = 0; i < len; i += 16) { 85 // 16진수 덤프 86 for (j = i; j < i + 16 && j < len; j++) { 87 printf("%02x", buff[j]); 88 sprintf(buf, "%02x", buff[j]); 89 fputs(buf, fp); 90 91 if (j % 2 == 1){ 92 printf(" "); 93 sprintf(buf, " "); 94 fputs(buf, fp); 95 } 96 } 97 98 // 제일 마지막 행의 끝 수를 정렬 99 if (j == len && len % 16 != 0) 100 for (j = 0; j < 40 - (len % 16)*2.5; j++){ 101 printf(" "); 102 sprintf(buf, " "); 103 fputs(buf, fp); 104 } 105 printf(": "); 106 sprintf(buf, ": "); 107 fputs(buf, fp); 108 109 // 아스키 문자 표시 110 for (j = i; j < i + 16 && j < len; j++) { 111 if ((buff[j] >= 0x20) && (buff[j] <= 0x7e)){ 112 putchar(buff[j]); 113 sprintf(buf, "%c", buff[j]); 114 fputs(buf, fp); 115 } 116 else{ 117 printf("."); 118 sprintf(buf, "."); 119 fputs(buf, fp); 120 } 121 } 122 printf("\n"); 123 fputs("\n", fp); 124 } 125 } 126 127 128 void tcp_ftoa(int flag, char *str) 129 { 130 static char f[][3] = {"URG", "ACK", "PSH", "RST", "SYN", "FIN"}; //tcp플래그를 나타내는 문자 131 int length = 0; 132 u_int mask = 1 << 5; 133 int i; 134 135 for(i = 0;i<6;i++) { 136 if (((flag << i) & mask) != 0) 137 { 138 strncat(str, f[i], 3); 139 strncat(str, " ", 1); 140 } 141 else 142 { 143 144 } 145 } 146 147 length = strlen(str); 148 str[length] = '\0'; 149 150 } 151 152 153 154 void print_ip(struct ip *ip, FILE *fp) 155 { 156 char buf[BUFSIZE]; 157 printf("Protocol: IP\n"); 158 sprintf(buf, "Protocol: IP\n"); 159 fputs(buf, fp); 160 printf("+-------------------------+--------+----------------+\n"); 161 sprintf(buf, "+-------------------------+--------+----------------+\n"); 162 fputs(buf, fp); 163 printf("| Identifier: %5u| TTL:%3u| Checksum: %5u|\n", 164 ntohs(ip->ip_id), ip->ip_ttl, ntohs(ip->ip_sum)); 165 sprintf(buf, "| Identifier: %5u| TTL:%3u| Checksum: %5u|\n", 166 ntohs(ip->ip_id), ip->ip_ttl, ntohs(ip->ip_sum)); 167 fputs(buf, fp); 168 printf("+------------+------------+-------------------------+\n"); 169 sprintf(buf, "+------------+------------+-------------------------+\n"); 170 fputs(buf, fp); 171 printf("| Source IP Address: %15s|\n", 172 inet_ntoa(*(struct in_addr *)&(ip->ip_src))); 173 sprintf(buf, "| Source IP Address: %15s|\n", 174 inet_ntoa(*(struct in_addr *)&(ip->ip_src))); 175 fputs(buf, fp); 176 printf("+---------------------------------------------------+\n"); 177 sprintf(buf, "+---------------------------------------------------+\n"); 178 fputs(buf, fp); 179 printf("| Destination IP Address: %15s|\n", 180 inet_ntoa(*(struct in_addr *)&(ip->ip_dst))); 181 sprintf(buf, "| Destination IP Address: %15s|\n", 182 inet_ntoa(*(struct in_addr *)&(ip->ip_dst))); 183 fputs(buf, fp); 184 printf("+---------------------------------------------------+\n"); 185 sprintf(buf, "+---------------------------------------------------+\n"); 186 fputs(buf, fp); 187 } |
1) 함수 요약 설명
- packetCapture : 소켓을 Ethernet 헤더부터 수신할 수 있는 옵션으로 오픈하고, 수신한 데이터를 각 헤더 별로 포인터 변수를 이용하여 분리합니다. Ip 헤더 포인터를 통해 ip를 체크하고, tcp 헤더 포인터를 통해 포트번호를 체크하여 print_ip, print_tcp, dump_packet 함수를 이용하여 각각의 헤더와 데이터 내용을 표준출력과 인자로 받은 파일포인터로 출력합니다. (파일명 예 :process_9553) – 숫자는 pid 나타냅니다.
- dump_packet : 16진수로 표현된 패킷의 데이터 버퍼의 내용을 ASCII 문자로 변환하여 표준출력과 인자로 받은 파일포인터로 출력합니다.
- tcp_ftoa : tcp 헤더 중에 제어 플래그 비트를 플래그 문자열로 변경합니다.
- print_ip : ip 구조체의 멤버변수를 표준출력과 파일 포인터로 출력합니다.
2) 소스 해설
1행 : packetCapture함수는 포트번호와 수신한 패킷 데이터를 저장할 파일 포인터를 인자로 받습니다.
19행 : 패킷의 Ethernet 헤더부터 수신 하기 위해 socket 함수에 SOCK_PACKET, ETH_P_ALL옵션을 매개변수로 대입합니다.
25행 : 오픈한 소켓을 통하여 데이터를 수신하여 buff에 저장합니다.
30행 : Ethernet 헤더 데이터부터 읽기 위해 buff의 선두 주소와 packetPointer 포인터를 일치시킵니다.
32행 : 현재 packetPointer 포인터 변수는 Ethernet 헤더 데이터를 가리키고 있기 때문에 ether_header 구조체 포인터로 캐스팅 하여ether_header 구조체 포인터인 eth 포인터 변수에 대입합니다.
33행 : Ethernet 헤더 다음에 위치한 ip 헤더 데이터를 가리키도록 하기 위해 ether_header 구조체 크기만큼 packetPointer 포인터를 이동시킵니다.
36행 : ethrnet 구조체 포인터인 eth의 타입이 ETHERTYPE_IP(IP타입)인지 확인한다. ip타입 이 외에 arp 타입인 경우도 있습니다.
37행 : 현재 packetPointer 포인터 변수는 ip 헤더 데이터를 가리키고 있기 때문에 ip 구조체 포인터로 캐스팅하여 ip 구조체 포인터인 ip포인터 변수에 대입합니다.
39행 : ip 헤더 다음에 위치한 tcp 헤더를 가리키도록 하기 위해 ip 헤더 길이만큼 포인터를 이동 시킵니다. Ip 헤더 길이는 ip 구조체의ip_hl 멤버 변수에 값을 4배 한 값입니다.
42행 ~ 43행 : ip 구조체 포인터인 ip의 프로토콜 타입이 TCP타입(IPPROTO_TCP)일 경우 현재 packetPointer 포인터 변수는 tcp 헤더 데이터를 가리키고 있기 때문에 tcphdr 구조체 포인터로 캐스팅하여 tcphdr 구조체 포인터인 tcp 포인터 변수에 대입합니다.
44행 ~ 54행 : tcphdr 구조체 포인터의 출발지 포트번호(th_sport)와 목적지 포트번호(th_dport)가 인자로 받은 모니터링 할 포트번호(portnum)와 일치하는지 확인합니다. 동일 포트번호를 담은 패킷 데이터이면 print_ip함수를 사용하여 ip헤더를 출력하고,print_tcp 함수를 사용하여 tcp헤더를 출력합니다. dump_packet 함수를 사용하여 수신받은 모든 버퍼의 내용을 16진수와 ASCII 값으로 표준출력과 파일 포인터로 출력합니다.
55행 ~ 56행 : ip 구조체 포인터인 ip의 프로토콜 타입이 UDP타입(IPPROTO_UDP)일 경우 현재 packetPointer 포인터 변수는 udp 헤더 데이터를 가리키고 있기 때문에 udphdr 구조체 포인터로 캐스팅하여 udphdr 구조체 포인터인 udp 포인터 변수에 대입합니다.
58행 ~ 67행 : udphdr 구조체 포인터의 출발지 포트번호(th_sport)와 목적지 포트번호(th_dport)가 인자로 받은 모니터링 할 포트번호(portnum)와 일치하는지 확인합니다. 동일 포트번호를 담은 패킷 데이터이면 print_ip함수를 사용하여 ip헤더를 출력하고,print_udp를 사용하여 udp헤더를 출력합니다. dump_packet 함수를 사용하여 수신받은 모든 버퍼의 내용을 16진수와 ASCII 값으로 표준출력과 파일 포인터로 출력합니다.
76행 : dump_packet 함수는 패킷 헤더와 데이터의 내용을 가리키고 있는 buff 포인터와 buff의 내용을 저장할 파일 포인터를 인자로 받습니다.
84행 ~ 123행 : 16바이트를 간격으로 수신한 데이터 크기만큼 Ethernet 헤더부터 데이터 내용까지 16진수와 ASCII 문자로 표준출력과 인자로 받은 파일 포인터로 출력합니다.
128행 : tcp_ftoa 함수는 tcp 헤더의 제어 플래그 비트 값과 제어 플래그 문자열을 저장할 문자열 포인터를 인자로 받습니다.
130행 : tcp 제어 플래그인 "URG", "ACK", "PSH", "RST", "SYN", "FIN" 문자열을 f배열에 저장합니다.
132행 : Tcp 제어 플래그는 6비트로 표현되기 때문에 6번째 비트를 기준으로 AND 마스크할 변수를 정의합니다.
135행 ~ 145행 : flag 변수를 0부터 5까지 왼쪽 쉬프트하여 mask 변수로 AND 마스크하면 어느 비트가 1로 세팅 되었는지 확인할 수 있습니다. 1로 세팅된 제어 플레그를 맵핑된 tcp플래그 문자열로 바꾸어 str 문자열 변수에 복사합니다.
154행 : print_ip 함수는 ip 구조체 포인터 변수와 ip헤더의 내용을 저장할 파일 포인터 변수를 인자로 받습니다.
163행 : ip헤더의 identifier, TTL, Checksum 내용을 ip 구조체의 멤버 변수를 통해 추출합니다. 각각의 데이터에는 ip구조체의 ip_id, ip_ttl, ip_sum 멤버 변수 값이 대입됩니다.
171행 ~ 179행 : ip헤더의 출발지 ip, 목적지 ip 내용을 ip구조체의 멤버 변수를 통해 추출합니다. 각각의 데이터에는 ip구조체의 ip_src, ip_dst 멤버변수 값이 대입됩니다.
3. 맺음말
지금까지 /proc 디렉토리에 담긴 정보를 분석, 이용하여 특정 프로세스가 사용 중인 포트 번호를 추출하는 방법과 socket 시스템 콜을 이용하여 TCP/IP 헤더와 실제 데이터를 분석하는 방법에 대해 공부하고, 실제 구현하는 방법까지 알아보았습니다. 본 프로그램은 /proc 디렉토리의 프로세스 정보와 네트워크 정보만을 다루었지만 /proc 디렉토리에는 시스템에 관련된 거의 대부분의 정보를 가지고 있기 때문에 시스템 모니터링 프로그램이나 시스템 성능 분석 프로그램 등을 개발하는데 유용하게 사용될 수 있습니다.
본 문서에서는 이와 유사한 프로그램을 만들기 위한 제작 과정을 소개하기 위한 목적으로 작성되었기 때문에 기능이 미약하고 불안전할 수도 있습니다. 문서에서 설명한 프로그램에 스니핑 된 패킷들의 데이터를 프로토콜, IP 주소 별로 분류하거나 기타 통계 기능 등을 추가한다면 더욱 유용하게 사용할 수 있을 것으로 생각합니다.
----------- 전체 소스코드 -----------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <netdb.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <net/ethernet.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#define __FAVOR_BSD
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <linux/sockios.h>
#define MAXSIZE 4096
#define BUFSIZE 128
void tcp_ftoa(int flag, char *str);
void print_ip(struct ip *ip, FILE *fp);
void print_tcp(struct tcphdr *tcp, FILE *fp);
void print_udp(struct udphdr *udp);
void dump_packet(unsigned char *buff, int len, FILE *fp);
int ProcParser(char *proc_file, char *proc_name);
int IsDigit(char *str);
int HexToDecimal(char *buffer);
int SockMapPort(char* ProcTcp, char* sockdevnum, unsigned short* processPortNum);
unsigned short FindPortNum(char* processID);
unsigned short FindProcInfo(char* process);
void packetCapture(unsigned short portnum, FILE *wfp);
void help(char *cmd);
int main(int argc, char **argv)
{
int c;
char data[BUFSIZE];
unsigned short portnum;
FILE *wfp;
while ((c = getopt(argc, argv, "i:p:")) != EOF) {
switch (c) {
case 'i':
portnum = FindProcInfo(argv[2]);
break;
case 'p' :
portnum = FindProcInfo(argv[2]);
break;
case 'h':
help(argv[0]);
default:
help(argv[0]);
exit(1);
break;
}
}
if(argc < 2) {
fprintf(stderr, "parameter errror!\n");
help(argv[0]);
exit(1);
}
sprintf(data, "process_%s", argv[2]);
if((wfp = fopen(data, "wa"))==NULL) {
perror("file open error\n");
exit(0);
}
packetCapture(portnum, wfp);
fclose(wfp);
return 0;
}
void tcp_ftoa(int flag, char *str)
{
static char f[][3] = {"URG", "ACK", "PSH", "RST", "SYN", "FIN"};
int length = 0;
u_int mask = 1 << 5;
int i;
for(i = 0;i<6;i++) {
if (((flag << i) & mask) != 0)
{
strncat(str, f[i], 3);
strncat(str, " ", 1);
}
}
length = strlen(str);
str[length] = '\0';
}
void print_ip(struct ip *ip, FILE *fp)
{
char buf[BUFSIZE];
printf("Protocol: IP\n");
sprintf(buf, "Protocol: IP\n");
fputs(buf, fp);
printf("+-------------------------+--------+----------------+\n");
sprintf(buf, "+-------------------------+--------+----------------+\n");
fputs(buf, fp);
printf("| Identifier: %5u| TTL:%3u| Checksum: %5u|\n",
ntohs(ip->ip_id), ip->ip_ttl, ntohs(ip->ip_sum));
sprintf(buf, "| Identifier: %5u| TTL:%3u| Checksum: %5u|\n",
ntohs(ip->ip_id), ip->ip_ttl, ntohs(ip->ip_sum));
fputs(buf, fp);
printf("+------------+------------+-------------------------+\n");
sprintf(buf, "+------------+------------+-------------------------+\n");
fputs(buf, fp);
printf("| Source IP Address: %15s|\n",
inet_ntoa(*(struct in_addr *)&(ip->ip_src)));
sprintf(buf, "| Source IP Address: %15s|\n",
inet_ntoa(*(struct in_addr *)&(ip->ip_src)));
fputs(buf, fp);
printf("+---------------------------------------------------+\n");
sprintf(buf, "+---------------------------------------------------+\n");
fputs(buf, fp);
printf("| Destination IP Address: %15s|\n",
inet_ntoa(*(struct in_addr *)&(ip->ip_dst)));
sprintf(buf, "| Destination IP Address: %15s|\n",
inet_ntoa(*(struct in_addr *)&(ip->ip_dst)));
fputs(buf, fp);
printf("+---------------------------------------------------+\n");
sprintf(buf, "+---------------------------------------------------+\n");
fputs(buf, fp);
}
void print_tcp(struct tcphdr *tcp, FILE *fp)
{
char buf[BUFSIZE];
char str[17];
memset(str, 0, sizeof(str));
printf("protocol: TCP\n");
sprintf(buf, "protocol: TCP\n");
fputs(buf, fp);
printf("+-------------------------+-------------------------+\n");
sprintf(buf, "+-------------------------+-------------------------+\n");
fputs(buf, fp);
printf("| Source Port: %5u| Destination Port: %5u|\n",
ntohs(tcp->th_sport), ntohs(tcp->th_dport));
sprintf(buf, "| Source Port: %5u| Destination Port: %5u|\n",
ntohs(tcp->th_sport), ntohs(tcp->th_dport));
fputs(buf, fp);
printf("+-------------------------+-------------------------+\n");
sprintf(buf, "+-------------------------+-------------------------+\n");
fputs(buf, fp);
printf("| Sequence Number: %10lu|\n",
(u_long)ntohl(tcp->th_seq));
sprintf(buf, "| Sequence Number: %10lu|\n",
(u_long)ntohl(tcp->th_seq));
fputs(buf, fp);
printf("+---------------------------------------------------+\n");
sprintf(buf, "+---------------------------------------------------+\n");
fputs(buf, fp);
tcp_ftoa(tcp->th_flags, str);
printf("| Ack Number: %10lu| Flag: %10s|\n",
(u_long)ntohl(tcp->th_ack), str);
sprintf(buf, "| Ack Number: %10lu| Flag: %10s|\n",
(u_long)ntohl(tcp->th_ack), str);
fputs(buf, fp);
printf("+-------------------------+-------------------------+\n");
sprintf(buf, "+-------------------------+-------------------------+\n");
fputs(buf, fp);
printf("| Checksum: %5u| Urgent Pointer: %5u|\n",
ntohs(tcp->th_sum), ntohs(tcp->th_urp));
sprintf(buf, "| Checksum: %5u| Urgent Pointer: %5u|\n",
ntohs(tcp->th_sum), ntohs(tcp->th_urp));
fputs(buf, fp);
printf("+-------------------------+-------------------------+\n");
sprintf(buf, "+-------------------------+-------------------------+\n");
fputs(buf, fp);
}
void print_udp(struct udphdr *udp)
{
printf("Protocol: UDP\n");
printf("+-------------------------+-------------------------+\n");
printf("|Source Port: %5u| Dest Port: %5u|\n",
ntohs(udp->uh_sport), ntohs(udp->uh_dport));
printf("+-------------------------+-------------------------+\n");
printf("|Length: %5u| Ckecksum: %5u|\n",
ntohs(udp->uh_ulen), ntohs(udp->uh_sum));
printf("+-------------------------+-------------------------+\n");
}
void dump_packet(unsigned char *buff, int len, FILE *fp)
{
int i, j;
char buf[BUFSIZE];
printf("Packet Dump:\n");
sprintf(buf, "Packet Dump\n");
fputs(buf, fp);
for (i = 0; i < len; i += 16) {
for (j = i; j < i + 16 && j < len; j++) {
printf("%02x", buff[j]);
sprintf(buf, "%02x", buff[j]);
fputs(buf, fp);
if (j % 2 == 1){
printf(" ");
sprintf(buf, " ");
fputs(buf, fp);
}
}
if (j == len && len % 16 != 0)
for (j = 0; j < 40 - (len % 16)*2.5; j++){
printf(" ");
sprintf(buf, " ");
fputs(buf, fp);
}
printf(": ");
sprintf(buf, ": ");
fputs(buf, fp);
for (j = i; j < i + 16 && j < len; j++) {
if ((buff[j] >= 0x20) && (buff[j] <= 0x7e)){
putchar(buff[j]);
sprintf(buf, "%c", buff[j]);
fputs(buf, fp);
}
else{
printf(".");
sprintf(buf, ".");
fputs(buf, fp);
}
}
printf("\n");
fputs("\n", fp);
}
}
void help(char *cmd)
{
fprintf(stderr, "usage: %s [-i processid] [-p processname] \n", cmd);
}
void packetCapture(unsigned short portnum, FILE *wfp)
{
struct ether_header *eth;
struct ether_arp *arp;
struct ip *ip;
struct icmp *icmp;
struct tcphdr *tcp;
struct udphdr *udp;
int s;
int len;
int c;
char buff[MAXSIZE];
char *packetPointer;
char *packetFirst;
char buf[BUFSIZE];
int i;
char data[BUFSIZE];
if ((s = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
perror("socket");
exit(1);
}
while (1){
if ((len = read(s, buff, MAXSIZE)) < 0) {
perror("read");
exit(1);
}
packetPointer = packetFirst = buff;
eth = (struct ether_header *) packetPointer;
packetPointer = packetPointer + sizeof (struct ether_header);
printf("ether : %d\n", sizeof(struct ether_header));
if (ntohs(eth->ether_type) == ETHERTYPE_IP) {
ip = (struct ip *) packetPointer;
printf("ip : %d\n", sizeof(struct ip));
packetPointer = packetPointer + ((int)(ip->ip_hl) << 2);
switch (ip->ip_p) {
case IPPROTO_TCP:
tcp = (struct tcphdr *) packetPointer;
if((portnum == ntohs(tcp->th_sport))
|| (portnum == ntohs(tcp->th_dport))){
cketPointer = packetPointer + ((int)(tcp->th_off) << 2);
print_ip(ip, wfp);
print_tcp(tcp, wfp);
dump_packet(packetFirst, len, wfp);
printf("\n");
sprintf(buf, "\n");
fputs(buf, wfp);
fflush(stdout);
}
break;
case IPPROTO_UDP:
udp = (struct udphdr *) packetPointer;
if((portnum == ntohs(udp->uh_sport))
|| (portnum == ntohs(udp->uh_dport))){
print_ip(ip, wfp);
print_udp(udp);
dump_packet(packetFirst, len, wfp);
printf("\n");
sprintf(buf, "\n");
fputs(buf, wfp);
fflush(stdout);
}
break;
}
}
}
}
int ProcParser(char *proc_file, char *proc_name)
{
FILE *fp;
char buf[512] = {0x00,};
int pid;
char *pname;
fp = fopen(proc_file, "r");
if (fp == NULL)
{
perror("error : ");
exit(0);
}
fgets(buf, 511, fp);
pid = atoi(strtok(buf, " "));
pname = (char*)strtok(NULL, " ");
if(strncmp(pname, proc_name, strlen(proc_name)))
{
return 0;
}
else
{
return 1;
}
fclose(fp);
}
int IsDigit(char *str)
{
int i;
for (i = 0; i < strlen(str); i++)
{
if (isdigit(str[i])==0)
return 0;
}
return 1;
}
int HexToDecimal(char *buffer)
{
int count=0, decimal=0;
for(count =0;count<4;count++)
{
if(buffer[count]>='0' && buffer[count]<='9')
{
decimal *= 16;
decimal += buffer[count] - '0';
}
else if(buffer[count]>='A' && buffer[count]<='F')
{
decimal *= 16;
decimal += buffer[count] - 'A' + 10;
}
}
return decimal;
}
int SockMapPort(char* ProcTcp, char* sockdevnum, unsigned short* processPortNum)
{
DIR *directory;
struct dirent *entry = NULL;
char buffer[256];
char *parsing;
char hexPortNum[40];
int culcount;
int linecount;
int find;
FILE *fp;
culcount = linecount = find = 0;
fp = fopen(ProcTcp, "r");
if (fp == NULL)
{
perror("/proc/net/tcp(6) error");
exit(0);
}
linecount = 0;
while(fgets(buffer,256, fp) != NULL)
{
if(linecount == 0)
{
linecount++;
continue;
}
culcount = 0;
parsing = strtok(buffer, ": ");
while((parsing = strtok(NULL, ": ")) != NULL)
{
if(culcount == 1)
{
strcpy(hexPortNum, parsing);
}
if(!strncmp(parsing, sockdevnum, strlen(sockdevnum)))
{
find = 1;
break;
}
else
{
}
culcount++;
}
if(find) break;
}
if(find){
*processPortNum = HexToDecimal(hexPortNum);
return 1;
}
else
return 0;
}
unsigned short FindPortNum(char* processID)
{
DIR *directory;
struct dirent *entry = NULL;
char procBuff[40];
char sockBuff[40];
char retBuff[40];
char *sockdev;
char *sockdevnum;
char ProcTcp[20];
int ret;
unsigned short processPortNum;
sprintf(procBuff,"/proc/%s/fd", processID);
if ((directory = opendir(procBuff)) == NULL)
{
perror("/proc opendir error");
exit(0);
}
while((entry = readdir(directory)) != NULL)
{
if (strcmp(entry->d_name, ".") !=0 && strcmp(entry->d_name, "..") != 0
&& strcmp(entry->d_name, "0") != 0 && strcmp(entry->d_name, "1") !=0
&& strcmp(entry->d_name, "2") != 0)
{
sprintf(sockBuff,"/proc/%s/fd/%s", processID, entry->d_name);
readlink(sockBuff, retBuff, sizeof(retBuff));
sockdev = strtok(retBuff, ":[");
if(strcmp(sockdev, "socket"))
continue;
sockdevnum = strtok(NULL, ":[]");
break;
}
}
sprintf(ProcTcp, "/proc/net/tcp");
ret = SockMapPort(ProcTcp, sockdevnum, &processPortNum);
if(ret == 0)
{
sprintf(ProcTcp, "/proc/net/tcp6");
SockMapPort(ProcTcp, sockdevnum, &processPortNum);
}
closedir(directory);
return processPortNum;
}
unsigned short FindProcInfo(char* process)
{
DIR *directory;
struct dirent *entry = NULL;
char proc_file[40];
char proc_name[20];
int processFlag = 0;
unsigned short port;
if (IsDigit(process))
{
processFlag = 1;
}
else
{
sprintf(proc_name, "(%s)", process);
}
if(processFlag == 0)
{
system(process);
if ((directory = opendir("/proc")) == NULL)
{
perror("/proc opendir error");
exit(0);
}
while((entry = readdir(directory)) != NULL)
{
if (strcmp(entry->d_name, ".") !=0 && strcmp(entry->d_name, "..") != 0)
{
sprintf(proc_file,"/proc/%s/stat", entry->d_name);
if (access(proc_file, F_OK) != 0)
{
continue;
}
if (IsDigit(entry->d_name))
{
if(ProcParser(proc_file, proc_name))
{
port = FindPortNum(entry->d_name);
printf("processName : %s\nport : %d\n", process, port);
break;
}
}
else
{
}
}
}
closedir(directory);
}
else
{
port = FindPortNum(process);
printf("processID : %s\nport : %d\n", process, port);
}
return port;
}