Эта статья описывает методы, используемые в Blacklight и IceSword для обнаружения скрытых процессов. Кроме того, будут описаны текущие недостатки в методах обнаружения rootkits и предложена законченная методика сокрытия, реализованная в FUTo.
Peter Silberman & C.H.A.O.S.
перевод: Владимир Куксенок
С появлением FU, мир rootkits перешел на следующий уровень - с реализации перехвата системных вызов на сокрытие своего присутствия. Новые возможности средств нападения потребовали разработки новой защиты. Современные алгоритмы, используемые rootkit-детекторами, такими как BlackLight, пытаются обнаружить максимально подробную информацию об установленном в систему rootkit-е, вместо того чтобы просто искать созданные им перехватчики системных вызовов. Эта статья описывает методы, используемые в Blacklight и IceSword для обнаружения скрытых процессов. Кроме того, будут описаны текущие недостатки в методах обнаружения rootkits и предложена законченная методика сокрытия, реализованная в FUTo.
За последние год или два произошло несколько значимых события в rootkit-мире. Это появление FU rootkit, использующего DKOM (Direct Kernel Object Manipulation) и VICE, одной из первых программ для обнаружения rootkits; рождение Rootkit Revealer компании Sysinternals и Blacklight компании F-Secure – первых, получивших широкое распространение, утилит для обнаружения rootkits; и, наконец, появление Shadow Walker, rootkit-а скрывающего свое присутствие путем перехвата вызовов менеджера памяти.
Рассмотрим Blacklight и IceSword. Здесь обсуждаются алгоритмы, используемые именно в этих программах, по той причине, что по ряду параметров они являются лучшими утилитами для обнаружения rootkits. 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 и получает второй список процессов. Далее идет сравнение двух списков: если процесс присутствует в первом и отсутствует во втором, считается, что он скрытый.
Функция 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 выполняет три основных действия:
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 производит обнаружение скрытых процессов:
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, это указатели на заголовки объектов, а не адреса самих объектов.Ниже приведен код, использующий неэкспортируемые функции для удаления объекта процесса из PspCidTable. Здесь используются явно указанные адреса нужных неэкспортируемых функций, но rootkit должен определять адреса этих функций динамически.
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 предназначена для слежения за всеми процессами и потоками, логично было бы, если бы программа для обнаружения rootkits использовала PspCidTable для поиска скрытых процессов. Однако полагаться на единственный источник данных не надежно. Если rootkit модифицирует этот источник, операционная система и другие программы не будут знать, что скрываемый процесс существует. Новые алгоритмы обнаружения rootkits должны быть разработаны таким образом, чтобы одна единственная модификация не делала процесс необнаруживаемым.
Для демонстрации недостатков алгоритмов, используемых в настоящее время в утилитах для обнаружения rootkits, таких как Blacklight и Icesword, мы создали FUTo – новую версию FU rootkit, имеющего дополнительную возможность манипулирования PspCidTable без использования любых вызовов функций. Этот rootkit использует технику 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 года была “Мы снова поднимаем планку в обнаружении rootkits”. Надеемся, теперь читатель будет лучше понимать, как передовые утилиты обнаружения rootkits ищут скрытые процессы и как они могут быть улучшены. Кто-то может спросить “Как я могу защититься?”. Самым простым решением будет не подключаться к интернет, но если это не самое приемлемое решение, комбинация из Blacklight, IceSword и Rootkit Revealer заметно повысит ваши шансы на отсутствие rootkits в вашей системе. Кроме того, несколько месяцев назад на конференции Blackhat в Амстердаме была представлена новая утилита RAIDE (Rootkit Analysis Identification Elimination), не подверженная проблемам, описанным в этой статье.
И мы тоже не спим, чтобы держать вас в курсе всех угроз