Всем привет! Win7-x64. Задача - драйвером перечислить все активные процессы, и вытащить из их структур EPROCESS по несколько полей. Для этого функцией MmGetSystemRoutineAddress() читаю значение переменной ядра "PsInitialSystemProcess", и начиная с процесса System двигаюсь вперёд по линкам "EPROCESS.ActiveProcessLinks". Всё работает нормально до тех пор, пока сделав полный круг не упираюсь в предыдущий от System самый первый "процесс бездействия" Idle, чтение из которого генерит почему-то BSOD. Поскольку у него нет Pid'a да и вся EPROCESS кривая, я не могу опознать его, чтобы остановиться. Была идея крутить цикл от System до своего процесса, т.к. по логике вещей после запуска свой должен встать последним. Кто-нибудь знает, как решить проблему с этим Idle? Для наглядности вот схема, по которой работает мой дров: Код (Text): 0: kd> dp nt!PsInitialSystemProcess L1 fffff800`02af9028 fffffa80`01890040 0: kd> dt _eprocess fffffa80`01890040 UniqueProcessId ActiveProcessLinks ImageFileName nt!_EPROCESS +0x180 UniqueProcessId : 0x00000000`00000004 Void +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`02ada1c8 - 0xfffff800`02a77960 ] +0x2e0 ImageFileName : [15] "System" 0: kd> 0: kd> !cmkd.ptelist -v 0xfffffa8001890040 VA=FFFFFA8001890040 PXE Idx=1F5 Va=FFFFF6FB7DBEDFA8 Contents=0000000004000863 Hard Pfn=00004000 Attr=---DA--KWEV PPE Idx=000 Va=FFFFF6FB7DBF5000 Contents=0000000004001863 Hard Pfn=00004001 Attr=---DA--KWEV PDE Idx=00C Va=FFFFF6FB7EA00060 Contents=000000007F4009E3 Hard Pfn=0007F400 Attr=-GLDA--KWEV PTE Idx=090 Va=FFFFF6FD4000C480 -NA- 0: kd> Здесь видно, что атрибуты у страницы = Guard + Long, т.е. больше дефолтных 4Кб. Далее шаг вперёд flink и попадаю в следующий smss.exe, где всё аналогично: Код (Text): 0: kd> dt _eprocess (0xfffffa8002ada1c8-0x188) UniqueProcessId ActiveProcessLinks ImageFileName nt!_EPROCESS +0x180 UniqueProcessId : 0x00000000`0000010c Void +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`02fb1c88 - 0xfffffa80`018901c8 ] +0x2e0 ImageFileName : [15] "smss.exe" 0: kd> 0: kd> !cmkd.ptelist -v 0xfffffa8002ada040 VA=FFFFFA8002ADA040 PXE Idx=1F5 Va=FFFFF6FB7DBEDFA8 Contents=0000000004000863 Hard Pfn=00004000 Attr=---DA--KWEV PPE Idx=000 Va=FFFFF6FB7DBF5000 Contents=0000000004001863 Hard Pfn=00004001 Attr=---DA--KWEV PDE Idx=015 Va=FFFFF6FB7EA000A8 Contents=000000007E2009E3 Hard Pfn=0007E200 Attr=-GLDA--KWEV PTE Idx=0DA Va=FFFFF6FD400156D0 -NA- 0: kd> Но если пойти назад blink от System, то попадаю в процесс Idle, у которого во-первых Pid невалидный, а во-вторых атрибуты страницы уже мелкие 4К без Long + Exe. Но что интересно flink у него правильный, т.к. смотрит на System. В общем хрень какая-то. Код (Text): 0: kd> dt _eprocess (0xfffff80002a77960-0x188) UniqueProcessId ActiveProcessLinks ImageFileName nt!_EPROCESS +0x180 UniqueProcessId : 0x00000010`63110522 Void +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`018901c8 - 0xfffffa80`026bd1e8 ] +0x2e0 ImageFileName : [15] "" 0: kd> 0: kd> !cmkd.ptelist -v 0xfffff80002a777d8 VA=FFFFF80002A777D8 PXE Idx=1F0 Va=FFFFF6FB7DBEDF80 Contents=0000000000199063 Hard Pfn=00000199 Attr=---DA--KWEV PPE Idx=000 Va=FFFFF6FB7DBF0000 Contents=0000000000198063 Hard Pfn=00000198 Attr=---DA--KWEV PDE Idx=015 Va=FFFFF6FB7E0000A8 Contents=00000000001DB063 Hard Pfn=000001DB Attr=---DA--KWEV PTE Idx=077 Va=FFFFF6FC000153B8 Contents=8000000002A77963 Hard Pfn=00002A77 Attr=-G-DA--KW-V 0: kd>
А тебе обязательно перебирать их через ссылки в EPROCESS? Почему не взять NtQuerySystemInformation, и дальше запрашивать PEPROCESS для каждого процесса отдельно? В твоей схеме проблема не только с Idle, но и с тем, что любой процесс может завершиться в любое время, и ты начнёшь обращаться к невалидной памяти. Если же референсить по PID'ам, полученная ссылка на процесс гарантирует, что EPROCESS останется валидным, пока ты с ним работаешь. А если всё-таки хочется ансейфа, и если в твоих тестах получается, что Idle всегда стоит на Blink от PsInitialSystemProcess, просто прочитай первым делом Blink, и иди от System вперёд, пока его не встретишь. --- Сообщение объединено, 29 ноя 2024 --- Или можешь прочитать переменную PsIdleProcess, найдя её по символам или по захардкоженному смещению. Система сама при переборе процессов сравнивает их с PsIdleProcess - можешь делать так же:
Так мне казалось наоборот.. Если сделать снимок сейчас и парсить структуры позже, тогда могу нарваться, что процесс исчезнет. А в моём случае я сканирую структуры в реальном времени - просто блокировать доступ спинлоками и всё. Или я ошибаюсь? А вот это походу решение. Чёто я сразу не додумался.. Спасибо большое - буду пробовать. --- Сообщение объединено, 30 ноя 2024 --- NtQuerySystemInformation() не подходит тем, что во-первых для её вызова даже драйвер не нужен (можно под юзером), а во-вторых она не возвращает то-что мне требуется, например адрес самой EPROCESS.
Ссылка на ядерный обьект получается через ObReferenceObjectX, что синхронизирует доступ, но апи для слабаков
Вот блин только дошло.. ObReferenceObject() увеличивает счётчик-ссылок на объект процесса, в результате чего его нельзя будет выгрузить, и я получу валидную структуру. Просто только вливаюсь в тему драйверов, поэтому ещё много непознанного. Например в данном случае объектом является процесс, а его OBJECT_HEADER будет лежать по адресу EPROCESS-0x30 (размер заголовка). Тогда в отладчике получаю: Код (Text): 0: kd> !process 0 0 system <-----// или любой другой PROCESS fffffa8001890040 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000 DirBase: 00187000 ObjectTable: fffff8a000001940 HandleCount: 603. Image: System 0: kd> dt _object_header (0xfffffa8001890040-0x30) nt!_OBJECT_HEADER +0x000 PointerCount : 0n137 <-------------// ObReferenceObject() сделает +1 +0x008 HandleCount : 0n3 +0x010 Lock : _EX_PUSH_LOCK +0x018 TypeIndex : 0x7 <-------------// 7 = Process or Thread +0x019 TraceFlags : 0 +0x01a InfoMask : 0 +0x01b Flags : 0x2 +0x020 ObjectCreateInfo : 0xfffff800`02a09e40 _OBJECT_CREATE_INFORMATION +0x028 SecurityDescriptor : 0xfffff8a0`00004b0c +0x030 Body : _QUAD <------------// EPROCESS
Пускай исчезает: PsLookupProcessByProcessId или вернёт тебе PEPROCESS, и тогда процесс есть и уже зареференсен (а значит, с ним можно безопасно работать, не боясь, что он завершится), или вернёт NULL, если процесса уже нет. И да, под этим PID’ом может быть уже другой процесс, если между снапшотом и референсом этот процесс завершился и создался новый, которому система дала тот же PID. С Zw-префиксом, конечно. Вызываешь ZwQueryInformationProcess, получаешь снапшот. Затем идёшь по каждому процессу и получаешь PEPROCESS по PID’у через PsLookupProcessByProcessId. Когда закончишь с процессом, дереференсишь его EPROCESS через ObDereferenceObject. Не получишь: в момент, когда ты вручную будешь референсить структуру, процесс уже может быть в состоянии удаления или уже может быть удалён. Блокировать доступ кому? Ты заведёшь спинлок, залочишь его, начнёшь бегать по этим процессам, но ядро про твой спинлок ничего не знает и так же беспрепятственно будет удалять и добавлять процессы. И то, что спинлок поднимает IRQL, отключая планировщик, тебя не спасёт, т.к. планировщик отключается только на твоём ядре: другие ничего про это не знают, продолжая переключать задачи. ObReferenceObject можно использовать только если есть гарантия, что нижележащий объект не может быть уничтожен, пока ты пытаешься взять на него ссылку. Проще говоря, референс можно делать только если ты УЖЕ владеешь как минимум одной ссылкой на объект. В случае ТС’а на момент референса у него ссылок на процесс нет, а значит, ObReferenceObject его не спасёт, т.к. будет начнёт падать на самом референсе, если в этот момент процесс уже перестал существовать или уже завершается.
Понятно.. Так Вальтер Онил говорит, что спинлок блокирует доступ к ресурсу, чтобы другие ядра ЦП не вмешивались, пока я не освобожу его. Иначе какой в нём смысл, ведь учитывая IRQL=2 на одном ядре от него толку нет? Как ос может удалить EPROCESS, если я удерживаю его спинлоком? Спин зареган в структуре PCR ядра, и соседнее ядро наверное проверяет его по IPI, перед тем как что-то делать с залоченной областью. Нет? Код (Text): 0: kd> dt _kpcr @$pcr nt!_KPCR +0x000 NtTib : _NT_TIB +0x000 GdtBase : 0xfffff800`00b9c000 _KGDTENTRY64 +0x008 TssBase : 0xfffff800`00b9b000 _KTSS64 +0x010 UserRsp : 0x00000000`07b1f2e8 +0x018 Self : 0xfffff800`029f5000 _KPCR +0x020 CurrentPrcb : 0xfffff800`029f5180 _KPRCB +0x028 LockArray : 0xfffff800`029f57f0 _KSPIN_LOCK_QUEUE <----------// +0x030 Used_Self : 0x000007ff`fffae000 Void +0x038 IdtBase : 0xfffff800`00b9a000 _KIDTENTRY64 +0x040 Unused : [2] 0 +0x050 Irql : 0 '' +0x051 SecondCacheAssoc : 0x8 '' +0x052 ObsoleteNumber : 0 '' +0x053 Fill0 : 0 '' +0x054 Unused0 : [3] 0 +0x060 MajorVersion : 1 +0x062 MinorVersion : 1 +0x064 StallScaleFactor : 0x9c3 +0x068 Unused1 : [3] (null) +0x080 KernelReserved : [15] 0 +0x0bc SecondCacheSize : 0x200000 +0x0c0 HalReserved : [16] 0x9502f900 +0x100 Unused2 : 0 +0x108 KdVersionBlock : (null) +0x110 Unused3 : (null) +0x118 PcrAlign1 : [24] 0 +0x180 Prcb : _KPRCB 0: kd> --- Сообщение объединено, 30 ноя 2024 --- Здесь обсуждали подобную проблему: https://community.osr.com/t/get-reference-count-of-kernel-object/51859/2
Верно, но чтобы это работало, доступ к объекту должен из всех потоков идти через общий объект синхронизации. Ядро про твой спинлок ничего не знает, у него там свои блокировки. IRQL = DISPATCH_LEVEL там для другого: чтобы гарантировать, что твой спинлок не останется залоченным на неопределённое время из-за планировщика, потому что в это время другим потокам, которые попытаются захватить этот спинлок, потребуется впустую сжигать такты. Вот чтобы такого не происходило, в спинлоке и поднимают IRQL, поскольку предполагается, что действия под спинлоком максимально простые (буквально свопнуть пару указателей или поменять несколько чисел - и всё), а значит, на это время можно запретить перепланирование. А как связана какая-то очередь спинлоков в KPCR с синхронизацией именно списка процессов? EPROCESS ты ничем не удерживаешь (тем более, какой EPROCESS? - у тебя же их много), он может стать невалидным в любое время. По твоей ссылке буквально говорят то же самое: у тебя есть гонка между захватом ссылки и освобождением объекта. Если тебе ядро не выдало заведомо зареференсенный объект - самостоятельно референсить нельзя, поскольку ядро референсит под всеми нужными блокировками (и только ядро знает, что и как надо лочить), а твой референс или не синхронизируется вообще, или нет гарантий, что синхронизируется правильно с учётом всех нюансов. --- Сообщение объединено, 30 ноя 2024 --- Вот смотри, как ядро референсит при переборе процессов, сколько здесь нюансов: заходит в Guarded-регион, чтобы отключить Normal- и Special Kernel APC, а также захватывает пуш-лок PspActiveProcessLock: