What is ld preload linux

Замещение функций через LD_PRELOAD

Дисклаймер: все описаное в статье является моим экспериментом и сделано в ознокомительных целяхa, автор не несет ответсвенности за ваши действия.

Ввведение

Недавно я озаботился вопросами информационой безопасности и в целях освоения информации решил сделать простенький руткит для реализации reverse shell. В статье будет описано что такое reverse shell и как его можно вызвать через перезагрузку функций в стандартной библиотеке в ОС Linux.

Что такое reverse shell

Reverse shell или Бэдконнект это схема взаимодействия с удаленным компьютером, при которой этот самый (атакуемый) компьютер будет клиентом и, соответвенно, будет сам устанавливать соедиенеие с нашим сервером. Основная опасность в том что если наш сервер будет поднят например на 80 порту, то это позволит с определнной долей вероятности обойти плохо настроенный файервол.

Самый простой способ реализовать бэдконнект это использование nc и bash .

Для этого на сервере (компьютер атакующего) нужно вылонить:

На клиентской машине (атакуемый компьютер) необходимо выполнить:

Это команда делает следующее: запускает интерактивный shell ( /bin/bash -i ) далее происходит редирект stdout и stderr ( >& ) в сетевое tcp соединение с SERVER-IP:PORT последняя команда ( 0>&1 ) перенаправляет файловый дескриптор для ввода stdin в stdout, который уже работает с удалленым сервером.

Надо отметить что если есть необходимость закрыть окно поле запуска этой команды нужно ее чуть чуть модернизировать:

В итоге можно увидеть следующее:

Что такое LD_PRELOAD

LD_PRELOAD — это переменная окружения, которая позволяет загрузить любую библиотеку раньше любой другой включая библиотеку libc.

загрузит сначала заданную библиотеку, а потом запустит команду top.

Получение списка системных вызов у программы в Linux.

Чтобы получить информацию о системых вызовах необходимо использовать утилиту strace или ltrace .

Например давайте посмотрим какие функции вызывает утилита date :

В результате будет видно что-то типа:

Создание бибилиотеки с reverse shell

После ознакомления с необходимыми вводными можно сформировать следующую задачу: нужно создать библиотеку которая перед вызовом основной версии запустит бэдконнект.

Создание функции c инъекцией

Если взять пример из выше, можно увидеть что date использует функцию strrchr , ее и попробуем расширить.

Для этого необходимо написать небольшую библиотеку на C:

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

Далее необходимо скомпилировать библиотеку следующей командой:

В результате после компиляции можно увидеть следующее:

После того как заготовка инъекции готова, осталось перед вызовом оригинальной функции добавить вызов бэдконнекта.

Для этого необходимо написать небольшую функцию на С, которая будет его вызывать:

Что делает данная функция не сложно понять из комментариев. Теперь добавим ее в нашу инъекцию, заменив вызов printf , на вызов reverse_shell . Полный код:

Если скомпелировать эту библиотеку и запусть, то получится следующее:

Реверс шел запустился, но как видно родительская функция у нас не вызвалась, и в глаза резко бросается не стандарнтое поведение. Чтобы этого избежать, соедиенеие с сервером для реверс шела необходмио запустить в отдельном потоке с помощью fork :

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

Читайте также:  Установить rdp windows server

Теперь после запуска можно увидеть следующее:

Тут видно что реверс шел запустился (справа), а исходная программа отработала как и ожидалось.

Исходный код библиотеки здесь

Инъекция с использованием Go

В предыдущем разделе я показал “классический” пример инъекции сделаной на С, но это не означает что это нельзя сделать на другом языке.

В данном разделе я покажу как сделать аналогичную функцию на Go. Для этого понадобится библиотека dl для работы с динамическими библотеками в Go.

В данном коде я расставил подробные коментарии, чтобы было понятней на какой стадии что происходит.

Для открытия бэдконнекта необходимо просто заменить код startReverseShell на следующий:

В этой функции, как и раньше, открывается сетевое соединение и дальше ввод/вывод перенаправляется в него.

Оличие от реализации на С, в том что тут мы не используем потоки ОС и поэтому после завершения родительской программы соединение с сервером будет закрыто, так как горутина завершится вместе с программой.

Полный код библиотеки здесь

Команда для компиляции:

Заключение

Выше я описал самый примитивный пример вызова реверс шела через инъекцию с помощью LD_PRELOAD, в итоге задача оказалось довольно простой в реализации и получен позитивный опыт. Для написания подобного ПО также не нужно сильно знать С, а использовать более высокоуровневые языки.

Еще раз отмечу что это был образовательный проект и за применение полученных знаний ответсвенность будет полностью на вас.

Источник

Практическое применение LD_PRELOAD или замещение функций в Linux

Всем привет!
В 2010 году, shoumikhin написал замечательную статью Перенаправление функций в разделяемых ELF-библиотеках. Та статья очень грамотно написана, полная, но она описывает более харкордный способ замещения функций. В этой статье, мы будем использовать стандартную возможность динамического линкера — переменную окружения LD_PRELOAD, которая может загрузить вашу библиотеку до загрузки остальных.

