...

суббота, 2 июля 2016 г.

PowerShell для исследователя

У каждого исследователя есть свой проверенный годами набор инструментов, однако бывает так, что какой-то из них снимается с поддержки и, как следствие, его дальнейшим развитием и разработкой никто не занимается (ностальгия по SoftICE, эх...), а бывает и так, что между релизами проходит внушительный отрезок времени, между которым кто-то успевает создать более продвинутый аналог некой программы или утилиты. Впрочем, по мере изучения устройства операционных систем исследователь обзаводится своими собственными наработками, постепенно вытесняющими если не все, то по крайней мере большее из того, чем он пользовался ранее.

Само собой разумеется, что речь не пойдет о том как соорудить посредством PowerShell некий отладчик, хотя теоретически это вполне возможно и допустимо. И потом, если подавать подобного рода информацию «в лоб», у некоторых это может вызвать недоумение, так что лучше говорить о подного рода вещах поступательно, в некоторых случаях не раскрывая деталей целиком, тем самым стимулируя читателя на свои собственные эксперименты и изыскания.

Strings


Не могу сказать, что strings из набора Sysinternals одна из самых востребованных утилит и все же время от времени она используется. Соорудить ее аналог на PowerShell можно буквально за пять минут.
function Get-Strings {
  param(
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    [ValidateScript({Test-Path $_})]
    [String]$FileName,
    
    [Alias('b')]
    [UInt32]$BytesToProcess = 0,
    
    [Alias('f')]
    [UInt32]$BytesOffset = 0,
    
    [Alias('n')]
    [Byte]$StringLength = 3, #длина строки по умолчанию
    
    [Alias('o')]
    [Switch]$StringOffset,
    
    [Alias('u')]
    [Switch]$Unicode
  )
  
  begin {
    $FileName = Resolve-Path $FileName
    
    $enc = switch ($Unicode) {
      $true  {[Text.Encoding]::Unicode}
      $false {[Text.Encoding]::UTF7} #кодировка по умолчанию
    }
    #вспомогательная функция, преобразующая байты в строки
    function private:Read-Buffer([Byte[]]$Bytes) {
      ([Regex]"[\x20-\x7E]{$StringLength,}").Matches(
        $enc.GetString($Bytes)
      ) | ForEach-Object {
        if ($StringOffset) {'{0}:{1}' -f $_.Index, $_.Value} else {$_.Value}
      }
    }
  }
  process {
    try {
      $fs = [IO.File]::OpenRead($FileName)
      #количество байтов для обработки, ровно как и смещение, не могут быть
      #больше длины самого файла, иначе данные находятся за пределами потока
      if ($BytesToProcess -ge $fs.Length -or $BytesOffset -ge $fs.Length) {
        throw New-Object InvalidOperationException('Out of stream.')
      }
      #если указано смещение, прыгаем в нужное место
      if ($BytesOffset -gt 0) {[void]$fs.Seek($BytesOffset, [IO.SeekOrigin]::Begin)}
      #количество байтов для обработки, по умолчанию считываются все байты
      $buf = switch ($BytesToProcess -gt 0) {
        $true  {New-Object Byte[] ($fs.Length - ($fs.Length - $BytesToProcess))}
        $false {New-Object Byte[] $fs.Length}
      }
      [void]$fs.Read($buf, 0, $buf.Length)
      Read-Buffer $buf #выводим данные в хост
    }
    catch { $_.Exception }
    finally {
      if ($fs) {
        $fs.Dispose()
        $fs.Close()
      }
    }
  }
  end {}
}

Set-Alias strings Get-Strings #псевдоним для нашей функции


Примеры:
PS E:\proj> strings -b 100 -f 20 debug\app.exe
!This program cannot be run in DOS mode.
PS E:\proj> strings -u debug\app.exe
...


Словом, по возможностям наша функция практически не отличима от оригинальной утилиты.

Обертки kd.exe


А вот это уже крайне полезные в быту вещи, так как вместо того, чтобы вызывать всякий раз kd.exe для того, чтобы посмотреть, скажем, на то как выглядит та или иная структура, достаточно «скормить» PowerShell команду-обертку.
function Invoke-DisplayType {
  param(
    [Parameter(Mandatory=$true, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$Module,
    
    [Parameter(Mandatory=$true, Position=1)]
    [ValidateNotNullOrEmpty()]
    [String]$DataType,
    
    [Parameter(Position=2)]
    [String]$Arguments = $null
  )
  
  begin {
    #если путь к Debugging Tools не прописан в переменной PATH, задаем его
    #для текущей сессии автоматически, извлекая оный до утилит из ярлыка
    #WinDbg, так как установочный пакет не пишет его в реестр
    if ((Get-Command -CommandType Application kd.exe -ea 0) -eq $null) {
      $env:path += ";$(Split-Path (
        New-Object -ComObject WScript.Shell
      ).CreateShortcut((
        Get-ChildItem $env:allusersprofile -Include WinDbg.lnk -Recurse
      ).FullName).TargetPath)"
    }
    
    if (!$Module.EndsWith('.dll')) {
      $BaseName = $Module
      $Module += '.dll'
    }
    else {
      $BaseName = $Module.Substring(0, $Module.LastIndexOf('.'))
    }
    $Module = "$([Environment]::SystemDirectory)\$Module"
    
    if (!(Test-Path $Module)) {
      throw "Модуль $Module не найден."
    }
  }
  process {
    #вызываем kd, попутно отсеивая лишнее из вывода
    kd -z $Module -c "dt $BaseName!_$DataType $Arguments;q" -r -snc |
    Select-String -Pattern '\A\s+'
  }
  end {}
}

Set-Alias dt Invoke-DisplayType


Примеры:
PS E:\proj> dt ole32 system_information_class

   SystemBasicInformation = 0n0
   SystemProcessorInformation = 0n1
   SystemPerformanceInformation = 0n2
   SystemTimeOfDayInformation = 0n3
   SystemPathInformation = 0n4
   SystemProcessInformation = 0n5
...

PS E:\proj> dt ole32 io_status_block

   +0x000 Status           : Int4B
   +0x000 Pointer          : Ptr32 Void
   +0x004 Information      : Uint4B

PS E:\proj> dt ole32 system* /t
...


В принципе можно было бы задать $Module по умолчанию, но лично мне более нравится синтаксическая близость к командам отладчика. Таким вот незатейливым способом можно наваять и другие обертки, скажем sizeof, uf и т.д.

Продолжение следует...

Комментарии (0)

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий