[技術] 在Linux下實作Sniffer

Written on 1:39 下午 by Yu Lai

在這裡先感謝一下黃仁竑老師,因為這篇其實這是之前唸研究所時,
上課所要交的作業。沒想到最近在工作上也用到了,呵呵。

雖然在Linux下有許多好用的Sniffer工具,如: tcpdump 或 libpcap等。
但若了解如何自己實作Sniffer,對於實作網路相關的protocol會有更深的了解。
在實作sniffer之前,在Linux下有幾個東西我們要先了解。

首先是raw socket,透過raw socket,可以讓我們從Layer 2(Ethernet)
的資料開始解讀,而不是已經被核心處理過的tcp/udp payload。

sock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));

接著是LPF(Linux Packet Filter),在Linux的kernel中,有個LPF的機制
可以預先設定過濾socket讀取到的內容。當然這個在實作sniffer不是必要
的,我們大可在讀取資料後自行filter掉非必要的data。但透過LPF,可以
讓我們專心在實作資料的呈現上。
而LPF的使用,我們可以使用一種pseudo-machine code language叫作BPF。
它是一種用來描述filter的語言,我們可以透過tcpdump這支好用的工具來
產生bpf的sample code。使用方法就是使用tcpdump -d
tcpdump -dd 來生出C使用的code。另外,tcpdump所生成的
sample code預設只會抓取96個字元,所以要再加上-s參數來指定抓取的數量。

tcpdump -d -s 2048 tcp and host 140.123.230.166
(000) ldh [12]
(001) jeq #0x86dd jt 10 jf 2
(002) jeq #0x800 jt 3 jf 10
(003) ldb [23]
(004) jeq #0x6 jt 5 jf 10
(005) ld [26]
(006) jeq #0x8c7be6a6 jt 9 jf 7
(007) ld [30]
(008) jeq #0x8c7be6a6 jt 9 jf 10
(009) ret #2048
(010) ret #0

tcpdump -dd -s 2048 tcp and host 140.123.230.166
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }

把Filter設到socket上。
struct sock_filter BPF_code[]= {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 }
};
struct sock_fprog Filter;
Filter.len = 11;
Filter.filter = BPF_code;

setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter))

最後是把網卡的promiscuos mode打開,網卡一般會比對dst mac和自己的
Mac Address,若不相同或不是broadcast的封包會被drop掉不處理。
若要實作sniffer,這個mode一定要打開,才能監聽到別人的封包。

strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
ioctl(sock,SIOCGIFFLAGS,ðreq);
ethreq.ifr_flags|=IFF_PROMISC;
ioctl(sock,SIOCSIFFLAGS,ðreq);

以下是完整的sample code,不過年代久遠,也沒時間compile測試,
如有錯誤不能compile,請見諒。XD

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <linux/filter.h>
#include <sys/ioctl.h>


int main(int argc, char **argv) {

int sock, sock_server, sock_client, n, i;
char buffer[2048];
unsigned char *iphead, *ethhead, *tcphead;
int addr_len;
struct sockaddr_in sa_cli;
struct ifreq ethreq;
FILE* fPtr;

struct sock_filter BPF_code[]= {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 8, 0, 0x000086dd },
{ 0x15, 0, 7, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 5, 0x00000006 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0x8c7be6a6 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0x8c7be6a6 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }
};

struct sock_fprog Filter;

Filter.len = 11;
Filter.filter = BPF_code;

if ((sock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)))<0) {
perror("socket");
exit(1);
}

/* Set the network card in promiscuos mode */
strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
if (ioctl(sock,SIOCGIFFLAGS,ðreq)==-1) {
perror("ioctl (SIOCGIFCONF) 1");
close(sock);
exit(1);
}
ethreq.ifr_flags|=IFF_PROMISC;
if (ioctl(sock,SIOCSIFFLAGS,ðreq)==-1) {
perror("ioctl (SIOCGIFCONF) 2");
close(sock);
exit(1);
}

/* Attach the filter to the socket */
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter))<0){
int temp=sizeof(Filter);
printf( "%d" , temp);
perror("setsockopt");
close(sock);
exit(1);
}

/* Open the result file. */
if ((fPtr = fopen("dump.txt", "w")) == NULL) {
perror("fopen");
close(sock);
exit(1);
}

while (1) {
n = recvfrom(sock, buffer, 2048, 0, NULL, NULL);
printf("%d bytes read\n",n);

/* Check to see if the packet contains at least
* complete Ethernet (14), IP (20) and TCP/UDP
* (8) headers.
*/
if (n<42) {
perror("recvfrom():");
printf("Incomplete packet (errno is %d)\n", errno);
close(sock);
exit(0);
}

ethhead = buffer;
printf("Source MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhead[0], ethhead[1], ethhead[2], ethhead[3], ethhead[4], ethhead[5]);
printf("Destination MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
ethhead[6], ethhead[7], ethhead[8], ethhead[9], ethhead[10], ethhead[11]);

iphead = buffer+14; /* Skip Ethernet header */
if (*iphead==0x45) { /* Double check for IPv4 and no options present */
printf("Source host %d.%d.%d.%d\n",
iphead[12], iphead[13], iphead[14], iphead[15]);
printf("Dest host %d.%d.%d.%d\n",
iphead[16], iphead[17], iphead[18], iphead[19]);
printf("Source,Dest ports %d,%d\n",
(iphead[20]<<8)+iphead[21], (iphead[22]<<8)+iphead[23]);
printf("Layer-4 protocol %d\n",
iphead[9]);
}

tcphead = iphead+20;
if (iphead[9] == 0x06) {
printf("TCP Sequence Number: %x%x%x%x\n",
tcphead[4], tcphead[5], tcphead[6], tcphead[7]);
printf("TCP Acknowledgement Number: %x%x%x%x\n",
tcphead[8], tcphead[9], tcphead[10], tcphead[11]);
printf("TCP Header Length: %d\n",
tcphead[12]>>4);
printf("TCP Control Bits: %02x (UAPRSF)\n",
tcphead[13]);
printf("TCP Window Size: %d\n",
(tcphead[14]<<8)+tcphead[15]);
for(i=4*(tcphead[12]>>4);i<n-14-20;i++) {
printf("%c", tcphead[i]);
}
printf("\n");
}
}

}


Ref:
http://www.linuxjournal.com/article/4659

If you enjoyed this post Subscribe to our feed

1 Comment

  1. 卡布基 |

    謝謝你的sniffer實作教學解說,受益良多!!

     

張貼留言