ethereal源码分析和编译、使用步骤
ethereal是一个抓包软件,是著名的抓包软件wireshark的前身。
下载地址:http://www.ethereal.com/distribution/all-versions/
ethereal依赖GTK+、GLIB、libpcap
GTK+下载地址:ftp://ftp.gtk.org/pub/gtk/v1.2/
GLIB下载地址:ftp://ftp.gtk.org/pub/gtk/v1.2/
libpcap下载地址:http://www.tcpdump.org/
这里我使用的版本是
glib-1.2.9.tar.gz
gtk+-1.2.9.tar.gz
libpcap-1.2.0rc1.tar.gz
ethereal-0.8.10.tar.gz
源码包都放在 /opt 目录下。
【源代码编译】
编译 libpcap
tar -zxvf libpcap-1.2.0rc1.tar.gz
mkdir /usr/local/libpcap-1.2.0
cd libpcap-1.2.0
./configure --prefix=/usr/local/libpcap-1.2.0/
make
make install
//转到 /usr/local/libpcap-1.2.0/include/ 目录,将 pcap 子目录拷贝一份,然后重命名为 net
cd /usr/local/libpcap-1.2.0/include/
cp -R ./pcap ./net
编译GLIB
tar -zxvf glib-1.2.9.tar.gz
mkdir /usr/local/glib-1.2.9
cd glib-1.2.9
./configure --prefix=/usr/local/glib-1.2.9/
make
//编译的时候会报错误 gstrfuncs.c 870: error: expected ')' before string constant ,这时候需要修改源代码 gstrfuncs.c,
将 g_warning 函数调用注释掉,以下遇到相同的错误都进行相同的修改
make install
编译GTK+
tar -zxvf gtk+-1.2.9.tar.gz
mkdir /usr/local/gtk+-1.2.9
cd gtk+-1.2.9
./configure --prefix=/usr/local/gtk+-1.2.9/ --with-glib-prefix=/usr/local/glib-1.2.9/ --disable-glibtest
make
make install
编译ethereal
tar -zxvf ethereal-0.8.10.tar.gz
mkdir /usr/local/ethereal-0.8.10
cd ethereal-0.8.10
//修改一下 configure 文件
vim ./configure
定位到 #Evidently, some systems have pcap.h, etc. in */include/pcap 这一段代码,
将 for pcap_dir in /usr/include/pcap /usr/local/include/pcap 中的目录替换成我们自己的libpcap目录
for pcap_dir in /usr/local/libpcap-1.2.0/include
./configure --prefix=/usr/local/ethereal-0.8.10/ --with-glib-prefix=/usr/local/glib-1.2.9/ --with-gtk-prefix=/usr/local/gtk+-1.2.9/ --includedir=/usr/local/libpcap-1.2.0/include/ --libdir=/usr/local/libpcap-1.2.0/lib/ --disable-gtktest --disable-glibtest
make
//编译时遇到报错:
capture.c:112 错误:对'sync_pipe'的静态声明出现在非静态声明之后
captrue.h:35: 错误:'sync_pipe'的上一个声明在此
修改 capture.c 源文件,将 static int sync_pipe[2]; 的关键字 static 去掉,以下遇到相同的错误都进行相同的修改
make install
【ethereal使用】
使用前首先 export libpcap,glib,GTK+
export LD_LIBRARY_PATH=/usr/local/libpcap-1.2.0/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/glib-1.2.9/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/gtk+-1.2.9/lib:$LD_LIBRARY_PATH
ethereal-0.8.10 默认使用的字体 -*-lucidatypewriter-medium-r-normal-*-*-120-*-*-*-*-iso8859-1 在 gtk+-1.2.9 上面载入失败,
使用 xlsfonts 程序查看可用的字体,发现 fixed 字体可以成功载入
cd /usr/local/ethereal-0.8.10/bin/
./ethereal -m fixed -b fixed
【ethereal源码分析】
ethereal 流程
main 函数会根据参数选择调用 do_capture 还是 capture ,do_capture 在内部调用 capture 。
capture 使用libpcap捕获网卡数据包。
dfilter_compile显示过滤器编译函数。
dfilter_apply把编译好的显示过滤器函数应用到协议树。
follow_stream_cb把捕获到的数据包按照显示过滤器进行默认的 ASCII 格式的输出。
重要的数据结构
typedef struct _capture_file {
FILE_T fh; /* 捕获到的数据包文件句柄 */
int filed; /* 捕获到的数据包文件描述符 */
gchar *filename; /* 捕获到的数据包文件名称 */
gboolean is_tempfile; /* 是一个捕获到的数据包文件还是一个临时文件? */
gboolean user_saved;/* 如果捕获到的数据包文件是一个临时的,它已经被用户保存了吗? */
long f_len; /* 数据包文件的长度 */
guint16 cd_t; /* 数据包文件的文件类型 */
int lnk_t; /* 捕获数据包的链路层类型 */
guint32 vers; /* 版本 */
guint32 count; /* 数据包的个数 */
gfloat unfiltered_count; /* 未过滤的个数,供显示过滤器进度条使用 */
guint32 drops; /* 丢失的数据包 */
guint32 esec; /* 经过的秒数 */
guint32 eusec; /* 经过的毫秒数 */
guint32 snap; /* 捕获的数据包的长度 */
gboolean update_progbar; /* 当我们应该更新进度条的时候设为真 */
long progbar_quantum; /* 每一次进度条更新的时候读取的字节数 */
long progbar_nextstep; /* 下一个应该更新进度条的时刻 */
gchar *iface; /* 网络接口 */
gchar *save_file; /* 用户保存捕获到的数据包的文件名称 */
int save_file_fd; /* 保存捕获到的数据包文件的描述符 */
wtap *wth; /* Wiretap session */
dfilter *rfcode; /* 已经编译过的读取过滤器程序 */
gchar *dfilter; /* 显示过滤器字符串 */
colfilter *colors; /* 着色数据包窗口的颜色 */
dfilter *dfcode; /* 已经编译过的显示过滤器程序 */
#ifdef HAVE_LIBPCAP
gchar *cfilter; /* libpcap使用的捕获数据包过滤器表达式 */
bpf_prog fcode; /* 已经编译过的libpcap捕获过滤器程序 */
#endif
gchar *sfilter; /* 查找过滤器字符串 */
gboolean sbackward; /* 向后查找的时候为真,向前的时候为假 */
guint8 pd[WTAP_MAX_PACKET_SIZE]; /* 数据包数据 */
frame_data *plist; /* 数据包列表 */
frame_data *plist_end; /* 数据包链表中最后一个帧 */
frame_data *first_displayed; /* 第一个显示的帧 */
frame_data *last_displayed; /* 最后一个显示的帧 */
column_info cinfo; /* 列格式信息 */
frame_data *current_frame; /* 当前帧的帧数据 */
int current_row; /* 当前帧的行 */
gboolean current_frame_is_selected; /* 如果当前帧被选中,为真 */
proto_tree *protocol_tree; /* 当前选中的数据包的协议树 */
FILE *print_fh; /* 我们将要打印的文件句柄 */
} capture_file;
typedef struct _frame_data {
struct _frame_data *next; /* 链表的下一个结点 */
struct _frame_data *prev; /* 链表的前一个结点 */
guint32 num; /* 帧序号 */
guint32 pkt_len; /* 包长度 */
guint32 cap_len; /* 实际捕获到的数据的长度 */
guint32 rel_secs; /* 相对的秒数 */
guint32 rel_usecs; /* 相对的毫秒数 */
guint32 abs_secs; /* 绝对的秒数 */
guint32 abs_usecs; /* 绝对的毫秒数 */
guint32 del_secs; /* 差量秒数 */
guint32 del_usecs; /* 差量毫秒数 */
long file_off; /* 文件偏移量 */
column_info *cinfo; /* 列格式信息 */
gint row; /* 这个数据包的行序号 */
int lnk_t; /* 每一个数据包的封装类型 */
gboolean passed_dfilter; /* 真 = 显示,假 = 不显示 */
char_enc encoding; /* 字符编码(ASCII,EBCDIC ...) */
union pseudo_header pseudo_header; /* "pseudo-header" from wiretap */
} frame_data;
typedef struct GNode proto_tree;
typedef struct _packet_info {
int len;
int captured_len;
address dl_src; /* MAC 源地址*/
address dl_dst; /* MAC 目的地址 */
address net_src; /* IP源地址 */
address net_dst; /* IP目的地址 */
address src; /* 源地址*/
address dst; /* 目的地址*/
guint32 ipproto;
port_type ptype; /* 下面2个端口的类型 */
guint32 srcport; /* 源端口 */
guint32 destport; /* 目的端口 */
guint32 match_port;
int iplen;
int iphdrlen;
} packet_info;
typedef struct _address {
address_type type; /* 地址类型 */
int len; /* 地址的长度(按字节算) */
const guint8 *data; /* 组成地址的数据 */
} address;
当打开菜单 “Capture” -> “Options”时,系统过程
capture_prep_cb(GtkWidget *w, gpointer d)
|_ if_list = get_interface_list(); //获取可用的网卡
|_ //显示各种配置控件
|_ gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
GTK_SIGNAL_FUNC(capture_prep_ok_cb), GTK_OBJECT(cap_open_w)); //注册信号,当点击“OK”按钮时候,调用capture_prep_ok_cb函数
capture_prep_ok_cb(GtkWidget *ok_bt, gpointer parent_w)
|_ if_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(if_cb)->entry))); //获取指定的网卡
|_ filter_text = gtk_entry_get_text(GTK_ENTRY(filter_te)); //获取libpcap过滤器表达式
|_ cf.cfilter = g_strdup(filter_text); //将当前libpcap过滤器表达式填充到 cf 中
|_ save_file = gtk_entry_get_text(GTK_ENTRY(file_te)); //获取将要保存的文件路径
|_ do_capture(save_file); //开始捕获数据
do_capture(char *capfile_name)
|_ if (capfile_name != NULL) {cf.save_file_fd = open(capfile_name, O_RDWR|O_TRUNC|O_CREAT, 0600);} //如果capfile_name不为空,打开给出路径的文件,保存文件句柄
|_ else {cf.save_file_fd = create_tempfile(tmpname, sizeof tmpname, "ether");} //创建一个临时文件,然后保存文件句柄
|_ close_cap_file(&cf, info_bar); //重置 cf 结构体有关字段
|_ if (sync_mode) //如果是同步模式
fork() //创建子进程
execlp(ethereal_path, //以合适的参数启动 ethereal
i = read(sync_pipe[0], &c, 1); //父进程读取子进程写入管道的内容
i == 0 ? //子进程异常退出,关闭管道,删除文件,给出提示,返回
c == ; break; //跳出循环
if (!isdigit(c)) //子进程处理失败,父进程关闭管道,删除文件,给出提示,返回
byte_count = byte_count*10 + c - '0'; //计算 byte_count
|_ if (byte_count == 0) {err = start_tail_cap_file(cf.save_file, is_tempfile, &cf);} // 成功,打开记录好的文件
else {i = read(sync_pipe[0], msg, byte_count);} //失败了,接收子进程发送的错误消息,显示出来
|_ else // 不是同步模式
capture_succeeded = capture(); //调用 capture 捕获数据包
|_ if (capture_succeeded) //捕获成功
open_cap_file(cf.save_file, is_tempfile, &cf) //打开捕获到的数据文件
read_cap_file(&cf); //读取文件
|_ success = wtap_loop(cf->wth, 0, wtap_dispatch_cb, (u_char *) cf, &err); //进入wtap_loop,每获得一个数据包,调用wtap_dispatch_cb 函数
|_ cf->lnk_t = wtap_file_encap(cf->wth); //设置wtap描述符封装类型
|_ wtap_close(cf->wth); //关闭wtap描述符
wtap_dispatch_cb(u_char *user, const struct wtap_pkthdr *phdr, int offset,
const u_char *buf) ; //wtap回调函数,每当wtap获得一个数据包,调用这个函数
|_ fdata = (frame_data *) g_malloc(sizeof(frame_data)); //申请内存用来保存一帧的数据
|_ protocol_tree = proto_tree_create_root(); //创建协议树
|_ dissect_packet(buf, fdata, protocol_tree); //分析一个数据包
|_ proto_tree_add_item_format () //安照协议树格式添加树
|_ fh_tree = proto_item_add_subtree(ti, ett_frame); //添加子树
|_ proto_tree_add_item(fh_tree, hf_frame_arrival_time, //添加帧到达时间
|_ proto_tree_add_item(fh_tree, hf_frame_time_delta, //添加帧和上一个包到达时间的间隔
|_ proto_tree_add_item(fh_tree, hf_frame_number, //添加帧的序号
|_ proto_tree_add_item_format(fh_tree, hf_frame_packet_len, //添加帧的包长度
|_ proto_tree_add_item_format(fh_tree, hf_frame_capture_len, //添加实际捕获到的包的长度
|_ blank_packetinfo(); //置空数据包信息这个数据结构的有关字段
|_ case WTAP_ENCAP_ETHERNET :dissect_eth(pd, 0, fd, tree); //根据这个数据包的封装类型,进行解析
|_ SET_ADDRESS(&pi.dl_src, AT_ETHER, 6, &pd[offset+6]); //设置MAC源地址
|_ SET_ADDRESS(&pi.dl_dst, AT_ETHER, 6, &pd[offset+0]); //设置MAC目的地址
|_ etype = pntohs(&pd[offset+12]); //获得以太网数据类型
|_ proto_tree_add_item_format //安照协议树格式添加树
|_ proto_item_add_subtree //添加子树
|_ proto_tree_add_item(fh_tree, hf_eth_dst, offset+0, 6, &pd[offset+0]); //添加目的MAC
|_ proto_tree_add_item(fh_tree, hf_eth_src, offset+6, 6, &pd[offset+6]); //添加源MAC
|_ ethertype(etype, offset, pd, fd, tree, fh_tree, hf_eth_type); //根据以太网类型分析数据封包
|_ proto_tree_add_item(fh_tree, item_id, offset - 2, 2, etype); //添加类型
|_ dissect_ip(pd, offset, fd, tree); //分析这个IP数据封包
|_ passed = dfilter_apply(cf->rfcode, protocol_tree, cf->pd); //应用显示过滤器
|_ proto_tree_free(protocol_tree); //释放协议树资源
|_ cf->plist = fdata; //将 cf -> plist 指针指向分析后的数据 fdata
|_ add_packet_to_packet_list(fdata, cf, buf); //将分析的数据添加到数据包列表
当在显示过滤器编辑框里面输入表达式,并按下回车键后,系统过程
filter_activate_cb(GtkWidget *w, gpointer data)
|_ char *s = gtk_entry_get_text(GTK_ENTRY(w)); //获取编辑框字符串
|_ filter_packets(&cf, g_strdup(s) //使用这个过滤表达式过滤数据包,然后重新显示在列表控件上
|_ dfilter_compile(dftext, &dfcode) //编译过滤器,其中包含了分析语法的过程
|_ g_free(cf->dfilter); //释放 cf 结构体的dfilter字段
|_ cf->dfilter = dftext; //重新赋值 cf 结构体的dfilter字段
|_ dfilter_destroy(cf->dfcode); //释放 cf 结构体的dfcode字段
|_ cf->dfcode = dfcode; //重新赋值 cf 结构体的dfcode字段
|_ colorize_packets(cf); //遍历解析到的数据包的链表,将符合规则的数据包显示出来
|_ conversation_init(); //初始化会话表
|_ init_all_protocols(); //初始化特定的协议变量
|_ gtk_clist_freeze(GTK_CLIST(packet_list)); //禁用数据包列表控件
|_ gtk_clist_clear(GTK_CLIST(packet_list)); //清空数据包列表
|_ for (fd = cf->plist; fd != NULL; fd = fd->next) //遍历数据包链表
{
wtap_seek_read (cf->cd_t, cf->fh, fd->file_off, cf->pd, fd->cap_len); //读取数据包内容
add_packet_to_packet_list(fd, cf, cf->pd); //添加这个数据包到数据包列表控件
}
|_ gtk_clist_moveto(GTK_CLIST(packet_list), cf->current_row, -1, 0.0, 0.0); //移动到current_row指定的数据包这一行
|_ gtk_clist_select_row(GTK_CLIST(packet_list), cf->current_row, -1); //选中这一行
|_ gtk_clist_thaw(GTK_CLIST(packet_list)); //启用数据包列表控件
当鼠标选中数据列中的一行时,调用
packet_list_select_cb(GtkWidget *w, gint row, gint col, gpointer evt)
|_ blank_packetinfo(); //置空数据包信息这个数据结构的有关字段
|_ select_packet(&cf, row); //选择这一行的数据包
|_ wtap_seek_read(); //获取这一帧的数据
|_ proto_tree_create_root(); //创建协议树
|_ dissect_packet(); 分析这一帧的数据包
当对选中的TCP数据包进行“Follow Tcp Stream”操作时
follow_stream_cb (GtkWidget *w, gpointer data )
|_ tmp_fd = create_tempfile( filename1, sizeof filename1, "follow"); //创建临时文件
|_ data_out_file = fdopen( tmp_fd, "w" ); //打开这个临时文件
|_ reset_tcp_reassembly(); //重置 TCP 组装
|_ follow_filter = build_follow_filter( &pi ); //生成 follow 过滤表达式
|_ filter_packets(&cf, follow_filter); //过滤数据包
|_ dfilter_compile(dftext, &dfcode); //编译过滤器
|_ df = dfilter_new(); //创建一个新的过滤器
|_ dfilter_scanner_text(dfilter_text); //告诉扫描器使用过滤表达式字符串作为输入
|_ retval = dfilter_parse(); //分析语法
|_ dfilter_scanner_cleanup(); //清理扫描器规则
|_ colorize_packets(cf); //着色过滤后的数据包
|_ follow_load_text(text, filename1, TRUE); //将分析后的数据显示到 text控件上面