20199101 2019-2020-2 网络嗅探与协议分析实践
tcpdump源码分析,抓包分析登陆用户名密码
1.tcpdump源码分析
首先说明一下为什么介绍tcpdump:目前我们手动编程实现一个简单的抓包应用程序都是基于libpcap这个C库的,这个库源于tcpdump项目,是从最开始tcpdump中剥离出来的一个库, tcpdump中抓包,过滤,capture file的读写的代码被提取出来成了libpcap。现在也是由tcpdump项目的开发者维护。所以只要非常了解libpcap各个函数的功能,那么编写一个简单的抓包程序无非就是函数的调用,实现非常简单。本文将详细介绍libpcap,并给出一个示例代码以供参考。
准备工作
我们可以通过tcpdump --version
来查看系统中tcpdump的源码版本(其源码并不是内核级的)。可以在tcpdump官网下载最新的源码,如果你是类Linux系统可以执行以下两条指令进行编译并运行。如果你想对参数有更深的了解,可以通过tcpdump -help
查看相关的命令参数或者查阅相关的博客和文档(在下面列举一些常用参数)。
./configure
make
./tcpdump
参数 | 含义 |
---|---|
-a | 将网络地址和广播地址转变成名字 |
-b | 在数据-链路层上选择协议,包括ip、arp、rarp、ipx都是这一层的 |
-nn | 直接以IP和端口号显示,而非主机与服务器名称 |
-r | 从指定的文件中读取包 |
-w | 直接将分组写入文件 |
![tcp2](https://img2020.cnblogs.com/blog/1927212/202005/1927212-20200503174618786-1686524958.png)
![tcp1](https://img2020.cnblogs.com/blog/1927212/202005/1927212-20200503174642445-820031105.png)
源码分析,原理
实现流程
先来看看包传递过来的流程,如下图(简单画的,这些图不好找)。包从网卡到内存,到内核态,最后给用户程序使用。我们知道tcpdump程序运行在用户态,那如何实现从内核态的抓包呢?
这个就是通过libpcap库来实现的,tcpdump调用libpcap的API函数,由libpcap进入到内核态到链路层来抓包,如下图。图中的BPF是过滤器,可以根据用户设置用于数据包过滤减少应用程序的数据包的包数和字节数从而提高性能。缓存是缓存供应用程序读取的数据包。我们可以说tcpdump底层原理其实就是libpcap的实现原理。
而libpcap在linux系统链路层中抓包是通过PF_PACKET
套接字来实现的(不同的系统其实现机制是由差异的),该方法在创建的时候,可以指定第二参数为SOCK_DGRAM
或者SOCK_RAW
,影响是否扣除链路层的首部。通过观察源码可以发现,tcpdump.c使用libpcap里的函数完成两个最关键的动作:获取捕获报文的接口,和捕获报文并将报文交给callback。libpcap支持BPF语法,BPF能够通过比较第2、3、4层协议中各个数据字段值的方法对流量进行过滤。Libpcap的使用逻辑如下图。
核心函数
核心函数 | 含义 |
---|---|
pcap_lookupdev | 如果分组捕获设备未曾指定(-i命令行选项),该函数选择一个设备 |
pcap_open_offine | 打开一个保存的文件 |
pcap_setfilter | 设置过滤器 |
pcap_open_live | 打开选择的设备 |
pcap_next | 接收一个包 |
pcap_dump | 将包写入到pcap_dump_t结构体 |
pcap_loopupnet | 返回分组捕获设备的网络地址和子网掩码,然后在调用pcap_compile时必须指定这个子网掩码 |
pcap_compile | 把cmd字符数组中构造的过滤器字符串编译成一个过滤器程序,存放在fcode中 |
pcap_setfilter | 把编译出来的过滤器程序装载到分组捕获设备,同时引发用该过滤器选取的分组的捕获 |
pcap_datalink | 返回分组捕获设备的数据链路类型 |
那么下面就让我们看看如何使用这些函数创建自己的抓包工具。光说不练假把式,let's go!!!
使用libpcap库创建自己的抓包应用程序
这里我将将向大家展示如何做一个自己的抓包应用程序。首先输入以下代码并利用指令gcc test.c -lpcap -lpthread -o test
编译执行。
#include <stdio.h>
#include <pcap.h>
int main (int argc, char *argv[])
{
char errbuf[PCAP_ERRBUF_SIZE], *dev;
dev = pcap_lookupdev(errbuf);
if (dev == NULL)
{
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
return 0;
}
printf("Device: %s\n", dev);
return 0;
}
上述代码主要是调用pcap_lookupdev
获取接口设备(如果你手敲了实现了上述代码,那么你已经成功了一半,errbuf
是错误信息)。
下面实现第二步
- 有了接口设备,就可以创建嗅探会话。利用函数
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,char *ebuf)
,其中snaplen
是pcap抓包的字节数,promisc
表示 是否启用混杂模式(不是混杂模式的话就只抓给本机的包),to_ms
表示是否超时。 - 创建了嗅探会话之后,就要一个过滤器来提取我们想要的数据。过滤器在应用之前必须要先编译,调用函数
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize,bpf_u_int32 netmask)
,第一个参数就是pcap_open_live
返回的值,fp 存储的过滤器的版本,optimize
是表示是否需要优化,最后netmask
是过滤器使用的所在子网掩码。 - 有了过滤器之后就是要使用过滤器调用函数
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
。
到这里你应该能完成下面的代码,并且不出现输出错误。
#include <stdio.h>
#include <pcap.h>
int main (int argc, char *argv[])
{
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *handle; /* Session handle */
char dev[] = "en0";
struct bpf_program fp; /* The compiled filter expression */
char filter_exp[] = "ip"; /* The filter expression */
bpf_u_int32 mask; /* The netmask of our sniffing device */
bpf_u_int32 net; /* The IP of our sniffing device */
if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1){
fprintf(stderr, "Can't get netmask for device %s\n", dev);
net = 0;
mask = 0;
}
handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL){
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return 0;
}
if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 0;
}
if (pcap_setfilter(handle, &fp) == -1){
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
return 0;
}
printf("Device: %s\n", dev);
return 0;
}
至此,如果你完全明白了整个流程,那么下面只剩10%的工作(可以很复杂,也可以很简单)。
下面实现第三步,开始抓包。
抓包技术有两种,一种是一次抓一个包;另一种是等待有n个包的时候在一起抓。 一次抓一个包使用函数如下:u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
,现在已经基本不使用,不再介绍,现在一般使用第二种,函数为int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
,第一个参数是创建的会话句柄,第二个参数是数量(抓几个包),第三个函数是抓到足够数量后的回调函数(每次抓到都会调用回调函数),第四个参数经常设置为NULL
,在一些应用中会有用。和pcap_loop
函数类似的是pcap_dispatch
,两者用法基本一致,主要差异是pcap_dispatch
只会执行一次回调函数,而pcap_loop
会执行n次。
综上所述,你的自己的简陋的抓包工具差不多就已经实现了。我这里给出两份代码,一份是上述实现的简单的代码,关于包本身其实是一个字符串指针,怎么去寻找我的ip头,tcp头,以及头中的内容呢?另外一份代码中就是这些详细信息的显示了。参考两份pcap实现代码。
2.登录网站,并嗅探,分析出账号和密码
对网站合肥论坛进行嗅探,现在大部分网站都改成了https协议,或者在传输密码时进行了Hash,譬如我一开始准备对小木虫嗅探,嗅探到的密码是一串MD5值,用户名获得是没有问题的,尝试用hashcat破解这段MD5值,但是没成功,主要还是字典的问题。
然后我发现我爸经常会浏览一些本地的论坛,譬如我们今天的主角,合肥论坛。我心想,这么个小网站应该没有加密吧,所以我迅速注册,登陆,嗅探(用http.request.method=="POST"
指令筛选),一顿操作猛如虎,顺利拿到密码。(手顺,用了常用密码,现在想来就是后悔)顺便在这里给一个公开的网站,练手用,免得大家注册这些垃圾网站。
网站:http://www.techpanda.org/
用户名:admin@google.com
密码:Password2010
3.抓取手机App的登录过程数据包,分析账号和密码。
首先,我们应该让手机共享到电脑的网络,才能用wireshark进行抓包分析,在macos下,打开共享设置,利用USB给手机共享网络即可。下图所示,并且此时应该关闭你手机的无线网和蜂窝数据,此时你的iPhone就可以共享你电脑的网络了。有的教程安卓手机可以编译tcpdump然后抓包利用wireshark分析,参考为Android平台编译tcpdump工具。
出看到这个问题,我本以为现在的app都至少要对账号密码hash一下再传,还是我太年轻啊!!!我找到手机中最**的但是又不得不用的app,超星,利用手机号和密码登陆。
我们看到超星的username=75429810
,密码是ice631
,这明显不是我的用户名和密码,但是又不是hash值之类的,我怀疑只是超星的代码里面做了一点变换而已。但是这个安全性也是很弱的。下面我们看另外一个app,合肥轨道,好吧,又是一个本地化app,我一般用它来坐地铁,涉及到金钱交易,按理说这个app的安全性总不能太差吧。
可以看到,我的手机号是明文传输的,也可以看到password是一串不知道什么的东西,但是又让我非常熟悉(主要是后面的那个=
号让我非常熟悉),是不是有点像base64加密的,那么我们直接把这个拷贝来解密试试,大概三次解密之后我的密码就暴露无遗了(见图),后面的那些是填充值。
后面的事情就是改我的密码了,顺便给这个app客服反馈一波。