Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Бесплатный конструктор сайтов и Landing Page

Хостинг с DDoS защитой от 2.5$ + Бесплатный SSL и Домен

SSD VPS в Нидерландах под различные задачи от 2.6$

✅ Дешевый VPS-хостинг на AMD EPYC: 1vCore, 3GB DDR4, 15GB NVMe всего за €3,50!

🔥 Anti-DDoS защита 12 Тбит/с!

VPS в 21 локации

От 104 рублей в месяц

Безлимитный трафик. Защита от ДДоС.

🔥 VPS до 5.7 ГГц под любые задачи с AntiDDoS в 7 локациях

💸 Гифткод CITFORUM (250р на баланс) и попробуйте уже сейчас!

🛒 Скидка 15% на первый платеж (в течение 24ч)

2006 г.

Слежение за процессами и файлами в режиме пользователя

Роман Кулаченко, Королевство Delphi

I. Слежение за процессами

На первый взгляд, задача кажется малорешаемой. На второй - после поиска в MSDN - понимаешь, что она не решаема в User-mode в том смысле, что нет соответствующих API. А впрочем, когда это было проблемой для настоящих программистов?

В режиме ядра задача решается тривиально - в драйвере регистрируешь callback функцией PsSetCreateProcessNotifyRoutine и он будет вызван при создании/удалении процесса. Но нам нужна реализация в user-mode...

Ограничимся тем, что будем отлавливать создание процессов. Первое, что приходит на ум, это следующий алгоритм:

  1. получить список процессов
  2. просмотреть его на предмет появления новых процессов
  3. переход на 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;

{$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. Файлы и статья доступны с моего сайта

Скидка до 20% на услуги дата-центра. Аренда серверной стойки. Colocation от 1U!

Миграция в облако #SotelCloud. Виртуальный сервер в облаке. Выбрать конфигурацию на сайте!

Виртуальная АТС для вашего бизнеса. Приветственные бонусы для новых клиентов!

Виртуальные VPS серверы в РФ и ЕС

Dedicated серверы в РФ и ЕС

По промокоду CITFORUM скидка 30% на заказ VPS\VDS

VPS/VDS серверы. 30 локаций на выбор

Серверы VPS/VDS с большим диском

Хорошие условия для реселлеров

4VPS.SU - VPS в 17-ти странах

2Gbit/s безлимит

Современное железо!

Новости мира IT:

Архив новостей

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 7861149
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2019 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...