nds病房1详细剧情:自己动手写网络抓包工具
来源:百度文库 编辑:偶看新闻 时间:2024/04/27 13:33:08
当打开一个标准SOCKET套接口时,我们比较熟悉的协议往往是用AF_INET来建立基于TCP(SOCK_STREAM)或UDP(SOCK_DGRAM)的链接。但是这些只用于IP层以上,要想从更底层抓包,我们需要使用AF_PACKET来建立套接字,它支持SOCK_RAW和SOCK_DGRAM,它们都能从底层抓包,不同的是后者得到的数据不包括以太网帧头(最开始的14个字节)。好了,现在我们就知道该怎样建立SOCKET套接口了:
sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
最后一个参数 ETH_P_IP 指出,我们只对IP包感兴趣,而不是ARP,RARP等。之后就可以用recvfrom从套接口读取数据了。
现在我们可以抓到发往本地的所有IP数据报了,那么有没有办法抓到那些“流经”本地的数据呢?呵呵,当然可以了,这种技术叫网络嗅探(sniff),它很能威胁网络安全,也非常有用,尤其是当你对网内其他用户的隐私感兴趣时:( 由于以太网数据包是对整个网段广播的,所以网内所有用户都能收到其他用户发出的数据,只是默认的,网卡只接收目的地址是自己或广播地址的数据,而把不是发往自己的数据包丢弃。但是多数网卡驱动会提供一种混杂模式(promiscous mode),工作在这种模式下的网卡会接收网络内的所有数据,不管它是发给谁的。下面的方法可以把网卡设成混杂模式:
// set NIC to promiscous mode, so we can recieve all packets of the network
strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
ioctl(sock, SIOCGIFFLAGS, ðreq);
ethreq.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, ðreq);
通过ifconfig可以很容易的查看当前网卡是否工作在混杂模式(PROMISC)。但是请注意,程序退出后,网卡的工作模式不会改变,所以别忘了关闭网卡的混杂模式:
// turn off promiscous mode
ethreq.ifr_flags &= ~IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, ðreq);
现在我们可以抓到本网段的所有IP数据包了,但是问题也来了:那么多的数据,怎么处理?CPU可能会被严重占用,而且绝大多数的数据我们可能根本就不敢兴趣!那怎么办呢?用if语句?可能要n多个,而且丝毫不会降低内核的繁忙程度。最好的办法就是告诉内核,把不感兴趣的数据过滤掉,不要往应用层送。BPF就为此而生。
BPF(Berkeley Packet Filter)是一种类是汇编的伪代码语言,它也有命令代码和操作数。例如,如果我们只对用户192.168.1.4的数据感兴趣,可以用tcpdump的-d选项生成BPF代码如下:
$tcpdump -d host 192.168.1.4
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 6
(002) ld [26]
(003) jeq #0xc0a80104 jt 12 jf 4
(004) ld [30]
(005) jeq #0xc0a80104 jt 12 jf 13
(006) jeq #0x806 jt 8 jf 7
(007) jeq #0x8035 jt 8 jf 13
(008) ld [28]
(009) jeq #0xc0a80104 jt 12 jf 10
(010) ld [38]
(011) jeq #0xc0a80104 jt 12 jf 13
(012) ret #96
(013) ret #0
其中第一列代表行号,第二列是命令代码,后面是操作数。下面我们采用汇编注释的方式简单的解释一下:
(000) ldh [12] ;load h?? (2 bytes) from ABS offset 12 (the TYPE of ethernet header)
(001) jeq #0x800 jt 2 jf 6 ;compare and jump, jump to line 2 if true; else jump to line 6
(002) ld [26] ;load word (4 bytes) from ABS offset 26 (src IP address of IP header)
(003) jeq #0xc0a80104 jt 12 jf 4 ;compare and jump, jump to line 12 if true, else jump to line 4
(004) ld [30] ; load word (4 bytes) from ABS offset 30 (dst IP address of IP header)
(005) jeq #0xc0a80104 jt 12 jf 13 ;see line 3
(006) jeq #0x806 jt 8 jf 7 ;compare with ARP, see line 1
(007) jeq #0x8035 jt 8 jf 13 ;compare with RARP, see line 1
(008) ld [28] ;src IP address for other protocols
(009) jeq #0xc0a80104 jt 12 jf 10
(010) ld [38] ;dst IP address for other protocols
(011) jeq #0xc0a80104 jt 12 jf 13
(012) ret #96 ;return 96 bytes to user application
(013) ret #0 ;drop the packet
但是这样的伪代码我们是无法在应用程序里使用的,所以tcpdum提供了一个-dd选项来输出一段等效的C代码:
$tcpdump -dd host 192.168.1.4
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 4, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 8, 0, 0xc0a80104 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 6, 7, 0xc0a80104 },
{ 0x15, 1, 0, 0x00000806 },
{ 0x15, 0, 5, 0x00008035 },
{ 0x20, 0, 0, 0x0000001c },
{ 0x15, 2, 0, 0xc0a80104 },
{ 0x20, 0, 0, 0x00000026 },
{ 0x15, 0, 1, 0xc0a80104 },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 },
该代码对应的数据结构是struct sock_filter,该结构在linux/filter.h中定义如下:
struct sock_filter // Filter block
{
__u16 code; // Actual filter code
__u8 jt; // Jump true
__u8 jf; // Jump false
__u32 k; // Generic multiuse field
};
code对应命令代码;jt是jump if true后面的操作数,注意这里用的是相对行偏移,如2就表示向前跳转2行,而不像伪代码中使用绝对行号;jf为jump if false后面的操作数;k对应伪代码中第3列的操作数。
了解了BPF伪代码和结构,我们就可以自己定制更加简单有效的BPF filter了,如上例中的6-11行不是针对IP协议的,而我们的套接字已经指定只读取IP数据了,所以就可以把他们删除,不过要注意,行偏移也要做相应的修改。
另外,tcpdump默认只返回96字节的数据,但对大部分应用来说,96字节是远远不够的,所以tcpdump提供了-s选项用于指定返回的数据长度。
OK,下面我们就来看看怎样把过滤器安装到套接口上吧:
$tcpdump ip -d -s 2048 host 192.168.1.2
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 7
(002) ld [26]
(003) jeq #0xc0a80102 jt 6 jf 4
(004) ld [30]
(005) jeq #0xc0a80102 jt 6 jf 7
(006) ret #2048
(007) ret #0
struct sock_filter bpf_code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 5, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0xc0a80102 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0xc0a80102 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }
};
struct sock_fprog filter;
filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
filter.filter = bpf_code;
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
最后加上信号处理器,以便能在程序退出前恢复网卡的工作模式。到现在我们已经可以看到一个小聚规模抓包小工具了,呵呵,麻雀虽小,但也五脏俱全啊!下面给出完整的代码。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ETH_HDR_LEN 14
#define IP_HDR_LEN 20
#define UDP_HDR_LEN 8
#define TCP_HDR_LEN 20
static int sock;
void sig_handler(int sig)
{
struct ifreq ethreq;
if(sig == SIGTERM)
printf("SIGTERM recieved, exiting...\n");
else if(sig == SIGINT)
printf("SIGINT recieved, exiting...\n");
else if(sig == SIGQUIT)
printf("SIGQUIT recieved, exiting...\n");
// turn off the PROMISCOUS mode
strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
if(ioctl(sock, SIOCGIFFLAGS, ðreq) != -1) {
ethreq.ifr_flags &= ~IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, ðreq);
}
close(sock);
exit(0);
}
int main(int argc, char ** argv) {
int n;
char buf[2048];
unsigned char *ethhead;
unsigned char *iphead;
struct ifreq ethreq;
struct sigaction sighandle;
#if 0
$tcpdump ip -s 2048 -d host 192.168.1.2
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 7
(002) ld [26]
(003) jeq #0xc0a80102 jt 6 jf 4
(004) ld [30]
(005) jeq #0xc0a80102 jt 6 jf 7
(006) ret #2048
(007) ret #0
#endif
struct sock_filter bpf_code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 5, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0xc0a80102 },
{ 0x20, 0, 0, 0x0000001e },
{ 0x15, 0, 1, 0xc0a80102 },
{ 0x6, 0, 0, 0x00000800 },
{ 0x6, 0, 0, 0x00000000 }
};
struct sock_fprog filter;
filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
filter.filter = bpf_code;
sighandle.sa_flags = 0;
sighandle.sa_handler = sig_handler;
sigemptyset(&sighandle.sa_mask);
//sigaddset(&sighandle.sa_mask, SIGTERM);
//sigaddset(&sighandle.sa_mask, SIGINT);
//sigaddset(&sighandle.sa_mask, SIGQUIT);
sigaction(SIGTERM, &sighandle, NULL);
sigaction(SIGINT, &sighandle, NULL);
sigaction(SIGQUIT, &sighandle, NULL);
// AF_PACKET allows application to read pecket from and write packet to network device
// SOCK_DGRAM the packet exclude ethernet header
// SOCK_RAW raw data from the device including ethernet header
// ETH_P_IP all IP packets
if((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1) {
perror("socket");
exit(1);
}
// set NIC to promiscous mode, so we can recieve all packets of the network
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);
}
// attach the bpf filter
if(setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) == -1) {
perror("setsockopt");
close(sock);
exit(1);
}
while(1) {
n = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
if(n < (ETH_HDR_LEN+IP_HDR_LEN+UDP_HDR_LEN)) {
printf("invalid packet\n");
continue;
}
printf("%d bytes recieved\n", n);
ethhead = buf;
printf("Ethernet: MAC[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[0], ethhead[1], ethhead[2],
ethhead[3], ethhead[4], ethhead[5]);
printf("->[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[6], ethhead[7], ethhead[8],
ethhead[9], ethhead[10], ethhead[11]);
printf(" type[%04x]\n", (ntohs(ethhead[12]|ethhead[13]<<8)));
iphead = ethhead + ETH_HDR_LEN;
// header length as 32-bit
printf("IP: Version: %d HeaderLen: %d[%d]", (*iphead>>4), (*iphead & 0x0f), (*iphead & 0x0f)*4);
printf(" TotalLen %d", (iphead[2]<<8|iphead[3]));
printf(" IP [%d.%d.%d.%d]", iphead[12], iphead[13], iphead[14], iphead[15]);
printf("->[%d.%d.%d.%d]", iphead[16], iphead[17], iphead[18], iphead[19]);
printf(" %d", iphead[9]);
if(iphead[9] == IPPROTO_TCP)
printf("[TCP]");
else if(iphead[9] == IPPROTO_UDP)
printf("[UDP]");
else if(iphead[9] == IPPROTO_ICMP)
printf("[ICMP]");
else if(iphead[9] == IPPROTO_IGMP)
printf("[IGMP]");
else if(iphead[9] == IPPROTO_IGMP)
printf("[IGMP]");
else
printf("[OTHERS]");
printf(" PORT [%d]->[%d]\n", (iphead[20]<<8|iphead[21]), (iphead[22]<<8|iphead[23]));
}
close(sock);
exit(0);
}
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wangxg_7520/archive/2008/08/19/2795229.aspx