Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Возможность получения информации о стороннем процессе раскрывает перед программистами и пользователями широкие возможности. Это могут быть продвинутые менеджеры процессов, антивирусные и антитроянские программы, утилиты для реверсной инженерии и многое другое. У меня, к примеру, подобные функции используются в программе Quick Task Terminator. Давайте посмотрим, как это делается. Для начала надо описать структуры, необходимые для работы с процессами. В стандартном комплекте FASM их, естественно, нет, но это и неудивительно.
  1. ; Структура для получения данных о процессе под Win32
  2. struct PROCESS_BASIC_INFORMATION
  3.     ExitStatus       dd ?
  4.     PebBaseAddress   dd ?
  5.     AffinityMask     dd ?
  6.     BasePriority     dd ?
  7.     uUniqueProcessId dd ?
  8.     uInheritedFromUniqueProcessId dd ?
  9. ends
  10.  
  11. ; Структура PEB процесса под Win32
  12. ; Process Enviroment Block или блок окружения процесса
  13. ; Содержит все параметры пользовательского режима, ассоциированные
  14. ; системой с текущим процессом
  15. struct PEB
  16.     InheritedAddressSpace    db ?
  17.     ReadImageFileExecOptions db ?
  18.     BeingDebugged            db ?
  19.     b003                     db ?
  20.     Mutant                   dd ?
  21.     ImageBaseAddress         dd ?
  22.     Ldr                      dd ?
  23.     ProcessParameters        dd ?
  24. ends
  25.  
  26. ; Юникодная строка в Win32
  27. struct UNICODE_STRING
  28.     Length                   dw ?
  29.     MaximumLength            dw ?
  30.     Buffer                   dd ?
  31. ends
  32.  
  33. ; Структура RTL_USER_PROCESS_PARAMETERS под Win32
  34. struct RTL_USER_PROCESS_PARAMETERS
  35.     MaximumLength            dd ?
  36.     Length                   dd ?
  37.     Flags                    dd ?
  38.     DebugFlags               dd ?
  39.     ConsoleHandle            dd ?
  40.     ConsoleFlags             dd ?
  41.     StdInputHandle           dd ?
  42.     StdOutputHandle          dd ?
  43.     StdErrorHandle           dd ?
  44.     CurrentDirectoryPath     UNICODE_STRING
  45.     CurrentDirectoryHandle   dd ?
  46.     DllPath                  UNICODE_STRING
  47.     ImagePathName            UNICODE_STRING
  48.     CommandLine              UNICODE_STRING
  49. ends
