научная статья по теме ОБНАРУЖЕНИЕ СКРЫТЫХ ПРОЦЕССОВ В 3 КОЛЬЦЕ ЗАЩИТЫ ОС WINDOWS Общие и комплексные проблемы естественных и точных наук

Текст научной статьи на тему «ОБНАРУЖЕНИЕ СКРЫТЫХ ПРОЦЕССОВ В 3 КОЛЬЦЕ ЗАЩИТЫ ОС WINDOWS»

Моляков А. С., аспирант Российского государственного гуманитарного университета

ОБНАРУЖЕНИЕ СКРЫТЫХ ПРОЦЕССОВ В 3 КОЛЬЦЕ ЗАЩИТЫ ОС WINDOWS

Я рассмотрю простые методы обнаружения, которые могут быть применены в 3 кольце, без использования драйверов. Они основаны на том, что каждый запущенный процесс порождает побочные проявления своей деятельности, по которым его и можно обнаружить. Этими проявлениями могут быть открытые им хэндлы, окна, созданные системные объекты. От подобных методик обнаружения несложно скрыться, но для этого нужно учесть ВСЕ побочные проявления работы процесса. Ни в одном из публичных руткитов это пока еще не сделано (приватные версии к сожалению ко мне не попали). Юзермодные методы просты в реализации, безопасны в применении, и могут дать положительный эффект, поэтому их использованием не стоит пренебрегать. Для начала определимся с форматом данных возвращаемых функциями поиска, пусть это будут связанные списки: type

PProcList = ATProcList; TProcList = packed record Nextltem: pointer;

ProcName: array [0..MAX_PATH] of Char; ProcId: dword; ParrentId: dword; end;

Получение списка процессов через ToolHelp API

Для начала определим образцовую функцию получающую список процессов, с ее результатами мы будем сравнивать результаты полученные всеми другими способами: {

Получение списка процессов через ToolHelp API.

}

procedure GetToolHelpProcessList(var List: PListStruct); var

Snap: dword;

Process: TPROCESSENTRY32; NewItem: PProcessRecord; begin

Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snap <> INVALID_HANDLE_VALUE then begin

Process.dwSize := SizeOf(TPROCESSENTRY32); if Process32First(Snap, Process) then repeat

GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItemA.ProcessId := Process.th32ProcessID; NewItemA.ParrentPID := Process.th32ParentProcessID; lstrcpy(@NewItemA.ProcessName, Process.szExeFile); AddItem(List, NewItem); until not Process32Next(Snap, Process); CloseHandle(Snap);

end; end;

Очевидно, что любой скрытый процесс при таком перечислении найден не будет, поэтому эта функция будет образцовой для отделения скрытых процессов от нескрытых.

Получение списка процессов через Native API

Следующим уровнем проверки будет получение списка процессов через ZwQuerySystemInformation (Native API). На этом уровне также врядли что-нибудь обнаружиться, но проверить все-таки стоит.

{

Получение списка процессов через ZwQuerySystemInformation.

}

procedure GetNativeProcessList(var List: PListStruct); var

Info: PSYSTEM_PROCESSES; NewItem: PProcessRecord; Mem: pointer; begin

Info := GetInfoTable(SystemProcessesAndThreadsInformation); Mem := Info; if Info = nil then Exit; repeat

GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); lstrcpy(@NewItemA.ProcessName,

PChar(WideCharToString(InfoA.ProcessName.Buffer))); NewItemA.ProcessId := InfoA.ProcessId; NewItemA.ParrentPID := InfoA.InheritedFromProcessId; AddItem(List, NewItem);

Info := pointer(dword(info) + infoA.NextEntryDelta); until InfoA.NextEntryDelta = 0; VirtualFree(Mem, 0, MEM_RELEASE); end;

Получение списка процессов по списку открытых хэндлов Многие программы скрывающие процесс, не скрывают открытые им хэндлы, следовательно перечислив открытые хэндлы через ZwQuerySystemInformation мы можем построить

список процессов.

{

Получение списка процессов по списку открытых хэндлов. Возвращает только ProcessId.

}

procedure GetHandlesProcessList(var List: PListStruct); var

Info: PSYSTEM_HANDLE_INFORMATION_EX; NewItem: PProcessRecord; r: dword; OldPid: dword; begin OldPid := 0;

Info := GetInfoTable(SystemHandleInformation);

if Info = nil then Exit; for r := 0 to InfoA.NumberOfHandles do if InfoA.Information[r].ProcessId <> OldPid then begin

OldPid := InfoA.Information[r].ProcessId; GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItemA.ProcessId := OldPid; AddItem(List, NewItem); end;

VirtualFree(Info, 0, MEM_RELEASE); end;

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

Получение списка процессов по списку открытых ими окон

Получив список окон зарегистрированных в системе и вызвав для каждого

GetWindowThreadProcessId можно построить список процессов имеющих окна. {

Получение списка процессов по списку окон. Возвращает только ProcessId.

}

procedure GetWindowsProcessList(var List: PListStruct);

