Дерево процесів

Висновок комадлета 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 $_
}

Після запуску отримаємо в хості подання процесів. Власне, на цьому вечірнє еротичний сеанс показ закінчено, можна розходитися.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.