Содержание материала

 Перехват

 

Перечисление процессов производится уже упомянутой API-функцией

NtQuerySystemInformation. Для перехвата функций используется метод перезаписи

ее первых инструкций. Это делается для каждого запущеного процесса. Мы выделим

память в нужном процессе, где запишем новый код для функций, которые хотим

перехватить. Затем заменим первые пять байт этих функций на инструкцию jmp.

Эта инструкция будет перенаправлять выполнение на наш код. Так, инструкция jmp

будет выполнена сразу, как только функция будет вызвана. Мы должны сохранить

первые инструкции каждой перезаписанной функции - они необходимы для вызова

оригинального кода перехваченной функции. Сохранение инструкций описывается в

разделе 3.2.3 статьи "Hooking Windows API".

Сначала мы должны открыть нужный процесс с помощью NtOpenProcess и

получить его хэндл. Произойдет ошибка, если у нас недостаточно привелегий.

 

NTSTATUS NtOpenProcess(

OUT PHANDLE ProcessHandle,

IN ACCESS_MASK DesiredAccess,

IN POBJECT_ATTRIBUTES ObjectAttributes,

IN PCLIENT_ID ClientId OPTIONAL

);

 

ProcessHandle - указатель на хэндл, где будет сохранен результат.

DesiredAccess следует установить равным PROCESS_ALL_ACCESS. Мы установим

поле UniqueProcess структуры ClientId равным PID нужного процесса, UniqueThread

должно быть равно нулю. Открытый хэндл должен быть закрыт с помощью NtClose.

 

#define PROCESS_ALL_ACCESS 0x001F0FFF

 

Теперь мы выделим память для нашего кода. Это может быть сделано

с помощью функции NtAllocateVirtualMemory.

 

NTSTATUS NtAllocateVirtualMemory(

IN HANDLE ProcessHandle,

IN OUT PVOID BaseAddress,

IN ULONG ZeroBits,

IN OUT PULONG AllocationSize,

IN ULONG AllocationType,

IN ULONG Protect

);

 

Используется значение ProcessHandle возвращенное функцией

NtOpenProcess. BaseAddress - указатель на указатель на желаемое начало блока

выделенной памяти. Здесь будет сохранен указатель на выделенную память.

Входное значение может быть равно NULL. AllocationSize - указатель на

переменную, содержащую размер буфера, который мы хотим выделить. И также

здесь будет сохранено количество реально выделенных байт. Рекомендую включить

значение MEM_TOP_DOWN в параметр AllocationType в дополнение к MEM_COMMIT, т.к.

в этом случае память будет выделена как можно выше рядом с DLL.

 

#define MEM_COMMIT 0x00001000

#define MEM_TOP_DOWN 0x00100000

  

Теперь мы можем записать наш код, используя NtWriteVirtualMemory.

 

NTSTATUS NtWriteVirtualMemory(

IN HANDLE ProcessHandle,

IN PVOID BaseAddress,

IN PVOID Buffer,

IN ULONG BufferLength,

OUT PULONG ReturnLength OPTIONAL

);

 

В параметре BaseAddress используем значение возвращенное

NtAllocateVirtual. Buffer указывает на байты, которые мы хотим записать,

BufferLength - количество этих байтов.

 

Теперь мы перехватим функции. Единственная DLL, которая загружается в

каждый процесс - ntdll.dll. Так, мы должны проверить импортирована ли функция,

которую мы хотим перехватить, в процесс, если эта функция не из ntdll.dll.

Но память, которую эта функция (из другой DLL) могла бы занимать может быть

выделена, и перезапись этих байт повлечет за собой сбой в процессе. Поэтому

мы должны проверить загружена ли эта библиотека (в которой нужная нам функция)

в процесс.

Мы должны получить PEB (Process Environment Block) нужного процесса,

используя NtQueryInformationProcess.

 

NTSTATUS NtQueryInformationProcess(

IN HANDLE ProcessHandle,

IN PROCESSINFOCLASS ProcessInformationClass,

OUT PVOID ProcessInformation,

IN ULONG ProcessInformationLength,

OUT PULONG ReturnLength OPTIONAL

);

 

