Возможность получения информации о стороннем процессе раскрывает перед программистами и пользователями широкие возможности. Это могут быть продвинутые менеджеры процессов, антивирусные и антитроянские программы, утилиты для реверсной инженерии и многое другое. Для начала надо описать структуры, необходимые для работы с процессами. В стандартном комплекте FASM их, естественно, нет, но это и неудивительно.
; Структура для получения данных о процессе под Win32
struct PROCESS_BASIC_INFORMATION
ExitStatus dd ?
PebBaseAddress dd ?
AffinityMask dd ?
BasePriority dd ?
uUniqueProcessId dd ?
uInheritedFromUniqueProcessId dd ?
ends
; Структура PEB процесса под Win32
; Process Enviroment Block или блок окружения процесса
; Содержит все параметры пользовательского режима, ассоциированные
; системой с текущим процессом
struct PEB
InheritedAddressSpace db ?
ReadImageFileExecOptions db ?
BeingDebugged db ?
b003 db ?
Mutant dd ?
ImageBaseAddress dd ?
Ldr dd ?
ProcessParameters dd ?
ends
; Юникодная строка в Win32
struct UNICODE_STRING
Length dw ?
MaximumLength dw ?
Buffer dd ?
ends
; Структура RTL_USER_PROCESS_PARAMETERS под Win32
struct RTL_USER_PROCESS_PARAMETERS
MaximumLength dd ?
Length dd ?
Flags dd ?
DebugFlags dd ?
ConsoleHandle dd ?
ConsoleFlags dd ?
StdInputHandle dd ?
StdOutputHandle dd ?
StdErrorHandle dd ?
CurrentDirectoryPath UNICODE_STRING
CurrentDirectoryHandle dd ?
DllPath UNICODE_STRING
ImagePathName UNICODE_STRING
CommandLine UNICODE_STRING
ends
В сегменте данных на основании структур опишем необходимые нам массивы для чтения данных о процессе.
section '.data' data readable writeable
...
; Данные о 32-битных процессах
Info PROCESS_BASIC_INFORMATION
peb PEB
pparam RTL_USER_PROCESS_PARAMETERS
Подготовительный этап завершен, можно переходить к основной части. Получение информации о процессе выполняется в несколько стадий. Сперва надо открыть процесс для чтения и получения данных, затем с помощью функции
NtQueryInformationProcess надо прочитать общую информацию о процессе в структуру
PROCESS_BASIC_INFORMATION (с параметром
ProcessBasicInformation). Имейте в виду, что приложение, которое выполняет такой запрос, должно обладать соответствующими привилегиями. Затем надо прочитать блок окружения процесса
PEB, который находится по адресу, определенному в
PebBaseAddress структуры
PROCESS_BASIC_INFORMATION. Дальше нам надо загрузить параметры процесса в структуру
RTL_USER_PROCESS_PARAMETERS, адрес которой определен в структуре
PEB параметром
ProcessParameters. На самом деле в
PEB содержится
гораздо больше различных значений, но нас интересует только эта структура. Именно в ней записана нужная нам информация - путь к исполняемому файлу процесса и его командная строка.
Оформим все вышесказанное в программный код. Подразумевается, что вы уже получили значение pID нужного вам процесса. Удобнее всего сделать это через перечисление процессов при помощи
CreateToolhelp32Snapshot. И да, в MSDN про
RTL_USER_PROCESS_PARAMETERS читать
бесполезно, лучше обратиться к
альтернативным источникам информации.
; Открыть процесс для получения информации
invoke OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
; Сохранить хэндл процесса
mov ebx,eax
; Прочитать PROCESS_BASIC_INFORMATION
invoke NtQueryInformationProcess,ebx,0,Info,\
sizeof.PROCESS_BASIC_INFORMATION,tmp
; Прочитать PEB
mov eax,[Info.PebBaseAddress]
invoke ReadProcessMemory,ebx,eax,peb,sizeof.PEB,tmp
; Прочитать RTL_USER_PROCESS_PARAMETERS
invoke ReadProcessMemory,ebx,[peb.ProcessParameters],pparam,\
sizeof.RTL_USER_PROCESS_PARAMETERS,tmp
; Получить путь запуска с параметрами (CommandLine)
movzx eax, word [pparam.CommandLine.Length]
invoke ReadProcessMemory,ebx,\
[pparam.CommandLine.Buffer],buff1,eax,tmp
; Получить рабочую директорию (CurrentDirectoryPath)
movzx eax, word [pparam.CurrentDirectoryPath.Length]
invoke ReadProcessMemory,ebx,\
[pparam.CurrentDirectoryPath.Buffer],buff2,eax,tmp
; Получить путь запуска (ImagePathName)
movzx eax, word [pparam.ImagePathName.Length]
invoke ReadProcessMemory,ebx,\
[pparam.ImagePathName.Buffer],buff3,eax,tmp
Вот и все, мы получили строку запуска приложения, рабочую директорию и путь к исполняемому файлу. Для сокращения размера кода в нем отсутствуют все проверки на ошибки, оставлен только основной функционал. В реальном проекте, естественно, после каждой выполненной функции обязательно должны проверяться корректность ее выполнения и результат. Также имейте в виду, что все строковые данные в структуре
RTL_USER_PROCESS_PARAMETERS записаны в юникоде, поэтому для конвертирования их в формат ASCII используйте функцию
WideCharToMultiByte.
С 32-битными системами ничего сложного, но теперь, к сожалению, все чаще появляются операционные системы с 64-битной разрядностью. Для 64-битных процессов создаются свои структуры PEB, PROCESS_BASIC_INFORMATION и т.д. Причем получить доступ к ним описанными выше функциями Win32 невозможно, так как эта память находится в другом адресном пространстве с 64-битной адресацией. Однако, совмещение в одной программе возможностей полноценной работы без особых ограничений как под Win32, так и под Win64, на мой взгляд, будет большим плюсом. К примеру, сейчас лишь единицы таскменеджеров могут похвастаться такой универсальностью, остальные или не работают с 64-битными процессами, или же выпускаются в виде двух самостоятельных версий для систем с различной разрядностью.
Я являюсь приверженцем 32-битных операционных систем, и поддержку 64-битных систем расцениваю не иначе как вынужденное и неизбежное зло. Конечно, пришлось провести не один час в отладчике и потратить уйму времени, чтобы собрать по крупицам информацию, зато теперь результат моей работы перед вами. Для начала опишем необходимые структуры для работы с 64-битными процессами. В чем-то они похожи на свои 32-битные аналоги, в чем-то отличаются.
; Структура для получения данных о процессе под Win64
struct PROCESS_BASIC_INFORMATION_WOW64
ExitStatus dd ?
Reserved0 dd ?
PebBaseAddress dq ? ; ULONG64
AffinityMask dq ? ; ULONG64
BasePriority dd ?
Reserved1 dd ?
uUniqueProcessId dq ? ; ULONG64
uInheritedFromUniqueProcessId dq ? ; ULONG64
ends
; Структура PEB процесса под Win64
struct PEB64
InheritedAddressSpace db ?
ReadImageFileExecOptions db ?
BeingDebugged db ?
b003 db ?
Reserved0 dd ?
Mutant dq ?
ImageBaseAddress dq ?
Ldr dq ?
ProcessParameters dq ?
ends
; Юникодная строка в Win64
struct UNICODE_STRING64
Length dw ?
MaximumLength dw ?
Fill dd ?
Buffer dq ? ; ULONG64
ends
; Структура RTL_USER_PROCESS_PARAMETERS64 под Win64
struct RTL_USER_PROCESS_PARAMETERS64
MaximumLength dd ?
Length dd ?
Flags dd ?
DebugFlags dd ?
ConsoleHandle dq ? ; ULONG64
ConsoleFlags dd ?
Reserved dd ?
StdInputHandle dq ? ; ULONG64
StdOutputHandle dq ? ; ULONG64
StdErrorHandle dq ? ; ULONG64
CurrentDirectoryPath UNICODE_STRING64
CurrentDirectoryHandle dq ?
DllPath UNICODE_STRING64
ImagePathName UNICODE_STRING64
CommandLine UNICODE_STRING64
ends
По аналогии с 32-битными системами в сегменте данных на основании структур опишем необходимые нам массивы для чтения данных о процессе.
section '.data' data readable writeable
...
; Данные о 64-битных процессах
Info64 PROCESS_BASIC_INFORMATION_WOW64
peb64 PEB64
pparam64 RTL_USER_PROCESS_PARAMETERS64
А теперь начинается самое интересное. Я уже писал выше, что из 32-битного приложения невозможно получить доступ к 64-битному пространству памяти при помощи обычных функций. Значит будем использовать необычные, то есть недокументированные функции. А именно,
NtWow64QueryInformationProcess64 для получения информации о выбранном процессе и
NtWow64ReadVirtualMemory64 для чтения данных из 64-битного пространства памяти. В MSDN описания этих функций нет, нет даже каких-либо упоминаний, можете даже не искать, к тому же их адреса придется самостоятельно импортировать из
ntdll.dll.
section '.data' data readable writeable
...
; Импортируемые функции
ntdll db 'ntdll.dll',0
nwiname db 'NtWow64QueryInformationProcess64',0
nwmname db 'NtWow64ReadVirtualMemory64',0
; Прототипы функций
; NtWow64QueryInformationProcess64 (
; IN HANDLE DWORD ProcessHandle,
; IN PROCESSINFOCLASS DWORD ProcessInformationClass,
; OUT PVOID DWORD ProcessInformation,
; IN ULONG DWORD ProcessInformationLength,
; OUT PULONG DWORD ReturnLength OPTIONAL
; )
;
; NtWow64ReadVirtualMemory64(
; IN HANDLE DWORD ProcessHandle,
; IN ULONG64 QWORD BaseAddress,
; OUT PVOID DWORD Buffer,
; IN ULONG64 QWORD BufferLength,
; OUT PULONG64 DWORD ReturnLength OPTIONAL
; )
Я долго не мог понять, как можно положить на 32-битный стек параметр функции размером четверного слова (QWORD). Чтобы наступило просветление, мне пришлось даже дизассемблировать и изучить несколько утилит из состава Windоws 7 x64, которые используют эти функции. Оказалось все очень просто. На стек кладется не целиковый QWORD, а по очереди его младший и старший DWORD. Из-за этого вызовы функций чтения памяти в исходнике выглядят немного непривычно. В остальном же принцип работы с процессами мало отличается от 32-битного варианта, последовательность действий точно такая же, разница только в используемых функциях.
; Получить информацию о процессе
invoke OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
; Сохранить хэндл процесса
mov ebx,eax
; Получить адрес функции NtWow64QueryInformationProcess64
invoke GetModuleHandle,ntdll
invoke GetProcAddress,eax,nwiname
; NtWow64QueryInformationProcess64
stdcall eax,ebx,0,Info64,sizeof.PROCESS_BASIC_INFORMATION_WOW64,tmp
; Получить адрес функции NtWow64ReadVirtualMemory64
invoke GetModuleHandle,ntdll
invoke GetProcAddress,eax,nwmname
; Сохранить адрес функции NtWow64ReadVirtualMemory64
mov esi,eax
; Прочитать из памяти PEB64 процесса
push 0
push 0
push sizeof.PEB64
push peb64
mov eax,Info64.PebBaseAddress
push dword [eax+4]
push dword [eax]
; NtWow64ReadVirtualMemory64
stdcall esi,ebx
; Прочитать из памяти ProcessParameters процесса
push 0
push 0
push sizeof.RTL_USER_PROCESS_PARAMETERS64
push pparam64
mov eax,peb64.ProcessParameters
push dword [eax+4]
push dword [eax]
; NtWow64ReadVirtualMemory64
stdcall esi,ebx
; Прочитать из памяти командную строку процесса (CommandLine)
push 0
push 0
movzx eax,[pparam64.CommandLine.Length]
push eax
push buff1
push dword [pparam64.CommandLine.Buffer+4]
push dword [pparam64.CommandLine.Buffer]
; NtWow64ReadVirtualMemory64
stdcall esi,ebx
; Прочитать из памяти рабочий каталог процесса (CurrentDirectoryPath)
push 0
push 0
movzx eax,[pparam64.CurrentDirectoryPath.Length]
push eax
push buff2
push dword [pparam64.CurrentDirectoryPath.Buffer+4]
push dword [pparam64.CurrentDirectoryPath.Buffer]
; NtWow64ReadVirtualMemory64
stdcall esi,ebx
; Прочитать из памяти путь процесса (ImagePathName)
push 0
push 0
movzx eax,[pparam64.ImagePathName.Length]
push eax
push buff3
push dword [pparam64.ImagePathName.Buffer+4]
push dword [pparam64.ImagePathName.Buffer]
; NtWow64ReadVirtualMemory64
stdcall esi,ebx
Как видите, тоже ничего сложного нет, из 32-битного приложения мы легко получили доступ к памяти 64-битного пространства. Строковые данные, как и в случае с 32-битными системами, также хранятся в юникоде.
Остался последний важный момент. Как узнать разрядность процесса? Ведь для разной разрядности нам придется использовать различные методы получения информации. Нам на помощь приходит функция
IsWow64Process. Логика ее работы следующая. Если такая функция отсутствует в системе, или присутствует, но при этом для нашего процесса возвращает FALSE, то это значит, что операционная система 32-битная и никаких 64-битных процессов на ней быть не может. А если она для нашего процесса возвращает TRUE, то система 64-битная и требуется дополнительная проверка. Естественно, подразумевается, что сами мы находимся в контексте 32-битного процесса. Если под 64-битной системой для проверяемого процесса функция IsWow64Process возвращает FALSE, то это 64-битный процесс, иначе 32-битный. Для выполнения всех этих проверок я написал вот такую вспомогательную функцию.
section '.data' data readable writeable
...
; Импортируемые функции
kernel db 'kernel32.dll',0
iwname db 'IsWow64Process',0
Code (Assembler) : Убрать нумерацию
;---------------------------------------------------
; Процедура проверки разрядности процесса
;---------------------------------------------------
; Параметры:
; pID = идентификатор проверяемого процесса
; На выходе:
; EAX = 0 если процесс 32-битный
; EAX = 1 если процесс 64-битный
;---------------------------------------------------
proc IsProcess64 pID:DWORD
local result:DWORD
local temp:DWORD
pusha
; По умолчанию результат FALSE
mov [result],0
; Определить запуск под Win64
invoke GetModuleHandle,kernel
invoke GetProcAddress,eax,iwname
or eax,eax
; Мы под Win32, тут все процессы 32-битные
jz .loc_ret
; Сохранить адрес IsWow64Process
mov esi,eax
; Наш процесс запущен под Win64?
invoke GetCurrentProcess
; IsWow64Process
lea edi,[temp]
stdcall esi,eax,edi
cmp [temp],FALSE
je .loc_ret
; Получить информацию о процессе
invoke OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
or eax,eax
jz .loc_ret
mov ebx,eax
; IsWow64Process
stdcall esi,ebx,edi
; Прибраться за собой
invoke CloseHandle,ebx
; 32-битный процесс под Win64?
cmp [temp],TRUE
je .loc_ret
mov [result],1
.loc_ret:
popa
mov eax,[result]
ret
endp