Многие пользователи привыкли к тому, что в Windows NT диспетчер задач показывает все процессы, и многие считают, что скрыться от него вообще невозможно. На самом деле, скрыть процесс черезвычайно просто. Для этого существует множество методов, и их реализации доступны в исходниках. Остается только удивляться, почему так редки трояны использующие эти методики?
Их буквально 1 на 1000 не умеющих скрываться. Я думаю, это объясняется тем, что авторам троянов лень, ведь для этого необязательно писать что-то свое, всегда можно взять готовый исходник и вставить в свою программу. Поэтому следует ожидать, что скоро скрытие процессов будет применяться во всех широкораспостраненных рядовых троянах. Естественно, от этого нужно иметь защиту.
Производители антивирусов и фаерволлов отстали от жизни, так как их продукты не умеют обнаруживать скрытые процессы. Для этого существует только несколько утилит, из которых единственной бесплатной является Klister(работает только на Windows 2000), а за остальные производители требуют немалых денег. Причем все эти утилиты довольно легко обходятся.
PS Tray Factory как скрыть процесс
Все имеющиеся сейчас программы для обнаружения скрытых процессов построены на каком-то одном принципе, поэтому для их обхода можно придумать метод скрытия от конкретного принципа обнаружения, либо привязываться к одной конкретной программе, что гораздо проще в реализации. Пользователь купивший коммерческую программу не может изменить ее, а поэтому привязка к конкретной программе будет работать достаточно надежно, поэтому этот метод используется в коммерческих руткитах (например hxdef Golden edition). Единственным выходом будет создание бесплатной Opensource программы для обнаружения скрытых процессов в которой будут применены несколько методов обнаружения, что позволит защититься от фундаментальных принципов скрытия, а от привязки к конкретным программам может защититься каждый пользователь, для этого нужно всего лишь взять исходники программы и переделать ее под себя. В этой статье я хочу рассмотреть основные методы обнаружения скрытых процессов, привести примеры кода использующего эти методы и создать в конце законченную программу для обнаружения скрытых процессов, которая удовлетворяла бы всем вышеприведенным требованиям.
Обнаружение в User Mode
Для начала рассмотрим простые методы обнаружения, которые могут быть применены в 3 кольце, без использования драйверов. Они основаны на том, что каждый запущенный процесс порождает побочные проявления своей деятельности, по которым его и можно обнаружить. Этими проявлениями могут быть открытые им хэндлы, окна, созданные системные объекты.
От подобных методик обнаружения несложно скрыться, но для этого нужно учесть ВСЕ побочные проявления работы процесса. Ни в одном из публичных руткитов это пока еще не сделано (приватные версии к сожалению ко мне не попали). Юзермодные методы просты в реализации, безопасны в применении, и могут дать положительный эффект, поэтому их использованием не стоит пренебрегать. Для начала определимся с форматом данных возвращаемых функциями поиска, пусть это будут связанные списки:
Как скрыть работающие программы в Windows
PProcList = ^TProcList;
TProcList = packed record
NextItem: pointer;
ProcName: array [0..MAX_PATH] of Char;
ProcId: dword;
ParrentId: dword;
Получение списка процессов через ToolHelp API
Для начала определим образцовую функцию получающую список процессов, с ее результатами мы будем сравнивать результаты полученные всеми другими способами:
Получение списка процессов через ToolHelp API.
procedure GetToolHelpProcessList(var List: PListStruct);
Snap: dword;
Process: TPROCESSENTRY32;
NewItem: PProcessRecord;
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;
AddItem(List, NewItem);
until not Process32Next(Snap, Process);
CloseHandle(Snap);
Очевидно, что любой скрытый процесс при таком перечислении найден не будет, поэтому эта функция будет образцовой для отделения скрытых процессов от нескрытых.
Получение списка процессов через Native API
Следующим уровнем проверки будет получение списка процессов через ZwQuerySystemInformation (Native API). На этом уровне также врядли что-нибудь обнаружиться, но проверить все-таки стоит.
Получение списка процессов через ZwQuerySystemInformation.
procedure GetNativeProcessList(var List: PListStruct);
Info: PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
Mem: pointer;
Info := GetInfoTable(SystemProcessesAndThreadsInformation);
if Info = nil then Exit;
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
PChar(WideCharToString(Info^.ProcessName.Buffer)));
NewItem^.ProcessId := Info^.ProcessId;
NewItem^.ParrentPID := Info^.InheritedFromProcessId;
AddItem(List, NewItem);
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;
NewItem: PProcessRecord;
OldPid: dword;
Info := GetInfoTable(SystemHandleInformation);
if Info = nil then Exit;
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;
AddItem(List, NewItem);
VirtualFree(Info, 0, MEM_RELEASE);
На этом этапе уже можно кое-что обнаружить. Но полагаться на результат такой проверки не стоит, так как скрыть открытые процессом хэндлы ничуть не сложнее, чем скрыть сам процесс, просто многие забывают это делать.
Получение списка процессов по списку открытых ими окон.
Получив список окон зарегистрированных в системе и вызвав для каждого GetWindowThreadProcessId можно построить список процессов имеющих окна.
Получение списка процессов по списку окон.
Возвращает только ProcessId.
procedure GetWindowsProcessList(var List: PListStruct);
function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall;
ProcId: dword;
NewItem: PProcessRecord;
GetWindowThreadProcessId(hwnd, ProcId);
if not IsPidAdded(PList^, ProcId) then
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
NewItem^.ProcessId := ProcId;
AddItem(PList^, NewItem);
Result := true;
Окна не скрывает почти никто, поэтому эта проверка также позволяет что-то найти, но полагаться на нее тоже не стоит.
Получение списка процессов с помощью прямого системного вызова.
Для скрытия процессов в 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;
mov eax, $AD
mov edx, esp
В связи с другим интерфейсом системных вызовов, для Windows 2000 этот код будет выглядеть иначе.
Системный вызов ZwQuerySystemInformation для Windows 2000.
Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;
ASystemInformation: Pointer;
ASystemInformationLength: dword;
AReturnLength: pdword): dword; stdcall;
mov eax, $97
lea edx, [esp + $04]
Теперь остается перечислить процессы не с помощью функций из ntdll.dll, а с помощью только что определенных функций. Вот код, который это делает:
Получение списка процессов через системный вызов
ZwQuerySystemInformation.
procedure GetSyscallProcessList(var List: PListStruct);
Info: PSYSTEM_PROCESSES;
NewItem: PProcessRecord;
mPtr: pointer;
mSize: dword;
St: NTStatus;
mSize := $4000;
GetMem(mPtr, mSize);
St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation,
mPtr, mSize, nil);
if St = STATUS_INFO_LENGTH_MISMATCH then
FreeMem(mPtr);
mSize := mSize * 2;
until St <> STATUS_INFO_LENGTH_MISMATCH;
if St = STATUS_SUCCESS then
Info := mPtr;
GetMem(NewItem, SizeOf(TProcessRecord));
ZeroMemory(NewItem, SizeOf(TProcessRecord));
PChar(WideCharToString(Info^.ProcessName.Buffer)));
NewItem^.ProcessId := Info^.ProcessId;
NewItem^.ParrentPID := Info^.InheritedFromProcessId;
Info := pointer(dword(info) + info^.NextEntryDelta);
AddItem(List, NewItem);
until Info^.NextEntryDelta = 0;
FreeMem(mPtr);
Этот метод практически 100% обнаруживает юзермодные руткиты, например все версии hxdef (в том числе и Golden) им обнаруживаются.
Получение списка процессов путем анализа связанных с ним хэндлов.
Также, можно применить еще один метод основанный на перечислении хэндлов. Его суть состоит в том, чтобы найти не хэндлы открытые искомым процессом, а хэндлы других процессов связанные с ним. Это могут быть хэндлы самого процесса либо его потоков. При получении хэндла процесса, можно определить его PID с ZwQueryInformationProcess. Для потока можно вызвать ZwQueryInformationThread и получить Id его процесса.
Все процессы существующие в системе были кем-то запущены, следовательно родительские процессы будут иметь их хэндлы (если только не успели их закрыть), также хэндлы всех работающих процессов имеются в сервере подсистемы Win32 (csrss.exe). Также в Windows NT активно используются Job объекты, которые позволяют обьединять процессы (например все процессы определенного прользователя, или какие-либо службы), следовательно при нахождении хэндла Job объекта, не стоит принебрегать возможностью получить Id всех обьединенных им процессов. Делается это с помощью функции QueryInformationJobObject с классом информации — JobObjectBasicProcessIdList. Код производящий поиск процесов путем анализа открытых другими процессами хэндлов будет выглядеть так:
К сожалению, некоторые из вышеприведенных методов позволяют определить только ProcessId, но не имя процесса. Следовательно, нам нужно уметь получить имя процесса по pid. ToolHelp API для этого использовать естественно не стоит, так как процесс можкт быть скрытым, поэтому мы будем открывать память процесса на чтение и читьть имя из его PEB. Адрес PEB в процессе можно определить с помощью функции ZwQueryInformationProcess. А вот и код осуществляющий все это:
Источник: wasm.in
Статья ASM для х86 (4.5.) Техники скрытия процессов
Win – это работающая по определённым правилам замкнутая система и если не придерживаться этих правил, её можно пустить по ложному следу. Тут главное не идти на поводу Microsoft, которая вправляет нам мозги своей/увесистой документацией. Сабж рассматривает несколько способов скрытия программ от системного «Диспетчера задач» как на пользовательском уровне, так и на уровне ядра.
4.5.0. Ядерный уровень
Всякий процесс в системе описывается своей структурой EPROCESS – сколько запущенных процессов, столько и структур. Чтобы держать все процессы под контролем, система связывает их в цепочку через механизм связных-списков LIST_ENTRY . Сам список состоит всего из двух элементов – это указатель FLink на следующую структуру EPROCESS в цепочке (Forward), и указатель BLink на предыдущую структуру в цепочке (Backward). На рисунке ниже, PsActiveProcessHead – это системный связной список, с него мастдай начинает сканировать активные процессы (в примере их всего три):
При такой схеме, чтобы скрыть процесс находящийся по-середине, нужно подправить FLink левого процесса так, чтобы он указывал на структуру EPROCESS сразу правого процесса, минуя средний. В свою очередь BLink правого процесса требует правки на структуру сразу левого, в обход среднего. В результате – средний процесс станет невидимкой. Разберёмся с деталями подробней..
По смещению 0x88 в структурах EPROCESS каждого из процессов есть поле ActiveProcessLinks , которое и представляет собой связной-список. Ознакомиться с ним можно в WinDbg, но сначала командой !process 0 0 получим список всех процессов в памяти. В первой строчке лога будет указан адрес структуры EPROCESS для представленного процесса (выделен цветом), который нужно вскормить следующей команде dt (display-type) .
Для наглядности, на скрине ниже я запустил подряд три процесса вставив свой по середине — его и буду скрывать от ‘Диспетчера задач’ и прочих утилит подобного рода. Последний процесс в моей цепочке это ‘AkelPad’, соответственно его Flink будет указывать на системную PsActiveProcessHead , т.е. круг замкнётся. Точка в конце имени поля, раскрывает список:
Разберём эту схему на атомы..
Как видно, Flink моего/красного процесса имеет значение 0х80561358 (в кв.скобках), и это как-раз адрес Flink’а, который принадлежит следущему процессу в цепочке «AkelPad.exe» (т.е. двигаемся вперёд). В тоже время, значение моего Blink равно 0х89ad0ad8 , и это в аккурат указатель на Blink предыдущего процесса «FSViewer.exe».
Теперь, если изменить данную схему как на рисунке ниже, мой красный процесс исчезнет из цепочки, хотя по-прежнему будет считаться активным, имея EPROCESS в системном пуле. То-есть мы копируем свой Flink в структуру предыдущего процесса, а Blink отправляем в следующий процесс:
Нужно сказать, что такой способ позволяет скрыть процесс от всех, включая саму систему. ОС не ведёт учёт конкретных значений в полях ActiveProcessLinks , а значит физически не сможет отследить их изменение. Процесс уйдёт из поля зрения не только виндового ‘Диспетчера задач’, но и буквально всех других утилит, типа ‘Process Explorer’ Марка Руссиновича. Но у данного алгоритма есть огромный недостаток – нужно проникнуть в ядро, т.к. это внутренние структуры оси и она держит их в своём сейфе.
4.5.1. Скрытие процессов на прикладном уровне
Ясно, что писать драйвер для скрытия обычного процесса никто не будет (хотя как знать), поэтому рассмотрим альтернативные решения для юзер-моды. Маскировка на местности осуществляется здесь по такому-же алгоритму – правка указателей FLink , только не в структуре EPROCESS, а в структурах прикладных API (они тоже имеют свои списки-связей). В порядке приоритетов от младшего к старшему, системные библиотеки расположены так:
Kernel32.dll (user) —> Ntdll.dll (user) —> Ntoskrnl.exe (kernel).
- ZwQuerySystemInformation() из библиотеки Ntdll.dll;
- CreateToolhelp32Snapshot() из Kernel32.dll;
- EnumProcesses() из либы Psapi.dll.
В своём младенчестве, эта недокументированная функция могла возвращать 150 типов информации о системе (т.н. класс), однако позже выяснилось, что большинство из них оказывают медвежью услугу, предоставляя хакерам широкий ассортимент. Так в последующих виндах классы урезали и на данный момент их осталось всего 54-штуки, хотя на сайте мелкософт указано итого меньше – 5 классов с пометкой, что остальные ушли в резерв для производственных нужд системы.
В этом списке под номером 5 числится класс ProcessesAndThreadsInformation , который и возвращает информацию о процессах (см.аргумент на рисунке выше).
Ссылка скрыта от гостей
поддерживаемых классов выглядит так:
Список классов
enum _SYSTEM_INFORMATION_CLASS < BasicInformation, = 0 ProcessorInformation, = 1 PerformanceInformation, = 2 TimeOfDayInformation, = 3 NotImplemented1, = 4 ProcessesAndThreadsInformation, = 5 ;// SYSTEM_INFORMATION_CLASS;
Когда мы вызываем эту функцию с аргументом 5, то в приёмный буфер (обязательно должен быть выровнен на 4-байтную границу) сбрасывается инфа не только о процессах, но и потоках каждого из них. Поэтому размер буфера у ZwQuerySystemInformation() не фиксирован и зависит от числа активных на данный момент процессов. Если при вызове умышленно задать размер буфера в байт-так 16 (чего явно не хватит), то функция вернёт в регистр EAX ошибку 0xC0000004 — STATUS_INFO_LENGTH_MISMATCH и ..реально требуемый размер в байтах. Теперь можно выделить нужное кол-во памяти через VirtualAlloc() , и повторно запросить ZwQuerySystemInformation() . Прототипы у этих функций такие:
LPVOID VirtualAlloc ( // EAX =0 ошибка. IN lpvAddress, // база нового блока памяти (ставим нуль, и система сама найдёт) IN dwSize, // размер нового блока IN fdwAllocationType, // MEM_COMMIT (выделить, а не зарезервировать память) IN fdwProtect ); // атрибуты ставим PAGE_READWRITE STATUS ZwQuerySystemInformation ( // EAX =0 успех. IN SystemInformationClass, // класс информации (в нашем случае =5) IN SystemInformation, // указатель на приёмный буфер (берём у VirtualAlloc) IN SystemInformationLength, // размер этого буфера (получим при первом вызове) OUT ReturnLength ); // реально требуемый размер буфера
Если VIrtualAlloc() отработает удачно, то в регистре EAX получим указатель на выделенный ею блок памяти, иначе – нуль Отправив этот указатель как аргумент в ZwQuerySystemInformation() , в приёмном буфере получим инфу о всех процессах. Описатель каждого из процессов в буфере состоит из одной структуры PROCESS_INFO с общей информацией о данном процессе, и одной или нескольких (если процесс многопоточный) структур THREAD_INFO с информацией о его потоках. Описание этих структур приводится ниже:
struct SYSTEM_PROCESS_INFORMATION ;// Размер = 0x00b8 (184 байта) NextEntryOffset dd 0 ; RVA-указатель на структуру сл.процесса ThreadCount dd 0 ; число потоков Reserved_01 dd 13 dup(?) ; ProcessName dd 0 ; VA-указатель на имя процесса BasePriority dd 0 ; приоритет процесса UniqueProcessId dd 0 ; pid-процесса ParentProcessID dd 0 ; pid-родителя HandleCount dd 0 ; число открытых дескрипторов Reserved_02 dd 05 dup(?) ; PeakVirtualSize dd 0 ; пик расхода виртуальной памяти VirtualSize dd 0 ; текущий размер занимаемой памяти Reserved_03 dd 19 dup(?) ; ends ;//*************************** struct SYSTEM_THREAD_INFORMATION ;// Размер = 0x0040 (64 байта) KernelTime dq 0 UserTime dq 0 CreateTime dq 0 WaitTime dd 0 StartAddress dd 0 ClientId dq 0 Priority dd 0 BasePriority dd 0 ContextSwitches dd 0 ThreadState dd 0 WaitReason dd 0 ends
Для скрытия процесса, информация о его тредах нам вообще ни к чему. Значимой фигурой является только поле NextEntryOffset – RVA-указатель на начало структуры следующего процесса в цепочке. В описателе последнего процесса, NextEntryOffset будет равен терминальному нулю.
На рисунке ниже, я привёл фрагмент дампа буфера ZwQuerySystemInformation() , где поле NextEntryOffset выделено овалом (это инфа только двух процессов из общего пула). В нём видно, что описатель моего процесса начинается по-адресу 0x00186Е40 , а NextEntryOffset имеет значение 0x00000110 . Если сложить эти два значения, то получим адрес описателя следующего процесса 0x00186Е40 + 0x0110 = 0x00186F50 , который является последним в цепочке, т.к. его NextEntryOffset равен нулю. Указатель на Unicode-имя моего процесса лежит по адресу 0x00186Е7С и равен 0x00186F38.
Таким образом, чтобы скрыть свой процесс, мне нужно по имени найти его описатель и скорректировать поле NextEntryOffset предыдущего от меня процесса так, чтобы оно указывало на следующую запись – т.е. берём предыдущий NextEntryOffset и прибавляем к нему своё значение. После этих манипуляций, структура моего процесса будет считаться продолжением структуры предыдущего, что позволит скрыть её от посторонних глаз.
4.5.2. Практическая часть
Покончив с теорией – перейдём к практике..
Здесь есть несколько моментов, на которые следует обратить внимание:
1. Прежде всего, нужно перехватить функцию ZwQuerySystemInformation() из библиотеки Ntdll.dll, причём не в своём пространстве, а в пространстве “Диспетчера задач”. Дело в том, что перечислять процессы в своём окне будет диспетчер, а не я. Ему по-барабану, что я сброшу карту всех процессов и скорректирую её в своём буфере – он сам вызывает эту функцию и получает свою карту. Значит я должен опередить его вызов, возвратив ему фиктивные данные.
2. Когда после перехвата я получу управление, в стеке будет лежать адрес его буфера (диспетчер положит его в качестве аргумента функции) – мне остаётся лишь скрыть себя в его буфере. Любой, кто вызывает эту функцию с аргументом(5) хочет получить список процессов, поэтому буду проверять (опять таки в стеке) вызов именно на пятёрку, а остальные – игнорировать.
3. Возможно, что юзер вообще не планирует запускать “Диспетчера задач”, поэтому мне нужно отловить факт его запуска, и только потом инжектить свой шелл в его процесс. Для этого, можно с некоторым интервалом просто искать окно диспетчера по его названию функцией FindWindow() , и лучше всего создать для этого отдельный поток Thread. Тогда основной поток не отвлекаясь будет заниматься своими/тёмными делишками, а дополнительный поток будет охранять его тылы. Период поиска окна функцией Sleep() можно выставить на 1-сек, поскольку сам диспетчер в дефолте сканирует процессы с интервалом в 2-сек:
Думаю нет смысла выкладывать здесь готовый код скрытия процесса – программист должен дойти до этого сам. Кому интересно, задавайте вопросы и мы сделаем это вместе. А пока я просто приведу пример техники разбора и модификации карты-процессов, которую предоставит нам функция ZwQuerySystemInformation() . Код сбоксит в мессагу: имя, pid, число потоков, кол-во расходуемой памяти всех процессов, кроме своего. То-есть я сначала скрою свой процесс (от себя-же), а потом в цикле обойду всю карту точно так, как это делает системный “Диспетчер задач”:
Системный диспетчер обходит карту процессов точно таким-же способом, как в примере выше. Если-бы я оформил свой код в виде шелла и заинжектил-бы его в процесс диспетчера, то мой процесс пропал-бы и в его окне, а так только в моём. Это один из бородатых способов скрытия процессов, который уходит корнями в Win-98.
В сети можно встретить и другие варианты, которые и назвать-то скрытием можно с натяжкой – например сворачивание окон, или оформление программ в виде сервисов. Однако финты с картой ZwQuerySestemInformation() – это самый нижний уровень, который доступен прикладной задаче. С другой стороны, право на существование имеют любые варианты, лишь-бы они удовлетворяли нашим потребностям.
Источник: codeby.net
HideToolz — скрывает процессы
/rating_1_half.png)
/rating_2_off.png)

HideToolz — самая простая программа для скрытия различных программных процессов. Например, если вы игрок Lineage 2 и используете сторонние программы [различные кликеры, радары, боты и др.], то с помощью HideToolz можно скрыть эти проги от любой защиты, которая стоит на сервере игры. Как известно, она обходит все действующие защиты: GameGuard, LameGuard, Frost.
Программы: HideToolz [др. названия: HideTools; HD; Hider] Версия: 2.2; 63864.19
Язык: English
Разработчики: Driver by Ms-Rem; GUI by [Korvin] Операционная система: Windows XP/7/8 — x32
Возможности HideToolz
• Скрытие процессов из всех возможных методов Ring3
• Сокрытие окна от перечисления и поиска на известном имени
• Эмуляции процесса [для всех видимых процессов — будет симулировать родительский процесс explorer.exe] • Защита от перезагрузки окна
• Защита от форматирования диска