Вооружаемся ASan'ом для поиска 0-day уязвимостей в OpenSource проектах.

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
1714484700674.png



Интро​

В процессе серфинга интернета, я наткнулся случайно на одну вакансию, на позицию vulnerability researcher'а. Всё как обычно, обычная вакансия, за исключением требовался человек, который преимущественно понимает алгоритмы работы AddressSanitizer (ASan), а так же знает технологии DBI (Dynamic Binary Instrumentation, DBI).

Вот частичное описание этой вакансии (не реклама)
1714484729275.png



Если с DBI всё понятно, но ASan? Как можно не знать и не понимать как он работает? ... Многие наверно слышали об этом инструменте, некоторые знают о нём и используют его, а другие и вовсе не знают. Так или иначе сегодня мы будем говорить об Asan'е. Это очень мощный инструмент, который помогает искать ЖИРНЫЕ баги. Сегодня я расскажу, как с помощью Asan'а можно найти 0-day уязвимости в OpenSource проектах, но найдете ли вы уязвимости или нет зависит только от вас. От себя же, я покажу простейший метод, с помощью которого можно искать баги в самых разных проектах. В общем предлагаю сегодня погрузится в тему баг-хантинга, ПОЕХАЛИ!!!

Что такое AddressSanitizer?​

1714484750025.png



Это быстрый динамический анализатор кода, для детектирования ошибок связанных с памятью - во время исполнения, для программ написанных на С\С++.

AddressSanitizer (ASan) разработала компания Google, как инструмент поика уязвимотей повреждения памяти, ASan обнаруживает ошибки в форме неопределенного или подозрительного поведения с помощью компилятора, вставляющего инструментальный код во время компиляции.

Впервые ASan был представлен на конференции USENIX в 2012 году, как более универсальный детектор ошибок в отличие от существующих на тот момент, по скольку большинство из них либо работали медленно, либо обнаруживали ограниченный набор ошибок, либо то и другое...

Вот ссылка на это событие, там же можно посмотреть запись выступления "AddressSanitizer: A Fast Address Sanity Checker", а так же прочитать статью и посмотреть слайды.

Событие

Видео
https://c59951.ssl.cf2.rackcdn.com/atc12/serebryany.mp4

Слайды
https://www.usenix.org/sites/default/files/conference/protected-files/serebryany_atc12_slides.pdf

Статья
https://www.usenix.org/system/files/conference/atc12/atc12-final39.pdf

С момента реализа этого инструмента было обнаружено 300+ уязвимостей в Chromium, только за первые 10 месяцев его использования. И это не считая другие проекты, такие как Firefox, FFmpeg, FreeType, MySQL итд. Поскольку инструмент показал себя эффективным с точки зрения обнаружения багов, его быстро стали интегрировать, добавлять во все компиляторы.

AddressSanitizer теперь является частью LLVM, начиная с версии 3.1, и частью GCC, начиная с версии 4.8. А так же Xcode с версии 7.0. А с 2019 года он был интегрирован в Visual Studio начиная с версии компилятора (MSVC) 16.9, как дополнительный установочный компонент.

Правда в Windows он всё еще бета, по крайне мере для меня.

Какие баги он определяет?​

1714484781326.png


ASan определяет достаточно широкий спектр багов, о чем свидетельствуют многочисленные баг репорты на багтрек лентах.

Примеры репортов


Собственно ASan умеет определять следующие баги.
Stack Buffer Overflow
Stack Buffer Underflow
Dynamic Stack Buffer Overflow
Stack-Use-After-Return
Stack-Use-After-Scope
Global Buffer Overflow
Alloc-Dealloc Mismatch
Allocation size too Big
Calloc parametrs Overflow
Container Overflow
Invalid Allocation Alignment
Memcpy parametr Overlap
Strncat parametr Overlap
New & Delete type Mismatch
Use After Poison
Heap Buffer Overflow
Heap Use-After-Free
Double Free
Initialization Order Bugs
Memory Leaks
Нажмите, чтобы раскрыть...

Из этого списка я выделил самые ЖИРНЫЕ баги, которые может обнаружить ASAN.

CWE-121: Stack-based Buffer Overflow

CWE-122: Heap-based Buffer Overflow

CWE-416: Use After Free

В любом случае при обнаружение других багов из списка в процессе тестирования ПО это всё равно уcпех. Но в первую очередь нас будет интересовать именно эти баги, а пока перейдем к процессу работы самого ASan'a.

