сокрытие процессов в rootkits

С появлением FU, мир руткитов перешел на следующий уровень - с реализации перехвата системных вызов на сокрытие своего присутствия. Новые возможности средств нападения потребовали разработки новой защиты. Современные алгоритмы, используемые rootkit-детекторами, такими как BlackLight, пытаются обнаружить максимально подробную информацию об установленном в систему рутките, вместо того чтобы просто искать созданные им перехватчики системных вызовов. Эта статья описывает методы, используемые в Blacklight и IceSword для обнаружения скрытых процессов. Кроме того, будут описаны текущие недостатки в методах обнаружения руткитов и предложена законченная методика сокрытия, реализованная в FUTo. Да, кстати, речь идет об операционных системах семейства Windows. Пользователя root там обычно не присутствует, что, впрочем, не мешает поселению на этой платформе rootkit’ов, вполне аналогичных по функционалу своим юниксовым собратьям.

введение

За последние год или два произошло несколько значимых события в мире руткитов. Это появление FU rootkit, использующего DKOM (Direct Kernel Object Manipulation) и VICE, одной из первых программ для обнаружения руткитов; рождение Rootkit Revealer компании Sysinternals и Blacklight компании F-Secure – первых, получивших широкое распространение, утилит для обнаружения руткитов; и, наконец, появление Shadow Walker, руткита, скрывающего свое присутствие путем перехвата вызовов менеджера памяти.

Рассмотрим Blacklight и IceSword. Здесь обсуждаются алгоритмы, используемые именно в этих программах, по той причине, что по ряду параметров они являются лучшими утилитами для обнаружения руткитов. Blacklight, разработанный финской компанией F-Secure, в первую очередь озабочен обнаружением скрытых процессов. Эта утилита не занимается поиском перехваченных системных вызовов, только обнаружением скрытых процессов. IceSword использует похожие методы, но в отличие от Blacklight, является более законченным приложением, позволяя пользователю увидеть перехваченные системные вызовы, скрытые модули ядра, узнать, какие TCP/UDP-порты открыты и какими именно программами.

Blacklight

После исследования Blacklight стало очевидно, что в IceSword используются очень похожие алгоритмы. Поэтому вполне вероятно, что недостатки, обнаруженные в Blacklight, будут присутствовать и в IceSword.

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

В целях противодействия исследованию внутренней архитектуры, Blacklight использует некоторые анти-отладочные приемы, например, создание callback таблицы TLS (Thread Local Storage). Blacklight's TLS callback пытается обмануть отладчик, создавая копию главного процесса (fork) до того, как объект процесса полностью создан. Это осуществимо благодаря тому, что функция TLS callback вызывается перед тем, как процесс полностью инициализируется. Blacklight имеет встроенные средства анти-отладки, обнаруживающие попытки отладки приложения. Для обхода анти-отладочных приемов мы решили просто отключить подпрограммTLS callback у. Чтобы сделать это, была использована утилита LordPE, позволяющая редактировать PE- файлы. Используя LordPE, мы обнулили таблицы TLS callback. Это отключило функцию создания копии исходного процесса и дало возможность использовать API Monitor. Нужно заметить, что отключение callback-функции позволяет подключить отладчик к процессу, но при нажатии на “scan” в Blacklight GUI, утилита обнаружит отладчик и завершит работу. Вместо того чтобы пытаться обойти второй рубеж анти-отладочной обороны, мы решили анализировать API-запросы внутри Blacklight. Для этого был использован Rohitabs API Monitor.

Во время тестирования можно было увидеть неудачный вызов функции OpenProcess (таблица TLS обнулена). Далее Blacklight пытается открыть процессы с идентификаторами 0x1CC, 0x1D0, 0x1D4, 0x1D8 и т.д. Этот метод, применяемый в Blacklight, мы назвали PID Bruteforce (PIDB). В цикле Blacklight пытается вызвать OpenProcess для всех возможных значений идентификатора процесса (PID) из диапазона от 0x0 до 0x4E1C. Таким образом, используя PIDB-методику, Blacklight получает список процессов, присутствующих в системе. Затем Blacklight вызывает функцию CreateToolhelp32Snapshot и получает второй список процессов. Далее идет сравнение двух списков: если процесс присутствует в первом и отсутствует во втором, считается, что он скрытый.

функция WinAPI OpenProcess

Функция OpenProcess является оберткой над NtOpenProcess. NtOpenProcess реализована в ядре, в NTOSKRNL.EXE. Прототип этой функции следующий:

NTSTATUS NtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);


Параметр ClientID – это идентификатор процесса, передаваемый в OpenProcess. Этот параметр опционален, но в процессе нашего наблюдения за функцией OpenProcess этот параметр всегда указывался при вызове NtOpenProcess.

NtOpenProcess выполняет три основных действия:

1. Вызовом PsLookupProcessByProcessId проверяет, существует ли процесс.

2. Пытается получить дескриптор (HANDLE) процесса вызовом ObOpenObjectByPointer.

3. Если дескриптор был получен успешно, он возвращается вызывающей подпрограмме.

PsLookupProcessByProcessId - очевидный следующий предмет нашего интереса. Возникает вопрос, как эта функция определяет, что полученный PID принадлежит допустимому процессу? Ответ на него мы получаем в первых строках дизассемблированного листинга:

PsLookupProcessByProcessId:

mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer


Функция ExMapHandleToPointer ищет соответствующий идентификатор процесса в PspCidTable.

Теперь у нас есть полная картина того, как Blacklight производит обнаружение скрытых процессов:

- Blacklight начинает цикл перебора идентификаторов процесса от 0 до 0x41DC;

- Blacklight вызывает OpenProcess для каждого возможного PID;
- OpenProcess вызывает NtOpenProcess;

- NtOpenProcess вызывает PsLookupProcessByProcessId для проверки существования процесса;

- PsLookupProcessByProcessId для проверки существования процесса использует PspCidTable;

- NtOpenProcess вызывает ObOpenObjectByPointer для получения дескриптора процесса.

Если функция OpenProcess отработала успешно, Blacklight сохраняет информацию о процессе и продолжает выполнение цикла.

Как только список процессов, получаемый методом PIDB, будет создан, создается второй список с помощью функции CreateToolhelp32Snapshot. CreateToolhelp32Snapshot – это функция Win32 API, получающая снимок всех запущенных процессов системы. Различие между двумя этими списками указывает на скрытый процесс, о чем Blacklight сообщает пользователю

PspCidTable

PspCidTable – эта таблица дескрипторов процессов и потоков. Каждый дескриптор процесса занимает свое место в PspCidTable. PspCidTable является указателем на структуру HANDLE_TABLE.

typedef struct _HANDLE_TABLE {
PVOID p_hTable;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
DWORD ExtraInfoPages;
DWORD FirstFree;
DWORD LastFree;
DWORD NextHandleNeedingPool;
DWORD HandleCount;
DWORD Flags;
};


Windows имеет много различных неэкспортируемых функций для работы с PspCidTable. Вот некоторые из них:

- ExCreateHandleTable – создает таблицу дескрипторов. Элементы всех таблиц дескрипторов, кроме PspCidTable, это указатели на заголовки объектов, а не адреса самих объектов;

- ExDupHandleTable – вызывается при создании процесса;

- ExSweepHandleTable – используется при прекращении работы процесса;

- ExDestroyHandleTable – вызывается, когда процесс завершает работу;

- ExCreateHandle – создает новый элемент таблицы дескрипторов;

- ExChangeHandle – используется для изменения маски доступа к дескриптору;

- ExDestroyHandle – реализует функционал системного вызова CloseHandle;

- ExMapHandleToPointer – возвращает адрес объекта соответствующего дескриптора;

- ExReferenceHandleDebugIn – используется для отладки дескрипторов;

- ExSnapShotHandleTables – используется для поиска дескрипторов (например, файла oh.exe).

Ниже приведен код, использующий неэкспортируемые функции для удаления объекта процесса из PspCidTable. Здесь используются явно указанные адреса нужных неэкспортируемых функций, но руткит должен определять адреса этих функций динамически.

typedef PHANDLE_TABLE_ENTRY (*ExMapHandleToPointerFUNC)
( IN PHANDLE_TABLE HandleTable, IN HANDLE ProcessId);
void HideFromBlacklight(DWORD eproc)
{
PHANDLE_TABLE_ENTRY CidEntry;
ExMapHandleToPointerFUNC map;
ExUnlockHandleTableEntryFUNC umap;
PEPROCESS p;
CLIENT_ID ClientId;
map = (ExMapHandleToPointerFUNC)0x80493285;
CidEntry = map((PHANDLE_TABLE)0x8188d7c8,

LongToHandle( *((DWORD*)(eproc+PIDOFFSET)) ) );

if(CidEntry != NULL)
{
CidEntry->Object = 0;
}
return;

}


Так как PspCidTable предназначена для слежения за всеми процессами и потоками, логично было бы, если программа для обнаружения руткитов использовала бы PspCidTable для поиска скрытых процессов. Однако полагаться на единственный источник данных не надежно. Если руткит модифицирует этот источник, операционная система и другие программы не будут знать, что скрываемый процесс существует. Новые алгоритмы обнаружения руткитов должны быть разработаны таким образом, чтобы одна единственная модификация не делала процесс необнаруживаемым.

FUTo

Для демонстрации недостатков алгоритмов, используемых в настоящее время в утилитах для обнаружения руткитов, таких как Blacklight и Icesword, мы создали FUTo – новую версию FU rootkit, имеющего дополнительную возможность манипулирования PspCidTable без использования любых вызовов функций. Этот руткит использует технику DKOM для сокрытия объектов внутри PspCidTable.

Рассмотрим некоторые новые возможности, реализованные в FUTo. Функции ExMapHandleXXX не экспортировались ядром. Для решения этой проблемы FUTo автоматически определяет PspCidTable (адрес таблицы дескрипторов), путем поиска функции PsLookupProcessByProcessId и дизассемблирования ее с определением первого вызова функции. На момент написания статьи первым вызовом функции был вызов ExMapHandleToPointer. ExMapHandleToPointer получает PspCidTable в качестве первого параметра. Используя эту информацию, определить PspCidTable (адрес таблицы дескрипторов) очень просто.

PsLookupProcessByProcessId:

mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer


Этот метод не сработает, если в коде ядре будет проделана хотя бы небольшая оптимизация. Opc0de реализовал более надежный алгоритм для поиска неэкспортируемых переменных, таких как PspCidTable, PspActiveProcessHead, PspLoadedModuleList и т.д. Данный алгоритм не сканирует память, как это сделано в текущей версии FUTo. Вместо этого, Opc0de установил, что поле KdVersionBlock в структуре области управления процессом (Process Control Region) указывает на структуру KDDEBUGGER_DATA32, выглядящую примерно следующим образом:

typedef struct _KDDEBUGGER_DATA32 {

DBGKD_DEBUG_DATA_HEADER32 Header;
ULONG KernBase;
ULONG BreakpointWithStatus; // адрес точки останова
ULONG SavedContext;
USHORT ThCallbackStack; // смещение в массиве данных потока
USHORT NextCallback; // сохраненный указатель на следующий

// callback-фрейм.
USHORT FramePointer; // сохраненный указатель фрейма
USHORT PaeEnabled;
ULONG KiCallUserMode; // подпрограмма ядра
ULONG KeUserCallbackDispatcher; // адрес в ntdll
ULONG PsLoadedModuleList;
ULONG PsActiveProcessHead;
ULONG PspCidTable;

ULONG ExpSystemResourcesList;
ULONG ExpPagedPoolDescriptor;
ULONG ExpNumberOfPagedPools;

[...]

ULONG KdPrintCircularBuffer;
ULONG KdPrintCircularBufferEnd;
ULONG KdPrintWritePointer;
ULONG KdPrintRolloverCount;

ULONG MmLoadedUserImageList;

} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;


Как вы можете заметить, эта структура содержит указатели на многие часто используемые неэкспортируемые переменные. Таким образом, это еще один способ определения PspCidTable и других подобных переменных.
Реализация второго нововведения была чуть более проблематична. Когда FUTo удаляет объект из PspCidTable, HANDLE_ENTRY заполняется нулями, что означает, что процесс не существует. Проблема возникает, когда скрытый процесс (не имеющий записи в PspCidTable) завершает работу. Когда система пытается завершить процесс, она ссылается на соответствующий элемент PspCidTable и разыменовывает нулевой объект, что вызывает голубой экран. Решение этой проблемы просто, но не изящно. FUTo устанавливает подпрограмму уведомления процесса вызовом PsSetCreateProcessNotifyRoutine. Callback-функция вызывается всякий раз, когда процесс создается и, что более важно, когда процесс завершается. Эта функция вызывается перед завершением работы скрытого процесса, то есть перед крахом системы. Перед удалением индексов, содержащих объекты, указывающие на скрываемый процесс, FUTo сохраняет значения HANDLE_ENTRY и индекс для дальнейшего использования. Перед завершением работы процесса FUTo восстанавливает эти объекты, позволяя системе корректно их разыменовать.

заключение

Коронной фразой 2005 года была “Мы снова поднимаем планку в обнаружении руткитов”. Надеемся, теперь читатель будет лучше понимать, как передовые утилиты обнаружения руткитов ищут скрытые процессы и как они могут быть улучшены. Кто-то может спросить “Как я могу защититься?”. Самым простым решением будет не подключаться к интернету, но если это не самое приемлемое решение, комбинация из Blacklight, IceSword и Rootkit Revealer заметно повысит ваши шансы на отсутствие руткитов в вашей системе. Кроме того, недавно на конференции Blackhat в Амстердаме была представлена новая утилита RAIDE (Rootkit Analysis Identification Elimination), не подверженная проблемам, описанным в этой статье.



Peter Silberman & C.H.A.O.S., перевод Владимира Куксенка


Сетевые решения. Статья была опубликована в номере 08 за 2006 год в рубрике save ass…

©1999-2024 Сетевые решения