news 2026/4/16 17:54:37

HNU软件安全测试模糊测试(改写Coverage类)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HNU软件安全测试模糊测试(改写Coverage类)

Fuzzing Book

Coverage Assignment

写在前面:文档中标注此次作业会查重,有需要的同学记得稍微修改一部分,别完全照搬了

还没有在本地配置好Fuzzing Book的同学可以参考:

Fuzzing Book安装指南

《Fuzzing Book》的"Code Coverage"章节介绍了用于测量Python程序覆盖率的Coverage类。在模糊测试的上下文中,覆盖率信息用于引导测试向未覆盖的代码位置。该章节主要介绍了Coverage类基础用法:

  • 基本语法:使用with Coverage() as cov:上下文管理器来捕获代码执行
  • 可视化输出:打印覆盖率对象会显示已覆盖和未覆盖的代码行(未覆盖行用#标记)

作业任务:改写Coverage类,增加event的种类为:line,call,return。

通过调用fuzzer(),随机生成长度在100个字符以内的字符串,连续跑10次,分别输入到cgi_decode(),观察程序的执行情况,打印出执行trace和覆盖率。

我们对任务进行分解:

  1. 首先我们分析一下原Coverage类

  • __init__函数:

首先定义了一个初始化函数,该部分会初始化一个空列表_trace,用于存储按执行顺序记录的每一行代码(因为原Coverage定义了event是"line")的位置。

  • 接下来我们先看这部分(与with配合使用):

我们在这里先介绍一下with的使用方法。

with需要配套实现__enter__()和__exit__()两个方法,这里定义通过OBJECT进行管理,然后将__enter__()的返回值给VARIABLE,之后执行BODY

那原Coverage这部分代码中:

首先__enter__()保存了原有的追踪函数(保存为original_trace_function),接下来将追踪函数设为我们自己实现的traceit(),然后返回自身给cov(相当于Coverage类的一个示例,可以用来访问和调用函数变量)

然后__exit__()恢复原有的追踪函数,返回None(表示让所有异常通过,即异常会向外抛出)

  • traceit函数

该函数是追踪函数,如果original_trace_function不是None,即先调用原有的追踪函数(如果存在的话),这时候会将frame(帧,上下文)、event(事件类型),arg(参数)传给原有的追踪函数;如果不存在就执行下面的内容:判断事件是否是"line"(原Coverage只定义了追踪换行事件),如果是的话,获取当前行的函数名、行号(如果是__exit__函数就跳过,避免追踪自己的退出函数,否则结果会含有大量的__exit__行号),将函数名和行号加入_trace列表。

  • 结果获取方法(trace、coverage、function_names)

  • 该部分是对外返回结果的函数。trace会直接返回_trace(原始结果);coverage会对原始结构进行去重操作(set是集合);function_names会返回所有执行过的函数名(也会去重,有set操作)
  • __repr__函数

这部分是可视化输出,遍历所有执行过的函数名,通过eval将函数名对应到具体的函数位置;如果出现异常会进入except块,把异常赋值给exc,然后记录跳过原因(跳过某个函数,因为什么原因),然后继续执行下一个函数。

之后通过inspect.getsourcelines(fun) 获取函数的源代码行数和起始行号,分别赋值给source_lines,start_line_num,对于源代码的所有行进行遍历,如果这个行没有出现在我们的_trace中,就将开头标记上#,执行过的行就在前面加上空格(与上面对齐),然后加上对应的行号(格式化对齐,至少两个字符宽度),接下来把这行的内容加到后面。

  1. fuzzer函数

这个函数是最fuzzing book介绍的最基础的函数:

这个函数是用来生成一组随机字符串,定义了最大字符串长度是100,起始char(ASCII码)值是32,char可选长度是32,即可以选择32-63的字符。接下来用随机数随机初始化字符串长度(0到100),然后构建字符串:每一位字符通过随机生成32-63的值,然后通过chr函数得到一个字符,将字符作为字符串out的当前位。最终输出out

  1. cgi_decode函数

这个函数是用来对字符串进行处理,遇到“+”会转为空格,如果是%,后续会读取两个字符,通过预先定义的hex_values,将其作为十六进制数转为十进制数,然后再转成ASCII码对应的字符(如果出现不符合的值就弹出错误)。如果是普通的字符就直接拷贝,最终返回处理完的字符串。

  1. 作业实现

首先对返回结果我们定义一个四元组,分别是函数名字,行号,触发的事件类型,和如果是return类型额外添加了其返回值(补充实现)

之后分别将cgi_decode和fuzzer函数拷贝过来直接复用即可

之后便是修改Coverage类

通过前面对于这个类各个功能的实现,我们很容易发现需要修改的地方:

  • traceit中对于event的操作:

前面原实现只进行了行的追踪,所以判断只需要判断event是否是line即可,而我们需要扩展为line、call、return,故需要对其进行修改:

使用in来判断event是否是其中之一。

我们前面提到return不同于其他两个,它应该多一个元素(即返回值)需要记录,所以我们还需要单独对其进行处理:

注意这里的哈希是为什么,因为原返回值可能是数字,字符串等可哈希的结果(为什么需要可哈希,因为coverage方法采用了集合,集合中的元素要求是可哈希的对象),直接返回就可以看见真实返回值;如果不可哈希就通过repr将返回值变成字符串类型(与str()相似)

然后返回四个元素(注意line和call第四个元素是None)

  • __repr__函数的实现:

主要修改这一部分(跟输出挂钩)

我们使用了一个嵌套生成器表达式+any函数,作用:
检查追踪的事件集合中是否存在函数名为当前函数名、行号是当前行号、事件类型符合我们的要求的结果,只要有一个满足条件(any)就返回True(表示这一行被覆盖)——即判断当前函数的当前行有没有被line、call、return任意一个事件触发。

虽然这样修改后直接执行以及能得到大概符合要求的结果了,但是依旧存在一个问题:

为什么会出现这个问题?

因为原__repr__函数在获取当前函数名的时候使用了eval来获取该函数的具体位置

而我们出现的错误是这些都是系统内部函数,无法通过eval()获取。需要修复__repr__方法:

我们还是先尝试使用eval调用,如果不能获取(捕获两种异常:找不到函数名或解析失败),那就尝试从全局主命名空间寻找函数对象:

导入__main__(python当前运行的主模块命名空间,存放所有在主脚本定义的函数/变量),使用hasattr,检查当前主命名空间是否存在当前名为function_name的对象,如果存在就使用getattr来获取这个对象,并且赋值给fun(跟前面正常情况对应)

如果__main__也不能获取到该函数名(hasattr返回false),就降级打印,只显示函数名和行号(并且去重并且按行号排序)

尽管后续运行基本上二次搜索也会失败,但可视性会比之前好很多

这部分尝试获取源代码,如果出现异常(源代码文件不存在或为内置函数)则降级(跟前面类似),只显示函数名+覆盖的行号

如果能获取源代码,这部分是我们修复前的部分,就不重复讲了。

接下来是测试代码

首先定义了一个全局变量all_coverage记录覆盖情况(去重)

接下来使用range(1,11)实现遍历十次(含前不含后,且默认步长是1)

调用fuzzer生成一个随机字符串作为测试的输入,然后讲这个字符串先进行一个打印

然后进入with部分:使用Coverage类进行跟踪,为了增强代码的健壮性与测试的可视性,我们增加了对执行结果的判断:

执行目标cgi_decode,进行解码,如果成功就把值返回给result并且打印成功信息,如果出现异常:ValueError(这部分是原本cgi_decode实现了的,对于%后的两位数进行十六进制转十进制,如果是无效编码就会返回这个)、IndexError(越界情况,即%可能出现后面不足两个字符的情况)、Exception(其他异常),然后打印具体异常类型的情况。

代码如下:

接下来是输出部分

首先我们使用print(cov)这个会自动调用Coverage类里面的__repr__函数,即可以将具体的覆盖情况打印出来:

如图,结果与我们预期的__repr__中的结果一致,对于前面和后面出现的内置函数,无法获取源码,我们只打印函数名和覆盖的行号,对于可以获取源码(也是我们核心目标)的cgi_decode,我们可以按照预期讲整个函数的覆盖情况打印出来,标记未运行到的行号

然后打印我们追踪的三个事件,每个事件的不同情况:

由于覆盖的行号很多,全部显示不太合理,我们只显示前20条

接下来打印call事件的情况:

然后是return事件(注意我们前面在traceit中提到了return应该相比前两种方法加上对应的返回值):

需要注意有些函数是没有返回值的,所以我们需要兼容原本考虑三个元素的情况,单独考虑返回值

每一轮执行完成,我们进行一个覆盖率总结:

按每个事件类型单独统计(使用set,会去重),通过前面定义的location元组的第2位即event类型来区分

然后分别打印每个事件覆盖的位置数,将具体的覆盖情况打印出来,最后将这一轮的情况存入前面定义的all_coverage中:

最后对十次结果进行一个统一的总结:

  1. 输出优化

我们刚才的实现以及符合要求了,但是在可视化方面仍然有缺陷,因为我们还是不能直观的了解程序是怎么执行的(执行顺序),所以可以对输出进行优化:

将结果按照执行的顺序依次打印,line,call,return混合,可以帮助我们更好的了解程序怎么执行,在哪里做了什么操作:

可以看看结果:

对于这样一串成功运行的字符串:

我们可以查看追踪结果

首先进行了call调用cgi_decode,然后在该程序中进行运行

然后在循环进行字符串格式转换后,在33行return,返回了操作完成的字符串;

接着call了write函数(调用cgi_decode后续我们进行了print操作,打印了执行结果)

这部分进行write内的调用操作,一直到:

这里返回了我们写了62位字符。

其实这里主要原因是因为我们在调用Coverage类的时候在里面使用了print语句,如果不希望出现这些函数可以将print删除。

不过针对这个作业,我们还是希望出现多用的call与return事件的,所以我进行了保留。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 6:31:43

开源项目分享:Gitee热榜项目 2025年12月第二周 周榜

本文档整理Gitee本周热门开源项目,包含名称、链接、星级、描述及本周趋势分析。 1. fay 项目链接:https://gitee.com/xszyou/fay Star 数:1680 项目描述:Fay是一个专为连接数字人(涵盖2.5D、3D、移动端、PC端及网页端…

作者头像 李华
网站建设 2026/4/16 2:15:18

Stata 15.1 中介效应 Sobel 检验完整指南:快速掌握中介分析技巧

Stata 15.1 中介效应 Sobel 检验完整指南:快速掌握中介分析技巧 【免费下载链接】Stata15.1中介效应Sobel检验安装包 Stata 15.1 中介效应 Sobel 检验安装包 项目地址: https://gitcode.com/open-source-toolkit/55355 想要在 Stata 15.1 中轻松进行中介效应…

作者头像 李华
网站建设 2026/4/16 13:53:59

AI图片修复革命:LaMa模型智能水印去除实战指南

AI图片修复革命:LaMa模型智能水印去除实战指南 【免费下载链接】IOPaint 项目地址: https://gitcode.com/GitHub_Trending/io/IOPaint 你是否曾经为了去除图片中的水印而烦恼?无论是商业照片上的版权标记,还是个人图片中的文字水印&a…

作者头像 李华
网站建设 2026/4/16 13:51:52

C语言编程学习指南:从零基础到实战应用

C语言编程学习指南:从零基础到实战应用 【免费下载链接】C语言程序设计电子书PDF版 这本C语言程序设计电子书是学习与提升C语言编程技能的绝佳资源,适合所有层次的读者。内容详实且深入浅出,不仅涵盖C语言的基本语法,还提供了丰富…

作者头像 李华
网站建设 2026/4/16 13:56:45

DeeplxFile跨平台文件翻译工具使用指南

DeeplxFile跨平台文件翻译工具使用指南 【免费下载链接】DeeplxFile 基于Deeplx和Playwright提供的简单易用,快速,免费,不限制文件大小,支持超长文本翻译,跨平台的文件翻译工具 / Easy-to-use, fast, free, unlimited …

作者头像 李华
网站建设 2026/4/16 13:56:33

ZeroTierOne游戏联机加速:如何解决NAT穿透实现低延迟P2P连接

【免费下载链接】ZeroTierOne A Smart Ethernet Switch for Earth 项目地址: https://gitcode.com/GitHub_Trending/ze/ZeroTierOne 作为一名技术顾问,我经常被问到:"为什么我和朋友联机游戏总是卡顿?" 🤔 今天&a…

作者头像 李华