fuzz总结

前言

找工作终于稳定了博客也该继续更起来了。后续可能主要都是更全平台开发相关的了。
在此之前,7-10月做了一段时间fuzz相关,毕设也要做fuzz。所以先总结下fuzz相关。

简单理解

fuzz :自动化执行大量随机测试用例
用途:测试、挖洞,漏洞与软件bug是相伴的。
高效的fuzz可以快速测试出软件的carsh漏洞。之后可以利用漏洞完成崩溃或者是攻击。由于软件逻辑扩大,仅靠人力不可能测出所有bug。

如何生成测试用例。
1)基于变异:根据已知数据样本通过变异的方法生成新的测试用例;
2)基于生成:根据已知的协议或接口规范进行建模,生成测试用例;

AFL、honggfuzz是典型的基于变异,大致流程:

  1. 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);这一步需要编译器的支持,可以基于函数、基本块、边界。clang 支持并提供了相应的回调函数。也可以进行二进制插桩
  2. 选择一些输入文件,作为初始测试集加入输入队列(queue);这个选择是有要求的,小而精…
  3. 将队列中的文件按一定的策略进行“突变”;
  4. 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
  5. 上述过程会一直循环进行,期间触发了crash的文件会被记录下来;会自动判断crash的触发条件是否重复,重复不记录

语料库:
好的种子可以加快fuzzing的速度,因此也不能是完全随机的。
有效的输入-无效输入产生bug与崩溃,但是有效输入更快的找到执行路径。
尽量小的体积-减少测试和处理的时间
开源语料库>测试用例>随意
语料库蒸馏-语料库是庞大的,寻找到的大量文件需要精简。尝试找到与语料库全集具有相同覆盖范围的最小子集。

fuzz的几种接口:不同的是在目标应用中插入的控制代码
Persistent fuzzing
由用户自己的程序连接fuzz模块,在一个进程内进行持久的fuzz
文件输入
由文件参数输入,使用forkserver运行目标

黑盒fuzz方式:
二进制插桩:使用Pin或DynamoRIO动态插桩监控代码覆盖率,比如winafl
虚拟化技术:使用Qemu或Boch等虚拟化技术实现应用层和内核层的代码覆盖率监控,比如afl、bochpwn
硬件级技术:使用Intel Processor Trace(PT)技术,比如honggfuzz

honggfuzz

代码的覆盖信息由编译器提供,LLVM的SanitizerCoverage
SanitizerCoverage简介:
随着-fsanitize-coverage=trace-pc编译器将插入 __sanitizer_cov_trace_pc()每个边缘。每次间接调用都会插入一个附加…=trace-pc,indirect-calls标志 。这些回调未在Sanitizer运行时实现,应由用户定义。
edge (默认):检测边缘
bb:基本块被检测。
func:只会检测每个函数的输入块。

AddressSanitizer是google的,llvm对其做了集成,可以使用-fsanitize=address开启。
-fno-omit-frame-pointer禁止把ebp当做通用寄存器使用,因为优化后可能将栈帧优化掉,其不利于对栈进行分析遍历调用
-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div
coverage为代码覆盖相关选项,trace-pc-guard跟踪调用块
trace-cmp跟踪比较检测,trace-gep捕获数组索引,trace-div检测整数除法指令
indirect-calls

honggfuzz 参数:
-P : 用于开启 persistent 模式
-f : 初始文件文集
-W : 工作目录

honggfuzz注册了llvm trace_pc的各种回调,用来记录代码覆盖率
ASAN-style的Persistent fuzzing中

1
2
3
4
5
6
7
#include <inttypes.h>
#include <testlib.h>
extern int LLVMFuzzerTestOneInput(uint8\_t **buf, size\_t *len);
int LLVMFuzzerTestOneInput(uint8\_t *buf, size\_t len) {
\_FuncFromFuzzedLib\_(buf, len);// 目标函数
return 0;
}

ASAN-style是由honggfuzz驱动,使用HF_ITER,和自己写HF_ITER style没区别。

因此输入形式有:
Persistent、直接向标准输入输入(in)、直接传入命令行(___FILE___)

java fuzz

classloader彻底理解
java里面 命名空间就是 类明+加载他的类加载器,所以开叉会加载俩遍,不同类加载器加载同一个类也处于不同的命名空间中,在内存中被视为不同的类。
判断自己是都加载过->委托向上
所以结果从下到上看是否加载过,然后再从上到下加载
contextClassLoader:
父Classloader可以使用当前线程Thread.currentthread().getContextLoader()中指定的classloader中加载的类。颠覆了父ClassLoader不能使用子Classloader或者是其它没有直接父子关系的Classloader中加载的类这种情况。这个就是Context Class Loader的意义。
Thread默认继承Parent Thread的 Context ClassLoader。如果你整个应用中都没有对此作任何处理,那么所有的Thread都会以System ClassLoader作为Context ClassLoader
获得全部线程:
ThreadGroup的enumerate可以设置第二个参数获得全部子线程
activeCount 返回此线程组及其子组中活动线程数的估计值。递归地迭代此线程组中的所有子组。

java实用工具:
jmap -dump:live,format=b,file=heap.bin pid(进程号)
jhat -J-mx512m heap.bin 分析,结果以网页展示http://ip:7000/ 含详尽的类实例、引用、类加载器信息。