function EnumWindowsProc(hwnd: dword; PList: PPListStruct): bool; stdcall; var

ProcId: dword;

NewItem: PProcessRecord;

begin

GetWindowThreadProcessId(hwnd, ProcId); if not IsPidAdded(PListA, ProcId) then begin

GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); NewItemA.ProcessId := ProcId; AddItem(PListA, NewItem); end;

Result := true; end;

begin

EnumWindows(@EnumWindowsProc, dword(@List)); end;

Окна не скрывает почти никто, поэтому эта проверка также позволяет что-то найти, но полагаться на нее тоже не стоит.

Получение списка процессов с помощью прямого системного вызова

Для скрытия процессов в 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;

asm

pop ebp mov eax, $AD call @SystemCall ret $10

@SystemCall: mov edx, esp sysenter end;

В связи с другим интерфейсом системных вызовов, для Windows 2000 этот код будет выглядеть иначе. {

Системный вызов ZwQuerySystemInformation для Windows 2000.

}

Function Win2kZwQuerySystemInfoCall(ASystemInformationClass: dword;

ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength: pdword): dword; stdcall;

asm

pop ebp

mov eax, $97

lea edx, [esp + $04]

int $2E

ret $10

end;

Теперь остается перечислить процессы не с помощью функций из ntdll.dll, а с помощью

только что определенных функций. Вот код, который это делает: {

Получение списка процессов через системный вызов ZwQuerySystemInformation.

}

procedure GetSyscallProcessList(var List: PListStruct); var

Info: PSYSTEM_PROCESSES; NewItem: PProcessRecord; mPtr: pointer; mSize: dword;

St: NTStatus; begin

mSize := $4000; repeat

GetMem(mPtr, mSize);

St := ZwQuerySystemInfoCall(SystemProcessesAndThreadsInformation,

mPtr, mSize, nil); if St = STATUS_INFO_LENGTH_MISMATCH then begin FreeMem(mPtr); mSize := mSize * 2; end;

until St <> STATUS_INFO_LENGTH_MISMATCH; if St = STATUS_SUCCESS then begin Info := mPtr; repeat

GetMem(NewItem, SizeOf(TProcessRecord)); ZeroMemory(NewItem, SizeOf(TProcessRecord)); lstrcpy(@NewItemA.ProcessName,

PChar(WideCharToString(InfoA.ProcessName.Buffer))); NewItemA.ProcessId := InfoA.ProcessId; NewItemA.ParrentPID := InfoA.InheritedFromProcessId; Info := pointer(dword(info) + infoA.NextEntryDelta); AddItem(List, NewItem); until InfoA.NextEntryDelta = 0; end;

FreeMem(mPtr); end;

Получение списка процессов путем анализа связанных с ним хэндлов Также, можно применить еще один метод основанный на перечислении хэндлов. Его суть состоит в том, чтобы найти не хэндлы открытые искомым процессом, а хэндлы других процессов связанные с ним. Это могут быть хэндлы самого процесса либо его потоков. При получении хэндла процесса, можно определить его PID с ZwQueryInformationProcess. Для потока можно вызвать ZwQueryInformationThread и получить Id его процесса. Все процессы существующие в системе были кем-то запущены, следовательно родительские процессы будут иметь их хэндлы (если только не успели их закрыть), также хэндлы всех работающих процессов имеются в сервере подсистемы Win32 (csrss.exe). Также в Windows NT активно используются Job объекты, которые позволяют обьединять процессы (например все процессы определенного прользователя, или какие-либо службы), следовательно при нахождении хэндла Job объекта, не стоит принебрегать возможностью получить Id всех обьединенных им процессов. Делается это с помощью функции QueryInformationJobObject с классом информации — JobObjectBasicProcessIdList. Код производящий поиск процесов путем анализа открытых другими процессами хэндлов будет выглядеть так: {

Получение списка процессов через проверку хэнжлов в других процессах.

}

procedure GetProcessesFromHandles(var List: PListStruct; Processes, Jobs, Threads: boolean); var

EcmecmeeHHbie u mexHunecKue HayKu, № 6, 2006

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 HandlesInfoA.NumberOfHandles do if HandlesInfoA.Information[r].ObjectTypeNumber in [OB_TYPE_PROCESS, OB_TYPE_JOB, OB_TYPE_THREAD] then begin

hProcess := OpenProcess(PROCESS_DUP_HANDLE, false, HandlesInfoA.Information[r].ProcessId);

if DuplicateHandle(hProcess, HandlesInfoA.Information[r].Handle, INVALID_HANDLE_VALUE, @tHandle, 0, false, DUPLICATE_SAME_ACCESS) then

begin

case HandlesInfoA.Information[r].ObjectTypeNumber of OB_TYPE_PROCESS : begin

if Processes and (HandlesInfoA.Information[r].ProcessId = CsrPid) then if ZwQueryInformationProces

Для дальнейшего прочтения статьи необходимо приобрести полный текст. Статьи высылаются в формате PDF на указанную при оплате почту. Время доставки составляет менее 10 минут. Стоимость одной статьи — 150 рублей.

Показать целиком