LibFuzzer workshop学习之路

 

LibFuzzer workshop学习之路

最近做项目开始上手学习libfuzzer,是跟着libfuzzer-workshop学的。写下自己的心得和收获。

官方给出的定义

LibFuzzer is in-process, coverage-guided, evolutionary fuzzing engine.
LibFuzzer is linked with the library under test, and feeds fuzzed inputs to the library via a specific fuzzing entrypoint (aka “target function”); the fuzzer then tracks which areas of the code are reached, and generates mutations on the corpus of input data in order to maximize the code coverage. The code coverage information for libFuzzer is provided by LLVM’s SanitizerCoverage instrumentation.

简单来说就是通过与要进行fuzz的库连接,并将libfuzzer生成的输入通过模糊测试进入点(fuzz target)喂给要fuzz的库进行fuzz testing。同时fuzzer会跟踪哪些区域的代码已经被测试过的,并且根据种料库的输入进行变异来使得代码覆盖率最大化。代码覆盖率的信息是由LLVM’s SanitizerCoverage插桩提供的

需要注意的是这几个libfuzzer的特性:in-process指进程内。即libfuzzer在fuzz时并不是产生出多个进程来分别处理不同的输入,而是将所有的测试数据放入进程的内存空间中。coverage-guided指覆盖率指导的。即会进行代码覆盖率的计算,正如定义所说的使得不断增大代码覆盖率。evolutionary是指libfuzzer是进化型的fuzz,结合了产生和变异两种形式。

环境搭建:

跟着https://github.com/Dor1s/libfuzzer-workshop 搭建就好了,主要是build llvm的环境可能要make一会儿。编译好后拿到libfuzzer.a(静态链接库文件),就可以开始上手实践了。

fuzz testing

libfuzzer已经提供了数据样本生成和异常检测功能,我们要做的就是要实现模糊测试进入点(fuzz target),将libfuzzer生成的数据交给目标程序处理。
fuzz target编写模板:

// fuzz_target.cc
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  DoSomethingInterestingWithMyAPI(Data, Size);
  return 0;  // Non-zero return values are reserved for future use.
}

需要注意的是LLVMFuzzerTestOneInput函数即使我们要实现的接口函数,他的两个参数Data(libfuzzer的测试样本数据),size(样本数据的大小)。
DoSomethingInterestingWithMyAPI函数即我们实际要进行fuzz的函数。

编译.cc文件:

clang++ -g -O1 -fsanitize=fuzzer,address \
fuzz_target.cc ../../libFuzzer/Fuzzer/libFuzzer.a \
-o fuzzer_target

几个参数:
-g 可选参数,保留调试符号。
-O1 指定优化等级为1
-fsanitize 指定sanitize。
fuzzer是必须的,用来启用libfuzzer。还可以附加的其他sanitize有:address(用来检测内存访问相关的错误,如stack_overflow,heap_overflow,uaf,可以与fuzzer一起使用);memory(检测未初始化内存的访问,应单独使用);undefined(检测其他的漏洞,如整数溢出,类型混淆等未定义的漏洞)
注:-fsanitize-coverage=trace-pc-guard选项在高版本的clang中已不再适用,代码的覆盖率情况默认自动开启。

这一步骤整体过程就是通过clang的-fsanitize=fuzzer选项可以启用libFuzzer,这个选项在编译和链接过程中生效,实现了条件判断语句和分支执行的记录,并且辅以libFuzzer中的库函数(libfuzzer.a),通过生成不同的测试样例然后能够获得代码的覆盖率情况,最终实现所谓的fuzz testing。

开始fuzz

先来lesson 04,要测试的库是vulnerable_functions.h:

// Copyright 2016 Google Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");

#ifndef LESSONS_04_VULNERABLE_FUNCTIONS_H_
#define LESSONS_04_VULNERABLE_FUNCTIONS_H_

#include <stdint.h>
#include <stddef.h>
#include <cstring>

#include <array>
#include <string>
#include <vector>


bool VulnerableFunction1(const uint8_t* data, size_t size) {
  bool result = false;
  if (size >= 3) {
    result = data[0] == 'F' &&
             data[1] == 'U' &&
             data[2] == 'Z' &&
             data[3] == 'Z';
  }

  return result;
}


template<class T>
typename T::value_type DummyHash(const T& buffer) {
  typename T::value_type hash = 0;
  for (auto value : buffer)
    hash ^= value;

  return hash;
}

constexpr auto kMagicHeader = "ZN_2016";
constexpr std::size_t kMaxPacketLen = 1024;
constexpr std::size_t kMaxBodyLength = 1024 - sizeof(kMagicHeader);

bool VulnerableFunction2(const uint8_t* data, size_t size, bool verify_hash) {
  if (size < sizeof(kMagicHeader))
    return false;

  std::string header(reinterpret_cast<const char*>(data), sizeof(kMagicHeader));

  std::array<uint8_t, kMaxBodyLength> body;

  if (strcmp(kMagicHeader, header.c_str()))
    return false;

  auto target_hash = data[--size];

  if (size > kMaxPacketLen)
    return false;

  if (!verify_hash)
    return true;

  std::copy(data, data + size, body.data());
  auto real_hash = DummyHash(body);
  return real_hash == target_hash;
}


constexpr std::size_t kZn2016VerifyHashFlag = 0x0001000;

bool VulnerableFunction3(const uint8_t* data, size_t size, std::size_t flags) {
  bool verify_hash = flags & kZn2016VerifyHashFlag;
  return VulnerableFunction2(data, size, verify_hash);
}


#endif // LESSONS_04_VULNERABLE_FUNCTIONS_H_

首先看VulnerableFunction1(),有两个参数data/size,当size>3时会产生数组越界。接下来编写测试接口:

//first_fuzzer.cc
// Copyright 2016 Google Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");

#include <stdint.h>
#include <stddef.h>

#include "vulnerable_functions.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  VulnerableFunction1(data, size);
  return 0;
}

可以看到,直接将Libfuzzer生成的测试样例给到VulnerableFunction1就好。
接下来编译:
clang++ -g -std=c++11 -fsanitize=fuzzer,address first_fuzzer.cc ../../libFuzzer/Fuzzer/libFuzzer.a -o first_fuzzer
生成可执行程序first_fuzzer。

mkdir corpus1
./first_fuzz corpus1

corpus1是我们提供的语料库。理想情况下,该语料库应该为被测代码提供各种有效和无效的输入,模糊器基于当前语料库中的样本输入生成随机突变。如果突变触发了测试代码中先前未覆盖的路径的执行,则该突变将保存到语料库中以供将来变更。当然LibFuzzer也可以没有任何初始种子的情况下工作(因为上面提到他是evolutionary型的fuzzer),但如果受测试的库接受复杂的结构化输入,则会因为随机产生的样例不易符合导致效率降低。
另外,如果我们有太多的样例并希望能够精简一下,则可以:

mkdir corpus1_min
./first_fuzzer -merge=1 corpus1_min corpus1

这样,corpus1_min将会存放精简后的输入样例。

运行后得到crash,很快啊!!!

➜  04 git:(master) ✗ ./first_fuzzer corpus1 
INFO: Seed: 2222548757
INFO: Loaded 1 modules   (35 inline 8-bit counters): 35 [0x7f7120, 0x7f7143), 
INFO: Loaded 1 PC tables (35 PCs): 35 [0x5b7a68,0x5b7c98), 
INFO:        0 files found in corpus1
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2    INITED cov: 3 ft: 3 corp: 1/1b exec/s: 0 rss: 27Mb
#3    NEW    cov: 4 ft: 4 corp: 2/4b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 1 CMP- DE: "\x00\x00"-
#1190    NEW    cov: 5 ft: 5 corp: 3/7b lim: 14 exec/s: 0 rss: 27Mb L: 3/3 MS: 2 ChangeBinInt-CMP- DE: "F\x00"-
#12191    NEW    cov: 6 ft: 6 corp: 4/11b lim: 122 exec/s: 0 rss: 28Mb L: 4/4 MS: 1 InsertByte-
#12297    REDUCE cov: 6 ft: 6 corp: 4/10b lim: 122 exec/s: 0 rss: 28Mb L: 3/3 MS: 1 EraseBytes-
#17213    REDUCE cov: 7 ft: 7 corp: 5/40b lim: 170 exec/s: 0 rss: 29Mb L: 30/30 MS: 1 InsertRepeatedBytes-
#17274    REDUCE cov: 7 ft: 7 corp: 5/27b lim: 170 exec/s: 0 rss: 29Mb L: 17/17 MS: 1 EraseBytes-
#17356    REDUCE cov: 7 ft: 7 corp: 5/24b lim: 170 exec/s: 0 rss: 29Mb L: 14/14 MS: 2 ChangeBit-EraseBytes-
#17437    REDUCE cov: 7 ft: 7 corp: 5/18b lim: 170 exec/s: 0 rss: 29Mb L: 8/8 MS: 1 EraseBytes-
#17458    REDUCE cov: 7 ft: 7 corp: 5/14b lim: 170 exec/s: 0 rss: 29Mb L: 4/4 MS: 1 EraseBytes-
=================================================================
==9875==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200005a1d3 at pc 0x00000059b461 bp 0x7ffd79c84880 sp 0x7ffd79c84878
READ of size 1 at 0x60200005a1d3 thread T0
    #0 0x59b460 in VulnerableFunction1(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14
    #1 0x59bde4 in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/04/first_fuzzer.cc:10:3
    #2 0x466186 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:556
    #3 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470
    #4 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699
    #5 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830
    #6 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824
    #7 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19
    #8 0x7fbfaac5abf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #9 0x41f599 in _start (/home/admin/libfuzzer-workshop/lessons/04/first_fuzzer+0x41f599)

