libFuzzer
⽬录
1.概述
LibFuzzer是单进程的,覆盖引导的,进化的模糊引擎。
LibFuzzer与被测试的库链接,并通过特定的模糊⼊⼝点(也称为“⽬标函数”)将模糊输⼊提供给库;然后,模糊器跟踪到达代码的哪些
区域,并在输⼊数据的语料库中⽣成突变,以便最⼤化代码覆盖。libFuzzer的代码覆盖率信息由LLVM的SanitizerCoverage检测提供
2.版本
LibFuzzer正在积极开发中,因此您将需要Clang编译器的当前(或⾄少是最新版本)(请参阅从主⼲构建Clang)
rget
在库上使⽤libFuzzer的第⼀步是实现⼀个模糊⽬标-⼀个接受字节数组的函数,并使⽤被测API对这些字节做⼀些有趣的事情。像这样:
//fuzz_
extern"C"intLLVMFuzzerTestOneInput(constuint8_t*Data,size_tSize){
DoSomethingInterestingWithMyAPI(Data,Size);
return0;//Non-zeroreturnvaluesarerervedforfutureu.
}
请注意,这种fuzz⽬标不以任何⽅式依赖于libfuzzer,因此可能甚⾄需要将其与其他fuzz引擎(如AFL和/或RADAMSA)⼀起使⽤。
关于fuzz⽬标,需要记住的⼀些重要事项:
在同⼀过程中,fuzz引擎会多次执⾏不同输⼊的fuzz⽬标。
它必须允许任何类型的输⼊(空、⼤、畸形等)。
它不能在任何输⼊上exit()。
它可以使⽤线程,但理想情况下,所有线程都应该在函数的末尾连接。
它必须尽可能具有确定性。不确定性(例如不基于输⼊字节的随机决策)会使模糊效率低下。
⼀定很快。尝试避免⽴⽅或更⼤的复杂性、⽇志记录或过多的内存消耗。
理想情况下,它不应该修改任何全局状态(尽管这并不严格)。
通常,⽬标越窄越好。例如。如果您的⽬标可以解析多种数据格式,请将其拆分为多个⽬标,每种格式⼀个。
Usage
最新版本的Clang(从6.0开始)包括libFuzzer,⽆需额外安装。
要构建模糊化⼆进制⽂件,请在编译和链接期间使⽤-fsanitize=fuzzer标志。在⼤多数情况下,您可能希望将libFuzzer与
AddressSanitizer(ASAN),UndefinedBehaviorSanitizer(UBSAN)或两者结合使⽤。您也可以使⽤
MemorySanitizer(MSAN)构建,但⽀持是实验性的:
clang-g-O1-fsanitize=fuzzermytarget.c#Buildsthefuzztargetw/osanitizers
clang-g-O1-fsanitize=fuzzer,addressmytarget.c#BuildsthefuzztargetwithASAN
clang-g-O1-fsanitize=fuzzer,signed-integer-overflowmytarget.c#BuildsthefuzztargetwithapartofUBSAN
clang-g-O1-fsanitize=fuzzer,memorymytarget.c#BuildsthefuzztargetwithMSAN
这将执⾏必要的检测,以及与libFuzzer库的链接。请注意,libFuzzer的main()符号中的-fsanitize=fuzzer链接。
如果修改⼤型项⽬的CFLAGS,它也编译需要⾃⼰的主符号的可执⾏⽂件,则可能需要在不链接的情况下仅请求检测:
clang-fsanitize=fuzzer-no-linkmytarget.c
然后通过在链接阶段传⼊-fsanitize=fuzzer,可以将libFuzzer链接到所需的驱动程序。
像libFuzzer这样的覆盖引导模糊器依赖于被测代码的样本输⼊语料库。理想情况下,该语料库应该为被测代码提供各种有效和⽆效的输⼊;
例如,对于图形库,初始语料库可能包含各种不同的⼩PNG/JPG/GIF⽂件。模糊器基于当前语料库中的样本输⼊⽣成随机突变。如果突
变触发了测试代码中先前未覆盖的路径的执⾏,则该突变将保存到语料库中以供将来变更。
LibFuzzer将在没有任何初始种⼦的情况下⼯作,但如果受测试的库接受复杂的结构化输⼊,则效率会降低。
语料库还可以充当sanity/regression检查,以确认模糊测试⼊⼝点仍然有效,并且所有样本输⼊都通过测试中的代码运⾏⽽没有问题。
如果您有⼤型语料库(通过模糊测试⽣成或通过其他⽅式获取),您可能希望在保留完整覆盖范围的同时将其最⼩化。⼀种⽅法是使⽤-
merge=1标志:
mkdirNEW_CORPUS_DIR#Storeminimizedcorpushere.
./my_fuzzer-merge=1NEW_CORPUS_DIRFULL_CORPUS_DIR
您可以使⽤相同的标志向现有语料库添加更多有趣的项⽬。只有触发新覆盖的输⼊才会添加到第⼀个语料库中。
./my_fuzzer-merge=1CURRENT_CORPUS_DIRNEW_POTENTIALLY_INTERESTING_INPUTS_DIR
g
Torunthefuzzer,firstcreateadirectorythatholdstheinitial“ed”sampleinputs:
mkdirCORPUS_DIR
cp/some/input/samples/*CORPUS_DIR
然后在语料库⽬录上运⾏模糊器:
./my_fuzzerCORPUS_DIR#-max_len=1000-jobs=20...
当模糊器发现新的有趣测试⽤例(即通过被测代码触发新路径覆盖的测试⽤例)时,这些测试⽤例将被添加到语料库⽬录中。
默认情况下,模糊测试过程将⽆限期地继续-⾄少在发现错误之前。任何crashes或sanitizer故障都将照常报告,停⽌模糊测试过程,触
发错误的特定输⼊将写⼊磁盘(通常为crash-
elFuzzing
每个libFuzzer进程都是单线程的,除⾮被测试的库启动⾃⼰的线程。但是,可以与共享语料库⽬录并⾏运⾏多个libFuzzer进程;这样做的
好处是,⼀个模糊进程找到的任何新输⼊都可⽤于其他模糊进程(除⾮使⽤-reload=0选项禁⽤此选项)。
这主要由-jobs=N选项控制,该选项指⽰N个模糊作业应该运⾏完成(即直到找到错误或达到时间/迭代限制)。这些作业将在⼀组⼯作进
程中运⾏,默认情况下使⽤⼀半的可⽤CPU核⼼;-workers=N选项可以覆盖⼯作进程的计数。例如,在12核计算机上使⽤-jobs=30运
⾏默认情况下将运⾏6个⼯作程序,每个⼯作程序在完成整个过程时平均会有5个错误。
de
实验模式-fork=N(其中N是并⾏作业的数量)使⽤单独的进程(使⽤fork-exec,⽽不仅仅是fork)启⽤oom-,timeout-和crash-
resistant-fuzzing。
顶级libFuzzer进程本⾝不会进⾏任何模糊测试,但会产⽣多达N个并发⼦进程,为它们提供语料库的⼩型随机⼦集。在孩⼦退出之后,顶
级过程将孩⼦⽣成的语料库合并回主语料库。
相关标志:
-ignore_ooms
默认为True。如果在其中⼀个⼦进程的模糊测试期间发⽣OOM,则复制器将保存在磁盘上,并继续进⾏模糊测试。
-ignore_timeouts
默认情况下为True,与-ignore_ooms相同,但是超时。
-ignore_crashes
默认情况下为Fal,与-ignore_ooms相同,但对于所有其他崩溃。
计划是最终⽤-fork=N替换-jobs=N和-workers=N.
ngmerge
合并⼤型语料库可能⾮常耗时,并且通常希望在可抢占的VM上执⾏此操作,其中该过程可能在任何时间被杀死。为了⽆缝地恢复合并,请
使⽤-merge_control_file标志并使⽤killall-SIGUSR1/path/to/fuzzer/binary来正常停⽌合并。例:
%rm-fSomeLocalPath
%./my_fuzzerCORPUS1CORPUS2-merge=1-merge_control_file=SomeLocalPath
...
MERGE-INNER:usingthecontrolfile'SomeLocalPath'
...
#Whilethisisrunning,do`killall-SIGUSR1my_fuzzer`inanotherconsole
==9015==INFO:libFuzzer:exitingasrequested
#ThiswillleavethefileSomeLocalPathwiththepartialstateofthemerge.
#Now,ge
#willcontinuefromwhereithasbeeninterrupted.
%./my_fuzzerCORPUS1CORPUS2-merge=1-merge_control_file=SomeLocalPath
...
MERGE-OUTER:non-emptycontrolfileprovided:'SomeLocalPath'
MERGE-OUTER:controlfileok,32filestotal,firstnotprocesdfile20
...
s
要运⾏模糊器,请将零个或多个语料库⽬录作为命令⾏参数传递。模糊器将读取每个语料库⽬录中的测试输⼊,并且⽣成的任何新测试输
⼊将被写回第⼀个语料库⽬录:
./fuzzer[-flag1=val1[-flag2=val2...]][dir1[dir2...]]
最重要的命令⾏选项是:
-help
打印帮助信息。
-ed
随机种⼦。如果为0(默认值),则⽣成种⼦。
-runs
单个测试运⾏的次数,-1(默认值)⽆限期运⾏。
-max_len
测试输⼊的最⼤长度。如果为0(默认值),则libFuzzer会尝试根据语料库猜测⼀个好的值(并报告它)。
len_control
⾸先尝试⽣成⼩输⼊,然后尝试更⼤的输⼊。指定长度限制增加的速率(更⼩==更快)。默认值为100.如果为0,则⽴即尝试输⼊⼤⼩为
max_len的输⼊。
-timeout
超时(以秒为单位),默认为1200.如果输⼊的时间超过此超时,则将该过程视为故障情况。
-rss_limit_mb
内存使⽤限制,单位为Mb,默认为2048.使⽤0禁⽤该限制。如果输⼊需要执⾏超过此数量的RSS内存,则该过程将被视为失败案例。每
秒在⼀个单独的线程中检查限制。如果运⾏没有ASAN/MSAN,您可以使⽤'ulimit-v'代替。
-malloc_limit_mb
如果⾮零,如果⽬标尝试使⽤⼀个malloc调⽤分配此数量的Mb,则模糊器将退出。如果应⽤零(默认)相同的限制,则应⽤
rss_limit_mb。
-timeout_exitcode
如果libFuzzer报告超时,则使⽤退出代码(默认为77)。
-error_exitcode
如果libFuzzer本⾝(不是清理程序)报告错误(泄漏,OOM等),则使⽤退出代码(默认为77)。
-max_total_time
如果为正,则表⽰运⾏模糊器的最长总时间(以秒为单位)。如果为0(默认值),则⽆限期运⾏。
-merge
如果设置为1,则触发新代码覆盖的第2,第3等语料库⽬录中的任何语料库输⼊将合并到第⼀个语料库⽬录中。默认为0.此标志可⽤于最⼩
化语料库。
-merge_control_file
指定⽤于合并进程的控制⽂件。如果合并进程被杀死,它会尝试将此⽂件保留在适合恢复合并的状态。默认情况下,将使⽤临时⽂件。
-minimize_crash
如果为1,则最⼩化提供的崩溃输⼊。与-runs=N或-max_total_time=N⼀起使⽤以限制尝试次数。
-reload
如果设置为1(默认值),则定期重新读取语料库⽬录以检查新输⼊;这允许检测由其他模糊测试过程发现的新输⼊。
-jobs
要运⾏完成的模糊测试作业的数量。默认值为0,运⾏单个模糊测试过程直到完成。如果值>=1,则在并⾏的单独⼯作进程的集合中运⾏执
⾏模糊测试的此数量的作业;每个这样的⼯作进程都将其stdout/stderr重定向到fuzz-
-workers
运⾏模糊测试作业的同时⼯作进程数。如果为0(默认值),则使⽤min(jobs,NumberOfCpuCores()/2)。
-dict
提供输⼊关键字的字典;看字典。
-u_counters
使⽤覆盖计数器⽣成代码块被击中频率的近似计数;默认为1。
-reduce_inputs
尽量减少输⼊的⼤⼩,同时保留其完整的功能集;默认为1。
-u_value_profile
使⽤价值观来指导语料库的扩展;默认为0。
-only_ascii
如果为1,则仅⽣成ASCII(isprint``+``isspace)输⼊。默认为0。
-artifact_prefix
提供在将fuzzing⼯件(崩溃,超时或慢速输⼊)保存为$(artifact_prefix)⽂件时使⽤的前缀。默认为空。
-exact_artifact_path
如果为空则忽略(默认值)。如果⾮空,则将失败时写⼊的单个⼯件(崩溃,超时)写为$(exact_artifact_path)。这会覆盖-
artifact_prefix,并且不会在⽂件名中使⽤校验和。不要对多个并⾏进程使⽤相同的路径。
-print_pcs
如果为1,则打印出新覆盖的PC。默认为0。
-print_final_stats
如果为1,则退出时打印统计信息。默认为0。
-detect_leaks
如果为1(默认值)且启⽤了LeakSanitizer,则尝试在模糊测试期间(即不仅在关闭时)检测内存泄漏。
-clo_fd_mask
指⽰在启动时关闭的输出流。请注意,这将从⽬标代码中删除诊断输出(例如断⾔失败时的消息)。
0(默认值):既不关闭stdout也不关闭stderr
1:关闭stdout
2:关闭stderr
3:关闭stdout和stderr。
对于完整的标志列表,使⽤-help=1运⾏fuzzer⼆进制⽂件。
在操作期间,模糊器将信息打印到stderr,例如:
INFO:Seed:1523017872
INFO:Loaded1modules(16guards):[0x744e60,0x744ea0),
INFO:-max_lenisnotprovided,using64
INFO:Acorpusisnotprovided,startingfromanemptycorpus
#0READunits:1
#1INITEDcov:3ft:2corp:1/1bexec/s:0rss:24Mb
#3811NEWcov:4ft:3corp:2/2bexec/s:0rss:25MbL:1MS:5ChangeBit-ChangeByte-ChangeBit-ShuffleBytes-ChangeByte-
#3827NEWcov:5ft:4corp:3/4bexec/s:0rss:25MbL:2MS:1CopyPart-
#3963NEWcov:6ft:5corp:4/6bexec/s:0rss:25MbL:2MS:2ShuffleBytes-ChangeBit-
#4167NEWcov:7ft:6corp:5/9bexec/s:0rss:25MbL:3MS:1InrtByte-
...
输出的早期部分包括有关fuzzer选项和配置的信息,包括当前随机种⼦(在ed:line中;这可以⽤-ed=n标志覆盖)。
其他输出⾏具有事件代码和统计信息的形式。可能的事件代码为:
READ
Fuzzer已经从语料库⽬录中读取了所有提供的输⼊样本。
INITED
fuzzer已经完成初始化,包括通过被测代码运⾏每个初始输⼊样本。
NEW
fuzzer已经创建了⼀个测试输⼊,它覆盖了被测代码的新领域。此输⼊将保存到主语料库⽬录。
REDUCE
fuzzer发现了⼀个更好(更⼩)的输⼊,可以触发先前发现的特征(设置-reduce_inputs=0以禁⽤)。
pul
fuzzer已产⽣2N输⼊(定期产⽣,以保证⽤户fuzzer仍在⼯作)。
DONE
fuzzer已完成操作,因为它已达到指定的迭代限制(-runs)或时间限制(-max_total_time)。
RELOAD
fuzzer正在定期从corpus⽬录重新加载输⼊;这允许它发现由其他fuzzer进程发现的任何输⼊(请参见并⾏模糊)。
每个输出⾏还报告以下统计信息(⾮零时):
COV:
执⾏当前语料库所涵盖的代码块或边的总数。
FT:
libFuzzer使⽤不同的信号来评估代码覆盖率:边缘覆盖,边缘计数器,值配置⽂件,间接调⽤者/被调⽤者对等。这些组合的信号称为特征
(ft:)。
CORP:
当前内存中测试语料库中的条⽬数及其⼤⼩(以字节为单位)。
LIM:
语料库中新条⽬长度的当前限制。随着时间的推移逐渐增加,直到达到最⼤长度(-max_len)。
EXEC/S:
每秒的模糊器迭代次数。
RSS:
当前的内存消耗。
对于NEW事件,输出⾏还包括有关⽣成新输⼊的变异操作的信息:
L:
新输⼊的⼤⼩(以字节为单位)。
MS:
计数和⽤于⽣成输⼊的变异操作列表。
mple
⼀个简单的函数,如果收到输⼊“HI!”,它会做⼀些有趣的事情:
cat<
#include
#include
extern"C"intLLVMFuzzerTestOneInput(constuint8_t*data,size_tsize){
if(size>0&&data[0]=='H')
if(size>1&&data[1]=='I')
if(size>2&&data[2]=='!')
__builtin_trap();
return0;
}
EOF
#Buildtest_asanandlinkagainstlibFuzzer.
clang++-fsanitize=address,fuzzertest_
#Runthefuzzerwithnocorpus.
./
你应该很快得到⼀个错误
INFO:Seed:1523017872
INFO:Loaded1modules(16guards):[0x744e60,0x744ea0),
INFO:-max_lenisnotprovided,using64
INFO:Acorpusisnotprovided,startingfromanemptycorpus
#0READunits:1
#1INITEDcov:3ft:2corp:1/1bexec/s:0rss:24Mb
#3811NEWcov:4ft:3corp:2/2bexec/s:0rss:25MbL:1MS:5ChangeBit-ChangeByte-ChangeBit-ShuffleBytes-ChangeByte-
#3827NEWcov:5ft:4corp:3/4bexec/s:0rss:25MbL:2MS:1CopyPart-
#3963NEWcov:6ft:5corp:4/6bexec/s:0rss:25MbL:2MS:2ShuffleBytes-ChangeBit-
#4167NEWcov:7ft:6corp:5/9bexec/s:0rss:25MbL:3MS:1InrtByte-
==31511==ERROR:libFuzzer:deadlysignal
...
artifact_prefix='./';Testunitwrittento./crash-b13e8756b13a00cf1fb4b91fefbed
amples
naries
libfuzzer⽀持⽤户提供的带有输⼊语⾔关键字或其他有趣的字节序列(例如多字节magic值)的字典。使⽤-dict=DICTIONARY_FILE。对
于某些输⼊语⾔,使⽤字典可能会显著提⾼搜索速度。字典语法类似于AFL在其-x选项中使⽤的语法:
#Linesstartingwith'#'andemptylinesareignored.
#Adds"blah"(w/oquotes)tothedictionary.
kw1="blah"
#Uforbackslashand"forquotes.
kw2=""acdc""
#UxABforhexvalues
kw3="xF7xF8"
#thenameofthekeywordfollowedby'='maybeomitted:
"foox0Abar"
gCMPinstructions
使⽤额外的编译器标志-fsanitize-coverage=trace-cmp(默认情况下,作为-fsanitize=fuzzer的⼀部分,请参阅
SanitizerCoverageTraceDataFlow),libFuzzer将拦截CMP指令并根据截获的CMP指令的参数指导突变。这可能会减慢模糊测试速
度,但很可能会改善结果。
rofile
使⽤-fsanitizecoverage=trace-cmp(默认为-fsanitize=fuzzer)和额外的运⾏时标志-u_value_profile=1,fuzzer将收集⽐较指令
参数的值配置⽂件,并将⼀些新值视为新的覆盖。
⽬前的⼯作⼤致如下:
编译器使⽤接收两个CMP参数的回调来检测所有CMP指令。
回调计算(caller_pc&4095)/(popcnt(arg1^arg2)<<12)并使⽤该值在位集中设置⼀个位。
位集中的每个新观测位都被视为新的覆盖范围。
这个特性有可能发现许多有趣的输⼊,但有两个缺点。⾸先,额外的⽅法可能会带来2倍的额外减速。第⼆,语料库可能会增长⼏倍。
-friendlybuildmode
有时,测试中的代码不是模糊测试的。例⼦:
⽬标代码使⽤PRNG种⼦,例如:通过系统时间,因此即使最终结果相同,两个随后的调⽤也可能潜在地执⾏不同的代码路径。这将导致模
糊器将两个相似的输⼊视为显着不同,并且它会炸毁测试语料库。例如。libxml在其哈希表中使⽤rand()。
⽬标代码使⽤校验和来防⽌⽆效输⼊。例如。png检查每个块的CRC。
在许多情况下,构建⼀个特殊的模糊友好构建是有意义的,其中某些模糊不友好的功能被禁⽤。我们建议为所有这些情况使⽤公共构建宏
以保持⼀致性:FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION。
voidMyInitPRNG(){
#ifdefFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
//Infuzzingmodethebehaviorofthecodeshouldbedeterministic.
srand(0);
#el
srand(time(0));
#endif
}
patibility
libfuzzer可以与afl⼀起在同⼀个测试语料库上使⽤。两个模糊器都期望测试语料库驻留在⼀个⽬录中,每个输⼊⼀个⽂件。你可以在同⼀
个语料库上运⾏两个fuzzer,⼀个接⼀个:
./afl-fuzz-itestca_dir-ofindings_dir/path/to/program@@
./llvm-fuzztestca_dirfindings_dir#Willwritenewteststotestca_dir
定期重新启动两个fuzzer,以便他们可以使⽤彼此的发现。⽬前,在共享同⼀个corpusdir时,没有简单的⽅法并⾏运⾏两个模糊引擎。
您也可以在⽬标函数llvmFuzzerteStoneInput上使⽤AFL:请参见下⾯的⽰例。
dismyfuzzer?
⼀旦实现了⽬标函数llvmfuzzertestoneinput并将其模糊到死亡,您将希望知道该函数或语料库是否可以进⼀步改进。当然,⼀个易于使⽤
的度量标准是代码覆盖率。
我们建议使⽤clang覆盖率来可视化和研究代码覆盖率(⽰例)。
-suppliedmu2tators
LibFuzzer允许使⽤⾃定义(⽤户提供的)突变,有关详细信息,请参阅Structure-AwareFuzzing。
pinitialization
如果需要初始化正在测试的库,则有⼏个选项。
最简单的⽅法是在LLVMFuzzerTestOneInput(或在全局范围内,如果它适⽤于您)中具有静态初始化的全局对象:
extern"C"intLLVMFuzzerTestOneInput(constuint8_t*Data,size_tSize){
staticboolInitialized=DoInitialization();
...
或者,您可以定义⼀个可选的init函数,它将接收您可以读取和修改的程序参数。只有在您确实需要访问argv/argc时才这样做。
extern"C"intLLVMFuzzerInitialize(int*argc,char***argv){
ReadAndMaybeModify(argc,argv);
return0;
}
使⽤AddressSanitizer或LeakSanitizer构建的⼆进制⽂件将尝试在进程关闭时检测内存泄漏。对于过程中模糊测试,这是不⽅便的,因为
⼀旦发现泄漏突变,模糊器需要⽤再现器报告泄漏。但是,在每次突变后运⾏完全泄漏检测是昂贵的。
默认情况下(-detect_leaks=1),libFuzzer将在执⾏每个突变时计算malloc和free调⽤的次数。如果数字不匹配(这本⾝并不意味着
存在泄漏),libFuzzer将调⽤更昂贵的LeakSanitizer传递,如果发现实际泄漏,它将与再现器⼀起报告,并且进程将退出。
如果您的⽬标有⼤量泄漏并且泄漏检测被禁⽤,则最终会耗尽RAM(请参阅-rss_limit_mb标志)。
pinglibFuzzer
默认情况下,LibFuzzer是作为LLVM项⽬的⼀部分构建在macos和Linux上的。其他操作系统的⽤户可以使⽤-DLIBFUZZER_ENABLE
=YES标志显式请求编译。使⽤来⾃构建⽬录的check-fuzzer⽬标运⾏测试,该⽬录使⽤-DLIBFUZZER_ENABLE_TESTS=ON标志进
⾏配置。
本文发布于:2022-12-26 21:06:05,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/36054.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |