Для начала нужно научиться обнаруживать стандартные процессы — знать что из них что делает, к чему относится, как себя ведет и как должен запускаться.
После этого уже можно искать нестандартные процессы.
Способы конечно есть, но объяснять их — значит научить человека администрированию windows на достаточно глубоком уровне. Это нельзя вместить в ответ на вопрос, а грубо говоря целый процесс обучения.
Пользуйтесь Comodo
Cleaning Essentials и KillSwitch, они как раз предназначены для анализа и идентификации скрытых системных процессов. Первая специализируется на поиске вирусов и руткитов, очистке системы, вторая улучшенный аналог Диспечера задач, не только отображает сетевую активность и все процессы в виде древа, но и сверяет все активные по электронной подписи, подсвечивая подозрительные. Ещё там есть Autorun Analyzer, он покажет все процессы и службы в автозапуске. Плюс хорошая мысль добавить отдельно VirusTotal Uploader, чтобы проверять всё подозрительные файлы более чем 50 известными антивирусами, чтоб наверняка.
Как выше сказано — гарантированных способов нет.
Но можно проверить систему DrWeb CureIt, Malwarebytes AntiMalware, AVZ, Kaspersky Free.
У этих программ достаточно сильна эвристика — и они теоретически могут подсказать, если какой-то процесс ведет себя не так, как ожидается, даже если сигнатуры вируса нет в их базах.
Начать с простого и посмотреть сетевой трафик утилитой currports, при обнаружении подозрительной активности изучить процессы тем же process explorer.
Проверяйте каждый процесс
1) Подпись
Нет подписи у экзешника это уже не профессиональное ПО.
2) Место запуска
Если запущено не из програм файлс, то повод задуматься. Иногда ставится скайп или дропбокс в профиль пользователя. Но нормальное ПО. Стоит только в програм файлз, а не темповских папках с замудреными именами
Инструмент AVZ, AnvirrTaskManager
шерстите автозагрузку, плагины браузеров, планировщик задач.
Сначала надо прибить все не системные / неизвестные процессы.
У системных посмотреть используемые библиотеки (эти тулзы показывают)
Есть простенькие программы типа Process Hacker, у него свой драйвер и он увидит скрытый процесс, но вы увидите ещё столько интересного, что без точного знания каждой строчки можете сделать ещё хуже.
Антивирусы тоже видят скрытые процессы и обычно реагируют на них негативно, так что сейчас чаще используют легальные проги нежели сокрытие процесса, либо внедрение в легальный процесс, что в разы безопасней.
Помню как три дня гонялся за полиморфным драйвером алкоголя. Когда поймал понял, для чего разрабы это сделали, много думал…
Не, ну есть же специальные дядьки, которые такие процессы выявляют и записывают в маленькую черненькую книжечку.
Например тот же malwarebytes
Почему бы не довериться им? Неужели на вашем компе взяло и установилось нечто такое, чего они не видывали?
Диспетчер задач является важным инструментом для контроля процессов в операционной системе Windows 11. Он позволяет пользователям просматривать активные процессы и ресурсы компьютера, чтобы определить потенциальные проблемы и улучшить производительность. Однако иногда могут существовать скрытые процессы, которые не отображаются в стандартном списке процессов.
Причины появления скрытых процессов могут быть различными. Некоторые программы могут скрывать свои процессы, чтобы предотвратить обнаружение или отслеживание. Вредоносные программы также могут использовать скрытые процессы для выполнения своих задач без вашего ведома. Поэтому важно знать, как увидеть эти скрытые процессы и принять соответствующие меры.
Для того чтобы увидеть скрытые процессы в диспетчере задач Windows 11, вам потребуется выполнить несколько дополнительных шагов. Во-первых, откройте диспетчер задач, нажав комбинацию клавиш Ctrl + Shift + Esc или щелкнув правой кнопкой мыши на панели задач и выбрав соответствующий пункт меню.
После этого откроется окно диспетчера задач, в котором вы увидите список активных процессов. Чтобы показать скрытые процессы, перейдите в меню «Вид» в верхней части окна и выберите пункт «Показать скрытые процессы». После этого скрытые процессы будут отображены в списке, и вы сможете их просмотреть и контролировать.
Методы обнаружения скрытых процессов
Для обнаружения скрытых процессов можно использовать несколько методов:
Метод | Описание |
---|---|
Использование специализированных программ | Существуют специальные программы, предназначенные для обнаружения скрытых процессов. Эти программы обычно основываются на анализе системных вызовов и поведения процессов, что позволяет выявлять скрытые действия в операционной системе. |
Использование командной строки | |
Анализ системных журналов | Системные журналы Windows могут содержать информацию о запущенных процессах. Путем анализа журналов возможно обнаружить аномальную активность, которая может быть связана со скрытыми процессами. |
Необходимо отметить, что обнаружение скрытых процессов может быть сложной задачей из-за продвинутых методов, которые используют злоумышленники. Поэтому рекомендуется обращаться к специалистам в области информационной безопасности для более эффективного обнаружения и удаления скрытых процессов.
Использование расширенных функций диспетчера задач
Диспетчер задач Windows 11 предоставляет множество полезных функций, которые помогут вам управлять процессами и ресурсами вашего компьютера. Расширенные функции диспетчера задач позволяют вам получить дополнительную информацию о работающих процессах и проследить за их активностью.
Одной из основных функций диспетчера задач является возможность просмотра скрытых процессов. Некоторые программы и процессы могут быть скрыты или работать в фоновом режиме, но с помощью диспетчера задач вы можете увидеть все запущенные процессы и проследить за их загрузкой и использованием ресурсов.
Для того чтобы увидеть скрытые процессы, откройте диспетчер задач, нажмите на вкладку «Детали» и выберите опцию «Показать процессы всех пользователей». Это позволит вам просматривать список всех запущенных процессов, включая скрытые.
Кроме того, расширенные функции диспетчера задач позволяют вам управлять приоритетами процессов, запускать и завершать процессы, а также просматривать дополнительную информацию о процессах, такую как использование ЦП и памяти.
Для изменения приоритета процесса нажмите правой кнопкой мыши на процессе в списке и выберите опцию «Приоритет». Здесь вы можете выбрать один из предложенных вариантов: высокий приоритет, низкий приоритет и т.д. Это позволит вам управлять загрузкой ресурсов и повысить производительность вашего компьютера.
Кроме того, вы можете завершить процесс, который работает некорректно или замедляет работу системы. Для этого выберите процесс, нажмите правой кнопкой мыши и выберите опцию «Завершить задачу». Диспетчер задач позволяет вам контролировать работу процессов и устранять проблемы в случае необходимости.
Использование расширенных функций диспетчера задач позволит вам эффективнее управлять процессами и ресурсами вашего компьютера. Это полезный инструмент, который поможет вам оптимизировать работу системы и решать возникающие проблемы.
Анализ сетевой активности скрытых процессов
Бывают случаи, когда в операционной системе Windows 11 могут быть запущены скрытые процессы, которые не видны в обычном списке задач в диспетчере задач. Отследить и проанализировать такие процессы может быть необходимо для обнаружения потенциальных угроз безопасности или нежелательной сетевой активности.
Один из способов анализа сетевой активности скрытых процессов — использовать сетевые анализаторы или специальные программы, которые могут отображать активные сетевые подключения и процессы, связанные с этими подключениями.
В диспетчере задач Windows 11 можно использовать вкладку «Сеть», чтобы просмотреть активные подключения и процессы, связанные с этими подключениями. Однако она может не показывать скрытые процессы. Для обнаружения скрытых процессов можно воспользоваться специализированными инструментами, такими как Process Explorer или TcpView.
Process Explorer предоставляет более подробную информацию о процессах, чем обычный диспетчер задач, и может показать скрытые процессы. Он также позволяет отследить сетевую активность процессов и просмотреть открытые сетевые соединения.
TcpView является утилитой, разработанной компанией Sysinternals, и предоставляет подробную информацию о текущих сетевых подключениях и процессах, связанных с этими подключениями. Она может быть полезна для обнаружения скрытых процессов и нежелательной сетевой активности.
Анализ сетевой активности скрытых процессов может быть полезным инструментом для обнаружения и предотвращения потенциальных угроз безопасности или нежелательной сетевой активности. Обнаружение и удаление скрытых процессов может помочь обеспечить безопасность операционной системы Windows 11 и личных данных пользователей.
Проверка цифровой подписи процесса
Для проверки цифровой подписи процесса в диспетчере задач Windows 11, следуйте этим шагам:
- Откройте диспетчер задач Windows 11, нажав комбинацию клавиш Ctrl + Shift + Esc.
- Переключитесь на вкладку Подробности.
- Щелкните правой кнопкой мыши на заголовке столбца и выберите Выбрать столбцы.
- В открывшемся окне Выбор столбцов прокрутите вниз и установите флажок напротив Цифровая подпись.
- Нажмите кнопку OK.
Теперь в диспетчере задач будет отображаться информация о цифровой подписи для каждого процесса. Проверка цифровой подписи позволяет убедиться, что процесс является легитимным и не был изменен или скомпрометирован.
Примечание: некоторые процессы могут не иметь цифровой подписи, это может быть связано с их особенностями или старыми версиями программ.
Проверка цифровой подписи процесса – это важный шаг для обеспечения безопасности системы и предотвращения возможных угроз. Регулярная проверка цифровых подписей поможет вам надежно определить и остановить потенциально опасные процессы в системе Windows 11.
Использование антивирусных программ для обнаружения скрытых процессов
При запуске антивирусной программы она сканирует систему и ищет потенциально опасные процессы. Она может использовать различные методы, такие как поиск подозрительных файлов, анализ активности процессов и сравнение их с базой данных известных вредоносных программ. Если антивирусная программа обнаруживает скрытый процесс, она может предложить удалить его или поместить его в карантин.
Важно отметить, что антивирусные программы не всегда могут обнаруживать все скрытые процессы. Некоторые вредоносные программы могут использовать сложные методы маскировки, чтобы избежать обнаружения. Однако, регулярное обновление антивирусной программы и ее базы данных может увеличить вероятность обнаружения скрытых процессов.
Для обнаружения скрытых процессов с использованием антивирусных программ в Windows 11, следуйте следующим шагам:
1. Установите антивирусную программу с надежными рейтингами и отзывами. |
2. Запустите программу и выполните полное сканирование системы. |
3. Дождитесь завершения сканирования и просмотрите результаты. |
4. Если антивирусная программа обнаружит скрытые процессы, примите соответствующие меры, предложенные программой. |
5. Периодически обновляйте антивирусную программу и ее базу данных для повышения эффективности обнаружения скрытых процессов. |
Использование антивирусных программ является одним из способов обнаружения скрытых процессов в Windows 11. Однако, важно помнить, что ни одна программа не может гарантировать 100% защиту от вредоносных программ. Поэтому рекомендуется обеспечивать компьютер дополнительными мерами безопасности, такими как установка обновлений операционной системы, ограничение доступа к непроверенным сайтам и файлам, и использование актуальных браузеров.
Внимательное наблюдение за системными ресурсами
Когда речь идет о скрытых процессах в операционной системе Windows 11, очень важно уметь внимательно следить за системными ресурсами. Это поможет обнаружить потенциальные проблемы и найти способы оптимизации работы компьютера.
Один из ключевых инструментов для этого — диспетчер задач Windows 11. С его помощью можно просмотреть активные процессы, узнать информацию о загрузке ЦП и использовании памяти, а также найти приложения, потребляющие большое количество ресурсов.
Важно понимать, что некоторые процессы могут быть скрыты и не отображаться в стандартном режиме диспетчера задач. Для увидения скрытых процессов нужно воспользоваться следующими шагами:
- Запустите диспетчер задач Windows 11, нажав комбинацию клавиш Ctrl + Shift + Esc.
- Перейдите на вкладку Детали.
- Щелкните правой кнопкой мыши по заголовку столбца и выберите пункт Выбрать столбцы.
- В окне настроек столбцов поставьте галочку напротив пункта Загрузка ЦП от других пользователей.
- Нажмите кнопку OK.
Теперь диспетчер задач отобразит информацию о всех процессах, включая скрытые, их использование ЦП и другие параметры. Это позволит более детально изучить, какие процессы потребляют наибольшее количество ресурсов и какие программы требуют оптимизации или возможно вызывают проблемы с системой.
Важно уметь анализировать информацию и принимать соответствующие меры. Если вы заметили процессы, потребляющие аномально большое количество ресурсов или вызывающие проблемы с системой, может потребоваться дополнительное исследование и решение проблемы.
Внимательное наблюдение за системными ресурсами поможет поддерживать работу компьютера на оптимальном уровне, предотвращать сбои и улучшать общую производительность системы.
Обнаружение скрытых процессов
Обнаружение скрытых процессов — Архив WASM.RU
Многие пользователи привыкли к тому, что в Windows NT диспетчер задач
показывает все процессы, и многие считают, что скрыться от него вообще
невозможно. На самом деле, скрыть процесс черезвычайно просто. Для этого
существует множество методов, и их реализации доступны в исходниках. Остается
только удивляться, почему так редки трояны использующие эти методики? Их
буквально 1 на 1000 не умеющих скрываться. Я думаю, это объясняется тем, что
авторам троянов лень, ведь для этого необязательно писать что-то свое, всегда
можно взять готовый исходник и вставить в свою программу. Поэтому следует
ожидать, что скоро скрытие процессов будет применяться во всех
широкораспостраненных рядовых троянах.Естественно, от этого нужно иметь защиту. Производители антивирусов и
фаерволлов отстали от жизни, так как их продукты не умеют обнаруживать скрытые
процессы. Для этого существует только несколько утилит, из которых единственной
бесплатной является Klister(работает только на Windows 2000), а за остальные
производители требуют немалых денег. Причем все эти утилиты довольно легко
обходятся.Все имеющиеся сейчас программы для обнаружения скрытых процессов построены на
каком-то одном принципе, поэтому для их обхода можно придумать метод скрытия от
конкретного принципа обнаружения, либо привязываться к одной конкретной
программе, что гораздо проще в реализации. Пользователь купивший коммерческую
программу не может изменить ее, а поэтому привязка к конкретной программе будет
работать достаточно надежно, поэтому этот метод используется в коммерческих
руткитах (например hxdef Golden edition). Единственным выходом будет создание
бесплатной Opensource программы для обнаружения скрытых процессов в которой
будут применены несколько методов обнаружения, что позволит защититься от
фундаментальных принципов скрытия, а от привязки к конкретным программам может
защититься каждый пользователь, для этого нужно всего лишь взять исходники
программы и переделать ее под себя.В этой статье я хочу рассмотреть основные методы обнаружения скрытых
процессов, привести примеры кода использующего эти методы и создать в конце
законченную программу для обнаружения скрытых процессов, которая удовлетворяла
бы всем вышеприведенным требованиям.Обнаружение в User Mode
Для начала рассмотрим простые методы обнаружения, которые могут быть
применены в 3 кольце, без использования драйверов. Они основаны на том, что
каждый запущенный процесс порождает побочные проявления своей деятельности, по
которым его и можно обнаружить. Этими проявлениями могут быть открытые им
хэндлы, окна, созданные системные объекты. От подобных методик обнаружения
несложно скрыться, но для этого нужно учесть ВСЕ побочные проявления работы
процесса. Ни в одном из публичных руткитов это пока еще не сделано (приватные
версии к сожалению ко мне не попали). Юзермодные методы просты в реализации,
безопасны в применении, и могут дать положительный эффект, поэтому их
использованием не стоит пренебрегать.Для начала определимся с форматом данных возвращаемых функциями поиска, пусть
это будут связанные списки:
TProcList = packed record ProcName: array [0..MAX_PATH] of Char;Получение списка процессов через ToolHelp API
Для начала определим образцовую функцию получающую список процессов, с ее
результатами мы будем сравнивать результаты полученные всеми другими
способами:
Получение списка процессов через ToolHelp API. procedure GetToolHelpProcessList(var List: PListStruct); Process: TPROCESSENTRY32; Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snap <> INVALID_HANDLE_VALUE then Process.dwSize := SizeOf(TPROCESSENTRY32); if Process32First(Snap, Process) then GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := Process.th32ProcessID; NewItem^.ParrentPID := Process.th32ParentProcessID; lstrcpy(@NewItem^.ProcessName, Process.szExeFile); until not Process32Next(Snap, Process);Очевидно, что любой скрытый процесс при таком перечислении найден не будет,
поэтому эта функция будет образцовой для отделения скрытых процессов от
нескрытых.Получение списка процессов через Native API
Следующим уровнем проверки будет получение списка процессов через
ZwQuerySystemInformation (Native API). На этом уровне также врядли что-нибудь
обнаружиться, но проверить все-таки стоит.
Получение списка процессов через ZwQuerySystemInformation. procedure GetNativeProcessList(var List: PListStruct); Info := GetInfoTable(SystemProcessesAndThreadsInformation); GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); lstrcpy(@NewItem^.ProcessName, PChar(WideCharToString(Info^.ProcessName.Buffer))); NewItem^.ProcessId := Info^.ProcessId; NewItem^.ParrentPID := Info^.InheritedFromProcessId; Info := pointer(dword(info) + info^.NextEntryDelta); until Info^.NextEntryDelta = 0; VirtualFree(Mem, 0, MEM_RELEASE);Получение списка процессов по списку открытых хэндлов.
Многие программы скрывающие процесс, не скрывают открытые им хэндлы,
следовательно перечислив открытые хэндлы через ZwQuerySystemInformation мы можем
построить список процессов.
Получение списка процессов по списку открытых хэндлов. Возвращает только ProcessId. procedure GetHandlesProcessList(var List: PListStruct); Info: PSYSTEM_HANDLE_INFORMATION_EX; Info := GetInfoTable(SystemHandleInformation); for r := 0 to Info^.NumberOfHandles do if Info^.Information[r].ProcessId <> OldPid then OldPid := Info^.Information[r].ProcessId; GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := OldPid; VirtualFree(Info, 0, MEM_RELEASE);На этом этапе уже можно кое-что обнаружить. Но полагаться на результат такой
проверки не стоит, так как скрыть открытые процессом хэндлы ничуть не сложнее,
чем скрыть сам процесс, просто многие забывают это делать.Получение списка процессов по списку открытых ими окон.
Получив список окон зарегистрированных в системе и вызвав для каждого
GetWindowThreadProcessId можно построить список процессов имеющих
окна.
Получение списка процессов по списку окон. Возвращает только ProcessId. procedure GetWindowsProcessList(var List: PListStruct); function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall; GetWindowThreadProcessId(hwnd, ProcId); if not IsPidAdded(PList^, ProcId) then GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := ProcId; AddItem(PList^, NewItem); EnumWindows(@EnumWindowsProc, dword(@List));Окна не скрывает почти никто, поэтому эта проверка также позволяет что-то
найти, но полагаться на нее тоже не стоит.Получение списка процессов с помощью прямого системного
вызова.Для скрытия процессов в User Mode обычно используется технология внедрения
своего кода в чужие процессы и перехвата функции ZwQuerySystemInformation из
ntdll.dll. Функции ntdll на самом деле являются переходниками к соответствующим
функциям ядра системы, и представляют из себя обращение к интерфейсу системных
вызовов (Int 2Eh в Windows 2000 или sysenter в XP), поэтому самым простым и
эффективным способом обнаружения процессов скрытых Usermode API перехватчиками
будет прямое обращение к интерфейсу системных вызовов минуя API.Вариант функции заменяющей ZwQuerySystemInformation будет выглядеть для
Windows XP так:
Системный вызов ZwQuerySystemInformation для Windows XP. Function XpZwQuerySystemInfoCall(ASystemInformationClass: dword; ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength: pdword): dword; stdcall;В связи с другим интерфейсом системных вызовов, для Windows 2000 этот код
будет выглядеть иначе.
Системный вызов ZwQuerySystemInformation для Windows 2000. Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword; ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength: pdword): dword; stdcall;Теперь остается перечислить процессы не с помощью функций из ntdll.dll, а с
помощью только что определенных функций. Вот код, который это делает:
Получение списка процессов через системный вызов ZwQuerySystemInformation. procedure GetSyscallProcessList(var List: PListStruct); St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation, if St = STATUS_INFO_LENGTH_MISMATCH then until St <> STATUS_INFO_LENGTH_MISMATCH; if St = STATUS_SUCCESS then GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); lstrcpy(@NewItem^.ProcessName, PChar(WideCharToString(Info^.ProcessName.Buffer))); NewItem^.ProcessId := Info^.ProcessId; NewItem^.ParrentPID := Info^.InheritedFromProcessId; Info := pointer(dword(info) + info^.NextEntryDelta); until Info^.NextEntryDelta = 0;Этот метод практически 100% обнаруживает юзермодные руткиты, например все
версии hxdef (в том числе и Golden) им обнаруживаются.Получение списка процессов путем анализа связанных с ним
хэндлов.Также, можно применить еще один метод основанный на перечислении хэндлов. Его
суть состоит в том, чтобы найти не хэндлы открытые искомым процессом, а хэндлы
других процессов связанные с ним. Это могут быть хэндлы самого процесса либо его
потоков. При получении хэндла процесса, можно определить его PID с
ZwQueryInformationProcess. Для потока можно вызвать ZwQueryInformationThread и
получить Id его процесса. Все процессы существующие в системе были кем-то
запущены, следовательно родительские процессы будут иметь их хэндлы (если только
не успели их закрыть), также хэндлы всех работающих процессов имеются в сервере
подсистемы Win32 (csrss.exe). Также в Windows NT активно используются Job
объекты, которые позволяют обьединять процессы (например все процессы
определенного прользователя, или какие-либо службы), следовательно при
нахождении хэндла Job объекта, не стоит принебрегать возможностью получить Id
всех обьединенных им процессов. Делается это с помощью функции
QueryInformationJobObject с классом информации — JobObjectBasicProcessIdList.
Код производящий поиск процесов путем анализа открытых другими процессами
хэндлов будет выглядеть так:{ Получение списка процессов через проверку хэнжлов в других процессах. } procedure GetProcessesFromHandles(var List: PListStruct; Processes, Jobs, Threads: boolean); var HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX; ProcessInfo: PROCESS_BASIC_INFORMATION; hProcess : dword; tHandle: dword; r, l : integer; NewItem: PProcessRecord; Info: PJOBOBJECT_BASIC_PROCESS_ID_LIST; Size: dword; THRInfo: THREAD_BASIC_INFORMATION; begin HandlesInfo := GetInfoTable(SystemHandleInformation); if HandlesInfo <> nil then for r := 0 to HandlesInfo^.NumberOfHandles do if HandlesInfo^.Information[r].ObjectTypeNumber in [OB_TYPE_PROCESS, OB_TYPE_JOB, OB_TYPE_THREAD] then begin hProcess := OpenProcess(PROCESS_DUP_HANDLE, false, HandlesInfo^.Information[r].ProcessId); if DuplicateHandle(hProcess, HandlesInfo^.Information[r].Handle, INVALID_HANDLE_VALUE, @tHandle, 0, false, DUPLICATE_SAME_ACCESS) then begin case HandlesInfo^.Information[r].ObjectTypeNumber of OB_TYPE_PROCESS : begin if Processes and (HandlesInfo^.Information[r].ProcessId = CsrPid) then if ZwQueryInformationProcess(tHandle, ProcessBasicInformation, @ProcessInfo, SizeOf(PROCESS_BASIC_INFORMATION), nil) = STATUS_SUCCESS then if not IsPidAdded(List, ProcessInfo.UniqueProcessId) then begin GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := ProcessInfo.UniqueProcessId; NewItem^.ParrentPID := ProcessInfo.InheritedFromUniqueProcessId; AddItem(List, NewItem); end; end; OB_TYPE_JOB : begin if Jobs then begin Size := SizeOf(JOBOBJECT_BASIC_PROCESS_ID_LIST) + 4 * 1000; GetMem(Info, Size); Info^.NumberOfAssignedProcesses := 1000; if QueryInformationJobObject(tHandle, JobObjectBasicProcessIdList, Info, Size, nil) then for l := 0 to Info^.NumberOfProcessIdsInList - 1 do if not IsPidAdded(List, Info^.ProcessIdList[l]) then begin GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := Info^.ProcessIdList[l]; AddItem(List, NewItem); end; FreeMem(Info); end; end; OB_TYPE_THREAD : begin if Threads then if ZwQueryInformationThread(tHandle, THREAD_BASIC_INFO, @THRInfo, SizeOf(THREAD_BASIC_INFORMATION), nil) = STATUS_SUCCESS then if not IsPidAdded(List, THRInfo.ClientId.UniqueProcess) then begin GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItem^.ProcessId := THRInfo.ClientId.UniqueProcess; AddItem(List, NewItem); end; end; end; CloseHandle(tHandle); end; CloseHandle(hProcess); end; VirtualFree(HandlesInfo, 0, MEM_RELEASE); end;К сожалению, некоторые из вышеприведенных методов позволяют определить только
ProcessId, но не имя процесса. Следовательно, нам нужно уметь получить имя
процесса по pid. ToolHelp API для этого использовать естественно не стоит, так
как процесс можкт быть скрытым, поэтому мы будем открывать память процесса на
чтение и читьть имя из его PEB. Адрес PEB в процессе можно определить с помощью
функции ZwQueryInformationProcess. А вот и код осуществляющий все это:
function GetNameByPid(Pid: dword): string; Info: PROCESS_BASIC_INFORMATION; ProcessParametres: pointer; ImagePath: TUnicodeString; ImgPath: array[0..MAX_PATH] of WideChar; ZeroMemory(@ImgPath, MAX_PATH * SizeOf(WideChar)); hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, Pid); if ZwQueryInformationProcess(hProcess, ProcessBasicInformation, @Info, SizeOf(PROCESS_BASIC_INFORMATION), nil) = STATUS_SUCCESS then if ReadProcessMemory(hProcess, pointer(dword(Info.PebBaseAddress) + $10), @ProcessParametres, SizeOf(pointer), Bytes) and ReadProcessMemory(hProcess, pointer(dword(ProcessParametres) + $38), @ImagePath, SizeOf(TUnicodeString), Bytes) and ReadProcessMemory(hProcess, ImagePath.Buffer, @ImgPath, ImagePath.Length, Bytes) then Result := ExtractFileName(WideCharToString(ImgPath));Естественно, юзермодные методы обнаружения на этом не заканчиваются. Если
приложить немного усилий, то можно придумать еще несколько новых (например
загрузку своей Dll в доступные процессы с помощью SetWindowsHookEx с последующим
анализом списка процессов, где наша Dll оказалась), но пока этих методов нам
хватит. Их достоинство в том, что они просты в программировании, но позволяют
обнаружить только процессы скрытые API перехватом в User Mode, либо плохо
скрытые из Kernel Mode. Для действительно надежного обнаружения скрытых
процессов нам придется писать драйвер и работать с внутренними структурами ядра
Windows.Kernel Mode detection
Вот мы и дошли до методов обнаружения скрытых процессов в режиме ядра. От
юзермодных методов они отличаются в первую очередь тем, что списки процессов
можно построить не используя API, а работая напрямую с структурами планировщика.
Скрыться от таких методов обнаружения гораздо труднее, так как они основаны на
самых принципах работы системы, и удаление всех следов процесса из списков
планировщика приведет к невозможности его работы.Что представляет из себя процесс изнутри? Каждый процесс имеет свое адресное
пространство, свои дескрипторы, потоки, и.т.д. С этими вещами связаны
соответствующие структуры ядра. Каждый процесс описывается структурой EPROCESS,
структуры всех процессов связаны в кольцевой двухсвязный список. Один из методов
скрытия процессов заключается в изменении указателей так, чтобы перечисление шло
в обход скрываемого процесса. Для работы процесса некритично, будет ли он
участвовать в перечислении или нет. Но структура EPROCESS всегда должна быть,
она необходима для работы процесса. Большинство методов обнаружения скрытых
процессов в Kernel Mode так или иначе связаны с обнаружением этой структуры.Сначала определимся с форматом хранения полученной информации о процессах.
Формат этот должен быть удобен для передачи из драйвера в приложение. Пусть этим
форматом будет следующая структура:
typedef struct _ProcessRecord } TProcessRecord, *PProcessRecord;Пусть структуры располагаются в памяти по порядку, и у последней из них
сброшен флаг Present.Получение списка процессов через ZwQuerySystemInformation в
ядре.Начнем как всегда с простого, с получения образцового списка процессов через
ZwQuerySystemInformation:
PVOID GetNativeProcessList(ULONG *MemSize) PVOID Info = GetInfoTable(SystemProcessesAndThreadsInformation); if (!Info) return NULL; else Proc = Info; Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta); } while (Proc->NextEntryDelta); *MemSize = (PsCount + 1) * sizeof(TProcessRecord); Mem = ExAllocatePool(PagedPool, *MemSize); if (!Mem) return NULL; else Data = Mem; Proc = (PSYSTEM_PROCESSES)((ULONG)Proc + Proc->NextEntryDelta); wcstombs(Data->ProcessName, Proc->ProcessName.Buffer, 255); Data->ProcessId = Proc->ProcessId; Data->ParrentPID = Proc->InheritedFromProcessId; PsLookupProcessByProcessId((HANDLE)Proc->ProcessId, &Data->pEPROCESS); ObDereferenceObject(Data->pEPROCESS); } while (Proc->NextEntryDelta); Data->Present = FALSE;Пусть эта функция будет образцовой, так как любой Kernel Mode метод скрытия
процесса не будет ею обнаружен. Но юзермодные руткиты типа hxdef будут здесь
обнаружены.В этом коде применяеся функция GetInfoTable для простого получения
информации. Для того чтобы не возникало вопросов что это такое я приведу ее
здесь полностью:
Получение буфера с результатом ZwQuerySystemInformation. PVOID GetInfoTable(ULONG ATableType) mPtr = ExAllocatePool(PagedPool, mSize); St = ZwQuerySystemInformation(ATableType, mPtr, mSize, NULL); if (St == STATUS_INFO_LENGTH_MISMATCH) } while (St == STATUS_INFO_LENGTH_MISMATCH); if (St == STATUS_SUCCESS) return mPtr;Я думаю, что понимание смысла этой функции ни у кого затруднений не
вызовет.Получение списка процессов из двусвязного списка структур
EPROCESS.Итак, идем дальше. Следующим шагом будет получение списка процессов проходом
по двухсвязному списку структур EPROCESS. Список начинается с головы —
PsActiveProcessHead, поэтому для корректного перечисления процессов нам сначала
нужно найти этот неэкспортируемый символ. Для этого проще всего будет
воспользоваться тем свойством, что процесс System является первым в списке
процессов. Нам нужно находясь в DriverEntry получить указатель на текущий
процесс с помощью PsGetCurrentProcess (драйвера загруженные с помощью SC Manager
API или ZwLoadDriver всегда грузятся в контексте процесса System), и Blink по
смещению ActiveProcessLinks будет указывать на PsActiveProcessHead. Выглядит это
примерно так:
PsActiveProcessHead = *(PVOID *)((PUCHAR)PsGetCurrentProcess + ActiveProcessLinksOffset + 4);Теперь можно пройтись по двухсвязному списку и построить список
процессов:
PVOID GetEprocessProcessList(ULONG *MemSize) if (!PsActiveProcessHead) return NULL; Process = PsActiveProcessHead->Flink; while (Process != PsActiveProcessHead) Process = Process->Flink; *MemSize = PsCount * sizeof(TProcessRecord); Mem = ExAllocatePool(PagedPool, *MemSize); memset(Mem, 0, *MemSize); if (!Mem) return NULL; else Data = Mem; Process = PsActiveProcessHead->Flink; while (Process != PsActiveProcessHead) Data->ProcessId = *(PULONG)((ULONG)Process — ActPsLink + pIdOffset); Data->ParrentPID = *(PULONG)((ULONG)Process — ActPsLink + ppIdOffset); Data->SignalState = *(PULONG)((ULONG)Process — ActPsLink + 4); Data->pEPROCESS = (PEPROCESS)((ULONG)Process — ActPsLink); strncpy(Data->ProcessName, (PVOID)((ULONG)Process — ActPsLink + NameOffset), 16); Process = Process->Flink;Для получения имени процесса, его Process Id и ParrentProcessId используются
смещения данных полей в структуре EPROCESS (pIdOffset, ppIdOffset, NameOffset,
ActPsLink). Эти смещения различаются в различных версиях Windows, поэтому их
получение вынесено в отдельную функцию, которую вы можете увидеть в исходном
коде программы Process Hunter (в приложении к статье).Любое скрытие процесса методом API перехвата будет обнаружено вышеприведенным
способом. Но если процесс скрыт с помощью метода DKOM (Direct Kernel Object
Manipulation), то этот способ не поможет, так как при этом процесс удаляется из
списка процессов.Получение списка процессов по спискам потоков
планировщика.Один из методов обнаружения такого скрытия состоит в получнии списка
процессов по списку потоков в планировщике. В Windows 2000 имеется три
двусвязных списка потоков: KiWaitInListHead, KiWaitOutListHead,
KiDispatcherReadyListHead. Первые два списка содержат потоки ожидающие
наступления какого-либо события, а третий содержит потоки готовые к исполнению.
Пройдясь по спискам и вычев смещение списка потоков в стуктуре ETHREAD мы
получим указатель на ETHREAD потока. Эта структура содержит несколько указателей
на процесс связанный с потоком, это struct _KPROCESS *Process (0x44, 0x150) и
sruct _EPROCESS *ThreadsProcess (0x22C, смещения указаны для Windows 2000).
Первые два указателя не оказывают никакого влияния на работу потока, поэтому
легко могут быть подменены в целях скрытия. А третий указатель используеся
планировщиком при переключении адресных пространств, поэтому подменен быть не
может. Его мы и будем использовать для определения процесса владеющего
потоком.Этот метод обнаружения применяется в программе klister, главный недостаток
которой — работа только под Windows 2000 (и то не со всеми сервиспаками).
Обусловлен это недостаток тем, что в Klister жестко зашиты адреса списков
потоков, которые меняются почти с каждым сервиспаком системы.Зашивать адреса списков в программу — это очень плохой метод, так как
гарантирует неработоспособность программы с следующими обновлениями ОС, да и
помогает укрыться от этого метода обнаружения, поэтому адреса списков придется
искать динамически, анализом кода функций, в которых они используются.Для начала попробуем найти KiWaitItListHead и KiWaitOutListHead в Windows
2000. Адреса этих списков используются в функции KeWaitForSingleObject в коде
следующего вида:
.text:0042DE56 mov ecx, offset KiWaitInListHead .text:0042DE5B test al, al .text:0042DE5D jz short loc_42DE6E .text:0042DE5F cmp byte ptr [esi+135h], 0 .text:0042DE66 jz short loc_42DE6E .text:0042DE68 cmp byte ptr [esi+33h], 19h .text:0042DE6C jl short loc_42DE73 .text:0042DE6E mov ecx, offset KiWaitOutListHeadДля получения адресов этих списков надо пройтись дизассемблером длин
инструкций (будем использовать мой LDasm) по KeWaitForSingleObject и когда
указатель (pOpcode) будет на команде mov ecx, KiWaitInListHead, то pOpcode + 5
будет указывать на test al, al, а pOpcode + 24 на mov ecx, KiWaitOutListHead.
После этого адреса KiWaitItListHead и KiWaitOutListHead извлекаются по
указателям pOpcode + 1 и pOpcode + 25 соответственно. Код поиска этих адресов
будет выглядеть так:
void Win2KGetKiWaitInOutListHeads() for (cPtr = (PUCHAR)KeWaitForSingleObject; cPtr < (PUCHAR)KeWaitForSingleObject + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*pOpcode == 0xB9 && *(pOpcode + 5) == 0x84 && *(pOpcode + 24) == 0xB9) KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 1); KiWaitOutListHead = *(PLIST_ENTRY *)(pOpcode + 25);KiDispatcherReadyListHead в Windows 2000 ищется аналогичным путем, с помощью
поиска в функции KeSetAffinityThread следующего кода:
.text:0042FAAA lea eax, KiDispatcherReadyListHead[ecx*8] .text:0042FAB1 cmp [eax], eaxА вот и функция ищущая KiDispatcherReadyListHead:
void Win2KGetKiDispatcherReadyListHead() for (cPtr = (PUCHAR)KeSetAffinityThread; cPtr < (PUCHAR)KeSetAffinityThread + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*(PUSHORT)pOpcode == 0x048D && *(pOpcode + 2) == 0xCD && *(pOpcode + 7) == 0x39) KiDispatcherReadyListHead = *(PVOID *)(pOpcode + 3);К сожалению, в Windows XP ядро довольно сильно отличается от Windows 2000.
Планировщик в XP имеет не три, а только два списка потоков: KiWaitListHead и
KiDispatcherReadyListHead. KiWaitListHead можно найти сканированием функции
KeDelayExecutionThread на следующий код:
.text:004055B5 mov dword ptr [ebx], offset KiWaitListHead .text:004055BB mov [ebx+4], eaxТакой поиск осуществляется следующим кодом:
void XPGetKiWaitListHead() for (cPtr = (PUCHAR)KeDelayExecutionThread; cPtr < (PUCHAR)KeDelayExecutionThread + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*(PUSHORT)cPtr == 0x03C7 && *(PUSHORT)(pOpcode + 6) == 0x4389) KiWaitInListHead = *(PLIST_ENTRY *)(pOpcode + 2);Самой трудной задачей оказалось найти KiDispatcherReadyListHead. Проблема в
том, что адрес KiDispatcherReadyListHead не присутствует ни в одной из
экспортируемых функций, поэтому для его получения придется немного усложнить
алгоритм поиска. Искать будем начиная с функции KiDispatchInterrupt, в ней нас
интересует только одно место, содержащее следующий код:
.text:00404E72 mov byte ptr [edi+50h], 1 .text:00404E76 call sub_404C5A .text:00404E7D call sub_404EB9Первый call в этом участке кода указывает на функцию, в которой есть ссылка
на KiDispatcherReadyListHead, но поиск адреса осложняется тем, что нужное нам
место функции имеет различный вид в Winows XP SP1 и SP2. В SP2 оно выглядит
так:
.text:00404CCD add eax, 60h .text:00404CD0 test bl, bl .text:00404CD2 lea edx, KiDispatcherReadyListHead[ecx*8] .text:00404CD9 jnz loc_401F12 .text:00404CDF mov esi, [edx+4]А в SP1 так:
.text:004180FE add eax, 60h .text:00418101 cmp [ebp+var_1], bl .text:00418104 lea edx, KiDispatcherReadyListHead[ecx*8] .text:0041810B jz loc_418760 .text:00418111 mov esi, [edx]Искать только по одной инструкции lea ненадежно, поэтому мы будем также
проверять присутствие после lea команды с rel32 смещением (функция IsRelativeCmd
в LDasm). Полный код поиска KiDispatcherReadyListHead будет выглядеть
так:
void XPGetKiDispatcherReadyListHead() for (cPtr = (PUCHAR)KiDispatchInterrupt; cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*pOpcode == 0xE8 && *(PUSHORT)(pOpcode + 5) == 0x01B1) CallAddr = (PUCHAR)(*(PULONG)(pOpcode + 1) + (ULONG)cPtr + Length); if (!CallAddr || !MmIsAddressValid(CallAddr)) return; for (cPtr = CallAddr; cPtr < CallAddr + PAGE_SIZE; cPtr += Length) Length = SizeOfCode(cPtr, &pOpcode); if (*(PUSHORT)pOpcode == 0x148D && *(pOpcode + 2) == 0xCD && IsRelativeCmd(pOpcode + 7)) KiDispatcherReadyListHead = *(PLIST_ENTRY *)(pOpcode + 3);После нахождения адресов списков потоков, мы можем легко перечислить их
процессы с помощью следующей функции:
void ProcessListHead(PLIST_ENTRY ListHead) Item = ListHead->Flink; CollectProcess(*(PEPROCESS *)((ULONG)Item + WaitProcOffset));CollectProcess — это функция добавляющая процесс в список, если он еще не был
туда добавлен.Получение списка процессов перехватом системных вызовов.
Любой работающий процесс взаимодействует с системой через API, и большинство
этих запросов превращаються в обращения к ядру системы через интерфейс системных
вызовов. Конечно, процесс может работать не вызывая API, но тогда никакой
полезной (или вредной) работы он выполнять не сможет. В общем идея состоит в
том, чтобы перехватить обращения к интерфейсу системных вызовов, а в обработчике
получать указатель на EPROCESS текущего процесса. Список указателей придется
собирать определенное время, и в него не войдут процессы ни разу не выполнявшие
системные вызовы за время сбора этой информации (например процессы, потоки
которых находятся в состоянии ожидания).В windows 2000 для системного вызова используется прерывание 2Eh, поэтому для
перехвата системных вызовов нам нужно изменить дескриптор соответствующего
прерывания в idt. Для этого нам нужно сначала определить положение idt в памяти
с помощью команды sidt. Эта команда возвращает следующую структуру:Код изменяющий вектор прерывания 2Eh будет выглядеть так:
Естественно, перед выгрузкой драйвера нужно все восстанавливать:
void Win2kSyscallUnhook()В Windows XP используется интерфейс системных вызовов построенный на основе
команды sysenter/sysexit которые появились в процессорах Pentium 2. Работой этих
команд управляют модельно-специфичные регистры (MSR). Адрес обработчика
системного вызова задается в MSR регистре SYSENTER_EIP_MSR (номер 0x176). Чтение
MSR регистра выполняется командой rdmsr, перед этим в ЕСХ должен быть помещен
номер читаемого регистра, а результат помещается в пару регистров EDX:EAX. В
нашем случае регистр SYSENTER_EIP_MSR 32 битный, поэтому в EDX будет 0, а в EAX
адрес обработчика системных вызовов. Аналогично, с помощью wrmsr выполняется
запись в MSR регистр. Но тут существует один подводный камень: при записи в 32
битный MSR регистр, EDX должен быть обнулен, иначе это вызовет исключений и
приведет к немедленному падению системы.С учетом вышесказанного, код заменяющий обработчик системных вызовов будет
выглядеть так:А восстанавливающий старый обработчик так:
Особенность Windows XP в том, что системный вызов может быть произведен как
через sysenter, так и через int 2Eh, поэтому нам нужно заменить оба обработчика
своими.Новый обработчик системного вызова должен получить указатель на EPROCESS
текущего процесса, и если это новый процесс, добавить этот процесс в списки.Соответственно, новый обработчик системного вызова будет выглядеть
так:
void __declspec(naked) NewSyscall()Для получения полного списка процессов этот код должен работать некоторое
время, и в связи с этим возникает следующая проблема: если процесс находящийся в
списке будет удален, то при последующем просмотре списка мы получим неверный
указатель, в результате мы либо ошибочно найдем скрытый процесс, либо вообще
получим BSOD. Выходом из этой ситуации является регистрация с помощью
PsSetCreateProcessNotifyRoutine Callback функции, которая будет вызвана при
создании или завершении процесса. При завершении процесса его нужно удалять из
списка. Callback функция имеет следующий прототип:
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (Установка обработчика производится так:
PsSetCreateProcessNotifyRoutine(NotifyRoutine, FALSE);А удаление так:
PsSetCreateProcessNotifyRoutine(NotifyRoutine, TRUE);Здесь существует один неочевидный момент, Callback функция всегда вызывается
в контексте завершаемого процесса, следовательно нельзя удалять процесс из
списков прямо в ней. Для этого мы воспользуемся рабочими потоками системы,
сначала выделим память под рабочий поток с помощью IoAllocateWorkItem, а затем
поместим свое задание в очередь рабочего потока с помощью IoQueueWorkItem. В
самом обработчике будем не только удалять из списка завершившиеся процессы, но и
добавлять создающиеся. А вот и код самого обработчика:
void WorkItemProc(PDEVICE_OBJECT DeviceObject, PWorkItemStruct Data) KeWaitForSingleObject(Data->pEPROCESS, Executive, KernelMode, FALSE, NULL); DelItem(&wLastItem, Data->pEPROCESS); ObDereferenceObject(Data->pEPROCESS); IoFreeWorkItem(Data->IoWorkItem); void NotifyRoutine(IN HANDLE ParentId, PsLookupProcessByProcessId(ProcessId, &process); if (!IsAdded(wLastItem, process)) AddItem(&wLastItem, process); ObDereferenceObject(process); process = PsGetCurrentProcess(); ObReferenceObject(process); Data = ExAllocatePool(NonPagedPool, sizeof(TWorkItemStruct)); Data->IoWorkItem = IoAllocateWorkItem(deviceObject); Data->pEPROCESS = process; IoQueueWorkItem(Data->IoWorkItem, WorkItemProc, DelayedWorkQueue, Data);Это весьма надежный способ обнаружения скрытых процессов, так как без
системных вызовов не может обойтись ни один процесс, но некоторые процессы могут
долго находиться в состоянии ожидания и не осуществлять системные вызовы в
течении продолжительного времени, такие процессы обнаружены не будут.Обойти этот метод обнаружения при желании также несложно, для этого нужно
изменить метод выполнения системного вызова в скрываемых процессах (перестроить
на другое прерывание или на каллгейт в GDT). Особенно легко это сделать для
Windows XP, так как достаточно пропатчить KiFastSystemCall в ntdll.dll и создать
соответствующий шлюз для системного вызова. В Windows 2000 это сделать несколько
сложнее, так как там вызовы int 2E разбросаны по ntdll, но найти и пропатчить
все эти места также не очень сложно, поэтому полностью полагаться на результаты
этой проверки тоже нельзя.Получение списка процессов просмотром списка таблиц
хэндлов.Если вы когда-нибудь скрывали процесс методом его удаления из списка
PsActiveProcesses, то наверняка обратили внимание на то, что при перечислении
хэндлов с помощью ZwQuerySystemInformation хэндлы скрытого процесса участвуют в
перечислении, в том числе определяется его ProcessId. Происходит это потому, что
для удобства перечисления хэндлов, все таблицы хэндлов обьединены в двусвязный
список HandleTableList. Смещение этого списка в структуре HANDLE_TABLE для
Windows 2000 равно 0x054, а для Windows XP — 0x01C, начинается этот список с
HandleTableListHead. Структура HANDLE_TABLE содержит в себе указатель на
владеющий ей процесс (QuotaProcess), смещение этого указателя в Windows 2000
равно 0x00C, а в Windows XP — 0x004. Пройдя по списку таблиц хэндлов мы можем
построить по ним список процессов. Для начала нам нужно найти
HandleTableListHead. Дизассемблирование ядра показало, что ссылки на него
находятся глубоко во вложенных функциях, поэтому метод поиска путем
дизассемблирования кода, который мы применяли ранее, здесь совсем не подходит.
Но для поиска HandleTableListHead можно использовать то свойство, что
HandleTableListHead — это глобальная переменная ядра, и следовательно она
находится в одной из секций его PE файла, а остальные элементы HandleTableList
находятся в динамически выделяемой памяти, а следовательно всегда будут за его
пределами. Из этого следует, что нам нужно получить указатель на HandleTable
любого процесса, и двигаться по связаному списку до тех пор, пока его элемент не
окажется внутри PE файла ядра. Этот элемент и будет HandleTableListHead. Для
определения базы и размера файла ядра в памяти используем функцию
ZwQuerySystemInformation с классом SystemModuleInformation. Она возвратит нам
массив описателей загруженных модулей, в котором первым элементом всегда будет
ядро. С учетом всего вышесказаного, код поиска HandleTableListHead будет
выглядеть так:
void GetHandleTableListHead() PSYSTEM_MODULE_INFORMATION_EX Info = GetInfoTable(SystemModuleInformation); ULONG NtoskrnlBase = (ULONG)Info->Modules[0].Base; ULONG NtoskrnlSize = Info->Modules[0].Size; PHANDLE_TABLE HandleTable = *(PHANDLE_TABLE *)((ULONG)PsGetCurrentProcess() + HandleTableOffset); PLIST_ENTRY HandleTableList = (PLIST_ENTRY)((ULONG)HandleTable + HandleTableListOffset); for (CurrTable = HandleTableList->Flink; CurrTable != HandleTableList; CurrTable = CurrTable->Flink) if ((ULONG)CurrTable > NtoskrnlBase && (ULONG)CurrTable < NtoskrnlBase + NtoskrnlSize) HandleTableListHead = CurrTable;Этот код весьма универсален, так как работает в любых версиях Windows NT, и к
тому же может применяться не только для поиска HandleTableListHead, но также и
любых других списков имеющих подобную структуру.После получения адреса HandleTableListHead мы можем пройтись по таблицам
хэндлов и построить по ним список запущенных процессов:
void ScanHandleTablesList() for (CurrTable = HandleTableListHead->Flink; CurrTable != HandleTableListHead; CurrTable = CurrTable->Flink) QuotaProcess = *(PEPROCESS *)((PUCHAR)CurrTable — HandleTableListOffset + QuotaProcessOffset); if (QuotaProcess) CollectProcess(QuotaProcess);Этот метод обнаружения скрытых процессов применяется в программах F-Secure
Black Light и в последней версии KProcCheck. Как его обойти, я думаю вы сами
догадаетесь.Получение списка процессов путем сканирования PspCidTable.
Еще одна особенность скрытия процесса с помощью удаления его из
PsActiveProcesses состоит в том, что это никак не мешает открытию процесса с
помощью OpenProcess. На этой особенности построен метод обнаружения процессов
путем перебора их pid с попыткой открыть такой процесс. Этот метод я приводить
не стал, так как по моему мнению, он лишен каких либо достоинств, в общем, можно
сказать — черезжопный метод. Но сам факт его существования говорит о том, что в
системе существует еще какой-то список процессов помимо PsActiveProcesses, по
которому и происходит открытие процесса. При переборе ProcessId обнаруживается
еще одна особенность — один процесс может быть открыт по нескольким разным pid,
а это наводит на мысль о том, что второй список процессов представляет из себя
ни что иное, как HANDLE_TABLE. Для того, чтобы удостовериться в этом, заглянем в
функцию ZwOpenProces:
PAGE:0049D59E ; NTSTATUS __stdcall NtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,PCLIENT_ID ClientId) PAGE:0049D59E public NtOpenProcess PAGE:0049D59E NtOpenProcess proc near PAGE:0049D59E ProcessHandle = dword ptr 4 PAGE:0049D59E DesiredAccess = dword ptr 8 PAGE:0049D59E ObjectAttributes= dword ptr 0Ch PAGE:0049D59E ClientId = dword ptr 10h PAGE:0049D5A3 push offset dword_413560 ; int PAGE:0049D5A8 call sub_40BA92 PAGE:0049D5AD xor esi, esi PAGE:0049D5AF mov [ebp-2Ch], esi PAGE:0049D5B2 xor eax, eax PAGE:0049D5B4 lea edi, [ebp-28h] PAGE:0049D5B8 mov eax, large fs:124h PAGE:0049D5BE mov al, [eax+140h] PAGE:0049D5C4 mov [ebp-34h], al PAGE:0049D5C7 test al, al PAGE:0049D5C9 jz loc_4BE034 PAGE:0049D5CF mov [ebp-4], esi PAGE:0049D5D2 mov eax, MmUserProbeAddress PAGE:0049D5D7 mov ecx, [ebp+8] PAGE:0049D5DA cmp ecx, eax PAGE:0049D5DC jnb loc_520CDE PAGE:0049D5E2 loc_49D5E2: PAGE:0049D5E2 mov eax, [ecx] PAGE:0049D5E4 mov [ecx], eax PAGE:0049D5E6 mov ebx, [ebp+10h] PAGE:0049D5EC jnz loc_520CE5 PAGE:0049D5F2 loc_49D5F2: PAGE:0049D5F2 mov eax, MmUserProbeAddress PAGE:0049D5F7 cmp ebx, eax PAGE:0049D5F9 jnb loc_520CEF PAGE:0049D5FF loc_49D5FF: PAGE:0049D5FF cmp [ebx+8], esi PAGE:0049D602 setnz byte ptr [ebp-1Ah] PAGE:0049D606 mov ecx, [ebx+0Ch] PAGE:0049D609 mov [ebp-38h], ecx PAGE:0049D60C mov ecx, [ebp+14h] PAGE:0049D60F cmp ecx, esi PAGE:0049D611 jz loc_4CCB88 PAGE:0049D61A jnz loc_520CFB PAGE:0049D620 loc_49D620: PAGE:0049D620 cmp ecx, eax PAGE:0049D622 jnb loc_520D0D PAGE:0049D628 loc_49D628: PAGE:0049D628 mov eax, [ecx] PAGE:0049D62A mov [ebp-2Ch], eax PAGE:0049D62D mov eax, [ecx+4] PAGE:0049D630 mov [ebp-28h], eax PAGE:0049D633 mov byte ptr [ebp-19h], 1 PAGE:0049D637 loc_49D637: PAGE:0049D637 or dword ptr [ebp-4], 0FFFFFFFFh PAGE:0049D63B loc_49D63B: PAGE:0049D63B cmp byte ptr [ebp-1Ah], 0 PAGE:0049D63F jnz loc_520D34 PAGE:0049D645 loc_49D645: PAGE:0049D645 mov eax, PsProcessType PAGE:0049D64A add eax, 68h PAGE:0049D64E push dword ptr [ebp+0Ch] PAGE:0049D651 lea eax, [ebp-0D4h] PAGE:0049D658 lea eax, [ebp-0B8h] PAGE:0049D65F call SeCreateAccessState PAGE:0049D664 cmp eax, esi PAGE:0049D666 jl loc_49D718 PAGE:0049D66C push dword ptr [ebp-34h] ; PreviousMode PAGE:0049D66F push ds:stru_5B6978.HighPart PAGE:0049D675 push ds:stru_5B6978.LowPart ; PrivilegeValue PAGE:0049D67B call SeSinglePrivilegeCheck PAGE:0049D680 test al, al PAGE:0049D682 jnz loc_4AA7DB PAGE:0049D688 loc_49D688: PAGE:0049D688 cmp byte ptr [ebp-1Ah], 0 PAGE:0049D68C jnz loc_520D52 PAGE:0049D692 cmp byte ptr [ebp-19h], 0 PAGE:0049D696 jz loc_4CCB9A PAGE:0049D69C mov [ebp-30h], esi PAGE:0049D69F cmp [ebp-28h], esi PAGE:0049D6A2 jnz loc_4C1301 PAGE:0049D6A8 lea eax, [ebp-24h] PAGE:0049D6AC push dword ptr [ebp-2Ch] PAGE:0049D6AF call PsLookupProcessByProcessId PAGE:0049D6B4 loc_49D6B4:Как вы видите, этот код безопасным образом копирует переданные указатели,
проверяя присутствие их в границах пользовательских адресов, проверяет права
доступа и наличие привилегии «SeDebugPrivilege», после чего извлекает ProcessId
из структуры CLIENT_ID и передает его функции PsLookupProcessByProcessId, задача
которой — получить по ProcessId указатель на EPROCESS. Дальнейшее продолжение
функции приводить не имеет смысла, поэтому заглянем теперь в
PsLookupProcessByProcessId:
PAGE:0049D725 public PsLookupProcessByProcessId PAGE:0049D725 PsLookupProcessByProcessId proc near PAGE:0049D725 ProcessId = dword ptr 8 PAGE:0049D725 Process = dword ptr 0Ch PAGE:0049D725 mov edi, edi PAGE:0049D728 mov ebp, esp PAGE:0049D72C mov eax, large fs:124h PAGE:0049D732 push [ebp+ProcessId] PAGE:0049D735 mov esi, eax PAGE:0049D737 dec dword ptr [esi+0D4h] PAGE:0049D73D push PspCidTable PAGE:0049D743 call ExMapHandleToPointer PAGE:0049D748 mov ebx, eax PAGE:0049D74A test ebx, ebx PAGE:0049D74C mov [ebp+ProcessId], STATUS_INVALID_PARAMETER PAGE:0049D753 jz short loc_49D787 PAGE:0049D756 mov edi, [ebx] PAGE:0049D758 cmp byte ptr [edi], 3 PAGE:0049D75B jnz short loc_49D77A PAGE:0049D75D cmp dword ptr [edi+1A4h], 0 PAGE:0049D764 jz short loc_49D77A PAGE:0049D766 mov ecx, edi PAGE:0049D768 call sub_4134A9 PAGE:0049D76D test al, al PAGE:0049D76F jz short loc_49D77A PAGE:0049D771 mov eax, [ebp+Process] PAGE:0049D774 and [ebp+ProcessId], 0 PAGE:0049D778 mov [eax], edi PAGE:0049D77A loc_49D77A: PAGE:0049D77B push PspCidTable PAGE:0049D781 call ExUnlockHandleTableEntry PAGE:0049D787 loc_49D787: PAGE:0049D787 inc dword ptr [esi+0D4h] PAGE:0049D78D jnz short loc_49D79A PAGE:0049D78F lea eax, [esi+34h] PAGE:0049D792 cmp [eax], eax PAGE:0049D794 jnz loc_52388A PAGE:0049D79A loc_49D79A: PAGE:0049D79A mov eax, [ebp+ProcessId]То что мы видим здесь, подтверждает наличие второй таблицы процессов,
организованной как HANDLE_TABLE. Сама таблица называется PspCidTable и хранит в
себе списки процессов и потоков, и используется еще в функциях
PsLookupProcessThreadByCid и PsLookupThreadByThreadId. Как мы видим, хэндл и
указатель на таблицу хэндлов передаются функции ExMapHandleToPointer, которая
(при валидности хэндла) возвращает указатель на элемент таблицы описывающий
данный хэндл — HANDLE_TABLE_ENTRY. Скормив файл ntoskrnl.pdb программе PDBdump и
порывшись в полученном логе, можно откопать следующее:
struct _HANDLE_TABLE_ENTRY { // static data ———————————— // non-static data ——————————— /*<thisrel this+0x0>*/ /*|0x4|*/ void* Object; /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long ObAttributes; /*<thisrel this+0x0>*/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY_INFO* InfoTable; /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long Value; /*<thisrel this+0x4>*/ /*|0x4|*/ unsigned long GrantedAccess; /*<thisrel this+0x4>*/ /*|0x2|*/ unsigned short GrantedAccessIndex; /*<thisrel this+0x6>*/ /*|0x2|*/ unsigned short CreatorBackTraceIndex; /*<thisrel this+0x4>*/ /*|0x4|*/ long NextFreeTableEntry;Из этого можно составить такую структуру HANDLE_TABLE_ENTRY:
typedef struct _HANDLE_TABLE_ENTRY PHANDLE_TABLE_ENTRY_INFO InfoTable; ACCESS_MASK GrantedAccess; USHORT GrantedAccessIndex; USHORT CreatorBackTraceIndex; } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;Что полезного мы можем из этого извлечь? Первым делом нас интересует
содержимое поля Object, которое является суммой указателя на описываемый хэндлом
объект и флага указывающего на занятость данного элемента таблицы (подробнее
этот момент мы рассмотрим немного позже). Весьма интересным является поле
GrantedAccess, которое указывает допустимые права доступа к объекту по этому
хэндлу. Нпример, можно открыть файл на чтение, поправить поле GrantedAccess и
писать в этот файл. Подобный метод можно использовать для чтения/записи файлов,
которых не удается открыть с требуемыми правами доступа (например занятых другим
процессом). Но вернемся к нашей задаче — получить список процессов путем
просмотра PspCidTable. Для этого нам нужно разобраться с форматом таблицы
хендлов, для того чтобы суметь их перечислить. С этого момента начинается
серьезная разница между Windows 2000 и Windows XP. Форматы их таблиц хэндлов
сильно отличаются, и нам придется разобраться с их форматом для каждой ОС
отдельно.Для начала рассмотрим формат таблицы хэндлов в Windows 2000, так как там она
гораздо проще для понимания. Для начала заглянем в код функции
ExMapHandleToPointer:
PAGE:00493285 ExMapHandleToPointer proc near PAGE:00493285 HandleTable = dword ptr 8 PAGE:00493285 Handle = dword ptr 0Ch PAGE:00493286 push [esp+Handle] PAGE:0049328A push [esp+4+HandleTable] PAGE:0049328E call ExpLookupHandleTableEntry PAGE:00493293 mov esi, eax PAGE:00493295 test esi, esi PAGE:00493297 jz short loc_4932A9 PAGE:0049329A push [esp+4+HandleTable] PAGE:0049329E call ExLockHandleTableEntry PAGE:004932A5 sbb eax, eax PAGE:004932A7 and eax, esi PAGE:004932A9 loc_4932A9: PAGE:004932AA ExMapHandleToPointer endpЗдесь происходит вызов функции ExMapHandleToPointer которая производит сам
поиск по HANDLE_TABLE, и вызов ExLockHandleTableEntry которая устанавливает Lock
Bit. Для понимания работы таблицы хэндлов нам придется разобрать обе эти
функции. Начнем с ExpLookupHandleTableEntry:
PAGE:00493545 ExpLookupHandleTableEntry proc near PAGE:00493545 HandleTable = dword ptr 0Ch PAGE:00493545 Handle = dword ptr 10h PAGE:00493547 mov edi, [esp+Handle] PAGE:0049354B mov eax, 0FFh PAGE:00493550 mov ecx, edi PAGE:00493552 mov edx, edi PAGE:00493554 mov esi, edi PAGE:00493556 shr ecx, 12h PAGE:00493559 shr edx, 0Ah PAGE:0049355F and ecx, eax PAGE:00493561 and edx, eax PAGE:00493563 and esi, eax PAGE:00493565 test edi, 0FC000000h PAGE:0049356B jnz short loc_49358A PAGE:0049356D mov eax, [esp+HandleTable] PAGE:00493571 mov eax, [eax+8] PAGE:00493574 mov ecx, [eax+ecx*4] PAGE:00493577 test ecx, ecx PAGE:00493579 jz short loc_49358A PAGE:0049357B mov ecx, [ecx+edx*4] PAGE:0049357E test ecx, ecx PAGE:00493580 jz short loc_49358A PAGE:00493582 lea eax, [ecx+esi*8] PAGE:00493585 loc_493585: PAGE:0049358A loc_49358A: PAGE:0049358A xor eax, eax PAGE:0049358C jmp short loc_493585 PAGE:0049358C ExpLookupHandleTableEntry endpВ дополнение к этому приведу структуру HANDLE_TABLE полученную из дампа
ntoskrnl.pdb:
// static data ———————————— // non-static data ——————————— /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long Flags; /*<thisrel this+0x4>*/ /*|0x4|*/ long HandleCount; /*<thisrel this+0x8>*/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY*** Table; /*<thisrel this+0xc>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess; /*<thisrel this+0x10>*/ /*|0x4|*/ void* UniqueProcessId; /*<thisrel this+0x14>*/ /*|0x4|*/ long FirstFreeTableEntry; /*<thisrel this+0x18>*/ /*|0x4|*/ long NextIndexNeedingPool; /*<thisrel this+0x1c>*/ /*|0x38|*/ struct _ERESOURCE HandleTableLock; /*<thisrel this+0x54>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList; /*<thisrel this+0x5C>*/ /*|0x10|*/ struct _KEVENT HandleContentionEvent;По этим данным восстановим структуру таблицы хэндлов:
typedef struct _WIN2K_HANDLE_TABLE PHANDLE_TABLE_ENTRY **Table; LONG FirstFreeTableEntry; LONG NextIndexNeedingPool; ERESOURCE HandleTableLock; LIST_ENTRY HandleTableList; KEVENT HandleContentionEvent; } WIN2K_HANDLE_TABLE , *PWIN2K_HANDLE_TABLE ;Из всего этого очевидно, что значение хэндла раскладывается на три части,
которые являются индексами в трехуровневой таблице объектов. Теперь посмотрим в
функцию ExLockHandleTableEntry:
PAGE:00492E2B ExLockHandleTableEntry proc near PAGE:00492E2B var_8 = dword ptr -8 PAGE:00492E2B var_4 = dword ptr -4 PAGE:00492E2B HandleTable = dword ptr 8 PAGE:00492E2B Entry = dword ptr 0Ch PAGE:00492E2C mov ebp, esp PAGE:00492E32 xor ebx, ebx PAGE:00492E34 loc_492E34: PAGE:00492E34 mov eax, [ebp+Entry] PAGE:00492E37 mov esi, [eax] PAGE:00492E39 test esi, esi PAGE:00492E3B mov [ebp+var_8], esi PAGE:00492E3E jz short loc_492E89 PAGE:00492E40 jle short loc_492E64 PAGE:00492E42 mov eax, esi PAGE:00492E44 or eax, 80000000h // set WIN2K_TABLE_ENTRY_LOCK_BIT PAGE:00492E49 mov [ebp+var_4], eax PAGE:00492E4C mov eax, [ebp+var_8] PAGE:00492E4F mov ecx, [ebp+Entry] PAGE:00492E52 mov edx, [ebp+var_4] PAGE:00492E55 cmpxchg [ecx], edx PAGE:00492E58 cmp eax, esi PAGE:00492E5A jnz short loc_492E64 PAGE:00492E5E loc_492E5E: PAGE:00492E64 loc_492E64: PAGE:00492E64 mov eax, ebx PAGE:00492E6A jb loc_4BC234 PAGE:00492E70 mov eax, [ebp+HandleTable] PAGE:00492E73 push offset unk_46D240 ; Timeout PAGE:00492E78 push 0 ; Alertable PAGE:00492E7A push 0 ; WaitMode PAGE:00492E7C add eax, 5Ch PAGE:00492E7F push 0 ; WaitReason PAGE:00492E81 push eax ; Object PAGE:00492E82 call KeWaitForSingleObject PAGE:00492E87 jmp short loc_492E34 PAGE:00492E89 loc_492E89: PAGE:00492E8B jmp short loc_492E5E PAGE:00492E8B ExLockHandleTableEntry endpСмысл данного кода состоит в том, что он проверяет 31 бит в элементе Object
структуры HANDLE_TABLE_ENTRY, устанавливает его, а в случае, если он установлен
— ждет HandleContentionEvent в HANDLE_TABLE. Для нас важен лишь факт установки
TABLE_ENTRY_LOCK_BIT, так как он являтся частью адреса объекта, и при сброшенном
бите мы получим невалидный адрес. С форматом таблицы хэндлов мы вроде
разобрались, теперь можно написать код перебора объектов в таблице:
void ScanWin2KHandleTable(PWIN2K_HANDLE_TABLE HandleTable) PHANDLE_TABLE_ENTRY Entry; for (i = 0; i < 0x100; i++) if (HandleTable->Table[i]) for (j = 0; j < 0x100; j++) if (HandleTable->Table[i][j]) for (k = 0; k < 0x100; k++) Entry = &HandleTable->Table[i][j][k]; ProcessObject((PVOID)((ULONG)Entry->Object | WIN2K_TABLE_ENTRY_LOCK_BIT));Эта функция перебирает все объекты в таблице и вызывает для каждого из них
функцию ProcessObject, которая определяет тип объекта и соответствующим образом
его обрабатывает. Вот код этой функции:
void ProcessObject(PVOID Object) POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object); if (ObjectHeader->Type == *PsProcessType) CollectProcess(Object); if (ObjectHeader->Type == *PsThreadType) ThreadCollect(Object);Итак, с форматом таблицы объектов в Windows 2000 мы разобрались, теперь пора
приступить к ее разбору в Windows XP. Начнем с дизассемблирования функции
ExpLookupHandleTableEntry:
PAGE:0048D3C1 ExpLookupHandleTableEntry proc near PAGE:0048D3C1 HandleTable = dword ptr 8 PAGE:0048D3C1 Handle = dword ptr 0Ch PAGE:0048D3C1 mov edi, edi PAGE:0048D3C4 mov ebp, esp PAGE:0048D3C6 and [ebp+Handle], 0FFFFFFFCh PAGE:0048D3CA mov eax, [ebp+Handle] PAGE:0048D3CD mov ecx, [ebp+HandleTable] PAGE:0048D3D0 mov edx, [ebp+Handle] PAGE:0048D3D6 cmp edx, [ecx+38h] PAGE:0048D3D9 jnb loc_4958D6 PAGE:0048D3E0 mov esi, [ecx] PAGE:0048D3E2 mov ecx, esi PAGE:0048D3E4 and ecx, 3 // ecx — table level PAGE:0048D3E7 and esi, not 3 // esi — pointer to first table PAGE:0048D3ED jnz loc_48DEA4 PAGE:0048D3F3 lea eax, [esi+eax*8] PAGE:0048D3F6 loc_48D3F6: PAGE:0048D3F7 loc_48D3F7: PAGE:0048DEA4 loc_48DEA4: PAGE:0048DEA5 mov ecx, eax PAGE:0048DEA7 jnz loc_52F57A PAGE:0048DEB0 mov ecx, [esi+ecx*4] PAGE:0048DEB3 loc_48DEB3: PAGE:0048DEB3 and eax, 1FFh PAGE:0048DEB8 lea eax, [ecx+eax*8] PAGE:0048DEBB jmp loc_48D3F6 PAGE:0052F57A loc_52F57A: PAGE:0052F57A shr ecx, 13h PAGE:0052F57D mov edx, ecx PAGE:0052F57F mov ecx, [esi+ecx*4] PAGE:0052F582 shl edx, 13h PAGE:0052F585 sub eax, edx PAGE:0052F587 mov edx, eax PAGE:0052F58C mov ecx, [ecx+edx*4] PAGE:0052F58F jmp loc_48DEB3Теперь посмотрим на структуру HANDLE_TABLE из дампа ntoskrnl.pdb:
// static data ———————————— // non-static data ——————————— /*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long TableCode; /*<thisrel this+0x4>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess; /*<thisrel this+0x8>*/ /*|0x4|*/ void* UniqueProcessId; /*<thisrel this+0xc>*/ /*|0x10|*/ struct _EX_PUSH_LOCK HandleTableLock[4]; /*<thisrel this+0x1c>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList; /*<thisrel this+0x24>*/ /*|0x4|*/ struct _EX_PUSH_LOCK HandleContentionEvent; /*<thisrel this+0x28>*/ /*|0x4|*/ struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo; /*<thisrel this+0x2c>*/ /*|0x4|*/ long ExtraInfoPages; /*<thisrel this+0x30>*/ /*|0x4|*/ unsigned long FirstFree; /*<thisrel this+0x34>*/ /*|0x4|*/ unsigned long LastFree; /*<thisrel this+0x38>*/ /*|0x4|*/ unsigned long NextHandleNeedingPool; /*<thisrel this+0x3c>*/ /*|0x4|*/ long HandleCount; /*<thisrel this+0x40>*/ /*|0x4|*/ unsigned long Flags; /*<bitfield this+0x40>*/ /*|0x1|*/ unsigned char StrictFIFO:0:1;И восстановим по нему описание структуры:
typedef struct _XP_HANDLE_TABLE EX_PUSH_LOCK HandleTableLock[4]; LIST_ENTRY HandleTableList; EX_PUSH_LOCK HandleContentionEvent; PHANDLE_TRACE_DEBUG_INFO DebugInfo; ULONG NextHandleNeedingPool; } XP_HANDLE_TABLE, *PXP_HANDLE_TABLE;Из приведенного кода очевидно, что функция ExpLookupHandleTableEntry
извлекает значение TableCode из структуры HANDLE_TABLE и в зависимости от его
двух младших бит определяет число уровней таблицы. Оставшиеся биты представляют
из себя указатель на таблицу первого уровня. Следовательно HANDLE_TABLE в
Windows XP может иметь от одного до трех уровней вложенности, при этом размер
таблицы на любом уровне равен 1FFh. При увеличении количества записей в таблице,
система может автоматически увеличить уровень вложенности. Очевидно, что второй
уровень таблица будет иметь при количестве записей большем 0x200, а третий
уровень — при количестве большем 0x40000. Производит ли система уменьшение числа
уровней таблице при освобождении объектов мне неизвестно, во всяком случае я
такого не наблюдал.Функция ExLockHandleTableEntry в Windows XP отсутствует, поэтому код
производящий блокировку элемента таблицы находится в функции
ExMapHandleToPointer. Дизассемблируем эту функцию и посмотрим что же она
делает:
PAGE:0048F61E ExMapHandleToPointer proc near PAGE:0048F61E var_8 = dword ptr -8 PAGE:0048F61E var_4 = dword ptr -4 PAGE:0048F61E HandleTable = dword ptr 8 PAGE:0048F61E Handle = dword ptr 0Ch PAGE:0048F61E mov edi, edi PAGE:0048F621 mov ebp, esp PAGE:0048F626 mov edi, [ebp+Handle] PAGE:0048F629 test di, 7FCh PAGE:0048F62E jz loc_4A2A36 PAGE:0048F637 push [ebp+HandleTable] PAGE:0048F63A call ExpLookupHandleTableEntry PAGE:0048F63F mov esi, eax PAGE:0048F641 test esi, esi PAGE:0048F643 jz loc_4A2711 PAGE:0048F649 mov [ebp+var_4], esi PAGE:0048F64C loc_48F64C: PAGE:0048F64C mov ebx, [esi] PAGE:0048F651 mov [ebp+var_8], ebx PAGE:0048F654 jz loc_508844 PAGE:0048F65A lea eax, [ebx-1] PAGE:0048F65D mov [ebp+Handle], eax PAGE:0048F660 mov eax, [ebp+var_8] PAGE:0048F663 mov ecx, [ebp+var_4] PAGE:0048F666 mov edx, [ebp+Handle] PAGE:0048F669 cmpxchg [ecx], edx PAGE:0048F66C cmp eax, ebx PAGE:0048F66E jnz loc_50884C PAGE:0048F674 mov eax, esi PAGE:0048F676 loc_48F676: PAGE:0048F678 loc_48F678: PAGE:0048F67A ExMapHandleToPointer endpПосле того, как функция ExpLookupHandleTableEntry возвращает указатель на
HANDLE_TABLE_ENTRY, проверяется младший бит поля Object, и если он не
установлен, то он сбрасывается, а если не установлен, то происходит ожидание его
установки. Следовательно при извлечении адреса объекта нам надо не устанавливать
старший бит (как в Windows 2000), а сбрасывать младший. С учетом вышесказанного
составим код сканирующий таблицу объектов:
void ScanXpHandleTable(PXP_HANDLE_TABLE HandleTable) PHANDLE_TABLE_ENTRY Entry; ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK; switch (HandleTable->TableCode & TABLE_LEVEL_MASK) for (i = 0; i < 0x200; i++) Entry = &((PHANDLE_TABLE_ENTRY)TableCode)[i]; if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT)); for (i = 0; i < 0x200; i++) if (((PVOID *)TableCode)[i]) for (j = 0; j < 0x200; j++) Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[i][j]; if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT)); for (i = 0; i < 0x200; i++) if (((PVOID *)TableCode)[i]) for (j = 0; j < 0x200; j++) if (((PVOID **)TableCode)[i][j]) for (k = 0; k < 0x200; k++) Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[i][j][k]; ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));Итак, с форматом таблиц объектов мы разобрались. Теперь для перечисления
процессов нам нужно найти сам адрес PspCidTable. Как вы уже догадались, искать
его мы будем дизассемблируя функцию PsLookupProcessByProcessId, в которой первый
call будет содержать адрес PspCidTable. А вот и код производящий
поиск:
for (cPtr = (PUCHAR)PsLookupProcessByProcessId; cPtr < (PUCHAR)PsLookupProcessByProcessId + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*(PUSHORT)cPtr == 0x35FF && *(pOpcode + 6) == 0xE8) PspCidTable = **(PVOID **)(pOpcode + 2);С просмотром PspCidTable мы разобрались. По аналогии с этим примером можно
легко реализовать просмотр всех объектов в таблицах всех процессов и анализ
объектов которые могут быть связаны с скрытыми процессами, по аналогии того, как
мы это делали в юзермоде. Всем, кто разобрался с вышенаписанным, я думаю не
составит труда это реализовать.Получение списка процессов перехватом SwapContext.
Если в системе имеется процесс, то соотвестсвенно он будет иметь свои потоки.
Если нам удастся поймать переключение потоков, то можно будет по полученным
данным построить список процессов. Для начала кратко рассмотрим механизм
переключения потоков. Через определенные промежутки времени (10-15 мс) системный
таймер генерирует прерывание, которое вызывает планировщик и если квант времени
связанный с текущим потоком истек, то происходит переключение потоков. Само
переключение потоков выполняется неэкспортируемой функцией ядра SwapContext,
которую тем не менее можно найти в отладочных символах на ntoskrnl.exe. Эта
функция вызывается планировщиком при истечении кванта времени потока, либо при
ожидании потоком какого-либо события. В первом случае эта функция вызывается из
KiDispatchInterrupt, а во втором случае из неэкспортируемой функции находящейся
глубоко в недрах ядра, которая в свою очередь вызывается из
KeWaitForSingleObject, KeDelayExecutionThread и KeWaitForMultipleObjects.
Параметры функции SwapContext передаются в регистрах и имеют следующее
назначение: cl — определяет режим обработки APC, edi — указатель на поток
отдающий управление, esi — указатель на поток получающий управление, ebx —
указатель на PCR. Нас интересуют только указатели на переключающиеся потоки
передаваемые в регистрах esi и edi. Для начала нам нужно найти адрес функции
SwapContext. Для этого будем дизассемблировать KiDispatchInterrupt и искать код
следующего вида:
.text:00404E76 call sub_404C5A .text:00404E7D call SwapContextИз этого участка мы и будем извлекать адрес SwapContext. Этот метод поиска
весьма надежен и универсален, так как позволяет найти SwapContext в любой
Windows, начиная он 2000 и заканчивая 2003 server включая все их сервиспаки. А
вот и код осуществляющий поиск SwapContext:
void GetSwapContextAddress() for (cPtr = (PUCHAR)KiDispatchInterrupt; cPtr < (PUCHAR)KiDispatchInterrupt + PAGE_SIZE; Length = SizeOfCode(cPtr, &pOpcode); if (*(PUSHORT)pOpcode == 0x01B1 && *(pOpcode + 2) == 0xE8) pSwapContext = (PVOID)(*(PULONG)(pOpcode + 3) + (ULONG)cPtr + 7);После нахождения адреса SwapContext нам нужно ее перехватить. Единственный
возможный в этом случае способ — это сплайсинг ее кода. Для этого скопируем
первые несколько инструкций с перехватываемой функции в буфер, в его конце
поставим jmp на продолжение, а начало своей функции заменим на jmp на свой
обработчик. При этом если в копируемом в буфер коде нам встретятся команды
содержащие relative offset, то нужно их подкорректировать, иначе мы получим
неизбежный BSOD. Для установки и снятие таких хуков воспользуемся следующим
кодом:
#define MemOpen() __asm cli; __asm mov eax, cr0; __asm mov oData, eax; \ __asm and eax, 0xFFFEFFFF; __asm mov cr0, eax; #define MemClose() __asm mov eax, oData; __asm mov cr0, eax; __asm sti; UCHAR SaveOldFunction(PUCHAR Proc, PUCHAR Old) Offset = (ULONG)Proc — (ULONG)Old; Size = SizeOfCode(Proc, &pOpcode); memcpy(oPtr, Proc, Size); if (IsRelativeCmd(pOpcode)) *(PULONG)((ULONG)pOpcode — (ULONG)Proc + (ULONG)oPtr + 1) += Offset; *(PUCHAR)((ULONG)Old + Result) = 0xE9; *(PULONG)((ULONG)Old + Result + 1) = Offset — 5; PVOID HookCode(PVOID TargetProc, PVOID NewProc) Address = (ULONG)NewProc — (ULONG)Proc — 5; OldFunction = ExAllocatePool(NonPagedPool, 20); *(PULONG)OldFunction = (ULONG)Proc; *(PUCHAR)((ULONG)OldFunction + 4) = SaveOldFunction((PUCHAR)Proc, (PUCHAR)((ULONG)OldFunction + 5)); *(PULONG)((ULONG)Proc + 1) = Address; return (PVOID)((ULONG)OldFunction + 5); void UnhookCode(PVOID OldProc) Proc = (PUCHAR)(*(PULONG)((ULONG)OldProc — 5)); SaveSize = *(PUCHAR)((ULONG)OldProc — 1); Offset = (ULONG)Proc — (ULONG)OldProc; memcpy(Proc, OldProc, SaveSize); while (ThisSize < SaveSize) Size = SizeOfCode(Proc, &pOpcode); if (IsRelativeCmd(pOpcode)) *(PULONG)((ULONG)pOpcode + 1) -= Offset; ExFreePool((PVOID)((ULONG)OldProc — 5));Сам обработчик SwapContext будет выглядеть так:
void __declspec(naked) NewSwapContext()Метод сплайсинга несомненно легок и удобен, но при неправильном применении он
может создать весьма немало проблем. Опасными являются моменты установки и
снятия хуков, так как на многопроцессорной системе (либо на системе с
Hiperthreading процессором) другой поток может вызвать перехватываемую функцию в
когда патч ее кода еще не завершен. Давайте сделаем приблизительную оценку
величины этой вероятности. Средняя частота вызова этой функции на
двухпроцессорном Pentium 4 2400 была равна 785 вызовов в секунду. Допустим, что
патч начала функции занимает 0.01 мкс (на самом деле гораздо меньше), при этом
мы получаем вероятность радения системы равную 0,00000785, тоесть на 127380
запусков программы будет одно падение системы. Для такой программы как детектор
руткитов (который к тому же редко запускают), это вполне приемлемая величина, но
если подобный метод будет использоваться в постоянно применяемой программе
(например антивирусе), то использовать сплайсинг следует исключительно
однократно (установка хуков без их последующего снятия), и только в момент
загрузки системы (в boot драйвере). В этом случае вероятность падения системы
настолько приближена к нулю, что ее можно вообще не принимать в расчет. Пока что
я не наблюдал ни одного случая падения системы из за этой причины. Но существует
еще одна, гораздо более реальная причина нестабильности сплайсинга (и любых
другим методов хуков тоже). Если драйвер допускает выгрузку и при выгрузке
снимает хуки, то выгрузка может произойти в тот момент, когда какой-либо поток
находится внутри обработчика перехвата. Вероятность такого расклада зависит от
содержимого обработчика перехвата и от частоты вызова перехватываемой функции.
Практика показала, что вероятность падения системы при выгрузке драйвера
достаточно велика, и смириться с ней никак нельзя, но этого можно избежать, если
синхронизировать выгрузку драйвера с функцией обработчиком. А еще лучше —
сделать драйвер невыгружаемым, что избавит нас от тормозов при синхронизации.
Как вы видите, при правильном применении сплайсинг может быть абсолютно
безопасным, хотя непонимание его работы может привести к весьма неслабым глюкам,
так что используйте, но с осторожностью.Приложение:
Все вышеприведенные методы обнаружения скрытых процессов реализованы в моей
программе Process Hunter. Обойти все описаные способы обнаружения будет весьма
непросто, но скорее всего авторы руткитов реализуют ее обход путем перехвата
IOCTL от приложения к драйверу. Надежной защитой от такого обхода будет
использование приватной версии детектора. Так как программа поставляется с
исходниками, вы можете сами изменить все что относиться к IOCTL, названия
модулей, окон и.т.д.
Файл Описание Process
Hunter (266 кб)Детектор скрытых процессов Process Hunter © Ms-Rem
archive
New Member
- Регистрация:
- 27 фев 2017
- Публикаций:
- 532
Описание
Hidden Process Finder — небольшая системная утилита, которая поможет выявить все скрытые процессы в работе операционки. Как известно, многие вредоносные приложения и руткиты скрывают свою деятельность, так что определить их наличие традиционным Диспетчером задач не удается.
Этот инструмент глубоко сканирует систему и анализирует все текущие процессы, показывая вам список программ с файловым атрибутом «скрытый» (hidden). Кроме этого, тут же вы имеете возможность прекратить такой процесс, безопасно удалить его при перезагрузке, скопировать имя файла в буфер обмена и открыть его расположение, посмотреть свойства файла. Все это поможет вам избавиться от нежелательного ПО и обезопасить себя от возможной утечки личной информации.
ТОП-сегодня раздела «Мониторинг, Анализ»
PE Explorer 1.99 R6
PE Explorer — программа для просмотра, редактирования и анализа внутреннего устройства…
Zemana AntiMalware 3.1.495
Zemana AntiMalware — облачное антивирусное решение, которое способно глубоко сканировать компьютер, выявляя вредоносные и нежелательные программы…
RKill 2.9.1.0
RKill — бесплатная утилита, которая закрывает процессы известных вредоносных программ,…
Отзывы о программе Hidden Process Finder
Дмитрий про Hidden Process Finder 1.1 [01-11-2021]
Спасибо, пользуюсь, удобно.
4 | 4 | Ответить
Большинство пользователей, заметив заторможенность в работе своего верного компьютера, открывают Диспетчер задач и пытаются выяснить, какой же процесс так нагрузил систему. Но видя следующую картину, недоумевают – что же не так?
Однако, при внимательном осмотре проблему обнаружить довольно легко.
Для этого достаточно просто взглянуть в строку состояния Диспетчера задач.
Число 77 как-то не сочетается с количеством в списке процессов, представленных выше. Оказывается, в операционной системе Windows имеется возможность скрывать процессы в списке и этим, конечно же, не могли не воспользоваться различные программы с не очень хорошим функционалом (троянцы, рекламные и прочие). Чтобы просмотреть полный список выполняемых процессов придётся воспользоваться сторонним программным обеспечением. В Сети его довольно много, я же воспользовался программой Spyware Process Detector. Она условно-бесплатна, но 14-дневного триального периода для наших целей вполне достаточно. После запуска этой программы картина вырисовывается уже не такая радужная.
Список запущенных процессов резко расширился и в нём появились весьма подозрительные записи (Zitenop, Mail.Ru, makecab и прочее). Особое внимание обращайте на якобы системные названия: тот же makecab или DCHP (правильное написание D
HC
P). Внимательно смотрите путь запускаемого файла – нетипичное его расположение тоже может выдать вредоносный процесс. Будем пытаться от всего этого избавиться.
Для начала я бы посоветовал проверить компьютер на вирусы с помощью антивирусной лечащей утилиты, например Dr.Web CureIt!. Утилита не требует установки и может запускаться независимо от того, есть у вас другой антивирус или нет. В случае обнаружения угроз обезвреживаем их.
Затем нужно попробовать удалить установленные «левые» приложения. В стандартном апплете Панели управления Установка и удаление программ или Программы и компоненты (в зависимости от версии системы) вредные программы тоже научились скрываться, поэтому мы снова воспользуемся сторонним ПО – CCleaner. Устанавливаем программу, заходим в раздел Сервис > Удаление программ. Здесь уже список установленного софта повнушительнее будет. Удаляем все подозрительные программы, выбрав её в списке и нажав кнопку Деинсталляция.
После этого переходим в раздел Автозагрузка и удаляем все подозрительные пункты в этом списке, выделив его и нажав кнопку Удалить. Но лучше, если вы сомневаетесь в каком-то пункте или боитесь удалить что-то нужное, вместо кнопки Удалить нажмите Выключить. В этом случае вы всегда сможете включить назад ошибочно отключённый пункт, а удалить можно будет и потом, когда убедитесь, что всё сделали правильно.
Половина дела сделана. Теперь мы должны проверить список запущенных служб. Службы – это приложения, автоматически запускаемые системой при старте и не зависящие от пользователя. Заходим Панель управления > Администрирование > Службы и в открывшемся окне видим список всех служб, установленных на компьютере.
Тут сразу бросаются в глаза пресловутый DCHP, Bamcof, Dripkix Service, System Tester Service, Zitenop… Как несложно заметить, у этих служб отсутствуют описания. На такие всегда в первую очередь следует обращать внимание. Но не стоит забывать, что и у вполне полезных служб могут отсутствовать описания, поэтому всё описанное дальше следует делать только в том случае, если вы уверены в своих действиях. Иначе лучше обратиться к специалисту.
Итак, подозрительные службы нужно попробовать отключить. Для этого делаем двойной щелчок на выбранной службе и в открывшемся окне в поле Тип запуска выбираем Отключена. Затем нажимаем ОК. И так для всех подозрительных служб.
После всех этих действий перезагружаем компьютер. Если всё сделали правильно и ничего лишнего не отключили/удалили, то сразу после перезагрузки можно будет ощутить результаты работы в виде более шустрой работы ПК, отсутствия запуска «лишних» приложений или открытия страниц. В Диспетчере задач картина тоже прояснится.
В следующей статье поговорим о том, как окончательно удалить отключённые вами раннее вредоносные или ненужные службы.
Ещё раз повторюсь: внимательно следите за тем, что делаете! Если сомневаетесь – лучше не трогайте и обратитесь к специалисту. По возможности вначале выбирайте вариант действий с отключением, а только потом, после проверки работоспособности системы, используйте удаление.