Перечисление процессов в ядре

Тема в разделе "WASM.NT.KERNEL", создана пользователем Marylin, 28 ноя 2024.

Метки:
  1. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    196
    Всем привет!
    Win7-x64. Задача - драйвером перечислить все активные процессы, и вытащить из их структур EPROCESS по несколько полей. Для этого функцией MmGetSystemRoutineAddress() читаю значение переменной ядра "PsInitialSystemProcess", и начиная с процесса System двигаюсь вперёд по линкам "EPROCESS.ActiveProcessLinks".

    Всё работает нормально до тех пор, пока сделав полный круг не упираюсь в предыдущий от System самый первый "процесс бездействия" Idle, чтение из которого генерит почему-то BSOD. Поскольку у него нет Pid'a да и вся EPROCESS кривая, я не могу опознать его, чтобы остановиться. Была идея крутить цикл от System до своего процесса, т.к. по логике вещей после запуска свой должен встать последним. Кто-нибудь знает, как решить проблему с этим Idle? Для наглядности вот схема, по которой работает мой дров:
    Код (Text):
    1. 0: kd> dp nt!PsInitialSystemProcess L1
    2. fffff800`02af9028   fffffa80`01890040
    3.  
    4. 0: kd> dt _eprocess fffffa80`01890040 UniqueProcessId ActiveProcessLinks ImageFileName
    5. nt!_EPROCESS
    6.    +0x180 UniqueProcessId    : 0x00000000`00000004 Void
    7.    +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`02ada1c8 - 0xfffff800`02a77960 ]
    8.    +0x2e0 ImageFileName      : [15]  "System"
    9. 0: kd>
    10. 0: kd> !cmkd.ptelist -v 0xfffffa8001890040
    11. VA=FFFFFA8001890040
    12.    PXE Idx=1F5  Va=FFFFF6FB7DBEDFA8  Contents=0000000004000863  Hard Pfn=00004000  Attr=---DA--KWEV
    13.    PPE Idx=000  Va=FFFFF6FB7DBF5000  Contents=0000000004001863  Hard Pfn=00004001  Attr=---DA--KWEV
    14.    PDE Idx=00C  Va=FFFFF6FB7EA00060  Contents=000000007F4009E3  Hard Pfn=0007F400  Attr=-GLDA--KWEV
    15.    PTE Idx=090  Va=FFFFF6FD4000C480  -NA-
    16. 0: kd>
    Здесь видно, что атрибуты у страницы = Guard + Long, т.е. больше дефолтных 4Кб.
    Далее шаг вперёд flink и попадаю в следующий smss.exe, где всё аналогично:
    Код (Text):
    1. 0: kd> dt _eprocess (0xfffffa8002ada1c8-0x188) UniqueProcessId ActiveProcessLinks ImageFileName
    2. nt!_EPROCESS
    3.    +0x180 UniqueProcessId    : 0x00000000`0000010c Void
    4.    +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`02fb1c88 - 0xfffffa80`018901c8 ]
    5.    +0x2e0 ImageFileName      : [15]  "smss.exe"
    6. 0: kd>
    7. 0: kd> !cmkd.ptelist -v 0xfffffa8002ada040
    8. VA=FFFFFA8002ADA040
    9.    PXE Idx=1F5  Va=FFFFF6FB7DBEDFA8  Contents=0000000004000863  Hard Pfn=00004000  Attr=---DA--KWEV
    10.    PPE Idx=000  Va=FFFFF6FB7DBF5000  Contents=0000000004001863  Hard Pfn=00004001  Attr=---DA--KWEV
    11.    PDE Idx=015  Va=FFFFF6FB7EA000A8  Contents=000000007E2009E3  Hard Pfn=0007E200  Attr=-GLDA--KWEV
    12.    PTE Idx=0DA  Va=FFFFF6FD400156D0  -NA-
    13. 0: kd>
    Но если пойти назад blink от System, то попадаю в процесс Idle, у которого во-первых Pid невалидный, а во-вторых атрибуты страницы уже мелкие 4К без Long + Exe. Но что интересно flink у него правильный, т.к. смотрит на System. В общем хрень какая-то.
    Код (Text):
    1. 0: kd> dt _eprocess (0xfffff80002a77960-0x188) UniqueProcessId ActiveProcessLinks ImageFileName
    2. nt!_EPROCESS
    3.    +0x180 UniqueProcessId    : 0x00000010`63110522 Void
    4.    +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`018901c8 - 0xfffffa80`026bd1e8 ]
    5.    +0x2e0 ImageFileName      : [15]  ""
    6. 0: kd>
    7. 0: kd> !cmkd.ptelist -v 0xfffff80002a777d8
    8. VA=FFFFF80002A777D8
    9.    PXE Idx=1F0  Va=FFFFF6FB7DBEDF80  Contents=0000000000199063  Hard Pfn=00000199  Attr=---DA--KWEV
    10.    PPE Idx=000  Va=FFFFF6FB7DBF0000  Contents=0000000000198063  Hard Pfn=00000198  Attr=---DA--KWEV
    11.    PDE Idx=015  Va=FFFFF6FB7E0000A8  Contents=00000000001DB063  Hard Pfn=000001DB  Attr=---DA--KWEV
    12.    PTE Idx=077  Va=FFFFF6FC000153B8  Contents=8000000002A77963  Hard Pfn=00002A77  Attr=-G-DA--KW-V
    13. 0: kd>
     
  2. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    А тебе обязательно перебирать их через ссылки в EPROCESS? Почему не взять NtQuerySystemInformation, и дальше запрашивать PEPROCESS для каждого процесса отдельно?
    В твоей схеме проблема не только с Idle, но и с тем, что любой процесс может завершиться в любое время, и ты начнёшь обращаться к невалидной памяти.
    Если же референсить по PID'ам, полученная ссылка на процесс гарантирует, что EPROCESS останется валидным, пока ты с ним работаешь.

    А если всё-таки хочется ансейфа, и если в твоих тестах получается, что Idle всегда стоит на Blink от PsInitialSystemProcess, просто прочитай первым делом Blink, и иди от System вперёд, пока его не встретишь.
    --- Сообщение объединено, 29 ноя 2024 ---
    Или можешь прочитать переменную PsIdleProcess, найдя её по символам или по захардкоженному смещению.
    Система сама при переборе процессов сравнивает их с PsIdleProcess - можешь делать так же:
    upload_2024-11-29_22-5-10.png
     
    Последнее редактирование: 29 ноя 2024
    Marylin нравится это.
  3. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    196
    Так мне казалось наоборот..
    Если сделать снимок сейчас и парсить структуры позже, тогда могу нарваться, что процесс исчезнет. А в моём случае я сканирую структуры в реальном времени - просто блокировать доступ спинлоками и всё. Или я ошибаюсь?
    А вот это походу решение. Чёто я сразу не додумался..
    Спасибо большое - буду пробовать.
    --- Сообщение объединено, 30 ноя 2024 ---
    NtQuerySystemInformation() не подходит тем,
    что во-первых для её вызова даже драйвер не нужен (можно под юзером),
    а во-вторых она не возвращает то-что мне требуется, например адрес самой EPROCESS.
     
  4. Ahimov

    Ahimov Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    41
    Ссылка на ядерный обьект получается через ObReferenceObjectX, что синхронизирует доступ, но апи для слабаков ;)
     
    Marylin нравится это.
  5. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    196
    Вот блин только дошло..
    ObReferenceObject() увеличивает счётчик-ссылок на объект процесса,
    в результате чего его нельзя будет выгрузить, и я получу валидную структуру.
    Просто только вливаюсь в тему драйверов, поэтому ещё много непознанного.

    Например в данном случае объектом является процесс,
    а его OBJECT_HEADER будет лежать по адресу EPROCESS-0x30 (размер заголовка).
    Тогда в отладчике получаю:
    Код (Text):
    1. 0: kd> !process 0 0 system   <-----// или любой другой
    2. PROCESS fffffa8001890040
    3.     SessionId: none    Cid: 0004    Peb: 00000000     ParentCid: 0000
    4.     DirBase: 00187000  ObjectTable: fffff8a000001940  HandleCount: 603.
    5.     Image: System
    6.  
    7. 0: kd> dt _object_header (0xfffffa8001890040-0x30)
    8. nt!_OBJECT_HEADER
    9.    +0x000 PointerCount       : 0n137   <-------------// ObReferenceObject() сделает +1
    10.    +0x008 HandleCount        : 0n3
    11.    +0x010 Lock               : _EX_PUSH_LOCK
    12.    +0x018 TypeIndex          : 0x7     <-------------// 7 = Process or Thread
    13.    +0x019 TraceFlags         : 0
    14.    +0x01a InfoMask           : 0
    15.    +0x01b Flags              : 0x2
    16.    +0x020 ObjectCreateInfo   : 0xfffff800`02a09e40  _OBJECT_CREATE_INFORMATION
    17.    +0x028 SecurityDescriptor : 0xfffff8a0`00004b0c
    18.    +0x030 Body               : _QUAD   <------------// EPROCESS
     
    Последнее редактирование: 30 ноя 2024
  6. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Пускай исчезает: PsLookupProcessByProcessId или вернёт тебе PEPROCESS, и тогда процесс есть и уже зареференсен (а значит, с ним можно безопасно работать, не боясь, что он завершится), или вернёт NULL, если процесса уже нет.
    И да, под этим PID’ом может быть уже другой процесс, если между снапшотом и референсом этот процесс завершился и создался новый, которому система дала тот же PID.

    С Zw-префиксом, конечно. Вызываешь ZwQueryInformationProcess, получаешь снапшот.
    Затем идёшь по каждому процессу и получаешь PEPROCESS по PID’у через PsLookupProcessByProcessId.
    Когда закончишь с процессом, дереференсишь его EPROCESS через ObDereferenceObject.

    Не получишь: в момент, когда ты вручную будешь референсить структуру, процесс уже может быть в состоянии удаления или уже может быть удалён.

    Блокировать доступ кому?
    Ты заведёшь спинлок, залочишь его, начнёшь бегать по этим процессам, но ядро про твой спинлок ничего не знает и так же беспрепятственно будет удалять и добавлять процессы.
    И то, что спинлок поднимает IRQL, отключая планировщик, тебя не спасёт, т.к. планировщик отключается только на твоём ядре: другие ничего про это не знают, продолжая переключать задачи.

    ObReferenceObject можно использовать только если есть гарантия, что нижележащий объект не может быть уничтожен, пока ты пытаешься взять на него ссылку. Проще говоря, референс можно делать только если ты УЖЕ владеешь как минимум одной ссылкой на объект.
    В случае ТС’а на момент референса у него ссылок на процесс нет, а значит, ObReferenceObject его не спасёт, т.к. будет начнёт падать на самом референсе, если в этот момент процесс уже перестал существовать или уже завершается.
     
    Marylin нравится это.
  7. Marylin

    Marylin Active Member

    Публикаций:
    0
    Регистрация:
    17 фев 2023
    Сообщения:
    196
    Понятно.. :good2:
    Так Вальтер Онил говорит, что спинлок блокирует доступ к ресурсу, чтобы другие ядра ЦП не вмешивались, пока я не освобожу его. Иначе какой в нём смысл, ведь учитывая IRQL=2 на одном ядре от него толку нет? Как ос может удалить EPROCESS, если я удерживаю его спинлоком? Спин зареган в структуре PCR ядра, и соседнее ядро наверное проверяет его по IPI, перед тем как что-то делать с залоченной областью. Нет?
    Код (Text):
    1. 0: kd> dt _kpcr @$pcr
    2. nt!_KPCR
    3.    +0x000 NtTib            : _NT_TIB
    4.    +0x000 GdtBase          : 0xfffff800`00b9c000  _KGDTENTRY64
    5.    +0x008 TssBase          : 0xfffff800`00b9b000  _KTSS64
    6.    +0x010 UserRsp          : 0x00000000`07b1f2e8
    7.    +0x018 Self             : 0xfffff800`029f5000  _KPCR
    8.    +0x020 CurrentPrcb      : 0xfffff800`029f5180  _KPRCB
    9.    +0x028 LockArray        : 0xfffff800`029f57f0  _KSPIN_LOCK_QUEUE  <----------//
    10.    +0x030 Used_Self        : 0x000007ff`fffae000   Void
    11.    +0x038 IdtBase          : 0xfffff800`00b9a000  _KIDTENTRY64
    12.    +0x040 Unused           : [2] 0
    13.    +0x050 Irql             : 0 ''
    14.    +0x051 SecondCacheAssoc : 0x8 ''
    15.    +0x052 ObsoleteNumber   : 0 ''
    16.    +0x053 Fill0            : 0 ''
    17.    +0x054 Unused0          : [3] 0
    18.    +0x060 MajorVersion     : 1
    19.    +0x062 MinorVersion     : 1
    20.    +0x064 StallScaleFactor : 0x9c3
    21.    +0x068 Unused1          : [3] (null)
    22.    +0x080 KernelReserved   : [15] 0
    23.    +0x0bc SecondCacheSize  : 0x200000
    24.    +0x0c0 HalReserved      : [16] 0x9502f900
    25.    +0x100 Unused2          : 0
    26.    +0x108 KdVersionBlock   : (null)
    27.    +0x110 Unused3          : (null)
    28.    +0x118 PcrAlign1        : [24] 0
    29.    +0x180 Prcb             : _KPRCB
    30. 0: kd>
    --- Сообщение объединено, 30 ноя 2024 ---
    Здесь обсуждали подобную проблему: https://community.osr.com/t/get-reference-count-of-kernel-object/51859/2
     
  8. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Верно, но чтобы это работало, доступ к объекту должен из всех потоков идти через общий объект синхронизации.
    Ядро про твой спинлок ничего не знает, у него там свои блокировки.
    IRQL = DISPATCH_LEVEL там для другого: чтобы гарантировать, что твой спинлок не останется залоченным на неопределённое время из-за планировщика, потому что в это время другим потокам, которые попытаются захватить этот спинлок, потребуется впустую сжигать такты.
    Вот чтобы такого не происходило, в спинлоке и поднимают IRQL, поскольку предполагается, что действия под спинлоком максимально простые (буквально свопнуть пару указателей или поменять несколько чисел - и всё), а значит, на это время можно запретить перепланирование.
    А как связана какая-то очередь спинлоков в KPCR с синхронизацией именно списка процессов?
    EPROCESS ты ничем не удерживаешь (тем более, какой EPROCESS? - у тебя же их много), он может стать невалидным в любое время.

    По твоей ссылке буквально говорят то же самое: у тебя есть гонка между захватом ссылки и освобождением объекта.
    Если тебе ядро не выдало заведомо зареференсенный объект - самостоятельно референсить нельзя, поскольку ядро референсит под всеми нужными блокировками (и только ядро знает, что и как надо лочить), а твой референс или не синхронизируется вообще, или нет гарантий, что синхронизируется правильно с учётом всех нюансов.
    --- Сообщение объединено, 30 ноя 2024 ---
    Вот смотри, как ядро референсит при переборе процессов, сколько здесь нюансов: заходит в Guarded-регион, чтобы отключить Normal- и Special Kernel APC, а также захватывает пуш-лок PspActiveProcessLock:

    upload_2024-11-30_17-0-11.png
     
    Последнее редактирование: 30 ноя 2024
    Marylin нравится это.