• XSS.stack #1 – первый литературный журнал от юзеров форума

Fuzzing Структурно-ориентированный фаззинг ядра Linux при помощи LibFuzzer

Blu-ray

(L2) cache
Пользователь
Регистрация
01.06.2024
Сообщения
301
Реакции
274
Гарант сделки
1
Переведено: 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

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.
 
Последнее редактирование модератором:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх