Pipelist в PowerShell

Найбільш поширеним методом отримання списку іменованих каналів серед буржуїнів є невигадлива команда:

[IO.Directory]::GetFiles(($$='\\.\pipe\'))|%{$_.Replace($$, ")}

Однак тим, кому раніше доводилося використовувати pipelist з набору Sysinternals Suite, висновок даної команди явно здасться малоінформативним.

Доступ до пайпам через метод GetFiles можливий завдяки двом функцій WinAPI, обгорткою для яких він є: FindFirstFile і FindNextFile. Ті в свою чергу використовують ряд NativeAPI функцій, одна з яких — NtQueryDirectoryFile (її опис можна знайти в MSDN) — згідно дізассемблер задіюється в утиліті pipelist.exe безпосередньо. Давайте подивимося, як це все працює в наближенні.

Find[First|Next]File
Тут винаходити нічого не будемо, а скористаємося рефлексією для демонстрації механізму закладеного в методі GetFiles.

#витягаємо FindFirstFile і FindNextFile
[Object].Assembly.GetType(
'Microsoft.Win32.Win32Native'
).GetMethods(
[Reflection.BindingFlags]40
) | Where-Object {
$_.Name -cmatch '\AFind(First|Next)File\Z'
} | ForEach-Object {
Set-Variable $_.Name $_
}

#обидві функції вимагають структуру WIN32_FIND_DATA
$WIN32_FIND_DATA = [Object].Assembly.GetType(
'Microsoft.Win32.Win32Native+WIN32_FIND_DATA'
).GetConstructor(
[Reflection.BindingFlags]20, $null, [Type[]]@(), $null
).Invoke($null)

#якщо раптом не вдалося отримати доступ до пайпам
if (($fff = $FindFirstFile.Invoke($null @(
'\\.\pipe\*', $WIN32_FIND_DATA
))).IsInvalid) {
(New-Object ComponentModel.Win32Exception(
[Runtime.InteropServices.Marshal]::GetLastWin32Error()
)).Message
break
}

#лямбда-функція, яка виводить дані з структури WIN32_FIND_DATA
&($lambda = {
$WIN32_FIND_DATA.GetType().GetFields(
[Reflection.BindingFlags]36
) | ForEach-Object {$data = @{}}{
$data[$_.Name] = $_.GetValue($WIN32_FIND_DATA)
}{
$data | Select-Object @{N='PipeName';E={$_.cFileName}}, @{
N='Instances';E={$_.nFileSizeLow}
}
}
})

#викликаємо FindNextFile до тих пір, поки вона не поверне $false
while ($FindNextFile.Invoke($null @($fff, $WIN32_FIND_DATA))) {
&$lambda
}

#звільняємо ресурси
if ($fff) {
$fff.Dispose()
$fff.Close()
}

Висновок стає більш наближеним до pipelist.exe не вистачає хіба що графи «Max Instances», але це максимум, який можна вичавити в даному випадку.

NtQueryDirectoryFile
У збірках, які я встиг розколупати, ця функція ніде не значиться, а тому в даному випадку рефлексія йде лісом. Можна, звичайно, звернутися за допомогою до динамічних методів укупі з узагальненими делегатами або статті командлет Add-Type, але особисто мені здається більш простим написати все спочатку на C#, оформивши це як команду.

Код команди
using System;
using System.IO;
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;

namespace Pipelist {
[StructLayout(LayoutKind.Explicit, Size = 8)]
internal struct LARGE_INTEGER {
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
[FieldOffset(0)]internal Int64 QuadPart;
[FieldOffset(0)]internal UInt32 LowPart;
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
[FieldOffset(4)]internal Int32 HighPart;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct FILE_DIRECTORY_INFORMATION {
internal UInt32 NextEntryOffset;
internal UInt32 FileIndex;
internal LARGE_INTEGER CreationTime;
internal LARGE_INTEGER LastAccessTime;
internal LARGE_INTEGER LastWriteTime;
internal LARGE_INTEGER ChangeTime;
internal LARGE_INTEGER EndOfFile;
internal LARGE_INTEGER AllocationSize;
internal UInt32 FileAttributes;
internal UInt32 FileNameLength;
internal UInt16 FileName;

[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal Int32 FileNameOffset { //щоб не викликати безпосередньо в циклі
get {
return Marshal.OffsetOf(
typeof(FILE_DIRECTORY_INFORMATION), "FileName"
).ToInt32();
}
}
}

internal static class NativeMethods {
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SafeFileHandle CreateFile(
String lpFileName,
UInt32 dwDesiredAccess,
FileShare dwShareMode,
IntPtr lpSecuriryAttributes,
FileMode dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile
);

[DllImport("ntdll.dll")]
internal static extern Int32 NtQueryDirectoryFile(
SafeFileHandle FileHandle,
IntPtr Event,
IntPtr ApcRoutine,
IntPtr ApcContext,
out IntPtr IoStatusBlock,
[Out] IntPtr FileInformation,
UInt32 Length,
UInt32 FileInformationClass,
[MarshalAs(UnmanagedType.Bool)]
Boolean ReturnSingleEntry,
IntPtr FileName,
[MarshalAs(UnmanagedType.Bool)]
Boolean RestartScan
);
}

[Cmdlet(VerbsCommon.Get, "PipeList")]
public sealed class GetPipeListCommand : PSCmdlet {
const UInt32 FileDirectoryInformation = 1;
const UInt32 GENERIC_READ = 0x80000000;
const Int32 STATUS_SUCCESS = 0x00000000;
const Int32 BufferLength = 0x00001000;

private static void GetLastError() {
Console.WriteLine(
new Win32Exception(Marshal.GetLastWin32Error()).Message
);
}

private static T PtrToStruct<T>(IntPtr p) {
return (T)Marshal.PtrToStructure(p, typeof(T));
}

protected override void ProcessRecord() {
SafeFileHandle pipes;
IntPtr dir, tmp, isb;
Boolean query = true;

pipes = NativeMethods.CreateFile( //чіпляємося до пайпам
@"\\.\\pipe\", GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero
);

if (pipes.IsInvalid) {
GetLastError();
return;
}

Console.WriteLine("{0,-40}{1,14}{2,20}", "Pipe Name", "Instances", "Max Instances");
Console.WriteLine("{0,-40}{1,14}{2,20}", "---------", "---------", "-------------");

dir = Marshal.AllocHGlobal(BufferLength);
try {
while (true) { //"опитуємо" до тих пір, поки нічого не залишиться
if (NativeMethods.NtQueryDirectoryFile(
pipes, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out isb, dir,
BufferLength, FileDirectoryInformation, false, IntPtr.Zero, query
) != STATUS_SUCCESS) break;

tmp = dir;
while (true) {
FILE_DIRECTORY_INFORMATION fdi = PtrToStruct<FILE_DIRECTORY_INFORMATION>(tmp);
IntPtr name = (IntPtr)(fdi.FileNameOffset + tmp.ToInt32());

Console.WriteLine("{0,-40}{1,14}{2,20}",
Marshal.PtrToStringUni(name, (Int32)(fdi.FileNameLength / 2)),
fdi.EndOfFile.LowPart,
(Int32)fdi.AllocationSize.LowPart
);

if (fdi.NextEntryOffset == 0) break;
tmp = (IntPtr)(tmp.ToInt32() + fdi.NextEntryOffset);
}

query = false;
}
}
catch (Exception e) {
Console.WriteLine(e);
}
finally {
Marshal.FreeHGlobal(dir);
pipes.Dispose();
}

pipes.Close();
}
}
}


Ідеологічно правильним було б використовувати щось на зразок WriteObject замість Console.WriteLine при створенні команди, код ж вище — всього лише приклад, в якому можна поступитися ідеологією.

Збірка команди з хоста PowerShell така: прописуємо в змінній PATH шлях до компілятора C# і переходимо в каталог з попередником.

PS E:\> $env:path += ';E:\Windows\Microsoft.NET\vXXXX' #XXXX -версія фреймворку
PS E:\> pushd proj
PS E:\proj>

Безпосередньо збірка:

PS E:\proj> copy $([PSObject].Assembly.Location)
PS E:\proj> csc /nologo /t:library /out:PipeList.dll /optimize+ /debug:pdbonly /r:System.Management.Automation.dll source.cs

Складання імпортуємо як модуль:

PS E:\proj> Import-Module .\PipeList.dll

Викликаємо наш команди:

PS E:\proj> Get-PipeList

Ось тепер у нас повноцінний pipelist.exe. При бажанні можна доповнити код «розшифровкою» окремо взятого пайпа, скажімо, keysvc відноситься до одного з криптографічних сервісів IKeySvc або ICertProtect, а ntsvcs — до сервісів plug and play, але це щось з розряду свистоперделок.
Джерело: Хабрахабр

0 коментарів

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