Статьи CsrWalker – использование csrss в качестве детектора руткитов

Discussion in 'Статьи' started by Delivery Man, 9 Oct 2010.

  1. Delivery Man

    Delivery Man New Member

    Joined:
    2 Jul 2010
    Messages:
    0
    Likes Received:
    0
    Reputations:
    0
    Автор: DiabloNova
    Переводчик: ARCHANGEL
    Оригинал (EN): www.rootkit.com
    Источник (RU): www.reverse4you.org

    CsrWalker – использование csrss в качестве детектора руткитов


    Игра в обнаружение и сокрытие всегда была интересной. Особенно когда предметом этой игры становились такие объекты, как процессы Windows.

    Как вы, возможно, знаете, существует огромное количество «крутых» утилит для доказательства возможности сокрытия процессов, основной задачей которых является сокрытие процессов, созданных в режиме пользователя, от всевозможных методов перечисления процессов.

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

    Ниже представлен небольшой список доступных утилит для сокрытия процессов (большинство из них уже устарели, но некоторые – новые):

    Hacker Defender
    FU/FUTO
    rkdemo (1.0-1.2)
    phide_ex
    Z0mBiE
    Некоторые другие, не представленные здесь.

    Принцип работы этих утилит – внедрение в ядро Windows для манипулирования внутренними объектами с целью сокрытия процессов от таких программ, как TaskManager или Process Explorer.

    С другой стороны, существует множество общедоступных (и приватных) утилит-детекторов, основной целью работы которых является выявление скрытого содержимого путём проведения ряда проверок внутренностей операционной системы.

    Каждая новая программа сокрытия процессов обходит все (или почти все) ранее известные методы обнаружения. В основном, это происходит, потому что заранее известно, как антируткит будет искать скрытые процессы. Большинство методов обнаружения основывается на поиске таких объектов, как EPROCESS и ETHREAD, несколькими различными методами. Например, для этого можно использовать анализ таблицы PspCidTable (таблицы дескрипторов), списки планировщика (как поступает GMER v1.14), проверять определённые поля в EPROCESS, такие как LIST_ENTRY, перехватывать KiSwapContext, искать потоки, выполнять поиск процессов полным перебором и т.д. Вообще-то, всего этого вполне достаточно для поиска любой существующей на сегодня малвари, которая скрывает процессы во время работы. Если в привате и существует какая-то малварь, способная лучше выполнять сокрытие, то это – неважно, так как она находится в привате.

    Это было вступление.

    Мистический CSRSS всегда был мне интересен просто потому, что он выполняет роль супервизора для процессов пользовательского режима. Для продолжения работы все win32 процессы должны информировать CSRSS о своём создании на стадии инициализации. Именно этот компонент выполняет всю работу по инициализации подсистем процессов (из csrsrv.dll, которая является сердцем CSRSS).

    Именно он – новый обнаружитель скрытых процессов (встроенный в NT с самого начала). Всё это – абсолютно недокументированно. Всё, что я получил, когда попытался найти ссылки по данному материалу, были исходные коды ReactOs, которые были неполными и попросту отличались от NT.

    Реверсинг и восстановление кода менеджера подсистем заняло много времени, но это было забавно и не так уж сложно при наличии небольшого куска исходного кода. CSRSS – не такая содержательная вещь, наиболее интересные моменты содержаться в csrsrv.dll.

    Внутри неё содержится неэкспортируемый символ CsrRootProcess. Он выполняет огромную роль в CSRSS, поскольку является указателем на структуру CSR_PROCESS, выглядящую так:

    Code:
     
    //Vista/2008 csrsrv.dll (истинно также для  XP, 2003)
     
    typedef struct _CSR_PROCESS {
    struct _CLIENT_ID ClientId;
    struct _LIST_ENTRY ListLink;
    struct _LIST_ENTRY ThreadList;
    struct _CSR_NT_SESSION* NtSession;
    ULONG ExpectedVersion;
    void* ClientPort;
    char* ClientViewBase;
    char* ClientViewBounds;
    void* ProcessHandle;
    ULONG SequenceNumber;
    ULONG Flags;
    ULONG DebugFlags;
    ULONG ReferenceCount;
    ULONG ProcessGroupId;
    ULONG ProcessGroupSequence;
    ULONG fVDM;
    ULONG ThreadCount;
    ULONG LastMessageSequence;
    ULONG NumOutstandingMessages;
    ULONG ShutdownLevel;
    ULONG ShutdownFlags;
    struct _LUID Luid;
    void* ServerDllPerProcessData[1];
    } CSR_PROCESS, *PCSR_PROCESS;
    
    В этой структуре присутствует поле ListLink, которое представляет из себя LIST_ENTRY. В csrsrv.dll существует другая внутренняя неэкспортируемая функция, именуемая CsrInsertProcess. Она включает в себя другую структуру CSR_PROCESS на этот LIST_ENTRY из CsrRootProcess.

    CsrInsertProcess для меня была ключом, помогающим найти CsrRootProcess. Она вызывается из CsrCreateProcess – экспортируемой, но недокументированной функции, вызов расположен ближе к концу CsrCreateProcess.

    Code:
     
    text:75B15E94                 call    CsrSetBackgroundPriority
    .text:75B15E99                 mov     dword ptr [esi+64h], 280h
    .text:75B15EA0                 mov     eax, large fs:18h
    .text:75B15EA6                 mov     eax, [eax+3Ch]
    .text:75B15EA9                 push    esi             ; a3
    .text:75B15EAA                 push    dword ptr [eax+20h] ; a2
    .text:75B15EAD                 xor     esi, esi
    .text:75B15EAF                 push    esi             ; Session
    .text:75B15EB0                 call    CsrInsertProcess
    
    CsrInsertProcess выглядит очень интересно:

    Code:
     
    text:75B14CC9 ; int __stdcall CsrInsertProcess(int Session, int a2, int a3)
    .text:75B14CC9 CsrInsertProcess proc near              ; CODE XREF: sub_75B13BF8+12Ap
    .text:75B14CC9                                         ; CsrCreateProcess+214p
    .text:75B14CC9
    .text:75B14CC9 Session         = dword ptr  8
    .text:75B14CC9 a2              = dword ptr  0Ch
    .text:75B14CC9 a3              = dword ptr  10h
    .text:75B14CC9
    .text:75B14CC9                 mov     edi, edi
    .text:75B14CCB                 push    ebp
    .text:75B14CCC                 mov     ebp, esp
    .text:75B14CCE                 mov     eax, [ebp+Session]
    .text:75B14CD1                 push    esi
    .text:75B14CD2                 mov     esi, [ebp+a3]
    .text:75B14CD5                 mov     [esi+18h], eax
    .text:75B14CD8                 mov     ecx, CsrRootProcess
    .text:75B14CDE                 mov     edx, [ecx+0Ch]
    .text:75B14CE1                 add     ecx, 8
    .text:75B14CE4                 lea     eax, [esi+8]
    .text:75B14CE7                 mov     [eax], ecx
    .text:75B14CE9                 mov     [eax+4], edx
    .text:75B14CEC                 push    edi
    .text:75B14CED                 mov     [edx], eax
    .text:75B14CEF                 mov     [ecx+4], eax
    .text:75B14CF2                 xor     edi, edi
    .text:75B14CF4
    .text:75B14CF4 loc_75B14CF4:                           ; CODE XREF: CsrInsertProcess+48j
    .text:75B14CF4                 mov     eax, CsrLoadedServerDll[edi]
    .text:75B14CFA                 test    eax, eax
    .text:75B14CFC                 jz      short loc_75B14D0B
    .text:75B14CFE                 mov     eax, [eax+44h]
    .text:75B14D01                 test    eax, eax
    .text:75B14D03                 jz      short loc_75B14D0B
    .text:75B14D05                 push    esi
    .text:75B14D06                 push    [ebp+a2]
    .text:75B14D09                 call    eax
    .text:75B14D0B
    .text:75B14D0B loc_75B14D0B:                           ; CODE XREF: CsrInsertProcess+33j
    .text:75B14D0B                                         ; CsrInsertProcess+3Aj
    .text:75B14D0B                 add     edi, 4
    .text:75B14D0E                 cmp     edi, 10h
    .text:75B14D11                 jb      short loc_75B14CF4
    .text:75B14D13                 pop     edi
    .text:75B14D14                 pop     esi
    .text:75B14D15                 pop     ebp
    .text:75B14D16                 retn    0Ch
    .text:75B14D16 CsrInsertProcess endp
    
    Исходя из этого, я составил псевдокод для CsrInsertProcess:

    Code:
     
    CsrInsertProcess( .... )
    {
        PCSR_SERVER_DLL ServerDll;
        ULONG i;
     
        InsertToList(&CsrRootProcess->ListLink, &CsrProcess->ListLink);
     
        for (i = 0; i < CSR_SERVER_DLL_MAX; i++)
        {
            ServerDll = CsrLoadedServerDll;
     
            if (ServerDll && ServerDll->NewProcessCallback)
            {
                (*ServerDll->NewProcessCallback)(CurrentProcess, CsrProcess);
            }
        }
    }
    
    Как видите, CsrRootProcess содержит двусвязный список процессов. Но способ, описанный выше, не является самым лучшим для нахождения этого неэкспортируемого символа. Существует другая экспортируемая функция, из которой легко извлечь CsrRootProcess. Имя этой функции – CsrLockProcessByClientId.

    Code:
     
    .text:75B152D3 CsrLockProcessByClientId proc near   
    .text:75B152D3
    .text:75B152D3 arg_0           = dword ptr  8
    .text:75B152D3 arg_4           = dword ptr  0Ch
    .text:75B152D3
    .text:75B152D3                 mov     edi, edi
    .text:75B152D5                 push    ebp
    .text:75B152D6                 mov     ebp, esp
    .text:75B152D8                 push    ebx
    .text:75B152D9                 push    esi
    .text:75B152DA                 push    edi
    .text:75B152DB                 mov     edi, offset unk_75B189A0
    .text:75B152E0                 push    edi
    .text:75B152E1                 call    ds:RtlEnterCriticalSection
    .text:75B152E7                 mov     edx, [ebp+arg_4]
    .text:75B152EA                 and     dword ptr [edx], 0
    .text:75B152ED                 mov     esi, CsrRootProcess
    .text:75B152F3                 add     esi, 8
    
    Однажды получив CsrRootProcess, становится возможным перечисление большинства существующих процессов простым проходом по двусвязному списку, где каждое значение LIST_ENTRY принадлежит очередной структуре CSR_PROCESS. Тем не менее, этот метод показался мне весьма нестабильным. Я не знаю, почему. Возможно, из-за недостаточной блокировки списка процессов, но проверка этого требует инжекта кода внутрь csrss.exe и получения списка процессов оттуда. Тем не менее, проделанной работы более чем достаточно для доказательства существования данной концепции, поэтому я оставляю возможность решения этой проблемы кому-нибудь другому.

    Кроме этого, CSRSS также хранит список потоков в структуре, на которую указывает другой неэкспортируемый символ – CsrHashThread. Это массив из 256 элементов, где каждый элемент представлен структурой LIST_ENTRY. Каждый LIST_ENTRY указывает на структуру CSR_THREAD:

    Code:
     
    typedef struct _CSR_THREAD { // <size 0x38>
      union _LARGE_INTEGER CreateTime;
      struct _LIST_ENTRY Link;
      struct _LIST_ENTRY HashLinks;
      struct _CLIENT_ID ClientId;
      struct _CSR_PROCESS* Process;
      struct _CSR_WAIT_BLOCK* WaitBlock;
      void* ThreadHandle;
      unsigned long Flags;
      unsigned long ReferenceCount;
      unsigned long ImpersonateCount;
    } CSR_THREAD, *PCSR_THREAD;
    
    Значение CsrHashThread может быть легко получено из экспортируемой функции CsrLockThreadByClientId:

    Code:
     
    .text:75B15353 CsrLockThreadByClientId proc near
    .text:75B15353
    .text:75B15353 a1              = dword ptr  8
    .text:75B15353 a2              = dword ptr  0Ch
    .text:75B15353
    .text:75B15353                 mov     edi, edi
    .text:75B15355                 push    ebp
    .text:75B15356                 mov     ebp, esp
    .text:75B15358                 push    ebx
    .text:75B15359                 push    esi
    .text:75B1535A                 push    edi
    .text:75B1535B                 mov     esi, offset unk_75B189A0
    .text:75B15360                 push    esi
    .text:75B15361                 call    ds:RtlEnterCriticalSection
    .text:75B15367                 mov     ebx, [ebp+a1]
    .text:75B1536A                 mov     edi, [ebp+a2]
    .text:75B1536D                 and     dword ptr [edi], 0
    .text:75B15370                 mov     eax, ebx
    .text:75B15372                 and     eax, 0FFh
    .text:75B15377                 lea     edx, CsrHashThread.anonymous_0[eax*8]
    .text:75B1537E                 mov     ecx, [edx]
    
    С её помощью у меня появилась возможность построить другой список процессов.

    Наиболее важным здесь является проход по структуре, на которую указывает CsrRootProcess. Но только CLIENT_ID и ещё немного информации становится доступной.

    Я потратил немного времени на написание небольшого детектора, который использует CsrRootProcess и CsrHashThread для поиска скрытых процессов. Результат был действительно впечатляющим. Даже не смотря на то, что внутренности CSRSS не предоставляют исчерпывающей информации о процессах (из них не удалось получить ETHREAD или EPROCESS, или я не нашёл способа, как это сделать {Примечание переводчика: про очки я тут не писал}) полученной информации хватило для обнаружения всех вышеупомянутых руткитов.

    Тем не менее, я думаю, что такой способ обнаружения непригоден для практического использования из-за малого объёма предоставляемой информации. Именно поэтому вы и читаете это сейчас.

    Посмотрите скриншоты (они настоящие). Пожалуйста, не беспокойте меня расспросами о примерах для теста. Если у вас их нет, значит на то есть свои причины.

    CsrWalker против RKDEMO v1.2.

    [​IMG]

    CsrWalker против PHIDE_EX

    [​IMG]

    CsrWalker против Z0mBiE v1.1

    [​IMG]

    CsrWalker против n0name PoC

    [​IMG]

    Самой крутой фишкой всего этого является то, что всё было сделано исключительно из режима пользователя, а код, обнаруживающий скрытые процессы, легко портировать на все версии Windows NT от 2000 до 2008.


    Важное дополнение для Windows Vista


    В Windows Vista и старше существует несколько процессов csrss.exe, одновременно выполняющихся в системе. Это было сделано для большей изоляции процессов, работающих под учётной записью SYSTEM, от всех остальных (я полагаю, что в этих системах каждая учётная запись имеет свой собственный csrss.exe). Поэтому под Windows Vista данный метод обнаружения скрытых процессов был слегка доработан. Перед просмотром csrss.exe необходимо построить список этих самых csrss.exe, а после – просмотреть каждый из них.

    Под ХР задача упрощалась – можно было получить идентификатор процесса csrss.exe с помощью простого вызова CsrGetProcessId. Всё, что делает эта функция – это считывает переменную, содержащую этот самый идентификатор.

    .text:77F5BAE2 public CsrGetProcessId
    .text:77F5BAE2 CsrGetProcessId proc near
    .text:77F5BAE2 mov eax, CsrProcessId
    .text:77F5BAE7 retn
    .text:77F5BAE7 CsrGetProcessId endp

    Начиная с Windows Vista, мы больше не можем полагаться только на это значение. Лучшим способом получения списка запущенных процессов csrss.exe будет проход по глобальной таблице дескрипторов и восстановление имён объектов, описываемых этими дескрипторами. Причина таких действий заключается в том, что csrss имеет права эксклюзивного доступа на два дескриптора типа Port/ALPC с именами ApiPort и SbApiPort. Однако под Windows Vista объекты типа ALPC имеют некоторые ограничения на открытие/чтение информации (для примера попробуйте просмотреть их свойства через ProcessExplorer и будете удивлены), поэтому данный метод не подходит. Существует несколько возможных решений, и одно из них реализовано в CsrWalker’е. Первое – искать все процессы с именем csrss.exe и пытаться разобрать их внутренние структуры, описанные выше. Неплохой метод, но он не подходит для настоящих хакеров. Второе – произвести внедрение кода в процесс, работающий под учётной записью SYSTEM, например, в один из процессов с именем svchost.exe, после из этого процесса вызвать CsrGetProcessId. Я оставлю это тем, кто хочет попробовать и улучшить/обойти CsrWalker.

    Помните, это только доказательство концепции. Поэтому это может работать, а может и нет. Никаких BSOD’ов, я обещаю.

    Файлы к статье

    ARCHANGEL © AHTeam, r0 Crew​
     
    #1 Delivery Man, 9 Oct 2010
    Last edited: 10 Oct 2010
  2. Gusev

    Gusev Banned

    Joined:
    4 Sep 2010
    Messages:
    86
    Likes Received:
    8
    Reputations:
    -5
    Очень обширно.
    Хотя можно было бы срезать на пару моментах
     
  3. Kamik

    Kamik Member

    Joined:
    2 Dec 2008
    Messages:
    122
    Likes Received:
    85
    Reputations:
    8
    Отлично =) Было бы канешн неплохо чтоб указывало путь к файлу но и так оч хорошо. я успокоился немного =) У меня 42/0