📄 sniff_snort.c
字号:
Example 1.
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/in.h>
#include <linux/if_ether.h>
int main(int argc, char **argv) {
int sock, n;
char buffer[2048];
unsigned char *iphead, *ethhead;
if ( (sock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)))<0) {
perror("socket");
exit(1);
}
while (1) {
printf("----------\n");
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]);
}
}
}
PF_PACKET协议簇可以让一个应用程序把数据包变成似乎从网络层接收的样子,但是没有办法抓到那些不是发向自己主机的包。正如我们前面看到的,网卡丢弃所有不含有主机MAC地址的数据包,这是因为网卡处于非混杂模式,即每个网卡只处理源地址是它自己的帧!只有三个例外:如果一个帧的目的MAC地址是一个受限的广播地址(255.255.255.255)那么它将被所有的网卡接收;如果一个帧的目的地址是组播地址,那么它将被那些打开组播接收功能的网卡所接收;网卡如被设置成混杂模式,那么它将接收所有流经它的数据包。
最后一种情况当然是我们最感兴趣的了,把网卡设置成混杂模式,我们只需要发出一个特殊的ioctl( )调用在那个网卡上打开一个socket,因为这是一个具有危险性的操作,所以这个调用只有具有root权限的用户才可完成,假设那个“sock”包含一个已经打开的socket,
下面的代码将完成这个操作:
strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
ioctl(sock, SIOCGIFFLAGS, ðreq);
ethreq.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, ðreq);
下面我们来看一个完整的例子:
Example 2.
#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 <sys/ioctl.h>
int main(int argc, char **argv) {
int sock, n;
char buffer[2048];
unsigned char *iphead, *ethhead;
struct ifreq ethreq;
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");
close(sock);
exit(1);
}
ethreq.ifr_flags|=IFF_PROMISC;
if (ioctl(sock,SIOCSIFFLAGS, ðreq)==-1) {
perror("ioctl");
close(sock);
exit(1);
}
while (1) {
printf("----------\n");
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]);
}
}
}
如果我们在一个与局域网相连的主机上以root权限编译和运行这个程序,你将会看到流经的所有数据包,即使它不是发向自己主机的,这是因为你的网卡现在处于混杂模式,你可以很容易的用ifconfig这个命令看到!
注意,如果你的局域网用的是交换机而不是集线器,那么你只能抓到在你主机所在的交换分支中的数据包,这是由于交换的工作原理,在这种情况下,你没有什么其他可做的了(除非你利用MAC地址欺骗来瞒过交换机,这个内容超出本文讨论的范围了),如果你想知道这方面更多的信息,你可以去www.google.com,那里你将会搜索到很多你需要的东西!
我们关于嗅探方面的问题似乎都解决了,但是还有一个问题值得我们去思考!如果我们实验Example2的时候正好遇到了网络拥塞,我们将会看到我们的嗅探器将打出非常多的数据,随着网络拥塞的加剧,主机将不能顺畅的执行这个程序,嗅探器这时会开始丢包!
解决这个问题的方法就是对你抓到的包进行过滤,使它只显示那些你感兴趣的那些部分!在源代码中多使用一些if语句,这将有益于去除那些不需要的信息,当然这样也许效率不是很高!内核仍然将处理所有的报文,这将浪费进程时间。
最好的解决办法就是把过滤过程尽可能早的放报文进程链中。(packet-processing chain它开始于链路层,终止于应用层)Linux的内核允许我们把一个名为BPF的过滤器直接放到PF_PACKET protocol-processing routines(在网卡接收中断执行后立即执行)中,过滤器将决定哪些包被送到应用程序,哪些包被丢弃!
为了做到尽可能的灵活,这个过滤程序可以根据使用者的定义来运行!这个程序是由一种名为BPF的伪机器码写成的。BPF看上去很像带着一对寄存器和保存值的汇编语言,完成数学运算和条件分支程序!过滤程序检查每个包,BPF进程操作的内存空间包含着报文数据!过滤的结果是一个整数,指出有多少字节的报文需要送到应用层。这是一个更进一步的优点,因为我们一般只对报文的前面几个字节感兴趣,这样可以避免复制过量的数据以节省进程时间。
虽然BPF语言非常简单易学,但是大多数人还是习惯于人类可以阅读的表达方式!所以为了不用BPF语言去描述那些细节和代码,我们将下面开始讨论如何得到一个过滤器的代码!
首先,你需要安装tcpdump,可以从LBL得到!但是,如果你正在读本文,那似乎说明你已经会使用tcpdump了!第一个版本的作者也是那些写BPF的人,事实上,tcpdump要用到BPF,它要用到一个叫libpcap的类库,以此去抓包和过滤。这个类库是一个与操作系统无关的独立的包,为BPF的实现提供支持,当在装有Linux的机器上使用时,BPF的功能就由Linux包过滤器实现了!
libpcap提供的一个最有用的函数是pcap_compile(), 它可以把一个输入输出的逻辑表达式变为BPF代码!tcpdump利用这个函数完成在用户输入的命令行和BPF代码之间的转换!
tcpdump有个我们很感兴趣但是很少使用的参数 ,-d,可以输出BPF代码!
举个例子,如果tcpdump host 192.168.9.10那么将启动嗅探器并只抓到源地址或者目的地址是192.168.9.10的报文!如果tcpdump -d host 192.168.9.10那么将显示出BPF代码:
Example 3.
Seal:~# tcpdump -d host 192.168.9.10
(000) ldh [12]
(001) jeq #0x0800 jt 2 jf 6
(002) ld [26]
(003) jeq #0xc0a8090a jt 12 jf 4
(004) ld [30]
(005) jeq #0xc0a8090a jt 12 jf 13
(006) jeq #0x0806 jt 8 jf 7
(007) jeq #0x8035 jt 8 jf 13
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -