实践三 网络嗅探与协议分析
1. 根据教材参考代码,编写有个简单网络抓包工具
1.0 libpcap原理解释
- 网络报文的接收源自网络设备(网卡)。以上是物理层,以下是内核态
- 网络设备在接收到一个报文之后,通过中断IRQ告知CPU。网卡驱动程序需要注册对该中断事件的处理函数,以处理接收到的报文。在中断中执行以下操作:
- 分配一个缓冲区sk_buff,把接收的数据拷贝进去;(第一次拷贝)
- 对缓冲区结构内的一些参数做初始化以告知较高层协议数据是什么类型skb->protocol;
- 非NAPI:调用netif_rx( )函数通知内核,将帧放入CPU的softnet_data->input_pkt_queue。netif_rx会调用网络接口函数netif_rx_schedule(使用softdate_net结构中内嵌的backlog_dev作为dev参数)
- NAPI:帧存放在每个设备自己的队列之中。调用netif_rx_schedule函数(直接以对应设备的dev结构为参数)
- 然后触发相关联的软IRQ--NET_RX_SOFTIRQ,此时网卡驱动程序已经将输入设备排入轮询列表poll_list,接下来执行net_rx_action函数:
- 浏览poll_list设备列表,这些设备的入口队列都有数据;
- 非NAPI设备:执行process_backlog函数(backlog_dev->poll)
- NAPI设备:执行poll函数
- 接着调用netif_receive_skb函数:
- 如果有抓包程序,由网络分接口进入BPF过滤器,将规则匹配的报文拷贝到系统内核缓存 (第二次拷贝)否则直接丢弃数据包;*注 : linux 在 PF_PACKET 类型的 socket 上支持内核过滤。Linux 内核允许我们把一个名为 LPF(Linux Packet Filter) 的过滤器直接放到 PF_PACKET 类型 socket 的处理过程中,过滤器在网卡接收中断执行后立即执行。 以上是内核态,以下是用户态
- libpcap绕过了Linux内核收包流程中协议栈部分的处理,使得用户空间API可以直接调用套接字PF_PACKET从链路层驱动程序中获得数据报文的拷贝,将其从内核缓冲区拷贝至用户空间缓冲区(第三次拷贝)
- BPF本质上来说是一也个设备驱动(device driver),能够被应用程序用来读取网络上通过这个网络适配器的包。但是BPF又是一个特殊的驱动,因为它并没有直接控制网络适配器,而是网络适配器真正的设备驱动调用BPF来传递数据。
- 通常情况下, 当一个包到达网络接口时, 数据链路设备驱动将把它发送到系统协议栈。但是当BPF在这个接口上面监听时,网络设备驱动将首先调用 BPF的
network tap
函数。这个tap函数将包送入每一个监听程序的filter
。而用户定义的filter
决定: 是否接收这个包; 每一个包有多少字节将会被保存。如果filter接收这个包, 那么tap 将会从数据链路层驱动的缓存中拷贝这个数目的字节数到 与这个filter关联的store buffer中(store buffer在内核中定义)。同时,网络接口的设备驱动将会重新获得控制权,且正常的协议处理将会进行。
- 最后libpcap面向用户空间提供独立于系统的可调用的函数接口
1.1 代码整体结构图
- 图片非常大可以右键查看原图。图中提到的所有函数和相关的解释均可以通过
man pcap
和vim /usr/include/pcap/pcap.h
查到。这里就不多说,相关注释在代码里。
1.2 输入逻辑分析
int ParseCommandLine(char *command){
//判断是不是退出条件
if (strcmp(command,COMMAND_QUIT) == 0)
{ //赋值命令参数
command_code = COMMAND_CODE_QUIT;
return 0;
}
//此处省略四种指令判断
return 1;
}
int pares_option_setting(){
char *tmp;
char *option;
tmp = strtok(NULL,TOKEN);
//判断是不是设置 device 参数
if (!strcmp(tmp,"device"))
{ //设置device参数
strcpy(options.device,option);
return 1;
}
//此处省略其他六种情况判断
}
int CommandDispacther(){
switch(command_code){
case COMMAND_CODE_QUIT:
return 0; //返回0代表退出
//此处省略五种赋值判断
}
}
int main(int argc, char const *argv[])
{
int status = 1;
do{
//此处省略读取操作
//对读取的字符进行分析。若是有效指令则进行指令操作或者赋值操作。若是非有效指令则输出提示
if (ParseCommandLine(buffer)){
PrintUsage();
}else{
status = CommandDispacther();
}
} while (status);
return 1;
}
1.3 抓包逻辑分析
void capture_packets(struct filter_options *options){
char error_content[PCAP_ERRBUF_SIZE];//用户分配的缓冲区的指针,该缓冲区将在该函数失败的情况下包含错误
pcap_t *pcap_handle; //pcap的主句柄
bpf_u_int32 net_mask; //子网页码
bpf_u_int32 net_ip; //网络IP地址
char *net_interface;//网络接口
struct bpf_program bpf_filter; //bpf过滤规则
char *bpf_filter_string = options->filter; //bpf过滤规则
//net_interface = pcap_lookupdev(error_content); /*获取网络接口*/
net_interface = options->device;//获取网络设备(接口)
printf("net_interface :%s
", net_interface);
/*获取网络地址和掩码地址*/
pcap_lookupnet(net_interface,
&net_ip,
&net_mask,
error_content);
/*打开网络接口*/
pcap_handle = pcap_open_live(net_interface,
BUFSIZ,
1, /*混杂模式*/
0, /*等待实践*/
error_content);
if (!pcap_handle)
{
printf("打开网络接口失败,请检查输入的网络接口是否存在,或者是否具有权限打开此网络接口。
");
return;
}
/*编译并设置bpf过滤规则*/
if (pcap_compile(pcap_handle, &bpf_filter,bpf_filter_string,0,net_mask))
{
printf("请检查你的输入规则是否有误。
");
return;
}
/*给主句柄设置过滤器*/
pcap_setfilter(pcap_handle,&bpf_filter);
/*判断主句柄是不是在线*/
if (pcap_datalink(pcap_handle) != DLT_EN10MB)
{
return;
}
pcap_dumper_t* out_pcap = NULL;
/*打开嗅探保存的文件地址*/
out_pcap = pcap_dump_open(pcap_handle,options->path);
/*循环抓包30次, 每次抓包调用回调函数ethernet_protocol_packet_callback*/
pcap_loop(pcap_handle,//回调函数
30 //loop infinity
,ethernet_protocol_packet_callback //回调函数
,(u_char *)out_pcap); //pass arguments to callback
/*关闭pcap主句柄*/
pcap_close(pcap_handle);
/*关闭嗅探结果保存文件*/
pcap_dump_close(out_pcap);
}
1.4 嗅探结果分析逻辑
void tcp_protocol_packet_callback(u_char *argument, //arguments pass by user
const struct pcap_pkthdr *packet_header,
const u_char *packet_content)
{/*此函数的相关参数都是按照TCP协议头的相关定义进行设置, 具体可以看《TCP/IP详解:协议》*/
struct tcp_header *tcp_protocol;
u_char flags;
int header_length;
u_short source_port, destination_port, windows, urgent_pointer;
u_int sequence, acknowlegement;
u_int16_t checksum;
tcp_protocol=(struct tcp_protocol*)(packet_content+ETHERNET_HEADER_LENGTH+IP_HEADER_LENGTH);
source_port = ntohs(tcp_protocol->tcp_source_port);
destination_port = ntohs(tcp_protocol->tcp_destination_port);
header_length = tcp_protocol->tcp_offset * 4;
sequence = ntohs(tcp_protocol->tcp_syn);
acknowlegement = ntohs(tcp_protocol->tcp_ack);
windows = ntohs(tcp_protocol->tcp_windows);
urgent_pointer = ntohs(tcp_protocol->tcp_windows);
flags = tcp_protocol->tcp_flags;
checksum = ntohs(tcp_protocol->tcp_checksum);
switch(destination_port) //判断上层协议
{
case 80:printf("HTTP protocol
");break;
case 21:printf("FTP protocol
");break;
case 23:printf("Telnet protocol
");break;
case 25:printf("SMTP protocol
");break;
case 110:printf("POP3 protocol
");break;
case 443:printf("HTTPS protocol
");break;
default:break;
}
//提取标志位
if (flags & 0x20)printf("URG");
if (flags & 0x01)printf("FIN");
if (flags & 0x04)printf("RST");
if (flags & 0x08)printf("PSH");
if (flags & 0x10)printf("ACK");
if (flags & 0x02)printf("SYN");
}
/*此处省略UDP和icmp的判断逻辑,与TCP基本相仿,只是相关参数的略微不同。详细信息看上图*/
/*arp协议分析回调函数*/
void arp_protocol_packet_callback(u_char *argument,
const struct pcap_pkthdr *packet_header,
const u_char *packet_content)
{/*此函数的相关参数都是按照TCP协议头的相关定义进行设置, 具体可以看《TCP/IP详解:协议》*/
struct arp_header *arp_protocol; //协议头变量
u_short protocol_type, hardware_type, operation_code;
/*协议类型, 硬件类型。 操作类型*/
u_char *mac_string; //以太网地址
struct in_addr source_ip_address;
struct in_addr destination_ip_address;
u_char hardware_length; //硬件地址长度
u_char protocol_length; //协议地址长度
/*获得arp协议数据。逐一在这里要跳过以太网数据部分*/
arp_protocol = (struct arp_header *)(packet_content+ETHERNET_HEADER_LENGTH);
/*使用ntohs函数将网络字节序转换为本机字节序*/
hardware_type = ntohs(arp_protocol->arp_hardware_type);
protocol_type = ntohs(arp_protocol->arp_protocol_type);
operation_code = ntohs(arp_protocol->arp_operation_code);
hardware_length = arp_protocol->arp_hardware_length;
protocol_length = arp_protocol->arp_protocol_length;
switch(operation_code){
case 1: printf("ARP Request Protocol
"); break;
case 2: printf("ARP Reply Protocol
"); break;
case 3: printf("RARP Request Protocol
"); break;
case 4: printf("RARP Reply Protocol
"); break;
default: break;
}
mac_string = arp_protocol->arp_source_ethernet_address;
memcpy((void *) &source_ip_address, (void *) &arp_protocol->
arp_source_ip_address, sizeof(struct in_addr));
mac_string = arp_protocol->arp_destination_ethernet_address;
memcpy((void *) &destination_ip_address, (void *) &arp_protocol->
arp_destination_ip_address, sizeof(struct in_addr));
}
/*回调函数实现ip协议包分析*/
void ip_protocol_packet_callback(u_char *argument, //arguments pass by user
const struct pcap_pkthdr *packet_header,
const u_char *packet_content)
{
struct ip_header *ip_protocol;
u_int header_length;
u_int offset;
u_char tos;
u_int16_t checksum;
/*去掉以太网头部,获得ip协议数据内容*/
ip_protocol = (struct ip_header *)(packet_content + ETHERNET_HEADER_LENGTH);
checksum = ntohs(ip_protocol->ip_checksum);
header_length = ip_protocol->ip_header_length*4;
tos = ip_protocol->ip_tos;
offset = ntohs(ip_protocol->ip_off);
printf("------------------IP Protocol (Network Layer)----------------------
");
printf("IP Version: %d
",ip_protocol->ip_version);
printf("Header_length: %d
",header_length);
printf("Tos:%d
", tos);
printf("Total length: %d
", ntohs(ip_protocol->ip_length));
printf("Identification: %d
",ntohs(ip_protocol->ip_id) );
printf("offset: %d
",(offset & 0x1fff)*8 );
printf("TTL: %d
",ip_protocol->ip_ttl );
printf("Protocol: %d
",ip_protocol->ip_protocol );
switch(ip_protocol->ip_protocol)
{
case 6: printf("The Transport Layer Protocol is TCP
");break;
case 17: printf("The Transport layer Protocol is UDP
");break;
case 1: printf("The Transport layer Protocol is ICMP
");break;
default:
break;
}
printf("Header checksum: %d
", checksum);
printf("Source address: %s
",inet_ntoa(ip_protocol->ip_source_address));
printf("Destination address: %s
", inet_ntoa(ip_protocol->ip_destination_address));
switch(ip_protocol->ip_protocol)
{
case 6: tcp_protocol_packet_callback(argument,packet_header,packet_content);break;
case 17: udp_protocol_packet_callback(argument,packet_header,packet_content);break;
case 1: icmp_protocol_packet_callback(argument,packet_header,packet_content);break;
default:
break;
}
}
/*回调函数实现以太网协议分析*/
void ethernet_protocol_packet_callback(u_char *argument, //arguments pass by user
const struct pcap_pkthdr *packet_header,
const u_char *packet_content){
pcap_dump(argument, packet_header, packet_content);
u_short ethernet_type; //以太网类型
struct ether_header *ethernet_protocol; //以太网协议类型
u_char *mac_string; //以太网地址
static int packet_number = 1;
ethernet_protocol = (struct ethernet_header *)packet_content;
/*获得以太网协议数据*/
ethernet_type = ntohs(ethernet_protocol->ether_type);//获得以太网类型
switch(ethernet_type)
{
case 0x0800: printf("The network layer is IP protocol
");break;
case 0x0806: printf("The network layer is ARP protocol
");break;
case 0x0835: printf("The network layer is RARP protocol
");break;
}
/*获得源以太网地址*/
mac_string = ethernet_protocol->ether_shost;
/*获得目的以太网地址*/
mac_string = ethernet_protocol->ether_dhost;
/*调用上层协议分析回调函数*/
switch(ethernet_type)
{
case 0x0800: ip_protocol_packet_callback(argument,packet_header,packet_content);
break;
case 0x0806: arp_protocol_packet_callback(argument,packet_header,packet_content);
break;
case 0x0835: break;
default: break;
}
}
1.5 执行截图
-
开始执行命令
-
执行命令结束
-
抓包结果(在抓包的时候,电脑和服务器之间在传数据集,符合下图的抓包结果)
2. 登录网站,并嗅探,分析出账号和密码
-
本科的一个小课设,搭建在了服务器上,算是用来完成这次试验把
-
首先访问这个网页http://49.233.180.160/Login.jsp
-
打开wireshark,嗅探数据包
-
账号和密码就出现在了数据包中
3. 抓取手机App的登录过程数据包,分析账号和密码
-
首先无线网卡开启嗅探模式需要特定的硬件支持。 换句话说,除非特定系列的网卡,否则不能抓取同一wifi下的所有数据包。这个问题有两个解决方法:arp欺骗,arp溢出攻击。第一种是你的电脑网卡不停的告诉交换机“我就是你要找的那个手机”,借此获得原本发往手机的数据包。第二种方法是生成巨量的MAC地址,使得交换机的映射表溢出,进而所有的数据包的mac地址均设置为
FF:FF:FF:FF:FF:FF
,从而获得当前网络下所有的数据信息。 -
但是考虑到上述三种方法:第一种方法没硬件pass,第二种方法kail虚拟机在实验室,用我的有线网卡去欺骗无线网卡,欺骗成功,我的远程连接就断了,从头再来,循环往复。第三种花费了我一个晚上,最后得出结论,我家的交换机在映射表满了之后断网重启。写到这我真的累了。
-
因此我使用win10自带的数据共享功能开了一个热点,让手机连到热点上。虽有点投机取巧,但是原理还是没问题的。
-
问题才刚刚开始,理论上第三问就是把第二问的过程迁移到手机上完成,应该没有多大难度,但是实际情况实在是难受。
-
首先上次实验用的POP3/SMTP服务在手机版foxmail中直接被禁了,只允许账号密码登录。
-
接下来转战ssh,想通过VPN和SSH到学校的linux服务器,在这个过程中抓包。结果VPN自带加密,而且SSHv2也是自带加密,密码实在是拿不到
-
再接着想试试现有的APP, 总体来说他们分为两大流派:以TIM为首的账户不加密,密码加密;以B站为首的账户密码都加密。这里我看到TIM的加密是椭圆曲线+密钥分发,我想试试能不能解密,试了半个晚上才想起来,椭圆曲线好像是非对称密码,原谅我的智商。
-
接下来就纠结了,作业要求嗅探账户和密码,这年头,那些APP还明文传输啊。
-
先说说如何设置热点把, 按照如下流程图行进
-
这里看了一下建国的博客,发现了一个base64编码当作加密的APP,但是看了别人的答案再写作业实在没啥意思。这里放一个嗅探TIM的数据包的截图吧,只有用户名和加密的密码。
我承认我是个弱智。有点难受