Переведено: Blu-ray
Специально для: xss.pro
Источник: https://r00tkitsmm.github.io/fuzzing/2024/03/27/libffuzzerkernel.html
Всем привет! Я очень рад рассказать вам о моем сегодняшнем эксперименте. Я решил поэкспериментировать с KCOV и посмотреть, как я могу подключить его к LibFuzzer и загрузить ядро, не тратя слишком много ресурсов на создание корневой файловой системы.
Прежде всего, почему бы просто не использовать syzkaller? Да потому что, почему бы и нет? Это может охватить больше состояний пространства.
Немного погуглив, я нашел очень интересную запись в блоге cloudflare
Они ответили на мой второй вопрос о том, как загрузить недавно созданное ядро Linux с текущей корневой файловой системой, с помощью virtme, так что, по сути, Virtme - это набор простых инструментов для запуска виртуализированного ядра Linux, которое использует основной дистрибутив Linux или простую rootfs вместо целого образа диска. Virtue очень мал, прост в использовании и упрощает тестирование изменений в ядре. Я также позаимствовал у них несколько скриптов.
Итак, давайте начнем: клонируем virtme и ядро Linux
вы должны включить kcov для всех целевых объектов с помощью KCOV_INSTRUMENT_ALL или определенного makefile. Включите KCOV во всех подкаталогах “fs” :
затем соберите ядро Linux с помощью KCOM и KASAN и некоторых других флагов, необходимых virtme
Чтобы обеспечить покрытие кода ядра при помощи libfuzzer, мы можем использовать __libfuzzer_extra_counters
хороший пример вы можете увидеть в syzkaller и его документации на веб-сайте kernel
Почти каждый вектор атаки ядра - это Stateful API. Вы не можете просто передать ему необработанный буфер. Мы должны использовать фаззинг с учетом структуры, и я решил использовать libprotobuf-mutator, который обладает очень мощными инструментами
Существует масса ресурсов об использовании libprotobuf-mutator. Я не смогу объяснить лучше, чем в оригинальной документации по фаззингу от Гугл:
Но проще будет клонировать репозиторий и заменить следующий код этим файлом
вы можете закомментировать другие файлы в CMakeLists.txt, потому что мы хотим изменить файл .proto
Для тестирования вы можете использовать SockFuzzer для фаззинга сетевого стека.
Начинается интригующая фаза. Если kasan запаникует, libfuzzer об этом не узнает и удалит семпл. Поэтому, чтобы сохранить семпл, вызывающую сбой, мы должны сообщить libfuzzer о панике ядра.
Первоначально я исследовал альтернативные методы уведомления фаззера о возникновении паники. Однако я решил имитировать SIGSEGV и отправлять сигнал в libfuzzer всякий раз, когда в ядре возникает паника kasan. Получив этот сигнал, libfuzzer сохранит семпл и завершит работу. таким образом, нам придется модифицировать ядро Linux и собрать его заново. добавьте функцию send_sigsegv_to_process в print_error_description в файле /mm/kasan/report.c.
Убедитесь, что
Скопируйте файл libprotobuf в testfuzz. Теперь вы можете загрузить новое ядро и запустить с ним фаззер
Следующий шаг включает в себя включение API и системных вызовов в proto-файл и добавления их в DEFINE_PROTO_FUZZER. Это позволит выполнять настройку дополнительных подсистем в ядре Linux.
Специально для: xss.pro
Источник: https://r00tkitsmm.github.io/fuzzing/2024/03/27/libffuzzerkernel.html
Всем привет! Я очень рад рассказать вам о моем сегодняшнем эксперименте. Я решил поэкспериментировать с KCOV и посмотреть, как я могу подключить его к LibFuzzer и загрузить ядро, не тратя слишком много ресурсов на создание корневой файловой системы.
Прежде всего, почему бы просто не использовать syzkaller? Да потому что, почему бы и нет? Это может охватить больше состояний пространства.
Немного погуглив, я нашел очень интересную запись в блоге cloudflare
Они ответили на мой второй вопрос о том, как загрузить недавно созданное ядро Linux с текущей корневой файловой системой, с помощью virtme, так что, по сути, Virtme - это набор простых инструментов для запуска виртуализированного ядра Linux, которое использует основной дистрибутив Linux или простую rootfs вместо целого образа диска. Virtue очень мал, прост в использовании и упрощает тестирование изменений в ядре. Я также позаимствовал у них несколько скриптов.
Итак, давайте начнем: клонируем virtme и ядро Linux
Bash:
git clone --depth 1 https://github.com/torvalds/linux.git
git clone --depth 1 https://github.com/amluto/virtme.git
cd linux
вы должны включить kcov для всех целевых объектов с помощью KCOV_INSTRUMENT_ALL или определенного makefile. Включите KCOV во всех подкаталогах “fs” :
Bash:
find "fs" -name Makefile \
| xargs -L1 -I {} bash -c 'echo "KCOV_INSTRUMENT := y" >> {}'
затем соберите ядро Linux с помощью KCOM и KASAN и некоторых других флагов, необходимых virtme
Bash:
../virtme/virtme-configkernel --defconfig
./scripts/config \
-e KCOV \
-d KCOV_INSTRUMENT_ALL \
-e KCOV_ENABLE_COMPARISONS
./scripts/config \
-e DEBUG_FS -e DEBUG_INFO \
-e KALLSYMS -e KALLSYMS_ALL \
-e NAMESPACES -e UTS_NS -e IPC_NS -e PID_NS -e NET_NS -e USER_NS \
-e CGROUP_PIDS -e MEMCG -e CONFIGFS_FS -e SECURITYFS \
-e KASAN -e KASAN_INLINE -e WARNING \
-e FAULT_INJECTION -e FAULT_INJECTION_DEBUG_FS \
-e FAILSLAB -e FAIL_PAGE_ALLOC \
-e FAIL_MAKE_REQUEST -e FAIL_IO_TIMEOUT -e FAIL_FUTEX \
-e LOCKDEP -e PROVE_LOCKING \
-e DEBUG_ATOMIC_SLEEP \
-e PROVE_RCU -e DEBUG_VM \
-e REFCOUNT_FULL -e FORTIFY_SOURCE \
-e HARDENED_USERCOPY -e LOCKUP_DETECTOR \
-e SOFTLOCKUP_DETECTOR -e HARDLOCKUP_DETECTOR \
-e BOOTPARAM_HARDLOCKUP_PANIC \
-e DETECT_HUNG_TASK -e WQ_WATCHDOG \
--set-val DEFAULT_HUNG_TASK_TIMEOUT 140 \
--set-val RCU_CPU_STALL_TIMEOUT 100 \
-e UBSAN \
-d RANDOMIZE_BASE
Чтобы обеспечить покрытие кода ядра при помощи libfuzzer, мы можем использовать __libfuzzer_extra_counters
хороший пример вы можете увидеть в syzkaller и его документации на веб-сайте kernel
Почти каждый вектор атаки ядра - это Stateful API. Вы не можете просто передать ему необработанный буфер. Мы должны использовать фаззинг с учетом структуры, и я решил использовать libprotobuf-mutator, который обладает очень мощными инструментами
Существует масса ресурсов об использовании libprotobuf-mutator. Я не смогу объяснить лучше, чем в оригинальной документации по фаззингу от Гугл:
Протокольные буферы в качестве промежуточных форматов Protobufs предоставляют удобный способ сериализации структурированных данных, а LPM предоставляет простой способ изменения protobufs для фаззинга с учетом структуры. Таким образом, возникает соблазн использовать libFuzzer+LPM для API, которые используют структурированные данные, отличные от protobufs.
Но проще будет клонировать репозиторий и заменить следующий код этим файлом
Bash:
git clone https://github.com/google/libprotobuf-mutator.git
вы можете закомментировать другие файлы в CMakeLists.txt, потому что мы хотим изменить файл .proto
C-подобный:
#include <cmath>
#include <iostream>
#include "examples/libfuzzer/libfuzzer_example.pb.h"
#include "port/protobuf.h"
#include "src/libfuzzer/libfuzzer_macro.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/bpf.h>
#include <memory.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
void fail(const char* msg, ...);
void kcov_start();
void kcov_stop();
#define KCOV_COVER_SIZE (256 << 10)
#define KCOV_TRACE_PC 0
#define KCOV_INIT_TRACE64 _IOR('c', 1, uint64_t)
#define KCOV_ENABLE _IO('c', 100)
__attribute__((section("__libfuzzer_extra_counters"))) unsigned char libfuzzer_coverage[32 << 10];
uint64_t* kcov_data;
int kcov;
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
kcov = open("/sys/kernel/debug/kcov", O_RDWR);
if (kcov <mark>-1)
fail("open of /sys/kernel/debug/kcov failed");
if (ioctl(kcov, KCOV_INIT_TRACE64, KCOV_COVER_SIZE))
fail("init trace write failed");
kcov_data = (uint64_t*)mmap(NULL, KCOV_COVER_SIZE * sizeof(kcov_data[0]),
PROT_READ | PROT_WRITE, MAP_SHARED, kcov, 0);
if (kcov_data <mark>MAP_FAILED)
fail("mmap failed");
if (ioctl(kcov, KCOV_ENABLE, KCOV_TRACE_PC))
fail("enable write trace failed");
//close(kcov); // where to close this?
return 0;
}
void kcov_start()
{
__atomic_store_n(&kcov_data[0], 0, __ATOMIC_RELAXED);
if (ioctl(kcov, KCOV_ENABLE, KCOV_TRACE_PC))
perror("ioctl"), exit(1);
}
void kcov_stop()
{
uint64_t ncov = __atomic_load_n(&kcov_data[0], __ATOMIC_RELAXED);
if (ncov >= KCOV_COVER_SIZE)
fail("too much cover: %llu", ncov);
for (uint64_t i = 0; i < ncov; i++) {
uint64_t pc = __atomic_load_n(&kcov_data[i + 1], __ATOMIC_RELAXED);
libfuzzer_coverage[pc % sizeof(libfuzzer_coverage)]++;
}
if (ioctl(kcov, KCOV_DISABLE, 0))
perror("ioctl"), exit(1);
}
void fail(const char* msg, ...)
{
int e = errno;
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, " (errno %d)\n", e);
_exit(1);
}
DEFINE_PROTO_FUZZER(const libfuzzer_example::Msg& message) {
protobuf_mutator::protobuf::FileDescriptorProto file;
kcov_start();
// your logic should be here:
// std::cerr << message.DebugString() << "\n";
// Emulate a bug.
//int fd = syscall(SYS_open, "example.txt", 4, message.sample_int());
int fd = syscall(SYS_open, "example.txt", 4, 0x4141);
syscall(SYS_close,fd);
kcov_stop();
}
Для тестирования вы можете использовать SockFuzzer для фаззинга сетевого стека.
Начинается интригующая фаза. Если kasan запаникует, libfuzzer об этом не узнает и удалит семпл. Поэтому, чтобы сохранить семпл, вызывающую сбой, мы должны сообщить libfuzzer о панике ядра.
Первоначально я исследовал альтернативные методы уведомления фаззера о возникновении паники. Однако я решил имитировать SIGSEGV и отправлять сигнал в libfuzzer всякий раз, когда в ядре возникает паника kasan. Получив этот сигнал, libfuzzer сохранит семпл и завершит работу. таким образом, нам придется модифицировать ядро Linux и собрать его заново. добавьте функцию send_sigsegv_to_process в print_error_description в файле /mm/kasan/report.c.
Убедитесь, что
- kernel.panic_on_warn=0
- kernel.panic_on_oops=0
C-подобный:
#include <linux/sched/signal.h>
#include <linux/sched.h>
#include <asm/siginfo.h>
void send_sigsegv_to_process(void* access_addr );
void send_sigsegv_to_process(void* access_addr ) {
kernel_siginfo_t info;
memset(&info, 0, sizeof(kernel_siginfo_t));
info.si_signo = SIGSEGV; // Signal type
info.si_pid = current->pid; // Process ID to send the signal to
info.si_code = SEGV_MAPERR; // Signal code for a memory access error
info.si_addr = access_addr; // Address that caused the fault
send_sig_info(SIGSEGV, &info, current);
}
static void print_error_description(struct kasan_report_info *info)
{
send_sigsegv_to_process((void*)info->access_addr);
pr_err("BUG: KASAN: %s in %pS\n", info->bug_type, (void *)info->ip);
if (info->type != KASAN_REPORT_ACCESS) {
pr_err("Free of addr %px by task %s/%d\n",
info->access_addr, current->comm, task_pid_nr(current));
return;
}
if (info->access_size)
pr_err("%s of size %zu at addr %px by task %s/%d\n",
info->is_write ? "Write" : "Read", info->access_size,
info->access_addr, current->comm, task_pid_nr(current));
else
pr_err("%s at addr %px by task %s/%d\n",
info->is_write ? "Write" : "Read",
info->access_addr, current->comm, task_pid_nr(current));
}
Скопируйте файл libprotobuf в testfuzz. Теперь вы можете загрузить новое ядро и запустить с ним фаззер
Bash:
cd linux
../virtme/virtme-run --kimg arch/x86/boot/bzImage --rwdir ../testfuzz/ --qemu-opts -m 2G -smp 2 -enable-kvm
Следующий шаг включает в себя включение API и системных вызовов в proto-файл и добавления их в DEFINE_PROTO_FUZZER. Это позволит выполнять настройку дополнительных подсистем в ядре Linux.
Последнее редактирование модератором: