tcpdump源码分析

进入tcpdump.c(函数入口)之前,先看一些头文件netdissect.h里定义了一个数据结构struct netdissect_options来描述tcdpump支持的所有参数动作,每一个参数有对应的flag, 在tcpdump 的main 里面, 会根据用户的传入的参数来增加相应flag数值, 最后根据这些flag数值来实现特定动作。各个参数含义请参考源代码注释。

1.netdissect.h 头文件

struct netdissect_options {int ndo_bflag;              /* 以 ASDOT 表示法打印 4 字节 AS 号 */int ndo_eflag;              /* 打印以太网头 */int ndo_fflag;              /* 不翻译“外部”IP 地址 */int ndo_Kflag;              /* 不检查 IP、TCP 或 UDP 校验和 */int ndo_nflag;              /* 将地址显示为数字形式 */int ndo_Nflag;              /* 打印主机名时去掉域名 */int ndo_qflag;              /* 简短输出 */int ndo_Sflag;              /* 打印原始 TCP 序列号 */int ndo_tflag;              /* 打印数据包到达时间 */int ndo_uflag;              /* 打印未解码的 NFS 句柄 */int ndo_vflag;              /* 冗余级别 */int ndo_xflag;              /* 以十六进制打印数据包 */int ndo_Xflag;              /* 以十六进制/ASCII 打印数据包 */int ndo_Aflag;              /* 仅以 ASCII 打印数据包,并将 TAB、LF、CR 和 SPACE 视为图形字符 */int ndo_Hflag;              /* 解析 802.11s 草案网状标准 */const char *ndo_protocol;   /* 协议 */jmp_buf ndo_early_end;      /* 用于 setjmp()/longjmp() 的 jmp_buf */void *ndo_last_mem_p;       /* 指向最后分配的内存块的指针 */int ndo_packet_number;      /* 在行首打印数据包编号 */int ndo_print_sampling;     /* 打印每 N 个数据包 */int ndo_suppress_default_print; /* 对未知的数据包类型不使用 default_print() */int ndo_tstamp_precision;   /* 请求的时间戳精度 */const char *program_name;   /* 使用该库的程序名称 */char *ndo_espsecret;               /* ESP 密钥 */struct sa_list *ndo_sa_list_head;  /* 由 print-esp.c 使用 */struct sa_list *ndo_sa_default;char *ndo_sigsecret;        /* 签名验证密钥 */int ndo_packettype;         /* 由 -T 指定的数据包类型 */int ndo_snaplen;            /* 抓取长度 */int ndo_ll_hdr_len;         /* 链路层头长度 *//* 全局指针,指向当前数据包的开始和结束(在打印过程中) */const u_char *ndo_packetp;const u_char *ndo_snapend;/* 保存的数据包边界和缓冲区信息的堆栈 */struct netdissect_saved_packet_info *ndo_packet_info_stack;/* 指向 if_printer 函数的指针 */if_printer ndo_if_printer;/* 指向输出内容的 void 函数的指针 */void (*ndo_default_print)(netdissect_options *,const u_char *bp, u_int length);/* 指向执行常规输出的函数的指针 */int  (*ndo_printf)(netdissect_options *,const char *fmt, ...)PRINTFLIKE_FUNCPTR(2, 3);/* 指向输出错误的函数的指针 */void NORETURN_FUNCPTR (*ndo_error)(netdissect_options *,status_exit_codes_t status,const char *fmt, ...)PRINTFLIKE_FUNCPTR(3, 4);/* 指向输出警告的函数的指针 */void (*ndo_warning)(netdissect_options *,const char *fmt, ...)PRINTFLIKE_FUNCPTR(2, 3);
};

字段解释:

  • 标志位

    • ndo_bflag: 决定是否以 ASDOT 表示法打印 4 字节 AS 号。
    • ndo_eflag: 决定是否打印以太网头。
    • ndo_fflag: 决定是否不翻译“外部”IP 地址。
    • ndo_Kflag: 决定是否不检查 IP、TCP 或 UDP 校验和。
    • ndo_nflag: 决定是否将地址保留为数字形式。
    • ndo_Nflag: 决定是否在打印主机名时去掉域名。
    • ndo_qflag: 决定是否进行简短输出。
    • ndo_Sflag: 决定是否打印原始 TCP 序列号。
    • ndo_tflag: 决定是否打印数据包到达时间。
    • ndo_uflag: 决定是否打印未解码的 NFS 句柄。
    • ndo_vflag: 设置冗余级别。
    • ndo_xflag: 决定是否以十六进制打印数据包。
    • ndo_Xflag: 决定是否以十六进制和 ASCII 打印数据包。
    • ndo_Aflag: 决定是否仅以 ASCII 打印数据包,并将 TAB、LF、CR 和 SPACE 视为图形字符。
    • ndo_Hflag: 决定是否解析 802.11s 草案网状标准。
  • 其他选项

    • ndo_protocol: 指定的协议。
    • ndo_early_end: 用于 setjmp()/longjmp() 的跳转缓冲区。
    • ndo_last_mem_p: 指向最后分配的内存块的指针。
    • ndo_packet_number: 决定是否在行首打印数据包编号。
    • ndo_print_sampling: 打印每 N 个数据包。
    • ndo_suppress_default_print: 决定是否对未知的数据包类型不使用 default_print()。
    • ndo_tstamp_precision: 请求的时间戳精度。
    • program_name: 使用该库的程序名称。
  • ESP 和签名相关

    • ndo_espsecret: ESP 密钥。
    • ndo_sa_list_head, ndo_sa_default: 由 print-esp.c 使用的 SA 列表。
    • ndo_sigsecret: 签名验证密钥。
  • 数据包和链接层相关

    • ndo_packettype: 由 -T 指定的数据包类型。
    • ndo_snaplen: 抓取长度。
    • ndo_ll_hdr_len: 链路层头长度。
    • ndo_packetp, ndo_snapend: 当前数据包的开始和结束指针。
    • ndo_packet_info_stack: 保存的数据包边界和缓冲区信息的堆栈。
  • 函数指针

    • ndo_if_printer: 指向 if_printer 函数的指针。
    • ndo_default_print: 指向输出内容的 void 函数的指针。
    • ndo_printf: 指向执行常规输出的函数的指针。
    • ndo_error: 指向输出错误的函数的指针。
    • ndo_warning: 指向输出警告的函数的指针。

此次需要关注的是 “ndo_snaplen: 抓取长度”变量。

2.从指针安全读取x字节的相关宏定义

在 print-eigrp.c 中有如下代码:

GET_BE_U_2(eigrp_com_header->checksum),

 继续追踪到 extract.h 文件:

#define GET_BE_U_2(p) get_be_u_2(ndo, (const u_char *)(p))

  继续追踪,仍在 extract.h 文件中:get_be_u_2 可见是一个内联函数

static inline uint16_t
get_be_u_2(netdissect_options *ndo, const u_char *p)
{if (!ND_TTEST_2(p))nd_trunc_longjmp(ndo);return EXTRACT_BE_U_2(p);
}

这个内联函数 get_be_u_2 做了以下几件事:

  1. 调用 ND_TTEST_2(p) 来检查是否可以从指针 p 安全地读取 2 个字节。
  2. 如果检查失败,调用 nd_trunc_longjmp(ndo) 进行错误处理,通常会跳转到某个提前定义的错误处理位置。
  3. 如果检查成功,调用 EXTRACT_BE_U_2(p) 从指针 p 提取 2 个字节的数据,并将其从网络字节顺序转换为主机字节顺序。

继续追踪 ND_TTEST_2 宏函数,仍在 extract.h 文件中:

#define ND_TTEST_2(p) ND_TTEST_LEN((p), 2)
//这个宏调用 ND_TTEST_LEN,传递参数 p 和长度 2,表示要检查从指针 p 开始的 2 个字节。 

继续追踪 ND_TTEST_LEN 宏函数,跳转到 netdissect.h 文件中:


/** True if "l" bytes from "p" were captured.** The "ndo->ndo_snapend - (l) <= ndo->ndo_snapend" checks to make sure* "l" isn't so large that "ndo->ndo_snapend - (l)" underflows.** The check is for <= rather than < because "l" might be 0.** We cast the pointers to uintptr_t to make sure that the compiler* doesn't optimize away any of these tests (which it is allowed to* do, as adding an integer to, or subtracting an integer from, a* pointer assumes that the pointer is a pointer to an element of an* array and that the result of the addition or subtraction yields a* pointer to another member of the array, so that, for example, if* you subtract a positive integer from a pointer, the result is* guaranteed to be less than the original pointer value). See**	https://www.kb.cert.org/vuls/id/162289*//** Test in two parts to avoid these warnings:* comparison of unsigned expression >= 0 is always true [-Wtype-limits],* comparison is always true due to limited range of data type [-Wtype-limits].*//** 如果从 "p" 开始的 "l" 字节被捕获,则返回真。** 通过 "ndo->ndo_snapend - (l) <= ndo->ndo_snapend" 的检查来确保* "l" 不会大到使 "ndo->ndo_snapend - (l)" 发生下溢。** 检查使用 <= 而不是 < 是因为 "l" 可能为 0。** 我们将指针转换为 uintptr_t 是为了确保编译器不会优化掉这些测试* (编译器被允许这样做,因为将整数加到指针上,或从指针上减去整数,* 假定指针是一个数组元素的指针,并且加法或减法的结果会生成另一个数组成员的指针,* 因此,例如,如果从指针上减去一个正整数,结果保证小于原始指针值)。参见**	https://www.kb.cert.org/vuls/id/162289*//** 分两部分测试以避免这些警告:* unsigned 表达式的比较总是大于等于 0 是始终为真的 [-Wtype-limits],* 由于数据类型的有限范围,比较总是为真 [-Wtype-limits]。*/#define IS_NOT_NEGATIVE(x) (((x) > 0) || ((x) == 0))#define ND_TTEST_LEN(p, l) \(IS_NOT_NEGATIVE(l) && \((uintptr_t)ndo->ndo_snapend - (l) <= (uintptr_t)ndo->ndo_snapend && \(uintptr_t)(p) <= (uintptr_t)ndo->ndo_snapend - (l)))
  1. 这个宏检查以下几个条件:

    • IS_NOT_NEGATIVE(l):长度 l 必须是非负数。
    • ((uintptr_t)ndo->ndo_snapend - (l) <= (uintptr_t)ndo->ndo_snapend):确保 ndo->ndo_snapend 减去 l 不会发生下溢。
    • ((uintptr_t)(p) <= (uintptr_t)ndo->ndo_snapend - (l)):确保指针 p 不会越过数据包的边界。

ND_TTEST_2 是一个宏,用于检查是否可以安全地从指定的指针 p 读取 2 个字节(即 16 位)。它通过验证内存边界和指针合法性来确保不会发生越界访问。具体来说,它结合了多个宏和函数,形成一个完整的边界检查机制。

总结

ND_TTEST_2 的主要目的是确保在从数据包读取数据时不会发生越界访问,从而保证程序的安全性和稳定性。它通过一系列的检查来验证指针和长度的合法性,防止因非法内存访问导致的崩溃或数据损坏。

3.setjmp/longjmp 非局部跳转函数的应用

现在存在的问题是没有搞清tcpdump 的执行流程:

调用顺序:

(gdb) bt
#0  eigrp_print (ndo=0x7fffffffcf00, pptr=0x8ea512 "\002\005\356h", len=40) at ./print-eigrp.c:233
#1  0x0000000000458255 in ip_demux_print (ndo=0x7fffffffcf00, bp=0x8ea512 "\002\005\356h", length=40, ver=4, fragmented=0, ttl_hl=2, nh=88 'X', iph=0x8ea4fe "E\300") at ./print-ip-demux.c:143
#2  0x00000000004227cf in ip_print (ndo=0x7fffffffcf00, bp=0x8ea4fe "E\300", length=60) at ./print-ip.c:487
#3  0x000000000041dbbc in ethertype_print (ndo=0x7fffffffcf00, ether_type=2048, p=0x8ea4fe "E\300", length=60, caplen=60, src=0x7fffffffccb0, dst=0x7fffffffcca0) at ./print-ether.c:536
#4  0x000000000041d680 in ether_common_print (ndo=0x7fffffffcf00, p=0x8ea4fe "E\300", length=60, caplen=60, print_switch_tag=0x0, switch_tag_len=0, print_encap_header=0x0, encap_header_arg=0x0) at ./print-ether.c:391
#5  0x000000000041d7c1 in ether_print (ndo=0x7fffffffcf00, p=0x8ea4f0 "\001", length=74, caplen=74, print_encap_header=0x0, encap_header_arg=0x0)at ./print-ether.c:448
#6  0x000000000041d81a in ether_if_print (ndo=0x7fffffffcf00, h=0x7fffffffce40, p=0x8ea4f0 "\001") at ./print-ether.c:464
#7  0x0000000000407ead in pretty_print_packet (ndo=0x7fffffffcf00, h=0x7fffffffce40, sp=0x8ea4f0 "\001", packets_captured=1) at ./print.c:414
#8  0x0000000000407343 in print_packet (user=0x7fffffffcf00 "", h=0x7fffffffce40, sp=0x8ea4f0 "\001") at ./tcpdump.c:3127
#9  0x00000000004f4f07 in pcap_offline_read (p=p@entry=0x8ea250, cnt=2147483647, cnt@entry=-1, callback=callback@entry=0x4072ed <print_packet>, user=user@entry=0x7fffffffcf00 "") at ./savefile.c:691
#10 0x00000000004e2baf in pcap_loop (p=0x8ea250, cnt=-1, callback=0x4072ed <print_packet>, user=0x7fffffffcf00 "") at ./pcap.c:2916
#11 0x00000000004066c2 in main (argc=3, argv=0x7fffffffe3c8) at ./tcpdump.c:2569

