看雪-系统0day安全-二进制漏洞攻防
菜鸡啥都学点,啥都不精!!!
模糊测试
简单介绍一下:
通过自动化生成
并执行大量的随机测试
用例来发现产品或协议的未知漏洞
简单写一下
这个分为手动和自动两种情况
主要区别就在于
data
的生成是自动的还是手动的有限变动就在于传参方式吧
这里还讲了基于
覆盖制导fuzz
,它这里存在一些变动,对感知进程进行了一些反馈,并且基于反馈样本进行变异。
工具的使用
这里详细学习AFI工具
一、AFL原理
AFL框架
AFL是一个基于覆盖制导的模糊工具测试工具。解释一下模糊测试,首先有一个进程,进程接收外界的输入然后给外界一个反馈,同时在基于覆盖制导的模糊测试运行的同时向外发送反馈,之后有相对于的输出。
这里我们就需要一个数据生成器,然后这里数据输入进去之后会触发进程中的哪些路径,之后对进程进行特殊处理,这里的专业术语叫插桩。
然后提出疑问,如何让这个程序告诉你发触发了哪些路径?这里就需要用到插桩也就是插入一段代码,这里插入的代码我们称之为AFL_maybe_log。这里我们的进程就会传递路径信息,并且进行统计规整,之后把这个传给数据生成器,进行评估(反馈)。
如下图:
插桩
如何插桩
两种方法:汇编层(ELF)和LLVM pass(官方api)
函数执行什么功能
覆盖信息反馈
主要讲的就是我们在插桩形成AFL_maybe_log时是有一段进程的,通过共享内存(shared_memory)进行访问、alloc、get和修改。还要注意的是AFL_maybe_log路径的生成的时候其实是有参数生成的(参数是随机生成的),路径的开始和结尾的路径唯一,根据这两个参数生成哈希 ,如下图
而这个index代表条唯一路径,之后就会在共享内存的索引四的地方加一
表示index=4的路径被触发了一次
如此我们的进程就修改了我们的共享内存,同样AFL-fuzz也可以去访问这块内存。这样我们进程内的路径信息就可以被AFL感知到了。
简单看下已经被插桩过的二进制文件
首先获得共享内存,从进程环境变量里获得ID去定位共享内存
生成哈希, index= afl_pre loc(这里是A) ^ random_num(这里是B)
但是如果我们从假设A-A的路径的话,我们会发现index会为0了,所以这里简单的右移1了,来避免这种情况的发生
解释CFADD
预防整数溢出的
forkserver
- 当我们在进行模糊测试运行目标程序时,每一次目标的输入都对应着一次进程的创建,然后运行目标程序。但是这个的弊端在于时间的消耗成本太大,效率低下。
- 对此我们使用forkserver,首先我们的afl-fuzz创建forkserver,然后我们的forkserver再去创建我们的进程。
作用
首先forkserver有两个通信管道, 如上图。
上面的通信管道是命令信息,告诉fork server我要创建新进程了,然后根据这个去fork一个新进程出来。
下面的通信管道是子进程信息,简单来说就是把子进程的信息进行反馈
!!!!同时也会返回进程的pid
目的
找到一个漏洞,也就是程序发生了崩溃,并且把这个崩溃的状态反馈给我们的AFL-fuzz,让我们知道我们的输入样本导致了我们的程序发生崩溃
- 当第一次运行时,他首先会进入初始化进程的逻辑里面
- 然后他会走一个while循环,通过管道会与afl-fuzz进行交互,直到触发异常,结束进程
- 除此之外其余都会执行上面的逻辑,也就是覆盖信息反馈(在插桩那块)。
完整AFL框架
路径反馈信息处理
数据规整
注意假设我们a-b的路径触发了4-7次都会规整到8次,以此类推
这样的目的时让样本一和样本二差异性不是特别大
样本变异
- bitflip,按位翻转(确定变异)
简单来说就是0变1,1变0
- arithmetic,整数加/减算术运算(确定变异)
- interest,特殊数值替换(确定变异)
- dictionary,把自动生成或用户提供的token替换/插入到原文件(确定变异)
- havoc,随机变异(不确定变异)
- splice,文件拼接(不确定变异)
AFL的覆盖制导问题
AFL自身的缺陷
针对大型项目:项目简单、映射数组长度有限、路径过多
AFL对抗
- 进行检测afl-fuzz特征,如上图的环境变量,如此就可以进行afl-fuzz检测
- 检测198、199的管道是否存在,进行推测是否存在afl-fuzz
- 清空共享内存
二、模糊测试实验
环境搭建:
1 | sudo docker aflplusolus/aflplusplus |
这里笔者在拉dockers环境的时候,会遇到连接超时的问题
这里建议直接进行代理
链接:
Docker 设置代理的三种方法(2025年02月08日亲测可用)_docker 代理-CSDN博客
—2025年3.19可以使用
基本的使用:
xpdf-3.02
1 | # 对 xpdf-3.02 进行编译 |
1 | # input |
1 | afl-fuzz -i input/ -o output/ /root/work/xpdf-3.02/install/bin/pdftotext @@ demo_test |
看到这个基本就是起起来了,按理说我没等一会,看看他有没有发生崩溃,查看一下漏洞
一般这个fuzz是不会停止的,我们手动断下,查看一下
cmdline
:记录AFL++启动时的完整命令行参数
crashes
:存放AFL++发现的导致xpdf-3.02
崩溃的样本
fastresume.bin
:AFL++用于恢复fuzzing进度的二进制文件。
fuzz_bitmap
:储存AFL++运行时的覆盖率信息,用于跟踪哪些代码路径已经被访问过,帮助AFL++生成AFL++生成新的变异输入
fuzzer_setup
:记录AFL++运行时的配置信息
fuzzer_stats
:当前AFL++运行的统计信息
我们看下程序运行的统计信息
1 | [AFL++ 6d9aaf4da41f] /AFLplusplus/xpdf-3.02/output/default # cat fuzzer_stats |
总结一下:发现了 2 个崩溃样本,发现了 5 个卡死样本,测试了 35.9 万个样本,覆盖率 12.94%,但仍有提升空间,执行速度 169 execs/sec。
libxml2-2.9.4
安装
1 | wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz |
1 | -x xml.dict |
三、ASAN原理
四、模糊测试挖掘命令注入
逻辑块漏洞和内存块漏洞
命令注入漏洞
前提
- 程序存在system函数,或者类似的函数
- system函数可以被我们命令执行触发(可达)
这里可以通过fuzz来证明函数是否可达
- 对system函数的参数可控(可控)
暂时无法解决
证明可达
简单来说就是,当一个样本能流经system函数时让函数崩溃
这里就要介绍怎么样定制化执行system函数让函数崩溃,也就是我们要自定义一个函数,只要这个函数被调用,就证明函数执行了system函数,这里就用到了hook
弊端:
当程序运行到system(id)时函数崩溃,但是后面的函数不会执行,无法判断后面函数是否存在内存块漏洞,保证不了afl-fuzz的完整性。
改善:
- 准备两个样本,当样本一执行到自定义函数时崩溃,但是当样本二执行是则继续向下进行。
但是我们如果情况是system(id)函数执行时,下面如果存在system(命令注入)则无法crash
改善:
当某地址函数自定义函数第一次被调用时crash(也就是多了一个地址)
这里讲一下afl-fuzz进行forkserver时,不仅反馈子进程信息还反馈子进程的pid。
现在就是要把命令注入的crash和内存块的crash分开,这里也就引用到sytem hook,只要执行则增加标记 single user 1。
然后就forkserver就把这个子进程信息返还给afl-fuzz,证明可达
代码实现
这里是使用hook,关于hook我们使用LD_PRELOAD
(它是linuxld.so
动态连接器提供的一个环境变量,它的作用是在加载程序时强制优先加载指定的共享库)。
libc.so时标准动态连接库
紫色为我们正常调用system函数,红色则是我们使用hook,当我们使用LD_PRELOAD去指定动态库,比如时hook.so ,这样我们的hook.so就会先于libc.so加载,如果我们的hook.so里面实现了一个system函数的实现,那么下一次调用system函数,则不会执行libc.so的system函数,而是执行我们的hook.so。这样以来我们就实现了劫持system函数。
代码:
1 |
|
五、实现模糊测试工具
附件这里就不贴了
NetFuzz-2.0
这里跟前面的分析的二进制文件对应
这里是创建forksever的类,跟进看一下初始化
之后就是创建两个管道一个,就是命令管道另一个就是接收子进程的pid和反馈信息的
1 | import os |
后面的 self.status = class_status()
就显示进程的状态的,也是有统计
1 | class class_status(): |
这里主要讲一下fuzz_one
这个函数
1 | def fuzz_one(forksrv, queue_cur): |
如果是确定性变异,则继续看一下他是怎么变异的
下面的还有很多种情况,也就是按bit进行反转
1 | stage_max = outbuf_len << 3 |
这里注意数据规整
一般超过500的,大部分都是web服务异常,也就发生了crash,并且保存样本。
WEBserver
这里以nginx
为例
特征:
- 多进程(可自定义变成单进程)
- socket(支持标准的输入,标准文件输入)
六、网络协议漏洞挖掘实战
webserver的常见架构。
程序运行的是开始就是首先会监听80端口,然后在做一个sp协议,进行端口解析,根据协议要求在去做一些要求,当webserver遭受crash之后,这种架构会很快让它恢复,让webserver对外界的请求达到一种不间断的情况。
看图片,首先监听80端口,然后创建三个子进程,现在的问题是谁来处理80端口的数据呢?这就要提到一个惊群现象。
例子:如果有一个数据包发过来,然后我们的三个子进程都会被触发去处理,这就叫做惊群现象。以nginx
为例,它使用加锁,当数据包发过来时,他们三个会去抢占这个资源锁,谁先抢到谁先对外界进行服务。以lighttpd
为例,它的产生是一个比较轻量的环境,比如说在路由器上,它比较消耗资源,所以它的处理方式是不处理,谁抢的谁来。
其实它还有一个守护进程,然后前面有一个初始状态,然后创建守护进程,这就是完整的架构。
解释一下保护进程
简单来说就是三个进程如果有进程崩溃后,守护进程会重新创建一个新进程,来维护着三个进程,以用来维持对外界的服务。
ligttpd-基础fuzz
主要用于嵌入式设备。
拿到项目之后直接vscode打开,然后正常编译./autogen.sh
然后运行./configure
。(报错拷打GPT)
然后运行make -j8
(默认路径是下在/usr/sbin/lighttpd
目录下了)
1 | afl-fuzz -i input/ -o output/ -- ./src/lighttpd -D -f lighttpd.conf |