热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用AFL对Linux内核Fuzzing的总结

模糊测试是现在最常用的漏洞挖掘技术,Fuzzer将半随机输入喂到到测试程序,目的是找到触发错误的输入。模糊测试在查找C或C程序中的内存破坏漏洞时特别有用

模糊测试是现在最常用的漏洞挖掘技术,Fuzzer将半随机输入喂到到测试程序,目的是找到触发错误的输入。模糊测试在查找C或C ++程序中的内存破坏漏洞时特别有用。

通常情况下,建议选择一个众所周知但很少探索的库,这个库在解析时很重要。历史上,像libjpeg,libpng和libyaml这样的东西都是完美的目标。如今找到一个好目标更难 – 一切似乎都已经被模糊化了。这是好事!我猜软件越来越好了!我没有选择用户空间目标,而是选择了Linux内核netlink机器。

Netlink是一个Linux内核工具,它用于配置网络接口,IP地址,路由表等。这是一个很好的fuzzing 目标:它是内核的一个小模块,并且生成畸形有效消息相对比较容易。最重要的是,我们可以在此过程中学到很多关于Linux内核的知识。

在这篇文章中,我将使用AFL模糊器,将netlink shim程序与自定义Linux内核相对应,所有这些都在KVM虚拟机中运行。

历史上的内核Fuzzing技术

我们将要使用的技术被称为“覆盖引导模糊测试”。有很多以前的文献:

· Dan Guido 的智能模糊革命,以及LWN关于它的文章

· Mateusz“j00ru”Jurczyk的有效文件格式模糊测试

· honggfuzz是一个现代化的,功能丰富的覆盖面引导的fuzzer

· ClusterFuzz

· Fuzzer测试套件

很多人过去都在Fuzzing Linux内核:

· 由Dmitry Vyukov 创建的syzkaller(又名syzbot)是一个非常强大的CI风格的持续运行的内核模糊器,它已经发现了数百个漏洞。

· 三位一体的模糊器

我们将使用AFL,可能是大家最喜欢的模糊器。AFL由MichałZalewski撰写。它以其易用性,速度和非常好的变异逻辑而闻名,这是开始模糊测试之旅的完美选择!奇热

如果您想了解有关AFL的更多信息,请参阅几个文件:

· 历史笔记

· 技术白皮书

· 自述

覆盖引导的模糊测试

覆盖引导的模糊测试基于反馈回路的原理:

· 模糊测试选择最有希望的测试用例

· 模糊测试将测试变为大量新的测试用例

· 目标代码运行变异的测试用例,并报告代码覆盖率

· 模糊器根据报告的覆盖范围计算得分,并使用它来确定有效的变异测试的优先级并删除冗余的测试

例如,假设输入测试是“hello”。Fuzzer可能会将其变为多种测试,例如:“hEllo”(位翻转),“hXello”(字节插入),“hllo”(字节删除)。如果这些测试中的任何一个将产生有趣的代码覆盖,那么它将被优先化并用作下一次测试的基础。

有关如何完成突变以及如何有效地比较数千个程序运行的代码覆盖率报告的细节是模糊测试的秘诀,阅读AFL的技术白皮书,可以了解更多细节。

通常,在使用AFL时,我们需要检测目标代码,以便以AFL兼容的方式报告覆盖范围。但我们想要Fuzzing 内核!我们不能只用“afl-gcc”重新编译它!。我们将准备一个二进制文件,让AFL认为它是用它的工具编译的。这个二进制文件将报告从内核中提取的代码覆盖率。

内核代码覆盖率

内核至少有两个内置的覆盖机制–GCOV和KCOV:

· 将gcov与Linux内核一起使用

· KCOV:模糊测试的代码覆盖率

KCOV的设计考虑了模糊测试,因此我们将使用它。

使用KCOV非常简单。我们必须使用正确的设置编译Linux内核。首先,启用KCOV内核配置选项:

 cd linux./scripts/config \-e KCOV \-d KCOV_INSTRUMENT_ALL

KCOV能够记录整个内核的代码覆盖率。可以使用KCOV_INSTRUMENT_ALL选项进行设置。有个缺点是,它会减慢我们不想分析的内核部分,并且会在Fuzzing 中引入噪声(降低“稳定性”)。对于初学者,让我们禁用KCOV_INSTRUMENT_ALL并有选择地在实际想要分析的代码上启用KCOV。

我们专注于Fuzzing netlink,所以在整个“net”目录树上启用KCOV:

 find net -name Makefile | xargs -L1 -I {} bash -c 'echo "KCOV_INSTRUMENT := y" >> {}'

在一个理想环境中,我们只能为真正感兴趣的几个文件启用KCOV。重庆但是netlink处理遍及整个网络堆栈代码,现在没有时间进行微调。

有了KCOV,将增加报告内存损坏错误的可能性。最重要的是KASAN,使用该集合,可以编译我们的KCOV和KASAN启用的内核。

我们将以kvm运行内核,所以需要切换一下:

 ./scripts/config \-e VIRTIO -e VIRTIO_PCI -e NET_9P -e NET_9P_VIRTIO -e 9P_FS \-e VIRTIO_NET -e VIRTIO_CONSOLE  -e DEVTMPFS ...

如何使用KCOV

KCOV非常容易上手。代码覆盖率会记录在每个进程的数据结构中,就是说必须在用户空间进程中启用和禁用KCOV,并且无法记录非任务事项的覆盖范围,比如最常见的中断处理。

KCOV将数据发送到缓冲区,然后就可以使用一个简单的ioctl启用&禁用它:

 ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC);/* profiled code */ioctl(kcov_fd, KCOV_DISABLE, 0);

缓冲区会包含启用KCOV内核代码的所有基本块的%rip值列表。

要读取缓冲区,运行如下代码:

 n &#61; __atomic_load_n(&kcov_ring[0], __ATOMIC_RELAXED);for (i &#61; 0; i < n; i&#43;&#43;) {printf("0x%lx\n", kcov_ring[i &#43; 1]);}

使用addr2line工具可以将&#xff05;rip解析为特定的代码行。

将KCOV喂到AFL中

AFL需要一个特制的可执行文件&#xff0c;但我们想要知道内核代码覆盖率。我们先了解一下AFL的工作原理。

AFL设置一个64K 8位数字的数组。该存储器区域称为“shared_mem”或“trace_bits”&#xff0c;并与trace的程序共享这块存储区域。数组中的每个字节都可以被认为是检测代码中特定对&#xff08;branch_src&#xff0c;branch_dst&#xff09;的命中计数器。

AFL更更多的是使用随机分支&#xff0c;而不是重用&#xff05;rip值来识别基本块&#xff0c;主要是为了增加熵&#xff0c;我们希望数组中的命中计数器均匀分布。

AFL使用的算法如下&#xff1a;

 cur_location &#61; ;shared_mem[cur_location ^ prev_location]&#43;&#43;; prev_location &#61; cur_location >> 1;

在使用KCOV的情况下&#xff0c;没有每个分支的编译时随机值。但是&#xff0c;我们可以使用哈希函数从KCOV记录的&#xff05;rip生成统一的16位数。

下面代码显示了如何将KCOV报告提供给AFL“shared_mem”数组&#xff1a;

 n &#61; __atomic_load_n(&kcov_ring[0], __ATOMIC_RELAXED);uint16_t prev_location &#61; 0;for (i &#61; 0; i < n; i&#43;&#43;) {uint16_t cur_location &#61; hash_function(kcov_ring[i &#43; 1]);shared_mem[cur_location ^ prev_location]&#43;&#43;;prev_location &#61; cur_location >> 1;}

从AFL读取测试数据

现在需要实际编写核心netlink接口的测试代码&#xff01;首先&#xff0c;我们需要从AFL读取输入数据。默认情况下&#xff0c;AFL将测试用例发送到stdin&#xff1a;

 /* read AFL test data */char buf[512*1024];int buf_len &#61; read(0, buf, sizeof(buf));

Fuzzing netlink

然后我们需要将此缓冲区发送到netlink套接字&#xff0c;使用前5个字节的输入作为netlink协议和组ID字段。这将允许AFL找出并猜测这些字段的正确值。

netlink测试代码&#xff08;简化&#xff09;&#xff1a;