0x60200005a1d3 is located 0 bytes to the right of 3-byte region [0x60200005a1d0,0x60200005a1d3)
allocated by thread T0 here:
    #0 0x597b58 in operator new[](unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cpp:102
    #1 0x466092 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:541
    #2 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470
    #3 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699
    #4 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830
    #5 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824
    #6 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19
    #7 0x7fbfaac5abf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14 in VulnerableFunction1(unsigned char const*, unsigned long)
Shadow bytes around the buggy address:
  0x0c04800033e0: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c04800033f0: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fd
  0x0c0480003400: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x0c0480003410: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x0c0480003420: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
=>0x0c0480003430: fa fa fd fa fa fa fd fa fa fa[03]fa fa fa fa fa
  0x0c0480003440: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480003450: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480003460: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480003470: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480003480: 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
==9875==ABORTING
MS: 1 EraseBytes-; base unit: aea2e3923af219a8956f626558ef32f30a914ebc
0x46,0x55,0x5a,
FUZ
artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
Base64: RlVa

需要注意的地方有点多:
前面的几行输出fuzzer相关的选项和配置信息。seed=2222548757是生成的随机数种子,可以利用./first_fuzzer -seed=2222548757指定随机种子。-max_len为测试输入的最大长度

以#开头的表示在fuzz的过程中覆盖的路径信息。
INITED fuzzer已完成初始化,其中包括通过被测代码运行每个初始输入样本。
READ fuzzer已从语料库目录中读取了所有提供的输入样本。
NEW fuzzer创建了一个测试输入,该输入涵盖了被测代码的新区域。此输入将保存到主要语料库目录。
pulse fuzzer已生成 2的n次方个输入(定期生成以使用户确信fuzzer仍在工作)。
REDUCE fuzzer发现了一个更好(更小)的输入,可以触发先前发现的特征(设置-reduce_inputs=0以禁用)。
cov 执行当前语料库所覆盖的代码块或边的总数。
ft libFuzzer使用不同的信号来评估代码覆盖率:边缘覆盖率,边缘计数器,值配置文件,间接调用方/被调用方对等。这些组合的信号称为功能(ft:)。
corp 当前内存中测试语料库中的条目数及其大小(以字节为单位)。
exec/s 每秒模糊器迭代的次数。
rss 当前的内存消耗。
L 新输入的大小(以字节为单位)。
MS:<n> <操作> 计数和用于生成输入的变异操作列表

#17458 REDUCE cov: 7 ft: 7 corp: 5/14b lim: 170 exec/s: 0 rss: 29Mb L: 4/4 MS: 1 EraseBytes-
指尝试了17458个输入,成功发现了5个样本(放入语料库)大小为14b,共覆盖了7个代码块,占用内存29mb,变异操作为EraseBytes-。

接下来就是异常检测相关的信息:

==9875==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200005a1d3 at pc 0x00000059b461 bp 0x7ffd79c84880 sp 0x7ffd79c84878
READ of size 1 at 0x60200005a1d3 thread T0

可以看到AddressSanitizer检测到其中的一个输入触发了堆溢出(heap-buffer-overflow)的漏洞。
更据错误信息中的#0 0x59b460 in VulnerableFunction1(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14可以看到错误点在vulnerable_functions.h:22:14,对应data[3] == 'Z';即数组越界的位置。下面的SUMMARY也与之对应。

倒数第二行给出了造成crash的输入,并将其写入了crash-0eb8e4ed029b774d80f2b66408203801cb982a60。
复现crash可执行./first_fuzzer ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60