Присвоим значение ProcessBasicInformation параметру

ProcessInformationClass. Тогда в буфер ProcessInformation, размер которого

указан в параметре ProcessInformationLength, будет возвращена структура

PROCESS_BASIC_INFORMATION.

 

#define ProcessBasicInformation 0

 

typedef struct _PROCESS_BASIC_INFORMATION {

NTSTATUS ExitStatus;

PPEB PebBaseAddress;

KAFFINITY AffinityMask;

KPRIORITY BasePriority;

ULONG UniqueProcessId;

ULONG InheritedFromUniqueProcessId;

} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

 

PebBaseAddress - то, что мы ищем. PebBaseAddress+0x0C - это адрес

PPEB_LDR_DATA. Он может быть получен вызовом NtReadVirtualMemory.

 

NTSTATUS NtReadVirtualMemory(

IN HANDLE ProcessHandle,

IN PVOID BaseAddress,

OUT PVOID Buffer,

IN ULONG BufferLength,

OUT PULONG ReturnLength OPTIONAL

);

 

Параметры такие же как и у NtWriteVirtualMemory.

PPEB_LDR_DATA+0x1C - адрес InInitializationOrderModuleList. Это список

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

 

typedef struct _IN_INITIALIZATION_ORDER_MODULE_LIST {

PVOID Next,

PVOID Prev,

DWORD ImageBase,

DWORD ImageEntry,

DWORD ImageSize,

...

);

 

Next - указатель на следующую запись, Prev - на предыдущую. Последняя

запись указывает на первую. ImageBase - адрес модуля в памяти. ImageEntry - это

точка входа модуля, ImageSize - его размер.

 

Для каждой библиотеки, функции которой мы хотим перехватить, мы получим

ImageBase (например, используя GetModuleHandle или LoadLibrary). Эту ImageBase

мы сравним с ImageBase каждого элемента в InInitializationOrderModuleList.

Теперь мы готовы к перехвату. Из-за того, что мы перехватываем функции

в работающих процессах, существует вероятность, что код, который мы будем

перезаписывать в тот момент будет выполняться. Это может вызвать ошибку,

поэтому сначала мы остановим все потоки этого процесса. Список потоков можно

получить, используя функцию NtQuerySystemInformation с классом

SystemProcessesAndThreadsInformation. Результат работы этой функции описан в

разделе 4, необходимо лишь добавить описание структуры SYSTEM_THREADS, которая

содержит информацию о потоке.

 

typedef struct _SYSTEM_THREADS {

LARGE_INTEGER KernelTime;

LARGE_INTEGER UserTime;

LARGE_INTEGER CreateTime;

ULONG WaitTime;

PVOID StartAddress;

CLIENT_ID ClientId;

KPRIORITY Priority;

KPRIORITY BasePriority;

ULONG ContextSwitchCount;

THREAD_STATE State;

KWAIT_REASON WaitReason;

} SYSTEM_THREADS, *PSYSTEM_THREADS;

 

Для каждого потока мы должны получить его хэндл, используя

NtOpenThread. Мы используем для этого ClientId.

 

NTSTATUS NtOpenThread(

OUT PHANDLE ThreadHandle,

IN ACCESS_MASK DesiredAccess,

IN POBJECT_ATTRIBUTES ObjectAttributes,

IN PCLIENT_ID ClientId

);

 

Хэндл, который нам нужен, будет сохранен в ThreadHandle. Параметр

DesiredAccess должен быть равен THREAD_SUSPEND_RESUME.

 

#define THREAD_SUSPEND_RESUME 2

 

ThreadHandle будет использован при вызове NtSuspendThread.

 

NTSTATUS NtSuspendThread(

IN HANDLE ThreadHandle,

OUT PULONG PreviousSuspendCount OPTIONAL

);

 

 

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

в разделе 3.2.2 статьи "Hooking Windows API". Единственная разница в том, что

функции будут использоваться для других процессов.

 

После перехвата мы возобновим работу всех потоков процесса, используя

NtResumeThread.

 

NTSTATUS NtResumeThread(

IN HANDLE ThreadHandle,

OUT PULONG PreviousSuspendCount OPTIONAL

);

 

 

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить