!vadump или первый шаг на пути превращения PowerShell в WinDbg

в 20:42, , рубрики: powershell, Программирование, разработка под windows

Три чукотских мудреца
Твердят, твердят мне без конца,
Мол, PoSh не принесет плода,
Игра не стоит свеч,
А результат — труда.
Но я сажаю powershell'овские огрурцы, а-а-а,
На DotNet'овском поле...

Те, кто довольно часто общается с WinDbg, знают о команде !vadump, отображающей весь диапазон виртуальной памяти процесса с соответствующими уровнями защиты, — более подробную информацию можно почерпнуть в руководстве WinDbg. В сущности данное расширение всего лишь обертка над апишной функцией VirtualQueryEx, которая в свою очередь задействует Native API, а именно NtQueryVirtualMemory — и так далее. Впрочем, если не лезть в дебри, а также учитывая опыт использования рефлексии в купе с обобщенными делегатами, можно вполне написать !vadump на PowerShell самостоятельно. И если кто-то сейчас захотел открыть свой васистдас и сказать, дескать, на кол оно нужно, то для начала задайтесь вопросом: а для чего, собственно, вообще все делается?

Что нам понадобится? GetSystemInfo, OpenProcess, VirtualQueryEx, — это что касается WinAPI. CloseHandle для OpenProcess не нужен, так как возвращаемый тип вызываемой рефлекторно OpenProcess — SafeProcessHandle, следовательно, Dispose и Close нам в руки. Для начала получим указатель на так называемый старший адрес памяти, доступный приложениям и dll'кам.

$GetSystemInfo = [Object].Assembly.GetType(
  'Microsoft.Win32.Win32Native'
).GetMethod(
  'GetSystemInfo', [Reflection.BindingFlags]40
)

$si = [Activator]::CreateInstance([Object].Assembly.GetType(
  ($GetSystemInfo.GetParameters() | ForEach-Object {
    $_.ParameterType.FullName.Trim('&')
  })
))

$si — это структура SYSTEM_INFO.

$GetSystemInfo.Invoke($null, ($par = [Object[]]@($si)))
$max = $par[0].GetType().GetField(
  'lpMaximumApplicationAddress', [Reflection.BindingFlags]36
).GetValue($par[0])

$max — это и есть искомый указатель. Получаем хэндл процесса (относительно ID):

$OpenProcess = [Regex].Assembly.GetType(
  'Microsoft.Win32.NativeMethods'
).GetMethod('OpenProcess')

if (($sph = $OpenProcess.Invoke($null, @(0x400, $false, $Id))).IsInvalid) {
  break
}
$ptr = $sph.DangerousGetHandle()

VirtualQueryEx вызываем посредством обобщенного делегата Func:

$VirtualQueryEx = Set-Delegate kernel32 VirtualQueryEx `
                        '[Func[IntPtr, IntPtr, [Byte[]], UInt32, Int32]]'

$mbi = New-Object Byte[] 28 #MEMORY_BASIC_INFORMATION
if ($VirtualQueryEx.Invoke($ptr, [IntPtr]::Zero, $mbi, $mbi.Length)) {
  $res = @()
  $read = Read-Bytes $mbi
  $res += $read
  while (($adr = [Int32]$read.BaseAddress + [Int32]$read.RegionSize) -lt $max.ToInt32()) {
    $mbi = New-Object Byte[] 28
    if (!$VirtualQueryEx.Invoke($ptr, [IntPtr]$adr, $mbi, $mbi.Length)) { break }
    $read = Read-Bytes $mbi
    $res += $read
  }
  
  $res | Format-Table -AutoSize
}

Что из себя представляет функция Read-Bytes? По идее третьим параметром VirtualQueryEx является указатель на структуру MEMORY_BASIC_INFORMATION, но так как обобщенные делегаты не поддерживают refout, мы пошли на небольшую хитрость, записывая данные в массив байтов. Read-Bytes приводит данные из массива к простому для восприятия виду.

function private:Read-Bytes([Byte[]]$bytes) {
  New-Object PSObject -Property @{
    BaseAddress = [BitConverter]::ToUInt32($bytes[0..3], 0)
    AllocationBase = [BitConverter]::ToUInt32($bytes[4..7], 0)
    AllocationProtect = (
      ($MEM_PROTECT.Keys | ForEach-Object {
        $$ = [BitConverter]::ToUint32($bytes[8..11], 0)
      }{
        if (($$ -band $MEM_PROTECT[$_]) -eq $MEM_PROTECT[$_]) {$_}
      }) -join ', '
    )
    RegionSize = [BitConverter]::ToUInt32($bytes[12..15], 0)
    State = (
      ($MEM_STATE.Keys | ForEach-Object {
        $$ = [BitConverter]::ToUInt32($bytes[16..19], 0)
      }{
        if (($$ -band $MEM_STATE[$_]) -eq $MEM_STATE[$_]) {$_}
      }) -join ', '
    )
    Protect = (
      ($MEM_PROTECT.Keys | ForEach-Object {
        $$ = [BitConverter]::ToUInt32($bytes[20..23], 0)
      }{
        if (($$ -band $MEM_PROTECT[$_]) -eq $MEM_PROTECT[$_]) {$_}
      }) -join ', '
    )
    Type = (
      ($MEM_TYPE.Keys | ForEach-Object {
        $$ = [BitConverter]::ToUInt32($bytes[24..27], 0)
      }{
        if (($$ -band $MEM_TYPE[$_]) -eq $MEM_TYPE[$_]) {$_}
      }) -join ', '
    )
  } | Select-Object @{N='BaseAddress';E={
    '0x{0:X8}' -f $_.BaseAddress
  }}, @{N='AllocationBase';E={
    '0x{0:X8}' -f $_.AllocationBase
  }}, AllocationProtect, @{N='RegionSize';E={
    '0x{0:X8}' -f $_.RegionSize
  }}, State, Protect, Type
}

Где $MEM_PROTECT, $MEM_STATE и $MEM_TYPE:

$MEM_PROTECT = @{
  PAGE_NOACCESS          = 0x00000001
  PAGE_READONLY          = 0x00000002
  PAGE_READWRITE         = 0x00000004
  PAGE_WRITECOPY         = 0x00000008
  PAGE_EXECUTE           = 0x00000010
  PAGE_EXECUTE_READ      = 0x00000020
  PAGE_EXECUTE_READWRITE = 0x00000040
  PAGE_EXECUTE_WRITECOPY = 0x00000080
  PAGE_GUARD             = 0x00000100
  PAGE_NOCACHE           = 0x00000200
  PAGE_WRITECOMBINE      = 0x00000400
}

$MEM_STATE = @{
  MEM_COMMIT  = 0x00001000
  MEM_RESERVE = 0x00002000
  MEM_FREE    = 0x00010000
}

$MEM_TYPE = @{
  MEM_PRIVATE = 0x00020000
  MEM_MAPPED  = 0x00040000
  MEM_IMAGE   = 0x01000000
}

Пример того, как будет выглядеть вывод для некоторого процесса:

BaseAddress AllocationBase AllocationProtect      RegionSize State       Protect                    Type
----------- -------------- -----------------      ---------- -----       -------                    ----
0x00000000  0x00000000                            0x00010000 MEM_FREE    PAGE_NOACCESS
0x00010000  0x00010000     PAGE_READWRITE         0x00001000 MEM_COMMIT  PAGE_READWRITE             MEM_PRIVATE
0x00011000  0x00000000                            0x0000F000 MEM_FREE    PAGE_NOACCESS
0x00020000  0x00020000     PAGE_READWRITE         0x00001000 MEM_COMMIT  PAGE_READWRITE             MEM_PRIVATE
0x00021000  0x00000000                            0x0000F000 MEM_FREE    PAGE_NOACCESS
0x00030000  0x00030000     PAGE_READWRITE         0x000F2000 MEM_RESERVE                            MEM_PRIVATE
0x00122000  0x00030000     PAGE_READWRITE         0x00001000 MEM_COMMIT  PAGE_READWRITE, PAGE_GUARD MEM_PRIVATE
0x00123000  0x00030000     PAGE_READWRITE         0x0000D000 MEM_COMMIT  PAGE_READWRITE             MEM_PRIVATE
...


Если не пихать предварительно данные в итоговый массив для последующего форматирования, сценарий будет работать, понятное дело, быстрее. Еще быстрее — если написать командлет целиком на C#, чай, отправная точка есть, так что дело за малым.

Автор: GrigoriZakharov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js