линукс и процессы (компьютерное)
Sep. 1st, 2004 11:23 pmЭта запись будет интересна, боюсь, только тем, кто разбирается в Юниксе и особенно в Линуксе.
На израильской линуксовской рассылке кто-то спросил помощь зала в решении проблемы. Проблема следующая. У меня есть процесс, который бежит с администраторскими привилегиями (как root, короче). Мне нужно сделать так, чтобы с помощью обычных ps/top этот факт нельзя было обнаружить: чтобы они показывали его бегущим под другим юзером. При этом можно предположить, что у меня есть root-доступ к этой системе, но я ограничен в некоторых своих действиях. Я не могу менять ядро, загружать новые модули в ядро, а также менять стандартные запускаемые файлы ps/top и стандартные библиотеки. Вопрос: что ещё я могу сделать?
Проблема сформулирована достаточно расплывчато, т.к. неясно в частности, кого нужно обмануть: человека, который запускает эти ps/top, или какой-нибудь скрипт. Но оставим эту расплывчатость, как она есть, и попытаемся придумать какие-нибудь методы. Способ “сменить PATH данному юзеру, чтобы запускались не стандартные ps/top, а другие” тоже отметём, как слишком тривиальный/очевидный/легкообнаруживаемый. Что остаётся?
Я придумал три штуки:
1) это достаточно серьёзное извращение ;), но можно сменить имя рута на данной системе, т.е. назвать uid 0 другим именем вместо root. Неискушённого юзера это вполне может обмануть. Недостаток: возможно, ломаем кучу скриптов/программ, которые полагаются на то, что uid 0 = root. В принципе, строго говоря, они все дефективны, если на это полагаются, но на практике, думаю, они на каждом шагу, в том числе среди скриптов, управляющих процессом загрузки системы.
Добавить ещё одну запись в /etc/passwd с uid 0 и другим именем не помогает, т.к. ps/top пользуются getpwuid(), а она берёт первую подходящую строку. Заставить все программы видеть одного юзера с uid 0, а ps/top другого, таким способом не выйдет.
2) ставим LD_PRELOAD: либо для данного юзера в его стартовых скриптах (рассчитывая на то, что он в этом не разбирается), либо общесистемно в /etc. В LD_PRELOAD ставим имя небольшой библиотеки, которая перекидывает на себя вызовы функции open(). ps/top читают информацию о процессах из "/proc/[pid]/stat" ; значит, наша фальшивая open() будет проверять, не пытаются ли открыть этот файл. Если пытаются, она копирует его в другое место (в /tmp например), меняя при этом нужное поле в нём, потом открывает его вместо настоящего и возвращает этот дескриптор. Тогда ps/top прочитают как миленькие из этого дескриптора и закроют его.
3) программа, бегущая как root, во время своей работы вызывает mount() и ставит поверх /proc/[my pid]/ копию tmpfs. При этом она держит открытый дескриптор старой директории. Раз в секунду она копирует все файлы и симлинки из старой директории в tmpfs, изменяя при этом файл stat, и маскируя свой настоящий uid. Всё.
Третий способ мне особенно нравится, т.к. не нужно менять никаких настроек юзера, которого обманываем. Не знаю, используют ли этот способ реально какие-нибудь руткиты.
В общем, это всё, что было предложено (идею номер 2) независимо предложили другие участники тоже). Да, есть ещё такая штука, как capabilities, когда программа может бежать не как root, но иметь некоторые рутовские привилегии. Но вроде бы в текущих версиях Линукса она работает плохо, куча багов, и поломаны user-mode утилиты для неё.
Есть ещё что-то существенное, интересно?
На израильской линуксовской рассылке кто-то спросил помощь зала в решении проблемы. Проблема следующая. У меня есть процесс, который бежит с администраторскими привилегиями (как root, короче). Мне нужно сделать так, чтобы с помощью обычных ps/top этот факт нельзя было обнаружить: чтобы они показывали его бегущим под другим юзером. При этом можно предположить, что у меня есть root-доступ к этой системе, но я ограничен в некоторых своих действиях. Я не могу менять ядро, загружать новые модули в ядро, а также менять стандартные запускаемые файлы ps/top и стандартные библиотеки. Вопрос: что ещё я могу сделать?
Проблема сформулирована достаточно расплывчато, т.к. неясно в частности, кого нужно обмануть: человека, который запускает эти ps/top, или какой-нибудь скрипт. Но оставим эту расплывчатость, как она есть, и попытаемся придумать какие-нибудь методы. Способ “сменить PATH данному юзеру, чтобы запускались не стандартные ps/top, а другие” тоже отметём, как слишком тривиальный/очевидный/легкообнаруживаемый. Что остаётся?
Я придумал три штуки:
1) это достаточно серьёзное извращение ;), но можно сменить имя рута на данной системе, т.е. назвать uid 0 другим именем вместо root. Неискушённого юзера это вполне может обмануть. Недостаток: возможно, ломаем кучу скриптов/программ, которые полагаются на то, что uid 0 = root. В принципе, строго говоря, они все дефективны, если на это полагаются, но на практике, думаю, они на каждом шагу, в том числе среди скриптов, управляющих процессом загрузки системы.
Добавить ещё одну запись в /etc/passwd с uid 0 и другим именем не помогает, т.к. ps/top пользуются getpwuid(), а она берёт первую подходящую строку. Заставить все программы видеть одного юзера с uid 0, а ps/top другого, таким способом не выйдет.
2) ставим LD_PRELOAD: либо для данного юзера в его стартовых скриптах (рассчитывая на то, что он в этом не разбирается), либо общесистемно в /etc. В LD_PRELOAD ставим имя небольшой библиотеки, которая перекидывает на себя вызовы функции open(). ps/top читают информацию о процессах из "/proc/[pid]/stat" ; значит, наша фальшивая open() будет проверять, не пытаются ли открыть этот файл. Если пытаются, она копирует его в другое место (в /tmp например), меняя при этом нужное поле в нём, потом открывает его вместо настоящего и возвращает этот дескриптор. Тогда ps/top прочитают как миленькие из этого дескриптора и закроют его.
3) программа, бегущая как root, во время своей работы вызывает mount() и ставит поверх /proc/[my pid]/ копию tmpfs. При этом она держит открытый дескриптор старой директории. Раз в секунду она копирует все файлы и симлинки из старой директории в tmpfs, изменяя при этом файл stat, и маскируя свой настоящий uid. Всё.
Третий способ мне особенно нравится, т.к. не нужно менять никаких настроек юзера, которого обманываем. Не знаю, используют ли этот способ реально какие-нибудь руткиты.
В общем, это всё, что было предложено (идею номер 2) независимо предложили другие участники тоже). Да, есть ещё такая штука, как capabilities, когда программа может бежать не как root, но иметь некоторые рутовские привилегии. Но вроде бы в текущих версиях Линукса она работает плохо, куча багов, и поломаны user-mode утилиты для неё.
Есть ещё что-то существенное, интересно?
no subject
Date: 2004-09-01 01:30 pm (UTC)А ps/top какой uid показывают? Effective?
no subject
Date: 2004-09-01 01:31 pm (UTC)no subject
Date: 2004-09-01 01:44 pm (UTC)no subject
Date: 2004-09-01 01:47 pm (UTC)сделать два /etc/passwd и в open() смотреть, как называется открывающий процесс и, в зависимости от этого, открывать тот или другой /etc/passwd.
no subject
Date: 2004-09-01 01:48 pm (UTC)no subject
Date: 2004-09-01 01:49 pm (UTC)no subject
Date: 2004-09-01 01:56 pm (UTC)no subject
Date: 2004-09-01 02:00 pm (UTC)Хороший вариант, но не действует в случае, если привилегии нужно использовать постоянно и случайный запуск ps с высокой вероятностью на них наткнётся.
no subject
Date: 2004-09-01 02:07 pm (UTC)main()
{
seteuid(500);
while(1)
{
seteuid(0);
unlink("/tmp/test");
open("/tmp/test", O_CREAT, 0644);
seteuid(500);
sleep(1);
}
}
и это работает. Вопрос, конечно, в том, что более заметно. К тому же такая команда
ps -eo uid,ruid,pid,cmd
меня сразу выводит на чистую воду.
И вообще, на такие вопросы в рассылках надо отвечать, что с домашними заданиями и написаниями троянов мы тут не помогаем ;)
no subject
Date: 2004-09-01 02:08 pm (UTC)no subject
Date: 2004-09-01 02:10 pm (UTC)Я помню как применял что-то подобное, когда строил honeypot для одной из гос. компании. Не помню конкретно как сделал,но код для этого не менял, но задача была похожая - когда хаккер вламывается в систему, (хаккер не новайс), он не должен обнаружить процесса. Код менял для скрытного лога, который шёл с последовательного порта на другой компьютер.
no subject
Date: 2004-09-01 02:39 pm (UTC)Пишем простенький фильтра для /proc файловой системы и подгружаем как модуль (или статически запихиваем в ядро). В фильтре перехватываем open и lookup на все /proc/pid/fd. Монтируем этот фильтр при загрузке. Теперь при вызове lookup/open смотрим на current и выясняем, является ли он ps/top. Если да - подменяем виртуальную таблицу inode своей с изменённой реализацией getattr.
Всё.
no subject
Date: 2004-09-01 02:44 pm (UTC)no subject
Date: 2004-09-01 03:20 pm (UTC)Вопрос в том, какие есть syscalls, и как библиотечные функции user mode ими пользуются. syscalls есть такие: setreuid, setuid, setresuid. Переменные у каждого процесса есть такие: euid, ruid (называется просто uid на самом деле), suid (saved uid).
Происхождение функций:
setuid() - SysV/POSIX
setreuid() - BSD
setresuid() - Linux
suid придумали в SysV/POSIX вот для чего. Предположим, я юзер 1 и запускаю setuid-файл юзера 2. Теперь я бегу с ruid=1, euid=2. Я хочу сделать что-то insecure. Для этого я возвращаюсь вначале к своему euid: делаю setuid(1). Теперь я бегу с ruid=1, euid=1. Но теперь я не могу вернуть себе обратно привилегии юзера 2. Если в SysV стоит расширение SAVED_IDS, или если это POSIX, где это стало частью стандарта, то у меня есть suid, который при запуске был поставлен тоже в 2. Когда я делаю setuid(), мне разрешается вернуть себе значение suid, а не только ruid. Это позволяет мне обойти эту проблему. Заметьте, что в SysV ruid я вообще не меняю в принципе. Кроме того, в случае, если "юзер 2" это на самом деле рут, то для меня эта дорога закрыта: когда я делаю setuid(1) и меняю euid с 0 на 1, то suid тоже получает этот 1, специально, чтобы я не мог вернуться к руту. Так решили разработчики POSIXа.
В BSD всё было проще. У них не было (поначалу) никаких suid. Есть seteuid(), который позволяет мне ставить euid во что угодно, если я рут, и в ruid, если я не рут. Есть расширенный вызов setreuid(), который позволяет мне ставит сразу euid и ruid, причём значение каждого должно (для не-рута) быть либо текущим ruid, либо текущим euid. Это позволяет мне поменять местами euid и ruid, а потом поменять обратно (как в Вашей программе); таким образом сбросить рут, а потом вернуть. Ещё в BSD был свой setuid(), но он ставил сразу ruid и euid в одно значение (а потом и suid, когда в BSD его добавили).
Теперь как Линукс с этим справляется. У него есть самый расширенный свой нестандартный setresuid(), который позволяет изменить все три, причём рут может всё, а другие могут каждый из них поставить в любое из трёх старых значений, так что можно делать всякие пермутации. У него есть setreuid(), который, как BSD'шный setreuid(), ставит сразу новые ruid и euid, но! полезная штука: если при этом меняется euid с 0 на что-то другое, он сохраняет euid=0 в suid, и потом с помощью того же setreuid() можно вернуть себе привилегии. Почему это удобно: потому что семантика setreuid() такая же, как у BSD, но при этом можно поставить себе ненулевые как euid, так и ruid, и всё равно смочь потом вернуться к руту с помощью suid. Т.е. это лучше, чем Ваш пример с seteuid(): setreuid() позволяет замаскироваться так, что и ps -eo uid,ruid не поможет. Правда, поможет ps -eo uid,ruid,suid , но надо об этом знать ;)
Наконец, у него есть seteuid(), которого нет на уровне syscalls, он сделан через setresuid() внутри glibc.
Старая программа, написанная для SysV, использует setuid() и будет работать без изменений. Старая программа, написанная для BSD, использует setuid() или setreuid() и будет работать без изменений. Программа, написанная для Линукса, может использовать setreuid(), помня при этом об удобной возможности полностью скинуть рут из euid и ruid, а потом вернуть, или может использовать нестандартный setresuid(), позволяющий больше всех.
Вот, кажется, ничего не упустил.
no subject
Date: 2004-09-01 03:22 pm (UTC)no subject
Date: 2004-09-01 03:23 pm (UTC)страшновато
ps -eo uid,ruid не поможет. Правда, поможет ps -eo uid,ruid,suid , но надо об этом знать ;)
:))
no subject
Date: 2004-09-01 05:22 pm (UTC)Ну итд. Часть из них, впрочем, скопируется, но подставу заметить легко: в procfs у них длина - нуль, а у копии (/proc/$$/stat, например) будет ненулевая. Единственный способ сделать что-то подобное 3) - это написать ядерный модуль со своей overlay file system, (однако не во всех версиях Линукса файловые системы stackable) и смонтировать её поверх procfs, который будет смотреть, на какой файл из procfs пришёл запрос, и либо подделать ответ либо отправить запрос по стеку дальше на обработку. Очень громоздко и легко обнаруживается.
Самый эффективный способ - если программа имеет права суперпользователя - это залезть в /dev/kmem и поковырять таблицу процессов. Если securelevel ядра не равен нулю, то и это бесполезно.
no subject
Date: 2004-09-01 05:40 pm (UTC)А ковыряние таблицы процессов - это зачем? Того же можно добиться вполне стандартными вызовамы setreuid() и сотоварищи. Какая разница, кто изменит current->euid, функция sys_setreuid() или прямой доступ к /dev/kmem?
no subject
Date: 2004-09-02 02:42 am (UTC)no subject
Date: 2004-09-02 02:52 am (UTC)no subject
Date: 2004-09-02 03:09 am (UTC)no subject
Date: 2004-09-02 03:15 am (UTC)no subject
Date: 2004-09-02 07:12 am (UTC)no subject
Date: 2004-09-02 07:12 am (UTC)