jps:java 进程信息
jstack:某个进程的线程堆栈信息
jmap: 堆内存使用状况
jstat:jvm状态检测
hprof:java -agentlib:hprof[=options] ToBeProfiledClass hprof能够展现CPU使用率,统计堆内存使用情况。

java fuzz的目的:

  1. 辅助测试,帮助进行java接口部分的持久测试
  2. 寻找 low-severity或非安全问题,如RuntimeExceptions,抛出一个未声明要抛出异常的代码。包括超时、jvm堆栈溢出、空指针、资源利用等。
  3. java代码已知安全问题-反序列化、SSRF、XXE
  4. java-jni-native中的问题。这样可以直接在java角度测试整个模块而不用拆分出jni接口实现fuzz

实现参考jqf。agent fuzz模式作为毕设项目一部分

libfuzz 源码简记

使用:
实现接口、连接到libfuzzer库即可。

驱动在Fuzzer::Loop()

核心在 Fuzzer::MutateAndTestOne()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
void Fuzzer::MutateAndTestOne() {
MD.StartMutationSequence();
//取一个语料----emmm我看的就是随机的取语料。。。。
auto &II = Corpus.ChooseUnitToMutate(MD.GetRand());
const auto &U = II.U;
memcpy(BaseSha1, II.Sha1, sizeof(BaseSha1));
assert(CurrentUnitData);
size\_t Size = U.size();
assert(Size <= MaxInputLen && "Oversized Unit");
//拷贝样本数据到CurrentUnitData
memcpy(CurrentUnitData, U.data(), Size);
assert(MaxMutationLen > 0);
size\_t CurrentMaxMutationLen =
Options.ExperimentalLenControl
? ComputeMutationLen(Corpus.MaxInputSize(), MaxMutationLen,
MD.GetRand())
: MaxMutationLen;
for (int i = 0; i < Options.MutateDepth; i++) {
if (TotalNumberOfRuns >= Options.MaxNumberOfRuns)
break;
size\_t NewSize = 0;
//执行变异
NewSize = MD.Mutate(CurrentUnitData, Size, CurrentMaxMutationLen);
assert(NewSize > 0 && "Mutator returned empty unit");
assert(NewSize <= CurrentMaxMutationLen && "Mutator return overisized unit");
Size = NewSize;
if (i == 0)
StartTraceRecording();
II.NumExecutedMutations++;
//这里执行一次用例
if (size\_t NumFeatures = RunOne(CurrentUnitData, Size)) {
Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size}, NumFeatures,
/*MayDeleteFile=*/true);
ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size});
CheckExitOnSrcPosOrItem();
}
StopTraceRecording();
TryDetectingAMemoryLeak(CurrentUnitData, Size,
/*DuringInitialCorpusExecution*/ false);
}
}
变异在:
// Mutates Data in place, returns new size.
size\_t MutationDispatcher::MutateImpl(uint8\_t *Data, size\_t Size,
size\_t MaxSize,
const std::vector<Mutator> &Mutators) {
assert(MaxSize > 0);
// Some mutations may fail (e.g. can't insert more bytes if Size == MaxSize),
// in which case they will return 0.
// Try several times before returning un-mutated data.
for (int Iter = 0; Iter < 100; Iter++) {
//随机100次变异,有一次变异成功就返回
auto M = Mutators[Rand(Mutators.size())];
size\_t NewSize = (this->*(M.Fn))(Data, Size, MaxSize);
if (NewSize && NewSize <= MaxSize) {
if (Options.OnlyASCII)
ToASCII(Data, NewSize);
//记录当前的变异序列
CurrentMutatorSequence.push\_back(M);
return NewSize;
}
}
*Data = ' ';
return 1; // Fallback, should not happen frequently.
}

Mutators中的变异类型有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DefaultMutators.insert(
DefaultMutators.begin(),
{
{&MutationDispatcher::Mutate\_EraseBytes, "EraseBytes"},
{&MutationDispatcher::Mutate\_InsertByte, "InsertByte"},
{&MutationDispatcher::Mutate\_InsertRepeatedBytes,
"InsertRepeatedBytes"},
{&MutationDispatcher::Mutate\_ChangeByte, "ChangeByte"},
{&MutationDispatcher::Mutate\_ChangeBit, "ChangeBit"},
{&MutationDispatcher::Mutate\_ShuffleBytes, "ShuffleBytes"},
{&MutationDispatcher::Mutate\_ChangeASCIIInteger, "ChangeASCIIInt"},
{&MutationDispatcher::Mutate\_ChangeBinaryInteger, "ChangeBinInt"},
{&MutationDispatcher::Mutate\_CopyPart, "CopyPart"},
{&MutationDispatcher::Mutate\_CrossOver, "CrossOver"},
{&MutationDispatcher::Mutate\_AddWordFromManualDictionary,
"ManualDict"},
{&MutationDispatcher::Mutate\_AddWordFromTemporaryAutoDictionary,
"TempAutoDict"},
{&MutationDispatcher::Mutate\_AddWordFromPersistentAutoDictionary,
"PersAutoDict"},
});

EraseBytes:删除随机位置的随机个字节
InsertByte:随机位置加一个字节
InsertRepeatedBytes:随机位置加多个字节
ChangeByte:随机位置改一个字节
ChangeBit:随机改一位