你的位置:小萝莉刘俊英 > 巨乳 乳首 > 海安幼儿园 白丝 [翻译]刻薄fuzz - part 5:深入了解代码心事率-二进制间隙-看雪-安全社区|安全招聘|kanxue.com
海安幼儿园 白丝 [翻译]刻薄fuzz - part 5:深入了解代码心事率-二进制间隙-看雪-安全社区|安全招聘|kanxue.com
发布日期:2024-09-03 18:15    点击次数:90

海安幼儿园 白丝 [翻译]刻薄fuzz - part 5:深入了解代码心事率-二进制间隙-看雪-安全社区|安全招聘|kanxue.com

先容

咱们之前也曾在这一系列著作中打算过代码心事率的迫切性,是以今天咱们将尝试结识一些相当基本的底层意见,一些常见的标准,一些器用,还会望望一些流行的拖沓测试框架未必诓骗的时刻。咱们将避让一些更难懂的战略海安幼儿园 白丝,而专注于所谓的“基础常识”,即那些常见的话题。是以,若是你是拖沓测试的生手或者软件测试的生手,这篇博客应该对你比较友好。我发现这个领域中使用的许多术语是直不雅且易于结识的,但也有一些例外。但愿本文能匡助你起步,开动你我方的接洽。

咱们会尽量不堕入界说的细枝小节,而是专注于学习常识。我不是狡计机科学家,这篇博客的目的只是向你先容这些意见,以便你能结识它们在拖沓测试中的实用性。本着这种精神,若是你发现任何误导性或严重造作的信息,请告诉我。

感谢那些在Twitter上粗野解答问题并匡助我的东说念主,比如:@gamozolabs、@domenuk、@is_eqv、@d0c_s4vage 和 @naehrdine,仅举几例。

中枢界说

咱们开赴点需要处理一些界说。这些界说很迫切,因为咱们将在后续的诠释和探索中建立在它们的基础上。

代码心事率

代码心事率是任何能让你了解测试、输入等心事了弱点代码若干的运筹帷幄。咱们不会花太多时候在这里打算,因为咱们在之前的著作中也曾打算过代码心事率。代码心事率对拖沓测试相当迫切,因为它不错让你追踪办法弱点中你未必心事的名义积。你不错想象,若是你只探索了弱点空间的一小部分,你的测试可能在全面性上会有所限制。

基本块

让咱们先望望维基百科的界说:

“在编译器构造中,基本块是一个直线代码序列,除了进口以外莫得分支干与,除了出口以外莫得分支拨去。”

是以“基本块”是一个线性实行的代码序列,在这里代码实行旅途莫得契机分支到不同的办法。让咱们用一个可视化的例子来证据。假定有以下这个浅近的弱点,它通过呐喊行获取一个密码,然后检验它是否相宜密码长度要求:

一朝咱们将其编译并在Ghidra中进行分析,咱们不错看到main()的以下图形视图:

“块”(Blocks)是那些直不雅的术语之一,咱们不错看到图形视图自动将main()剖析为多个代码块。若是你检讨每个块的里面,你会发当代码实行是单向的,块里面莫得契机走两条或更多不同的旅途。代码实行就像在轨说念上行驶的火车,而轨说念上莫得分岔。你不错看到在这个例子中,块通过要求跳转(JZ, JNZ)、main函数复返以及调用exit函数来间隔。

边/分支/调理

“边”(Edge)是狡计机科学/图论中的术语之一,我觉得它并不绝顶直不雅,我更可爱用“调理”(Transition)或“分支”(Branch),但执行上它是用来形貌基本块之间的关系的。回看咱们在Ghidra中的基本块图,咱们不错看到存在几种不同的关系,也即是说,凭证一些要求,代码实行不错遴选多条旅途。

基本块001006cf与两个不同的块相关系:001006e4和00100706。是以代码实行在001006cf中时,不错凭证要求到达其关联的两个块中的苟且一个。在咱们的例子中,这个要求是JZ操作,即判断呐喊行参数的数目是否为2:

