Версия для печати
Слежение за процессами и файлами в режиме пользователя
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1279SomeName SomeSurname
дата публикации 22-06-2006 04:12Слежение за процессами и файлами в режиме пользователя I. Слежение за процессами
На первый взгляд, задача кажется малорешаемой. На второй - после поиска в MSDN - понимаешь, что она не решаема в User-mode в том смысле, что нет соответствующих API. А впрочем, когда это было проблемой для настоящих программистов?
В режиме ядра задача решается тривиально - в драйвере регистрируешь callback функцией PsSetCreateProcessNotifyRoutine и он будет вызван при создании/удалении процесса. Но нам нужна реализация в user-mode...
Ограничимся тем, что будем отлавливать создание процессов. Первое, что приходит на ум, это следующий алгоритм:
Реализуем его с помощью NATIVE API.
- получить список процессов
- просмотреть его на предмет появления новых процессов
- переход на 1)
Отступление: Native API - это набор API, не документированный Microsoft (или документированный частично). С его помощью можно сделать все то же что можно сделать с помощью обычных API и многое другое. Мне в Native API нравится его структура, например с помощью одной функции ZwQuerySystemInformation можно получить очень большое количество информации (перечислить хэндлы, получить информацию о процессе и многое другое).
Для Delphi порт заголовочных файлов существует в нескольких вариантах, наиболее распространен вариант JEDI. Только, при использовании JEDI, придется все Zw-функции заменить на их Nt-аналоги. Впрочем, в режиме пользователя эти функции абсолютно идентичны. Разница наблюдается только в режиме ядра, подробнее читайте статью http://www.osronline.com/article.cfm?article=257 (она доступна только по подписке, или в кэше Google )
program process_seeker; {$APPTYPE CONSOLE} uses SysUtils, windows, tintlist; type NTStatus = cardinal; PVOID = pointer; USHORT = WORD; UCHAR = byte; PWSTR = PWideChar; CONST //Статус константы STATUS_SUCCESS = NTStatus($00000000); STATUS_ACCESS_DENIED = NTStatus($C0000022); STATUS_INFO_LENGTH_MISMATCH = NTStatus($C0000004); const SystemProcessesAndThreadsInformation = 5; type PClientID = ^TClientID; TClientID = packed record UniqueProcess:cardinal; UniqueThread:cardinal; end; PUnicodeString = ^TUnicodeString; TUnicodeString = packed record Length: Word; MaximumLength: Word; Buffer: PWideChar; end; PVM_COUNTERS = ^VM_COUNTERS; VM_COUNTERS = packed record PeakVirtualSize, VirtualSize, PageFaultCount, PeakWorkingSetSize, WorkingSetSize, QuotaPeakPagedPoolUsage, QuotaPagedPoolUsage, QuotaPeakNonPagedPoolUsage, QuotaNonPagedPoolUsage, PagefileUsage, PeakPagefileUsage: dword; end; PIO_COUNTERS = ^IO_COUNTERS; IO_COUNTERS = packed record ReadOperationCount, WriteOperationCount, OtherOperationCount, ReadTransferCount, WriteTransferCount, OtherTransferCount: LARGE_INTEGER; end; PSYSTEM_THREADS = ^SYSTEM_THREADS; SYSTEM_THREADS = packed record KernelTime, UserTime, CreateTime: LARGE_INTEGER; WaitTime: dword; StartAddress: pointer; ClientId: TClientId; Priority, BasePriority, ContextSwitchCount: dword; State: dword; WaitReason: dword; end; PSYSTEM_PROCESSES = ^SYSTEM_PROCESSES; SYSTEM_PROCESSES = packed record NextEntryDelta, ThreadCount: dword; Reserved1 : array [0..5] of dword; CreateTime, UserTime, KernelTime: LARGE_INTEGER; ProcessName: TUnicodeString; BasePriority: dword; ProcessId, InheritedFromProcessId, HandleCount: dword; Reserved2: array [0..1] of dword; VmCounters: VM_COUNTERS; IoCounters: IO_COUNTERS; // Windows 2000 only Threads: array [0..0] of SYSTEM_THREADS; end; Function ZwQuerySystemInformation(ASystemInformationClass: dword; ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength:PCardinal): NTStatus; stdcall;external 'ntdll.dll'; { Получение буфера с системной информацией } Function GetInfoTable(ATableType:dword):Pointer; var mSize: dword; mPtr: pointer; St: NTStatus; begin Result := nil; mSize := $4000; //начальный размер буфера repeat mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); if mPtr = nil then Exit; St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil); if St = STATUS_INFO_LENGTH_MISMATCH then begin //надо больше памяти VirtualFree(mPtr, 0, MEM_RELEASE); mSize := mSize * 2; end; until St <> STATUS_INFO_LENGTH_MISMATCH; if St = STATUS_SUCCESS then Result := mPtr else VirtualFree(mPtr, 0, MEM_RELEASE); end; var info, info2: PSystem_Processes; i, j, k: integer; t, t1: LARGE_INTEGER; process_id: tintegerlist; begin process_id := TIntegerList.Create; //СОЗДАЕМ СПИСОК ПРОЦЕССОВ НА МОМЕНТ СОЗДАНИЯ НАШЕГО ПРОЦЕССА info := GetInfoTable(SystemProcessesAndThreadsInformation); info2 := info; while (info2^.NextEntryDelta <> 0) do begin if (process_id.IndexOf(info2^.ProcessId)=-1) then process_id.Add(info2^.ProcessId); info2 := Pointer(dword(info2)+info2^.NextEntryDelta); end; VirtualFree(info, 0, MEM_RELEASE); //А теперь смотрим что добавилось while true do begin Sleep(200); info := GetInfoTable(SystemProcessesAndThreadsInformation); info2 := info; while (info2^.NextEntryDelta <> 0) do begin if (process_id.IndexOf(info2^.ProcessId)=-1) then begin writeln(info2^.ProcessId, ' - created'); process_id.Add(info2^.ProcessId); end; info2 := Pointer(dword(info2)+info2^.NextEntryDelta); end; VirtualFree(info, 0, MEM_RELEASE); end; end.Вы можете легко переделать этот код для того, чтобы отслеживать также и терминирование процессов :) Оставим это читателю в качестве домашнего задания :)
II. Слежение за файлами
Здесь можно применить либо тот же подход что описан выше для процессов либо воспользоваться портами завершения.
Приведем реализацию первого методаprogram file_seeker; {$APPTYPE CONSOLE} uses SysUtils, windows, tintlist; type NTStatus = cardinal; PVOID = pointer; USHORT = WORD; UCHAR = byte; PWSTR = PWideChar; CONST //Статус константы STATUS_SUCCESS = NTStatus($00000000); STATUS_ACCESS_DENIED = NTStatus($C0000022); STATUS_INFO_LENGTH_MISMATCH = NTStatus($C0000004); SEVERITY_ERROR = NTStatus($C0000000); const SystemHandleInformation = 16; OB_TYPE_FILE = 28; type PClientID = ^TClientID; TClientID = packed record UniqueProcess:cardinal; UniqueThread:cardinal; end; PUnicodeString = ^TUnicodeString; TUnicodeString = packed record Length: Word; MaximumLength: Word; Buffer: PWideChar; end; PSYSTEM_HANDLE_INFORMATION = ^SYSTEM_HANDLE_INFORMATION; SYSTEM_HANDLE_INFORMATION = packed record ProcessId: dword; ObjectTypeNumber: byte; Flags: byte; Handle: word; pObject: pointer; GrantedAccess: dword; end; PSYSTEM_HANDLE_INFORMATION_EX = ^SYSTEM_HANDLE_INFORMATION_EX; SYSTEM_HANDLE_INFORMATION_EX = packed record NumberOfHandles: dword; Information: array [0..0] of SYSTEM_HANDLE_INFORMATION; end; Function ZwQuerySystemInformation(ASystemInformationClass: dword; ASystemInformation: Pointer; ASystemInformationLength: dword; AReturnLength:PCardinal): NTStatus; stdcall;external 'ntdll.dll'; { Включение заданой привилегии для процесса } function EnablePrivilegeEx(Process: dword; lpPrivilegeName: PChar):Boolean; var hToken: dword; NameValue: Int64; tkp: TOKEN_PRIVILEGES; ReturnLength: dword; begin Result:=false; //Получаем токен нашего процесса OpenProcessToken(Process, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //Получаем LUID привилегии if not LookupPrivilegeValue(nil, lpPrivilegeName, NameValue) then begin CloseHandle(hToken); exit; end; tkp.PrivilegeCount := 1; tkp.Privileges[0].Luid := NameValue; tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //Добавляем привилегию к процессу AdjustTokenPrivileges(hToken, false, tkp, SizeOf(TOKEN_PRIVILEGES), tkp, ReturnLength); if GetLastError() <> ERROR_SUCCESS then begin CloseHandle(hToken); exit; end; Result:=true; CloseHandle(hToken); end; { включение заданной привилегии для текущего процесса } function EnablePrivilege(lpPrivilegeName: PChar):Boolean; begin Result := EnablePrivilegeEx(INVALID_HANDLE_VALUE, lpPrivilegeName); end; { Включение привилегии SeDebugPrivilege для процесса } function EnableDebugPrivilegeEx(Process: dword):Boolean; begin Result := EnablePrivilegeEx(Process, 'SeDebugPrivilege'); end; { Включение привилегии SeDebugPrivilege для текущего процесса } function EnableDebugPrivilege():Boolean; begin Result := EnablePrivilegeEx(INVALID_HANDLE_VALUE, 'SeDebugPrivilege'); end; { Получение буфера с системной информацией } Function GetInfoTable(ATableType:dword):Pointer; var mSize: dword; mPtr: pointer; St: NTStatus; begin Result := nil; mSize := $4000; //начальный размер буфера repeat mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); if mPtr = nil then Exit; St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil); if St = STATUS_INFO_LENGTH_MISMATCH then begin //надо больше памяти VirtualFree(mPtr, 0, MEM_RELEASE); mSize := mSize * 2; end; until St <> STATUS_INFO_LENGTH_MISMATCH; if St = STATUS_SUCCESS then Result := mPtr else VirtualFree(mPtr, 0, MEM_RELEASE); end; var HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX; r: integer; hProcess, tHandle: dword; file_h: tintegerlist; begin file_h := tintegerlist.Create; EnableDebugPrivilege(); HandlesInfo := GetInfoTable(SystemHandleInformation); for r := 0 to HandlesInfo^.NumberOfHandles do if HandlesInfo^.Information[r].ObjectTypeNumber = OB_TYPE_FILE then begin file_h.Add(HandlesInfo^.Information[r].Handle); end; VirtualFree(HandlesInfo, 0, MEM_RELEASE); //а теперь смотрим что изменилось while true do begin Sleep(200); HandlesInfo := GetInfoTable(SystemHandleInformation); for r := 0 to HandlesInfo^.NumberOfHandles do if HandlesInfo^.Information[r].ObjectTypeNumber = OB_TYPE_FILE then begin if file_h.IndexOf(HandlesInfo^.Information[r].Handle)=-1 then begin file_h.Add(HandlesInfo^.Information[r].Handle); writeln(HandlesInfo^.Information[r].Handle, ' - added a file handle'); end; end; VirtualFree(HandlesInfo, 0, MEM_RELEASE); end; readln; end.Вторая технология - использование ReadDirectoryChangesA(W) и портов завершения ввода/вывода, реализация несложная, исходники (не мои, откомментированные) брать здесь.
III. Outro
Безусловно, все эти решения - некие "полумеры", самое лучшее - писать драйвер. Но это достаточно нетривиальная задача, так что...
Я надеюсь, что Вы найдете применение описанному в статье, или хотя бы, что эта статья была Вам интересна.
Возможно, в последующих статьях я опишу как это все можно реализовать с помощью перехвата API. Файлы и статья доступны с моего сайта
С уважением, SLoW.