Как это работает?

Да очень просто — линкер загружает вашу библиотеку с вашими «стандартными» функциями первой, а кто первый — того и тапки. А вы из своей библиотеки можете загрузить уже реальную, и «проксировать» вызовы, попутно делая что вам угодно.

Реальный Use-Case #1: Блокируем mimeinfo.cache в Opera

Мне очень нравится браузер Opera. А еще я использую KDE. Opera не очень уважает приоритеты приложений KDE, и, зачастую, так и норовит открыть скачанный ZIP-архив в mcomix, PDF в imgur-uploader, в общем, вы уловили суть. Однако, если ей запретить читать файл mimeinfo.cache, то она все будет открывать через «kioclient exec», а он-то уж лучше знает, в чем я хочу открыть тот или иной файл.

Чем может приложение открывать файл? На ум приходят две функции: fopen и open. В моем случае, opera использовала 64-битный аналог fopen — fopen64. Определить это можно, воспользовавшись утилитой ltrace, или просто посмотрев таблицу импорта утилитой objdump.

Что нам нужно для написания библиотеки? Первым делом, нужно составить прототип оригинальной функции.
Судя по man fopen, прототип у этой функции следующий:
И возвращает она указатель на FILE, либо NULL, если файл невозможно открыть. Отлично, пишем код:

Как видите, все просто: объявляем функцию fopen64, загружаем «следующую» (оригинальную), по отношению к нашей, функцию, и проверяем, не открываем ли мы файл «mimeinfo.cache». Компилируем ее следующей командой:

И запускаем opera:
И видим:
Успех!

Реальный Use-Case #2: Превращаем файл в сокет

Есть у меня проприетарное приложение, которое использует прямой доступ к принтеру (файл устройства /dev/usb/lp0). Захотел я написать для него свой сервер в целях отладки. Что возвращает open()? Файловый дескриптор. Что возвращает socket()? Такой же файловый дескриптор, на котором совершенно так же работают read() и write(). Приступаем:

Не совсем реальный Use-Case #3: Перехват C++-методов

С C++ все немного иначе. Скажем, есть у нас класс:

Читайте также:  Windows phone 8s от htc его характеристика

Но функции не будут называться «Testclass::getvar» и «Testclass::setvar» в результирующем файле. Чтобы узнать названия функций, достаточно посмотреть таблицу экспорта:
Это называется name mangling.
Тут есть два выхода: либо сделать библиотеку-перехватчик на C++, описав класс так же, каким он был в оригинале, но, в этом случае, у вас, с большой вероятностью, будут проблемы с доступом к конкретному инстансу класса, либо же сделать библиотеку на C, назвав функцию так, как она экспортируется, в таком случае, первым параметром вам передастся указатель на инстанс:

Вот, собственно, и все, о чем хотелось рассказать. Надеюсь, это будет кому-то полезно.

Источник

В поисках LD_PRELOAD

Эта заметка была написана в 2014-м году, но я как раз попал под репрессии на хабре и она не увидела свет. За время бана я про неё забыл, а сейчас нашёл в черновиках. Думал было удалить, но авось кому пригодится.

В общем, небольшое пятничное админское чтиво на тему поиска «включенного» LD_PRELOAD.

1. Небольшое отступление для тех, кто не знаком с замещением функций

Остальным можно сразу переходить к п.2.

Начнём с классического примера:

Компилируем без каких-либо флагов:

И, ожидаемо, получаем 5 случайных чисел меньше 100:

Но представим, что у нас нет исходного кода программы, а поведение изменить нужно.

Создадим свою библиотеку с собственным прототипом функции, например:

И теперь наш случайный выбор вполне предсказуем:

Этот трюк выглядит ещё более впечатляющим, если мы сначала экспортируем нашу библиотеку через

или предварительно выполним

а затем запустим программу в обычном режиме. Мы не изменили ни строчки в коде самой программы, но её поведение теперь зависит от крошечной функции в нашей библиотеке. Более того, на момент написания программы фальшивый rand даже не существовал.

Что же заставило нашу программу использовать поддельный rand? Разберем по шагам.
Когда приложение запускается, загружаются определенные библиотеки, которые содержат функции необходимые программе. Мы можем посмотреть их используя ldd:

Этот список может быть различен в зависимости от версии OS, но там обязательно должен быть файл libc.so. Именно эта библиотека обеспечивает системные вызовы и основные функции, такие как open, malloc, printf и т. д. Наш rand также входит в их число. Убедимся в этом:

Посмотрим, не изменится ли набор библиотек при использовании LD_PRELOAD

Оказывается установленная переменная LD_PRELOAD заставляет загрузиться нашу ld_rand.so даже, не смотря на то, что программа сама её не требует. И, так как наша функция «rand» загружается раньше чем rand от libc.so, то она и правит балом.

Ok, заменить родную функцию нам удалось, но как бы сделать так, чтобы и её функционал сохранился и некие действия добавились. Модифицируем наш рандом:

Здесь в качестве нашей «добавки» мы лишь печатаем одну строку текста, после чего создаём указатель на исходную функция rand. Для получения адреса этой функции нам потребуется dlsym — это функция из библиотеки libdl, которая найдет наш rand в стеке динамических библиотек. После чего мы вызовем эту функцию и возвратим её значение. Соответственно, нам понадобится добавить «-ldl» при сборке:

И наша программа использует «родной» rand, предварительно исполнив некие непотребные действия.

2. Муки поиска

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

Далее парами пойдут решения для обнаружения и их опровержение.

2.1. Начнём с простого

Как говорилось ранее, указать загружаемую библиотеку можно с помощью переменной LD_PRELOAD или прописав её в файле /etc/ld.so.preload. Создадим два простейших детектора.

Первый — для проверки установленной переменной окружения:

Второй — для проверки открытия файла:

Читайте также:  Astra linux special edition совместимость

Здесь и далее [+] указывает на успешное обнаружение.
Соответственно, [-] означает обход детектирования.

Насколько же действенен такой обнаружитель? Сначала займёмся переменной окружения:

Аналогично избавляемся и от проверки open:

Да, здесь могут быть использованы другие способы доступа к файлу, такие как, open64, stat и т.д., но, по сути, для их обмана необходимы те же 5-10 строк кода.

2.2. Двигаемся дальше

Выше мы использовали getenv() для получения значения LD_PRELOAD, но есть же и более «низкоуровневый» способ добраться до ENV-переменных. Не будем использовать промежуточные функции, а обратимся к массиву **environ, в котором хранится копия окружения:

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

Казалось бы на этом проблема решена? Всё ещё только начинается.

После того как программа запущена, значение переменной LD_PRELOAD в памяти уже не нужно взломщикам, то есть можно считать её и удалить до выполнения каких-либо инструкций. Конечно, править массив в памяти, как минимум, плохой стиль программирования, но разве это может остановить того, кто и так не особо желает нам добра?

Для этого нам понадобится создать свою фейковую функцию init(), в которой перехватим установленный LD_PRELOAD и передать её нашему компоновщику:

2.3. /proc/self/

Однако, память — это не последнее место, где можно обнаружить подмену LD_PRELOAD, есть же ещё и /proc/. Начнём с очевидного /proc//environ.

На самом деле есть универсальное решение для undetect’а **environ и /proc/self/environ. Проблема заключается в «неправильном» поведении unsetenv(env).

Но представим, что мы его не нашли и /proc/self/environ содержит «проблемные» данные.

Сначала попробуем с нашей предыдущей «маскировкой»:

cat использует для открытия файла всё тот же open(), поэтому решение сходно с тем, что уже делалось в п.2.1, но теперь мы создаем временный файл, куда копируем значения истинной памяти без строк содержащих LD_PRELOAD.

И этот этап пройден:

Следующее очевидное место — /proc/self/maps. Задерживаться на нем нет смысла. Решение абсолютно идентичное предыдущему: копируем данные из файла за вычетом строк между libc.so и ld.so.

2.4. Вариант от Chokepoint

Это решение мне особенно понравилось своей простотой. Сравниваем адреса функций, загружаемых непосредственно из libc, и «NEXT»-адреса.

Загружаем библиотеку с перехватом «open()» и проверяем:

Опровержение оказалось ещё проще:

2.5. Syscalls

Казалось бы на этом всё, но ещё побарахтаемся. Если мы направим системный вызов напрямую к ядру, то это позволит обойти весь процесс перехвата. Решение ниже, разумеется, архитектурозависимое (x86_64). Попробуем реализовать для обнаружения открытия ld.so.preload.

И данная задачка имеет решение. Выдержка из man‘а:

ptrace — это средство, позволяющее родительскому процессу наблюдать и контролировать протекание другого процесса, просматривать и изменять его данные и регистры. Обычно эта функция используется для создания точек прерывания в программе отладки и отслеживания системных вызовов.

Родительский процесс может начать трассировку, сначала вызвав функцию fork(2), а затем получившийся дочерний процесс может выполнить PTRACE_TRACEME, за которым (обычно) следует выполнение exec(3). С другой стороны, родительский процесс может начать отладку существующего процесса при помощи PTRACE_ATTACH.

При трассировке дочерний процесс останавливается каждый раз при получении сигнала, даже если этот сигнал игнорируется. (Исключением является SIGKILL, работающий обычным образом.) Родительский процесс будет уведомлен об этом при вызове wait(2), после которого он может просматривать и изменять содержимое дочернего процесса до его запуска. После этого родительский процесс разрешает дочернему продолжать работу, в некоторых случаях игнорируя посылаемый ему сигнал или отправляя вместо этого другой сигнал).

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

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

Источник

Оцените статью