В сегменте данных на основании структур опишем необходимые нам массивы для чтения данных о процессе.
  1. section '.data' data readable writeable
  2. ...
  3. ; Данные о 32-битных процессах
  4. Info            PROCESS_BASIC_INFORMATION
  5. peb             PEB
  6. 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 читать бесполезно, лучше обратиться к альтернативным источникам информации.
  1.         ; Открыть процесс для получения информации
  2.         invoke  OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
  3.  
  4.         ; Сохранить хэндл процесса
  5.         mov     ebx,eax
  6.  
  7.         ; Прочитать PROCESS_BASIC_INFORMATION
  8.         invoke  NtQueryInformationProcess,ebx,0,Info,\
  9.                 sizeof.PROCESS_BASIC_INFORMATION,tmp
  10.  
  11.         ; Прочитать PEB
  12.         mov     eax,[Info.PebBaseAddress]
  13.         invoke  ReadProcessMemory,ebx,eax,peb,sizeof.PEB,tmp
  14.  
  15.         ; Прочитать RTL_USER_PROCESS_PARAMETERS
  16.         invoke  ReadProcessMemory,ebx,[peb.ProcessParameters],pparam,\
  17.                 sizeof.RTL_USER_PROCESS_PARAMETERS,tmp
  18.  
  19.         ; Получить путь запуска с параметрами (CommandLine)
  20.         movzx   eax, word [pparam.CommandLine.Length]
  21.         invoke  ReadProcessMemory,ebx,\
  22.                 [pparam.CommandLine.Buffer],buff1,eax,tmp
  23.  
  24.         ; Получить рабочую директорию (CurrentDirectoryPath)
  25.         movzx   eax, word [pparam.CurrentDirectoryPath.Length]
  26.         invoke  ReadProcessMemory,ebx,\
  27.                 [pparam.CurrentDirectoryPath.Buffer],buff2,eax,tmp
  28.  
  29.         ; Получить путь запуска (ImagePathName)
  30.         movzx   eax, word [pparam.ImagePathName.Length]
  31.         invoke  ReadProcessMemory,ebx,\
  32.                 [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-битные аналоги, в чем-то отличаются.
  1. ; Структура для получения данных о процессе под Win64
  2. struct PROCESS_BASIC_INFORMATION_WOW64
  3.     ExitStatus        dd ?
  4.     Reserved0         dd ?
  5.     PebBaseAddress    dq ?              ; ULONG64
  6.     AffinityMask      dq ?              ; ULONG64
  7.     BasePriority      dd ?
  8.     Reserved1         dd ?
  9.     uUniqueProcessId  dq ?              ; ULONG64
  10.     uInheritedFromUniqueProcessId dq ?  ; ULONG64
  11. ends
  12.  
  13. ; Структура PEB процесса под Win64
  14. struct PEB64
  15.     InheritedAddressSpace    db ?
  16.     ReadImageFileExecOptions db ?
  17.     BeingDebugged            db ?
  18.     b003                     db ?
  19.     Reserved0                dd ?
  20.     Mutant                   dq ?
  21.     ImageBaseAddress         dq ?
  22.     Ldr                      dq ?
  23.     ProcessParameters        dq ?
  24. ends
  25.  
  26. ; Юникодная строка в Win64
  27. struct UNICODE_STRING64
  28.     Length                   dw ?
  29.     MaximumLength            dw ?
  30.     Fill                     dd ?
  31.     Buffer                   dq ?  ; ULONG64
  32. ends
  33.  
  34. ; Структура RTL_USER_PROCESS_PARAMETERS64 под Win64
  35. struct RTL_USER_PROCESS_PARAMETERS64
  36.     MaximumLength            dd ?
  37.     Length                   dd ?
  38.     Flags                    dd ?
  39.     DebugFlags               dd ?
  40.     ConsoleHandle            dq ?  ; ULONG64
  41.     ConsoleFlags             dd ?
  42.     Reserved                 dd ?
  43.     StdInputHandle           dq ?  ; ULONG64
  44.     StdOutputHandle          dq ?  ; ULONG64
  45.     StdErrorHandle           dq ?  ; ULONG64
  46.     CurrentDirectoryPath     UNICODE_STRING64
  47.     CurrentDirectoryHandle   dq ?
  48.     DllPath                  UNICODE_STRING64
  49.     ImagePathName            UNICODE_STRING64
  50.     CommandLine              UNICODE_STRING64
  51. ends
По аналогии с 32-битными системами в сегменте данных на основании структур опишем необходимые нам массивы для чтения данных о процессе.
  1. section '.data' data readable writeable
  2. ...
  3. ; Данные о 64-битных процессах
  4. Info64          PROCESS_BASIC_INFORMATION_WOW64
  5. peb64           PEB64
  6. pparam64        RTL_USER_PROCESS_PARAMETERS64
А теперь начинается самое интересное. Я уже писал выше, что из 32-битного приложения невозможно получить доступ к 64-битному пространству памяти при помощи обычных функций. Значит будем использовать необычные, то есть недокументированные функции. А именно, NtWow64QueryInformationProcess64 для получения информации о выбранном процессе и NtWow64ReadVirtualMemory64 для чтения данных из 64-битного пространства памяти. В MSDN описания этих функций нет, нет даже каких-либо упоминаний, можете даже не искать, к тому же их адреса придется самостоятельно импортировать из ntdll.dll.
  1. section '.data' data readable writeable
  2. ...
  3. ; Импортируемые функции
  4. ntdll   db 'ntdll.dll',0
  5. nwiname db 'NtWow64QueryInformationProcess64',0
  6. nwmname db 'NtWow64ReadVirtualMemory64',0
  7.  
  8. ; Прототипы функций
  9. ; NtWow64QueryInformationProcess64 (
  10. ;     IN  HANDLE            DWORD ProcessHandle,
  11. ;     IN  PROCESSINFOCLASS  DWORD ProcessInformationClass,
  12. ;     OUT PVOID             DWORD ProcessInformation,
  13. ;     IN  ULONG             DWORD ProcessInformationLength,
  14. ;     OUT PULONG            DWORD ReturnLength OPTIONAL
  15. ; )
  16. ;
  17. ; NtWow64ReadVirtualMemory64(
  18. ;     IN  HANDLE            DWORD ProcessHandle,
  19. ;     IN  ULONG64           QWORD BaseAddress,
  20. ;     OUT PVOID             DWORD Buffer,
  21. ;     IN  ULONG64           QWORD BufferLength,
  22. ;     OUT PULONG64          DWORD ReturnLength OPTIONAL
  23. ; )
Я долго не мог понять, как можно положить на 32-битный стек параметр функции размером четверного слова (QWORD). Чтобы наступило просветление, мне пришлось даже дизассемблировать и изучить несколько утилит из состава Windоws 7 x64, которые используют эти функции. Оказалось все очень просто. На стек кладется не целиковый QWORD, а по очереди его младший и старший DWORD. Из-за этого вызовы функций чтения памяти в исходнике выглядят немного непривычно. В остальном же принцип работы с процессами мало отличается от 32-битного варианта, последовательность действий точно такая же, разница только в используемых функциях.
  1.         ; Получить информацию о процессе
  2.         invoke  OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
  3.         ; Сохранить хэндл процесса
  4.         mov     ebx,eax
  5.  
  6.         ; Получить адрес функции NtWow64QueryInformationProcess64
  7.         invoke  GetModuleHandle,ntdll
  8.         invoke  GetProcAddress,eax,nwiname
  9.  
  10.         ; NtWow64QueryInformationProcess64
  11.         stdcall eax,ebx,0,Info64,sizeof.PROCESS_BASIC_INFORMATION_WOW64,tmp
  12.  
  13.         ; Получить адрес функции NtWow64ReadVirtualMemory64
  14.         invoke  GetModuleHandle,ntdll
  15.         invoke  GetProcAddress,eax,nwmname
  16.         ; Сохранить адрес функции NtWow64ReadVirtualMemory64
  17.         mov     esi,eax
  18.  
  19.         ; Прочитать из памяти PEB64 процесса
  20.         push    0
  21.         push    0
  22.         push    sizeof.PEB64
  23.         push    peb64
  24.         mov     eax,Info64.PebBaseAddress
  25.         push    dword [eax+4]
  26.         push    dword [eax]
  27.         ; NtWow64ReadVirtualMemory64
  28.         stdcall esi,ebx
  29.  
  30.         ; Прочитать из памяти ProcessParameters процесса
  31.         push    0
  32.         push    0
  33.         push    sizeof.RTL_USER_PROCESS_PARAMETERS64
  34.         push    pparam64
  35.         mov     eax,peb64.ProcessParameters
  36.         push    dword [eax+4]
  37.         push    dword [eax]
  38.         ; NtWow64ReadVirtualMemory64
  39.         stdcall esi,ebx
  40.  
  41.         ; Прочитать из памяти командную строку процесса (CommandLine)
  42.         push    0
  43.         push    0
  44.         movzx   eax,[pparam64.CommandLine.Length]
  45.         push    eax
  46.         push    buff1
  47.         push    dword [pparam64.CommandLine.Buffer+4]
  48.         push    dword [pparam64.CommandLine.Buffer]
  49.         ; NtWow64ReadVirtualMemory64
  50.         stdcall esi,ebx
  51.  
  52.         ; Прочитать из памяти рабочий каталог процесса (CurrentDirectoryPath)
  53.         push    0
  54.         push    0
  55.         movzx   eax,[pparam64.CurrentDirectoryPath.Length]
  56.         push    eax
  57.         push    buff2
  58.         push    dword [pparam64.CurrentDirectoryPath.Buffer+4]
  59.         push    dword [pparam64.CurrentDirectoryPath.Buffer]
  60.         ; NtWow64ReadVirtualMemory64
  61.         stdcall esi,ebx
  62.  
  63.         ; Прочитать из памяти путь процесса (ImagePathName)
  64.         push    0
  65.         push    0
  66.         movzx   eax,[pparam64.ImagePathName.Length]
  67.         push    eax
  68.         push    buff3
  69.         push    dword [pparam64.ImagePathName.Buffer+4]
  70.         push    dword [pparam64.ImagePathName.Buffer]
  71.         ; NtWow64ReadVirtualMemory64
  72.         stdcall esi,ebx
Как видите, тоже ничего сложного нет, из 32-битного приложения мы легко получили доступ к памяти 64-битного пространства. Строковые данные, как и в случае с 32-битными системами, также хранятся в юникоде.

Остался последний важный момент. Как узнать разрядность процесса? Ведь для разной разрядности нам придется использовать различные методы получения информации. Нам на помощь приходит функция IsWow64Process. Логика ее работы следующая. Если такая функция отсутствует в системе, или присутствует, но при этом для нашего процесса возвращает FALSE, то это значит, что операционная система 32-битная и никаких 64-битных процессов на ней быть не может. А если она для нашего процесса возвращает TRUE, то система 64-битная и требуется дополнительная проверка. Естественно, подразумевается, что сами мы находимся в контексте 32-битного процесса. Если под 64-битной системой для проверяемого процесса функция IsWow64Process возвращает FALSE, то это 64-битный процесс, иначе 32-битный. Для выполнения всех этих проверок я написал вот такую вспомогательную функцию.
  1. section '.data' data readable writeable
  2. ...
  3. ; Импортируемые функции
  4. kernel  db 'kernel32.dll',0
  5. iwname  db 'IsWow64Process',0
  1. ;---------------------------------------------------
  2. ; Процедура проверки разрядности процесса
  3. ;---------------------------------------------------
  4. ; Параметры:
  5. ;   pID = идентификатор проверяемого процесса
  6. ; На выходе:
  7. ;   EAX = 0 если процесс 32-битный
  8. ;   EAX = 1 если процесс 64-битный
  9. ;---------------------------------------------------
  10. proc    IsProcess64 pID:DWORD
  11.         local   result:DWORD
  12.         local   temp:DWORD
  13.         pusha
  14.  
  15.         ; По умолчанию результат FALSE
  16.         mov     [result],0
  17.  
  18.         ; Определить запуск под Win64
  19.         invoke  GetModuleHandle,kernel
  20.         invoke  GetProcAddress,eax,iwname
  21.         or      eax,eax
  22.         ; Мы под Win32, тут все процессы 32-битные
  23.         jz      .loc_ret
  24.  
  25.         ; Сохранить адрес IsWow64Process
  26.         mov     esi,eax
  27.  
  28.         ; Наш процесс запущен под Win64?
  29.         invoke  GetCurrentProcess
  30.         ; IsWow64Process
  31.         lea     edi,[temp]
  32.         stdcall esi,eax,edi
  33.         cmp     [temp],FALSE
  34.         je      .loc_ret
  35.  
  36.         ; Получить информацию о процессе
  37.         invoke  OpenProcess,PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,[pID]
  38.         or      eax,eax
  39.         jz      .loc_ret
  40.  
  41.         mov     ebx,eax
  42.         ; IsWow64Process
  43.         stdcall esi,ebx,edi
  44.  
  45.         ; Прибраться за собой
  46.         invoke  CloseHandle,ebx
  47.  
  48.         ; 32-битный процесс под Win64?
  49.         cmp     [temp],TRUE
  50.         je      .loc_ret
  51.  
  52.         mov     [result],1
  53. .loc_ret:
  54.         popa
  55.         mov     eax,[result]
  56.         ret
  57. endp
Поскольку готовых исходников в этой статье не будет, в качестве компенсации выкладываю удобную картинку-шпаргалку по работе с функцией NtQueryInformationProcess.

Шпаргалка по NtQueryInformationProcessШпаргалка по NtQueryInformationProcess

NtQueryInformationProcess.Sheet.zip (458,952 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 10069 | Комментариев: 6

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (20.09.2018 в 10:38):
Вот еще хорошее описание структуры PEB вплоть до современных систем: http://bytepointer.com/resources/tebpeb32.htm
NoName (08.11.2014 в 17:19):
Спасибо за статью, есть всё что мне надо =)
Марат (09.06.2013 в 22:48):
Огромное спасибо и низкий поклон за Ваши труды
ManHunter (18.05.2013 в 21:31):
К парсеру прикручена экспериментальная фича - подсветка WinAPI в ассемблерных листингах.
ManHunter (17.05.2013 в 18:39):
А я пробовал по аналогии qword [var], нифига не получилось. Спасибо!
disciple27 (17.05.2013 в 15:13):
ManHunter вы прям мысли читаете, как раз такое искал)))
в стек dq можно так загонять, win32ax/wx только надо:
var dq 0
invoke proc, double [var]

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

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.09 сек. / MySQL: 2 (0.0049 сек.) / Память: 4.5 Mb
Наверх