前言
在本章中,我们将从介绍最简单的测试生成技术开始—随机文本生成(也称为模糊测试),其关键思想就是将一串随机字符输入到程序中,以期发现故障。
背景知识
您应该了解软件测试的基础知识;即先学习Fuzzbook系列(1):软件的安全性测试。
您应该对Python有一定的了解
模糊测试历史
模糊测试诞生于1988年秋季的一个黑暗暴风雨之夜 [Takanen et al, 2008.]。巴顿·米勒教授坐在麦迪逊威斯康星州的公寓里,通过一条1200波特的电话线连接到他所属大学的计算机。阵阵的雷暴在线路上造成噪音,这些噪音又导致两端的UNIX命令获得错误的输入,并导致崩溃。频繁的崩溃使他感到惊讶—我们编写的程序不是应该十分强大吗?作为一名科学家,他想探究该问题的严重程度及其原因。因此,他为威斯康星大学麦迪逊分校的学生编写了一个编程练习,而该练习将使他的学生创建第一个模糊测试器。
这项作业的原文描述是这样的:
The goal of this project is to evaluate the robustness of various UNIX utility programs, given an unpredictable input stream. […] First, you will build a fuzz generator. This is a program that will output a random character stream. Second, you will take the fuzz generator and use it to attack as many UNIX utilities as possible, with the goal of trying to break them.
该项目的目标是在给定不可预测的输入流的情况下评估各种UNIX实用程序的健壮性。[…]首先,您将构建一个模糊发生器。这是一个将输出随机字符流的程序。其次,您将使用模糊发生器,并使用它来攻击尽可能多的UNIX实用程序,以试图破坏它们。
这个作业在不经意间抓住了模糊测试的本质:创建随机的输入,并持续性观察它是否会破坏目标应用程序,理论上只要运行足够长的时间,我们就会看到错误的发生。
创建一个简单的Fuzzer
让我们尝试完成上面这项作业,并用python构建一个模糊生成器。这个想法就是产生随机的字符,并将它们添加到缓冲区字符串变量(out)里,最后返回字符串。
要实现该功能可能会用到python的以下功能函数:
-
random.randrange(start, end)
:从start到end的范围里返回一个随机数 -
range(start, end)
:创建一个start到end范围内为整数的列表,通常在迭代中使用 -
for elem in list: body
:从list列表中获取每个值,带入body
循环执行 -
for i in range(start, end): body
:使用 i 循环执行body函数,i 的范围为从start到end-1 -
chr(n)
:返回带有ASCII码的字符n
要使用随机数,我们必须导入相应的模块:
import random
下面是实际fuzzer()
功能:
def fuzzer(max_length=100, char_start=32, char_range=32): """一个字符串最多有`max_length`个字符, 随机生成字符的ASCII码范围为 [`char_start`, `char_start` + `char_range`]""" string_length = random.randrange(0, max_length + 1) out = "" for i in range(0, string_length): out += chr(random.randrange(char_start, char_start + char_range)) return out
如果使用默认参数,该fuzzer()函数会返回遗传随机字符:
fuzzer()
结果返回:
'!7#%"*#0=)$;%6*;>638:*>80"=</>(/*:-(2<4 !:5*6856&?""11<7+%<%7,4.8,*+&,,$,."'
巴特·米勒(Bart Miller)为此类随机创造了“模糊”一词。现在,假设此“模糊”字符串正是所期望特定输入格式的程序输入-例如:用逗号分隔的值列表或电子邮件地址,以此检验程序是否可以处理此类输入而不会出现任何问题?
如果上述模糊输入已经ok,请考虑可以轻松设置模糊以产生其他各种类型的输入。例如,我们还可以fuzzer()
产生一系列小写字母。我们ord(c)
用来返回字符c的ASCII码。
fuzzer(1000, ord('a'), 26)
结果返回:
'zskscocrxllosagkvaszlngpysurezehvcqcghygphnhonehczraznkibltfmocxddoxcmrvatcleysksodzlwmzdndoxrjfqigjhqjxkblyrtoaydlwwisrvxtxsejhfbnforvlfisojqaktcxpmjqsfsycisoexjctydzxzzutukdztxvdpqbjuqmsectwjvylvbixzfmqiabdnihqagsvlyxwxxconminadcaqjdzcnzfjlwccyudmdfceiepwvyggepjxoeqaqbjzvmjdlebxqvehkmlevoofjlilegieeihmetjappbisqgrjhglzgffqrdqcwfmmwqecxlqfpvgtvcddvmwkplmwadgiyckrfjddxnegvmxravaunzwhpfpyzuyyavwwtgykwfszasvlbwojetvcygectelwkputfczgsfsbclnkzzcjfywitooygjwqujseflqyvqgyzpvknddzemkegrjjrshbouqxcmixnqhgsgdwgzwzmgzfajymbcfezqxndbmzwnxjeevgtpjtcwgbzptozflrwvuopohbvpmpaifnyyfvbzzdsdlznusarkmmtazptbjbqdkrsnrpgdffemnpehoapiiudokczwrvpsonybfpaeyorrgjdmgvkvupdtkrequicexqkoikygepawmwsdcrhivoegynnhodfhryeqbebtbqnwhogdfrsrksntqjbocvislhgrgchkhpaiugpbdygwkhrtyniufabdnqhtnwreiascfvmuhettfpbowbjadfxnbtzhobnxsnf'
假设程序需要一个标识符作为其输入。它会期望这么长的标识符吗?
对外部程序进行模糊测试
创建输入文件
让我们引入一个临时文件模块,以避免使文件系统混乱。
import os
import tempfile
basename = "input.txt"
tempdir = tempfile.mkdtemp()
FILE = os.path.join(tempdir, basename)
print(FILE)
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt
现在,我们可以打开该文件进行写入。Python的open()
函数打开一个文件,我们可以在其中写入任意内容。它通常与with
语句一起使用,以确保不再需要该文件时就将其关闭。
data = fuzzer() with open(FILE, "w") as f: f.write(data)
我们可以通过读取其内容来验证该文件是否真的实际创建:
contents = open(FILE).read()
print(contents)
assert(contents == data)
!6"*-2,$994,%*:"$25!2=!+!2#''6/3'4!6%7056'??2#7;75>27'15#-4.?*<?6&" !3'7-5>18%
调用外部程序
现在我们有一个输入的文件了,让我们来测试一个有趣的程序—bc计算器(bc
calculator program),该程序采取算术表达式对其求值。
要调用bc,我们可以使用Python的subprocess
模块:
import os
import subprocess
program = "bc" with open(FILE, "w") as f: f.write("2 + 2\n") result = subprocess.run([program, FILE], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) # Will be "text" in Python 3.7
从result,我们可以检查程序的输出。对于bc计算器,以下是对算术表达式求解的结果:
result.stdout
'4\n'
我们还可以检查返回的状态值,为0表示程序正确终止。
result.returncode
0
任何错误消息都可以在中找到results.stderr
:
result.stderr
''
除了bc
,您实际上可以放入您喜欢的任何程序。但是请注意,如果您的程序能够更改甚至损坏系统,则模糊输入很可能包含执行此操作的数据或命令。
只是为了好玩,想象一下您将测试一个文件删除程序,模糊器产生有效文件名的机会有多少?
长时间的运行模糊测试
现在让我们将大量输入提供给我们的测试程序,以查看它是否可能在某些程序上崩溃。我们将所有结果存储在runs
变量中,作为输入数据和实际结果对。(注意:运行此程序可能需要一段时间。)
trials = 100 program = "bc" runs = [] for i in range(trials): data = fuzzer() with open(FILE, "w") as f: f.write(data) result = subprocess.run([program, FILE], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) runs.append((data, result))
现在通过runs
列表,我们可以查询一些统计的信息。例如,我们可以查询实际有多少次运行。我们使用sum()
汇总列表中的所有元素。
sum(1 for (data, result) in runs if result.stderr == "")
4
显然,大多数输入都是无效的,这不足为奇,毕竟随机输入不太可能包含有效的算术表达式。
让我们看一下第一个返回的错误:
errors = [(data, result) for (data, result) in runs if result.stderr != ""]
(first_data, first_result) = errors[0]
print(repr(first_data))
print(first_result.stderr)
'5*,55&8>"86,?"/7!1%5-**&-$&)$91;"21(\'8"(%$'
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: parse error
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: &
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: parse error
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: &
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: $
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: &
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: $
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: parse error
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmpjx6ywfdu/input.txt 1: illegal character: $
除了非法字符,解析错误或语法错误外,是否还有其他错误消息? (比如说崩溃或致命错误?)似乎不是很多,我们简单过滤一下看看:
[result.stderr for (data, result) in runs if
result.stderr != ""
and "illegal character" not in result.stderr
and "parse error" not in result.stderr
and "syntax error" not in result.stderr]
[]
也许bc程序的奔溃由程序自己捕捉。不幸的是,返回码似乎永远都不会为0.
sum(1 for (data, result) in runs if result.returncode != 0)
0
我们再让上面的bc
测试运行怎么样?当它运行时,让我们看一下1989年的技术水平能做到哪一步。
利用Fuzzers查找Bug
当Miller和他的学生在1989年运行他们的第一个模糊器时,他们发现了一个令人震惊的结果:大约1/3的UNIX实用程序遇到了问题—它们在模糊测试输入时崩溃、挂起或以其他方式失败[ Miller,1990]。这也包括上面的bc
程序。(当然,这些错误现已修复!)
考虑到这些UNIX实用程序中有许多都是在脚本中使用的,这些脚本也将处理网络输入,因此这是一个令人震惊的结果。程序员迅速构建并运行了自己的模糊器,急于修复所报告的错误,并学会了不再信任外部输入。
米勒的模糊测试实验发现了什么问题?事实证明,程序员在1990年犯的错误在今天我们仍然在不断地重犯。
1.缓冲区溢出
许多程序具有输入和输入元素的内置最大长度。在像C这样的语言中,很容易超出这些长度,而不会被程序(或程序员)甚至注意到,我们称之为缓冲区溢出。例如,以下代码即使有八个以上的字符,也会将input
字符串复制到字符串中:
char weekday[9]; // 8 characters + trailing '\0' terminator
strcpy (weekday, input);
比如input
是"Wednesday"
(9个字符),则该操作已经失败;任何多余的字符(此处'y'
以及后面的'\0'
字符串终止符)都将被简单地复制到after之后的内存中weekday
,从而触发任意行为;可能是一些布尔字符变量,将'n'
变为'y'
。使用模糊测试,很容易产生任意长的输入和输入元素。
我们可以在Python函数中轻松模拟这种缓冲区溢出行为:
def crash_if_too_long(s): buffer = "Thursday" if len(s) > len(buffer): raise ValueError
不出所料,它很快就崩溃了。
from ExpectError import ExpectError
trials = 100 with ExpectError(): for i in range(trials): s = fuzzer() crash_if_too_long(s)
Traceback (most recent call last): File "<ipython-input-23-f83db3d59a06>", line 5, in <module> crash_if_too_long(s) File "<ipython-input-21-928c2d2de976>", line 4, in crash_if_too_long raise ValueError ValueError (expected)
上面代码中的with ExpectError()行可确保打印错误消息,但继续执行; 这是为了将其他代码示例中的“意外”错误与“意外”错误区分开。
2.缺少错误检查
许多编程语言没有exceptions,但是在异常情况下,函数会返回特殊的错误代码。比如getchar()
,C函数通常从标准输入返回一个字符;如果没有可用的输入,它将返回特殊值EOF
(end of file)。现在假设程序员正在扫描输入中的下一个字符,以读取字符,getchar()
直到读取空格为止:
while (getchar() != ' ') {
}
猜想如果输入在预想之前结束(模糊测试完全可能生成这种结果),会发生什么情况?好吧,getchar()
会返回EOF
,并在再次调用时继续返回EOF
;因此,上面的代码将会进入一个无限循环。
同样,我们可以模拟这种丢失的错误检查。如果输入中没有空格,此函数将有效挂起:
def hang_if_no_space(s): i = 0 while True: if i < len(s): if s[i] == ' ': break i += 1
使用我们从在第一篇文章介绍的超时机制,我们可以在一段时间后中断此功能。实测,经过一些模糊的输入后,它确实会挂起。
from ExpectError import ExpectTimeout
trials = 100 with ExpectTimeout(2): for i in range(trials): s = fuzzer() hang_if_no_space(s)
Traceback (most recent call last): File "<ipython-input-26-8e40f7d62a1b>", line 5, in <module> hang_if_no_space(s) File "<ipython-input-24-5f437edacff4>", line 7, in hang_if_no_space i += 1 File "<ipython-input-24-5f437edacff4>", line 7, in hang_if_no_space i += 1 File "ExpectError.ipynb", line 59, in check_time TimeoutError (expected)
上面代码中的with ExpectTimeout()行可确保在两秒钟后中断内含代码的执行,并显示错误消息。
3.敏感数值
使用模糊测试,很容易在输入中生成不常见的值,从而引起各种有趣的行为。 再次考虑下面的C语言代码,该代码首先从输入中读取缓冲区大小,然后分配给定大小的缓冲区。
char *read_input() { size_t size = read_buffer_size(); char *buffer = (char *)malloc(size); // fill buffer return (buffer); }
如果size很大,超过了程序内存,会发生什么?如果size小于以下字符数,会发生什么?如果size为负数会怎样?通过在此处提供随机数,模糊处理可以造成各种损害。
同样,我们可以轻松地在Python中模拟此类流氓数字。如果传递的值(字符串)在转换为整数后过大,则函数crash_if_too_large()将失败。
def collapse_if_too_large(s): if int(s) > 1000: raise ValueError
我们可以让fuzzer()创建一个数字字符串:
long_number = fuzzer(100, ord('0'), 10)
print(long_number)
7056414967099541967374507745748918952640135045
如果我们将这些数字输入collapse_if_too_large(),它将很快崩溃。
with ExpectError(): collapse_if_too_large(long_number)
Traceback (most recent call last): File "<ipython-input-29-7a1817f3dbfc>", line 2, in <module> collapse_if_too_large(long_number) File "<ipython-input-27-2c22a83a4dca>", line 3, in collapse_if_too_large raise ValueError ValueError (expected)
如果我们真的想在系统上分配那么多的内存,那么如上所述使它快速故障实际上是更好的选择。 但实际上,内存的不足可能会极大地降低系统的速度,甚至导致系统完全无响应,并且重启是唯一的选择。
有人可能会说,这些都是不良编程或不良编程语言的问题。但是,每天都有成千上万的人开始编程,甚至在今天,所有人都一次又一次地犯同样的错误。
捕获错误
当Miller和他的学生建造第一个模糊器时,他们可以仅仅因为程序崩溃或挂起就能识别错误-这两个条件很容易识别。但是,如果故障更加微妙,我们需要进行其他检查。
通用检查器
如上一部分所述那样,缓冲区溢出是普遍问题实例:在C和C ++之类的语言中,程序可以访问其内存的任意部分,甚至是那些未初始化,已经释放或根本不属于数据部分的部分。如果要编写一个操作系统,这是必需的;如果要获得最大的性能或控制,这是非常好的;但是,如果要避免错误,这是很糟糕的。幸运的是,有一些工具可在运行时帮助捕获此类问题,与模糊测试结合使用时,它们非常有用。
1.检查存储器访问
为了在测试期间捕获有问题的内存访问,可以在特殊的内存检查环境中运行C程序。在运行时,它们检查每个内存操作是否访问有效和初始化的内存。一个流行的例子是LLVM Address Sanitizer,它检测整套潜在的危险内存安全违规行为。在下面的示例中,我们将使用该工具编译一个相当简单的C程序,并通过读取内存的已分配部分来引发越界读取。
with open("program.c", "w") as f: f.write(""" #include <stdlib.h> #include <string.h> int main(int argc, char** argv) { /* Create an array with 100 bytes, initialized with 42 */ char *buf = malloc(100); memset(buf, 42, 100); /* Read the N-th element, with N being the first command-line argument */ int index = atoi(argv[1]); char val = buf[index]; /* Clean up memory so we don't leak */ free(buf); return val; } """)
from bookutils import print_file
print_file("program.c")
#include <stdlib.h> #include <string.h> int main(int argc, char** argv) { /* Create an array with 100 bytes, initialized with 42 */ char *buf = malloc(100); memset(buf, 42, 100); /* Read the N-th element, with N being the first command-line argument */ int index = atoi(argv[1]); char val = buf[index]; /* Clean up memory so we don't leak */ free(buf); return val; }
我们在启用ASAN的情况下编译此C程序:
!clang -fsanitize=address -g -o program program.c
如果运行参数为的程序99
,则返回buf[99]
,即42。
!./program 99; echo $?
42
但是,访问buf [110]会导致AddressSanitizer中出现越界错误。
!./program 110
=================================================================
==9753==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60b0000000ae at pc 0x000104078ef0 bp 0x7ffeebb87390 sp 0x7ffeebb87388
READ of size 1 at 0x60b0000000ae thread T0
#0 0x104078eef in main program.c:12
#1 0x7fff6a25acc8 in start (libdyld.dylib:x86_64+0x1acc8)
0x60b0000000ae is located 10 bytes to the right of 100-byte region [0x60b000000040,0x60b0000000a4)
allocated by thread T0 here:
#0 0x1040df793 in wrap_malloc (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x61793)
#1 0x104078e3f in main program.c:7
#2 0x7fff6a25acc8 in start (libdyld.dylib:x86_64+0x1acc8)
SUMMARY: AddressSanitizer: heap-buffer-overflow program.c:12 in main
Shadow bytes around the buggy address:
0x1c15ffffffc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1c15ffffffd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1c15ffffffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1c15fffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1c1600000000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x1c1600000010: 00 00 00 00 04[fa]fa fa fa fa fa fa fa fa fa fa
0x1c1600000020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c1600000030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c1600000040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c1600000050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c1600000060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==9753==ABORTING
如果您想在C程序中发现错误,则为模糊测试打开此类检查相当容易。根据工具的不同,它会将执行速度降低一定的因素(对于AddressSanitizer,通常为2××),并且消耗更多的内存,但是与发现这些错误所需的人工相比,CPU周期实在是太便宜了。
对内存的越界访问具有很大的安全风险,因为它们可能使攻击者访问甚至修改并非针对他们的信息。作为一个著名的例子,HeartBleed错误是OpenSSL库中的一个安全错误,它实现了加密协议,该协议提供了计算机网络上的通信安全性。(如果您在浏览器中阅读此文本,则可能已使用这些协议对其进行了加密。)
通过向SSL心跳服务发送特制命令来利用HeartBleed错误。心跳服务用于检查另一端的服务器是否仍在运行。客户端会向服务发送一个字符串,例如
BIRD (4 letters)
服务器将向其回复BIRD
,并且客户端将知道服务器仍在运行。
不幸的是,这种服务可以通过要求服务器回复利用指定的一组字母。下面的XKCD漫画很好地解释了这一点:
在OpenSSL实现中,这些内存内容可能涉及加密证书,私钥等,而且更糟的是,没有人会注意到刚刚访问了该内存。发现HeartBleed错误后,它已经存在了很多年,没有人会知道是否泄漏了哪些秘密;哪些秘密已经泄漏。HeartBleed公告页面说明](http://heartbleed.com/)了一切。
但是如何发现HeartBleed?非常简单。Codenomicon公司以及Google的研究人员都使用内存清理器编译了OpenSSL库,然后用模糊的命令很快将其触发。然后,内存清理程序会注意到是否发生了越界内存访问-实际上,它将很快发现这一点。
内存检查器只是众多检查器之一,可在模糊检查期间运行该检查器以检测运行时错误。在后续章节我们将学习更多有关如何定义通用检查器的信息。
我们已经完成了program
,所以我们进行清理:
!rm -fr program program.*
2.信息泄露
信息泄漏不仅可能通过非法的内存访问而发生;它们也可能出现在“有效”内存中-如果此“有效”内存包含不应泄漏的敏感信息。让我们在Python程序中说明此问题。首先,让我们创建一些程序存储器,其中填充了实际数据和随机数据:
secrets = ("<space for reply>" + fuzzer(100) + "<secret-certificate>" + fuzzer(100) + "<secret-key>" + fuzzer(100) + "<other-secrets>")
我们向中添加了更多的“内存”字符secrets
,并用它们"deadbeef"
作为未初始化内存的标记:
uninitialized_memory_marker = "deadbeef" while len(secrets) < 2048: secrets += uninitialized_memory_marker
我们定义了一个服务(类似于上面讨论的心跳服务),该服务将接收答复并发送回一个长度。它会将要发送的回复存储在内存中,然后以给定的长度发送回去。
def heartbeat(reply, length, memory): # Store reply in memory memory = reply + memory[len(reply):] # Send back heartbeat s = "" for i in range(length): s += memory[i] return s
这非常适合标准字符串:
heartbeat("potato", 6, memory=secrets)
'potato'
heartbeat("bird", 4, memory=secrets)
'bird'
但是,如果该长度大于应答字符串的长度,则会溢出其他内存内容。请注意,所有这些仍然在常规数组范围内发生,因此不会触发地址清理器:
heartbeat("hat", 500, memory=secrets)
'hatace for reply>#,,!3?30>#61)$4--8=<7)4 )03/%,5+! "4)0?.9+?3();<42?=?0<secret-certificate>7(+/+((1)#/0\'4!>/<#=78%6$!!$<-"3"\'-?1?85!05629%/); *)1\'/=9%<secret-key>.(#.4%<other-secrets>deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadb'
一个人如何发现这种问题?这个想法是要识别不应该泄漏的信息,例如给定的机密,以及未初始化的内存。我们可以在一个小的Python示例中模拟这种检查:
from ExpectError import ExpectError
with ExpectError(): for i in range(10): s = heartbeat(fuzzer(), random.randint(1, 500), memory=secrets) assert not s.find(uninitialized_memory_marker) assert not s.find("secret")
Traceback (most recent call last): File "<ipython-input-44-b7e8a1210599>", line 4, in <module> assert not s.find(uninitialized_memory_marker) AssertionError (expected)
通过这种检查,我们发现机密和/或未初始化的内存确实泄漏了。在以后有关信息流的章节中,我们将讨论如何自动执行此操作,“污染”敏感信息及其衍生的值,并确保“污染”的值不会泄漏出去。
根据经验,在模糊测试期间,应始终启用尽可能多的自动检查器。CPU周期便宜,错误也很昂贵。如果仅执行该程序而没有实际检测错误的选项,那么您将失去几个发现漏洞机会。
经验教训
随机生成输入(“模糊测试”)是一种简单,经济高效的方法,可以快速测试任意程序的健壮性。
模糊器发现的错误主要是由于输入处理中的错误和不足。
要捕获错误,请具有尽可能多的一致性检查
下一步
在现有输入上使用变异以获得更多有效输入
使用语法指定输入格式,从而获得更多有效输入
减少失败的输入以进行有效的调试
来源:freebuf.com 2021-01-04 17:34:18 by: fstark
请登录后发表评论
注册