setjmp() 函数是在 #7 的 pretty_print_packet() 函数中调用的。

参考:

1、非局部跳转:8.6 非本地跳转 | 深入理解计算机系统(CSAPP) (gitbook.io)

2、 【C指针(五)】6种转移表实现整合longjmp()/setjmp()函数和qsort函数详解分析&&模拟实现-支付宝开发者社区 (alipay.com)

3、tcpdump 源码解析:https://www.cnblogs.com/pangblog/p/3364690.html

 分析2:tcpdump源码分析-CSDN博客

4、tcpdump 越界访问追踪: tcpdump 4.5.1 crash 深入分析-安全客 - 安全资讯平台

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/16276.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙ArkTS声明式开发:跨平台支持列表【触摸事件】

触摸事件 当手指在组件上按下、滑动、抬起时触发。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独…

总是等不是办法,向媒体投稿你得学会用新方法

初入信息宣传领域,我怀揣着对文字的热爱与传播价值的热情,肩负起了单位活动的宣传报道重任。那时的我,满脑子都是传统的投稿思维:精心撰写每一篇稿件,然后逐一搜寻各大媒体的投稿邮箱,一封封邮件满怀期待地发出,像播撒希望的种子,渴望在广袤的媒体土壤中生根发芽。然而,理想很丰…

红蓝对抗-HW红蓝队基本知识(网络安全学习路线笔记)

第一, 什么是蓝队 蓝队&#xff0c;一般是指网络实战攻防演习中的攻击一方。 蓝队一般会采用针对目标单位的从业人员&#xff0c;以及目标系统所在网络内的软件、硬件设备同时执行多角度、全方位、对抗性的混合式模拟攻击手段&#xff1b;通过技术手段实现系统提权、控制业务、…

将点位转换为圆环极坐标绘画

将一段染色体可视化为一个圆环,根据一段基因的起始点和终止点绘画,根据基因的方向绘画箭头,可以任意确定染色体哪个位置在哪个角度上,例如染色体的1700点位在180上,默认是顺时针方向从起始点向终止点绘画。 1.将一段染色体的基因数组加上极坐标绘画属性 function compute…

pycharm连接阿里云服务器过程记录

因为不想用自己的电脑安装anaconda环境,所以去查了一下怎么用服务器跑代码,试着用pycharm连接阿里云服务器,参考了很多博客,自己简单配置了一下,记录一下目前完成的流程. 主要是:阿里云服务器的远程登录和安装anaconda,以及怎么用pycharm连接阿里云服务器上的解释器. 小白刚开始…

Day 3:1738. 找出第 K 大的异或坐标值

Leetcode 1738. 找出第 K 大的异或坐标值 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下标从 0 开始计…

Dou音滑块日志分析

记得加入我们的学习群&#xff1a;961566389 点击链接加入群聊&#xff1a;[https://h5.qun.qq.com/s/62P0xwrCNO](https://h5.qun.qq.com/s/62P0xwrCNO) 1.插桩-打印日志 获取背景和滑块的图片的接口一看没啥参数需要逆向的 验证的接口body参数需要进行逆向&#xff0c;直接…

浅谈Docker容器的网络通信原理

文章目录 1、回顾容器概念2、容器网络3、容器与主机之间的网络连通4、交换机的虚拟实现---虚拟网桥&#xff08;Bridge&#xff09;5、Docker 守护进程daemon管理容器网络 1、回顾容器概念 我们知道容器允许我们在同一台宿主机&#xff08;电脑&#xff09;上运行多个服务&…

moviepy入门

1. 简介 由于恶心的工作和没有规划的部门安排&#xff0c;我被排到了算法部门&#xff0c;从事和算法没有半毛钱关系的业务上&#xff0c;也就是。。。搞视频。咋说呢&#xff1f;视频这东西我没有一点基础&#xff0c;还好有前人写好的代码&#xff0c;用的是moviepy和ffmpeg…

Zoho Campaigns邮件营销怎么发邮件?

Zoho Campaigns&#xff0c;作为业界领先的邮件营销平台&#xff0c;以其强大的功能、用户友好的界面以及深度的分析能力&#xff0c;为企业提供了一站式的邮件营销解决方案&#xff0c;助力企业高效地触达目标受众&#xff0c;构建并巩固庞大的客户基础。云衔科技为企业提供Zo…

数据结构(四)

数据结构&#xff08;四&#xff09; 算法算法的特征算法和程序的区别怎么样评判一个算法的好坏 常见的查找算法线性树状哈希查找构建哈希函数的方法质数求余法解决冲突 算法 一堆指令的有序集合 算法的特征 唯一性&#xff1a;每一句话只有一种解释 有穷性&#xff1a;算法能…

企业活动想找媒体报道宣传怎样联系媒体?

在那遥远的公关江湖里,有一个传说,说的是一位勇士,手持鼠标和键盘,踏上了寻找媒体圣杯的征途。这位勇士,就是我们亲爱的市场部门小李,他的任务是为公司即将举行的一场盛大的企业活动找到媒体的聚光灯。 小李的故事,开始于一张空白的Excel表格,上面列着各大媒体的名称,旁边是一片…

如何让大模型更聪明

目录 如何让大模型更聪明&#xff1f; &#x1f349;算法创新 &#x1f348;新型优化算法 &#x1f34d;案例分析&#xff1a;LAMB优化器 &#x1f348;对比学习 &#x1f34d;应用案例&#xff1a;SimCLR &#x1f348;强化学习 &#x1f34d;案例分析&#xff1a;Alph…

【30天精通Prometheus:一站式监控实战指南】第4天:node_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

蓝桥杯-班级活动

题目描述 小明的老师准备组织一次班级活动。班上一共有 ( n ) 名&#xff08;( n ) 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 ( n ) 以内的正整数作为 id&#xff0c;第 …

C++标准库中string的底层实现方式

对于C中 std::string 的一些基本功能和用法&#xff0c;我们应该都很熟悉。但它底层到底是如何实现的呢? 其实在 std::string 的历史中&#xff0c;出现过几种不同的方式。下面我们来一一揭晓。 我们可以从一个简单的问题来探索&#xff0c;一个 std::string 对象占据的内存空…

RK3568笔记二十五:RetinaFace人脸检测训练部署

若该文为原创文章&#xff0c;转载请注明原文出处。 一、介绍 Retinaface是来自insightFace的又一力作&#xff0c;基于one-stage的人脸检测网络。RetinaFace是在RetinaNet基础上引申出来的人脸检测框架&#xff0c;所以大致结构和RetinaNet非常像。 官方提供两种主干特征提取网…

Python 中别再用 ‘+‘ 拼接字符串了!

当我开始学习 Python 时&#xff0c;使用加号来连接字符串非常直观和容易&#xff0c;就像许多其他编程语言&#xff08;比如Java&#xff09;一样。 然而&#xff0c;很快我意识到许多开发者似乎更喜欢使用.join()方法而不是。 在本文中&#xff0c;我将介绍这两种方法之间的…

关于数据库和数据表的基础SQL

目录 一. 数据库的基础SQL 1. 创建数据库 2. 查看当前有哪些数据库 3. 选中数据库 4. 删除数据库 5. 小结 二. 数据表的基础SQL 1. 创建数据表 2. 查看当前数据库中有哪些表 3. 查看指定表的详细情况(查看表的结构) 4. 删除表 5. 小结 一. 数据库的基础SQL 1. 创建…

python内置函数map/filter/reduce详解

在Python中&#xff0c;map(), filter(), 和 reduce() 是内置的高级函数(实际是class)&#xff0c;用于处理可迭代对象&#xff08;如列表、元组等&#xff09;的元素。这些函数通常与lambda函数一起使用&#xff0c;以简洁地表达常见的操作。下面我将分别解释这三个函数。 1. …