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} #кодування за замовчуванням
}
#допоміжна функція, яка перетворює байти в рядки
private function: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 коментарів

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