Получение информации о другом процессе

Возможность получения информации о стороннем процессе раскрывает перед программистами и пользователями широкие возможности. Это могут быть продвинутые менеджеры процессов, антивирусные и антитроянские программы, утилиты для реверсной инженерии и многое другое. Для начала надо описать структуры, необходимые для работы с процессами. В стандартном комплекте 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
Добавлено: 13 Сентября 2013 02:24:21 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...