Категория: Vulnerability Research / Static Analysis
Уровень: Intermediate
Оригинал: core-jmp.org by Grzegorz Wypych (h0rac)
Инструмент: ByteRay — движок охоты за уязвимостями
Перевод: Aka Tor
Дата: Март 2026
Введение
Статья объясняет как движок ByteRay для охоты за уязвимостями обнаруживает баги безопасности внутри разделяемых библиотек, от которых зависят основные бинарники. Ключевая проблема — идентификация связей между источниками данных и опасными операциями (sinks), когда они пересекают границы разных бинарных файлов.
Уязвимости часто прячутся в коде который разработчики редко проверяют индивидуально. Традиционный статический анализ часто терпит неудачу потому что библиотечный код анализируется изолированно. ByteRay решает эту проблему выполняя межбинарный анализ, трассируя реальные пути выполнения которые распространяют пользовательский ввод в библиотечные функции где происходят небезопасные операции.
1. Демонстрационный случай
Практический пример использует две уязвимые библиотеки, спроектированные для триггера уязвимостей переполнения стека.
1.1 Основной бинарник — main_static.c
Принимает пользовательский ввод через аргументы командной строки или stdin, затем передаёт данные в функции внешних библиотек:
#include <stdio.h>
#include <string.h>
#include "libsink.h"
#include "libmemcpy_overflow.h"
// Source = argv[1] или stdin
int main(int argc, char **argv)
{
char input[256];
if (argc > 1) {
// Источник: argv[1]
const char *user_data = argv[1];
int user_len = strlen(user_data);
// Старый sink (strcpy overflow)
dangerous_sink(user_data);
// Новый sink (memcpy overflow)
memcpy_overflow_sink(user_data, user_len);
} else {
// Источник: stdin
printf("Enter some text: ");
fflush(stdout);
if (fgets(input, sizeof(input), stdin) == NULL) {
return 1;
}
// удаляем newline
input[strcspn(input, "\n")] = '\0';
int user_len = strlen(input);
// Старый sink (strcpy overflow)
dangerous_sink(input);
// Новый sink (memcpy overflow)
memcpy_overflow_sink(input, user_len);
}
return 0;
}
1.2 libsink.c — переполнение через strcpy
#include <string.h>
#include <stdio.h>
#include "libsink.h"
// Sink в библиотеке .so
void __attribute__((noinline)) dangerous_sink(const char *user_data)
{
char buf[16];
// Переполнение
strcpy(buf, user_data);
// Предотвращение оптимизации компилятором
printf("Copied data: %s\n", buf);
}
1.3 libmemcpy_overflow.c — переполнение через контролируемую длину memcpy
#include <stdio.h>
#include <string.h>
// Stack overflow через заражённую длину в memcpy
void __attribute__((noinline))
memcpy_overflow_sink(const char *user_input, int user_size)
{
char local[16];
// БАГ: контролируемая пользователем длина
memcpy(local, user_input, user_size);
// Предотвращение оптимизации
printf("Copied: %s (len=%d)\n", local, user_size);
}
2. Анализ с ByteRay
После компиляции получаем 3 файла, основной бинарник включает обе библиотеки плюс стандартную libc:
readelf -d main_static2-aarch64-linux-gnu Dynamic section at offset 0xfda8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [./libsink-aarch64-linux-gnu.so] 0x0000000000000001 (NEEDED) Shared library: [./libmemcpy_overflow-aarch64-linux-gnu.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
Уязвимости очевидны при просмотре исходного кода, но из дизассемблирования бинарника это значительно сложнее. Если мы посетим вызов первой уязвимой библиотечной функции dangerous_sink, мы увидим что она просто возвращает указатель из .got (GLOBAL OFFSET TABLE). Анализируя только основной бинарник, мы не найдём бага потому что не видим уязвимый дизассемблированный код.
2.1 Запуск анализа
ByteRay выполняет:
- Загрузку и анализ базы данных нескольких бинарников
- Обратную рекурсивную трассировку параметров для определения происхождения данных
- Прямое распространение заражения (taint propagation) для локализации опасных sink’ов
- Разрешение межбиблиотечных вызовов функций через GOT (Global Offset Table)
Запуск с флагом --library=true:
python3 -m vulnhunt tests/analysis/libload/main_static2-aarch64-linux-gnu \
--output-directory=libtest --library=true
Альтернативный вариант с указанием директории библиотек:
python3 -m vulnhunt tests/analysis/libload/main_static-aarch64-linux-gnu \
--output-directory=libtest --lib_dirs=test_libload/lib --library=true
Проверка зависимостей бинарника:
readelf -d main_static2-aarch64-linux-gnu
2.2 Дизассемблирование
2.3 Вывод движка
Полный вывод ByteRay (сокращённо ключевые моменты):
____ _ ____
| __ ) _ _| |_ ___| _ \ __ _ _ _
| _ \| | | | __/ _ \ |_) / _` | | | |
| |_) | |_| | || __/ _ < (_| | |_| |
|____/ \__, |\__\___|_| \_\__,_|\__, |
|___/ |___/
[info] Loaded database: main_static2-aarch64-linux-gnu.bndb
[info] Loaded external library: ./libsink-aarch64-linux-gnu.so
[info] Loaded external library: ./libmemcpy_overflow-aarch64-linux-gnu.so
[debug] Skipping system library libc.so.6
[*] 'main_static2-aarch64-linux-gnu'
Arch: aarch64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[info] stackoverflow module enabled...
[debug] func_name: main, func_addr: 0x400a14, func_sinks: ['strlen', 'printf', 'strlen'],
func_sources: ['fgets']
[debug] func_name: dangerous_sink, func_addr: 0x400634, func_sinks: ['strcpy', 'printf'],
func_sources: []
[debug] func_name: memcpy_overflow_sink, func_addr: 0x400634, func_sinks: ['memcpy', 'printf'],
func_sources: []
[info] Start of analysis: main_static2-aarch64-linux-gnu
-------------------------------------------------------------------------------------
[info] 0x400a20: Tracing var_130#1 = argv#0
[info] Forward recursive param use search...
[info] 0x400a34: Tracing param: x0_2#13 = [x0_1#12 + 8].q @ mem#0
[info] 0x400a48: Tracing param: x0_5#16 = user_data#1
[info] Resolved [dangerous_sink] by address: 0x400860
[info] Found param: x0_2#13 = [x0_1#12 + 8].q @ mem#0
[info] Visit [dangerous_sink] function
[info] Resolved [memcpy_overflow_sink] by address: 0x400850
[info] Found param usage in sign extension operation: x1#4 = zx.q(user_len#1)
[info] Visit [memcpy_overflow_sink] function
[info] Source fgets detected
[info] End of analysis: main_static2-aarch64-linux-gnu
-------------------------------------------------------------------------------------
[info] Start of analysis: libsink-aarch64-linux-gnu.so
-------------------------------------------------------------------------------------
[info] Resolved [strcpy] by address: 0x400530
[info] Sink strcpy detected
[info] Reverse recursive search for: mem#1 = 0x400530(x0#1, x1#1)
[info] Found param: x0#1 = &buf
[info] Possible type stack overflow detected at 0x400648
[info] Variable x1#1 = var_18#1 computed without bounds checking
[info] Destination buffer size: 16 (0x10)
[info] End of analysis: libsink-aarch64-linux-gnu.so
-------------------------------------------------------------------------------------
[info] Start of analysis: libmemcpy_overflow-aarch64-linux-gnu.so
-------------------------------------------------------------------------------------
[info] Resolved [memcpy] by address: 0x400530
[info] Sink memcpy detected
[info] Possible type stack overflow detected at 0x400648
[info] Variable w2#1 = w1#1 computed without bounds checking
[info] Destination buffer size: 16 (0x10)
[info] End of analysis: libmemcpy_overflow-aarch64-linux-gnu.so
-------------------------------------------------------------------------------------
3. Результаты анализа
ByteRay успешно обнаружил обе уязвимости:
- strcpy overflow в
libsink-aarch64-linux-gnu.soпо адресу 0x400648 —dangerous_sink()вызываетstrcpy(buf[16], user_data)без проверки длины - memcpy overflow через заражённый параметр длины в
libmemcpy_overflow-aarch64-linux-gnu.so—memcpy_overflow_sink()использует контролируемый пользователемuser_sizeкак длину дляmemcpyв 16-байтный буфер
Движок проследил поток данных от источников argv[1] и fgets() через вызовы библиотечных функций, идентифицируя что контролируемый пользователем ввод достигает небезопасных операций без валидации.
4. Технический подход
Ключевой инсайт статьи: «Уязвимости часто находятся в коде который разработчики редко проверяют». Изоляция анализа библиотечного кода предотвращает обнаружение уязвимостей, требующих трассировки потока данных через границы бинарников.
ByteRay решает это через:
- Cross-binary taint analysis — трассировка пользовательского ввода (sources) из argv или stdin
- Отслеживание распространения данных через вызовы функций между бинарниками
- Идентификация опасных sink’ов (strcpy, memcpy) без валидации границ
- Маппинг цепочек параметров между бинарниками через GOT/PLT
Заключение
Эта методология раскрывает уязвимости, прячущиеся в часто используемых разделяемых библиотеках — коде, который разработчики редко проверяют индивидуально, делая баги «скрытыми на виду». Межбинарный анализ потока данных является критически важным для полного покрытия поверхности атаки в современных приложениях.
Ссылки
- core-jmp.org — оригинал
- Автор: Grzegorz Wypych (h0rac)
- Инструмент: ByteRay vulnerability hunting engine