这样fuzz tesing基本上已经完成了,我们得到了一个造成程序crash的输入,并得知存在堆溢出的漏洞。这样我们就可以有针对性的对程序进行动态调试,利用造成crash的输入回溯出漏洞的细节。

继续fuzz函数VulnerableFunction2

constexpr auto kMagicHeader = "ZN_2016";
constexpr std::size_t kMaxPacketLen = 1024;
constexpr std::size_t kMaxBodyLength = 1024 - sizeof(kMagicHeader);

bool VulnerableFunction2(const uint8_t* data, size_t size, bool verify_hash) {
  if (size < sizeof(kMagicHeader))
    return false;

  std::string header(reinterpret_cast<const char*>(data), sizeof(kMagicHeader));

  std::array<uint8_t, kMaxBodyLength> body;

  if (strcmp(kMagicHeader, header.c_str()))
    return false;

  auto target_hash = data[--size];

  if (size > kMaxPacketLen)
    return false;

  if (!verify_hash)
    return true;

  std::copy(data, data + size, body.data());
  auto real_hash = DummyHash(body);
  return real_hash == target_hash;
}

该函数多了一个bool参数,因此我们的的接口函数要有所改动:

#include <stdint.h>
#include <stddef.h>

#include "vulnerable_functions.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  bool verify_hash_flags[] = {true, false};
  for(auto flag : verify_hash_flags)
    VulnerableFunction2(data, size, flag);
  return 0;
}

为了提高fuzz出crash的概率,我们要分别fuzz flag为true和false的请况,而不应该把flag写死。
接着编译并执行得到:

INFO: Seed: 296692635
INFO: Loaded 1 modules   (37 inline 8-bit counters): 37 [0x7f8160, 0x7f8185), 
INFO: Loaded 1 PC tables (37 PCs): 37 [0x5b7d00,0x5b7f50), 
INFO:        0 files found in corpus2
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2    INITED cov: 5 ft: 6 corp: 1/1b exec/s: 0 rss: 27Mb
#414    NEW    cov: 6 ft: 7 corp: 2/9b lim: 8 exec/s: 0 rss: 27Mb L: 8/8 MS: 2 ChangeByte-CrossOver-
    NEW_FUNC[1/13]: 0x59bab0 in unsigned char* std::copy<unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:447
    NEW_FUNC[2/13]: 0x59bb40 in std::array<unsigned char, 1016ul>::data() /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/array:235
#584    NEW    cov: 24 ft: 26 corp: 3/17b lim: 8 exec/s: 0 rss: 28Mb L: 8/8 MS: 5 CopyPart-CopyPart-ShuffleBytes-CrossOver-CMP- DE: "ZN_2016"-
#105505    NEW    cov: 25 ft: 27 corp: 4/1067b lim: 1050 exec/s: 0 rss: 47Mb L: 1050/1050 MS: 1 CrossOver-
#106508    REDUCE cov: 25 ft: 27 corp: 4/1046b lim: 1050 exec/s: 0 rss: 48Mb L: 1029/1029 MS: 3 EraseBytes-ChangeBit-CopyPart-
#108257    REDUCE cov: 25 ft: 27 corp: 4/1043b lim: 1060 exec/s: 0 rss: 49Mb L: 1026/1026 MS: 4 ChangeByte-ChangeByte-CrossOver-InsertRepeatedBytes-
=================================================================
==10468==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff31422548 at pc 0x00000055286d bp 0x7fff31421f90 sp 0x7fff31421740
WRITE of size 1023 at 0x7fff31422548 thread T0
    #0 0x55286c in __asan_memmove /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:30
    #1 0x59c238 in unsigned char* std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<unsigned char>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:368:6
    #2 0x59c158 in unsigned char* std::__copy_move_a<false, unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:385:14
    #3 0x59c0b6 in unsigned char* std::__copy_move_a2<false, unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:422:18
    #4 0x59bb39 in unsigned char* std::copy<unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:454:15
    #5 0x59b8b5 in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:61:3
    #6 0x59bf63 in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/04/second_fuzzer.cc:12:2
    #7 0x466186 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:556
    #8 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470
    #9 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699
    #10 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830
    #11 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824
    #12 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19
    #13 0x7fd1b4bf6bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #14 0x41f599 in _start (/home/admin/libfuzzer-workshop/lessons/04/second_fuzzer+0x41f599)

Address 0x7fff31422548 is located in stack of thread T0 at offset 1128 in frame
    #0 0x59b4af in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:42

  This frame has 3 object(s):
    [32, 64) 'header' (line 46)
    [96, 97) 'ref.tmp' (line 46)
    [112, 1128) 'body' (line 48) <== Memory access at offset 1128 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:30 in __asan_memmove
Shadow bytes around the buggy address:
  0x10006627c450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10006627c4a0: 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3 f3 f3 f3
  0x10006627c4b0: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00
  0x10006627c4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10006627c4e0: 00 00 00 00 f1 f1 f1 f1 02 f3 f3 f3 00 00 00 00
  0x10006627c4f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==10468==ABORTING
MS: 4 ChangeBinInt-CrossOver-ChangeByte-EraseBytes-; base unit: e63bf1b07c6950248bb14fe74c3a84f06c711b15
artifact_prefix='./'; Test unit written to ./crash-a42cbe2ff7331f281ef213e54919e8cd932883bd

相信大家已经不再陌生了,定位错误位于#5 0x59b8b5 in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:61:3
std::copy(data, data + size, body.data());,该句造成了stack_overflow,原因在于vector类型的body的大小为1024 – sizeof(kMagicHeader),而在copy时的data的size的限制条件是size > kMaxPacketLen(1024),从而造成了缓冲区溢出。

如果我们写死flag为false的话就可能会跑很久,也跑不出来crash。因此,我们在套用模板时要结合函数的逻辑,使得fuzz的接口设计的更加合理,从而增加fuzz的效率。

继续继续VulnerableFunction3:

constexpr std::size_t kZn2016VerifyHashFlag = 0x0001000;

bool VulnerableFunction3(const uint8_t* data, size_t size, std::size_t flags) {
  bool verify_hash = flags & kZn2016VerifyHashFlag;
  return VulnerableFunction2(data, size, verify_hash);
}

可以看到,与2不同的地方就是对flag进行了& kZn2016VerifyHashFlag的计算,这种其实我们可以计算出使得flags & kZn2016VerifyHashFlag为true/false的输入,并模仿second_fuzzer那样写个循环,这样就和2没差了。
但这里workshop提供了一个不同的方法:In this case, we can get some randomization of flags values using data provided by libFuzzer:

#include <stdint.h>
#include <stddef.h>

#include "vulnerable_functions.h"

#include <functional>
#include <string>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  std::string data_string(reinterpret_cast<const char*>(data), size);
  auto data_hash = std::hash<std::string>()(data_string);

  std::size_t flags = static_cast<size_t>(data_hash);
  VulnerableFunction3(data, size, flags);
  return 0;
}

采用了libfuzzer提供的随机化方法使得我们的输入flag为随机数,从而flags & kZn2016VerifyHashFlag的结果也随机化。在次数足够多的情况下false和true的情况将趋于同为50%。

接下来看lesson05:

这次的目标和lesson04有所不同,lesson04中我们针对.h函数库中的函数进行fuzz,而libfuzzer的威力远不止于此,它还可以对大型开源库进行模糊测试。在源码编译开源库时选择合适的选项以及将libfuzzer与开源库链接在一起以进行fuzz,这些细节将在该lesson05体现。

首先解包tar xzf openssl1.0.1f.tgz。接着执行./config生成makefile。之后:

make clean
make CC="clang -O2 -fno-omit-frame-pointer -g -fsanitize=address" -j$(nproc)

第二条指令其实并不太规范,将编辑器以外的其他参数也一股脑写到CC变量里了。clang后的参数本应该是由CFLAGS或CXXFLAGS指定的。解释一下选项:

-02 指定优先级别
-g 带有调试符号来编译
-fno-omit-frame-pointer 对于不需要栈指针的函数就不在寄存器中保存指针,因此可以忽略存储和检索地址的代码,同时对许多函数提供一个额外的寄存器。所有”-O”级别都打开它,但仅在调试器可以不依靠栈指针运行时才有效。在AMD64平台上此选项默认打开,但是在x86平台上则默认关闭。建议显式的设置它。
-fsanitize 指定sanitize

这些参数的指定是至关重要的,它会影响到之后开源库与libfuzzer的链接以及fuzz的效率。如果不设置这些编译选项直接make的话fuzz的效率如下:

➜  05 git:(master) ✗ ./openssl_fuzzer2
INFO: Seed: 1865248494
INFO: Loaded 1 modules   (10 inline 8-bit counters): 10 [0x96c950, 0x96c95a), 
INFO: Loaded 1 PC tables (10 PCs): 10 [0x6d56d0,0x6d5770), 
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2    INITED cov: 2 ft: 3 corp: 1/1b exec/s: 0 rss: 29Mb
#131072    pulse  cov: 2 ft: 3 corp: 1/1b lim: 1300 exec/s: 43690 rss: 388Mb
#262144    pulse  cov: 2 ft: 3 corp: 1/1b lim: 2611 exec/s: 43690 rss: 397Mb
#524288    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 37449 rss: 411Mb
#1048576    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 34952 rss: 411Mb
#2097152    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 32768 rss: 411Mb

这路径覆盖率太感人,其中的重要原因就在于编译开源库时的选项设置。
设置编译选项后的fuzz效果:

INFO: Seed: 2565779026
INFO: Loaded 1 modules   (35878 inline 8-bit counters): 35878 [0xcd8590, 0xce11b6), 
INFO: Loaded 1 PC tables (35878 PCs): 35878 [0x954da8,0x9e1008), 
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2    INITED cov: 410 ft: 411 corp: 1/1b exec/s: 0 rss: 36Mb
#112    NEW    cov: 413 ft: 416 corp: 2/2b lim: 4 exec/s: 0 rss: 42Mb L: 1/1 MS: 5 ChangeBit-ChangeBinInt-ChangeByte-InsertByte-EraseBytes-
    NEW_FUNC[1/1]: 0x65f0a0 in ERR_put_error /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/crypto/err/err.c:708
#319    NEW    cov: 420 ft: 448 corp: 3/8b lim: 6 exec/s: 0 rss: 51Mb L: 6/6 MS: 2 ShuffleBytes-CrossOver-
#327    REDUCE cov: 420 ft: 448 corp: 3/7b lim: 6 exec/s: 0 rss: 51Mb L: 5/5 MS: 3 ChangeByte-EraseBytes-CrossOver-
    NEW_FUNC[1/2]: 0x562a40 in tls1_enc /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_enc.c:685
    NEW_FUNC[2/2]: 0x67e1d0 in EVP_MD_CTX_md /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/crypto/evp/evp_lib.c:282
#454    REDUCE cov: 431 ft: 467 corp: 4/12b lim: 6 exec/s: 0 rss: 57Mb L: 5/5 MS: 2 ShuffleBytes-CMP- DE: "\xfd\x03\x00\x00"-
    NEW_FUNC[1/6]: 0x5663d0 in tls1_alert_code /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_enc.c:1214
    NEW_FUNC[2/6]: 0x5c6560 in do_ssl3_write /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_pkt.c:634
#470    NEW    cov: 465 ft: 514 corp: 5/17b lim: 6 exec/s: 0 rss: 58Mb L: 5/5 MS: 1 ChangeByte-
#472    REDUCE cov: 465 ft: 521 corp: 6/23b lim: 6 exec/s: 0 rss: 58Mb L: 6/6 MS: 2 CrossOver-CrossOver-
#475    NEW    cov: 467 ft: 523 corp: 7/28b lim: 6 exec/s: 0 rss: 58Mb L: 5/6 MS: 3 PersAutoDict-ChangeByte-ShuffleBytes- DE: "\xfd\x03\x00\x00"-
#1025    NEW    cov: 467 ft: 526 corp: 8/39b lim: 11 exec/s: 0 rss: 81Mb L: 11/11 MS: 5 CopyPart-ShuffleBytes-CrossOver-ShuffleBytes-ChangeBinInt-
#1097    NEW    cov: 474 ft: 549 corp: 9/49b lim: 11 exec/s: 0 rss: 84Mb L: 10/11 MS: 2 ChangeBit-CopyPart-
#1293    REDUCE cov: 474 ft: 549 corp: 9/48b lim: 11 exec/s: 0 rss: 92Mb L: 10/10 MS: 1 EraseBytes-

选择合适的编译器和编译选项,完成对该库的源码编译,生成.a文件。接下来就要研究编写fuzzer接口函数了。
workshop提供了openssl_fuzzer.cc:

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <assert.h>
#include <stdint.h>
#include <stddef.h>

#ifndef CERT_PATH
# define CERT_PATH
#endif

