-
Notifications
You must be signed in to change notification settings - Fork 31
Fuzzing YamlDotNet (C#) project with sydr‐fuzz (AFL and Sharpfuzz backend) (rus)
В данной статье будет продемонстрирован подход к фаззингу приложений на языке C# с помощью интерфейса Sydr-Fuzz на основе инструмента Sharpfuzz в связке с фаззером AFLplusplus. Sydr-Fuzz предоставляет удобный интерфейс для запуска гибридного фаззинга, задействуя возможности динамического символьного выполнения на базе инструмента Sydr в сочетании с современными фаззерами libFuzzer и AFLplusplus. Помимо фаззинга, Sydr-Fuzz предлагает набор возможностей для минимизации корпуса, сбора покрытия, поиска ошибок в программах посредством проверки предикатов безопасности, а также анализа аварийных завершений при помощи Casr. Помимо программ на чисто компилируемых языках, Sydr-Fuzz поддерживает фаззинг приложений на языках Python, Java и JavaScript. Следующим шагом было добавление возможности фаззинга C# кода через наш инструмент Sydr-Fuzz. Для фаззинга C# приложений бинарные файлы проекта сначала инструментируются через Sharpfuzz, а сам фаззинг осуществляется через AFLplusplus.
Для примера создания фаззинг-цели рассмотрим проект YamlDotNet. Инструкция по установке и использованию инструмента Sharpfuzz может быть найдена в его репозитории на GitHub, инструкция по установке фаззера AFLplusplus так же находится в его GitHub репозитории. В нашем репозитории OSS-Sydr-Fuzz уже есть готовый Docker-контейнер с настроенным окружением, который мы и будем использовать для фаззинга.
Сборка и запуск докер-контейнера производится следующей командой:
$ docker build -t oss-sydr-fuzz-yamldotnet .
$ docker run --privileged --network host -v /etc/localtime:/etc/localtime:ro --rm -it -v $PWD:/fuzz oss-sydr-fuzz-yamldotnet /bin/bash
Рассмотрим фаззинг-цель Program.cs:
using SharpFuzz;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using System.IO;
using System.Text;
using YamlDotNet.RepresentationModel;
public class Program
{
public static void Main(string[] args)
{
Fuzzer.OutOfProcess.Run(stream =>
{
try {
string yml = File.ReadAllText(args[0]);
var input = new StringReader(yml);
var yaml = new YamlStream();
var deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
var serializer = new SerializerBuilder()
.JsonCompatible()
.Build();
var doc = deserializer.Deserialize(input);
var json = serializer.Serialize(doc);
var parser = new Parser(input);
parser.Consume<StreamStart>();
yaml.Load(input);
}
catch (YamlException) { }
catch (System.InvalidOperationException) { }
catch (System.ArgumentNullException) { }
catch (System.ArgumentException) { }
});
}
}
Во-первых, необходимо подключить модуль Sharpfuzz
для использования необходимого интерфейса. Во-вторых, по необходимости нужно добавить необходимые библиотеки проекта. Далее в функции Main нужно вызвать метод Fuzzer.OutOfProcess.Run()
, в качестве
параметра которой нужно передать функцию, являющуюся целью фаззинга. В данном случае мы передаем lambda-функцию, которая вызывает функции фаззинг-цели.
Фаззинг C# кода через Sharpfuzz
подразумевает поиск необработанных исключений, поэтому в данной обертке стоит блок try-catch
, так как мы хотим перехватывать и игнорировать исключения, связанные с неправильным форматом .yaml
файлов и не являющиеся ошибочным поведением программы.
Сборку фаззинг цели желательно производить в новой директории: создать там новое консольное .NET приложение, скопировать туда написанную фазз-обертку и добавить библиотеку Sharpfuzz:
$ mkdir build_fuzz && cd build_fuzz
$ dotnet new console
$ dotnet add package SharpFuzz
$ cp -r /path/to/fuzz_target.cs .
Далее для сборки фаззинг-цели нужно в конфигурационном .csproj
файле связать ее с модулем фаззинг-проекта. Для этого нужно либо собрать сам проект (в директории проекта с помощью dotnet build
или dotnet publish
), найти скомпилированный модуль target_name.dll
(обычно лежит внутри директории bin/
) и указать путь до него в build_fuzz.csproj
файле, либо можно указать путь до .csproj
файла проекта, тогда при сборке фазз-таргета проект будет пересобираться автоматически. Примеры .csproj
файлов:
<ItemGroup>
<Reference Include="target_name">
<HintPath>/path/to/bin/target_name.dll</HintPath>
</Reference>
<PackageReference Include="SharpFuzz" Version="2.1.1" />
</ItemGroup>
либо
<ItemGroup>
<ProjectReference Include="/path/to/csproj/target_name.csproj" />
<PackageReference Include="SharpFuzz" Version="2.1.1" />
</ItemGroup>
После этого нужно собрать фаззинг цель и проинструментировать ее для фаззинга с помощью инструмента Sharpfuzz. Настоятельно рекомендуется при сборке фаззинг-цели использовать ключ -p:CheckForOverfllowUnderflow=true
компилятора, который включает проверку целочисленного переполнения в рантайме. Данная проверка позволит отловить ошибки, которые не будут задетектированы фаззером.
$ dotnet publish build_fuzz.csproj -c release -o bin -p:CheckForOverfllowUnderflow=true
$ sharpfuzz bin/target_name.dll
Сборка готова! Теперь можно перейти к фаззингу нашей цели.
Посмотрим на конфигурационный файл проекта YamlDotNet
parse_yaml.toml:
[sharpfuzz]
args = "-i /corpus -t 10000 -x /yaml.dict"
target = "/build_fuzz/bin/fuzz.dll @@"
casr_bin = "/build_fuzz/bin/release/net8.0/fuzz.dll"
jobs = 2
[cov]
build_dir = "/build_cov"
Здесь таблица [sharpfuzz]
отвечает за конфигурацию запуска фаззера AFL++
. args
- аргументы, здесь обязательно нужно указать путь до корпуса (-i /path/to/corpus), target
- путь до .dll
файла фазз-обертки, @@
обозначают, что ввод задается через файл, без этого символа ввод будет осуществляться через стандартный ввод, a casr_bin
- путь до .dll
файла фазз-обертки, не проинструментированного Sharpfuzz (это поле обязательно, если будет использоваться команда sydr-fuzz casr
для анализа аварийных завершений). И, наконец, jobs
- количество потоков, в которых будет запускаться AFL++.
Далее перейдем к таблице [cov]
. Она отвечает за конфигурацию покрытия. Здесь build_dir
- директория, в которой находится и собирается фазз-таргет.
Для сбора покрытия необходимо создать отдельную директорию и скопировать туда фазз-обертку (ту же самую, что и для фаззинга). .csproj
файл конфигурируется аналогично сборке под фаззинг:
$ mkdir build_cov && cd build_cov
$ dotnet new console
$ dotnet add package SharpFuzz
$ cp -r /path/to/fuzz_target.cs .
Рассмотрим пример build-файла проекта, чтобы понять, как можно собрать готовый фазз-таргет.
# Make directories for fuzzing and coverage.
mkdir -p /build_fuzz /build_cov
cp /Program.cs /fuzz.csproj /build_fuzz
cp /Program.cs /fuzz.csproj /build_cov
# Build target for fuzzing.
cd /build_fuzz
dotnet publish fuzz.csproj -c release -o bin
sharpfuzz bin/YamlDotNet.dll
У нас уже написаны фазз-обертка и .csproj
файл по вышеописанным правилам. Мы создаем две директории (для фаззинга и сбора покрытия) и копируем в них обертку и конфигурационный файл. Далее, как показано выше, собираем проект в директории для фаззинга и инструментируем .dll
модуль проекта. В директории покрытия собирать фазз-таргет необязательно, он будет собран автоматически при запуске сбора покрытия через Sydr-Fuzz через dotnet build
. Если нужно собрать проект с определенными настройками, то можно смело это делать, в таком случае нужно собрать его так, чтобы все .dll
файлы лежали внутри директории bin
.
Теперь, перейдем к фаззингу! Запустим Sydr-Fuzz
следующей командой:
$ sydr-fuzz -c parse_yaml.toml run
[2024-03-13 17:12:20] [INFO] [AFL++] [*] Fuzzing test case #499 (2207 total, 0 crashes saved, state: in progress, mode=explore, perf_score=229, weight=0, favorite=1, was_fuzzed=1, exec_us=223, hits=32, map=1833, ascii=0, run_time=0:00:10:58)...
[2024-03-13 17:12:20] [INFO] [AFL++] [*] Fuzzing test case #502 (2207 total, 0 crashes saved, state: in progress, mode=explore, perf_score=172, weight=0, favorite=1, was_fuzzed=1, exec_us=187, hits=66, map=1537, ascii=0, run_time=0:00:10:58)...
[2024-03-13 17:12:20] [INFO] [AFL++] [*] Fuzzing test case #506 (2207 total, 0 crashes saved, state: in progress, mode=explore, perf_score=114, weight=0, favorite=1, was_fuzzed=1, exec_us=200, hits=174, map=1566, ascii=0, run_time=0:00:10:58)...
[2024-03-13 17:12:20] [INFO] [AFL++] [*] Fuzzing test case #507 (2207 total, 0 crashes saved, state: in progress, mode=explore, perf_score=114, weight=0, favorite=1, was_fuzzed=1, exec_us=262, hits=231, map=1140, ascii=0, run_time=0:00:10:58)...
[2024-03-13 17:12:20] [INFO] Found crash /fuzz/parse_yaml-out/crashes/crash-48745d7888086b915e559192e1c2dc785db5a1c1
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #510 (2207 total, 0 crashes saved, state: in progress, mode=explore, perf_score=114, weight=0, favorite=1, was_fuzzed=1, exec_us=230, hits=155, map=1874, ascii=0, run_time=0:00:10:59)...
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #514 (2208 total, 0 crashes saved, state: in progress, mode=explore, perf_score=128, weight=0, favorite=1, was_fuzzed=1, exec_us=182, hits=108, map=921, ascii=0, run_time=0:00:11:00)...
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #515 (2210 total, 0 crashes saved, state: in progress, mode=explore, perf_score=128, weight=0, favorite=1, was_fuzzed=1, exec_us=176, hits=97, map=904, ascii=0, run_time=0:00:11:00)...
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #516 (2210 total, 0 crashes saved, state: in progress, mode=explore, perf_score=172, weight=0, favorite=1, was_fuzzed=1, exec_us=186, hits=43, map=1558, ascii=0, run_time=0:00:11:00)...
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #518 (2210 total, 0 crashes saved, state: in progress, mode=explore, perf_score=114, weight=0, favorite=1, was_fuzzed=1, exec_us=200, hits=160, map=1548, ascii=0, run_time=0:00:11:00)...
[2024-03-13 17:12:21] [INFO] [AFL++] [*] Fuzzing test case #523 (2210 total, 0 crashes saved, state: in progress, mode=explore, perf_score=300, weight=0, favorite=0, was_fuzzed=1, exec_us=194, hits=11, map=1599, ascii=0, run_time=0:00:11:00)...
[2024-03-13 17:12:24] [INFO] [AFL++]
[2024-03-13 17:12:24] [INFO] [AFL++] +++ Testing aborted by user +++
[2024-03-13 17:12:24] [INFO] [AFL++] [!]
[2024-03-13 17:12:24] [INFO] [AFL++] Performing final sync, this make take some time ...
[2024-03-13 17:12:24] [INFO] [AFL++] [!] Done!
[2024-03-13 17:12:24] [INFO] [AFL++] [+] We're done here. Have a nice day!
[2024-03-13 17:12:24] [INFO] [AFL++]
[2024-03-13 17:12:24] [INFO] [RESULTS] Fuzzing corpuses are saved in workers queue directories. Run sydr-fuzz cmin subcommand to gather full corpus at "/fuzz/parse_yaml-out/corpus-old" and minimized corpus at "/fuzz/parse_yaml-out/corpus".
[2024-03-13 17:12:24] [INFO] [RESULTS] [afl_main] Statistics: 1664 new corpus items found, 6.86% coverage achieved, 0 crashes saved, 0 timeouts saved, total runtime 0 days, 0 hrs, 11 min, 3 sec
[2024-03-13 17:12:24] [INFO] [RESULTS] [afl_s01] Statistics: 1563 new corpus items found, 6.99% coverage achieved, 1 crashes saved, 2 timeouts saved, total runtime 0 days, 0 hrs, 11 min, 3 sec
[2024-03-13 17:12:24] [INFO] [RESULTS] timeout/crash: 2/1
После завершения фаззинга у нас на выходе получилось более 4000 новых файлов в корпусе. Стоит запустить минимизацию корпуса:
sydr-fuzz -c parse_yaml.toml cmin
[2024-03-13 17:17:14] [INFO] [CMIN] corpus minimization tool for AFL++ (awk version)
[2024-03-13 17:17:14] [INFO] [CMIN] [*] Obtaining traces for 4312 input files in '/fuzz/parse_yaml-out/corpus-old'.
[2024-03-13 17:17:14] [INFO] [CMIN] [*] Creating 8 parallel tasks with about 539 items each.
[2024-03-13 17:17:14] [INFO] [CMIN] [*] Waiting for parallel tasks to complete ...
[2024-03-13 17:17:26] [INFO] [CMIN] [*] Done!
[2024-03-13 17:17:26] [INFO] [CMIN] Processing file 1/4312
[2024-03-13 17:17:26] [INFO] [CMIN] Processing file 44/4312
[2024-03-13 17:17:26] [INFO] [CMIN] Processing file 87/4312
[2024-03-13 17:17:27] [INFO] [CMIN] Processing file 130/4312
...
[2024-03-13 17:17:46] [INFO] [CMIN] Processing file 4215/4312
[2024-03-13 17:17:46] [INFO] [CMIN] Processing file 4258/4312
[2024-03-13 17:17:47] [INFO] [CMIN] Processing file 4301/4312
[2024-03-13 17:17:49] [INFO] [CMIN] [+] Found 22119 unique tuples across 4312 files.
[2024-03-13 17:17:49] [INFO] [CMIN] [+] Narrowed down to 839 files, saved in '/fuzz/parse_yaml-out/corpus'.
Теперь наш корпус уменьшился примерно в 5 раз! Теперь можно посмотреть на покрытие кода.
Для сбора покрытия C# кода мы используем 2 инструмента: minicover и AltCover. minicover
используется для форматов html
, clover
, coveralls
, xml
, opencover
, cobertura
, text
; AltCover
- для форматов html
или lcov
. По умолчанию используется AltCover
(так как он позволяет собирать покрытие в параллельном режиме), но если нужно использовать minicover
, то в конфигурационном файле нужно задать таблицу [cov]
в следующем виде:
[cov]
target = "/build_cov/Program.cs"
source = "/YamlDotNet"
build_dir = "/build_cov"
use_minicover = true
Здесь target
- путь до исходного кода обертки, source
- путь до директории с исходным кодом проекта, build_dir
- директория, в которой находится и собирается фазз-таргет, use_minicover
- флаг, указывающий на то, что используется инструмент minicover
(по умолчанию он равен false).
Соберем покрытие!
$ sydr-fuzz -c parse_yaml.toml sharpcov html
[2024-03-13 17:23:13] [INFO] Running sharpcov html "/fuzz/parse_yaml.toml"
[2024-03-13 17:23:13] [INFO] Collecting coverage data for each file in corpus: /fuzz/parse_yaml-out/corpus
[2024-03-13 17:23:13] [INFO] Saving coverage data to /fuzz/parse_yaml-out/coverage/
[2024-03-13 17:23:13] [INFO] Launching dotnet build: cd "/build_cov" && "/usr/bin/dotnet" "build"
[2024-03-13 17:23:20] [INFO] Launching minicover instrumentation: "/root/.dotnet/tools/minicover" "instrument" "--workdir" "/" "--sources" "/build_cov/Program.cs" "--sources" "/YamlDotNet/" "--assemblies" "/build_cov/**/*.dll" "--coverage-file" "/fuzz/parse_yaml-out/coverage/coverage.json" "--hits-directory" "/fuzz/parse_yaml-out/coverage/coverage-hits"
[2024-03-13 17:23:21] [INFO] Launching coverage.
[2024-03-13 17:23:21] [INFO] Collecting coverage: 1/839
[2024-03-13 17:23:22] [INFO] Collecting coverage: 2/839
[2024-03-13 17:23:23] [INFO] Collecting coverage: 3/839
[2024-03-13 17:23:24] [INFO] Collecting coverage: 4/839
...
[2024-03-13 17:33:53] [INFO] Collecting coverage: 836/839
[2024-03-13 17:33:54] [INFO] Collecting coverage: 837/839
[2024-03-13 17:33:54] [INFO] Collecting coverage: 838/839
[2024-03-13 17:33:55] [INFO] Collecting coverage: 839/839
[2024-03-13 17:33:56] [INFO] Launching minicover report to html format: "/root/.dotnet/tools/minicover" "htmlreport" "--workdir" "/" "--coverage-file" "/fuzz/parse_yaml-out/coverage/coverage.json" "--no-fail" "--output" "/fuzz/parse_yaml-out/coverage"
[2024-03-13 17:33:57] [INFO] Coverage collecting to html format is done!
Покрытие (в случае minicover
в формате html
) будет выглядеть следующим образом:
В случае сбора покрытия с помощью инструмента AltCover
, можно дополнительно воспользоваться инструментом reportgenerator. С помощью него можно сгенерировать покрытие в многих форматах, используя .xml
файл, сгенерированный после работы AltCover
:
$ reportgenerator -reports:/fuzz/parse_yaml-out/coverage/coverage.xml -reporttypes:Html -targetdir:/fuzz/parse_yaml-out/coverage/html
В опции -reporttypes:
следует указать желаемый формат. В результате в директории /fuzz/parse_yaml-out/coverage/html будет находиться отчет о покрытии в формате html
.
Наконец, можно применить Casr для анализа и сортировки аварийных завершений. Но перед этим в конфигурационный файл необходимо добавить поле casr_bin
в таблицу [sharpfuzz]
, где нужно указать путь до бинарного файла, не проинструментированного Sharpfuzz:
[sharpfuzz]
...
target = "/build_fuzz/bin/fuzz.dll @@"
casr_bin = "/build_fuzz/bin/release/net8.0/fuzz.dll"
...
Теперь запустим анализ аварийных завершений с помощью Casr:
$ sydr-fuzz -c parse_yaml.toml casr
О Casr можно узнать больше в репозитории Casr или из гайда.
Вывод команды будет следующим:
[2024-04-10 14:06:14] [INFO] [CASR-AFL] Analyzing 265 files...
[2024-04-10 14:06:14] [INFO] [CASR-AFL] Timeout for target execution is 30 seconds
[2024-04-10 14:06:14] [INFO] [CASR-AFL] Generating CASR reports...
[2024-04-10 14:06:14] [INFO] [CASR-AFL] Using 8 threads
[2024-04-10 14:06:15] [INFO] [CASR-AFL] Progress: 16/265
[2024-04-10 14:06:16] [INFO] [CASR-AFL] Progress: 36/265
...
[2024-04-10 14:06:26] [INFO] [CASR-AFL] Progress: 235/265
[2024-04-10 14:06:27] [INFO] [CASR-AFL] Progress: 256/265
[2024-04-10 14:06:28] [INFO] [CASR-AFL] Deduplicating CASR reports...
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Number of reports before deduplication: 265. Number of reports after deduplication: 8
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Clustering CASR reports...
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Number of clusters: 5
[2024-04-10 14:06:29] [INFO] [CASR-AFL] ==> <cl1>
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Crash: /fuzz/parse_yaml-out/casr/cl1/id:000011,sig:02,src:000333,time:3317,execs:15906,op:havoc,rep:2
[2024-04-10 14:06:29] [INFO] [CASR-AFL] casrep: NOT_EXPLOITABLE: System.InvalidOperationException: /YamlDotNet/YamlDotNet/Core/Parser.cs:312
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Similar crashes: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Cluster summary -> System.InvalidOperationException: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] ==> <cl2>
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Crash: /fuzz/parse_yaml-out/casr/cl2/id:000054,sig:02,src:000993,time:35940,execs:149891,op:havoc,rep:1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] casrep: NOT_EXPLOITABLE: System.ArgumentOutOfRangeException: /YamlDotNet/YamlDotNet/Core/Scanner.cs:2001
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Similar crashes: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Cluster summary -> System.ArgumentOutOfRangeException: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] ==> <cl3>
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Crash: /fuzz/parse_yaml-out/casr/cl3/id:000065,sig:02,src:000835,time:69261,execs:299637,op:havoc,rep:2
[2024-04-10 14:06:29] [INFO] [CASR-AFL] casrep: NOT_EXPLOITABLE: System.ArgumentException: /YamlDotNet/YamlDotNet/Core/TagName.cs:46
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Similar crashes: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Cluster summary -> System.ArgumentException: 1
[2024-04-10 14:06:29] [INFO] [CASR-AFL] ==> <cl4>
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Crash: /fuzz/parse_yaml-out/casr/cl4/id:000000,sig:02,src:000023,time:198,execs:1077,op:havoc,rep:6
[2024-04-10 14:06:29] [INFO] [CASR-AFL] casrep: NOT_EXPLOITABLE: System.ArgumentException: /YamlDotNet/YamlDotNet/Core/TagName.cs:51
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Similar crashes: 2
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Cluster summary -> System.ArgumentException: 2
[2024-04-10 14:06:29] [INFO] [CASR-AFL] ==> <cl5>
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Crash: /fuzz/parse_yaml-out/casr/cl5/id:000000,sig:02,src:000000,time:53,execs:474,op:havoc,rep:2
[2024-04-10 14:06:29] [INFO] [CASR-AFL] casrep: NOT_EXPLOITABLE: System.ArgumentNullException: /YamlDotNet/YamlDotNet/Core/Tokens/TagDirective.cs:81
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Similar crashes: 3
[2024-04-10 14:06:29] [INFO] [CASR-AFL] Cluster summary -> System.ArgumentNullException: 3
[2024-04-10 14:06:29] [INFO] [CASR-AFL] SUMMARY -> System.ArgumentException: 3 System.ArgumentNullException: 3 System.ArgumentOutOfRangeException: 1 System.InvalidOperationException: 1
[2024-04-10 14:06:29] [INFO] Crashes and Casr reports are saved in /fuzz/parse_yaml-out/casr
В результате работы Casr сократил количество крешей с 265 до 8! А затем разбил их на 5 кластеров. Далее рассмотрим отчет работы Casr на одном из крешей (файл .casrep
в выходной директории):
В отчете указаны место ошибки, стектрейс, информация об окружении и многое другое, что будет полезно для анализа ошибок!
В данной статье был показан подход к фаззингу C# кода с помощью инструмента Sydr-Fuzz. Были поддержаны такие этапы, как запуск фаззинга, минимизация корпуса, сбор покрытия и анализ аварийных завершений, а главное, все эти этапы можно быстро и удобно запускать через Sydr-Fuzz!