Вывод комадлета Get-Process для восприятия не слишком удобен — просто список, в котором данные ко всему прочему отражены в стиле стран ближнего востока, то бишь справа налево, подается Microsoft как идеолгоически верный путь к пониманию сути того, что творится в процессах. Речь не о том, как с помощью PowerShell палить вирусы, а об отсутствии визуального представления какой процесс какого Ерофеича родил, чай, ведь пятая версия PowerShell'а, а древовидного представления по-прежнему нема, да и в ближайшей перспективе, должно быть, не предвидется.
И здесь из зрительного зала раздается: «А нафига нам древовидное представление процессов в PowerShell, когда есть ProcessExplorer, на худой конец — pslist?» Во-первых, GUI для консольщика как серпом по яйцам, во-вторых, какой резон разводить зоопарк из набора сторонних утилит, когда наличие PowerShell по сути является синонимом «уже все есть»? — остается лишь творить под цвет своих фломастеров. Преамбула приобретает некий сюрреалистический оттенок, да и рискует затянуться, если продолжать в том же роде, так что готовим фломастеры…
Сюрприз!
Если кто-то из читающих раскатал губу на WMI, то может смело закатывать ее фломастером обратно, ибо речь опять-таки пойдет о рефлексии, точнее не столько о ней самой, сколько о достижении цели через нее. Кудряво сказано, но да ладно. Как подсказывает Кэп, для построения дерева процессов нужно знать такие параметры, как имя процесса, его PID, а также PPID. Последний служит отправной точной при определении отпрыска от родителя, причем получить оный в Windows можно как миниму тремя приемами дзюдо: счетчики производительности, WMI и NtQuerySystemInformation. Первый (ровно как и второй) идет лесом, так как в грубом приближении является оберткой над NtQuerySystemInformation, токмо с тормозами в придачу — показатель варьируется от начинки ПК, но это тема отдельного разговора.
Открываем Vim (или что у кого там любимое) и пишем:
Set-Variable ($$ = [Regex].Assembly.GetType(
'Microsoft.Win32.NativeMethods'
).GetMethod('NtQuerySystemInformation')).Name $$
Итак, мы определили переменую $NtQuerySystemInformation. Теперь нужно получить указатель на структуру SYSTEM_PROCESS_INFORMATION, на Win7 x86 выглядящую так:
+0x000 NextEntryOffset : Uint4B
+0x004 NumberOfThreads : Uint4B
+0x008 WorkingSetPrivateSize : _LARGE_INTEGER
+0x010 HardFaultCount : Uint4B
+0x014 NumberOfThreadsHighWatermark : Uint4B
+0x018 CycleTime : Uint8B
+0x020 CreateTime : _LARGE_INTEGER
+0x028 UserTime : _LARGE_INTEGER
+0x030 KernelTime : _LARGE_INTEGER
+0x038 ImageName : _UNICODE_STRING
+0x040 BasePriority : Int4B
+0x044 UniqueProcessId : Ptr32 Void
+0x048 InheritedFromUniqueProcessId : Ptr32 Void
+0x04c HandleCount : Uint4B
+0x050 SessionId : Uint4B
+0x054 UniqueProcessKey : Uint4B
+0x058 PeakVirtualSize : Uint4B
+0x05c VirtualSize : Uint4B
+0x060 PageFaultCount : Uint4B
+0x064 PeakWorkingSetSize : Uint4B
+0x068 WorkingSetSize : Uint4B
+0x06c QuotaPeakPagedPoolUsage : Uint4B
+0x070 QuotaPagedPoolUsage : Uint4B
+0x074 QuotaPeakNonPagedPoolUsage : Uint4B
+0x078 QuotaNonPagedPoolUsage : Uint4B
+0x07c PagefileUsage : Uint4B
+0x080 PeakPagefileUsage : Uint4B
+0x084 PrivatePageCount : Uint4B
+0x088 ReadOperationCount : _LARGE_INTEGER
+0x090 WriteOperationCount : _LARGE_INTEGER
+0x098 OtherOperationCount : _LARGE_INTEGER
+0x0a0 ReadTransferCount : _LARGE_INTEGER
+0x0a8 WriteTransferCount : _LARGE_INTEGER
+0x0b0 OtherTransferCount : _LARGE_INTEGER
Причем из всей структуры нас интересуют такие поля как NextEntryOffset, ImageName, UniqueProcessId и InheritedFromUniqueProcessId, так что для получения только этих четырех полей определять структуру в домене приложений слишком жирно — воспользуемся методами типа Marshal.
Получаем указатель:
if (($ta = [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
))::Get.Keys -notcontains 'Marshal') {
$ta::Add('Marshal', [Runtime.InteropServices.Marshal])
}
$ret = 0
try {
#задаем размер буфера минимальным значением
$ptr = [Marshal]::AllocHGlobal(1024)
if ($NtQuerySystemInformation.Invoke($null, (
$par = [Object[]]@(5, $ptr, 1024, $ret)
)) -eq 0xC0000004) { #STATUS_INFO_LENGTH_MISMATCH
$ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3])
if ($NtQuerySystemInformation.Invoke($null, (
$par = [Object[]]@(5, $ptr, $par[3], 0)
)) -ne 0) {
throw New-Object InvalidOperationException('Что-то пошло не так...')
}
}
}
catch { $_.Exception }
finally {
if ($ptr -ne $null) {
[Marshal]::FreeHGlobal($ptr)
}
}
Указатель получили, читаем данные. Стоп! А ведь ImageName — это структура UNICODE_STRING, как быть? Делаем ход конем:
$UNICODE_STRING = [Activator]::CreateInstance(
[Object].Assembly.GetType(
'Microsoft.Win32.Win32Native+UNICODE_STRING'
)
)
Вот теперь мы во всеоружии и готовы «читать» указатель.
$len = [Marshal]::SizeOf($UNICODE_STRING) - 1
$tmp = $ptr
$Processes = while (($$ = [Marshal]::ReadInt32($tmp))) { #NextEntryOffset
[Byte[]]$bytes = 0..$len | ForEach-Object {$ofb = 0x38}{
[Marshal]::ReadByte($tmp, $ofb)
$ofb++
}
#конвертируем байты в UNICODE_STRING
$gch = [Runtime.InteropServices.GCHandle]::Alloc($bytes, 'Pinned')
$uni = [Marshal]::PtrToStructure(
$gch.AddrOfPinnedObject(), [Type]$UNICODE_STRING.GetType()
)
$gch.Free()
New-Object PSObject -Property @{
ProcessName = if ([String]::IsNullOrEmpty((
$proc = $uni.GetType().GetField(
'Buffer', [Reflection.BindingFlags]36
).GetValue($uni))
)) { 'Idle' } else { $proc }
PID = [Marshal]::ReadInt32($tmp, 0x44)
PPID = [Marshal]::ReadInt32($tmp, 0x48)
}
$tmp = [IntPtr]($tmp.ToInt32() + $$)
}
Переменная $Processes отныне хранит массив объектов PSObject, эдакие контейнеры для нужных нам данных. Теперь, согласно женевской конвенции, остается построить само дерево.
function Get-ProcessChild {
param(
[Parameter(Mandatory=$true, Position=0)]
[PSObject]$Process,
[Parameter(Position=1)]
[Int32]$Depth = 1
)
$Processes | Where-Object {
$_.PPID -eq $Process.PID -and $_.PPID -ne 0
} | ForEach-Object {
"$("$([Char]32)" * 2 * $Depth)$($_.ProcessName) ($($_.PID))"
Get-ProcessChild $_ (++$Depth)
$Depth--
}
}
$Processes | Where-Object {
-not (Get-Process -Id $_.PPID -ea 0) -or $_.PPID -eq 0
} | ForEach-Object {
"$($_.ProcessName) ($($_.PID))"
Get-ProcessChild $_
}
После запуска получим в хосте древовидное представление процессов. Собственно, на этом вечерний эротический сеанс показ окончен, можно расходиться.
Автор: GrigoriZakharov