若是参数数目不是2,咱们通过不进行要求跳转(JZ)天然地分支到块001006e4。 若是参数数目是2,咱们通过进行要求跳转分支到块00100706。

这两种可能性不错称为“边”(Edges),是以块01006cf有两个边。你不错想象,从拖沓测试的角度来看,这可能有多迫切。若是咱们的拖沓测试器只探索了某个基本块的一条边,那就意味着咱们遗漏了通盘这个词分支未进行测试,因此咱们有必要追踪这类信息。

显着,这个意见比我在这里提到的要复杂得多,你不错在Wikipedia上阅读更多对于端正流图(Control-flow graph)的内容。

旅途

“旅途”(Path)只是咱们弱点识行遍历的基本块列表。看着咱们的示例弱点,有几个不同的旅途,如下图中的橙色、绿色和红色线条所示。

旅途一:0x001006cf -> 0x001006e4

旅途二:0x001006cf -> 0x00100706 -> 0x00100738

旅途三:0x001006cf -> 0x00100706 -> 0x0000722

插桩

在这篇博客中,“插桩”指的是为拖沓测试办法提供代码心事率反映数据的历程。这可能意味着许多事情。它不错像完全重写咱们莫得源代码的已编译二进制文献那样复杂,也不错像在每个基本块进口地址上摈弃一个断点那样浅近。

对于插桩,需要记取的一个迫切方面是插桩所带来的性能耗损。若是你的插桩时刻不错提供比另一种时刻多50%有用信息,但后者的性能却杰出1000倍,那么你就必须辩论衡量。多50%的数据可能相当值得这种弘远的性能耗损,这取决于具体情况。

仅有二进制文献

这是一个浅近的意见,“仅有二进制文献”是指咱们莫得源代码的办法。是以咱们只可操作一个二进制文献。它不错是动态连气儿的或静态连气儿的。这类办法在某些环境中更为渊博,比如镶嵌式办法、MacOS 和 Windows。不外,在 Linux 上也依然存在仅有二进制文献的办法,只是相对较少。

即使“仅有二进制文献”这个意见容易结识,但它对获取代码心事率数据的影响却是潜入的。许多流行的代码心事率机制依赖于领有源代码,这么不错以一种成心于集中心事率数据的样子编译办法。而对于仅有二进制文献的办法,咱们无法按照我方的意愿编译办法,只可处理也曾编译好的办法。

常见战略

在本节中,咱们将开动探讨拖沓测试器用用于集中代码心事率数据的常见战略。

追踪基本块

集中代码心事率最浅近的标准之一即是浅近地追踪给定输入到达的基本块数目。你不错想象咱们正在用输入探索一个办法弱点,咱们想知说念哪些代码也曾被心事。凭证咱们上头临于基本块的界说,若是咱们干与一个基本块,咱们将实行其中的通盘代码,因此,若是咱们只追踪某个基本块是否已被触及,至少咱们不错知说念哪些旅途尚未被心事,而且不错手动检验它们。

这种标准并不复杂,在提供高保真心事率数据方面的才智较弱;但是,它结束起来极其浅近,而且适用于多样办法。莫得源代码?在上头加一些断点。没时候写编译器代码?在上头加一些断点。

从性能角度来看,这种时刻相当棒。获取新的心事率意味着触发一个断点,移除断点并还原在插桩历程中被心事的原始内容,保存触发断点的输入,然后不竭实行。这些事件在发生时执行上是慢的;但是,跟着拖沓测试的进行,新的心事率变得越来越萧疏。因此,这种标准在前期本钱较高,但跟着时候的推移最终会降至接近零。

据我有限的训戒来看,这种类型的心事率频繁用于闭源办法(二进制文献),因为咱们遴荐有限,而这种低时刻的标准已迷漫灵验。

