Отслеживание процессов и синхронизация

Тема в разделе "WASM.NT.KERNEL", создана пользователем T800, 5 янв 2010.

  1. T800

    T800 Member

    Публикаций:
    0
    Регистрация:
    7 дек 2006
    Сообщения:
    293
    Адрес:
    Moscow
    Всего неделю изучаю С и ring0 (точнее написание драйверов). Прочитал закреплённый пост Great и сделал всё необходимое.
    Прочитал статьи Four-F и Ms-Rem'а.
    Написал драйвер на VS8, который просто занимается слежением процессов. Всё работает нормально.
    Далее хочется следить за процессами не только при помощи PsSetCreateProcessNotifyRoutine , но и через перехват Nt ф-ий. Для этого уже реализовал односвязный список (как у Ms-Rem'а в phunter).
    И тут возникло много вопросов. Начну по-порядку.

    1) Ms-Rem в phunter странно реализовал ф-ию NotifyRoutine. Зачем он использовал рабочий поток не могу понять.
    Код (Text):
    1. void WorkItemProc(PDEVICE_OBJECT DeviceObject, PWorkItemStruct Data)
    2. {
    3.   KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL);
    4.   DelItem(&wLastItem, Data->pEPROCESS);
    5.   ObDereferenceObject(Data->pEPROCESS);
    6.   IoFreeWorkItem(Data->IoWorkItem);
    7.   ExFreePool(Data);
    8.   return;
    9. }
    10.  
    11. void NotifyRoutine(IN HANDLE  ParentId, IN HANDLE  ProcessId, IN BOOLEAN Create)
    12. {
    13.   PEPROCESS       process;
    14.   PWorkItemStruct Data;
    15.  
    16.   if (Create)
    17.   {
    18.     PsLookupProcessByProcessId(ProcessId, &process);
    19.     if (!IsAdded(wLastItem, process)) AddItem(&wLastItem, process);
    20.     ObDereferenceObject(process);
    21.   } else {
    22.     process = PsGetCurrentProcess();
    23.     ObReferenceObject(process);
    24.     Data = ExAllocatePool(NonPagedPool, sizeof(TWorkItemStruct));
    25.     Data->IoWorkItem = IoAllocateWorkItem(deviceObject);
    26.     Data->pEPROCESS  = process;
    27.     IoQueueWorkItem(Data->IoWorkItem, WorkItemProc, DelayedWorkQueue, Data);
    28.   }
    29.   return;
    30. }
    Жаль от автора ответ не получить.

    2) Сейчаз у меня список процессов хранится в NonPagedPool (выделение памяти для статичной структры и для FullName PWCHAR). На сколько это разумно?

    3) В каждой ф-ии, работающей со списком процессов, использую спин-блокировку (увидел в каком то исходнике). Я так понял что критические секции можно реализовывать как при помощи спин-блокировки, мьютексов и FastMutex'ов. Если мы работаем с NonPagedPool, то можно использовать спин-блокировки. В остальных случах стоит использовать мьютексы?

    4) Four-F в статье пишет, что FastMutex менее универсален, но работает побысрее KeWaitForMutexObject.
    Для реализации чтения/изменения списка процессов хватит функционала FastMutex?

    5) Критические секции стоит использовать непосредственно внутри AddItem или лучше так: ExAcquireFastMutex - AddItem - ExReleaseFastMutex. На данном этапе не пойму как более универсально.

    6) Сейчаз реализовал перехват NtOpenProcess. Я так понял что код внутри данной операции будет выполняться в контексте того процесса, который вызвал данную ф-ию.
    Если в списке нету cpid=PsGetCurrentProcessId , то AddItem(cpid). Здесь очевидно стоит использовать мьютексы.
    При выполнении DriverUnload возвращаю на место настоящие указатели syscall'ов. А как на этапе "убиения драйвера" быть уверенным, что никакая из перехваченных ф-ий сейчаз не выполняется в контексте "левого" процесса?


    Пока ещё каша в голове по поводу правильной реализации синхронизации. Вот надо как то всё выстроить в голове правильно, что бы сразу пойти по верному пути.
     
  2. T800

    T800 Member

    Публикаций:
    0
    Регистрация:
    7 дек 2006
    Сообщения:
    293
    Адрес:
    Moscow
    Наверное слишком нубские вопросы, коли ответа так и нет [​IMG].
    Ну да ладно.
    Спин-блокировки заменил на FastMutex (незнаю по скорости чем они отличаются).
    Заметил что FastMutex увеличивает на 1 текущий уровень IRQL. Поэтому пришлось реализовать вдобавок к быстрому мьютексу и обычный. Там где нужен PASSIVE_LEVEL использую KeWaitForMutexObject, а там где допустимо IRQL=APC_LEVEL использую FastMutex. Поэтому 4 и 5 вопросы исчерпаны.

    Более всего интересуют 1 и 6 вопросы.
     
  3. Nicky

    Nicky New Member

    Публикаций:
    0
    Регистрация:
    28 дек 2009
    Сообщения:
    16
    Мое почтение, я с основами разбирался намного дольше, но так до сих пор и не разобрался к сожалению :dntknw:. Здесь нужно очень много времени и терпения, которого у вас по видимому нету
    Неужели так трудно набрать в гугле "отличие spinlock от mutex?"
    http://www.eggheadcafe.com/software/aspnet/35447758/spinlockmutex--differen.aspx

    Ну вы же сами написали ответ:
    Удалив процесс вы его завершаете, вместе с его адресным пространством. Следовательно, обратившись по какому-то адресу из callback-функции после удаления, вы обратитесь в космос. Вы же сами говорите про "контексты" дальше по тексту. Если вы читали те статьи, вам должно быть известно, что это такое.

    Наверное тем, кто так открыто пишет руткиты, здесь не очень помогают..
     
  4. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Его объяснение в цитате. Если честно, я тоже не вижу очевидной причины держать воркитем.
    Разумно: SwapContext вызывается на повышенном IRQL при залоченной блокировке. PagedPool тут не подходит.
    Мьютексы не подойдут при IRQL>=2, при котором вызывается NewSwapContext->CollectProcess->AddItem.
    Мьютексы - объекты синхронизации при низких IRQL, на высоких IRQL только спин-блокировки.
    См. ответ к 3)
    Разумеется, AddItem должна скрывать в себе синхронихацию. Внутри нужно. (в исходнике MsRem не нашел - вероятно, он посчитал излишней синхронизацию при IRQL>=2. На многопроцессорных системах работать не будет)
    У него в исходнике и так есть перехват syscall и коллект всех процессов, которые вызывают сисколы. Зачем дополнительно NtOpenProcess хукать?
    ПС. Синхронизация доступа должна быть одинаковой во всех местах, где идет обращение к объекту. Не пойдет использовать в одном месте спинлоки, а в другом мьютексы. А выше мы выяснили, что поскольку наибольший IRQL, на котором будут обращаться к списку, это DPC/Dispatch, то нам нужны спинлоки. Забудь про мьютексы в этом контексте

    Nicky

    При чем тут удаление процесса, если речь про удаление итема из списка. Или я не так понял вопрос автора?
    По-моему, речь про антируткит. Нэ?
     
  5. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    ага, ага, а это?
    KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL);
     
  6. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    n0name
    ну я к тому, что можно было удалить процесс из списка, не дожидаясь фактического завершения, хотя это некорректно)
    ну а так конечно надо дождаться завершения, а поэтому нужен воркитем
     
  7. T800

    T800 Member

    Публикаций:
    0
    Регистрация:
    7 дек 2006
    Сообщения:
    293
    Адрес:
    Moscow
    Great
    Ах вот оно как. До чего "правильный" код. Тогда и у себя оставлю WorkItem (благо понял для чего он).

    Да это я во втором посте погорячился. Сейчаз в коде только FastMutex. Но из-за этого приходится использовать такую конструкцию:
    Код (Text):
    1. KeAcquireFastMutex  
    2. // IRQL = APC_LEVEL
    3. if IsAdded(pid) ....
    4. KeReleaseFastMutex
    5. // достаём ProcessImageName
    6. ZwQueryInformationProcess  // только в PASSIVE_LEVEL выполняется
    7. KeAcquireFastMutex  
    8. // IRQL = APC_LEVEL
    9. AddItem(pid)    // внутри тоже есть проверка IsAdded, поэтому "разрыв блокировки" не особо страшен  
    10. KeReleaseFastMutex
    А с чего ты решил что я передираю код Ms-Rem'а ? Я просто смотрю в его исходники. SwapContext не хукаю. У меня вроде бы все хуки происходят при PASSIVE_LEVEL. Хотя не уверен что, перехватив любую Nt* ф-ию из ntdll.dll, текущий IRQL будет равен PASSIVE_LEVEL. На примере NtOpenProcess реально получить IRQL > PASSIVE_LEVEL ?

    Это я уяснил [​IMG].
    А где можно почитать о влиянии разных типов блокировок на скорость выполнения? А то пишут что быстрее, а на какой порядок быстрее не совсем ясно. Т.е. хочется понять что я "потерял" при переходе со SpinLock на FastMutex?

    А я "снаружи" уже сделал. Добавил ф-ии lock() и unlock(). Мне кажется что так удобнее и универсальнее.

    Я не пишу аналог phunter. Я просто изучаю ring0 [​IMG]
    Просто NtOpenProcess могут использовать скрытые процессы. Вот и пришла в голову хукать её.

    А вот на мой вопрос "При выполнении DriverUnload возвращаю на место настоящие указатели syscall'ов. А как на этапе "убиения драйвера" быть уверенным, что никакая из перехваченных ф-ий сейчаз не выполняется в контексте "левого" процесса?" так никто и не ответил ...


    Nicky
    Откуда такое мнение? Терпения у меня достаточно. Я вот и разбираюсь с основами. Ну а то что быстро разбираюсь - это всё опыт (asm и Pascal). До этого на С не писал, но уж очень много портировал кода на делфю, что позволило отказаться от этапа "изучение основ программирования на С" [​IMG].

    Спасибо за ссылку. Пока не совсем уснил о чём там речь, но попробую уснить для себя отличия (на великом русском было бы интереснее почитать).

    Мои убеждения не позволяют мне этим заниматься. Да и только читателям журнала Хакер вбрёдёт в голову мысля, что нужно за неделю изучить C, Win32 Kernel и написать славный руткит [​IMG].
     
  8. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    Рабочие потоки ядра (worker threads) - ценный системный ресурс. Расходовать его там, где можно было бы использовать другие, более подходящие, средства, - нельзя. Другими словами, ждать чего-либо неопределённо-продолжительное время настоятельно не рекомендуется.
     
  9. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    T800
    Мб лучше использовать уже установленный нотификатор, а то вполне вероятно что свободного не будет. Легко ставится, легко снимается, легко обходится, как и реверенс на обькты.. Весьма просто и слишком не надёжно, но зато легально и есть в мсдн :)
     
  10. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    См. выше.

    Конструкция правильная, если irql<2.

    Пользовательский код исполняется на irql=0, следовательно, если в перехватчике previous mode = user, то irql=0.

    Спин-блокировки, в первую очередь, это синхронизация между процессорами, а не потоками, а уж irql здесь вторично.

    Да, быстрее. Конкретно - возьми да померяй, если интересно.

    Некорректный вопрос, они для разных случаев.

    Идея верная, но реализация идиотская. Нормальный руткит вряд ли воспользуется NtOpenProcess(). Думай дальше.

    Какая разница?

    Перевожу:

     
  11. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    Опять геморрой советуешь.

    Эта проблема решается по-другому.
     
  12. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    x64
    Ну там все же DelayedWorkQueue в сорце, к тому же завершение процесса будет не так уж и нескоро после вызова NtTerminateProcess, в мерках delayed work queue, ну а так ты, конечно, прав.

    В общем случае разрыв критической секции неприемлим, но в твоем случае у тебя две критических секции - на проход списка и на добавление элемента. Вообще,выглядит это ужасно. По-хорошему нужны две версии, например:
    - _IsAdded() - проверяет наличие элемента без блокировки
    - IsAdded() - обернутый в критическую секцию вызов _IsAdded().
    И для AddItem() так же. Код,который использует эти функции, ничего не должен знать о внутренностях реализации списков, в том числе о блокировке списка. Это не "универсально", как ты написал, а нарушает инкапсуляцию. Впрочем,я не силен в теории ООП :) Но лично мне это кажется как минимум некрасивым.

    Ну тогда вопрос снят насчет SwapContext. А ты перехватываешь их в ntdll.dll? Из ядра? Зачем? В юзермоде они будут вызыватся всегда при PASSIVE.

    Вот тебе минимум одно отличие - мьютексы могут быть именованными. И они могут открываться из юзермода и с ними может идти работа из юзермода. Обычный объект менеджера объектов. А fast mutex сделаны для использования в ядре. Точно про скорость выполнения не скажу - всегда можешь замерить самостоятельно. Можно сравнить с CRITICAL_SECTION в юзермоде - сделано почти так же.
    Про типы блокировок подробнее:
    - SpinLock исключительно аппаратная. Если блокировка захвачена, то процессор крутится в цикле, пока она не будет освобождена, что может быть изменено только с другого процессора в системе (а повторный захват спинблокировки на одном процессоре это ошибка и ведет к зависанию системы. checked hal делает assert() при удерживании блокировки продолжительное, порядка секунд, время). Блокировка захватывается на крайне непродолжительное время, ждать чего-либо неприемлимо. Соответственно и работает быстрее всего.
    - FastMutex работают по принципу - когда блокировка свободна, то используем аппаратный захват, когда занята - ждем на прикрепленному к FastMUtex евенте. То есть если код, который заключен в FastMutex, компактен и выполняется быстро, то тогда периоды, пока блокировка захвачена невелики, а значит ситуация, когда будет захват уже занятой блокировки, редки. Ожидание на объекте (KeWaitForXXX) это продожительная операция. Соответственно часто будет применяться аппаратный захват как в случае со спин-блокировкой и это будет очень быстро. Нужно исходить из того, насколько по времени тебе нужно захватывать блокировку. К слову скажу, что очень часто применяются fast mutex - блокировки редко захватываются на продолжительное время.
    - Mutex всегда использует ожидание на объекте и его сигнализацию при освобождении, что накладно. Не могу придумать применения кроме синхронизации между юзером и кернелом или между различными процессами юзермода. Сама блокировка медленная (по ср. с fastmutex) и захватывается обычно так же на продолжительное время (по сравнению с временем выполнения блокировки).

    Соответственно нужно исходить из времени, на которое планируется захватывать блокировку. Использовать FastMutex
    при захвате на продолжительное время нерационально, по быстродействию приблизится к мьютексам. Использовать мьютексы при захвате на небольшое время тоже нерационально - время захвата блокировки может оказаться больше времени, на которое её захватывают.
    А спинлоки вообще из другой оперы, они для других случаев, x64 +1.
     
  13. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    x64
    Для вас коденг в NT сплошной гемор, ибо в экспорт вынесено не много чего.)
     
  14. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Clerk
    Не надо советовать недокументированных решений там, где все обходится без них или гораздо проще с ними. Основная мысль - для каждого инструмента своё назначение. Ты палишь из пушки по воробьям - вы с ТС пишете разных типов драйвера.
     
  15. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    Если не говорить о руткитах, то вполне достаточно.
     
  16. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    И, кстати говоря, автору рекомендую посмотреть на то, как именно реализованы FastMutex (и похожие на них юзермодные CRITICAL_SECTION). Многое прояснится - все выводы в своем посте выше я сделал из созерцания кода захвата-освобождения CRITICAL_SECTION когда-то :)
     
  17. T800

    T800 Member

    Публикаций:
    0
    Регистрация:
    7 дек 2006
    Сообщения:
    293
    Адрес:
    Moscow
    Ок. Спасибо за подробные ответы.

    Great
    Ф-ия _IsAdded() будет работать некорректно, т.к. в момент исполнения данной ф-ии со списком процессов может работать другой поток (удаление/добавление Item'ом из перехваченных Nt* ф-ий). Поэтому везде сейчаз использую FastMutex.

    Перехват идёт на уровне таблицы KeServiceDescriptorTable->ntoskrnl.ServiceTable
    Всё это для того делаю, что бы научиться в ring0 корректно работать с глобальными переменными (используюя синхронизацию). Практической цели пока нету.


    А на вопрос про корректный DriverUnload так никто и не ответил. Попробую его переформулировать.
    Ф-ия NewNtOpenProcess может быть вызвана до того как в DriverUnload я в таблицу ntoskrnl.ServiceTable верну истинное значение. Ф-ии DriverUnload и NewNtOpenProcess вополняются разными потоками. И система может попытаться выгрузить дров в тот момент, когда ещё eip не дошёл до retn в ф-ии NewNtOpenProcess.
    Это возможно? Как этого избежать? Я в чём то заблуждаюсь?
     
  18. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    Конечно, потому что подчеркивание означает, что она внутренняя. Просто такое разделение будет наиболее универсально. А вызывать извне нужно только IsAdded, в которой будет скрыта синхронизация.
    Ну теперь понятно, просто речь была про ntdll.
    В ядре же в нт-сервисах: если PreviousMode ==Usermode, тогда IRQL всегда 0. В случае KernelMode некоторые сервисы могут вызываться и на APC_LEVEL, но выше 1 IRQL не будет.
    Стандартная проблема. Решается так же стандартно, например, есть такая I/O Remove Lock - смотри MSDN (IoAcquireRemoveLock и компания). Нужна как раз для того, чтобы синхронизировать выгрузку драйвера с callback-функциями, можно использовать в перехватах.
    Когда хуки выполняются недолго я предпочитаю использовать "компактный" вариант - глобальная переменная LONG RemoveLockCount = 0; и следующий код:
    перехват:
    - в начале lock inc [RemoveLockCount]
    - при передаче управления на оригинальную функцию:
    Код (Text):
    1. cli
    2. push dword ptr [pOriginalFunction]
    3. lock dec [RemoveLockCount]
    4. sti ; фактически IF будет установлен после следующей команды - RET
    5. ret
    - при возврате в вызвавшую функцию:
    Код (Text):
    1. cli
    2. mov eax, Status
    3. lock dec [RemoveLockCount]
    4. sti
    5. ret SizeOfParameters
    в DriverUnload:
    Код (Text):
    1. // снять хук..
    2. ...
    3. // ждать
    4. KdPrint(("Waiting for hooks.. (RemoveLockCount = %ld)\n", RemoveLockCount));
    5. while (RemoveLockCount > 0)
    6. {
    7.   KdPrint(("."));
    8.   // а тут переключить контекст
    9.   ZwYieldExecution();
    10.   // или ждать немножко
    11.   LARGE_INTEGER timeout = { -10000*100, -1 }; // 100мс
    12.   KeDelayExecutionThread (KernelMode, FALSE, &timeout);
    13. }
    14. KdPrint("\nReady to unload\n");
    Не шибко корректно, но я предпочитаю это в случае небольших размеров кода перехвата или же в случае, когда нельзя вызывать IoAcquireRemoveLock - если перехват выполняется на высоких IRQL.
     
  19. x64

    x64 New Member

    Публикаций:
    0
    Регистрация:
    29 июл 2008
    Сообщения:
    1.370
    Адрес:
    Россия
    Правильнее было бы реализовать событие, сигнализирующее о том, что все перехватчики завершили своё выполнение. В DriverUnload() снимать хуки, ждать этого события и только потом выгружаться. Ну а само событие сигнализировать в функции дереференса при условии (gbShuttingDown && uHookRefCount == 0).
     
  20. T800

    T800 Member

    Публикаций:
    0
    Регистрация:
    7 дек 2006
    Сообщения:
    293
    Адрес:
    Moscow
    Great
    Ну глобальный счётчик я позавчера добавил. Правда без cli и sti , т.к. полагаю что INC для size_t атомарная операция.
    А ZwYieldExecution и KeDelayExecutionThread обязательно добавлю!