netlink_fd &#61; socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, buf[0]); struct sockaddr_nl sa &#61; {         .nl_family &#61; AF_NETLINK,         .nl_groups &#61; (buf[1] <<24) | (buf[2]<<16) | (buf[3]<<8) | buf[4], }; bind(netlink_fd, (struct sockaddr *) &sa, sizeof(sa)); struct iovec iov &#61; { &buf[5], buf_len - 5 }; struct sockaddr_nl sax &#61; {       .nl_family &#61; AF_NETLINK, }; struct msghdr msg &#61; { &sax, sizeof(sax), &iov, 1, NULL, 0, 0 }; r &#61; sendmsg(netlink_fd, &msg, 0); if (r !&#61; -1) {       /* sendmsg succeeded! great I guess... */ }

为了提升Fuzzing速度&#xff0c;我们将它包装在一个模仿AFL“fork服务器”逻辑的短循环中。

AFL-to-KCOV的结果代码如下所示&#xff1a;

forksrv_welcome(); while(1) {     forksrv_cycle();     test_data &#61; afl_read_input();     kcov_enable();     /* netlink magic */     kcov_disable();     /* fill in shared_map with tuples recorded by kcov */     if (new_crash_in_dmesg) {          forksrv_status(1);     } else {          forksrv_status(0);     } }

查看完整的源代码

运行自定义内核

如何实际运行构建的自定义内核。有三种选择&#xff1a;

“native”&#xff1a;您以在服务器上完全启动构建的内核并在本机Fuzzing它。这种方法速度很快&#xff0c;但也很有问题。如果Fuzzing成功找到了crash&#xff0c;电脑可能会蓝屏崩溃&#xff0c;可能会丢失测试数据。

“uml”&#xff1a;可以将内核配置为以用户模式运行。运行UML内核不需要任何权限&#xff0c;内核只运行用户空间进程。UML有一个问题是&#xff0c;它不支持KASAN&#xff0c;因此对于内存破坏漏洞的挖掘就那么有用了。

“kvm”&#xff1a;可以使用kvm在虚拟机环境中运行自定义内核&#xff0c;这就是我们要做的。

在KVM环境中运行自定义内核的最简单方法之一是使用“virtme”脚本。有了它们&#xff0c;我们可以避免创建专用的磁盘映像或分区&#xff0c;只需共享主机文件系统。

这就是我们运行代码的方式&#xff1a;

virtme-run \     --kimg bzImage \     --rw --pwd --memory 512M \     --script-sh ""

构建输入语料库

每个Fuzzer都需要精心设计的测试用例作为输入&#xff0c;以引导使程序产生突变输出。测试用例应该简短&#xff0c;并尽可能覆盖大部分代码。

这是我们的输入语料库&#xff1a;

mkdir inp echo "hello world" > inp/01.txt

如何编译和运行的说明都在我们的github上的README.md中。

virtme-run \     --kimg bzImage \     --rw --pwd --memory 512M \     --script-sh "./afl-fuzz -i inp -o out -- fuzznetlink"

运行后&#xff0c;AFL就开始Fuzzing了&#xff1a;

 

总结

在这篇文章中我们没有提到&#xff1a;

· AFL shared_memory设置的详细信息

· 运行AFL持久模式

· 关于如何读取dmesg&#xff08;/ dev / kmsg&#xff09;以查找内核crash的技巧

· 在KVM之外运行AFL&#xff0c;以获得速度和稳定性

但是实现了我们的目标&#xff0c;我们针对内核建立了一个基本但仍然有用的Fuzzer。最重要的是&#xff1a;可以重复使用相同的机制来Fuzzing Linux子系统的其他部分&#xff0c;比如从文件系统到bpf验证程序。

我有一些感悟&#xff1a;正确的模糊测试绝对不是启动Fuzzer后无所事事地等待崩溃。总有一些东西需要改进&#xff0c;调整和重新实现。Mateusz Jurczyk在之前演讲开头的一句话引起了我的共鸣&#xff1a;“Fuzzing很容易学&#xff0c;但很难掌握。”


推荐阅读
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 假设我有两个数组A和B,其中A和B都是mxn.我现在的目标是,对于A和B的每一行,找到我应该在B的相应行中插入A的第i行元素的位置.也就是说,我希望将np.digitize或np. ... [详细]
  • 1、题目给出两个序列pushed和poped两个序列,其取值从1到n(n\le100000)n(n≤100000)。已知入栈序列是pushed,如果出 ... [详细]
author-avatar
rgx-秀_550
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有