让咱们快速望望 @gamozolabs 的一个相当浅近的基本块追踪心事器用叫作念 Mesos。你不错看到它主要用于 Windows 上的办法,因为大多数办法是仅有二进制文献的。这个器用的奥密之处在于它的性能。你不错在README中看到他的基准测试效能:

需要可贵的极少是,若是你使用这种样子集中心事率数据,你可能会将我方限制在第一个到达基本块的输入上。比如说,咱们有以下代码:

假定咱们第一次输入以一个值为200的input[0x9]到达了这段代码,那么咱们将干与parsing_routine_1块的进口。咱们会在parsing_routine_1的进口处移除断点,并将触及该块的输入添加到咱们的测试集(corpus)中。但是,由于咱们也曾用值为200的输入达到了这个块,因此咱们恒久不会再用其他可能到达这个块的值触发这个断点。是以咱们恒久不会保存一个“以不同样子措置”这个基本块的输入到测试聚首。这可能瑕瑜常迫切的。假定parsing_routine_1接着会读取通盘这个词输入,并在输入的每一个字节上实行某种永劫候的知道操作。而且假定莫得后续的例程是高度有景况的,即大输入与小输入在活动上有显耀互异。若是咱们给弱点的第一个措置这个块的输入是1MB大小的,那么咱们的拖沓测试器就与咱们在测试聚首保存的大输入“绑缚”在一王人了,而咱们也许是因为祸殃地莫得开赴点用较小的输入措置这个块,这可能会影响性能。

措置这个问题的一种标准是如期重新建立通盘的断点。比如说,你的拖沓测试器也曾运行了100亿个测试案例,而且在24小时内莫得发现任何新的心事率,此时你不错重新插入通盘也曾发现的断点,并尝试用不同的样子措置这些基本块,也许不错保存一个更小、更高效的输入来措置这个块,比如用input[0x9] = 20。执行上,有一百万种不同的标准不错措置这个问题。我深信 @gamozolabs 之前在Twitter上打算过这个问题,但我没能找到那条推文。

总的来说,这是一个相当灵验的心事标准,绝顶是辩论到它适用于多种办法而且结束起来也很浅近。

追踪边和旅途

追踪边相当流行,因为这是AFL偏激养殖器用遴选的战略。这种标准不仅热心哪些基本块被触及,还热心基本块之间探索了哪些关系。

AFL++ 的统计输出中提到了旅途(paths)和边(edges),而且隐含提到了“计数器”(counters)。我不是百分之百笃定,但我深信他们对“旅途”的界说与咱们上头提到的界说是一致的。我觉得他们在文档中暗示“旅途”与测试用例(testcase)沟通。

我不会在这里深入分析 AFL 偏激养殖器用(执行上 AFL++ 和 AFL 也曾有很大不同)是如何集中和分析心事率的,原因很浅近:这是为高智力东说念主群准备的,我对此了解未几。若是你对更详备的分析感兴致,不错检讨他们的文档,尽情学习。

为了追踪边,AFL 使用了触及关系的基本块地址元组。因此在咱们的示例弱点中,若是咱们因为莫得提供正确数目的呐喊行参数而从块 0x001006cf 转到块 0x001006e4,那么这个元组 (0x001006cf, 0x001006e4) 就会被添加到 AFL++ 用于追踪私有旅途的心事率图中。让咱们追踪一下在弱点中遍历通盘这个词旅途时会纪录哪些元组:

0x001006cf -> 0x00100706 -> 0x00100722

若是咱们走上头的旅途,咱们不错造成两个心事率数据元组:(0x001006cf, 0x00100706) 和 (0x00100706, 0x00100722)。这些元组不错在 AFL 的心事数据中查找,以笃定这些关系是否也曾被探索过。

AFL 不仅追踪这些关系,还追踪它们的频率。举例,它不错知说念每个特定边被触及和探索的频率。

这种心事数据比只是追踪到达的基本块要复杂得多;但是,获取这种细节的历程也远不像前者那么浅近。