Как его использовать?​

1714484863694.png



Если уж говорить совсем простыми словами, то ASan работает по принципу "доступа за пределами границ" (out-of-bounds access). Аналогично с защитой стека - стековой куки, стек канарейкой. Когда в стеке у нас перезаписывается кука, мы знаем, что произошло переполнение. Другими словами, когда у нас что-то выходит за пределы и перезаписывается, ASan это детектит. В асане для это существует red зона и зона карантина. Red zone для отлова OOB, карантин для UAF.

ASan находит внешний доступ к памяти используя специальный аллокатор и используя инструментацию кода, тем самым обнаруживая доступ за пределы границ для стека, кучи и глобальных объектов.

Как я уже сказал выше AddressSanitizer уже встроен во многие компиляторы, и чтобы его задействовать достаточно указать специальный ключик при компиляции программы. Тогда проект соберется с Asan'ом, т.е. будет проинструментирован проверками.

Например у нас есть такой код.
C: Скопировать в буфер обмена
Код:
/*
* stack-buffer-overflow.c
* CWE-121: Stack-based Buffer Overflow
* 
*/

#include <stdio.h>
#include <string.h>

#define BUFSIZE 100
 
int main(int argc, char *argv[])
{
    char array[BUFSIZE];
 
    /* vulnerability */
    strcpy(array, argv[1]);
 
    printf("string: %s\n", array);
 
    return 0;
}

Компилируем

Код: Скопировать в буфер обмена
gcc stack.c -o stack -fsanitize=address

И запускаем с рандомными аргументами

Код: Скопировать в буфер обмена
Код:
./stack 1
./stack 1dasd

Программа не падает, и даже не определяет переполнение буфера в программе. Но если мы повторно запустим программу и передадим ей большую строку.

Код: Скопировать в буфер обмена
./stack AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...

То программа сразу упадет.

1714485086549.png



Всё дело в том, что Asan не анализирует программу, он анализирует исполнение программы. Как видно из скриншота, ASan любезно нам предоставил лог падения со всеми подробностями.

Пользоваться таким инструментом достаточно просто, вызываем (триггерим) баг и получаем лог от Asan'a.

Лог асана кстати можно улучшить, добавив такой ключик как "-g". Чтобы компилятор добавил отладочные символы. А так же для лучшей эффективности свяжем библиотеку асана статически, флагом "-static-libasan".

Код: Скопировать в буфер обмена
gcc stack.c -o stack -fsanitize=address -static-libasan -g

Ниже скриншот улучшенного лога асана. Улучшение заключается в том, что помимо оффсетов, нам указавают еще номер строки в исходном коде где находится сама уязвимость это очень удобно.

1714485140967.png



Еще пару примеров...

heap buffer overflow
C: Скопировать в буфер обмена
Код:
/*
* heap-buffer-overflow.c
* CWE-122: Heap-based Buffer Overflow
* 
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 100
 
int main(int argc, char *argv[])
{
    char *ptr = malloc(BUFSIZE);
 
    /* vulnerability */
    strcpy(ptr, argv[1]);
 
    printf("string: %s\n", ptr);
 
    free(ptr);
 
    return 0;
}

1714485197834.png



use after free
C: Скопировать в буфер обмена
Код:
/*
* use-after-free.c
* CWE-416: Use After Free
* 
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, const char *argv[])
{
    char *array = malloc(100);
    free(array);
 
    /* vulnerability */
    strcpy(array, argv[1]);
 
    printf("string is: %s\n", array);
 
    return 0;
}

1714485285286.png



Объединим два примера выше.

C: Скопировать в буфер обмена
Код:
/*
* example_vuln.c
* heap overflow & use-after-free
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
    char *array = malloc(100);
 
    /* vulnerability - heap overflow */
    strcpy(array, argv[1]);
    printf("string: %s\n", array);
 
    free(array);
    printf("Input: ");
    /* vulnerability - use after free */
    scanf("%s", array);
 
    printf("string is: %s\n", array);
 
    return 0;
}

Тестируем

1714485379088.png



Как видно из этого скриншота мы обнаружила переполние буфера в куче, но не обнаружили UAF.

Протестируем еще раз

1714485407722.png



Тут мы успешно обнаружили использование памяти после освобождения, но не обнаружили heap buffer overflow.

