I. Слежение за процессами
На первый взгляд, задача кажется малорешаемой. На второй - после поиска в MSDN - понимаешь, что она не решаема в User-mode в том смысле, что нет соответствующих API. А впрочем, когда это было проблемой для настоящих программистов?
В режиме ядра задача решается тривиально - в драйвере регистрируешь callback функцией PsSetCreateProcessNotifyRoutine и он будет вызван при создании/удалении процесса. Но нам нужна реализация в user-mode...
Ограничимся тем, что будем отлавливать создание процессов.
Первое, что приходит на ум, это следующий алгоритм:
- получить список процессов
- просмотреть его на предмет появления новых процессов
- переход на 1)
Реализуем его с помощью NATIVE API.
Отступление: 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;
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;
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;
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);
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;
function EnableDebugPrivilegeEx(Process: dword):Boolean;
begin
Result := EnablePrivilegeEx(Process, 'SeDebugPrivilege');
end;
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. Файлы и статья доступны с моего сайта