在最常见的情况下,AFL 通过在办法上使用编译时插桩来获取这些数据。你不错使用 AFL 编译器编译你的办法(前提是你有源代码),编译器会生成包含插桩代码的办法编译代码。这相当奥密,但需要拜访源代码,而这并不老是可能的。

AFL 对于仅有二进制文献的办法也有措置有运筹帷幄,它诓骗了遒劲的 QEMU 模拟器来集中肖似的详备心事数据。模拟器不错相对解放地拜访这类数据,因为它们需要处理办法辅导,要么诠释它们(即模拟其实行),要么即时编译(JIT)这些块为土产货代码并以土产货样子实行。在这里使用的 QEMU 中,块被即时编译为土产货代码并存储在缓存中,以便后续实行能缓慢叠加使用。因此,当 QEMU 遭受一个基本块时,它不错检验该块是否也曾被编译,并凭证情况遴选相应的活动。AFL 诓骗这个历程来追踪哪些块正在被实行,并获取与编译时插桩肖似的数据。

最新三级片

我并不完全结识其中的隐隐隐别,但有一篇很棒的博客著作不错阅读:@abiondo 在2018年撰写的对于他们对 AFL QEMU 花样进行优化的帖子。简单地转头(但愿不会太不准确),QEMU 会瞻望算所谓的径直跳转,并将这些块基本编译为一个单一块(通过保抓实行在土产货编译的块中)手脚一种加快样子。以这个浅近的例子为例:

在这个例子中,咱们有一个不错瞻望算的跳转办法。咱们知说念从现时地址到LAB_0x00100738的相对偏移量(current_addr - LAB_0x00100738的实足值),是以在模拟器中,咱们不错径直实行这个跳转,并将办法替换为LAB_0x00100738的已编译块,这么在每次实行时就不需要进行狡计(只需在初度狡计相对偏移量时狡计一次)。这将允许模拟器以土产货实行的样子不竭运行,而无谓回到我称之为“模拟花样”的景况中,在每次实行时都需要狡计跳转地址。这在QEMU中称为“块链化”(block-chaining)。你不错想象,若是发生这种情况,那么通过土产货实行的那块弘远的代码(执行上是两个块)对于AFL来说是完全不透明的,因为AFL并不知说念其中包含了两个块,因此无法纪录所遴选的边。因此,手脚一种变通标准,AFL会修补QEMU,使其不再进行这种块链化,并保抓每个块的寂然性,以便不错追踪边。这意味着在每个块驱散时,不管是径直跳转照旧其他,QEMU都会回到“模拟花样”,这会带来性能耗损。

不外,务必阅读 @abiondo 的博客著作,它要详备得多,也更具信息性。

若是你想知说念什么是曲折跳转,它指的是唯有在实行时才知说念跳转位置的情况,在一个浅近的示例中可能会像这么:

独一的问题是,使用QEMU来集中心事率数据的速率相对较慢,与纯土产货实行比较有一定的性能耗损。天然,这种性能下跌是值得的,因为你获取的数据量相当大,而且在仅有二进制文献的办法上有时莫得其他替代有运筹帷幄。

比较心事率/比较剖析

与只是追踪输入或测试在弱点的基本块/边中的阐述不同,比较心事率旨在了解咱们的测试在弱点中的比较操作中取得了若干阐述。比较不错通过不同的样子进行,但咱们的示例密码弱点中也曾存在一个常见的例子。在001006cf块中,咱们有一个CMP操作:

CMP dword ptr [RBP + local_1c], 0x2

dword是一个4字节(32位)值,这个操作将咱们弱点中的argc值与0x2进行比较,以检验提供了若干呐喊行参数。因此,咱们的两个比较操作数划分是堆栈上RBP + local_1c偏移位置的值和0x2。若是这两个操作数相当,零绚丽(Zero Flag)将被建立,咱们不错诓骗JZ要求跳转在弱点中进行相应的操作。

但是,和拖沓测试相关的问题是,这种比较是二元的。它要么建立零绚丽,要么不建立,莫得任何隐隐隐别。咱们无法知说念咱们在通过比较时有多接近,无法知说念建立零绚丽的可能性有多大。

举个例子,假定咱们不是用0x2进行比较,而是用0xdeadbeef。在这种情况下,若是咱们提交了0xdeadbebe手脚另一个操作数,咱们会比提交0x0更接近怡悦JZ要求。

从高等次上看,比较心事率将这种比较剖析成多个部分,以便未必比二元的通过/失败(PASS/FAIL)更紧密地追踪比较的阐述。因此,使用比较心事率,这个比较可能会被重写如下:

之前:

0xdeadbebe == 0xdeadbeef ?

之后:

0xde == 0xde ? 若是是,纪录咱们匹配了第一个字节,然后再比较

0xad == 0xad ? 若是是,纪录咱们匹配了第二个字节,然后再比较

0xbe == 0xbe ? 若是是,纪录咱们匹配了第三个字节,然后再比较

0xbe == 0xef ? 若是是,纪录咱们完全匹配了这两个操作数。

在咱们重写后的例子中,咱们不再得到二元的通过/失败,而是看到咱们在比较中取得了75%的阐述,匹配了4个字节中的3个。当今咱们知说念不错保存这个输入并进一步变异它,但愿通过正确的变异通过临了一个字节的比较。

咱们也无谓将每个比较仅剖析到字节级别,咱们还不错在比特级别上比较两个操作数。举例,咱们也不错将它们如下进行比较:

1101 1110 1010 1101 1011 1110 1110 1111 vs

1101 1110 1010 1101 1011 1110 1011 1110

这不错剖析成32个寂然的比较,而不是4个,予以咱们更多的保真度和阐述追踪(尽管在实践中可能会罢休性能)。

这里咱们将4字节的比较剖析成4个寂然的单字节比较。这也被称为“比较剖析”(Compare Shattering)。从执行上讲,它与比较心事率相当不异。其中枢念念想是将大的比较剖析成更小的部分,以便能以更高的保真度追踪阐述。

一些拖沓测试器会将通盘比较操作数,比如这个例子中的0xdeadbeef,添加到一种“魔术值字典”中,拖沓测试器会立时将其插入输入中,但愿畴前能通过比较。

你不错想象一个场景:弱点在分支到一个需要探索的复杂例程之前检验一个大值。仅靠基原意事率很难通过这些检验,这频繁需要多半东说念主为侵扰。有东说念主可能会在IDA中检讨泄漏已到达块的彩色图形,试图手动找出是什么窒碍了拖沓测试器到达未到达的块,并笃定32字节的大比较失败了。然后不错通过字典或其他样子调理拖沓测试器以辩论这种比较,但通盘这个词历程相当手动。

对于具有源代码和仅有二进制文献的办法,执行上有一些相当酷爱且高度时刻化的标准来作念到这极少!

AFL++ 提供了一种LLVM花样,你不错诓骗他们称之为“laf-intel插桩”的时刻,这里有相关形貌,领先的著作在这里。径直援用laf-intel的博客著作,咱们不错看到他们的例子与咱们刚刚打算的念念想实验相当不异,他们有这么的源代码:

这段代码片断被“去优化”成多个较小的比较,拖沓测试器不错通过这些比较来斟酌其阐述。

这种去优化的代码不错在指定某些环境变量并使用 afl-clang-fast 编译办法时产生。

这相当奥密,不错极地面减少拖沓测试中的手动使命量。

但若是咱们无法拜访源代码,而且咱们的仅有二进制文献的办法可能充满了多半的比较操作,咱们该怎么办呢?

行运的是,针对这个问题也有开源措置有运筹帷幄。让咱们来看一个名为“TinyInst”的器用,由 @ifsecure 和他的团队斥地。我无法深入知道这个器用的时刻细节,因为我从未使用过它,但README文献形貌得很详备!

正如咱们所见,TinyInst 针对的是 MacOS 和 Windows 办法,相宜其为仅有二进制文献的办法插桩的目的。TinyInst 通过调试器插桩遴荐的例程来获取心事率数据,改动实行权限,使得对咱们插桩代码的任何实行拜访(而不是读取或写入,因为这些权限保抓不变)都会导致故障,然后由 TinyInst 调试器处理,在此历程中,代码实行被重定向到重新编写的插桩例程/模块。因此,TinyInst 窒碍了原始模块的通盘实行,而是将通盘实行重定向到插入到弱点中的重写模块。你不错看到这有多遒劲,因为它不错将大领域的比较剖析成更小的比较,样子相当肖似于 laf-intel 标准,但针对的是也曾编译的办法。望望这个由 @ifsecure 共享的展示比较心事率执行操作的酷炫动图:[https://twitter.com/ifsecure/status/1298341219614031873?s=20] 你不错看到他有一个弱点检验一个8字节的值,而他的拖沓测试器清静通过它,直到措置了这个比较。

还有一些其他器用表面上与 TinyInst 的使命样子肖似,也值得一看,它们也在 README 中提到,比如:DynamoRIO 和 PIN。

还应该提到的是,即使在 QEMU 花样下,AFL++ 也未必进行比较心事率追踪。

非凡内容:使用硬件获取心事率数据

这基本上概括了咱们感兴致的数据类型、原因以及如何索求它们。还有一种索求数据的标准莫得提到,但对仅有二进制文献的办法绝顶有匡助,那即是诓骗执行硬件获取心事率数据。

天然这不算是一种“战略”,但它使上述战略的实行成为可能,因此值得一提。咱们不会深入打算这个话题。如今,CPU内置了多样旨在结束高保真性能分析的实用器用。这些实用器用也不错被用来为咱们提供心事率数据。

Intel-PT 是新款 Intel CPU 提供的一种实用器用,它允许你索求联系你正在运行的软件的信息,比如端正流。每个硬件线程都不错存储对于其正在实行的应用弱点的数据。使用处理器追踪的最大问题在于解码集中到的追踪数据一直以来都相当清静且忙碌。但是,最近 @is_eqv 和 @ms_s3c 得手创建了一个名为 libxdc 的高性能库,它不错高效地解码 Intel-PT 追踪数据。他们的 README 中包含的图表相当酷,你不错看到它比其他硬件开首的心事指挥拖沓测试器用快得多,同期集中到最高保真度的心事数据,他们称之为“完整边心事”(Full Edge Coverage)。径直从 CPU 获取心事率数据似乎是联想的遴荐,哈哈。因此,他们未必联想出一个基本上能给你完好心事率的库,而且不需要源代码,这似乎是一个弘远的成就。我个东说念主面前莫得处理这种心事率的工程才智,但某一天我会有。许多流行的拖沓测试器不错径直诓骗 Intel-PT,举例:AFL++、honggfuzz 和 WinAFL。

还有许多其他肖似的实用器用,但它们超出了本先容性博客著作的范围。

论断

在这篇著作中,咱们打算了一些在这个领域中使用的基础术语、一些用于获取有真义心事率数据的颠倒常见的基本战略,以及一些用于索求这些数据的器用(在某些情况下,哪些拖沓测试框架使用了哪些器用)。需要提到的是,像 AFL++ 和 honggfuzz 这么的流行拖沓测试框架在尽可能天真地联想它们的框架以稳健平时的办法方面付出了很大奋力。它们频繁予以你多半的天真性,使你未必遴选最适当你情况的心事率数据索求标准。但愿这能在一定进度上匡助你结识与拖沓测试相关的代码心事率问题。

译者言

本文使用chatGPT-4o翻译而成,如有造作之处,请斧正原文连气儿:https://h0mbre.github.io/Fuzzing-Like-A-Caveman-5/

[培训]内核驱动高级班,冲击BAT一流互联网大厂使命海安幼儿园 白丝,每周日13:00-18:00直播讲课