Этот пример наглядно демонстрирует, то о чем я говорил ранее. Asan не анализирует программу, он анализирует исполнение программы. Другими словами если мы не вызовим баг мы не узнаем о существование уязвимости. В иделе асан должен применяться с фаззерами на основе покрытия (сoverage-guided). Чтобы как раз и находит подобные случаи.

Иногда при ручном тестировании или фаззинге нужно оптимизировать работу приложения, чтобы ускорить его на определенном участке. Как это сделать? Ведь ASan замедляет работу приложения в 2 раза. Всё на самом деле очень просто, для этого были добавлены опции игнорирования определенных функций, которые не нужно инструментировать.

Ниже пример макроса, при добавление которого можно указать какую функцию игнорировать при инструментировании кода.

C: Скопировать в буфер обмена
Код:
#if defined(__clang__) || defined (__GNUC__)
# define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#else
# define ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif
...
ATTRIBUTE_NO_SANITIZE_ADDRESS
void ThisFunctionWillNotBeInstrumented() {...

Посмотрим как это выглядит на практике. Напишем простую программу с уязвимостью.

C: Скопировать в буфер обмена
Код:
#if defined(__clang__) || defined (__GNUC__)
# define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#else
# define ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif

#include <stdio.h>
#include <string.h>

int vuln_func(char *s);

int main(void)
{
    char *param = "AAAAAAAAAAAAAAAAAAAA";
    int result=0;

    result = vuln_func(param); /* no instrumentation */
    printf("Len: %d\n", result);

    return 0;
}


//ATTRIBUTE_NO_SANITIZE_ADDRESS
int vuln_func(char *s)
{
    int len=0;
    char buff[40];

    strcpy(buff, s);
    printf("Buffer: %s\n", buff);

    len = strlen(buff);
    return len;
}

1714485536608.png



В примере 1 - обычное выполнение с инструментированием кода, но без длинной строки.
В примере 2 - передается большая строка и игнорируется уязвимая функция (которая не была инструментирована)
В примере 3 - так же передается большая строка, но происходит инструментирование всего кода.

В примере 2 видно, что мы можем отключать инструментирование определенных фунций, это выгодно, когда вы разобрали какую-то определенную функцию и вам известно, что в данной функции уж точно не будет багов и накладные вычислительные расходы вам не нужны при тестах. Или же чтобы выйграть в борьбе за ресурсы, мы можем тестировать ПО частями. Так как инструментированный код ASan'a в среднем съедает в 2 раза больше ресурсов.

Так же можно игнорировать сразу целый список функций не копаясь в сорцах, правда этот вариант доступен только для clang.

Код: Скопировать в буфер обмена
-fsanitize-blacklist=my_ignores.txt

Список команд GCC можно посмотреть тут.
gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

Список команда для Clang
clang.llvm.org/docs/UsersManual.html#controlling-code-generation
clang.llvm.org/docs/ClangCommandLineReference.html#compilation-options

MSVC
learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170#command-prompt
learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170#ide-msbuild
learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170#ide-cmake

XCode
developer.apple.com/documentation/xcode/diagnosing-memory-thread-and-crash-issues-early

1001 зиродей​

1714485588759.png



Теперь я думаю самое время рассмотреть реальные кейсы... Пока коллеги играют с веб запросами через curl, мы будем играть с исходниками и асаном. И так, чтобы протестировать ПО на предмет багов идем на гитхаб или любой другой хостинг проектов и выбираем любой проект который написан на С\С++. Собираем его из исходников с асаном и пытаемся в ручном режиме уронить программу. На вход программе подаём некорректные данные. Т.е в любом месте где программа обрабатывает данные мы пихаем на вход большую строку типа AAAAAAAAAAAAAAAAAA..... Короче говоря любыми всевозможными способами пихаем в программу некорректные данные.

Тут стоит сказать вот что... По хорошему мы должны скомбинировать какой-либо фаззер и наш асан для теста ПО. Но так, как эта статья посвящена конкретно инструменту AddressSaninizer мы исключим использование фаззеров =/

В таком случае шанс на успех у нас уменьшается, так как мы не сможешь покрыть всё, используя ручной ввод.

От вас требуетсялишь навык сборки и компиляции программ, разбираться во всяких confugure, make, cmake править мейки и добавлять флаги -fsanitize=address -static-libasan -g

Ниже я приведу несколько примитивных примеров, по некоторым соображениям... Которые должны дать вам пищу для размышления, о том, что вполне можно Скачать

View hidden content is available for registered users!
 
Сверху Снизу