Немодные вещи куда интересней нежели то, что у всех на слуху и на виду. В мире .NET, например, немодной является рефлексия, о которой знают, но не пользуются в виду преклонения перед мантрами Рихтера. Несомненно, монография «CLR via C#» — лучшее из книг о .NET, однако сам ее автор далеко не везде следует своим же рекомендациям, а потому принимающим на веру абсолютно все написанное в ней, стоит перестать выдавать чужие мысли за свои.
Рефлексия типов действительно достаточно медленная вещь, но не настолько, чтобы отказаться от ее использования вовсе. В случае PowerShell издержки на упаковку и распаковку практически незаметны глазу, поэтому за производительность шибко опасаться не приходится. При этом в некоторых случаях использование рефлексии способно существенно сократить количество кода, что в свою очередь упрощает сопровождение последнего (с чем модники категорически не согласны) и открывает доступ к интимным местам операционной системы в обход оснастке управления (WMI). С точки зрения безопасности это не очень-то и хорошо, но вот в плане системного администрирования — недурственно. Хотя у этого мнения также найдутся свои противники.
Допустим, мы все же решились на использование рефлексии: как наиболее эффективно ее применять в PowerShell? Во-первых, готовых рецептов ни у кого нет, да и вряд ли когда-то будут, ибо самая суть уже описана все в той же «CLR via C#», во-вторых, само по себе понятие «эффективность» относительно, следовательно, рефлексию можно рассматривать лишь как альтернативный вариант решения некоторых задач. В качестве примера — пусть и весьма натянутого, — рассмотрим получение сборок установленных в GAC.
#requires -version 2 $al = New-Object Collections.ArrayList [Object].Assembly.GetType( 'Microsoft.Win32.Fusion' ).GetMethod('ReadCache').Invoke($null, @( [Collections.ArrayList]$al, $null, [UInt32]2 )) $al
В отсутствии gacutil пример вполне может заменить собой первый, запускаемый с ключом /l. Впрочем, интереснее методов-оберток могут быть только WinAPI сигнатуры, однако перебирать типы в которых они имеются ILDASM’ом или просто ковыряться в исходных кода .NET платформы не шибко заманчиво. Почему бы не доверить эту работу самому PowerShell?!
function Find-Pinvoke { <# .EXAMPLE PS C:\> Find-Pinvoke Regex #> param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String]$TypeName ) begin { if (($base = $TypeName -as [Type]) -eq $null) { Write-Warning "В текущем домене приложений указанный тип не нйден." break } } process { foreach ($type in $base.Assembly.GetTypes()) { $type.GetMethods([Reflection.BindingFlags]60) | % { if (($_.Attributes -band 0x2000) -eq 0x2000) { $sig = [Reflection.CustomAttributeData]::GetCustomAttributes( $_ # данные о pinvoke методе ) | ? {$_.ToString() -cmatch 'DllImportAttribute'} New-Object PSObject -Property @{ Module = if (![IO.Path]::HasExtension( ($$ = $sig.ConstructorArguments[0].Value) )) { "$($$).dll" } else { $$ } EntryPoint = ($sig.NamedArguments | ? { $_.MemberInfo.Name -eq 'EntryPoint' }).TypedValue.Value MethodName = $_.Name Attributes = $_.Attributes TypeName = $type.FullName Signature = $_.ToString() -replace '(\S+)\s+(.*)', '$2 as $1' DllImport = $sig } | Select-Object Module, EntryPoint, TypeName, MethodName, ` Attributes, Signature, DllImport } } } #foreach } end {} }
Как оно работает? Мы передаем название некоторого публичного типа, скажем Regex, функции выше, далее тип проверяется на доступность в текущем домене приложений и извлекаются данные о сигнатурах сборки, в которой этот тип определен. Можно, конечно, вывод перенаправить в XML или любой другой формат, дабы не тратиться на повторное сканирование, но это кому как нравится, да и идея здесь главным образом в том, чтобы не размениваться на поиски сигнатур вручную. А сигнатур, между тем, не просто много, а очень много; особый интерес могут вызвать DeviceIoControl (Systemd.Data.dll), а также NtQueryInformationProcess и NtQuerySystemInformation (System.dll), — и вот здесь мы вплотную подобрались к вещам совершенно немодным: чтению данным по смещениям посредством рефлекторно вызываемых методов. В процессе подбора примера ничего оригинального, кроме как вывести список модулей, загруженных системой, не надумалось, так что будем рассматривать его.
PS C:\> Invoke-Debugger ... 0.000> dt ole32!_rtl_process_modules /r +0x000 NumberOfModules : Uint4B +0x004 Modules : [1] _RTL_PROCESS_MODULE_INFORMATION +0x000 Section : Ptr32 Void +0x004 MappedBase : Ptr32 Void +0x008 ImageBase : Ptr32 Void +0x00c ImageSize : Uint4B +0x010 Flags : Uint4B +0x014 LoadOrderIndex : Uint2B +0x016 InitOrderIndex : Uint2B +0x018 LoadCount : Uint2B +0x01a OffsetToFileName : Uint2B +0x01c FullPathName : [256] UChar 0.000> ?? sizeof(ole32!_rtl_process_modules) unsigned int 0x120
То есть, размеры структур RTL_PROCESS_MODULES и RTL_PROCESS_MODULE_INFORMATION равны 288 и 284 байт соответственно.
0.000> dt ole32!_system_information_class ... SystemModuleInformation = 0n11 ...
Здорово! Дело за малым.
#function Get-LoadedModules { begin { # акселератор типа Marshal if (($$ = [PSObject].Assembly.GetType( 'System.Management.Automation.TypeAccelerators' ))::Get.Keys -notcontains 'Marshal') { [void]$$::Add('Marshal', [Runtime.InteropServices.Marshal]) } $NtQuerySystemInformation = [Regex].Assembly.GetType( 'Microsoft.Win32.NativeMethods' ).GetMethod('NtQuerySystemInformation') $ret = 0 } process { try { # устанавливаем истинный размер буфера $ptr = [Marshal]::AllocHGlobal(1024) if ($NtQuerySystemInformation.Invoke($null, ( $par = [Object[]]@(11, $ptr, 1024, $ret) )) -ne 0) { $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3]) if ($NtQuerySystemInformation.Invoke($null, @(11, $ptr, $par[3], 0)) -ne 0) { throw New-Object InvalidOperationException('Что-то пошло не так...') } } # считываем интересующую нас информацию относительно смещений 0..([Marshal]::ReadInt32($ptr) - 1) | % {$i = 12}{ New-Object PSObject -Property @{ Address = '0x{0:X}' -f [Marshal]::ReadInt32($ptr, $i) Size = [Marshal]::ReadInt32($ptr, $i + 4) Name = [IO.Path]::GetFileName(([Marshal]::PtrToStringAnsi( [IntPtr]($ptr.ToInt64() + $i + 20), 256 )).Split("`0")[0]) } $i += 284 # переходим к следующей структуре } | Select-Object Name, Address, Size | Format-Table -AutoSize } catch { $_.Exception } finally { if ($ptr) { [Marshal]::FreeHGlobal($ptr) } } } end { [void]$$::Remove('Marshal') # удаляем акселератор } #}
Вот таким вот незатейливым способом мы извлекли интересующие нас данные, — ничего сложного. При использовании Add-Type то же заняло примерно одинаковое количество кода, разница лишь в том, что в домене приложений не было создано вспомогательной сборки. Можно ли это считать приятным бонусом или это все же пространство для злокодинг-маневра, вопрос риторический.
ссылка на оригинал статьи https://habrahabr.ru/post/282842/
Добавить комментарий