Fuzzbook系列(2):使用随机输入测试程序 – 作者:fstark

前言

在本章中,我们将从介绍最简单的测试生成技术开始—随机文本生成(也称为模糊测试),其关键思想就是将一串随机字符输入到程序中,以期发现故障。

背景知识

您应该了解软件测试的基础知识;即先学习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计算器(bccalculator 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漫画很好地解释了这一点:

xkcd_heartbleed_1

xkcd_heartbleed_2

xkcd_heartbleed_3

在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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论