SSL_CTX *Init() {
  SSL_library_init();
  SSL_load_error_strings();
  ERR_load_BIO_strings();
  OpenSSL_add_all_algorithms();
  SSL_CTX *sctx;
  assert (sctx = SSL_CTX_new(TLSv1_method()));
  /* These two file were created with this command:
      openssl req -x509 -newkey rsa:512 -keyout server.key \
     -out server.pem -days 9999 -nodes -subj /CN=a/
  */
  assert(SSL_CTX_use_certificate_file(sctx, CERT_PATH "server.pem",
                                      SSL_FILETYPE_PEM));
  assert(SSL_CTX_use_PrivateKey_file(sctx, CERT_PATH "server.key",
                                     SSL_FILETYPE_PEM));
  return sctx;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  static SSL_CTX *sctx = Init();
  SSL *server = SSL_new(sctx);
  BIO *sinbio = BIO_new(BIO_s_mem());
  BIO *soutbio = BIO_new(BIO_s_mem());
  SSL_set_bio(server, sinbio, soutbio);
  SSL_set_accept_state(server);
  BIO_write(sinbio, Data, Size);
  SSL_do_handshake(server);
  SSL_free(server);
  return 0;
}

这里就涉及到openssl库提供的相关方法了,本篇主要讲解fuzz相关,就不细讲openssl了。总之就是要先搞清楚openssl的用法,再通过include openssl提供的函数来对openssl进行fuzz。编译如下:

clang++ -g openssl_fuzzer.cc -O2 -fno-omit-frame-pointer -fsanitize=address,fuzzer \
    -I openssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a \
    ../../libFuzzer/Fuzz/libFuzzer.a -o openssl_fuzzer

-I指定inlcude的搜索路径,同时链接静态库libcrypto.a和libFuzzer.a以使用库中的函数。

运行跑出crash:

#24646    REDUCE cov: 611 ft: 889 corp: 50/1105b lim: 116 exec/s: 24646 rss: 382Mb L: 64/77 MS: 5 InsertRepeatedBytes-PersAutoDict-InsertByte-ShuffleBytes-ChangeBit- DE: "\xff\xff\xff\xff\xff\xff\xff\x04"-
#25193    REDUCE cov: 611 ft: 889 corp: 50/1097b lim: 116 exec/s: 25193 rss: 382Mb L: 61/77 MS: 1 EraseBytes-
=================================================================
==2133==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000009748 at pc 0x00000051eaba bp 0x7ffd255dae50 sp 0x7ffd255da618
READ of size 21050 at 0x629000009748 thread T0
    #0 0x51eab9 in __asan_memcpy /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:22:3
    #1 0x55e323 in tls1_process_heartbeat /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_lib.c:2586:3
    #2 0x5cb97d in ssl3_read_bytes /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_pkt.c:1092:4
    #3 0x5cff83 in ssl3_get_message /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_both.c:457:7
    #4 0x59ac86 in ssl3_get_client_hello /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_srvr.c:941:4
    #5 0x596ac1 in ssl3_accept /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_srvr.c:357:9
    #6 0x5518ad in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/05/openssl_fuzzer.cc:39:3
    #7 0x459a01 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:553:15
    #8 0x459245 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:469:3
    #9 0x45b4e7 in fuzzer::Fuzzer::MutateAndTestOne() /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:695:19
    #10 0x45c205 in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:831:5
    #11 0x449fc8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:825:6
    #12 0x473432 in main /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
    #13 0x7f40a3932bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #14 0x41e159 in _start (/home/admin/libfuzzer-workshop/lessons/05/openssl_fuzzer2+0x41e159)

0x629000009748 is located 0 bytes to the right of 17736-byte region [0x629000005200,0x629000009748)
allocated by thread T0 here:
    #0 0x51f67d in malloc /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:145:3
    #1 0x60091b in CRYPTO_malloc /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/crypto/mem.c:308:8
    #2 0x5d1737 in freelist_extract /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_both.c:708:12
    #3 0x5d1737 in ssl3_setup_read_buffer /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_both.c:770:10
    #4 0x5d1d4c in ssl3_setup_buffers /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_both.c:827:7
    #5 0x597703 in ssl3_accept /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_srvr.c:292:9
    #6 0x5518ad in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/05/openssl_fuzzer.cc:39:3
    #7 0x459a01 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:553:15
    #8 0x45b8a5 in fuzzer::Fuzzer::ReadAndExecuteSeedCorpora(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:740:3
    #9 0x45be79 in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:793:3
    #10 0x449fc8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:825:6
    #11 0x473432 in main /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
    #12 0x7f40a3932bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

SUMMARY: AddressSanitizer: heap-buffer-overflow /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:22:3 in __asan_memcpy
Shadow bytes around the buggy address:
  0x0c527fff9290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff92a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff92b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff92c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c527fff92d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c527fff92e0: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
  0x0c527fff92f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff9300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff9310: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff9320: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c527fff9330: 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
==2133==ABORTING
MS: 2 ChangeBinInt-InsertByte-; base unit: 7bfa057a7ae6a7bc6ea487749407e4e7d4e9bf84
0x15,0x3,0xd4,0x0,0x7,0x1,0x3a,0x1,0x3a,0x1,0xd3,0xb3,0x18,0x3,0xd4,0x0,0x7,0x1,0x52,0x3a,0x1,0x3a,0x9,0xd3,0xb3,0x18,0x2c,0x0,0x7,0x2,0x7,0x1,0x3a,0x1,0x0,
\x15\x03\xd4\x00\x07\x01:\x01:\x01\xd3\xb3\x18\x03\xd4\x00\x07\x01R:\x01:\x09\xd3\xb3\x18,\x00\x07\x02\x07\x01:\x01\x00
artifact_prefix='./'; Test unit written to ./crash-4bfb53055de3fed318590b20ddd4cda0d95e8ed8
Base64: FQPUAAcBOgE6AdOzGAPUAAcBUjoBOgnTsxgsAAcCBwE6AQA=

通过SUMMARY容易找到造成heap_overflow的漏洞点在于:/local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:22:3 in __asan_memcpy这种目录一看就是很底层的目录,不易定位。再往找一层:#1 0x55e323 in tls1_process_heartbeat /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_lib.c:2586:3这就很容易定位了,接下来就是漏洞溯源了。
定位到了代码:

tls1_process_heartbeat(SSL *s)
    {
    unsigned char *p = &s->s3->rrec.data[0], *pl;
    unsigned short hbtype;
    unsigned int payload;
    unsigned int padding = 16; /* Use minimum padding */

    /* Read type and payload length first */
    hbtype = *p++;
    n2s(p, payload);
    pl = p;

    if (s->msg_callback)
        s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
            &s->s3->rrec.data[0], s->s3->rrec.length,
            s, s->msg_callback_arg);

    if (hbtype == TLS1_HB_REQUEST)
        {
        unsigned char *buffer, *bp;
        int r;

        /* Allocate memory for the response, size is 1 bytes
         * message type, plus 2 bytes payload length, plus
         * payload, plus padding
         */
        buffer = OPENSSL_malloc(1 + 2 + payload + padding);
        bp = buffer;

        /* Enter response type, length and copy payload */
        *bp++ = TLS1_HB_RESPONSE;
        s2n(payload, bp);
        memcpy(bp, pl, payload);   //漏洞点
        bp += payload;
        /* Random padding */
        RAND_pseudo_bytes(bp, padding);

        r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);

        if (r >= 0 && s->msg_callback)
            s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
                buffer, 3 + payload + padding,
                s, s->msg_callback_arg);

        OPENSSL_free(buffer);

        if (r < 0)
            return r;
        }
    else if (hbtype == TLS1_HB_RESPONSE)
        {
        unsigned int seq;

        /* We only send sequence numbers (2 bytes unsigned int),
         * and 16 random bytes, so we just try to read the
         * sequence number */
        n2s(pl, seq);

        if (payload == 18 && seq == s->tlsext_hb_seq)
            {
            s->tlsext_hb_seq++;
            s->tlsext_hb_pending = 0;
            }
        }

    return 0;
    }

漏洞点再memcpy(bp, pl, payload);根据crash原因为over_flow说明payload的长度有问题。往上分析发现payload是通过n2s函数从p指向的结构体(用户数据包)内容得到的。该结构体为:

typedef struct ssl3_record_st
    {
        int type;               / type of record /
        unsigned int length;    / How many bytes available /
        unsigned int off;       / read/write offset into 'buf' /
        unsigned char data;    / pointer to the record data /
        unsigned char input;   / where the decode bytes are /
        unsigned char comp;    / only used with decompression - malloc()ed /
        unsigned long epoch;    / epoch number, needed by DTLS1 /
        unsigned char seq_num[8]; / sequence number, needed by DTLS1 /
    } SSL3_RECORD;

而程序并没有对用户可控的length做检查,从而导致memcpy溢出(有可能将server端的数据写入到返回数据包中返回给用户)。

初学libfuzzer,如有纰漏错误之后还烦请师傅们指正。

(完)